3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2013-2014 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2013-2014 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7.6dev - 2013-08-27
41 use Galette\Core\Preferences
;
42 use Galette\Core\History
;
43 use Galette\Entity\Adherent
;
44 use Galette\Entity\ImportModel
;
45 use Galette\Entity\FieldsConfig
;
46 use Galette\Entity\Status
;
47 use Galette\Entity\Title
;
48 use Galette\Repository\Titles
;
49 use Galette\IO\FileTrait
;
50 use Galette\Repository\Members
;
58 * @author Johan Cwiklinski <johan@x-tnd.be>
59 * @copyright 2013-2014 The Galette Team
60 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
61 * @link http://galette.tuxfamily.org
62 * @since Available since 0.7.6dev - 2013-08-27
65 class CsvIn
extends Csv
implements FileInterface
69 const DEFAULT_DIRECTORY
= GALETTE_IMPORTS_PATH
;
70 const DATA_IMPORT_ERROR
= -10;
72 protected $extensions = array('csv', 'txt');
75 private $_default_fields = array(
97 private $_dryrun = true;
99 private $_members_fields;
100 private $_members_fields_cats;
107 private $preferences;
111 * Default constructor
113 * @param Db $zdb Database
115 public function __construct(Db
$zdb)
119 self
::DEFAULT_DIRECTORY
,
123 'txt' => 'text/plain'
128 parent
::__construct(self
::DEFAULT_DIRECTORY
);
132 * Load fields list from database or from default values
136 private function loadFields()
138 //at last, we got the defaults
139 $this->_fields
= $this->_default_fields
;
141 $model = new ImportModel();
142 //we go with default fields if model cannot be loaded
143 if ($model->load()) {
144 $this->_fields
= $model->getFields();
153 public function getDefaultFields()
155 return $this->_default_fields
;
159 * Import members from CSV file
161 * @param Db $zdb Database instance
162 * @param Preferences $preferences Preferences instance
163 * @param History $history History instance
164 * @param string $filename CSV filename
165 * @param array $members_fields Members fields
166 * @param array $members_fields_cats Members fields categories
167 * @param boolean $dryrun Run in dry run mode (do not store in database)
171 public function import(
173 Preferences
$preferences,
176 array $members_fields,
177 array $members_fields_cats,
181 !file_exists(self
::DEFAULT_DIRECTORY
. '/' . $filename)
182 ||
!is_readable(self
::DEFAULT_DIRECTORY
. '/' . $filename)
185 'File ' . $filename . ' does not exists or cannot be read.',
192 $this->preferences
= $preferences;
193 $this->history
= $history;
194 if ($dryrun === false) {
195 $this->_dryrun
= false;
199 $this->_members_fields
= $members_fields;
200 $this->_members_fields_cats
= $members_fields_cats;
202 if (!$this->check($filename)) {
203 return self
::INVALID_FILE
;
206 if (!$this->storeMembers($filename)) {
207 return self
::DATA_IMPORT_ERROR
;
214 * Check if input file meet requirements
216 * @param string $filename File name
220 private function check($filename)
222 $handle = fopen(self
::DEFAULT_DIRECTORY
. '/' . $filename, 'r');
225 'File ' . $filename . ' cannot be open!',
232 _T('File %filename cannot be open!')
238 $cnt_fields = count($this->_fields
);
240 //check required fields
241 $fc = new FieldsConfig(
244 $this->_members_fields
,
245 $this->_members_fields_cats
247 $config_required = $fc->getRequired();
248 $this->_required
= array();
250 foreach (array_keys($config_required) as $field) {
251 if (in_array($field, $this->_fields
)) {
252 $this->_required
[$field] = $field;
256 $member = new Adherent($this->zdb
);
258 $member->setDependencies(
260 $this->_members_fields
,
269 self
::DEFAULT_SEPARATOR
,
274 $count = count($data);
275 if ($count != $cnt_fields) {
278 array('%should_count', '%count', '%row'),
279 array($cnt_fields, $count, $row),
280 _T("Fields count mismatch... There should be %should_count fields and there are %count (row %row)")
287 //header line is the first one. Here comes data
290 foreach ($data as $column) {
291 $column = trim($column);
293 //check required fields
295 in_array($this->_fields
[$col], $this->_required
)
300 array('%field', '%row'),
301 array($this->_fields
[$col], $row),
302 _T("Field %field is required, but missing in row %row")
309 //if missing, set default one; if not check it does exists
310 if ($this->_fields
[$col] == Status
::PK
) {
311 if (empty($column)) {
312 $column = Status
::DEFAULT_STATUS
;
314 if ($this->statuses
=== null) {
315 //load existing status
316 $status = new Status($this->zdb
);
317 $this->statuses
= $status->getList();
319 if (!isset($this->statuses
[$column])) {
324 _T("Status %status does not exists!")
333 if ($this->_fields
[$col] == 'titre_adh' && !empty($column)) {
334 if ($this->titles
=== null) {
335 //load existing titles
336 $this->titles
= Titles
::getList($this->zdb
);
338 if (!isset($this->titles
[$column])) {
343 _T("Title %title does not exists!")
350 //check for email unicity
351 if ($this->_fields
[$col] == 'email_adh' && !empty($column)) {
352 if ($this->emails
=== null) {
353 //load existing emails
354 $this->emails
= Members
::getEmails($this->zdb
);
356 if (isset($this->emails
[$column])) {
357 $existing = $this->emails
[$column];
358 $extra = ($existing == -1 ?
359 _T("from another member in import") : str_replace('%id_adh', $existing, _T("from member %id_adh"))
363 ['%address', '%extra'],
365 _T("Email address %address is already used! (%extra)")
371 $this->emails
[$column] = -1;
376 if ($this->_fields
[$col] == 'pref_lang') {
377 if ($this->langs
=== null) {
378 //load existing titles
380 $this->langs
= $i18n->getArrayList();
382 if (empty($column)) {
383 $column = $this->preferences
->pref_lang
;
385 if (!isset($this->langs
[$column])) {
390 _T("Lang %lang does not exists!")
399 if ($this->_fields
[$col] == 'mdp_adh' && !empty($column)) {
400 $this->_fields
['mdp_adh2'] = $column;
403 if (substr($this->_fields
[$col], 0, strlen('dynfield_')) === 'dynfield_') {
404 //dynamic field, keep to check later
405 $dfields[$this->_fields
[$col] . '_1'] = $column;
408 $member->validate($this->_fields
[$col], $column, $this->_fields
);
410 $errors = $member->getErrors();
411 if (count($errors)) {
412 foreach ($errors as $error) {
413 $this->addError($error);
421 //check dynamic fields
422 $errcnt = count($errors);
423 $member->dynamicsValidate($dfields);
424 $errors = $member->getErrors();
425 if (count($errors) > $errcnt) {
426 $lcnt = ($errcnt > 0 ?
$errcnt - 1 : 0);
427 $cnt_err = count($errors);
428 for ($i = $lcnt; $i < $cnt_err; ++
$i) {
429 $this->addError($errors[$i]);
440 //no data in file, just headers line
451 * Store members in database
453 * @param string $filename CSV filename
457 private function storeMembers($filename)
459 $handle = fopen(self
::DEFAULT_DIRECTORY
. '/' . $filename, 'r');
464 $this->zdb
->connection
->beginTransaction();
469 self
::DEFAULT_SEPARATOR
,
476 foreach ($data as $column) {
477 if (substr($this->_fields
[$col], 0, strlen('dynfield_')) === 'dynfield_') {
478 //dynamic field, keep to check later
479 $values[str_replace('dynfield_', 'info_field_', $this->_fields
[$col] . '_1')] = $column;
484 $values[$this->_fields
[$col]] = $column;
485 if ($this->_fields
[$col] === 'societe_adh') {
486 $values['is_company'] = true;
490 ($this->_fields
[$col] == 'bool_admin_adh'
491 ||
$this->_fields
[$col] == 'bool_exempt_adh'
492 ||
$this->_fields
[$col] == 'bool_display_info'
493 ||
$this->_fields
[$col] == 'activite_adh')
494 && ($column == null ||
trim($column) == '')
496 $values[$this->_fields
[$col]] = 0; //defaults to 0 as in Adherent
499 if ($this->_fields
[$col] == Status
::PK
&& empty(trim($column))) {
500 $values[Status
::PK
] = Status
::DEFAULT_STATUS
;
505 //import member itself
506 $member = new Adherent($this->zdb
);
507 $member->setDependencies(
509 $this->_members_fields
,
512 //check for empty creation date
513 if (isset($values['date_crea_adh']) && trim($values['date_crea_adh']) === '') {
514 unset($values['date_crea_adh']);
516 if (isset($values['mdp_adh'])) {
517 $values['mdp_adh2'] = $values['mdp_adh'];
520 $valid = $member->check($values, $this->_required
, null);
521 if ($valid === true) {
522 if ($this->_dryrun
=== false) {
523 $store = $member->store();
524 if ($store !== true) {
527 array('%row', '%name'),
528 array($row, $member->sname
),
529 _T("An error occurred storing member at row %row (%name):")
538 array('%row', '%name'),
539 array($row, $member->sname
),
540 _T("An error occurred storing member at row %row (%name):")
543 if (is_array($valid)) {
544 foreach ($valid as $e) {
553 $this->zdb
->connection
->commit();
555 } catch (\Exception
$e) {
556 $this->zdb
->connection
->rollBack();
557 $this->addError($e->getMessage());
564 * Return textual error message
566 * @param int $code The error code
568 * @return string Localized message
570 public function getErrorMessage($code)
574 case self
::DATA_IMPORT_ERROR
:
575 $error = _T("An error occurred while importing members");
579 if ($error === null) {
580 $error = $this->getErrorMessageFromCode($code);