aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/vobject/lib/Component.php
diff options
context:
space:
mode:
authorAndrew Manning <tamanning@zoho.com>2016-05-11 05:54:20 -0400
committerAndrew Manning <tamanning@zoho.com>2016-05-11 05:54:20 -0400
commitd968fc51eab8b0fb259ecbeae517056b99554017 (patch)
tree10e551cff9fefbefbfd7e5031b57320116bb7fce /vendor/sabre/vobject/lib/Component.php
parentc7698e4dc388b7d9a9db368672cb057c1d4d3a01 (diff)
parent4dd3839c41e18d9724855e7955d8737b6f52dcd6 (diff)
downloadvolse-hubzilla-d968fc51eab8b0fb259ecbeae517056b99554017.tar.gz
volse-hubzilla-d968fc51eab8b0fb259ecbeae517056b99554017.tar.bz2
volse-hubzilla-d968fc51eab8b0fb259ecbeae517056b99554017.zip
Merge remote-tracking branch 'upstream/dev' into plugin-repo
Diffstat (limited to 'vendor/sabre/vobject/lib/Component.php')
-rw-r--r--vendor/sabre/vobject/lib/Component.php700
1 files changed, 700 insertions, 0 deletions
diff --git a/vendor/sabre/vobject/lib/Component.php b/vendor/sabre/vobject/lib/Component.php
new file mode 100644
index 000000000..9a10ed3f8
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component.php
@@ -0,0 +1,700 @@
+<?php
+
+namespace Sabre\VObject;
+
+use Sabre\Xml;
+
+/**
+ * Component.
+ *
+ * A component represents a group of properties, such as VCALENDAR, VEVENT, or
+ * VCARD.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Component extends Node {
+
+ /**
+ * Component name.
+ *
+ * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * A list of properties and/or sub-components.
+ *
+ * @var array
+ */
+ protected $children = [];
+
+ /**
+ * Creates a new component.
+ *
+ * You can specify the children either in key=>value syntax, in which case
+ * properties will automatically be created, or you can just pass a list of
+ * Component and Property object.
+ *
+ * By default, a set of sensible values will be added to the component. For
+ * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
+ * ensure that this does not happen, set $defaults to false.
+ *
+ * @param Document $root
+ * @param string $name such as VCALENDAR, VEVENT.
+ * @param array $children
+ * @param bool $defaults
+ *
+ * @return void
+ */
+ function __construct(Document $root, $name, array $children = [], $defaults = true) {
+
+ $this->name = strtoupper($name);
+ $this->root = $root;
+
+ if ($defaults) {
+ // This is a terribly convoluted way to do this, but this ensures
+ // that the order of properties as they are specified in both
+ // defaults and the childrens list, are inserted in the object in a
+ // natural way.
+ $list = $this->getDefaults();
+ $nodes = [];
+ foreach ($children as $key => $value) {
+ if ($value instanceof Node) {
+ if (isset($list[$value->name])) {
+ unset($list[$value->name]);
+ }
+ $nodes[] = $value;
+ } else {
+ $list[$key] = $value;
+ }
+ }
+ foreach ($list as $key => $value) {
+ $this->add($key, $value);
+ }
+ foreach ($nodes as $node) {
+ $this->add($node);
+ }
+ } else {
+ foreach ($children as $k => $child) {
+ if ($child instanceof Node) {
+ // Component or Property
+ $this->add($child);
+ } else {
+
+ // Property key=>value
+ $this->add($k, $child);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Adds a new property or component, and returns the new item.
+ *
+ * This method has 3 possible signatures:
+ *
+ * add(Component $comp) // Adds a new component
+ * add(Property $prop) // Adds a new property
+ * add($name, $value, array $parameters = []) // Adds a new property
+ * add($name, array $children = []) // Adds a new component
+ * by name.
+ *
+ * @return Node
+ */
+ function add() {
+
+ $arguments = func_get_args();
+
+ if ($arguments[0] instanceof Node) {
+ if (isset($arguments[1])) {
+ throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
+ }
+ $arguments[0]->parent = $this;
+ $newNode = $arguments[0];
+
+ } elseif (is_string($arguments[0])) {
+
+ $newNode = call_user_func_array([$this->root, 'create'], $arguments);
+
+ } else {
+
+ throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
+
+ }
+
+ $name = $newNode->name;
+ if (isset($this->children[$name])) {
+ $this->children[$name][] = $newNode;
+ } else {
+ $this->children[$name] = [$newNode];
+ }
+ return $newNode;
+
+ }
+
+ /**
+ * This method removes a component or property from this component.
+ *
+ * You can either specify the item by name (like DTSTART), in which case
+ * all properties/components with that name will be removed, or you can
+ * pass an instance of a property or component, in which case only that
+ * exact item will be removed.
+ *
+ * @param string|Property|Component $item
+ * @return void
+ */
+ function remove($item) {
+
+ if (is_string($item)) {
+ // If there's no dot in the name, it's an exact property name and
+ // we can just wipe out all those properties.
+ //
+ if (strpos($item, '.') === false) {
+ unset($this->children[strtoupper($item)]);
+ return;
+ }
+ // If there was a dot, we need to ask select() to help us out and
+ // then we just call remove recursively.
+ foreach ($this->select($item) as $child) {
+
+ $this->remove($child);
+
+ }
+ } else {
+ foreach ($this->select($item->name) as $k => $child) {
+ if ($child === $item) {
+ unset($this->children[$item->name][$k]);
+ return;
+ }
+ }
+ }
+
+ throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
+
+ }
+
+ /**
+ * Returns a flat list of all the properties and components in this
+ * component.
+ *
+ * @return array
+ */
+ function children() {
+
+ $result = [];
+ foreach ($this->children as $childGroup) {
+ $result = array_merge($result, $childGroup);
+ }
+ return $result;
+
+ }
+
+ /**
+ * This method only returns a list of sub-components. Properties are
+ * ignored.
+ *
+ * @return array
+ */
+ function getComponents() {
+
+ $result = [];
+
+ foreach ($this->children as $childGroup) {
+ foreach ($childGroup as $child) {
+ if ($child instanceof self) {
+ $result[] = $child;
+ }
+ }
+ }
+ return $result;
+
+ }
+
+ /**
+ * Returns an array with elements that match the specified name.
+ *
+ * This function is also aware of MIME-Directory groups (as they appear in
+ * vcards). This means that if a property is grouped as "HOME.EMAIL", it
+ * will also be returned when searching for just "EMAIL". If you want to
+ * search for a property in a specific group, you can select on the entire
+ * string ("HOME.EMAIL"). If you want to search on a specific property that
+ * has not been assigned a group, specify ".EMAIL".
+ *
+ * @param string $name
+ * @return array
+ */
+ function select($name) {
+
+ $group = null;
+ $name = strtoupper($name);
+ if (strpos($name, '.') !== false) {
+ list($group, $name) = explode('.', $name, 2);
+ }
+ if ($name === '') $name = null;
+
+ if (!is_null($name)) {
+
+ $result = isset($this->children[$name]) ? $this->children[$name] : [];
+
+ if (is_null($group)) {
+ return $result;
+ } else {
+ // If we have a group filter as well, we need to narrow it down
+ // more.
+ return array_filter(
+ $result,
+ function($child) use ($group) {
+
+ return $child instanceof Property && strtoupper($child->group) === $group;
+
+ }
+ );
+ }
+
+ }
+
+ // If we got to this point, it means there was no 'name' specified for
+ // searching, implying that this is a group-only search.
+ $result = [];
+ foreach ($this->children as $childGroup) {
+
+ foreach ($childGroup as $child) {
+
+ if ($child instanceof Property && strtoupper($child->group) === $group) {
+ $result[] = $child;
+ }
+
+ }
+
+ }
+ return $result;
+
+ }
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ function serialize() {
+
+ $str = "BEGIN:" . $this->name . "\r\n";
+
+ /**
+ * Gives a component a 'score' for sorting purposes.
+ *
+ * This is solely used by the childrenSort method.
+ *
+ * A higher score means the item will be lower in the list.
+ * To avoid score collisions, each "score category" has a reasonable
+ * space to accomodate elements. The $key is added to the $score to
+ * preserve the original relative order of elements.
+ *
+ * @param int $key
+ * @param array $array
+ *
+ * @return int
+ */
+ $sortScore = function($key, $array) {
+
+ if ($array[$key] instanceof Component) {
+
+ // We want to encode VTIMEZONE first, this is a personal
+ // preference.
+ if ($array[$key]->name === 'VTIMEZONE') {
+ $score = 300000000;
+ return $score + $key;
+ } else {
+ $score = 400000000;
+ return $score + $key;
+ }
+ } else {
+ // Properties get encoded first
+ // VCARD version 4.0 wants the VERSION property to appear first
+ if ($array[$key] instanceof Property) {
+ if ($array[$key]->name === 'VERSION') {
+ $score = 100000000;
+ return $score + $key;
+ } else {
+ // All other properties
+ $score = 200000000;
+ return $score + $key;
+ }
+ }
+ }
+
+ };
+
+ $children = $this->children();
+ $tmp = $children;
+ uksort(
+ $children,
+ function($a, $b) use ($sortScore, $tmp) {
+
+ $sA = $sortScore($a, $tmp);
+ $sB = $sortScore($b, $tmp);
+
+ return $sA - $sB;
+
+ }
+ );
+
+ foreach ($children as $child) $str .= $child->serialize();
+ $str .= "END:" . $this->name . "\r\n";
+
+ return $str;
+
+ }
+
+ /**
+ * This method returns an array, with the representation as it should be
+ * encoded in JSON. This is used to create jCard or jCal documents.
+ *
+ * @return array
+ */
+ function jsonSerialize() {
+
+ $components = [];
+ $properties = [];
+
+ foreach ($this->children as $childGroup) {
+ foreach ($childGroup as $child) {
+ if ($child instanceof self) {
+ $components[] = $child->jsonSerialize();
+ } else {
+ $properties[] = $child->jsonSerialize();
+ }
+ }
+ }
+
+ return [
+ strtolower($this->name),
+ $properties,
+ $components
+ ];
+
+ }
+
+ /**
+ * This method serializes the data into XML. This is used to create xCard or
+ * xCal documents.
+ *
+ * @param Xml\Writer $writer XML writer.
+ *
+ * @return void
+ */
+ function xmlSerialize(Xml\Writer $writer) {
+
+ $components = [];
+ $properties = [];
+
+ foreach ($this->children as $childGroup) {
+ foreach ($childGroup as $child) {
+ if ($child instanceof self) {
+ $components[] = $child;
+ } else {
+ $properties[] = $child;
+ }
+ }
+ }
+
+ $writer->startElement(strtolower($this->name));
+
+ if (!empty($properties)) {
+
+ $writer->startElement('properties');
+
+ foreach ($properties as $property) {
+ $property->xmlSerialize($writer);
+ }
+
+ $writer->endElement();
+
+ }
+
+ if (!empty($components)) {
+
+ $writer->startElement('components');
+
+ foreach ($components as $component) {
+ $component->xmlSerialize($writer);
+ }
+
+ $writer->endElement();
+ }
+
+ $writer->endElement();
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return [];
+
+ }
+
+ /* Magic property accessors {{{ */
+
+ /**
+ * Using 'get' you will either get a property or component.
+ *
+ * If there were no child-elements found with the specified name,
+ * null is returned.
+ *
+ * To use this, this may look something like this:
+ *
+ * $event = $calendar->VEVENT;
+ *
+ * @param string $name
+ *
+ * @return Property
+ */
+ function __get($name) {
+
+ if ($name === 'children') {
+
+ throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');
+
+ }
+
+ $matches = $this->select($name);
+ if (count($matches) === 0) {
+ return;
+ } else {
+ $firstMatch = current($matches);
+ /** @var $firstMatch Property */
+ $firstMatch->setIterator(new ElementList(array_values($matches)));
+ return $firstMatch;
+ }
+
+ }
+
+ /**
+ * This method checks if a sub-element with the specified name exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ function __isset($name) {
+
+ $matches = $this->select($name);
+ return count($matches) > 0;
+
+ }
+
+ /**
+ * Using the setter method you can add properties or subcomponents.
+ *
+ * You can either pass a Component, Property
+ * object, or a string to automatically create a Property.
+ *
+ * If the item already exists, it will be removed. If you want to add
+ * a new item with the same name, always use the add() method.
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ function __set($name, $value) {
+
+ $name = strtoupper($name);
+ $this->remove($name);
+ if ($value instanceof self || $value instanceof Property) {
+ $this->add($value);
+ } else {
+ $this->add($name, $value);
+ }
+ }
+
+ /**
+ * Removes all properties and components within this component with the
+ * specified name.
+ *
+ * @param string $name
+ *
+ * @return void
+ */
+ function __unset($name) {
+
+ $this->remove($name);
+
+ }
+
+ /* }}} */
+
+ /**
+ * This method is automatically called when the object is cloned.
+ * Specifically, this will ensure all child elements are also cloned.
+ *
+ * @return void
+ */
+ function __clone() {
+
+ foreach ($this->children as $childName => $childGroup) {
+ foreach ($childGroup as $key => $child) {
+ $clonedChild = clone $child;
+ $clonedChild->parent = $this;
+ $clonedChild->root = $this->root;
+ $this->children[$childName][$key] = $clonedChild;
+ }
+ }
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * It is also possible to specify defaults and severity levels for
+ * violating the rule.
+ *
+ * See the VEVENT implementation for getValidationRules for a more complex
+ * example.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return [];
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ *
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $rules = $this->getValidationRules();
+ $defaults = $this->getDefaults();
+
+ $propertyCounters = [];
+
+ $messages = [];
+
+ foreach ($this->children() as $child) {
+ $name = strtoupper($child->name);
+ if (!isset($propertyCounters[$name])) {
+ $propertyCounters[$name] = 1;
+ } else {
+ $propertyCounters[$name]++;
+ }
+ $messages = array_merge($messages, $child->validate($options));
+ }
+
+ foreach ($rules as $propName => $rule) {
+
+ switch ($rule) {
+ case '0' :
+ if (isset($propertyCounters[$propName])) {
+ $messages[] = [
+ 'level' => 3,
+ 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
+ 'node' => $this,
+ ];
+ }
+ break;
+ case '1' :
+ if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] !== 1) {
+ $repaired = false;
+ if ($options & self::REPAIR && isset($defaults[$propName])) {
+ $this->add($propName, $defaults[$propName]);
+ $repaired = true;
+ }
+ $messages[] = [
+ 'level' => $repaired ? 1 : 3,
+ 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
+ 'node' => $this,
+ ];
+ }
+ break;
+ case '+' :
+ if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
+ $messages[] = [
+ 'level' => 3,
+ 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
+ 'node' => $this,
+ ];
+ }
+ break;
+ case '*' :
+ break;
+ case '?' :
+ if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
+ $messages[] = [
+ 'level' => 3,
+ 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
+ 'node' => $this,
+ ];
+ }
+ break;
+
+ }
+
+ }
+ return $messages;
+
+ }
+
+ /**
+ * Call this method on a document if you're done using it.
+ *
+ * It's intended to remove all circular references, so PHP can easily clean
+ * it up.
+ *
+ * @return void
+ */
+ function destroy() {
+
+ parent::destroy();
+ foreach ($this->children as $childGroup) {
+ foreach ($childGroup as $child) {
+ $child->destroy();
+ }
+ }
+ $this->children = [];
+
+ }
+
+}