1 <?php
2
3 mb_internal_encoding("UTF-8");
4 echo mb_internal_encoding();
5
6 class RealtyXMLImportTask extends ScheduledTask {
7
8 private static $baseFileURL = 'assets/';
9 private static $baseImageURL = 'importedImages';
10 private static $max_log_count = 30;
11
12
13
14
15 private static $houseTypeID = array(
16 "не выбрано" => 0,
17 "кирпичный" => 10,
18 "панельный" => 11,
19 "блочный" => 12,
20 "монолит" => 13,
21 "деревянный" => 14,
22
23 );
24
25 private static $roomsCount = array(
26
27 );
28
29 static function absolute_import_path($filename) {
30 return BASE_PATH . '/' . self::$baseFileURL . preg_replace('!^/!', '', $filename);
31 }
32
33 static function absolute_image_path($filename) {
34 return BASE_PATH . '/' . self::$baseFileURL . self::$baseImageURL . '/' . preg_replace('!^/!', '', $filename);
35 }
36
37 static function relative_image_path($filename) {
38 return self::$baseFileURL . self::$baseImageURL . '/' . preg_replace('!^/!', '', $filename);
39 }
40
41 public static function file_path($filename) {
42 return self::base_dir() . '/' . $filename;
43 }
44
45 private static function base_dir() {
46 return TEMP_FOLDER . '/import';
47 }
48
49
50
51 function init() {
52 parent::init();
53
54
55
56
57
58
59
60 if (!is_dir(self::base_dir())) {
61 mkdir(self::base_dir(), 0775);
62 }
63 }
64
65 function turnOffLogging(){
66 if (class_exists('LogItem')) {
67 LogItem::enable(false);
68 }
69 }
70
71 function getFeedFromAdmin(){
72 $sc = SiteConfig::current_site_config();
73 $URL = $sc->YRLFeed;
74 if(!$URL){
75 die('Не указана ссылка на фид');
76 }
77 return $URL;
78 }
79
80 function getAndValidateFeed($URL){
81 $result = file_get_contents($URL);
82 libxml_use_internal_errors(true);
83 if (!($importData = simplexml_load_string($result))) {
84 foreach(libxml_get_errors() as $error) {
85 echo "\t", $error->message;
86 }
87
88 die('Неверный файл xml выгрузки!');
89 }
90
91 return $importData;
92 }
93
94 function process(){
95 $pidFile = TEMP_FOLDER . '/import.pid';
96 if (is_file($pidFile)) {
97 $pid = (int) file_get_contents($pidFile);
98 if (posix_getsid($pid) !== false) {
99
100 echo "already running";
101 return;
102 }
103
104
105
106
107
108
109
110
111
112 }
113 file_put_contents($pidFile, getmypid());
114
115 if(!is_dir(self::absolute_image_path(''))){
116 mkdir(self::absolute_image_path(''), 0775, true);
117 }
118
119 $this->turnOffLogging();
120 RealtyImportLog::optimize_log_tables(self::$max_log_count);
121 $URL = $this->getFeedFromAdmin();
122 $importData = $this->getAndValidateFeed($URL);
123
124 $this->importLog = new RealtyImportLog();
125 $this->importLog->write();
126 $startTime = time();
127 $this->importLog->addLog( "Импорт начался в " . date("Y-m-d H:i:s", $startTime));
128 $this->importLog->addStatus('process', 'Импорт в процессе');
129
130
131
132
133
134
135 $offersList = array(0);
136
137 $offers = $importData->offer;
138 foreach ($offers as $offer) {
139
140 $importType = $this->getOfferCategory($offer);
141
142 if((!$importType && $offer->{'lot-area'}) || $importType == 'house'){
143 $newFlat = $this->createHouse($offer);
144 }elseif($importType == 'flat'){
145
146 $newFlat = $this->createFlat($offer);
147 }elseif($importType == 'land'){
148 $newFlat = $this->createLand($offer);
149 }elseif($importType == 'commercial'){
150 $newFlat = $this->createCommercial($offer);
151 }else{
152 continue;
153 }
154
155 $offersList[] = $newFlat->ImportID;
156
157
158 $this->createItemPhotos($newFlat, $offer);
159 }
160
161
162
163 $oldOffers = Versioned::get_by_stage('BaseObject', 'Live', '"ImportID" not in ('. implode(',', $offersList) .')');
164
165 if(!$oldOffers){
166 $oldOffers = Versioned::get_by_stage('BaseObject', 'Stage', '"ImportID" not in ('. implode(',', $offersList) .')');
167 }
168 foreach ($oldOffers as $oldOffer) {
169 $oldOffer->doUnpublish();
170 $oldOffer->delete();
171 }
172 $this->importLog->EndTime = date('Y-m-d H:i:s');
173 $this->importLog->addStatus('ok', 'Импорт завершен');
174 $this->importLog->addLog('Импорт завершен в ' . date("Y-m-d H:i:s"));
175 unlink($pidFile);
176 }
177
178 private function getOfferCategory($offer){
179 $category = (string) $offer->category;
180 $countryHouse = array(
181 'дача',
182 'коттедж',
183 'cottage',
184 'дом',
185 'house',
186 'дом с участком',
187 'house with lot',
188 'часть дома',
189 'таунхаус',
190 'townhouse'
191 );
192 $plot = array(
193 'участок',
194 'lot'
195 );
196 $apartment = array(
197 'квартира',
198 'flat',
199 'комната',
200 'room'
201 );
202 $commercial = array(
203 'коммерческая',
204 'commercial'
205 );
206
207 if(in_array((string) $category, $countryHouse)){
208 $importType = "house";
209 }elseif(in_array((string) $category, $plot)) {
210 $importType = "land";
211 }elseif(in_array((string) $category, $apartment)){
212 $importType = "flat";
213 }elseif(in_array((string) $category, $commercial)){
214 $importType = "commercial";
215 }else{
216 $importType = false;
217 }
218
219 return $importType;
220 }
221
222 function createOrGetRegion($location){
223
224
225
226
227
228
229
230
231
232 $id = null;
233 if(!$location){
234 return null;
235 }
236
237 if($location->region){
238 $title = (string)$location->region;
239 if(!$region = DataObject::get_one('EstateRegion_Main', "Title = '{$title}'")){
240 $region = new EstateRegion_Main();
241 $region->Title = (string)$location->region;
242 $region->write();
243 }
244 $id = $region->ID;
245 if($location->district){
246 $title = (string)$location->district;
247 if(!$district = DataObject::get_one('EstateRegion_Sub', "Title = '{$title}'")){
248 $district = new EstateRegion_Sub();
249 $district->Title = (string)$location->district;
250 $district->ParentID = $region->ID;
251 $district->write();
252 }
253 $id = $district->ID;
254 if($location->{'locality-name'}){
255 $title = (string)$location->{'locality-name'};
256 if(!$locality = DataObject::get_one('EstateRegion_Sub', "Title = '{$title}'")){
257 $locality = new EstateRegion_Sub();
258 $locality->Title = (string)$location->{'locality-name'};
259 $locality->ParentID = $district->ID;
260 $locality->write();
261 }
262 $id = $locality->ID;
263 if($location->{'sub-locality-name'}){
264 $title = (string)$location->{'sub-locality-name'};
265 if(!$subLocality = DataObject::get_one('EstateRegion_Sub', "Title = '{$title}'")){
266 $subLocality = new EstateRegion_Sub();
267 $subLocality->Title = (string)$location->{'sub-locality-name'};
268 $subLocality->ParentID = $locality->ID;
269 $subLocality->write();
270 }
271 $id = $subLocality->ID;
272 }
273 }
274 }
275 }
276
277 return $id;
278 }
279
280 function createFlat($offer){
281 $offerID = $offer->attributes()->{'internal-id'};
282
283
284
285 $type = $offer->type;
286 if($type == 'продажа'){
287 $flat = DataObject::get_one('Flat', "ImportID = $offerID");
288 if($flat){
289 $newFlat = $flat;
290 }else{
291 $newFlat = new Flat();
292 }
293 }else{
294 $flat = DataObject::get_one('FlatLease', "ImportID = $offerID");
295 if($flat){
296 $newFlat = $flat;
297 }else{
298 $newFlat = new FlatLease();
299 }
300 }
301
302
303
304
305
306
307
308 if(!empty($offer->location->address)){
309 $title = array();
310 if(!empty($offer->location->{'locality-name'})){
311 $title[] = (string)$offer->location->{'locality-name'};
312 }
313 $title[] = (string)$offer->location->address;
314 $newFlat->Title = implode(',', $title);
315 }else if(!empty($offer->location->{'locality-name'})){
316 $newFlat->Title = (string) $offer->location->{'locality-name'};
317 }else if(!empty($offer->location->district)){
318 $newFlat->Title = (string) $offer->location->district;
319 }else if(!empty($offer->location->region)){
320 $newFlat->Title = (string) $offer->location->region;
321 }else{
322 $newFlat->Title = (string) md5(uniqid(rand(), true));
323 }
324
325
326
327 $newFlat->RegionID = $this->createOrGetRegion($offer->location);
328 $newFlat->ImportID = (int) $offerID;
329 $newFlat->Content = (string) $offer->description;
330
331 $newFlat->ManualLatitude = ($offer->latitude) ? $offer->latitude : 0;
332 $newFlat->ManualLongtitude = ($offer->longitude) ? $offer->longitude : 0;
333
334 $newFlat->TotalRoomsCount = (int) $offer->rooms;
335 $roomsSwitch = (int) $offer->rooms;
336 switch (true) {
337 case $roomsSwitch == 1:
338 $FlatTypeID = 16;
339 break;
340
341 case $roomsSwitch == 2:
342 $FlatTypeID = 17;
343 break;
344
345 case $roomsSwitch == 3:
346 $FlatTypeID = 18;
347 break;
348
349 case $roomsSwitch == 4:
350 $FlatTypeID = 19;
351 break;
352
353 case $roomsSwitch > 4:
354 $FlatTypeID = 20;
355 break;
356
357 default:
358
359
360
361
362 if($offer->studio){
363 $FlatTypeID = 45;
364 break;
365 }
366 $FlatTypeID = 0;
367 break;
368 }
369 $newFlat->FlatTypeID = $FlatTypeID;
370
371
372
373 $newFlat->Floor = (int) $offer->floor;
374 $newFlat->FloorCount = (int) $offer->{'floors-total'};
375
376 $newFlat->Square = (float) $offer->area->value;
377 $newFlat->KitchenSquare = (float) $offer->{'kitchen-space'}->value;
378 $newFlat->LivingSquare = (float) $offer->{'living-space'}->value;
379
380 $newFlat->Price = (int) $offer->price->value;
381 $newFlat->HouseTypeID = (int) self::$houseTypeID[mb_strtolower($offer->{'building-type'})];
382
383 $newFlat->Address = (string) $offer->location->address;
384 $newFlat->Locality = (string) $offer->location->{'sub-locality-name'};
385
386 $newFlat->Active = 1;
387
388 $salesAgent = $offer->{'sales-agent'};
389
390 $agentCategories = array(
391 'agency' => 'agency',
392 'owner' => 'owner',
393 'developer' => 'developer',
394 'агентство' => 'agency',
395 'владелец' => 'owner',
396 'застройщик' => 'developer',
397 );
398
399 if($salesAgent){
400 $newFlat->agentName = (string)$salesAgent->name;
401
402 $agentCategory = (string)$salesAgent->category;
403 if($agentCategory){
404 $agentCategory = $agentCategories[mb_strtolower($agentCategory)];
405 }else{
406 $agentCategory = "unspecified";
407 }
408 $newFlat->agentCategory = $agentCategory;
409
410
411 $tmpPhones = array();
412 foreach($salesAgent->phone as $value){
413 $tmpPhones[] = (string)$value;
414 }
415 $newFlat->agentPhone = implode(',', $tmpPhones);
416 }
417
418
419 $newHouseViews = array(
420 'первичная продажа',
421 'продажа от застройщика',
422 'reassignment',
423 'переуступка'
424 );
425
426 $secondaryHouseViews = array(
427 'прямая продажа',
428 'первичная продажа вторички',
429 'встречная продажа',
430 '«sale»',
431 'primary sale of secondary',
432 '«countersale»'
433 );
434 $houseView = '';
435 if(in_array(mb_strtolower($offer->{'deal-status'}), $newHouseViews)){
436 $houseView = 'new';
437 $newHouse = DataObject::get_one('FlatCategory', "Title = 'Новостройки'");
438 if($newHouse){
439 $newFlat->ParentID = $newHouse->ID;
440 }
441 }elseif (in_array(mb_strtolower($offer->{'deal-status'}), $secondaryHouseViews)) {
442 $houseView = 'secondary';
443 }
444
445
446
447 $newFlat->HouseView = $houseView;
448 $newFlat->writeToStage('Stage');
449 $newFlat->publish('Stage', 'Live');
450
451 return $newFlat;
452 }
453
454 function createCommercial($offer){
455 $offerID = $offer->attributes()->{'internal-id'};
456
457
458 $type = $offer->type;
459 if($type == 'продажа'){
460 $flat = DataObject::get_one('CommercialEstate', "ImportID = $offerID");
461 if($flat){
462 $newFlat = $flat;
463 }else{
464 $newFlat = new CommercialEstate();
465 }
466 }else{
467 $flat = DataObject::get_one('CommercialEstateLease', "ImportID = $offerID");
468 if($flat){
469 $newFlat = $flat;
470 }else{
471 $newFlat = new CommercialEstateLease();
472 }
473 }
474
475
476
477
478 if(!empty($offer->location->address)){
479 $newFlat->Title = (string) $offer->location->address;
480 }else{
481 $newFlat->Title = (string) md5(uniqid(rand(), true));
482 }
483 $newFlat->RegionID = (int)$this->createOrGetRegion($offer->location);
484 $newFlat->ImportID = (int) $offerID;
485 $newFlat->Content = (string) $offer->description;
486
487 $newFlat->Square = (float) $offer->area->value;
488
489 $newFlat->Price = (int) $offer->price->value;
490
491 $newFlat->Address = (string) $offer->location->address;
492 $newFlat->Locality = (string) $offer->location->{'sub-locality-name'};
493
494
495 $newFlat->Active = 1;
496
497 $salesAgent = $offer->{'sales-agent'};
498
499 $agentCategories = array(
500 'agency' => 'agency',
501 'owner' => 'owner',
502 'developer' => 'developer',
503 'агентство' => 'agency',
504 'владелец' => 'owner',
505 'застройщик' => 'developer',
506 );
507
508 if($salesAgent){
509 $newFlat->agentName = (string)$salesAgent->name;
510
511 $agentCategory = (string)$salesAgent->category;
512 if($agentCategory){
513 $agentCategory = $agentCategories[mb_strtolower($agentCategory)];
514 }else{
515 $agentCategory = "unspecified";
516 }
517 $newFlat->agentCategory = $agentCategory;
518
519
520 $tmpPhones = array();
521 foreach($salesAgent->phone as $value){
522 $tmpPhones[] = (string)$value;
523 }
524 $newFlat->agentPhone = implode(',', $tmpPhones);
525 }
526
527 $commercialType = (string)$offer->{'commercial-type'};
528 $res = DataObject::get_one('CommercialEstateType', "YaRealtyTitle = '{$commercialType}'");
529 if($res){
530 $commercialTypeID = $res->ID;
531 }else{
532 $commercialTypeID = 0;
533 }
534
535 $newFlat->CommercialTypeID = $commercialTypeID;
536 $newFlat->writeToStage('Stage');
537 $newFlat->publish('Stage', 'Live');
538
539 return $newFlat;
540 }
541
542 function createHouse($offer){
543 $offerID = $offer->attributes()->{'internal-id'};
544 $flat = DataObject::get_one('House', "ImportID = $offerID");
545 if($flat){
546 $newFlat = $flat;
547 }else{
548 $newFlat = new House();
549 }
550
551
552 $type = $offer->type;
553
554
555
556
557
558
559
560 if(!empty($offer->location->address)){
561 $title = array();
562 if(!empty($offer->location->{'locality-name'})){
563 $title[] = (string)$offer->location->{'locality-name'};
564 }
565 $title[] = (string)$offer->location->address;
566 $newFlat->Title = implode(',', $title);
567 }else if(!empty($offer->location->{'locality-name'})){
568 $newFlat->Title = (string) $offer->location->{'locality-name'};
569 }else if(!empty($offer->location->district)){
570 $newFlat->Title = (string) $offer->location->district;
571 }else if(!empty($offer->location->region)){
572 $newFlat->Title = (string) $offer->location->region;
573 }else{
574 $newFlat->Title = (string) md5(uniqid(rand(), true));
575 }
576
577
578
579 $newFlat->RegionID = $this->createOrGetRegion($offer->location);
580
581 $newFlat->ImportID = (int) $offerID;
582 $newFlat->Content = (string) $offer->description;
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617 $newFlat->Square = (float) $offer->area->value;
618 $newFlat->LandSquare = (float) $offer->{'lot-area'}->value;
619
620 $newFlat->Price = (int) $offer->price->value;
621
622 $newFlat->Address = (string) $offer->location->address;
623 $newFlat->Locality = (string) $offer->location->{'sub-locality-name'};
624
625
626 $newFlat->Active = 1;
627
628 $salesAgent = $offer->{'sales-agent'};
629
630 $agentCategories = array(
631 'agency' => 'agency',
632 'owner' => 'owner',
633 'developer' => 'developer',
634 'агентство' => 'agency',
635 'владелец' => 'owner',
636 'застройщик' => 'developer',
637 );
638
639 if($salesAgent){
640 $newFlat->agentName = (string)$salesAgent->name;
641
642 $agentCategory = (string)$salesAgent->category;
643 if($agentCategory){
644 $agentCategory = $agentCategories[mb_strtolower($agentCategory)];
645 }else{
646 $agentCategory = "unspecified";
647 }
648 $newFlat->agentCategory = $agentCategory;
649
650
651 $tmpPhones = array();
652 foreach($salesAgent->phone as $value){
653 $tmpPhones[] = (string)$value;
654 }
655 $newFlat->agentPhone = implode(',', $tmpPhones);
656 }
657
658
659 $newHouseViews = array(
660 'первичная продажа',
661 'продажа от застройщика',
662 'reassignment',
663 'переуступка'
664 );
665
666 $secondaryHouseViews = array(
667 'прямая продажа',
668 'первичная продажа вторички',
669 'встречная продажа',
670 '«sale»',
671 'primary sale of secondary',
672 '«countersale»'
673 );
674 $houseView = NULL;
675 if(in_array(mb_strtolower($offer->{'deal-status'}), $newHouseViews)){
676 $houseView = 'new';
677 }elseif (in_array(mb_strtolower($offer->{'deal-status'}), $secondaryHouseViews)) {
678 $houseView = 'secondary';
679 }
680
681
682
683
684
685
686 $newFlat->HouseView = $houseView;
687 $newFlat->writeToStage('Stage');
688 $newFlat->publish('Stage', 'Live');
689
690 return $newFlat;
691 }
692
693 function createLand($offer){
694 $offerID = $offer->attributes()->{'internal-id'};
695 $flat = DataObject::get_one('Land', "ImportID = $offerID");
696 if($flat){
697 $newFlat = $flat;
698 }else{
699 $newFlat = new Land();
700 }
701
702
703 $type = $offer->type;
704
705
706
707
708
709
710 if(!empty($offer->location->address)){
711 $title = array();
712 if(!empty($offer->location->{'locality-name'})){
713 $title[] = (string)$offer->location->{'locality-name'};
714 }
715 $title[] = (string)$offer->location->address;
716 $newFlat->Title = implode(',', $title);
717 }else if(!empty($offer->location->{'locality-name'})){
718 $newFlat->Title = (string) $offer->location->{'locality-name'};
719 }else if(!empty($offer->location->district)){
720 $newFlat->Title = (string) $offer->location->district;
721 }else if(!empty($offer->location->region)){
722 $newFlat->Title = (string) $offer->location->region;
723 }else{
724 $newFlat->Title = (string) md5(uniqid(rand(), true));
725 }
726
727
728
729 $newFlat->RegionID = $this->createOrGetRegion($offer->location);
730
731 $newFlat->ImportID = (int) $offerID;
732 $newFlat->Content = (string) $offer->description;
733
734
735 $newFlat->Square = (float) $offer->{'lot-area'}->value;
736
737
738 $newFlat->Price = (int) $offer->price->value;
739
740 $newFlat->Address = (string) $offer->location->address;
741 $newFlat->Locality = (string) $offer->location->{'sub-locality-name'};
742
743
744 $newFlat->Active = 1;
745
746 $salesAgent = $offer->{'sales-agent'};
747
748 $agentCategories = array(
749 'agency' => 'agency',
750 'owner' => 'owner',
751 'developer' => 'developer',
752 'агентство' => 'agency',
753 'владелец' => 'owner',
754 'застройщик' => 'developer',
755 );
756
757 if($salesAgent){
758 $newFlat->agentName = (string)$salesAgent->name;
759
760 $agentCategory = (string)$salesAgent->category;
761 if($agentCategory){
762 $agentCategory = $agentCategories[mb_strtolower($agentCategory)];
763 }else{
764 $agentCategory = "unspecified";
765 }
766 $newFlat->agentCategory = $agentCategory;
767
768
769 $tmpPhones = array();
770 foreach($salesAgent->phone as $value){
771 $tmpPhones[] = (string)$value;
772 }
773 $newFlat->agentPhone = implode(',', $tmpPhones);
774 }
775
776
777 $newHouseViews = array(
778 'первичная продажа',
779 'продажа от застройщика',
780 'reassignment',
781 'переуступка'
782 );
783
784 $secondaryHouseViews = array(
785 'прямая продажа',
786 'первичная продажа вторички',
787 'встречная продажа',
788 '«sale»',
789 'primary sale of secondary',
790 '«countersale»'
791 );
792 $houseView = NULL;
793 if(in_array(mb_strtolower($offer->{'deal-status'}), $newHouseViews)){
794 $houseView = 'new';
795 }elseif (in_array(mb_strtolower($offer->{'deal-status'}), $secondaryHouseViews)) {
796 $houseView = 'secondary';
797 }
798
799 $newFlat->HouseView = $houseView;
800 $newFlat->writeToStage('Stage');
801 $newFlat->publish('Stage', 'Live');
802
803 return $newFlat;
804 }
805
806 function createItemPhotos($item, $offer){
807 $newFlat = $item;
808
809 $oldImagesList = $newFlat->Photos();
810 $oldImagesListIDs = $oldImagesList->getIdList();
811
812 $images = $offer->image;
813 $mainImage = true;
814 foreach ($images as $image) {
815 $image = (string) $image;
816 $fileName = array_pop(explode('/',$image));
817 $relativeFilePath = self::relative_image_path($fileName);
818
819
820
821
822
823
824
825 $imageFile = DataObject::get_one('File', "\"Filename\" = '".Convert::raw2sql($relativeFilePath)."' and \"ParentID\" = {$newFlat->ID}");
826 if(!$imageFile){
827
828 $imageFile = new Image();
829 $imageData = file_get_contents($image);
830 file_put_contents(self::absolute_image_path($fileName), $imageData);
831 $imageFile->ClassName = 'Image';
832 $imageFile->OwnerID = 0;
833 $imageFile->ParentID = $newFlat->ID;
834 $imageFile->Name = $fileName;
835 $imageFile->Title = $fileName;
836 $imageFile->Filename = $relativeFilePath;
837 $imageFile->write();
838 }
839
840 if($mainImage){
841 $newFlat->ImageID = $imageFile->ID;
842 $newFlat->writeToStage('Stage');
843 $newFlat->publish('Stage', 'Live');
844 $mainImage = false;
845 }
846
847
848
849
850
851 if ($mwPhoto = $oldImagesList->find('PhotoID', $imageFile->ID)) {
852 unset($oldImagesListIDs[$mwPhoto->ID]);
853 } else {
854 $mwPhoto = new MediawebPage_Photo();
855 $mwPhoto->PhotoID = $importedImage->ID;
856 }
857
858
859 $mwPhoto->MediawebPageID = $newFlat->ID;
860 $mwPhoto->PhotoID = $imageFile->ID;
861 $mwPhoto->Caption = $fileName;
862 $mwPhoto->write();
863
864 }
865 if (count($oldImagesListIDs) > 0) {
866 foreach($oldImagesListIDs as $oldPhotosID) {
867 if ($oldPhoto = DataObject::get_by_id('MediawebPage_Photo', $oldPhotosID)) {
868 $oldPhotoID = $oldPhoto->PhotoID;
869 $oldPhoto->delete();
870
871
872
873
874 }
875 }
876 }
877 }
878
879
880
881
882
883 }
[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.
-