Completed
Push — master ( a56798...527387 )
by Aimeos
03:05
created

Standard::closeContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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, ) = explode( '.', $filename );
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
				$container->close();
159
				$fs->rm( $dir . '/' . $filename );
160
161
				$msg = 'Finished coupon import: %1$d successful, %2$s errors, %3$s total (%4$s)';
162
				$logger->log( sprintf( $msg, $total - $errors, $errors, $total, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
163
			}
164
		}
165
		catch( \Exception $e )
166
		{
167
			$logger->log( 'Coupon import error: ' . $e->getMessage() );
168
			$logger->log( $e->getTraceAsString() );
169
170
			throw $e;
171
		}
172
	}
173
174
175
	/**
176
	 * Returns the position of the "coupon.code" column from the coupon item mapping
177
	 *
178
	 * @param array $mapping Mapping of the "item" columns with position as key and code as value
179
	 * @return integer Position of the "coupon.code" column
180
	 * @throws \Aimeos\Controller\Jobs\Exception If no mapping for "coupon.code.code" is found
181
	 */
182
	protected function getCodePosition( array $mapping )
183
	{
184
		foreach( $mapping as $pos => $key )
185
		{
186
			if( $key === 'coupon.code.code' ) {
187
				return $pos;
188
			}
189
		}
190
191
		throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No "coupon.code.code" column in CSV mapping found' ) );
192
	}
193
194
195
	/**
196
	 * Opens and returns the container which includes the coupon data
197
	 *
198
	 * @param string $filepath Path to the container file
199
	 * @return \Aimeos\MW\Container\Iface Container object
200
	 */
201
	protected function getContainer( $filepath )
202
	{
203
		$config = $this->getContext()->getConfig();
204
205
		/** controller/jobs/coupon/import/csv/code/container/type
206
		 * Name of the container type to read the data from
207
		 *
208
		 * The container type tells the importer how it should retrieve the data.
209
		 * There are currently three container types that support the necessary
210
		 * CSV content:
211
		 * * File (plain)
212
		 * * Zip
213
		 * * PHPExcel
214
		 *
215
		 * '''Note:''' For the PHPExcel container, you need to install the
216
		 * "ai-container" extension.
217
		 *
218
		 * @param string Container type name
219
		 * @since 2017.10
220
		 * @category Developer
221
		 * @category User
222
		 * @see controller/jobs/coupon/import/csv/code/container/content
223
		 * @see controller/jobs/coupon/import/csv/code/container/options
224
		 */
225
		$container = $config->get( 'controller/jobs/coupon/import/csv/code/container/type', 'File' );
226
227
		/** controller/jobs/coupon/import/csv/code/container/content
228
		 * Name of the content type inside the container to read the data from
229
		 *
230
		 * The content type must always be a CSV-like format and there are
231
		 * currently two format types that are supported:
232
		 * * CSV
233
		 * * PHPExcel
234
		 *
235
		 * '''Note:''' for the PHPExcel content type, you need to install the
236
		 * "ai-container" extension.
237
		 *
238
		 * @param array Content type name
239
		 * @since 2017.10
240
		 * @category Developer
241
		 * @category User
242
		 * @see controller/jobs/coupon/import/csv/code/container/type
243
		 * @see controller/jobs/coupon/import/csv/code/container/options
244
		 */
245
		$content = $config->get( 'controller/jobs/coupon/import/csv/code/container/content', 'CSV' );
246
247
		/** controller/jobs/coupon/import/csv/code/container/options
248
		 * List of file container options for the coupon import files
249
		 *
250
		 * Some container/content type allow you to hand over additional settings
251
		 * for configuration. Please have a look at the article about
252
		 * {@link http://aimeos.org/docs/Developers/Utility/Create_and_read_files container/content files}
253
		 * for more information.
254
		 *
255
		 * @param array Associative list of option name/value pairs
256
		 * @since 2017.10
257
		 * @category Developer
258
		 * @category User
259
		 * @see controller/jobs/coupon/import/csv/code/container/content
260
		 * @see controller/jobs/coupon/import/csv/code/container/type
261
		 */
262
		$options = $config->get( 'controller/jobs/coupon/import/csv/code/container/options', [] );
263
264
		return \Aimeos\MW\Container\Factory::getContainer( $filepath, $container, $content, $options );
265
	}
266
267
268
	/**
269
	 * Imports the CSV data and creates new coupons or updates existing ones
270
	 *
271
	 * @param \Aimeos\MShop\Coupon\Item\Code\Iface[] $items List of coupons code items
272
	 * @param array $data Associative list of import data as index/value pairs
273
	 * @param string $couponId ID of the coupon item the coupon code should be added to
274
	 * @param \Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor Processor object
275
	 * @return integer Number of coupons that couldn't be imported
276
	 * @throws \Aimeos\Controller\Jobs\Exception
277
	 */
278
	protected function import( array $items, array $data, $couponId,
279
		\Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor )
280
	{
281
		$errors = 0;
282
		$context = $this->getContext();
283
		$manager = \Aimeos\MShop\Factory::createManager( $context, 'coupon/code' );
284
285
		foreach( $data as $code => $list )
286
		{
287
			$manager->begin();
288
289
			try
290
			{
291
				if( isset( $items[$code] ) ) {
292
					$item = $items[$code];
293
				} else {
294
					$item = $manager->createItem();
295
				}
296
297
				$item->setParentId( $couponId );
298
				$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...
299
300
				$manager->commit();
301
			}
302
			catch( \Exception $e )
303
			{
304
				$manager->rollback();
305
306
				$msg = sprintf( 'Unable to import coupon with code "%1$s": %2$s', $code, $e->getMessage() );
307
				$context->getLogger()->log( $msg );
308
309
				$errors++;
310
			}
311
		}
312
313
		return $errors;
314
	}
315
}
316