Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
export.php
1<?php
3
13
15{
17 protected $module = '';
18
20 const EXPORT_PATH = '/export/';
21
23 const EXPIRE_DAYS = 24;
24
25 const ACTION_EXPORT = 'export';
26 const ACTION_UPLOAD = 'upload';
27 const ACTION_CANCEL = 'cancel';
28 const ACTION_FINISH = 'finish';
29 const ACTION_DOWNLOAD = 'download';
30 const ACTION_CLEAR = 'clear';
31 const ACTION_VOID = 'void';
32 const ACTION_PURGE = 'purge';
33
34 const STATUS_COMPLETED = 'COMPLETED';
35 const STATUS_PROGRESS = 'PROGRESS';
36
37
39 protected $componentName = '';
40
42 protected $componentParameters = [];
43
45 protected $exportType;
46
47 const EXPORT_TYPE_CSV = 'csv';
48 const EXPORT_TYPE_EXCEL = 'excel';
49
51 protected $processToken;
52
54 protected $isNewProcess = true;
55
57 protected $fileName = '';
58
60 protected $filePath = '';
61
63 protected $fileSize = 0;
64
66 protected $fileType = 'application/csv';
67
69 const KEEP_FILE_HOURS = 24;
70
72 protected $processedItems = 0;
73
75 protected $totalItems = 0;
76
78 protected $pageSize = 0;
79
81 const ROWS_PER_PAGE = 100;
82
84 protected $lastExportedId = -1;
85
87 protected $bucket;
88
90 protected $bucketId = -1;
91
93 protected $uploadPath = '';
94
96 protected $uploadedSize = 0;
97
99 protected $uploadedPart = 0;
100
102 protected $cloudMinChunkSize = 5 * 1024 * 1024; // 5M
103
105 protected $fieldToStoreInProcess = array(
106 'exportType',
107 'fileName',
108 'filePath',
109 'fileSize',
110 'fileType',
111 'uploadedSize',
112 'uploadPath',
113 'uploadedPart',
114 'pageSize',
115 'processedItems',
116 'totalItems',
117 'lastExportedId',
118 'cloudChunkSize',
119 'bucketId',
120 'isExportCompleted',
121 'isUploadFinished',
122 'timeStart',
123 'stepCount',
124 'useCloud',
125 );
126
128 protected $isBitrix24 = false;
129
131 protected $isCloudAvailable = false;
132
134 protected $isExportCompleted = false;
135
137 protected $isUploadFinished = false;
138
140 protected $timeStart = 0;
141
143 protected $stepCount = 0;
144
146 protected $useCloud = null;
147
149 protected $timeLimit = -1;
150
152 protected $hitStartTime = -1;
153
155 protected $timeLimitReached = false;
156
158 const TIME_LIMIT = 20; // 20 seconds
159
164 public function configureActions()
165 {
166 $configureActions = parent::configureActions();
167 $configureActions[self::ACTION_DOWNLOAD] = array(
168 '-prefilters' => array(
169 Main\Engine\ActionFilter\Csrf::class
170 )
171 );
172
173 return $configureActions;
174 }
175
181 protected function init()
182 {
183 parent::init();
184
185 Loc::loadMessages(__FILE__);
186
187 $this->isBitrix24 = Main\ModuleManager::isModuleInstalled('bitrix24');
188
189 $this->isCloudAvailable =
190 Main\ModuleManager::isModuleInstalled('clouds') &&
191 Main\Loader::includeModule('clouds');
192
193 $this->processToken = $this->request->get('PROCESS_TOKEN');
194 $this->exportType = $this->request->get('EXPORT_TYPE');
195 $this->componentName = $this->request->get('COMPONENT_NAME');
196
197 $signedParameters = $this->request->getPost('signedParameters');
198 if (!empty($signedParameters))
199 {
200 $this->componentParameters = $this->decodeSignedParameters($this->componentName, $signedParameters);
201 }
202
203 $initialOptions = $this->request->getPost('INITIAL_OPTIONS');
204 if (!empty($initialOptions))
205 {
206 $this->componentParameters['STEXPORT_INITIAL_OPTIONS'] = $initialOptions;
207 }
208
209 $progressData = $this->getProgressParameters();
210 if (!empty($progressData))
211 {
212 $this->isNewProcess = (empty($progressData['processToken']) || $progressData['processToken'] !== $this->processToken);
213 if (!$this->isNewProcess)
214 {
215 // restore state
216 foreach ($this->fieldToStoreInProcess as $fieldName)
217 {
218 if (isset($progressData[$fieldName]))
219 {
220 $this->{$fieldName} = $progressData[$fieldName];
221 }
222 }
223 }
224 }
225
226 if ($this->isCloudAvailable)
227 {
228 $bucketList = $this->getBucketList();
229 if (empty($bucketList))
230 {
231 $this->isCloudAvailable = false;
232 }
233 }
234 }
235
236
242 protected function processBeforeAction(Main\Engine\Action $action)
243 {
244 if (parent::processBeforeAction($action))
245 {
246 if (!$this->checkCommonErrors($action))
247 {
248 return false;
249 }
250 }
251
252 return true;
253 }
254
262 protected function keepFieldInProcess($fieldName)
263 {
264 $this->fieldToStoreInProcess[] = $fieldName;
265
266 return $this;
267 }
268
272 public function dispatcherAction()
273 {
274 // can we use cloud
275 if ($this->isCloudAvailable && $this->useCloud === null)
276 {
277 if (
278 ($this->bucketId < 0) &&
279 ($this->totalItems > 0) &&
280 ($this->processedItems > 0) &&
281 ($this->fileName !== '') &&
282 ($this->getSizeTempFile() > 0) &&
283 (($this->getSizeTempFile() > $this->cloudMinChunkSize) || $this->isExportCompleted)
284 )
285 {
286 if ($this->isExportCompleted)
287 {
288 $predictedFileSize = $this->getSizeTempFile();
289 }
290 else
291 {
292 $predictedFileSize = round($this->getSizeTempFile() * ceil($this->totalItems / $this->processedItems) * 1.5);
293 }
294 // check cloud bucket rules
295 if ($this->findInitBucket(array('fileSize' => $predictedFileSize, 'fileName' => $this->fileName)) === true)
296 {
297 // allow
298 $this->useCloud = true;
299 }
300 else
301 {
302 // deny
303 $this->useCloud = false;
304 $this->isCloudAvailable = false;
305 }
306 }
307 }
308
309 if ($this->isNewProcess)
310 {
311 // start export
312 $this->stepCount = 0;
313 $this->timeStart = time();
314 $nextAction = self::ACTION_EXPORT;
315 }
316 elseif ($this->isBitrix24)
317 {
318 if ($this->isExportCompleted && !$this->isUploadFinished)
319 {
320 // upload last chunk
321 $nextAction = self::ACTION_UPLOAD;
322 }
323 elseif ($this->isExportCompleted && $this->isUploadFinished)
324 {
325 // purge cloud and finish
326 $nextAction = self::ACTION_PURGE;
327 }
328 elseif ($this->getSizeTempFile() > $this->cloudMinChunkSize)
329 {
330 // upload chunk
331 $nextAction = self::ACTION_UPLOAD;
332 }
333 else
334 {
335 // continue export into temporally file
336 $nextAction = self::ACTION_EXPORT;
337 }
338 }
339 else
340 {
341 // non b24
342 if ($this->isExportCompleted && ($this->useCloud === true) && !$this->isUploadFinished)
343 {
344 // upload last chunk
345 $nextAction = self::ACTION_UPLOAD;
346 }
347 elseif ($this->isExportCompleted && ($this->useCloud === true) && $this->isUploadFinished)
348 {
349 // purge cloud and finish
350 $nextAction = self::ACTION_PURGE;
351 }
352 elseif ($this->isExportCompleted && ($this->useCloud === false || $this->useCloud === null))
353 {
354 // completed
355 $nextAction = self::ACTION_FINISH;
356 }
357 elseif (($this->useCloud === true) && ($this->getSizeTempFile() > $this->cloudMinChunkSize))
358 {
359 // upload chunk
360 $nextAction = self::ACTION_UPLOAD;
361 }
362 else
363 {
364 // continue export into temporally file
365 $nextAction = self::ACTION_EXPORT;
366 }
367 }
368
369 if ($nextAction === self::ACTION_PURGE)
370 {
371 return $this->purgeAction();
372 }
373 if ($nextAction === self::ACTION_EXPORT)
374 {
375 $this->stepCount ++;
376 return $this->exportAction();
377 }
378 if ($nextAction === self::ACTION_UPLOAD)
379 {
380 $this->stepCount ++;
381 return $this->uploadAction();
382 }
383 if ($nextAction === self::ACTION_FINISH)
384 {
385 return $this->finishAction();
386 }
387
388 return $this->cancelAction();
389 }
390
391
395 public function downloadAction()
396 {
397 if (
398 $this->filePath !== '' &&
399 $this->fileName !== ''
400 )
401 {
402 $path = new Main\IO\File($this->filePath);
403 if ($path->isExists())
404 {
405
406 $response = new Main\Engine\Response\File(
407 $path->getPath(),
408 $this->fileName,
409 $this->getFileType()
410 );
411
412 return $response;
413 }
414 }
415
416 $this->addError(new Error('File not found'));
417 }
418
419
425 public function exportAction()
426 {
428 global $APPLICATION;
429
430 if ($this->isNewProcess)
431 {
432 $this->fileType = $this->getFileType();
433 $this->fileName = $this->generateExportFileName();
434 $this->filePath = $this->generateTempDirPath(). $this->fileName;
435 $this->processedItems = 0;
436 $this->totalItems = 0;
437 $this->pageSize = self::ROWS_PER_PAGE;
438 $this->saveProgressParameters();
439 }
440
441 $this->startTimer(self::TIME_LIMIT);
442 do
443 {
444 $nextPage = (int)floor($this->processedItems / $this->pageSize) + 1;
445
446 $componentParameters = array_merge(
447 $this->componentParameters,
448 array(
449 'STEXPORT_MODE' => 'Y',
450 'EXPORT_TYPE' => $this->exportType,
451 'STEXPORT_PAGE_SIZE' => $this->pageSize,
452 'STEXPORT_TOTAL_ITEMS' => $this->totalItems,
453 'STEXPORT_LAST_EXPORTED_ID' => $this->lastExportedId,
454 'PAGE_NUMBER' => $nextPage,
455 )
456 );
457
458 ob_start();
459 $componentResult = $APPLICATION->IncludeComponent(
460 $this->componentName,
461 '',
463 );
464 $exportData = ob_get_contents();
465 ob_end_clean();
466
467 $processedItemsOnStep = 0;
468
469 if (is_array($componentResult))
470 {
471 if (isset($componentResult['ERROR']))
472 {
473 $this->addError(new Error($componentResult['ERROR']));
474 break;
475 }
476 else
477 {
478 if (isset($componentResult['PROCESSED_ITEMS']))
479 {
480 $processedItemsOnStep = (int)$componentResult['PROCESSED_ITEMS'];
481 }
482
483 // Get total items quantity on 1st step.
484 if ($nextPage === 1 && isset($componentResult['TOTAL_ITEMS']))
485 {
486 $this->totalItems = (int)$componentResult['TOTAL_ITEMS'];
487 }
488
489 if (isset($componentResult['LAST_EXPORTED_ID']))
490 {
491 $this->lastExportedId = (int)$componentResult['LAST_EXPORTED_ID'];
492 }
493 }
494 }
495
496 if ($this->totalItems == 0)
497 {
498 break;
499 }
500
501 if ($processedItemsOnStep > 0)
502 {
503 $this->processedItems += $processedItemsOnStep;
504
505 $this->writeTempFile($exportData, ($nextPage === 1));
506 unset($exportData);
507
508 $this->isExportCompleted = ($this->processedItems >= $this->totalItems);
509
510 if ($this->isExportCompleted && !$this->isCloudAvailable)
511 {
512 // adjust completed file size
513 $this->fileSize = $this->getSizeTempFile();
514 }
515 }
516 elseif ($processedItemsOnStep == 0)
517 {
518 // Smth went wrong - terminate process.
519 $this->isExportCompleted = true;
520
521 if (!$this->isCloudAvailable)
522 {
523 $this->fileSize = $this->getSizeTempFile();
524 }
525 }
526
527 if ($this->isExportCompleted === true)
528 {
529 // finish
530 break;
531 }
532 if ($nextPage === 1)
533 {
534 // to answer faster
535 break;
536 }
537 }
538 while ($this->hasTimeLimitReached() !== true);
539
540 if ($this->totalItems == 0)
541 {
542 $this->isExportCompleted = true;
543
544 // Save progress
545 $this->saveProgressParameters();
546
547 // finish
548 $result = $this->preformAnswer(self::ACTION_VOID);
549 $result['STATUS'] = self::STATUS_COMPLETED;
550 }
551 else
552 {
553 // Save progress
554 $this->saveProgressParameters();
555
556 $result = $this->preformAnswer(self::ACTION_EXPORT);
557 $result['STATUS'] = self::STATUS_PROGRESS;
558 }
559
560 return $result;
561 }
562
563
569 public function uploadAction()
570 {
571 $chunkSize = $this->getSizeTempFile();
572
573 $isUploadStarted = ($this->uploadedPart > 0);
574 if (!$isUploadStarted)
575 {
576 $reservedSize = round($chunkSize * ceil($this->totalItems / $this->pageSize) * 1.5);
577
578 $this->uploadPath = $this->generateUploadDir(). $this->fileName;
579
580 $this->findInitBucket(array(
581 'fileSize' => $reservedSize,
582 'fileName' => $this->uploadPath,
583 ));
584
585 if ($this->checkCloudErrors())
586 {
587 $this->saveProgressParameters();
588
589 if ($this->bucket->FileExists($this->uploadPath))
590 {
591 if(!$this->bucket->DeleteFile($this->uploadPath))
592 {
593 $this->addError(new Error('File exists in a cloud.'));
594 }
595 }
596 }
597 }
598 else
599 {
600 $this->instanceBucket();
601
602 $this->checkCloudErrors();
603 }
604
605
606 if (!file_exists($this->filePath))
607 {
608 $this->addError(new Error('Uploading file not exists.'));
609 }
610
611 if (!empty($this->getErrors()))
612 {
613 return AjaxJson::createError($this->errorCollection);
614 }
615
616
617 $isSuccess = false;
618
619 if ($this->isExportCompleted && !$isUploadStarted)
620 {
621 // just only one file
622 $uploadFile = array(
623 'name' => $this->fileName,
624 'size' => $this->fileSize,
625 'type' => $this->fileType,
626 'tmp_name' => $this->filePath,
627 );
628
629 if ($this->bucket->SaveFile($this->uploadPath, $uploadFile))
630 {
631 $this->uploadedPart ++;
632 $this->uploadedSize += $chunkSize;
633 $isSuccess = true;
634 $this->isUploadFinished = true;
635 }
636 else
637 {
638 $this->addError(new Error('Uploading error.'));
639 }
640 }
641 else
642 {
643 $uploader = new \CCloudStorageUpload($this->uploadPath);
644 if (!$uploader->isStarted())
645 {
646 if (!$uploader->Start($this->bucketId, $reservedSize, $this->fileType))
647 {
648 $this->addError(new Error('Start uploading error.'));
649
650 return AjaxJson::createError($this->errorCollection);
651 }
652 }
653
654 $part = $this->getContentTempFile();
655
656 while ($uploader->hasRetries())
657 {
658 if ($uploader->Next($part, $this->bucket))
659 {
660 $this->uploadedPart ++;
661 $this->uploadedSize += $chunkSize;
662 $isSuccess = true;
663 break;
664 }
665 }
666 unset($part);
667
668 // finish
669 if ($isSuccess && $this->isExportCompleted)
670 {
671 if ($uploader->Finish())
672 {
673 $this->isUploadFinished = true;
674 }
675 else
676 {
677 $this->addError(new Error('FILE_UNKNOWN_ERROR'));
678 }
679 }
680 }
681
682 if ($isSuccess)
683 {
684 $this->dropTempFile();
685 }
686
687 // Save progress
688 $this->saveProgressParameters();
689
690 // continue export into temporally file
691 $result = $this->preformAnswer(self::ACTION_UPLOAD);
692 $result['STATUS'] = self::STATUS_PROGRESS;
693
694 return $result;
695 }
696
697
703 public function clearAction()
704 {
705 $this->cancelAction();
706
707 $result = $this->preformAnswer(self::ACTION_CLEAR);
708
709 $result['STATUS'] = self::STATUS_COMPLETED;
710
711 return $result;
712
713 }
714
715
721 public function cancelAction()
722 {
723 $this->processToken = null;
724 $this->isNewProcess = true;
725
726 $this->dropTempFile();
727
728 $this->instanceBucket();
729
730 if ($this->bucket instanceof \CCloudStorageBucket)
731 {
732 if ($this->bucket->FileExists($this->uploadPath))
733 {
734 if (!$this->bucket->DeleteFile($this->uploadPath))
735 {
736 $this->addError(new Error('Cloud drop error.'));
737 }
738 }
739 }
740
742
743 $result = $this->preformAnswer(self::ACTION_CANCEL);
744
745 $result['STATUS'] = self::STATUS_COMPLETED;
746
747 return $result;
748 }
749
750
756 public function purgeAction()
757 {
758 $this->instanceBucket();
759
760 if ($this->bucket instanceof \CCloudStorageBucket)
761 {
762 $dirFiles = $this->bucket->ListFiles($this->generateUploadDir(), true);
763
764 $now = new \DateTime('now');
765 foreach ($dirFiles['file'] as $fileName)
766 {
767 if (preg_match('/^([^_]+)_([0-9]{8})_([^_]+)_.+$/i', $fileName, $parts))
768 {
769 //$type = $parts[1];
770 //$hash = $parts[3];
771 $date = \DateTime::createFromFormat('Ymd', $parts[2]);
772 $interval = $now->diff($date);
773 if ($interval->d > self::EXPIRE_DAYS)
774 {
775 $this->bucket->DeleteFile($this->generateUploadDir(). $fileName);
776 }
777 }
778 }
779 }
780
781 return $this->finishAction();
782 }
783
784
790 public function finishAction()
791 {
792 if ($this->isExportCompleted && !$this->isCloudAvailable)
793 {
794 // adjust completed local file size
795 $this->fileSize = $this->getSizeTempFile();
796 }
797
798 $result = $this->preformAnswer(self::ACTION_FINISH);
799
800 $result['STATUS'] = self::STATUS_COMPLETED;
801
802 return $result;
803 }
804
805
811 protected function preformAnswer($action)
812 {
813 if ($action == self::ACTION_CLEAR || $action == self::ACTION_CANCEL || $action == self::ACTION_PURGE)
814 {
815 $result = [];
816 }
817 else
818 {
820 if (!$this->isExportCompleted && $this->uploadedSize > 0)
821 {
822 $fileSize = $this->uploadedSize + $this->fileSize;
823 }
824 elseif ($this->isExportCompleted && $this->uploadedSize > $this->fileSize)
825 {
827 }
828
829 $result = array(
830 'STATUS' => ($this->isExportCompleted ? self::STATUS_COMPLETED : self::STATUS_PROGRESS),
831 'PROCESSED_ITEMS' => $this->processedItems,
832 'TOTAL_ITEMS' => $this->totalItems,
833 'UPLOADED_PART' => $this->uploadedPart,
834 'UPLOADED_SIZE' => $this->uploadedSize,
835 'UPLOADED_SIZE_FORMAT' => \CFile::FormatSize($this->uploadedSize),
836 'FILE_SIZE_FORMAT' => \CFile::FormatSize($fileSize),
837 );
838
839 $messagePlaceholders = array(
840 '#PROCESSED_ITEMS#' => $this->processedItems,
841 '#TOTAL_ITEMS#' => $this->totalItems,
842 '#UPLOADED_PART#' => $this->uploadedPart,
843 '#UPLOADED_SIZE#' => $this->uploadedSize,
844 '#UPLOADED_SIZE_FORMAT#' => \CFile::FormatSize($this->uploadedSize),
845 '#FILE_SIZE_FORMAT#' => \CFile::FormatSize($fileSize),
846 );
847 }
848
849 if ($action == self::ACTION_VOID)
850 {
851 $message = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_VOID'));
852 }
853 elseif ($action == self::ACTION_PURGE)
854 {
855 $message = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_PURGE'));
856 }
857 elseif ($action == self::ACTION_CLEAR)
858 {
859 $message = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_FILE_DROPPED'));
860 }
861 elseif ($action == self::ACTION_CANCEL)
862 {
863 $message = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_ACTION_CANCEL'));
864 }
865 elseif ($action == self::ACTION_UPLOAD)
866 {
867 $message =
868 htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_ACTION_UPLOAD', $messagePlaceholders)). '<br>'.
870 }
871 elseif ($action == self::ACTION_EXPORT)
872 {
873 $message =
874 htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_ACTION_EXPORT', $messagePlaceholders)). '<br>'.
876 }
877 elseif ($this->isExportCompleted)
878 {
879 $message =
880 htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_COMPLETED')). '<br>'.
881 htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_ACTION_EXPORT', $messagePlaceholders));
882
883 $downloadLink = '';
884 if ($this->isBitrix24 || $this->useCloud === true)
885 {
886 if ($this->isUploadFinished)
887 {
888 $downloadLink = $this->generateCloudLink();
889 }
890 }
891 else
892 {
893 $downloadLink = $this->generateDownloadLink();
894 }
895 if ($downloadLink !== '')
896 {
897 $result['DOWNLOAD_LINK'] = $downloadLink;
898 $result['FILE_NAME'] = $this->fileName;
899 $result['DOWNLOAD_LINK_NAME'] = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_DOWNLOAD'));
900 $result['CLEAR_LINK_NAME'] = htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_CLEAR'));
901 }
902 }
903 else
904 {
905 $message =
906 htmlspecialcharsbx(Loc::getMessage('MAIN_EXPORT_ACTION_EXPORT', $messagePlaceholders)). '<br>'.
908 }
909
910 $result['SUMMARY_HTML'] = $message;
911
912 return $result;
913 }
914
920 protected function preformExpectedDuration()
921 {
922 $message = '';
923 $avgStepDuration = $predictedStepCount = $predictedTimeDuration = 0;
924 $avgRowsPerStep = self::ROWS_PER_PAGE;
925 if ($this->stepCount > 0 && $this->timeStart > 0)
926 {
927 $avgStepDuration = round((time() - $this->timeStart) / $this->stepCount);
928 if ($this->processedItems > 0)
929 {
930 $avgRowsPerStep = round($this->processedItems / $this->stepCount);
931 }
932 }
933 if ($this->totalItems > 0)
934 {
935 $predictedStepCount = round(($this->totalItems - $this->processedItems) / $avgRowsPerStep);
936 if ($this->useCloud === true)
937 {
938 $predictedStepCount *= 2;
939 }
940 }
941 if ($avgStepDuration > 0 && $predictedStepCount > 0)
942 {
943 $predictedTimeDuration = $avgStepDuration * $predictedStepCount * 1.1;
944 }
945 if ($predictedTimeDuration > 0)
946 {
947 $predictedTimeDurationHours = floor($predictedTimeDuration / 3600);
948 if ($predictedTimeDurationHours > 0)
949 {
950 $predictedTimeDurationMinutes = ceil(($predictedTimeDuration - $predictedTimeDurationHours * 3600) / 60);
951 $message =
952 Loc::getMessage('MAIN_EXPORT_EXPECTED_DURATION').' '.
953 Loc::getMessage('MAIN_EXPORT_EXPECTED_DURATION_HOURS', array(
954 '#HOURS#' => $predictedTimeDurationHours,
955 '#MINUTES#' => $predictedTimeDurationMinutes,
956 ));
957 }
958 else
959 {
960 $predictedTimeDurationMinutes = round($predictedTimeDuration / 60);
961 $message =
962 Loc::getMessage('MAIN_EXPORT_EXPECTED_DURATION').' '.
963 Loc::getMessage('MAIN_EXPORT_EXPECTED_DURATION_MINUTES', array(
964 '#MINUTES#' => ($predictedTimeDurationMinutes < 1 ? "&lt;&nbsp;1" : $predictedTimeDurationMinutes),
965 ));
966 }
967 }
968
969 return $message;
970 }
971
972
978 protected function generateDownloadLink()
979 {
980 $params = array(
981 'PROCESS_TOKEN' => $this->processToken,
982 'EXPORT_TYPE' => $this->exportType,
983 'COMPONENT_NAME' => $this->componentName,
984 );
985
986 return $this->getActionUri(self::ACTION_DOWNLOAD, $params);
987 }
988
994 protected function generateCloudLink()
995 {
996 $this->instanceBucket();
997
998 if ($this->checkCloudErrors())
999 {
1000 return $this->bucket->GetFileSRC($this->uploadPath);
1001 }
1002
1003 return '';
1004 }
1005
1006
1013 protected function checkCommonErrors($action)
1014 {
1015 if (empty($this->module))
1016 {
1017 $this->addError(new Error('Module Id property is not filled.'));
1018 }
1019 if (!Main\Loader::includeModule($this->module))
1020 {
1021 $this->addError(new Error('Module '.$this->module.' is not included.'));
1022 }
1023
1024 if ($this->isBitrix24)
1025 {
1026 if ($this->isCloudAvailable !== true)
1027 {
1028 $this->addError(new Error(Loc::getMessage('MAIN_EXPORT_ERROR_NO_CLOUD_BUCKET')));
1029 }
1030 }
1031
1032 if ($action->getName() === self::ACTION_PURGE)
1033 {
1034 return true;
1035 }
1036
1037 if ($this->componentName === '')
1038 {
1039 $this->addError(new Error('Component name is not specified.'));
1040 }
1041 if (!in_array($this->exportType, array(self::EXPORT_TYPE_CSV, self::EXPORT_TYPE_EXCEL), true))
1042 {
1043 $this->addError(new Error('The export type is not supported.'));
1044 }
1045
1046 if($this->processToken === '')
1047 {
1048 $this->addError(new Error('Process token is not specified.'));
1049 }
1050
1051 return empty($this->getErrors());
1052 }
1053
1054
1060 protected function checkCloudErrors()
1061 {
1062 if (!($this->bucket instanceof \CCloudStorageBucket))
1063 {
1064 $this->errorCollection[] = new Error(Loc::getMessage('MAIN_EXPORT_ERROR_NO_CLOUD_BUCKET'));
1065 }
1066
1067 return empty($this->getErrors());
1068 }
1069
1070
1078 protected function findInitBucket(array $attributes)
1079 {
1080 if (!($this->bucket instanceof \CCloudStorageBucket))
1081 {
1082 $this->bucket = \CCloudStorage::FindBucketForFile(
1083 array(
1084 'FILE_SIZE' => $attributes['fileSize'],
1085 'MODULE_ID' => $this->module,
1086 ),
1087 $attributes['fileName']
1088 );
1089
1090 if (
1091 $this->bucket === null ||
1092 !($this->bucket instanceof \CCloudStorageBucket) ||
1093 !$this->bucket->init()
1094 )
1095 {
1096 return false;
1097 }
1098
1099 $this->bucketId = $this->bucket->ID;
1100 $this->cloudMinChunkSize = $this->bucket->GetService()->GetMinUploadPartSize(); //5M
1101 }
1102
1103 return true;
1104 }
1105
1111 protected function instanceBucket()
1112 {
1113 if (!($this->bucket instanceof \CCloudStorageBucket))
1114 {
1115 if ($this->bucketId > 0)
1116 {
1117 $this->bucket = new \CCloudStorageBucket($this->bucketId);
1118 $this->bucket->init();
1119 }
1120 }
1121
1122 return $this->bucket;
1123 }
1124
1132 protected function getBucketList($filter = [])
1133 {
1134 $result = [];
1135 $res = \CCloudStorageBucket::GetList([], array_merge(array('ACTIVE' => 'Y', 'READ_ONLY' => 'N'), $filter));
1136 while($bucket = $res->Fetch())
1137 {
1138 $result[] = $bucket;
1139 }
1140
1141 return $result;
1142 }
1143
1149 protected function saveProgressParameters(): void
1150 {
1151 // store state
1152 $progressData = [
1153 'processToken' => $this->processToken,
1154 ];
1155 foreach ($this->fieldToStoreInProcess as $fieldName)
1156 {
1157 $progressData[$fieldName] = $this->{$fieldName};
1158 }
1159
1160 $progressData = (new Signer())->sign(Json::encode($progressData));
1161
1162 $localStorage = Application::getInstance()->getLocalSession($this->module.'_export_stepper');
1163 $localStorage->set($this->getProgressParameterOptionName(), $progressData);
1164 }
1165
1171 protected function getProgressParameters()
1172 {
1173 $localStorage = Application::getInstance()->getLocalSession($this->module.'_export_stepper');
1174 $progressData = $localStorage->get($this->getProgressParameterOptionName());
1175
1176 if ($progressData)
1177 {
1178 try
1179 {
1180 $progressData = Json::decode((new Signer())->unsign($progressData));
1181 }
1182 catch (BadSignatureException $exception)
1183 {
1184 $progressData = [];
1185 }
1186 catch (ArgumentException $exception)
1187 {
1188 $progressData = [];
1189 }
1190 }
1191
1192 if (!is_array($progressData))
1193 {
1194 $progressData = [];
1195 }
1196
1197 return $progressData;
1198 }
1199
1205 protected function clearProgressParameters(): void
1206 {
1207 $localStorage = Application::getInstance()->getLocalSession($this->module.'_export_stepper');
1208 $localStorage->offsetUnset($this->getProgressParameterOptionName());
1209 }
1210
1217 {
1218 return $this->module. '_cloud_export';
1219 }
1220
1226 protected function generateExportFileName()
1227 {
1228 if ($this->exportType === self::EXPORT_TYPE_CSV)
1229 {
1230 $fileExt = 'csv';
1231 }
1232 elseif ($this->exportType === self::EXPORT_TYPE_EXCEL)
1233 {
1234 $fileExt = 'xls';
1235 }
1236
1237 $prefix = date('Ymd');
1238 $hash = str_pad(dechex(crc32($prefix)), 8, '0', STR_PAD_LEFT);
1239
1240 return uniqid($prefix. '_'. $hash. '_', false).'.'.$fileExt;
1241 }
1242
1248 protected function getFileType()
1249 {
1250 if ($this->exportType === self::EXPORT_TYPE_CSV)
1251 {
1252 $fileType = 'application/csv';
1253 }
1254 elseif ($this->exportType === self::EXPORT_TYPE_EXCEL)
1255 {
1256 $fileType = 'application/vnd.ms-excel';
1257 }
1258
1259 return $fileType;
1260 }
1261
1267 protected function generateTempDirPath()
1268 {
1269 $tempDir = \CTempFile::GetDirectoryName(self::KEEP_FILE_HOURS, array($this->module, uniqid( 'export_', true)));
1270
1271 \CheckDirPath($tempDir);
1272
1273 return $tempDir;
1274 }
1275
1281 protected function generateUploadDir()
1282 {
1283 return self::EXPORT_PATH;
1284 }
1285
1286
1296 protected function getSignedParameters($componentName, array $params)
1297 {
1298 return Main\Component\ParameterSigner::signParameters($componentName, $params);
1299 }
1300
1301
1311 protected function decodeSignedParameters($componentName, $signedParameters)
1312 {
1313 return Main\Component\ParameterSigner::unsignParameters($componentName, $signedParameters);
1314 }
1315
1321 protected function writeTempFile($data, $precedeUtf8Bom = true)
1322 {
1323 $file = fopen($this->filePath, 'ab');
1324 if(is_resource($file))
1325 {
1326 // add UTF-8 BOM marker
1327 if (\Bitrix\Main\Application::isUtfMode() || defined('BX_UTF'))
1328 {
1329 if($precedeUtf8Bom === true && (filesize($this->filePath) === 0))
1330 {
1331 fwrite($file, chr(239).chr(187).chr(191));
1332 }
1333 }
1334 fwrite($file, $data);
1335 fclose($file);
1336 unset($file);
1337
1338 $this->fileSize = filesize($this->filePath);
1339 }
1340 }
1341
1345 protected function dropTempFile()
1346 {
1347 if (file_exists($this->filePath))
1348 {
1349 @unlink($this->filePath);
1350 }
1351 $this->fileSize = 0;
1352 }
1353
1357 protected function getContentTempFile()
1358 {
1359 return file_get_contents($this->filePath);
1360 }
1361
1365 protected function getSizeTempFile()
1366 {
1367 $this->fileSize = filesize($this->filePath);
1368
1369 return $this->fileSize;
1370 }
1371
1378 protected function startTimer($timeLimit = 25)
1379 {
1380 $this->timeLimit = $timeLimit;
1381
1382 if (defined('START_EXEC_TIME') && START_EXEC_TIME > 0)
1383 {
1384 $this->hitStartTime = (int)START_EXEC_TIME;
1385 }
1386 else
1387 {
1388 $this->hitStartTime = time();
1389 }
1390 }
1391
1392
1398 protected function hasTimeLimitReached()
1399 {
1400 if ($this->timeLimit > 0)
1401 {
1402 if ($this->timeLimitReached)
1403 {
1404 return true;
1405 }
1406 if ((time() - $this->hitStartTime) >= $this->timeLimit)
1407 {
1408 $this->timeLimitReached = true;
1409
1410 return true;
1411 }
1412 }
1413
1414 return false;
1415 }
1416}
getSignedParameters($componentName, array $params)
Definition export.php:1296
decodeSignedParameters($componentName, $signedParameters)
Definition export.php:1311
findInitBucket(array $attributes)
Definition export.php:1078
processBeforeAction(Main\Engine\Action $action)
Definition export.php:242
writeTempFile($data, $precedeUtf8Bom=true)
Definition export.php:1321
getActionUri(string $actionName, array $params=[], bool $absolute=false)
static includeModule($moduleName)
Definition loader.php:69
static loadMessages($file)
Definition loc.php:64
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29