Completed
Push — master ( 0010c2...5cb668 )
by Aimeos
02:18
created

Standard::import()   B

Complexity

Conditions 4
Paths 11

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 21
nc 11
nop 4
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Coupon\Import\Csv\Code;
12
13
14
/**
15
 * Job controller for CSV coupon imports.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Common\Coupon\Import\Csv\Base
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
22
	implements \Aimeos\Controller\Jobs\Iface
0 ignored issues
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
23
{
24
	/**
25
	 * Returns the localized name of the job.
26
	 *
27
	 * @return string Name of the job
28
	 */
29
	public function getName()
30
	{
31
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Coupon code import CSV' );
32
	}
33
34
35
	/**
36
	 * Returns the localized description of the job.
37
	 *
38
	 * @return string Description of the job
39
	 */
40
	public function getDescription()
41
	{
42
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Imports new and updates existing coupon code from CSV files' );
43
	}
44
45
46
	/**
47
	 * Executes the job.
48
	 *
49
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
50
	 */
51
	public function run()
52
	{
53
		$total = $errors = 0;
54
		$context = $this->getContext();
55
		$config = $context->getConfig();
56
		$logger = $context->getLogger();
57
		$mappings = $this->getDefaultMapping();
58
59
60
		/** controller/jobs/coupon/import/csv/code/mapping
61
		 * List of mappings between the position in the CSV file and item keys
62
		 *
63
		 * This configuration setting overwrites the shared option
64
		 * "controller/common/coupon/import/csv/mapping" if you need a
65
		 * specific setting for the job controller. Otherwise, you should
66
		 * use the shared option for consistency.
67
		 *
68
		 * @param array Associative list of processor names and lists of key/position pairs
69
		 * @since 2017.10
70
		 * @category Developer
71
		 * @see controller/jobs/coupon/import/csv/code/skip-lines
72
		 * @see controller/jobs/coupon/import/csv/code/max-size
73
		 */
74
		$mappings = $config->get( 'controller/jobs/coupon/import/csv/code/mapping', $mappings );
75
76
77
		/** controller/jobs/coupon/import/csv/code/max-size
78
		 * Maximum number of CSV rows to import at once
79
		 *
80
		 * It's more efficient to read and import more than one row at a time
81
		 * to speed up the import. Usually, the bigger the chunk that is imported
82
		 * at once, the less time the importer will need. The downside is that
83
		 * the amount of memory required by the import process will increase as
84
		 * well. Therefore, it's a trade-off between memory consumption and
85
		 * import speed.
86
		 *
87
		 * @param integer Number of rows
88
		 * @since 2017.10
89
		 * @category Developer
90
		 * @see controller/jobs/coupon/import/csv/code/skip-lines
91
		 * @see controller/jobs/coupon/import/csv/code/mapping
92
		 */
93
		$maxcnt = (int) $config->get( 'controller/jobs/coupon/import/csv/code/max-size', 1000 );
94
95
96
		/** controller/jobs/coupon/import/csv/code/skip-lines
97
		 * Number of rows skipped in front of each CSV files
98
		 *
99
		 * Some CSV files contain header information describing the content of
100
		 * the column values. These data is for informational purpose only and
101
		 * can't be imported into the database. Using this option, you can
102
		 * define the number of lines that should be left out before the import
103
		 * begins.
104
		 *
105
		 * @param integer Number of rows
106
		 * @since 2015.08
107
		 * @category Developer
108
		 * @see controller/jobs/coupon/import/csv/code/mapping
109
		 * @see controller/jobs/coupon/import/csv/code/max-size
110
		 */
111
		$skiplines = (int) $config->get( 'controller/jobs/coupon/import/csv/code/skip-lines', 0 );
112
113
114
		try
115
		{
116
			$processor = $this->getProcessors( $mappings );
117
			$codePos = $this->getCodePosition( $mappings['code'] );
118
			$fs = $context->getFileSystemManager()->get( 'fs-import' );
119
			$dir = 'couponcode/' . $context->getLocale()->getSite()->getCode();
120
121
			if( $fs->isDir( $dir ) === false ) {
122
				return;
123
			}
124
125
			foreach( $fs->scan( $dir ) as $filename )
126
			{
127
				if( $filename == '.' || $filename == '..' ) {
128
					continue;
129
				}
130
131
				list( $couponId, $ext ) = explode( '.', $filename );
0 ignored issues
show
Unused Code introduced by
The assignment to $ext is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
132
				$container = $this->getContainer( $fs->readf( $dir . '/' . $filename ) );
133
134
				$msg = sprintf( 'Started coupon import from "%1$s" (%2$s)', $filename, __CLASS__ );
135
				$logger->log( $msg, \Aimeos\MW\Logger\Base::NOTICE );
136
137
				foreach( $container as $content )
138
				{
139
					for( $i = 0; $i < $skiplines; $i++ ) {
140
						$content->next();
141
					}
142
143
					while( ( $data = $this->getData( $content, $maxcnt, $codePos ) ) !== [] )
144
					{
145
						$items = $this->getCouponCodeItems( array_keys( $data ) );
146
						$errcnt = $this->import( $items, $data, $couponId, $processor );
147
						$chunkcnt = count( $data );
148
149
						$msg = 'Imported coupon lines from "%1$s": %2$d/%3$d (%4$s)';
150
						$logger->log( sprintf( $msg, $filename, $chunkcnt - $errcnt, $chunkcnt, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
151
152
						$errors += $errcnt;
153
						$total += $chunkcnt;
154
						unset( $items, $data );
155
					}
156
				}
157
158
				$this->closeContainer( $container );
159
160
				$msg = 'Finished coupon import: %1$d successful, %2$s errors, %3$s total (%4$s)';
161
				$logger->log( sprintf( $msg, $total - $errors, $errors, $total, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
162
			}
163
		}
164
		catch( \Exception $e )
165
		{
166
			$logger->log( 'Coupon import error: ' . $e->getMessage() );
167
			$logger->log( $e->getTraceAsString() );
168
169
			throw $e;
170
		}
171
	}
172
173
174
	/**
175
	 * Closes the container and removes the temporary files
176
	 *
177
	 * @param \Aimeos\MW\Container\Iface $container Container object
178
	 */
179
	protected function closeContainer( \Aimeos\MW\Container\Iface $container )
180
	{
181
		$container->close();
182
		unlink( $container->getName() );
183
	}
184
185
186
	/**
187
	 * Returns the position of the "coupon.code" column from the coupon item mapping
188
	 *
189
	 * @param array $mapping Mapping of the "item" columns with position as key and code as value
190
	 * @return integer Position of the "coupon.code" column
191
	 * @throws \Aimeos\Controller\Jobs\Exception If no mapping for "coupon.code.code" is found
192
	 */
193
	protected function getCodePosition( array $mapping )
194
	{
195
		foreach( $mapping as $pos => $key )
196
		{
197
			if( $key === 'coupon.code.code' ) {
198
				return $pos;
199
			}
200
		}
201
202
		throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No "coupon.code.code" column in CSV mapping found' ) );
203
	}
204
205
206
	/**
207
	 * Opens and returns the container which includes the coupon data
208
	 *
209
	 * @param string $filepath Path to the container file
210
	 * @return \Aimeos\MW\Container\Iface Container object
211
	 */
212
	protected function getContainer( $filepath )
213
	{
214
		$config = $this->getContext()->getConfig();
215
216
		/** controller/jobs/coupon/import/csv/code/container/type
217
		 * Name of the container type to read the data from
218
		 *
219
		 * The container type tells the importer how it should retrieve the data.
220
		 * There are currently three container types that support the necessary
221
		 * CSV content:
222
		 * * File (plain)
223
		 * * Zip
224
		 * * PHPExcel
225
		 *
226
		 * '''Note:''' For the PHPExcel container, you need to install the
227
		 * "ai-container" extension.
228
		 *
229
		 * @param string Container type name
230
		 * @since 2017.10
231
		 * @category Developer
232
		 * @category User
233
		 * @see controller/jobs/coupon/import/csv/code/container/content
234
		 * @see controller/jobs/coupon/import/csv/code/container/options
235
		 */
236
		$container = $config->get( 'controller/jobs/coupon/import/csv/code/container/type', 'File' );
237
238
		/** controller/jobs/coupon/import/csv/code/container/content
239
		 * Name of the content type inside the container to read the data from
240
		 *
241
		 * The content type must always be a CSV-like format and there are
242
		 * currently two format types that are supported:
243
		 * * CSV
244
		 * * PHPExcel
245
		 *
246
		 * '''Note:''' for the PHPExcel content type, you need to install the
247
		 * "ai-container" extension.
248
		 *
249
		 * @param array Content type name
250
		 * @since 2017.10
251
		 * @category Developer
252
		 * @category User
253
		 * @see controller/jobs/coupon/import/csv/code/container/type
254
		 * @see controller/jobs/coupon/import/csv/code/container/options
255
		 */
256
		$content = $config->get( 'controller/jobs/coupon/import/csv/code/container/content', 'CSV' );
257
258
		/** controller/jobs/coupon/import/csv/code/container/options
259
		 * List of file container options for the coupon import files
260
		 *
261
		 * Some container/content type allow you to hand over additional settings
262
		 * for configuration. Please have a look at the article about
263
		 * {@link http://aimeos.org/docs/Developers/Utility/Create_and_read_files container/content files}
264
		 * for more information.
265
		 *
266
		 * @param array Associative list of option name/value pairs
267
		 * @since 2017.10
268
		 * @category Developer
269
		 * @category User
270
		 * @see controller/jobs/coupon/import/csv/code/container/content
271
		 * @see controller/jobs/coupon/import/csv/code/container/type
272
		 */
273
		$options = $config->get( 'controller/jobs/coupon/import/csv/code/container/options', [] );
274
275
		return \Aimeos\MW\Container\Factory::getContainer( $filepath, $container, $content, $options );
276
	}
277
278
279
	/**
280
	 * Imports the CSV data and creates new coupons or updates existing ones
281
	 *
282
	 * @param \Aimeos\MShop\Coupon\Item\Code\Iface[] $items List of coupons code items
283
	 * @param array $data Associative list of import data as index/value pairs
284
	 * @param string $couponId ID of the coupon item the coupon code should be added to
285
	 * @param \Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor Processor object
286
	 * @return integer Number of coupons that couldn't be imported
287
	 * @throws \Aimeos\Controller\Jobs\Exception
288
	 */
289
	protected function import( array $items, array $data, $couponId,
290
		\Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor )
291
	{
292
		$errors = 0;
293
		$context = $this->getContext();
294
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon/code' );
295
296
		foreach( $data as $code => $list )
297
		{
298
			$manager->begin();
299
300
			try
301
			{
302
				if( isset( $items[$code] ) ) {
303
					$item = $items[$code];
304
				} else {
305
					$item = $manager->createItem();
306
				}
307
308
				$item->setParentId( $couponId );
309
				$list = $processor->process( $item, $list );
0 ignored issues
show
Compatibility introduced by
$item of type object<Aimeos\MShop\Common\Item\Iface> is not a sub-type of object<Aimeos\MShop\Coupon\Item\Code\Iface>. It seems like you assume a child interface of the interface Aimeos\MShop\Common\Item\Iface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
310
311
				$manager->commit();
312
			}
313
			catch( \Exception $e )
314
			{
315
				$manager->rollback();
316
317
				$msg = sprintf( 'Unable to import coupon with code "%1$s": %2$s', $code, $e->getMessage() );
318
				$context->getLogger()->log( $msg );
319
320
				$errors++;
321
			}
322
		}
323
324
		return $errors;
325
	}
326
}
327