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

Standard::initCriteria()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 4
nop 2
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\Order\Export\Csv;
12
13
14
/**
15
 * Job controller for CSV order exports.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Jobs\Order\Export\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', 'Order export 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', 'Exports orders to CSV file' );
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
		$context = $this->getContext();
54
		$config = $context->getConfig();
55
		$logger = $context->getLogger();
56
		$mappings = $this->getDefaultMapping();
57
58
59
		/** controller/common/order/export/csv/mapping
60
		 * List of mappings between the position in the CSV file and item keys
61
		 *
62
		 * The exporter has to know which data is at which position in the CSV
63
		 * file. Therefore, you need to specify a mapping between each position
64
		 * and the MShop domain item key (e.g. "order.type") it represents.
65
		 *
66
		 * These mappings are grouped together by their processor names, which
67
		 * are responsible for exporting the data, e.g. all mappings in "invoice"
68
		 * will be managed by the invoice processor while the mappings in
69
		 * "product" will be exported by the product processor.
70
		 *
71
		 * @param array Associative list of processor names and lists of key/position pairs
72
		 * @since 2017.08
73
		 * @category Developer
74
		 * @see controller/common/order/export/csv/max-size
75
		 */
76
		$mappings = $config->get( 'controller/common/order/export/csv/mapping', $mappings );
77
78
		/** controller/jobs/order/export/csv/mapping
79
		 * List of mappings between the position in the CSV file and item keys
80
		 *
81
		 * This configuration setting overwrites the shared option
82
		 * "controller/common/order/export/csv/mapping" if you need a
83
		 * specific setting for the job controller. Otherwise, you should
84
		 * use the shared option for consistency.
85
		 *
86
		 * @param array Associative list of processor names and lists of key/position pairs
87
		 * @since 2017.08
88
		 * @category Developer
89
		 * @see controller/common/order/export/csv/max-size
90
		 */
91
		$mappings = $config->get( 'controller/jobs/order/export/csv/mapping', $mappings );
92
93
94
		/** controller/common/order/export/csv/max-size
95
		 * Maximum number of CSV rows to export at once
96
		 *
97
		 * It's more efficient to read and export more than one row at a time
98
		 * to speed up the export. Usually, the bigger the chunk that is exported
99
		 * at once, the less time the exporter will need. The downside is that
100
		 * the amount of memory required by the export process will increase as
101
		 * well. Therefore, it's a trade-off between memory consumption and
102
		 * export speed.
103
		 *
104
		 * @param integer Number of rows
105
		 * @since 2017.08
106
		 * @category Developer
107
		 * @see controller/common/order/export/csv/mapping
108
		 */
109
		$maxcnt = (int) $config->get( 'controller/common/order/export/csv/max-size', 1000 );
110
111
112
		$processors = $this->getProcessors( $mappings );
113
		$mq = $context->getMessageQueueManager()->get( 'mq-admin' )->getQueue( 'order-export' );
114
115
		while( ( $msg = $mq->get() ) !== null )
116
		{
117
			try
118
			{
119
				if( ( $data = json_decode( $msg->getBody(), true ) ) === null ) {
120
					throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'Invalid message: %2$s', $msg->getBody() ) );
121
				}
122
123
				$this->export( $processors, $data, $maxcnt );
124
			}
125
			catch( \Exception $e )
126
			{
127
				$logger->log( 'Order export error: ' . $e->getMessage() );
128
				$logger->log( $e->getTraceAsString() );
129
			}
130
131
			$mq->del( $msg );
132
		}
133
	}
134
135
136
	/**
137
	 * Creates a new job entry for the exported file
138
	 *
139
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item
140
	 * @param string $path Absolute path to the exported file
141
	 */
142
	protected function addJob( $context, $path )
143
	{
144
		$manager = \Aimeos\MAdmin\Factory::createManager( $context, 'job' );
145
146
		$item = $manager->createItem();
147
		$item->setResult( ['file' => $path] );
148
		$item->setLabel( $path );
149
150
		$manager->saveItem( $item, false );
151
	}
152
153
154
	/**
155
	 * Opens and returns the container which includes the order data
156
	 *
157
	 * @return \Aimeos\MW\Container\Iface Container object
158
	 */
159
	protected function getContainer()
160
	{
161
		$config = $this->getContext()->getConfig();
162
163
		/** controller/jobs/order/export/csv/location
164
		 * Temporary file or directory where the content is stored which should be exported
165
		 *
166
		 * The path can point to any supported container format as long as the
167
		 * content is in CSV format, e.g.
168
		 * * Directory container / CSV file
169
		 * * Zip container / compressed CSV file
170
		 * * PHPExcel container / PHPExcel sheet
171
		 *
172
		 * @param string Absolute file or directory path
173
		 * @since 2017.08
174
		 * @category Developer
175
		 * @category User
176
		 * @see controller/jobs/order/export/csv/container/type
177
		 * @see controller/jobs/order/export/csv/container/content
178
		 * @see controller/jobs/order/export/csv/container/options
179
		 */
180
		$location = $config->get( 'controller/jobs/order/export/csv/location', sys_get_temp_dir() );
181
182
		/** controller/jobs/order/export/csv/container/type
183
		 * Nave of the container type to read the data from
184
		 *
185
		 * The container type tells the exporter how it should retrieve the data.
186
		 * There are currently three container types that support the necessary
187
		 * CSV content:
188
		 * * Directory
189
		 * * Zip
190
		 * * PHPExcel
191
		 *
192
		 * '''Note:''' For the PHPExcel container, you need to install the
193
		 * "ai-container" extension.
194
		 *
195
		 * @param string Container type name
196
		 * @since 2015.05
197
		 * @category Developer
198
		 * @category User
199
		 * @see controller/jobs/order/export/csv/location
200
		 * @see controller/jobs/order/export/csv/container/content
201
		 * @see controller/jobs/order/export/csv/container/options
202
		 */
203
		$container = $config->get( 'controller/jobs/order/export/csv/container/type', 'Directory' );
204
205
		/** controller/jobs/order/export/csv/container/content
206
		 * Name of the content type inside the container to read the data from
207
		 *
208
		 * The content type must always be a CSV-like format and there are
209
		 * currently two format types that are supported:
210
		 * * CSV
211
		 * * PHPExcel
212
		 *
213
		 * '''Note:''' for the PHPExcel content type, you need to install the
214
		 * "ai-container" extension.
215
		 *
216
		 * @param array Content type name
217
		 * @since 2015.05
218
		 * @category Developer
219
		 * @category User
220
		 * @see controller/jobs/order/export/csv/location
221
		 * @see controller/jobs/order/export/csv/container/type
222
		 * @see controller/jobs/order/export/csv/container/options
223
		 */
224
		$content = $config->get( 'controller/jobs/order/export/csv/container/content', 'CSV' );
225
226
		/** controller/jobs/order/export/csv/container/options
227
		 * List of file container options for the order export files
228
		 *
229
		 * Some container/content type allow you to hand over additional settings
230
		 * for configuration. Please have a look at the article about
231
		 * {@link http://aimeos.org/docs/Developers/Utility/Create_and_read_files container/content files}
232
		 * for more information.
233
		 *
234
		 * @param array Associative list of option name/value pairs
235
		 * @since 2015.05
236
		 * @category Developer
237
		 * @category User
238
		 * @see controller/jobs/order/export/csv/location
239
		 * @see controller/jobs/order/export/csv/container/content
240
		 * @see controller/jobs/order/export/csv/container/type
241
		 */
242
		$options = $config->get( 'controller/jobs/order/export/csv/container/options', [] );
243
244
		return \Aimeos\MW\Container\Factory::getContainer( $location, $container, $content, $options );
245
	}
246
247
248
	/**
249
	 * Exports the orders and returns the exported file name
250
	 *
251
	 * @param Aimeos\Controller\Common\Order\Export\Csv\Processor\Iface[] List of processor objects
252
	 * @param array $msg Message data passed from the frontend
253
	 * @param integer $maxcnt Maximum number of retrieved orders at once
254
	 * @return string Path of the file containing the exported data
255
	 */
256
	protected function export( array $processors, $msg, $maxcnt )
257
	{
258
		$lcontext = $this->getLocaleContext( $msg );
259
		$baseRef = ['order/base/address', 'order.base.coupon', 'order/base/product', 'order/base/service'];
260
261
		$manager = \Aimeos\MShop\Factory::createManager( $lcontext, 'order' );
262
		$baseManager = \Aimeos\MShop\Factory::createManager( $lcontext, 'order/base' );
263
264
		$container = $this->getContainer();
265
		$content = $container->create( 'order-export_' . date( 'Y-m-d_H-i-s' ) );
266
		$search = $this->initCriteria( $manager->createSearch(), $msg );
267
		$start = 0;
268
269
		do
270
		{
271
			$baseIds = [];
272
			$search->setSlice( $start, $maxcnt );
273
			$items = $manager->searchItems( $search );
274
275
			foreach( $items as $item ) {
276
				$baseIds[] = $item->getBaseId();
277
			}
278
279
			$baseSearch = $baseManager->createSearch();
280
			$baseSearch->setConditions( $baseSearch->compare( '==', 'order.base.id', $baseIds ) );
281
			$baseSearch->setSlice( 0, count( $baseIds ) );
282
283
			$baseItems = $baseManager->searchItems( $baseSearch, $baseRef );
284
285
			foreach( $items as $id => $item )
286
			{
287
				foreach( $processors as $type => $processor )
288
				{
289
					foreach( $processor->process( $item, $baseItems[$item->getBaseId()] ) as $line ) {
290
						$content->add( [0 => $type, 1 => $id] + $line );
291
					}
292
				}
293
			}
294
295
			$count = count( $items );
296
			$start += $count;
297
		}
298
		while( $count === $search->getSliceSize() );
299
300
		$path = $content->getResource();
301
		$container->add( $content );
302
		$container->close();
303
304
		$path = $this->moveFile( $lcontext, $path );
305
		$this->addJob( $lcontext, $path );
306
	}
307
308
309
	/**
310
	 * Returns a new context including the locale from the message data
311
	 *
312
	 * @param array $msg Message data including a "sitecode" value
313
	 * @return \Aimeos\MShop\Context\Item\Iface New context item with updated locale
314
	 */
315
	protected function getLocaleContext( array $msg )
316
	{
317
		$lcontext = clone $this->getContext();
318
		$manager = \Aimeos\MShop\Factory::createManager( $lcontext, 'locale' );
319
320
		$sitecode = ( isset( $msg['sitecode'] ) ? $msg['sitecode'] : 'default' );
321
		$localeItem = $manager->bootstrap( $sitecode, '', '', false, \Aimeos\MShop\Locale\Manager\Base::SITE_ONE );
322
323
		return $lcontext->setLocale( $localeItem );
324
	}
325
326
327
	/**
328
	 * Initializes the search criteria
329
	 *
330
	 * @param \Aimeos\MW\Criteria\Iface $criteria New criteria object
331
	 * @param array $msg Message data
332
	 * @return \Aimeos\MW\Criteria\Iface Initialized criteria object
333
	 */
334
	protected function initCriteria( \Aimeos\MW\Criteria\Iface $criteria, array $msg )
335
	{
336
		if( isset( $msg['filter'] ) ) {
337
			$criteria->setConditions( $criteria->toConditions( $msg['filter'] ) );
338
		}
339
340
		if( isset( $msg['sort'] ) ) {
341
			$criteria->setSortations( $criteria->toSortations( $msg['sort'] ) );
342
		}
343
344
		return $criteria;
345
	}
346
347
348
	/**
349
	 * Moves the exported file to the final storage
350
	 *
351
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context item
352
	 * @param string $path Absolute path to the exported file
353
	 * @return string Relative path of the file in the storage
354
	 */
355
	protected function moveFile( $context, $path )
356
	{
357
		$filename = basename( $path );
358
		$context->getFileSystemManager()->get( 'fs-admin' )->writef( $filename, $path );
359
360
		unlink( $path );
361
		return $filename;
362
	}
363
}
364