Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
34.18% |
27 / 79 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
ItemExporter | |
34.18% |
27 / 79 |
|
0.00% |
0 / 11 |
405.60 | |
0.00% |
0 / 1 |
__construct | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
4.37 | |||
export | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getDataByItems | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getDataByItem | |
95.65% |
22 / 23 |
|
0.00% |
0 / 1 |
9 | |||
save | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
72 | |||
getFilePath | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getHeaders | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getItems | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultUriClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCsvEnclosure | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getCsvDelimiter | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2015 (original work) Open Assessment Technologies SA; |
19 | * |
20 | * |
21 | */ |
22 | |
23 | namespace oat\taoQtiItem\model\flyExporter\simpleExporter; |
24 | |
25 | use oat\oatbox\service\ConfigurableService; |
26 | use oat\tao\model\TaoOntology; |
27 | use oat\taoQtiItem\model\flyExporter\extractor\Extractor; |
28 | use oat\taoQtiItem\model\flyExporter\extractor\ExtractorException; |
29 | |
30 | /** |
31 | * Class ItemExporter |
32 | * |
33 | * @package oat\taoQtiItem\model\flyExporter\simpleExporter |
34 | */ |
35 | class ItemExporter extends ConfigurableService implements SimpleExporter |
36 | { |
37 | /** |
38 | * Default csv delimiter |
39 | */ |
40 | public const CSV_DELIMITER = ','; |
41 | |
42 | /** |
43 | * Default csv enclosure |
44 | */ |
45 | public const CSV_ENCLOSURE = '"'; |
46 | |
47 | /** |
48 | * Default property delimiter |
49 | */ |
50 | public const DEFAULT_PROPERTY_DELIMITER = '|'; |
51 | |
52 | /** |
53 | * Optional config option to set CSV enclosure |
54 | */ |
55 | public const CSV_ENCLOSURE_OPTION = 'enclosure'; |
56 | |
57 | /** |
58 | * Optional config option to set CSV delimiter |
59 | */ |
60 | public const CSV_DELIMITER_OPTION = 'delimiter'; |
61 | |
62 | /** |
63 | * Location of the export file, a relative path with the name of final file. |
64 | * The final destination will be under /tmp. |
65 | */ |
66 | public const OPTION_FILE_LOCATION = 'fileLocation'; |
67 | |
68 | /** |
69 | * CSV file headers |
70 | * |
71 | * @var array |
72 | */ |
73 | protected $headers = []; |
74 | |
75 | /** |
76 | * Columns requested by export |
77 | * |
78 | * @var array |
79 | */ |
80 | protected $columns = []; |
81 | |
82 | /** |
83 | * Available extractors |
84 | * |
85 | * @var array |
86 | */ |
87 | protected $extractors = []; |
88 | |
89 | public function __construct(array $options) |
90 | { |
91 | parent::__construct($options); |
92 | |
93 | if (!$this->hasOption(self::OPTION_FILE_LOCATION)) { |
94 | throw new ExtractorException('File location config is not correctly set.'); |
95 | } |
96 | |
97 | $this->extractors = $this->getOption('extractors'); |
98 | $this->columns = $this->getOption('columns'); |
99 | if (!$this->extractors || !$this->columns) { |
100 | throw new ExtractorException('Data config is not correctly set.'); |
101 | } |
102 | } |
103 | |
104 | /** |
105 | * @inheritdoc |
106 | * |
107 | * @throws ExtractorException |
108 | * @param \core_kernel_classes_Resource[] $items |
109 | * @return string |
110 | */ |
111 | public function export(array $items = null) |
112 | { |
113 | $this->headers = []; |
114 | if (empty($items)) { |
115 | $items = $this->getItems(); |
116 | } |
117 | |
118 | $data = $this->getDataByItems($items); |
119 | |
120 | return $this->save($this->headers, $data); |
121 | } |
122 | |
123 | /** |
124 | * Loop all items and call extract function |
125 | * |
126 | * @param array $items |
127 | * @return array |
128 | * @throws ExtractorException |
129 | */ |
130 | public function getDataByItems(array $items) |
131 | { |
132 | $output = []; |
133 | foreach ($items as $item) { |
134 | try { |
135 | $output[] = $this->getDataByItem($item); |
136 | } catch (ExtractorException $e) { |
137 | \common_Logger::e('ERROR on item ' . $item->getUri() . ' : ' . $e->getMessage()); |
138 | } |
139 | } |
140 | |
141 | if (empty($output)) { |
142 | throw new ExtractorException('No data item to export.'); |
143 | } |
144 | |
145 | return $output; |
146 | } |
147 | |
148 | /** |
149 | * Loop foreach columns and extract data thought extractors |
150 | * |
151 | * @param \core_kernel_classes_Resource $item |
152 | * @return array |
153 | */ |
154 | public function getDataByItem(\core_kernel_classes_Resource $item) |
155 | { |
156 | foreach ($this->columns as $column => $config) { |
157 | /** @var Extractor $extractor */ |
158 | $extractor = $this->extractors[$config['extractor']]; |
159 | if (isset($config['parameters'])) { |
160 | $parameters = $config['parameters']; |
161 | } else { |
162 | $parameters = []; |
163 | } |
164 | $extractor->addColumn($column, $parameters); |
165 | } |
166 | |
167 | $data = ['0' => []]; |
168 | foreach ($this->extractors as $extractor) { |
169 | $extractor->setItem($item); |
170 | $extractor->run(); |
171 | $values = $extractor->getData(); |
172 | |
173 | foreach ($values as $key => $value) { |
174 | $interactionData = is_array($value) && count($value) > 1 ? $value : $values; |
175 | |
176 | if ( |
177 | array_values( |
178 | array_intersect(array_keys($data[0]), array_keys($interactionData)) |
179 | ) === array_keys($interactionData) |
180 | ) { |
181 | $line = array_intersect_key($data[0], array_flip($this->headers)); |
182 | $data[] = array_merge($line, $interactionData); |
183 | } else { |
184 | foreach ($data as &$piece) { |
185 | $piece = array_merge($piece, $interactionData); |
186 | } |
187 | unset($piece); |
188 | } |
189 | |
190 | $this->headers = array_unique(array_merge($this->headers, array_keys($interactionData))); |
191 | } |
192 | } |
193 | |
194 | return $data; |
195 | } |
196 | |
197 | /** |
198 | * Save data to file |
199 | * |
200 | * @param array $headers |
201 | * @param array $data |
202 | * @return string |
203 | */ |
204 | public function save(array $headers, array $data) |
205 | { |
206 | $output = $contents = []; |
207 | |
208 | $enclosure = $this->getCsvEnclosure(); |
209 | $delimiter = $this->getCsvDelimiter(); |
210 | |
211 | $contents[] = $enclosure |
212 | . implode($enclosure . $delimiter . $enclosure, $headers) |
213 | . $enclosure; |
214 | |
215 | if (!empty($data)) { |
216 | foreach ($data as $item) { |
217 | foreach ($item as $line) { |
218 | foreach ($headers as $index => $value) { |
219 | if (isset($line[$value]) && $line[$value] !== '') { |
220 | $output[$value] = $enclosure . (string)$line[$value] . $enclosure; |
221 | unset($line[$value]); |
222 | } else { |
223 | $output[$value] = ''; |
224 | } |
225 | } |
226 | $contents[] = implode($delimiter, array_merge($output, $line)); |
227 | } |
228 | } |
229 | } |
230 | |
231 | $filePath = $this->getFilePath(); |
232 | |
233 | if (file_put_contents($filePath, chr(239) . chr(187) . chr(191) . implode("\n", $contents))) { |
234 | $this->headers = []; |
235 | return $filePath; |
236 | } |
237 | |
238 | return ''; |
239 | } |
240 | |
241 | private function getFilePath() |
242 | { |
243 | $filePath = \tao_helpers_Export::getExportPath() . DIRECTORY_SEPARATOR |
244 | . ltrim($this->getOption(self::OPTION_FILE_LOCATION), '/'); |
245 | |
246 | $basePath = dirname($filePath); |
247 | |
248 | // if "self::OPTION_FILE_LOCATION" contains folder(s), we have to create it |
249 | if (!file_exists($basePath)) { |
250 | mkdir($basePath, 0777, true); |
251 | } |
252 | |
253 | return $filePath; |
254 | } |
255 | |
256 | /** |
257 | * Get csv headers |
258 | * |
259 | * @return array |
260 | */ |
261 | public function getHeaders() |
262 | { |
263 | return $this->headers; |
264 | } |
265 | |
266 | /** |
267 | * Get all items of given uri otherwise get default class |
268 | * |
269 | * @return array |
270 | */ |
271 | protected function getItems() |
272 | { |
273 | $class = new \core_kernel_classes_Class($this->getDefaultUriClass()); |
274 | |
275 | return $class->getInstances(true); |
276 | } |
277 | |
278 | /** |
279 | * Get default class e.q. root class |
280 | * |
281 | * @return mixed |
282 | */ |
283 | protected function getDefaultUriClass() |
284 | { |
285 | return TaoOntology::CLASS_URI_ITEM; |
286 | } |
287 | |
288 | /** |
289 | * Get the CSV enclosure from config, if not set use default value |
290 | * |
291 | * @return string |
292 | */ |
293 | protected function getCsvEnclosure() |
294 | { |
295 | if ($this->hasOption(self::CSV_ENCLOSURE_OPTION)) { |
296 | return $this->getOption(self::CSV_ENCLOSURE_OPTION); |
297 | } |
298 | |
299 | return self::CSV_ENCLOSURE; |
300 | } |
301 | |
302 | /** |
303 | * Get the CSV delimiter from config, if not set use default value |
304 | * |
305 | * @return string |
306 | */ |
307 | protected function getCsvDelimiter() |
308 | { |
309 | if ($this->hasOption(self::CSV_DELIMITER_OPTION)) { |
310 | return $this->getOption(self::CSV_DELIMITER_OPTION); |
311 | } |
312 | |
313 | return self::CSV_DELIMITER; |
314 | } |
315 | } |