Passed
Push — master ( 5ec36b...a966a8 )
by Stefan
01:45
created

VCard::getContactList()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\VCard;
5
6
/**
7
 * Base class to create or read vcard (.vcf) file.
8
 *
9
 * A vcard file may contain multiple contacts.
10
 * - Creation of vCard Files Version <b>3.0</b> (RFC 2426)
11
 * - Import of vCard Files Version <b>2.1</b> and <b>3.0</b>
12
 *
13
 * #### Create a VCard for writing:
14
 * Create an instance of `VCard`, add the desired `VCardContacts` to it and save
15
 * it as file with the `write()` method.
16
 *
17
 * #### Retrieve contacts from an existing VCard file:
18
 * Open the file with the `read()` method and retrieve the containing contacts with
19
 * the `getContact()` method.
20
 *
21
 * You can either iterate over all contacts from `0 ... getContactCount()` (instead
22
 * of getContactCount() the return value of `read()` can be used) or you can use
23
 * `getContactList()` to call up a list of names of all contacts contained so you be
24
 * able to access a specific contact.
25
 *
26
 * @package VCard
27
 * @author Stefanius <[email protected]>
28
 * @copyright MIT License - see the LICENSE file for details
29
 */
30
class VCard
31
{
32
    use VCardHelper;
33
34
    /** preferrred entry     */
35
    public const PREF      = 'PREF';
36
    /** information for work     */
37
    public const WORK      = 'WORK';
38
    /** information for home     */
39
    public const HOME      = 'HOME';
40
    /** postal address   */
41
    public const POSTAL    = 'POSTAL';
42
    /** parcel address for delivery      */
43
    public const PARCEL    = 'PARCEL';
44
    /** international address for delivery      */
45
    public const INTER     = 'INTL';
46
    /** domestic address for delivery      */
47
    public const DOMESTIC  = 'DOM';
48
49
    /** communication number: standard phone    */
50
    public const VOICE     = 'VOICE';
51
    /** communication number: cellular phone    */
52
    public const CELL      = 'CELL';
53
    /** communication number: facsimile device  */
54
    public const FAX       = 'FAX';
55
    /** communication number: number has voice messaging support     */
56
    public const MSG       = 'MSG';
57
    /** communication number: video conferencing telephone number    */
58
    public const VIDEO     = 'VIDEO';
59
    /** communication number: paging device telephone number    */
60
    public const PAGER     = 'PAGER';
61
    /** communication number: bulletin board system telephone number    */
62
    public const BBS       = 'BBS';
63
    /** communication number: modem connected telephone number    */
64
    public const MODEM     = 'MODEM';
65
    /** communication number: car-phone telephone number    */
66
    public const CAR       = 'CAR';
67
    /** communication number: ISDN service telephone number    */
68
    public const ISDN      = 'ISDN';
69
    /** communication number: <b>p</b>ersonal <b>c</b>ommunication <b>s</b>ervices telephone number    */
70
    public const PCS       = 'PCS';
71
72
73
    /** @internal max. length of line in a vcard - file    */
74
    public const MAX_LINE_LENGTH = 75;
75
76
    /** @var string  encoding for values    */
77
    static protected string $strEncoding = 'UTF-8';
78
    /** @var VCardContact[] all contacts in the VCard file */
79
    protected array $aContacts = array();
80
81
    /**
82
     * Get the encoding currently set.
83
     * @return string
84
     */
85
    public static function getEncoding() : string
86
    {
87
        return VCard::$strEncoding;
88
    }
89
90
    /**
91
     * Set the encoding for the file.
92
     * For export:
93
     * - always use UTF-8 (default).
94
     *   only exception i found so far is MS-Outlook - it comes in trouble with german
95
     *   umlauts, so use 'Windwos-1252' instead.
96
     *   please send note to [email protected] if you found any further exceptions...
97
     *
98
     * For import:
99
     * -  feel free to use your preferred charset (may depends on configuration of your system)
100
     * @param string $strEncoding
101
     */
102
    public static function setEncoding(string $strEncoding) : void
103
    {
104
        VCard::$strEncoding = $strEncoding;
105
    }
106
107
    /**
108
     * Add contact to vcard file.
109
     * @param VCardContact $oContact
110
     */
111
    public function addContact(VCardContact $oContact) : void
112
    {
113
        $this->aContacts[] = clone $oContact;
114
    }
115
116
    /**
117
     * Write vcard to file.
118
     * @param string $strFilename
119
     * @param bool $bTest   output to browser for internal testing...
120
     */
121
    public function write(string $strFilename, bool $bTest = false) : void
122
    {
123
        $buffer = '';
124
        foreach ($this->aContacts as $oContact) {
125
            $oContactWriter = new VCardContactWriter($oContact);
126
            $buffer .= $oContactWriter;
127
        }
128
        // vcf-file generation doesn't make sense if some errormessage generated before...
129
        if (!$bTest && ob_get_contents() == '') {
130
            header('Content-Type: text/x-vCard; name=' . $strFilename);
131
            header('Content-Length: ' . strlen($buffer));
132
            header('Connection: close');
133
            header('Content-Disposition: attachment; filename=' . $strFilename);
134
        } else {
135
            // output for test or in case of errors
136
            $buffer = str_replace(PHP_EOL, '<br>', $buffer);
137
            echo '<!DOCTYPE html>' . PHP_EOL;
138
            echo '<head><title>vCard Exporttest Display</title>' . PHP_EOL;
139
            echo '</head>' . PHP_EOL;
140
            echo '<body>' . PHP_EOL;
141
            echo '<h1>Filename: ' . $strFilename . '</h1>';
142
            $buffer = '<pre>' . $buffer . '</pre></body>';
143
        }
144
145
        echo $buffer;
146
    }
147
148
    /**
149
     * Read vcard - file.
150
     * @param string $strFilename
151
     * @return int  count of contacts imported
152
     */
153
    public function read(string $strFilename) : int
154
    {
155
        $aLines = @file($strFilename);
156
        if ($aLines === false) {
157
            return 0;
158
        }
159
        $iLn = 0;
160
        $oContact = null;
161
        $oReader = null;
162
        while ($iLn < count($aLines)) {
163
            $strLine = rtrim($aLines[$iLn++], "\r\n");
164
165
            // QUOTED-PRINTABLE multiline values: (supported by vcard version 2.1 only)
166
            // if line ends with '=', go on next line (ignore ending '=' sign!)
167
            if (substr($strLine, -1) == '=') {
168
                while ($iLn < count($aLines) && substr($strLine, -1) == '=') {
169
                    $strLine = rtrim($strLine, '='); // remove ending '='
170
                    if (strlen(trim($aLines[$iLn])) == 0) {
171
                        break;
172
                    }
173
                    $strLine .= rtrim($aLines[$iLn++]);
174
                }
175
            }
176
            // for multiline values suceeding line starts with blank
177
            while ($iLn < count($aLines) && substr($aLines[$iLn], 0, 1) == ' ') {
178
                if (strlen(trim($aLines[$iLn])) == 0) {
179
                    break;
180
                }
181
                $strLine .= rtrim(substr($aLines[$iLn++], 1), "\r\n"); // ignore leading blank
182
            }
183
184
            if (strtoupper($strLine) == 'BEGIN:VCARD') {
185
                $oContact = new VCardContact();
186
                $oReader = new VCardContactReader($oContact);
187
            } elseif (strtoupper($strLine) == 'END:VCARD') {
188
                $this->aContacts[] = $oContact;
189
                $oContact = null;
190
                $oReader = null;
191
            } elseif ($oReader) {
192
                // split property name/params from value
193
                $aSplit = explode(':', $strLine, 2);
194
                if (count($aSplit) == 2) {
195
                    $aNameParams = explode(';', $aSplit[0]);
196
                    $strName = $aNameParams[0];
197
                    $aParams = $this->parseParams($aNameParams);
198
                    $oReader->addProperty($strName, $aParams, $aSplit[1]);
199
                }
200
            }
201
        }
202
        return count($this->aContacts);
203
    }
204
205
    /**
206
     * Number of contacts the vcard containing.
207
     * @return int
208
     */
209
    public function getContactCount() : int
210
    {
211
        return count($this->aContacts);
212
    }
213
214
    /**
215
     * Get a named list of all contacts.
216
     * The complete VCardContact object can be called up with the method
217
     * `getContact ()` via the corresponding index.
218
     * @return array
219
     */
220
    public function getContactList() : array
221
    {
222
        $aList = array();
223
        foreach ($this->aContacts as $oContact) {
224
            $aList[] = $oContact->getName;
225
        }
226
        return $aList;
227
    }
228
229
    /**
230
     * Get contact data.
231
     * @param int $i    index of the requested contact (`0 <= $i < getContactCount`)
232
     * @return VCardContact|null    null, if index is out of the contact count
233
     */
234
    public function getContact(int $i) : ?VCardContact
235
    {
236
        $oContact = null;
237
        if ($i >= 0 && $i < count($this->aContacts)) {
238
            $oContact = $this->aContacts[$i];
239
        }
240
        return $oContact;
241
    }
242
}
243