FunctionsExport::exportGedcom()   F
last analyzed

Complexity

Conditions 20
Paths 15750

Size

Total Lines 130
Code Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 96
nc 15750
nop 3
dl 0
loc 130
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2019 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
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
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
namespace Fisharebest\Webtrees\Functions;
17
18
use Fisharebest\Webtrees\Auth;
19
use Fisharebest\Webtrees\Database;
20
use Fisharebest\Webtrees\Fact;
21
use Fisharebest\Webtrees\Family;
22
use Fisharebest\Webtrees\GedcomRecord;
23
use Fisharebest\Webtrees\Individual;
24
use Fisharebest\Webtrees\Media;
25
use Fisharebest\Webtrees\Note;
26
use Fisharebest\Webtrees\Repository;
27
use Fisharebest\Webtrees\Source;
28
use Fisharebest\Webtrees\Tree;
29
30
/**
31
 * Class FunctionsExport - common functions
32
 */
33
class FunctionsExport
34
{
35
    /**
36
     * Tidy up a gedcom record on export, for compatibility/portability.
37
     *
38
     * @param string $rec
39
     *
40
     * @return string
41
     */
42
    public static function reformatRecord($rec)
43
    {
44
        global $WT_TREE;
45
46
        $newrec = '';
47
        foreach (preg_split('/[\r\n]+/', $rec, -1, PREG_SPLIT_NO_EMPTY) as $line) {
48
            // Split long lines
49
            // The total length of a GEDCOM line, including level number, cross-reference number,
50
            // tag, value, delimiters, and terminator, must not exceed 255 (wide) characters.
51
            if (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH) {
52
                list($level, $tag) = explode(' ', $line, 3);
53
                if ($tag != 'CONT' && $tag != 'CONC') {
54
                    $level++;
55
                }
56
                do {
57
                    // Split after $pos chars
58
                    $pos = WT_GEDCOM_LINE_LENGTH;
59
                    if ($WT_TREE->getPreference('WORD_WRAPPED_NOTES')) {
60
                        // Split on a space, and remove it (for compatibility with some desktop apps)
61
                        while ($pos && mb_substr($line, $pos - 1, 1) != ' ') {
62
                            --$pos;
63
                        }
64
                        if ($pos == strpos($line, ' ', 3) + 1) {
65
                            // No spaces in the data! Can’t split it :-(
66
                            break;
67
                        } else {
68
                            $newrec .= mb_substr($line, 0, $pos - 1) . WT_EOL;
69
                            $line = $level . ' CONC ' . mb_substr($line, $pos);
70
                        }
71
                    } else {
72
                        // Split on a non-space (standard gedcom behaviour)
73
                        while ($pos && mb_substr($line, $pos - 1, 1) == ' ') {
74
                            --$pos;
75
                        }
76
                        if ($pos == strpos($line, ' ', 3)) {
77
                            // No non-spaces in the data! Can’t split it :-(
78
                            break;
79
                        }
80
                        $newrec .= mb_substr($line, 0, $pos) . WT_EOL;
81
                        $line = $level . ' CONC ' . mb_substr($line, $pos);
82
                    }
83
                } while (mb_strlen($line) > WT_GEDCOM_LINE_LENGTH);
84
            }
85
            $newrec .= $line . WT_EOL;
86
        }
87
88
        return $newrec;
89
    }
90
91
    /**
92
     * Create a header for a (newly-created or already-imported) gedcom file.
93
     *
94
     * @param Tree $tree
95
     *
96
     * @return string
97
     */
98
    public static function gedcomHeader(Tree $tree)
99
    {
100
        // Default values for a new header
101
        $HEAD = "0 HEAD";
102
        $SOUR = "\n1 SOUR " . WT_WEBTREES . "\n2 NAME " . WT_WEBTREES . "\n2 VERS " . WT_VERSION;
103
        $DEST = "\n1 DEST DISKETTE";
104
        $DATE = "\n1 DATE " . strtoupper(date("d M Y")) . "\n2 TIME " . date("H:i:s");
105
        $GEDC = "\n1 GEDC\n2 VERS 5.5.1\n2 FORM Lineage-Linked";
106
        $CHAR = "\n1 CHAR UTF-8";
107
        $FILE = "\n1 FILE " . $tree->getName();
108
        $LANG = '';
109
        $COPR = '';
110
        $SUBN = '';
111
        $SUBM = "\n1 SUBM @SUBM@\n0 @SUBM@ SUBM\n1 NAME " . Auth::user()->getUserName(); // The SUBM record is mandatory
112
113
        // Preserve some values from the original header
114
        $record = GedcomRecord::getInstance('HEAD', $tree);
115
        $fact = $record->getFirstFact('LANG');
116
        if ($fact instanceof Fact) {
117
            $LANG = $fact->getValue();
118
        }
119
        $fact = $record->getFirstFact('SUBN');
120
        if ($fact instanceof Fact) {
121
            $SUBN = $fact->getValue();
122
        }
123
        $fact = $record->getFirstFact('COPR');
124
        if ($fact instanceof Fact) {
125
            $COPR = $fact->getValue();
126
        }
127
        // Link to actual SUBM/SUBN records, if they exist
128
        $subn =
129
            Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
130
                ->execute(array('SUBN', $tree->getTreeId()))
131
                ->fetchOne();
132
        if ($subn) {
133
            $SUBN = "\n1 SUBN @{$subn}@";
134
        }
135
        $subm =
136
            Database::prepare("SELECT o_id FROM `##other` WHERE o_type=? AND o_file=?")
137
                ->execute(array('SUBM', $tree->getTreeId()))
138
                ->fetchOne();
139
        if ($subm) {
140
            $SUBM = "\n1 SUBM @{$subm}@";
141
        }
142
143
        return $HEAD . $SOUR . $DEST . $DATE . $GEDC . $CHAR . $FILE . $COPR . $LANG . $SUBN . $SUBM . "\n";
144
    }
145
146
    /**
147
     * Prepend the GEDCOM_MEDIA_PATH to media filenames.
148
     *
149
     * @param string $rec
150
     * @param string $path
151
     *
152
     * @return string
153
     */
154
    public static function convertMediaPath($rec, $path)
155
    {
156
        if ($path && preg_match('/\n1 FILE (.+)/', $rec, $match)) {
157
            $old_file_name = $match[1];
158
            // Don’t modify external links
159
            if (!preg_match('~^(https?|ftp):~', $old_file_name)) {
160
                // Adding a windows path? Convert the slashes.
161
                if (strpos($path, '\\') !== false) {
162
                    $new_file_name = preg_replace('~/+~', '\\', $old_file_name);
163
                } else {
164
                    $new_file_name = $old_file_name;
165
                }
166
                // Path not present - add it.
167
                if (strpos($new_file_name, $path) === false) {
168
                    $new_file_name = $path . $new_file_name;
169
                }
170
                $rec = str_replace("\n1 FILE " . $old_file_name, "\n1 FILE " . $new_file_name, $rec);
171
            }
172
        }
173
174
        return $rec;
175
    }
176
177
    /**
178
     * Export the database in GEDCOM format
179
     *
180
     * @param Tree $tree Which tree to export
181
     * @param resource $gedout Handle to a writable stream
182
     * @param string[] $exportOptions Export options are as follows:
183
     *                                'privatize':    which Privacy rules apply? (none, visitor, user, manager)
184
     *                                'toANSI':       should the output be produced in ISO-8859-1 instead of UTF-8? (yes, no)
185
     *                                'path':         what constant should prefix all media file paths? (eg: media/  or c:\my pictures\my family
186
     *                                'slashes':      what folder separators apply to media file paths? (forward, backward)
187
     */
188
    public static function exportGedcom(Tree $tree, $gedout, $exportOptions)
189
    {
190
        switch ($exportOptions['privatize']) {
191
            case 'gedadmin':
192
                $access_level = Auth::PRIV_NONE;
193
                break;
194
            case 'user':
195
                $access_level = Auth::PRIV_USER;
196
                break;
197
            case 'visitor':
198
                $access_level = Auth::PRIV_PRIVATE;
199
                break;
200
            case 'none':
201
                $access_level = Auth::PRIV_HIDE;
202
                break;
203
        }
204
205
        $head = self::gedcomHeader($tree);
206
        if ($exportOptions['toANSI'] == 'yes') {
207
            $head = str_replace('UTF-8', 'ANSI', $head);
208
            $head = utf8_decode($head);
209
        }
210
        $head = self::reformatRecord($head);
211
        fwrite($gedout, $head);
212
213
        // Buffer the output. Lots of small fwrite() calls can be very slow when writing large gedcoms.
214
        $buffer = '';
215
216
        // Generate the OBJE/SOUR/REPO/NOTE records first, as their privacy calcualations involve
217
        // database queries, and we wish to avoid large gaps between queries due to MySQL connection timeouts.
218
        $tmp_gedcom = '';
219
        $rows       = Database::prepare(
220
            "SELECT m_id AS xref, m_gedcom AS gedcom" .
221
            " FROM `##media` WHERE m_file = :tree_id ORDER BY m_id"
222
        )->execute(array(
223
            'tree_id' => $tree->getTreeId(),
224
        ))->fetchAll();
225
226
        foreach ($rows as $row) {
227
            $rec = Media::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $access_level does not seem to be defined for all execution paths leading up to this point.
Loading history...
228
            $rec = self::convertMediaPath($rec, $exportOptions['path']);
229
            if ($exportOptions['toANSI'] === 'yes') {
230
                $rec = utf8_decode($rec);
231
            }
232
            $tmp_gedcom .= self::reformatRecord($rec);
233
        }
234
235
        $rows = Database::prepare(
236
            "SELECT s_id AS xref, s_file AS gedcom_id, s_gedcom AS gedcom" .
237
            " FROM `##sources` WHERE s_file = :tree_id ORDER BY s_id"
238
        )->execute(array(
239
            'tree_id' => $tree->getTreeId(),
240
        ))->fetchAll();
241
242
        foreach ($rows as $row) {
243
            $rec = Source::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
244
            if ($exportOptions['toANSI'] === 'yes') {
245
                $rec = utf8_decode($rec);
246
            }
247
            $tmp_gedcom .= self::reformatRecord($rec);
248
        }
249
250
        $rows = Database::prepare(
251
            "SELECT o_type AS type, o_id AS xref, o_gedcom AS gedcom" .
252
            " FROM `##other` WHERE o_file = :tree_id AND o_type NOT IN ('HEAD', 'TRLR') ORDER BY o_id"
253
        )->execute(array(
254
            'tree_id' => $tree->getTreeId(),
255
        ))->fetchAll();
256
257
        foreach ($rows as $row) {
258
            switch ($row->type) {
259
                case 'NOTE':
260
                    $record = Note::getInstance($row->xref, $tree, $row->gedcom);
261
                    break;
262
                case 'REPO':
263
                    $record = Repository::getInstance($row->xref, $tree, $row->gedcom);
264
                    break;
265
                default:
266
                    $record = GedcomRecord::getInstance($row->xref, $tree, $row->gedcom);
267
                    break;
268
            }
269
270
            $rec = $record->privatizeGedcom($access_level);
271
            if ($exportOptions['toANSI'] === 'yes') {
272
                $rec = utf8_decode($rec);
273
            }
274
            $tmp_gedcom .= self::reformatRecord($rec);
275
        }
276
277
        $rows = Database::prepare(
278
            "SELECT i_id AS xref, i_gedcom AS gedcom" .
279
            " FROM `##individuals` WHERE i_file = :tree_id ORDER BY i_id"
280
        )->execute(array(
281
            'tree_id' => $tree->getTreeId(),
282
        ))->fetchAll();
283
284
        foreach ($rows as $row) {
285
            $rec = Individual::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
286
            if ($exportOptions['toANSI'] === 'yes') {
287
                $rec = utf8_decode($rec);
288
            }
289
            $buffer .= self::reformatRecord($rec);
290
            if (strlen($buffer) > 65536) {
291
                fwrite($gedout, $buffer);
292
                $buffer = '';
293
            }
294
        }
295
296
        $rows = Database::prepare(
297
            "SELECT f_id AS xref, f_gedcom AS gedcom" .
298
            " FROM `##families` WHERE f_file = :tree_id ORDER BY f_id"
299
        )->execute(array(
300
            'tree_id' => $tree->getTreeId(),
301
        ))->fetchAll();
302
303
        foreach ($rows as $row) {
304
            $rec = Family::getInstance($row->xref, $tree, $row->gedcom)->privatizeGedcom($access_level);
305
            if ($exportOptions['toANSI'] === 'yes') {
306
                $rec = utf8_decode($rec);
307
            }
308
            $buffer .= self::reformatRecord($rec);
309
            if (strlen($buffer) > 65536) {
310
                fwrite($gedout, $buffer);
311
                $buffer = '';
312
            }
313
        }
314
315
        fwrite($gedout, $buffer);
316
        fwrite($gedout, $tmp_gedcom);
317
        fwrite($gedout, '0 TRLR' . WT_EOL);
318
    }
319
}
320