Passed
Push — master ( de9f16...6836c0 )
by Aleksandr
02:35
created

ApiController::getProductClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace carono\exchange1c\controllers;
4
5
use carono\exchange1c\behaviors\BomBehavior;
6
use carono\exchange1c\ExchangeEvent;
7
use carono\exchange1c\ExchangeModule;
8
use carono\exchange1c\helpers\ByteHelper;
9
use carono\exchange1c\helpers\NodeHelper;
10
use carono\exchange1c\helpers\SerializeHelper;
11
use carono\exchange1c\interfaces\DocumentInterface;
12
use carono\exchange1c\interfaces\OfferInterface;
13
use carono\exchange1c\interfaces\ProductInterface;
14
use Yii;
15
use yii\db\ActiveRecord;
16
use yii\helpers\FileHelper;
17
use yii\web\Response;
18
use Zenwalker\CommerceML\CommerceML;
19
use Zenwalker\CommerceML\Model\Classifier;
20
use Zenwalker\CommerceML\Model\Group;
21
use Zenwalker\CommerceML\Model\Image;
22
use Zenwalker\CommerceML\Model\Offer;
23
use Zenwalker\CommerceML\Model\Product;
24
use Zenwalker\CommerceML\Model\PropertyCollection;
25
use Zenwalker\CommerceML\Model\Simple;
26
use Zenwalker\CommerceML\Model\RequisiteCollection;
27
28
/**
29
 * Default controller for the `api` module
30
 *
31
 * @property ExchangeModule $module
32
 */
33
class ApiController extends Controller
34
{
35
    public $enableCsrfValidation = false;
36
    const EVENT_BEFORE_UPDATE_PRODUCT = 'beforeUpdateProduct';
37
    const EVENT_AFTER_UPDATE_PRODUCT = 'afterUpdateProduct';
38
    const EVENT_BEFORE_UPDATE_OFFER = 'beforeUpdateOffer';
39
    const EVENT_AFTER_UPDATE_OFFER = 'afterUpdateOffer';
40
    const EVENT_BEFORE_PRODUCT_SYNC = 'beforeProductSync';
41
    const EVENT_AFTER_PRODUCT_SYNC = 'afterProductSync';
42
    const EVENT_BEFORE_OFFER_SYNC = 'beforeOfferSync';
43
    const EVENT_AFTER_OFFER_SYNC = 'afterOfferSync';
44
    const EVENT_AFTER_FINISH_UPLOAD_FILE = 'afterFinishUploadFile';
45
    const EVENT_AFTER_EXPORT_ORDERS = 'afterExportOrders';
46
47
    private $_ids;
48
49
    public function init()
50
    {
51
        set_time_limit($this->module->timeLimit);
52
        if ($this->module->memoryLimit) {
53
            ini_set('memory_limit', $this->module->memoryLimit);
54
        }
55
        parent::init();
56
    }
57
58
59
    /**
60
     * @return array
61
     */
62
    public function behaviors()
63
    {
64
        return array_merge(parent::behaviors(), [
65
            'bom' => [
66
                'class' => BomBehavior::className(),
67
                'only' => ['query'],
68
            ],
69
        ]);
70
    }
71
72
    /**
73
     * @param \yii\base\Action $action
74
     * @param mixed $result
75
     * @return mixed|string
76
     */
77
    public function afterAction($action, $result)
78
    {
79
        Yii::$app->response->headers->set('uid', Yii::$app->user->getId());
80
        parent::afterAction($action, $result);
81
        if (is_bool($result)) {
82
            return $result ? "success" : "failure";
83
        } elseif (is_array($result)) {
84
            $r = [];
85
            foreach ($result as $key => $value) {
86
                $r[] = is_int($key) ? $value : $key . '=' . $value;
87
            }
88
            return join("\n", $r);
89
        } else {
90
            return parent::afterAction($action, $result);
91
        }
92
    }
93
94
    /**
95
     * @param $type
96
     * @return array|bool
97
     */
98
    public function actionCheckauth($type)
99
    {
100
        if (Yii::$app->user->isGuest) {
101
            return false;
102
        } else {
103
            return [
104
                "success",
105
                "PHPSESSID",
106
                Yii::$app->session->getId(),
107
            ];
108
        }
109
    }
110
111
    /**
112
     * @return float|int
113
     */
114
    protected function getFileLimit()
115
    {
116
        $limit = ByteHelper::maximum_upload_size();
117
        if (!($limit % 2)) {
118
            $limit--;
119
        }
120
        return $limit;
121
    }
122
123
    /**
124
     * @return array
125
     */
126
    public function actionInit()
127
    {
128
        return [
129
            "zip" => class_exists('ZipArchive') && $this->module->useZip ? "yes" : "no",
130
            "file_limit" => $this->getFileLimit(),
131
        ];
132
    }
133
134
    /**
135
     * @param $type
136
     * @param $filename
137
     * @return bool
138
     */
139
    public function actionFile($type, $filename)
140
    {
141
        $body = Yii::$app->request->getRawBody();
142
        $filePath = $this->module->getTmpDir() . DIRECTORY_SEPARATOR . $filename;
143
        if (!self::getData('archive') && pathinfo($filePath, PATHINFO_EXTENSION) == 'zip') {
144
            self::setData('archive', $filePath);
145
        }
146
        file_put_contents($filePath, $body, FILE_APPEND);
147
        if ((int)Yii::$app->request->headers->get('Content-Length') != $this->getFileLimit()) {
148
            $this->afterFinishUploadFile($filePath);
149
        }
150
        return true;
151
    }
152
153
    /**
154
     * @param $file
155
     */
156
    public function parsingImport($file)
157
    {
158
        $this->_ids = [];
159
        $commerce = new CommerceML();
160
        $commerce->loadImportXml($file);
161
        $classifierFile = Yii::getAlias($this->module->tmpDir . '/classifier.xml');
162
        if ($commerce->classifier->xml) {
163
            $commerce->classifier->xml->saveXML($classifierFile);
164
        } else {
165
            $commerce->classifier->xml = simplexml_load_file($classifierFile);
166
        }
167
        $this->beforeProductSync();
168
        if ($groupClass = $this->getGroupClass()) {
169
            $groupClass::createTree1c($commerce->classifier->getGroups());
170
        }
171
        $productClass = $this->getProductClass();
172
        $productClass::createProperties1c($commerce->classifier->getProperties());
173
        foreach ($commerce->catalog->getProducts() as $product) {
174
            if (!$model = $productClass::createModel1c($product)) {
175
                Yii::error("Модель продукта не найдена, проверьте реализацию $productClass::createModel1c", 'exchange1c');
176
                continue;
177
            }
178
            $this->parseProduct($model, $product);
179
            $this->_ids[] = $model->getPrimaryKey();
180
            $model = null;
181
            unset($model);
182
            unset($product);
183
            gc_collect_cycles();
184
        }
185
        $this->afterProductSync();
186
    }
187
188
    /**
189
     * @param $file
190
     */
191
    public function parsingOffer($file)
192
    {
193
        $this->_ids = [];
194
        $commerce = new CommerceML();
195
        $commerce->loadOffersXml($file);
196
        if ($offerClass = $this->getOfferClass()) {
197
            $offerClass::createPriceTypes1c($commerce->offerPackage->getPriceTypes());
198
        }
199
        $this->beforeOfferSync();
200
        foreach ($commerce->offerPackage->getOffers() as $offer) {
201
            $product_id = $offer->getClearId();
202
            if ($product = $this->findProductModelById($product_id)) {
203
                $model = $product->getOffer1c($offer);
204
                $this->parseProductOffer($model, $offer);
205
                $this->_ids[] = $model->getPrimaryKey();
206
            } else {
207
                Yii::warning("Продукт $product_id не найден в базе", 'exchange1c');
208
                continue;
209
            }
210
            unset($model);
211
        }
212
        $this->afterOfferSync();
213
    }
214
215
    /**
216
     * @param $file
217
     */
218
    public function parsingOrder($file)
219
    {
220
        /**
221
         * @var DocumentInterface $documentModel
222
         */
223
        $commerce = new CommerceML();
224
        $commerce->addXmls(false, false, $file);
225
        $documentClass = $this->module->documentClass;
226
        foreach ($commerce->order->documents as $document) {
227
            if ($documentModel = $documentClass::findOne((string)$document->Номер)) {
228
                $documentModel->setRaw1cData($commerce, $document);
229
            }
230
        }
231
    }
232
233
    /**
234
     * @param $type
235
     * @param $filename
236
     * @return bool
237
     */
238
    public function actionImport($type, $filename)
239
    {
240
        if (($archive = self::getData('archive')) && file_exists($archive)) {
241
            $zip = new \ZipArchive();
242
            $zip->open($archive);
243
            $zip->extractTo(dirname($archive));
244
            $zip->close();
245
            @unlink($archive);
246
        }
247
        $file = $this->module->getTmpDir() . DIRECTORY_SEPARATOR . $filename;
248
        if ($type == 'catalog') {
249
            if (strpos($file, 'offer') !== false) {
250
                $this->parsingOffer($file);
251
            } elseif (strpos($file, 'import') !== false) {
252
                $this->parsingImport($file);
253
            }
254
        } elseif ($type == 'sale' && strpos($file, 'order') !== false) {
255
            $this->parsingOrder($file);
256
        }
257
        if (!$this->module->debug) {
258
            $this->clearTmp();
259
        }
260
        return true;
261
    }
262
263
    protected function clearTmp()
264
    {
265
        FileHelper::removeDirectory($this->module->getTmpDir());
266
    }
267
268
    /**
269
     * @param $type
270
     * @return mixed
271
     */
272
    public function actionQuery($type)
273
    {
274
        /**
275
         * @var DocumentInterface $document
276
         */
277
        $response = Yii::$app->response;
278
        $response->format = Response::FORMAT_RAW;
279
        $response->getHeaders()->set('Content-Type', 'application/xml; charset=windows-1251');
280
281
        $root = new \SimpleXMLElement('<КоммерческаяИнформация></КоммерческаяИнформация>');
282
        $root->addAttribute('ВерсияСхемы', '2.10');
283
        $root->addAttribute('ДатаФормирования', date('Y-m-d\TH:i:s'));
284
285
        $ids = [];
286
        if ($this->module->exchangeDocuments) {
287
            $document = $this->module->documentClass;
288
            foreach ($document::findDocuments1c() as $order) {
289
                $ids[] = $order->getPrimaryKey();
290
                NodeHelper::appendNode($root, SerializeHelper::serializeDocument($order));
291
            }
292
            if ($this->module->debug) {
293
                $xml = $root->asXML();
294
                $xml = html_entity_decode($xml, ENT_NOQUOTES, 'UTF-8');
295
                file_put_contents($this->module->getTmpDir() . '/query.xml', $xml);
296
            }
297
        }
298
        $this->afterExportOrders($ids);
299
        return $root->asXML();
300
    }
301
302
    /**
303
     * @param $type
304
     * @return bool
305
     */
306
    public function actionSuccess($type)
307
    {
308
        return true;
309
    }
310
311
    /**
312
     * @param $name
313
     * @param $value
314
     */
315
    protected static function setData($name, $value)
316
    {
317
        Yii::$app->session->set($name, $value);
318
    }
319
320
    /**
321
     * @param $name
322
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
323
     * @return mixed
324
     */
325
    protected static function getData($name, $default = null)
326
    {
327
        return Yii::$app->session->get($name, $default);
328
    }
329
330
    /**
331
     * @return bool
332
     */
333
    protected static function clearData()
334
    {
335
        return Yii::$app->session->closeSession();
336
    }
337
338
    /**
339
     * @param ProductInterface $model
340
     * @param \Zenwalker\CommerceML\Model\Product $product
341
     */
342
    protected function parseProduct($model, $product)
343
    {
344
        $this->beforeUpdateProduct($model);
345
        $model->setRaw1cData($product->owner, $product);
346
        $this->parseGroups($model, $product);
347
        $this->parseProperties($model, $product);
348
        $this->parseRequisites($model, $product);
349
        $this->parseImage($model, $product);
350
        $this->afterUpdateProduct($model);
351
        unset($group);
352
    }
353
354
    /**
355
     * @param OfferInterface $model
356
     * @param Offer $offer
357
     */
358
    protected function parseProductOffer($model, $offer)
359
    {
360
        $this->beforeUpdateOffer($model, $offer);
361
        $this->parseSpecifications($model, $offer);
362
        $this->parsePrice($model, $offer);
363
        $model->{$model::getIdFieldName1c()} = $offer->id;
364
        $model->save();
365
        $this->afterUpdateOffer($model, $offer);
366
        unset($model);
367
    }
368
369
    /**
370
     * @param string $id
371
     *
372
     * @return ProductInterface|null
373
     */
374
    protected function findProductModelById($id)
375
    {
376
        /**
377
         * @var $class ProductInterface
378
         */
379
        $class = $this->getProductClass();
380
        return $class::find()->andWhere([$class::getIdFieldName1c() => $id])->one();
381
    }
382
383
    /**
384
     * @param Offer $offer
385
     *
386
     * @return OfferInterface|null
387
     */
388
    protected function findOfferModel($offer)
389
    {
390
        /**
391
         * @var $class ProductInterface
392
         */
393
        $class = $this->getOfferClass();
394
        return $class::find()->andWhere([$class::getIdFieldName1c() => $offer->id])->one();
395
    }
396
397
    /**
398
     * @return ActiveRecord
399
     */
400
    protected function createProductModel($data)
401
    {
402
        $class = $this->getProductClass();
403
        if ($model = $class::createModel1c($data)) {
404
            return $model;
405
        } else {
406
            return Yii::createObject(['class' => $class]);
407
        }
408
    }
409
410
    /**
411
     * @param OfferInterface $model
412
     * @param Offer $offer
413
     */
414
    protected function parsePrice($model, $offer)
415
    {
416
        foreach ($offer->getPrices() as $price) {
417
            $model->setPrice1c($price);
418
        }
419
    }
420
421
    /**
422
     * @param ProductInterface $model
423
     * @param Product $product
424
     */
425
    protected function parseImage($model, $product)
426
    {
427
        $images = $product->getImages();
428
        foreach ($images as $image) {
429
            $path = realpath($this->module->getTmpDir() . DIRECTORY_SEPARATOR . $image->path);
430
            if (file_exists($path)) {
431
                $model->addImage1c($path, $image->caption);
432
            }
433
        }
434
    }
435
436
    /**
437
     * @param ProductInterface $model
438
     * @param Product $product
439
     */
440
    protected function parseGroups($model, $product)
441
    {
442
        $group = $product->getGroup();
443
        $model->setGroup1c($group);
444
    }
445
446
    /**
447
     * @param ProductInterface $model
448
     * @param Product $product
449
     */
450
    protected function parseRequisites($model, $product)
451
    {
452
        $requisites = $product->getRequisites();
453
        foreach ($requisites as $requisite) {
454
            $model->setRequisite1c($requisite->name, $requisite->value);
455
        }
456
    }
457
458
    /**
459
     * @param OfferInterface $model
460
     * @param Offer $offer
461
     */
462
    protected function parseSpecifications($model, $offer)
463
    {
464
        foreach ($offer->getSpecifications() as $specification) {
465
            $model->setSpecification1c($specification);
466
        }
467
    }
468
469
    /**
470
     * @param ProductInterface $model
471
     * @param Product $product
472
     */
473
    protected function parseProperties($model, $product)
474
    {
475
        $properties = $product->getProperties();
476
        foreach ($properties as $property) {
477
            $model->setProperty1c($property);
478
        }
479
    }
480
481
    /**
482
     * @return OfferInterface
483
     */
484
    protected function getOfferClass()
485
    {
486
        return $this->module->offerClass;
487
    }
488
489
    /**
490
     * @return ProductInterface
491
     */
492
    protected function getProductClass()
493
    {
494
        return $this->module->productClass;
495
    }
496
497
    /**
498
     * @return DocumentInterface
499
     */
500
    protected function getDocumentClass()
501
    {
502
        return $this->module->documentClass;
503
    }
504
505
    /**
506
     * @return \carono\exchange1c\interfaces\GroupInterface
507
     */
508
    protected function getGroupClass()
509
    {
510
        return $this->module->groupClass;
511
    }
512
513
    /**
514
     * @return bool
515
     */
516
    public function actionError()
517
    {
518
        return false;
519
    }
520
521
    /**
522
     * @param $filePath
523
     */
524
    public function afterFinishUploadFile($filePath)
525
    {
526
        $this->module->trigger(self::EVENT_AFTER_FINISH_UPLOAD_FILE, new ExchangeEvent());
527
    }
528
529
    public function beforeProductSync()
530
    {
531
        $this->module->trigger(self::EVENT_BEFORE_PRODUCT_SYNC, new ExchangeEvent());
532
    }
533
534
    public function afterProductSync()
535
    {
536
        $this->module->trigger(self::EVENT_AFTER_PRODUCT_SYNC, new ExchangeEvent(['ids' => $this->_ids]));
537
    }
538
539
    public function beforeOfferSync()
540
    {
541
        $this->module->trigger(self::EVENT_BEFORE_OFFER_SYNC, new ExchangeEvent());
542
    }
543
544
    public function afterOfferSync()
545
    {
546
        $this->module->trigger(self::EVENT_AFTER_OFFER_SYNC, new ExchangeEvent(['ids' => $this->_ids]));
547
    }
548
549
    /**
550
     * @param $model
551
     */
552
    public function afterUpdateProduct($model)
553
    {
554
        $this->module->trigger(self::EVENT_AFTER_UPDATE_PRODUCT, new ExchangeEvent(['model' => $model]));
555
    }
556
557
    /**
558
     * @param $model
559
     */
560
    public function beforeUpdateProduct($model)
561
    {
562
        $this->module->trigger(self::EVENT_BEFORE_UPDATE_PRODUCT, new ExchangeEvent(['model' => $model]));
563
    }
564
565
    /**
566
     * @param $model
567
     * @param $offer
568
     */
569
    public function beforeUpdateOffer($model, $offer)
570
    {
571
        $this->module->trigger(self::EVENT_BEFORE_UPDATE_OFFER, new ExchangeEvent([
572
            'model' => $model,
573
            'ml' => $offer,
574
        ]));
575
    }
576
577
    /**
578
     * @param $model
579
     * @param $offer
580
     */
581
    public function afterUpdateOffer($model, $offer)
582
    {
583
        $this->module->trigger(self::EVENT_AFTER_UPDATE_OFFER, new ExchangeEvent(['model' => $model, 'ml' => $offer]));
584
    }
585
586
    /**
587
     * @param $ids
588
     */
589
    public function afterExportOrders($ids)
590
    {
591
        $this->module->trigger(self::EVENT_AFTER_EXPORT_ORDERS, new ExchangeEvent(['ids' => $ids]));
592
    }
593
}
594