1<?php
2
3namespace Sabre\VObject;
4
5use Sabre\VObject\Component\VCalendar;
6
7/**
8 * This class generates birthday calendars.
9 *
10 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11 * @author Dominik Tobschall (http://tobschall.de/)
12 * @license http://sabre.io/license/ Modified BSD License
13 */
14class BirthdayCalendarGenerator {
15
16    /**
17     * Input objects.
18     *
19     * @var array
20     */
21    protected $objects = [];
22
23    /**
24     * Default year.
25     * Used for dates without a year.
26     */
27    const DEFAULT_YEAR = 2000;
28
29    /**
30     * Output format for the SUMMARY.
31     *
32     * @var string
33     */
34    protected $format = '%1$s\'s Birthday';
35
36    /**
37     * Creates the generator.
38     *
39     * Check the setTimeRange and setObjects methods for details about the
40     * arguments.
41     *
42     * @param mixed $objects
43     */
44    function __construct($objects = null) {
45
46        if ($objects) {
47            $this->setObjects($objects);
48        }
49
50    }
51
52    /**
53     * Sets the input objects.
54     *
55     * You must either supply a vCard as a string or as a Component/VCard object.
56     * It's also possible to supply an array of strings or objects.
57     *
58     * @param mixed $objects
59     *
60     * @return void
61     */
62    function setObjects($objects) {
63
64        if (!is_array($objects)) {
65            $objects = [$objects];
66        }
67
68        $this->objects = [];
69        foreach ($objects as $object) {
70
71            if (is_string($object)) {
72
73                $vObj = Reader::read($object);
74                if (!$vObj instanceof Component\VCard) {
75                    throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
76                }
77
78                $this->objects[] = $vObj;
79
80            } elseif ($object instanceof Component\VCard) {
81
82                $this->objects[] = $object;
83
84            } else {
85
86                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
87
88            }
89
90        }
91
92    }
93
94    /**
95     * Sets the output format for the SUMMARY
96     *
97     * @param string $format
98     *
99     * @return void
100     */
101    function setFormat($format) {
102
103        $this->format = $format;
104
105    }
106
107    /**
108     * Parses the input data and returns a VCALENDAR.
109     *
110     * @return Component/VCalendar
111     */
112    function getResult() {
113
114        $calendar = new VCalendar();
115
116        foreach ($this->objects as $object) {
117
118            // Skip if there is no BDAY property.
119            if (!$object->select('BDAY')) {
120                continue;
121            }
122
123            // We've seen clients (ez-vcard) putting "BDAY:" properties
124            // without a value into vCards. If we come across those, we'll
125            // skip them.
126            if (empty($object->BDAY->getValue())) {
127                continue;
128            }
129
130            // We're always converting to vCard 4.0 so we can rely on the
131            // VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
132            $object = $object->convert(Document::VCARD40);
133
134            // Skip if the card has no FN property.
135            if (!isset($object->FN)) {
136                continue;
137            }
138
139            // Skip if the BDAY property is not of the right type.
140            if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
141                continue;
142            }
143
144            // Skip if we can't parse the BDAY value.
145            try {
146                $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
147            } catch (InvalidDataException $e) {
148                continue;
149            }
150
151            // Set a year if it's not set.
152            $unknownYear = false;
153
154            if (!$dateParts['year']) {
155                $object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date'];
156
157                $unknownYear = true;
158            }
159
160            // Create event.
161            $event = $calendar->add('VEVENT', [
162                'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
163                'DTSTART' => new \DateTime($object->BDAY->getValue()),
164                'RRULE'   => 'FREQ=YEARLY',
165                'TRANSP'  => 'TRANSPARENT',
166            ]);
167
168            // add VALUE=date
169            $event->DTSTART['VALUE'] = 'DATE';
170
171            // Add X-SABRE-BDAY property.
172            if ($unknownYear) {
173                $event->add('X-SABRE-BDAY', 'BDAY', [
174                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
175                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
176                    'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
177                ]);
178            } else {
179                $event->add('X-SABRE-BDAY', 'BDAY', [
180                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
181                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
182                ]);
183            }
184
185        }
186
187        return $calendar;
188
189    }
190
191}
192