Overview
Comment:Suppression de l'import de Galette, remplacé par un import de CSV générique, permettant de s'adapter à n'importe quel format de CSV
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 7af44a178e434ee14a42a54f559769708e5bdfc3
User & Date: bohwaz on 2018-08-22 19:49:49
Other Links: branch diff | manifest | tags
Context
2018-08-26
00:43
Ménage : les versions inférieures à 0.7 (3 ans et demi) devront faire une mise à jour intermédiaire pour pouvoir passer à une nouvelle version. check-in: b0063b95ef user: bohwaz tags: dev
2018-08-22
19:49
Suppression de l'import de Galette, remplacé par un import de CSV générique, permettant de s'adapter à n'importe quel format de CSV check-in: 7af44a178e user: bohwaz tags: dev
2018-08-21
00:56
Améliore aspect messages d'erreur check-in: 2ddd23337c user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Membres/Import.php from [032b787733] to [6fc6eacc71].

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
115
116
117
118
119
120
121
122
123

124
125

126


127


128








129






130

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use Garradin\Utils;
use Garradin\UserException;

use KD2\ODSWriter;

class Import
{
	/**
	 * Champs du CSV de Galette
	 * les lignes vides ('') ne seront pas proposées à l'import
	 * @var array
	 */
	public $galette_fields = [
		'Numéro',
		1,
		'Nom',
		'Prénom',
		'Pseudo',
		'Société',
		2,
		'Date de naissance',
		3,
		'Adresse, ligne 1',
		'Adresse, ligne 2',
		'Code postal',
		'Ville',
		'Pays',
		'Téléphone fixe',
		'Téléphone mobile',
		'E-Mail',
		'Site web',
		'ICQ',
		'MSN',
		'Jabber',
		'Infos (réservé administrateur)',
		'Infos (public)',
		'Profession',
		'Identifiant',
		'Mot de passe',
		'Date création fiche',
		'Date modification fiche',
		4, // activite_adh
		5, // bool_admin_adh
		6, // bool_exempt_adh
		7, // bool_display_info
		8, // date_echeance
		9, // pref_lang
		'Lieu de naissance',
		10, // GPG id
		11 // Fingerprint
	];

	/**
	 * Importer un CSV de la liste des membres depuis Galette
	 * @param  string $path              Chemin vers le CSV
	 * @param  array  $translation_table Tableau indiquant la correspondance à effectuer entre les champs
	 * de Galette et ceux de Garradin. Par exemple : ['Date création fiche' => 'date_inscription']
	 * @return boolean                   TRUE en cas de succès
	 */
	public function fromGalette($path, $translation_table)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');

		if (!$fp)
		{
			return false;
		}

		$db = DB::getInstance();
		$db->begin();
		$membres = new Membres;

		$columns = array_flip($this->galette_fields);

		$col = function($column) use (&$row, &$columns)
		{
			if (!isset($columns[$column]))
				return null;

			if (!isset($row[$columns[$column]]))
				return null;

			return $row[$columns[$column]];
		};

		$line = 0;
		$delim = Utils::find_csv_delim($fp);
		Utils::skip_bom($fp);


		while (!feof($fp))
		{
			$row = fgetcsv($fp, 4096, $delim);
			$line++;

			if (empty($row))
			{
				continue;
			}






			if (count($row) != count($columns))









































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

			$data = [];

			foreach ($translation_table as $galette=>$garradin)
			{
				// Champs qu'on ne veut pas importer
				if (empty($garradin))

					continue;


				// Concaténer plusieurs champs


				if (isset($data[$garradin]))


					$data[$garradin] .= "\n" . $col($galette);








				else






					$data[$garradin] = $col($galette);

			}

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

		$db->commit();

		fclose($fp);
		return true;
	}

	/**
	 * Importer un CSV de la liste des membres depuis un export Garradin
	 * @param  string $path 	Chemin vers le CSV
	 * @param  int    $current_user_id
	 * @return boolean          TRUE en cas de succès
	 */
	public function fromCSV($path, $current_user_id)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');







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













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

|
<
>











>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







|


|
>

|
>
|
>
>
|
>
>
|
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
>













<
<









|







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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use Garradin\Utils;
use Garradin\UserException;

use KD2\ODSWriter;

class Import
{




















































	public function getCSVAsArray($path)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');

		if (!$fp)
		{
			return false;
		}




		$delim = Utils::find_csv_delim($fp);

		Utils::skip_bom($fp);











		$line = 0;
		$out = [];

		$nb_columns = null;

		while (!feof($fp))
		{
			$row = fgetcsv($fp, 4096, $delim);
			$line++;

			if (empty($row))
			{
				continue;
			}

			if (null === $nb_columns)
			{
				$nb_columns = count($row);
			}

			if (count($row) != $nb_columns)
			{
				throw new UserException('Erreur sur la ligne ' . $line . ' : incohérence dans le nombre de colonnes avec la première ligne.');
			}

			$out[$line] = $row;
		}

		fclose($fp);

		return $out;
	}

	/**
	 * 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, $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];
				}
			}

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

		$db->commit();


		return true;
	}

	/**
	 * Importer un CSV de la liste des membres depuis un export Garradin
	 * @param  string $path 	Chemin vers le CSV
	 * @param  int    $current_user_id
	 * @return boolean          TRUE en cas de succès
	 */
	public function fromGarradinCSV($path, $current_user_id)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');

Modified src/templates/admin/membres/import.tpl from [239f90c5ba] to [f967ae9cb7].

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
{if $ok}
    <p class="confirm">
        L'import s'est bien déroulé.
    </p>
{/if}

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







































    <fieldset>
        <legend>Importer depuis un fichier</legend>
        <dl>
            <dt><label for="f_file">Fichier à importer</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <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">Export 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,
                les lignes sans numéro créeront de nouveaux membres.
            </dd>
            <dd>
                <input type="radio" name="type" id="f_type_galette" value="galette" {form_field name=type checked="galette"} />
                <label for="f_type_galette">Export CSV de Galette</label>
            </dd>
            <dd class="help">
                Export des données au format CSV provenant du logiciel libre
                <a href="http://galette.eu/">Galette</a>.
            </dd>
            <dt class="galette"><label>Correspondance des champs</label></dt>
            <dd class="help">Indiquer quels champs des fiches membre de Garradin les données de Galette doivent remplir.</dd>
            <dd class="galette">
                <table class="list auto">
                    <tbody>
                    {foreach from=$galette_champs item="galette"}
                        {if is_int($galette)}{continue}{/if}
                        <tr>
                            <th>{$galette}</th>
                            <td><select name="galette_translate[{$galette}]">
                                <option value="">-- Ne pas importer ce champ</option>
                                {foreach from=$garradin_champs item="champ" key="name"}
                                {if $champ.type == 'checkbox' || $champ.type == 'multiple'}{continue}{/if}
                                <option value="{$name}" {if (!empty($translate[$galette]) && $translate[$galette] == $name)}selected="selected"{/if}>{$champ.title}</option>
                                {/foreach}
                            </select></td>
                        </tr>
                    {/foreach}
                    </tbody>
                </table>
            </dd>
        </dl>
    </fieldset>



    <p class="submit">
        {csrf_field key="membres_import"}
        <input type="submit" name="import" value="Importer &rarr;" />
    </p>

</form>

<script type="text/javascript">
{literal}
(function () {
    function toggleGalette() {
        g.toggle('.galette', $('#f_type_galette').checked);
    }

    $('#f_type').onchange = toggleGalette;
    $('#f_type_galette').onchange = toggleGalette;
    toggleGalette();
})();
{/literal}
</script>

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







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|



|
|


|
|


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



>
>








<
<
<
<
<
<
<
<
<
<
<
<
<
<

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
{if $ok}
    <p class="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><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"}
        <input type="submit" name="import" value="Importer &rarr;" />
    </p>

</form>















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

Modified src/www/admin/membres/import.php from [414da74c86] to [87bae42958].

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
    $import->toODS();
    exit;
}

$champs = $config->get('champs_membres')->getAll();
$champs->date_inscription = (object) ['title' => 'Date inscription', 'type' => 'date'];


























if (f('import'))
{
    $form->check('membres_import', [
        'upload' => 'file|required',
        'type'   => 'required|in:galette,garradin',
        'galette_translate' => 'array',
    ]);

    if (!$form->hasErrors())
    {
        try
        {
            if (f('type') == 'galette')
            {
                $import->fromGalette($_FILES['upload']['tmp_name'], f('galette_translate'));

            }
            elseif (f('type') == 'garradin')
            {
                $import->fromCSV($_FILES['upload']['tmp_name'], $user->id);
            }
            else
            {
                throw new UserException('Import inconnu.');
            }

            Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

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




$tpl->assign('garradin_champs', $champs);
$tpl->assign('galette_champs', $import->galette_fields);
$tpl->assign('translate', f('galette_translate'));

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







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|



|
<






|

|
>

|

|





<
<










>
>
>

<
<


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
    $import->toODS();
    exit;
}

$champs = $config->get('champs_membres')->getAll();
$champs->date_inscription = (object) ['title' => 'Date inscription', 'type' => 'date'];

$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'), 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 = $import->getCSVAsArray($_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('garradin_champs', $champs);



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