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    public function __construct($objects = null)
45    {
46        if ($objects) {
47            $this->setObjects($objects);
48        }
49    }
50
51    /**
52     * Sets the input objects.
53     *
54     * You must either supply a vCard as a string or as a Component/VCard object.
55     * It's also possible to supply an array of strings or objects.
56     *
57     * @param mixed $objects
58     */
59    public function setObjects($objects)
60    {
61        if (!is_array($objects)) {
62            $objects = [$objects];
63        }
64
65        $this->objects = [];
66        foreach ($objects as $object) {
67            if (is_string($object)) {
68                $vObj = Reader::read($object);
69                if (!$vObj instanceof Component\VCard) {
70                    throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
71                }
72
73                $this->objects[] = $vObj;
74            } elseif ($object instanceof Component\VCard) {
75                $this->objects[] = $object;
76            } else {
77                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
78            }
79        }
80    }
81
82    /**
83     * Sets the output format for the SUMMARY.
84     *
85     * @param string $format
86     */
87    public function setFormat($format)
88    {
89        $this->format = $format;
90    }
91
92    /**
93     * Parses the input data and returns a VCALENDAR.
94     *
95     * @return Component/VCalendar
96     */
97    public function getResult()
98    {
99        $calendar = new VCalendar();
100
101        foreach ($this->objects as $object) {
102            // Skip if there is no BDAY property.
103            if (!$object->select('BDAY')) {
104                continue;
105            }
106
107            // We've seen clients (ez-vcard) putting "BDAY:" properties
108            // without a value into vCards. If we come across those, we'll
109            // skip them.
110            if (empty($object->BDAY->getValue())) {
111                continue;
112            }
113
114            // We're always converting to vCard 4.0 so we can rely on the
115            // VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
116            $object = $object->convert(Document::VCARD40);
117
118            // Skip if the card has no FN property.
119            if (!isset($object->FN)) {
120                continue;
121            }
122
123            // Skip if the BDAY property is not of the right type.
124            if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
125                continue;
126            }
127
128            // Skip if we can't parse the BDAY value.
129            try {
130                $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
131            } catch (InvalidDataException $e) {
132                continue;
133            }
134
135            // Set a year if it's not set.
136            $unknownYear = false;
137
138            if (!$dateParts['year']) {
139                $object->BDAY = self::DEFAULT_YEAR.'-'.$dateParts['month'].'-'.$dateParts['date'];
140
141                $unknownYear = true;
142            }
143
144            // Create event.
145            $event = $calendar->add('VEVENT', [
146                'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
147                'DTSTART' => new \DateTime($object->BDAY->getValue()),
148                'RRULE' => 'FREQ=YEARLY',
149                'TRANSP' => 'TRANSPARENT',
150            ]);
151
152            // add VALUE=date
153            $event->DTSTART['VALUE'] = 'DATE';
154
155            // Add X-SABRE-BDAY property.
156            if ($unknownYear) {
157                $event->add('X-SABRE-BDAY', 'BDAY', [
158                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
159                    'X-SABRE-VCARD-FN' => $object->FN->getValue(),
160                    'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
161                ]);
162            } else {
163                $event->add('X-SABRE-BDAY', 'BDAY', [
164                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
165                    'X-SABRE-VCARD-FN' => $object->FN->getValue(),
166                ]);
167            }
168        }
169
170        return $calendar;
171    }
172}
173