Overview
Comment:Migrate user import to using CSV_Import
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 83c91d0799d52d3c398d3b77818649caef146de7
User & Date: bohwaz on 2020-11-22 19:47:49
Other Links: branch diff | manifest | tags
Context
2020-11-22
19:48
Not possible to merge columns anymore check-in: 9e46483296 user: bohwaz tags: dev
19:47
Migrate user import to using CSV_Import check-in: 83c91d0799 user: bohwaz tags: dev
19:30
Update config.dist.php file to comment everything by default check-in: da1574366c user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/CSV_Custom.php from [53c7fd6e8d] to [12dd8c64a5].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

namespace Garradin;

use KD2\UserSession;

class CSV_Custom
{
	protected $session;
	protected $key;
	protected $csv;
	protected $translation;
	protected $columns;
	protected $mandatory_columns;
	protected $skip = 1;

	public function __construct(UserSession $session, string $key)
	{
		$this->session = $session;
		$this->key = $key;
		$this->csv = $this->session->get($this->key);
		$this->translation = $this->session->get($this->key . '_translation') ?: [];
		$this->skip = $this->session->get($this->key . '_skip') ?: 1;
	}

	public function load(array $file)
	{
		if (empty($file['size']) || empty($file['tmp_name'])) {
			throw new UserException('Fichier invalide');
		}

		$csv = CSV::readAsArray($_FILES['file']['tmp_name']);

		if (!count($csv)) {
			throw new UserException('Ce fichier est vide (aucune ligne trouvée).');
		}

		$this->session->set($this->key, $csv);
	}













|

















|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

namespace Garradin;

use KD2\UserSession;

class CSV_Custom
{
	protected $session;
	protected $key;
	protected $csv;
	protected $translation;
	protected $columns;
	protected $mandatory_columns = [];
	protected $skip = 1;

	public function __construct(UserSession $session, string $key)
	{
		$this->session = $session;
		$this->key = $key;
		$this->csv = $this->session->get($this->key);
		$this->translation = $this->session->get($this->key . '_translation') ?: [];
		$this->skip = $this->session->get($this->key . '_skip') ?: 1;
	}

	public function load(array $file)
	{
		if (empty($file['size']) || empty($file['tmp_name'])) {
			throw new UserException('Fichier invalide');
		}

		$csv = CSV::readAsArray($file['tmp_name']);

		if (!count($csv)) {
			throw new UserException('Ce fichier est vide (aucune ligne trouvée).');
		}

		$this->session->set($this->key, $csv);
	}

Modified src/include/lib/Garradin/Membres/Import.php from [e86cd8f598] to [0c863b55dc].

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php

namespace Garradin\Membres;

use Garradin\Membres;
use Garradin\Config;
use Garradin\DB;
use Garradin\Utils;
use Garradin\CSV;

use Garradin\UserException;

class Import
{
	/**
	 * Importer un CSV générique
	 * @param  string $path              Chemin vers le CSV
	 * @param  array  $translation_table Tableau indiquant la correspondance à effectuer entre les colonnes
	 * du CSV et les champs de Garradin. Par exemple : ['Date création fiche' => 'date_inscription']
	 * @return boolean                   TRUE en cas de succès
	 */
	public function fromArray(array $table, $translation_table, $current_user_id, $skip_lines = 0)
	{
		$db = DB::getInstance();
		$db->begin();
		$membres = new Membres;
		$champs = Config::getInstance()->get('champs_membres');

		$nb_columns = count($translation_table);

		if ($skip_lines)
		{
			$table = array_slice($table, $skip_lines, null, true);
		}

		foreach ($table as $line => $row)
		{
			if (empty($row))
			{
				continue;
			}

			if (count($row) != $nb_columns)
			{
				$db->rollback();
				throw new UserException('Erreur sur la ligne ' . $line . ' : le nombre de colonnes est incorrect.');
			}

			$data = [];

			foreach ($translation_table as $column_index => $garradin_field)
			{
				// Champs qu'on ne veut pas importer
				if (empty($garradin_field))
				{
					continue;
				}

				// Concaténer plusieurs champs, si on choisit d'indiquer plusieurs fois
				// le même champ pour plusieurs colonnes (par exemple pour mettre nom et prénom
				// dans un seul champ)
				if (isset($data[$garradin_field]))
				{
					$champ = $champs->get($garradin_field);

					if ($champ->type == 'text')
					{
						$data[$garradin_field] .= ' ' . $row[$column_index];
					}
					elseif ($champ->type == 'textarea')
					{
						$data[$garradin_field] .= "\n" . $row[$column_index];
					}
					else
					{
						throw new UserException(sprintf('Erreur sur la ligne %d : impossible de concaténer des colonnes avec le champ %s : n\'est pas un champ de type texte', $line, $champ->title));
					}
				}
				else
				{
					$data[$garradin_field] = $row[$column_index];
				}
			}

			if (!empty($data['numero']) && $data['numero'] > 0)
			{
				$numero = (int)$data['numero'];
			}
			else
			{
				unset($data['numero']);
				$numero = false;
			}

			try {
				if ($numero && ($id = $membres->getIDWithNumero($numero)))
				{
					if ($id === $current_user_id)
					{
						// Ne pas modifier le membre courant, on risque de se tirer une balle dans le pied
						continue;
					}

					$membres->edit($id, $data);
				}
				else
				{
					$membres->add($data, false);
				}
			}
			catch (UserException $e)
			{
				$db->rollback();
				throw new UserException('Erreur sur la ligne ' . $line . ' : ' . $e->getMessage());
			}









>






<
<
<


|




<

<
<
<
<
<
<
<
|

|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|



|












|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16



17
18
19
20
21
22
23

24







25
26
27
28















































29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php

namespace Garradin\Membres;

use Garradin\Membres;
use Garradin\Config;
use Garradin\DB;
use Garradin\Utils;
use Garradin\CSV;
use Garradin\CSV_Custom;
use Garradin\UserException;

class Import
{
	/**
	 * Importer un CSV générique



	 * @return boolean                   TRUE en cas de succès
	 */
	public function fromCustomCSV(CSV_Custom $csv, int $current_user_id)
	{
		$db = DB::getInstance();
		$db->begin();
		$membres = new Membres;









		foreach ($csv->iterate() as $line => $row)
		{
			if (!empty($row->numero) && $row->numero > 0)
			{















































				$numero = (int)$row->numero;
			}
			else
			{
				unset($row->numero);
				$numero = false;
			}

			try {
				if ($numero && ($id = $membres->getIDWithNumero($numero)))
				{
					if ($id === $current_user_id)
					{
						// Ne pas modifier le membre courant, on risque de se tirer une balle dans le pied
						continue;
					}

					$membres->edit($id, (array)$row);
				}
				else
				{
					$membres->add((array)$row, false);
				}
			}
			catch (UserException $e)
			{
				$db->rollback();
				throw new UserException('Erreur sur la ligne ' . $line . ' : ' . $e->getMessage());
			}

Modified src/templates/admin/membres/import.tpl from [ded8d62c89] to [6008c3dcf1].

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
    <p class="block confirm">
        L'import s'est bien déroulé.
    </p>
{/if}

<form method="post" action="{$self_url}" enctype="multipart/form-data">

    {if $csv_file}

    <fieldset>
        <legend>Importer depuis un fichier CSV générique</legend>
        <p class="help">{$csv_file|count} lignes trouvées dans le fichier</p>
        <dl>
            <dt><label><input type="checkbox" name="skip_first_line" value="1" checked="checked" /> Ne pas importer la première ligne</label></dt>
            <dd class="help">Décocher cette case si la première ligne ne contient pas l'intitulé des colonnes mais des données.</dd>
            <dt><label>Correspondance des champs</label></dt>
            <dd class="help">Indiquer la correspondance entre colonnes du CSV et champs des fiches membre.</dd>
            <dd>
                <table class="list auto">
                    <tbody>
                    {foreach from=$csv_first_line key="index" item="csv_field"}
                        <tr>
                            <th>{$csv_field}</th>
                            <td>
                                <select name="csv_translate[{$index}]">
                                    <option value="">-- Ne pas importer ce champ</option>
                                    {foreach from=$garradin_champs item="champ" key="name"}
                                        {if $champ.type == 'multiple' || $champ.type == 'file' || $name == 'passe'}{continue}{/if}
                                        <option value="{$name}">{$champ.title}</option>
                                    {/foreach}
                                </select>
                            </td>
                        </tr>
                    {/foreach}
                    </tbody>
                </table>
            </dd>
            <dd class="help">Pour fusionner des colonnes, il suffit d'indiquer le même nom de champ pour plusieurs colonnes.</dd>
        </dl>
    </fieldset>

    <input type="hidden" name="csv_encoded" value="{$csv_file|escape:'json'|escape}" />

    {else}

    <fieldset>
        <legend>Importer depuis un fichier</legend>
        <dl>
            <dt><label for="f_file">Fichier à importer</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd class="help">La taille maximale du fichier est de {$max_upload_size|format_bytes}.</dd>
            <dd><input type="file" name="upload" id="f_file" required="required" /></dd>
            <dt><label for="f_type">Type de fichier</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <input type="radio" name="type" id="f_type" value="garradin" {form_field name=type checked="garradin" default="garradin"} />
                <label for="f_type">Fichier CSV de Garradin</label>
            </dd>
            <dd class="help">
                Export de la liste des membres au format CSV provenant de Garradin.
                Les lignes comportant un numéro de membre mettront à jour les fiches des membres ayant ce numéro (si le numéro existe),
                les lignes sans numéro ou avec un numéro inexistant créeront de nouveaux membres.
            </dd>
            <dd>
                <input type="radio" name="type" id="f_type_csv" value="csv" {form_field name=type checked="csv"} />
                <label for="f_type_csv">Fichier CSV générique</label>
            </dd>
            <dd class="help">
                Vous pourrez choisir la correspondance entre colonnes du CSV et champs des fiches membres
                dans le prochain écran.
            </dd>
        </dl>
    </fieldset>

    {/if}

    <p class="submit">
        {csrf_field key="membres_import"}
        {button type="submit" name="import" label="Importer" shape="upload" class="main"}
    </p>

</form>

{include file="admin/_foot.tpl"}







|

<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<




















|












|






16
17
18
19
20
21
22
23
24













25
26













27


28

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    <p class="block confirm">
        L'import s'est bien déroulé.
    </p>
{/if}

<form method="post" action="{$self_url}" enctype="multipart/form-data">

    {if $csv->loaded()}














        {include file="common/_csv_match_columns.tpl"}














        <p class="help">Astuce : pour fusionner des colonnes, il suffit d'indiquer le même nom de champ pour plusieurs colonnes.


            Par exemple si la fiche membre a un champ «&nbsp;Nom et prénom&nbsp;» mais le CSV deux colonnes «&nbsp;Nom&nbsp;» et «&nbsp;Prénom&nbsp;» séparés, choisir le même champ «&nbsp;Nom et prénom&nbsp;» pour les deux colonnes.</p>


    {else}

    <fieldset>
        <legend>Importer depuis un fichier</legend>
        <dl>
            <dt><label for="f_file">Fichier à importer</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd class="help">La taille maximale du fichier est de {$max_upload_size|format_bytes}.</dd>
            <dd><input type="file" name="upload" id="f_file" required="required" /></dd>
            <dt><label for="f_type">Type de fichier</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <input type="radio" name="type" id="f_type" value="garradin" {form_field name=type checked="garradin" default="garradin"} />
                <label for="f_type">Fichier CSV de Garradin</label>
            </dd>
            <dd class="help">
                Export de la liste des membres au format CSV provenant de Garradin.
                Les lignes comportant un numéro de membre mettront à jour les fiches des membres ayant ce numéro (si le numéro existe),
                les lignes sans numéro ou avec un numéro inexistant créeront de nouveaux membres.
            </dd>
            <dd>
                <input type="radio" name="type" id="f_type_csv" value="custom" {form_field name=type checked="csv"} />
                <label for="f_type_csv">Fichier CSV générique</label>
            </dd>
            <dd class="help">
                Vous pourrez choisir la correspondance entre colonnes du CSV et champs des fiches membres
                dans le prochain écran.
            </dd>
        </dl>
    </fieldset>

    {/if}

    <p class="submit">
        {csrf_field key=$csrf_key}
        {button type="submit" name="import" label="Importer" shape="upload" class="main"}
    </p>

</form>

{include file="admin/_foot.tpl"}

Modified src/templates/common/_csv_match_columns.tpl from [6da8cbfb4e] to [f8a953f97c].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<fieldset>
	<legend>Importer depuis un fichier CSV générique</legend>
	<dl>
		<dd class="help">{$csv->count()} lignes trouvées dans le fichier</dd>
		<dt>{input type="checkbox" name="skip_first_line" value="1" label="Ne pas importer la première ligne" help="Décocher cette case si la première ligne ne contient pas l'intitulé des colonnes, mais des données" default=1}
		<dt><label>Correspondance des colonnes</label></dt>
		<dd class="help">Indiquer la correspondance entre colonnes du CSV et données comptables.</dd>
		<dd>
			<table class="list auto">
				<tbody>
				{foreach from=$csv->getFirstLine() key="index" item="csv_field"}
					<tr>
						<th>{$csv_field}</th>
						<td>






<







1
2
3
4
5
6

7
8
9
10
11
12
13
<fieldset>
	<legend>Importer depuis un fichier CSV générique</legend>
	<dl>
		<dd class="help">{$csv->count()} lignes trouvées dans le fichier</dd>
		<dt>{input type="checkbox" name="skip_first_line" value="1" label="Ne pas importer la première ligne" help="Décocher cette case si la première ligne ne contient pas l'intitulé des colonnes, mais des données" default=1}
		<dt><label>Correspondance des colonnes</label></dt>

		<dd>
			<table class="list auto">
				<tbody>
				{foreach from=$csv->getFirstLine() key="index" item="csv_field"}
					<tr>
						<th>{$csv_field}</th>
						<td>

Modified src/www/admin/membres/import.php from [7adc58cf10] to [e4d2ac02de].

16
17
18
19
20
21
22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

47
48
49
50
51

52
53
54
55
56
57
58



59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
}
elseif (qg('export') == 'ods')
{
    $import->toODS();
    exit;
}


$champs = $config->get('champs_membres')->getAll();

$csv_file = false;

if (f('csv_encoded'))
{
    $form->check('membres_import', [
        'csv_encoded'     => 'required|json',
        'csv_translate'   => 'required|array',
        'skip_first_line' => 'boolean',
    ]);

    $csv_file = json_decode(f('csv_encoded'), true);

    if (!$form->hasErrors())
    {
        try
        {
            $import->fromArray($csv_file, f('csv_translate'), $user->id, f('skip_first_line') ? 1 : 0);
            Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());

        }
    }
}
elseif (f('import'))
{

    $form->check('membres_import', [
        'upload' => 'file|required',
        'type'   => 'required|in:csv,garradin',
    ]);

    if (!$form->hasErrors())
    {



        try

        {
            if (f('type') == 'garradin')
            {
                $import->fromGarradinCSV($_FILES['upload']['tmp_name'], $user->id);
                Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
            }
            elseif (f('type') == 'csv')
            {
                $csv_file = CSV::readAsArray($_FILES['upload']['tmp_name']);
            }
            else
            {
                throw new UserException('Import inconnu.');
            }
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('ok', null !== qg('ok') ? true : false);

$tpl->assign('csv_file', $csv_file);
$tpl->assign('csv_first_line', $csv_file ? reset($csv_file) : null);

$tpl->assign('max_upload_size', Utils::getMaxUploadSize());

$tpl->assign('garradin_champs', $champs);

$tpl->display('admin/membres/import.tpl');







>

|
<

|
|
<
<
<
<
<
|
<
|
<
<
|
<
<
<
|
<
|
<
>
|
|
<
<
<
>
<
<
<
<

|
<
>
>
>
|
>
|
|
<
|
|
|
|
<
|
<
<
<
|
<
<
<
<
<
<
<
<



|
<



<
<

16
17
18
19
20
21
22
23
24
25

26
27
28





29

30


31



32

33

34
35
36



37




38
39

40
41
42
43
44
45
46

47
48
49
50

51



52








53
54
55
56

57
58
59


60
}
elseif (qg('export') == 'ods')
{
    $import->toODS();
    exit;
}

$csv = new CSV_Custom($session, 'users_import');
$champs = $config->get('champs_membres')->getAll();
$csrf_key = 'membres_import';


$columns = [];






foreach ($champs as $key => $config) {

    if (!isset($config->title)) {


        continue;



    }



    $columns[$key] = $config->title;
}




$csv->setColumns($columns);





$form->runIf(f('import') && $csv->ready(), function () use ($csv, $import, $user) {

    $csv->setTranslationTable(f('translation_table'));
    $csv->skip((int)f('skip_first_line'));
    $import->fromCustomCSV($csv, $user->id);
    $csv->clear();
}, $csrf_key, ADMIN_URL . 'membres/import.php?ok');

$form->runIf(f('import') && f('type') == 'garradin' && !empty($_FILES['upload']['tmp_name']), function () use ($import, $user) {

    $import->fromGarradinCSV($_FILES['upload']['tmp_name'], $user->id);
}, $csrf_key, ADMIN_URL . 'membres/import.php?ok');

$form->runIf(f('import') && f('type') == 'custom' && !empty($_FILES['upload']['tmp_name']), function () use ($csv) {

    $csv->load($_FILES['upload']);



}, $csrf_key, ADMIN_URL . 'membres/import.php?ok');









$tpl->assign('ok', null !== qg('ok') ? true : false);

$tpl->assign(compact('csv', 'csrf_key'));


$tpl->assign('max_upload_size', Utils::getMaxUploadSize());



$tpl->display('admin/membres/import.tpl');