Import::logErrors()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
/**
4
 * @package Importer
5
 * @author Iurii Makukh
6
 * @copyright Copyright (c) 2017, Iurii Makukh
7
 * @license https://www.gnu.org/licenses/gpl-3.0.en.html GPL-3.0+
8
 */
9
10
namespace gplcart\modules\import\handlers;
11
12
use gplcart\core\Config;
13
use gplcart\core\models\FileTransfer;
14
use gplcart\core\models\Product;
15
use gplcart\core\models\Translation;
16
use gplcart\core\models\User;
17
use gplcart\core\models\Validator;
18
use gplcart\modules\import\helpers\Csv;
19
20
/**
21
 * Handler for Importer module
22
 */
23
class Import
24
{
25
26
    /**
27
     * Config class instance
28
     * @var \gplcart\core\Config $config
29
     */
30
    protected $config;
31
32
    /**
33
     * Translation UI model instance
34
     * @var \gplcart\core\models\Translation $translation
35
     */
36
    protected $translation;
37
38
    /**
39
     * User model instance
40
     * @var \gplcart\core\models\User $user
41
     */
42
    protected $user;
43
44
    /**
45
     * Validator model instance
46
     * @var \gplcart\core\models\Validator $validator
47
     */
48
    protected $validator;
49
50
    /**
51
     * File transfer model instance
52
     * @var \gplcart\core\models\FileTransfer $file_transfer
53
     */
54
    protected $file_transfer;
55
56
    /**
57
     * Product model instance
58
     * @var \gplcart\core\models\Product $product
59
     */
60
    protected $product;
61
62
    /**
63
     * CSV class instance
64
     * @var \gplcart\modules\import\helpers\Csv $csv
65
     */
66
    protected $csv;
67
68
    /**
69
     * An array of errors
70
     * @var array
71
     */
72
    protected $errors = array();
73
74
    /**
75
     * An array of parsed CSV rows
76
     * @var array
77
     */
78
    protected $rows = array();
79
80
    /**
81
     * An array of the current job
82
     * @var array
83
     */
84
    protected $job = array();
85
86
    /**
87
     * An array of the current line data
88
     * @var array
89
     */
90
    protected $data = array();
91
92
    /**
93
     * @param Config $config
94
     * @param Product $product
95
     * @param User $user
96
     * @param FileTransfer $file_transfer
97
     * @param Translation $translation
98
     * @param Validator $validator
99
     * @param Csv $csv
100
     */
101
    public function __construct(Config $config, Product $product, User $user,
102
                                FileTransfer $file_transfer, Translation $translation,
103
                                Validator $validator, Csv $csv)
104
    {
105
        $this->csv = $csv;
106
        $this->user = $user;
107
        $this->config = $config;
108
        $this->product = $product;
109
        $this->validator = $validator;
110
        $this->translation = $translation;
111
        $this->file_transfer = $file_transfer;
112
    }
113
114
    /**
115
     * Processes one import iteration
116
     * @param array $job
117
     */
118
    public function process(array &$job)
119
    {
120
        $this->job = &$job;
121
        $this->rows = array();
122
        $this->errors = array();
123
124
        $this->csv->open($this->job['data']['filepath'], $this->job['data']['filesize'])
125
            ->setLimit($this->job['data']['limit'])
126
            ->setHeader($this->job['data']['header'])
127
            ->setDelimiter($this->job['data']['delimiter']);
128
129
        if (empty($this->job['context']['offset'])) {
130
            $this->rows = $this->csv->skipHeader()->parse();
131
        } else {
132
            $this->rows = $this->csv->setOffset($this->job['context']['offset'])->parse();
133
        }
134
135
        $this->import();
136
        $this->finish();
137
    }
138
139
    /**
140
     * Returns a total number of errors and logs them
141
     * @return integer
142
     */
143
    protected function countErrors()
144
    {
145
        $count = 0;
146
147
        foreach ($this->errors as $line => $errors) {
148
            $errors = array_filter($errors);
149
            $count += count($errors);
150
            $this->logErrors($line, $errors);
151
        }
152
153
        return $count;
154
    }
155
156
    /**
157
     * Logs all errors happened on the line
158
     * @param integer $line
159
     * @param array $errors
160
     * @return boolean
161
     */
162
    protected function logErrors($line, array $errors)
163
    {
164
        $line_message = $this->translation->text('Line @num', array('@num' => $line));
165
        return gplcart_file_csv($this->job['log']['errors'], array($line_message, implode(PHP_EOL, $errors)));
166
    }
167
168
    /**
169
     * Finishes the current iteration
170
     */
171
    protected function finish()
172
    {
173
        if (empty($this->rows)) {
174
            $this->job['status'] = false;
175
            $this->job['done'] = $this->job['total'];
176
        } else {
177
            $offset = $this->csv->getOffset();
178
            $this->job['context']['offset'] = $offset;
179
            $this->job['done'] = $offset ? $offset : $this->job['total'];
180
        }
181
182
        $this->job['errors'] += $this->countErrors();
183
        $vars = array('@num' => $this->job['context']['line']);
184
        $this->job['message']['process'] = $this->translation->text('Last processed line: @num', $vars);
185
    }
186
187
    /**
188
     * Adds/updates an array of products
189
     */
190
    protected function import()
191
    {
192
        foreach ($this->rows as $row) {
193
            if ($this->prepare($row) && $this->validate()) {
194
                $this->set();
195
            }
196
        }
197
    }
198
199
    /**
200
     * Prepare a data taken from a CSV row
201
     * @param array $row
202
     * @return boolean
203
     */
204
    protected function prepare(array $row)
205
    {
206
        $this->job['context']['line']++;
207
        $this->data = array_filter(array_map('trim', $row));
208
209
        if (isset($this->data['product_id']) && $this->data['product_id'] !== '') {
210
            $product_id = $this->data['product_id'];
211
            $this->data = array_intersect_key($this->data, array_flip($this->job['data']['update']));
212
            $this->data['update'] = $this->data['product_id'] = $product_id;
213
            $skip = $this->job['data']['mode'] === 'create';
214
        } else {
215
            $skip = $this->job['data']['mode'] === 'update';
216
        }
217
218
        if ($skip) {
219
            return false;
220
        }
221
222
        if (empty($this->data['update']) && empty($this->data['user_id'])) {
223
            $this->data['user_id'] = $this->user->getId();
224
        }
225
226
        return true;
227
    }
228
229
    /**
230
     * Validates a data to be imported
231
     * @return boolean
232
     */
233
    public function validate()
234
    {
235
        if (empty($this->data['update']) && !$this->user->access('product_add')) {
236
            $this->setError($this->translation->text('No access to add products'));
237
            return false;
238
        }
239
240
        if (!empty($this->data['update']) && !$this->user->access('product_update')) {
241
            $this->setError($this->translation->text('No access to update products'));
242
            return false;
243
        }
244
245
        if (!empty($this->data['images']) && !$this->user->access('file_upload')) {
246
            $this->setError($this->translation->text('No access to upload files'));
247
            return false;
248
        }
249
250
        $result = $this->validator->run('product', $this->data);
251
252
        if ($result === true) {
253
            return true;
254
        }
255
256
        settype($result, 'array');
257
        $this->setError(gplcart_array_flatten($result));
258
        return false;
259
    }
260
261
    /**
262
     * Sets a error
263
     * @param string|array $error
264
     */
265
    protected function setError($error)
266
    {
267
        settype($error, 'array');
268
269
        $line = $this->job['context']['line'];
270
        $existing = empty($this->errors[$line]) ? array() : $this->errors[$line];
271
        $this->errors[$line] = gplcart_array_merge($existing, $error);
272
    }
273
274
    /**
275
     * Adds/updates a single product
276
     */
277
    protected function set()
278
    {
279
        if (!empty($this->data['images'])) {
280
            $this->data['images'] = $this->getImages($this->data['images']);
281
        }
282
283
        if (empty($this->data['update']) && $this->product->add($this->data)) {
284
            $this->job['inserted']++;
285
        }
286
287
        if (!empty($this->data['update']) && $this->product->update($this->data['product_id'], $this->data)) {
288
            $this->job['updated']++;
289
        }
290
    }
291
292
    /**
293
     * Returns an array of values from a string using a delimiter character
294
     * @param string $string
295
     * @return array
296
     */
297
    protected function explodeValues($string)
298
    {
299
        $delimiter = $this->job['data']['multiple'];
300
        return array_filter(array_map('trim', explode($delimiter, $string)));
301
    }
302
303
    /**
304
     * Returns an array of image data
305
     * @param string $string
306
     * @return array
307
     */
308
    protected function getImages($string)
309
    {
310
        $images = array();
311
312
        foreach ($this->explodeValues($string) as $image) {
313
            $path = $this->getImagePath($image);
314
            if (!empty($path)) {
315
                $images[] = array('path' => $path);
316
            }
317
        }
318
319
        return $images;
320
    }
321
322
    /**
323
     * Validates and returns a relative image path.
324
     * If given an absolute URL, the file will be downloaded
325
     * @param string $image
326
     * @return boolean|string
327
     */
328
    protected function getImagePath($image)
329
    {
330
        if (strpos($image, 'http') === 0) {
331
            return $this->downloadImage($image);
332
        }
333
334
        $path = trim($image, '/');
335
        $file = gplcart_file_absolute($path);
336
337
        if (!is_file($file)) {
338
            $vars = array('@name' => $path);
339
            $error = $this->translation->text('@name is unavailable', $vars);
340
            $this->setError($error);
341
            return false;
342
        }
343
344
        $result = $this->file_transfer->validate($file);
345
346
        if ($result === true) {
347
            return $path;
348
        }
349
350
        $this->setError($result);
351
        return false;
352
    }
353
354
    /**
355
     * Downloads a remote image
356
     * @param string $url
357
     * @return boolean|string
358
     */
359
    protected function downloadImage($url)
360
    {
361
        $dirname = $this->config->get('product_image_dirname', 'product');
362
        $path = gplcart_path_relative(GC_DIR_IMAGE, GC_DIR_FILE) . "/$dirname";
363
364
        $result = $this->file_transfer->download($url, 'image', $path);
365
366
        if ($result === true) {
367
            return $this->file_transfer->getTransferred(true);
368
        }
369
370
        $this->setError($result);
371
        return false;
372
    }
373
374
}
375