From 3afb1e65ad0856e988c28c6547c3745c901b29e4 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Thu, 24 May 2018 17:05:23 +0200 Subject: [PATCH 001/166] Coordinator Model proposal --- docs/documentation/maintenance-coordinator.md | 87 +++++++++++++++++++ docs/documentation/maintenance.md | 19 ++-- 2 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 docs/documentation/maintenance-coordinator.md diff --git a/docs/documentation/maintenance-coordinator.md b/docs/documentation/maintenance-coordinator.md new file mode 100644 index 000000000000..eebd2edc388a --- /dev/null +++ b/docs/documentation/maintenance-coordinator.md @@ -0,0 +1,87 @@ +#Coordinator Model + +## Table of Contents + + +1. [Role of the Coordinator](#role-of-a-coordinator) +1. [Change Management](#change-management) +1. [Issue Management](#issue-management) +1. [Scenarios](#scenarios) +1. [What can be expected of a coordinator?](#expectations) + + + + +## Role of the Coordinator + +The Coordinator is not the owner of the component but much more the curator. +The coordinator ensures the quality of contributations to the component +and makes it possible for others to commit. The coordinator is responsible +that the documentation is kept up to date by the contributors and that the +guidelines of the component are met. The coordinator moderates the discussion on +finding a vision and on the development. The coordinator further is contact +for any sort of question that may arise about the respective component. + +The motivation of the coordinator is mostly driven by the need of a +reliable component for a certain aspect. Further the coordinator is +probably the most attractive contractor for clients aiming to change +aspects of the component due to the very indepth know how and the +listing as coordinator. + + +##Change Management +Everybody may contribute to any aspect of the component. Such contributions +are handed in by pull requests or some other source of data if declared so in the components guidelines. +Pull requests on the public interface must be accepted by the JF. The coordinator gives a +recommendation to the JF on whether to accept or decline the PR. The decision of the JF may +be implicit if no objections to the recommendation of the coordinator is made. If no agreement +is achieved in the JF, the Technical Board will decide upon the request. + +Final implementations without further changes on the interface do not need +formal approval by the JF. The merge of the implementation is performed +by the coordinator. + + +##Issue Management +Everybody is invited to make proposals on how to tackle any issue by proposing +a respective PR. Issues of the component must be reported as bugs. The coordinators +are responsible to assign the developer in charge on solving the bug. Due to the focus +on code quality, a low amount of bugs should be expected. + + +##Scenarios +This maintainance model is suited for components that have the potential +to grow too large to be handled by one single developer and therefore highly +benefit from contributions among different developers and even service providers. +It is further important that the component is of modular structure with different +parts following a similar scheme. It is especially suited, if some component is +of a critical importance for many other components, since it is designed to allow +a collaborative development of the vision for such a key aspect. + + +##What can be expected of a coordinator? +* The coordinator MUST moderate the discussion on finding a vision on +the development of the component. +* The coordinator MUST give recommendations to the JF whether to accept +or decline changes. +* The coordinator MUST accept decisions of the Technical Board on change +requests in case of disagreement on the JF. +* The coordinator MUST review final implementation of some accepted +interface change or organize some substitute to perform the review. +* The coordinator MAY ask for funding to perform the review. +* The coordinator MAY ask to split some interface change or implementation +into multiple pieces to make the change easier to understand. +* The coordinator MUST ensure, that the documentation of the component is +up to date. The coordinator MAY ask to update the documentation as a condition +for accepting some change request. +* The coordinator MUST ensure, that the automated unit tests are kept up +to date. The coordinator MAY ask other developers to write and update those +tests in their own development. +* The coordinator MUST assign unassigned issues to the responsible developer. +* The coordinator SHOULD devise some guidelines concerning the processes +around the respective component fitting it's exact needs as done so for the +UI-Service. Such guidelines MUST be accepted by the JF. + +**Please note:** The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", +"SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" +in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). \ No newline at end of file diff --git a/docs/documentation/maintenance.md b/docs/documentation/maintenance.md index f7e43eb913ca..5c3dda97021d 100644 --- a/docs/documentation/maintenance.md +++ b/docs/documentation/maintenance.md @@ -2,7 +2,7 @@ ILIAS Maintenance ================= The development of the ILIAS source code is coordinated and maintained by a coordination team within the ILIAS network. Besides the main responsibilities for the project, several developers and users are maintaining certain modules of ILIAS. -# Coordination Team +# Special Roles * **Product Management**: [Matthias Kunkel] * **Technical Board**: [Alexander Killing], [Michael Jansen], [Fabian Schmid], [Timon Amstutz], [Richard Klees] @@ -13,9 +13,14 @@ The development of the ILIAS source code is coordinated and maintained by a coor # Maintainers We highly appreciate to get new developers but we have to guarantee the sustainability and the quality of the ILIAS source code. The system is complex for new developers and they need to know the concepts of ILIAS that are described in the development guide. -Communication among developers that are working on a specific module needs to be assured. Final decision about getting write access to the ILIAS development system (Github) is handled by the product manager. +Communication among developers that are working on a specific component needs to be assured. Final decision about getting write access to the ILIAS development system (Github) is handled by the product manager. -The following rules must be respected for everyone involved in the programming of ILIAS: +ILIAS is currently maintained by three types of Maintainerships: +- First Component Maintainer +- Second Component Maintainer +- [Coordinator Model](maintenance-coordinator.md) + +The following rules must be respected for everyone involved in the programming of ILIAS for all components having a listed component maintainer (see bellow): 1. Decisions on new features or feature removals are made by the responsible first maintainer and the product manager in the Jour Fixe meetings after an open discussion. 2. All components have a first and second maintainer. Code changes are usually done by the first maintainer. The first maintainer may forward new implementations to the second maintainer. @@ -26,10 +31,6 @@ Responsibilities of a component maintainer: - Component maintainers must agree to coordinate the development of their component with the product manager. - Component maintainer are responsible for bug fixing of their component and get assigned related bugs automatically by the [Issue-Tracker](http://mantis.ilias.de). -ILIAS is currently maintained by two types of Maintainerships: - -- First Maintainer -- Second Maintainer The code base is deviced in several components: @@ -569,13 +570,13 @@ The code base is deviced in several components: * Tester: [richtera](http://www.ilias.de/docu/goto_docu_usr_41247.html) -Components in the Service Maintenance Model: +Components in the Coordinator Model [Coordinator Model](maintenance-coordinator.md): * **UI-Service** * Coordinators: [amstutz](http://www.ilias.de/docu/goto_docu_usr_26468.html) * Used in Directories: src/UI, -The following directories are currently maintained unter the Service Maintenance Model: +The following directories are currently maintained under the [Coordinator Model](maintenance-coordinator.md): * src/UI (Coordinator: [amstutz](http://www.ilias.de/docu/goto_docu_usr_26468.html)) From 31b82a99520a3e5cdb04901664294a120e6a4327 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Mon, 4 Jun 2018 15:18:39 +0200 Subject: [PATCH 002/166] added constraints to the password-input --- src/UI/Component/Input/Field/Factory.php | 8 ++- .../Input/Field/Password/with_contraints.php | 50 ++++++++++++++ .../Constraints/Password/Factory.php | 66 +++++++++++++++++++ .../Constraints/Password/HasLowerChars.php | 23 +++++++ .../Constraints/Password/HasMinLength.php | 30 +++++++++ .../Constraints/Password/HasNumbers.php | 23 +++++++ .../Constraints/Password/HasSpecialChars.php | 26 ++++++++ .../Constraints/Password/HasUpperChars.php | 23 +++++++ src/Validation/Factory.php | 9 +++ 9 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 src/UI/examples/Input/Field/Password/with_contraints.php create mode 100644 src/Validation/Constraints/Password/Factory.php create mode 100644 src/Validation/Constraints/Password/HasLowerChars.php create mode 100644 src/Validation/Constraints/Password/HasMinLength.php create mode 100644 src/Validation/Constraints/Password/HasNumbers.php create mode 100644 src/Validation/Constraints/Password/HasSpecialChars.php create mode 100644 src/Validation/Constraints/Password/HasUpperChars.php diff --git a/src/UI/Component/Input/Field/Factory.php b/src/UI/Component/Input/Field/Factory.php index 4a1a47dafd1c..608fc72158a0 100644 --- a/src/UI/Component/Input/Field/Factory.php +++ b/src/UI/Component/Input/Field/Factory.php @@ -228,7 +228,13 @@ public function checkbox($label, $byline = null); * 1: Password Input MUST be used for passwords. * interaction: * 1: > - * Password Input MUST NOT limit the number of characters. + * Password Input SHOULD NOT limit the number of characters. + * 2: > + * When used for authentication, Password Input MUST NOT reveal any + * settings by placing constraints on it. + * 3: > + * On the other hand, when setting a password, Password Input + * SHOULD enforce strong passwords by appropiate contraints. * * --- * diff --git a/src/UI/examples/Input/Field/Password/with_contraints.php b/src/UI/examples/Input/Field/Password/with_contraints.php new file mode 100644 index 000000000000..8e8c7b08fd55 --- /dev/null +++ b/src/UI/examples/Input/Field/Password/with_contraints.php @@ -0,0 +1,50 @@ +ui()->factory(); + $renderer = $DIC->ui()->renderer(); + $request = $DIC->http()->request(); + $data = new \ILIAS\Data\Factory(); + $validation = new \ILIAS\Validation\Factory($data); + $pw_validation = $validation->password(); + + //Step 1: Define the input field + //and add some constraints. + $pwd_input = $ui->input()->field()->password("Password", "contraints in place.") + ->withAdditionalConstraint( + $validation->parallel([ + $pw_validation->hasMinLength(8), + $pw_validation->hasUpperChars(), + $pw_validation->hasLowerChars(), + $pw_validation->hasNumbers(), + $pw_validation->hasSpecialChars() + ]) + ); + + //Step 2: Define the form and attach the field. + $DIC->ctrl()->setParameterByClass( + 'ilsystemstyledocumentationgui', + 'example', + 'password' + ); + $form_action = $DIC->ctrl()->getFormActionByClass('ilsystemstyledocumentationgui'); + $form = $ui->input()->container()->form()->standard($form_action, ['pwd'=>$pwd_input]); + + //Step 3: Define some data processing. + $result = ''; + if ($request->getMethod() == "POST" + && $request->getQueryParams()['example'] =='password') { + $form = $form->withRequest($request); + $result = $form->getData(); + } + + //Step 4: Render the form. + return + $renderer->render($form); +} + + diff --git a/src/Validation/Constraints/Password/Factory.php b/src/Validation/Constraints/Password/Factory.php new file mode 100644 index 000000000000..9d5489bb8875 --- /dev/null +++ b/src/Validation/Constraints/Password/Factory.php @@ -0,0 +1,66 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Data; + +/** + * Factory for creating password constraints. + */ +class Factory { + /** + * @var ILIAS\Data\Factory + */ + protected $data_factory; + + public function __construct(Data\Factory $data_factory) { + $this->data_factory = $data_factory; + } + + /** + * Get the constraint that a password has a minimum length. + * + * @param int $min_length + * @return Constraint + */ + public function hasMinLength($min_length) { + return new HasMinLength($min_length, $this->data_factory); + } + + /** + * Get the constraint that a password has upper case chars. + * + * @return Constraint + */ + public function hasUpperChars() { + return new HasUpperChars($this->data_factory); + } + + /** + * Get the constraint that a password has lower case chars. + * + * @return Constraint + */ + public function hasLowerChars() { + return new HasLowerChars($this->data_factory); + } + + /** + * Get the constraint that a password has numbers. + * + * @return Constraint + */ + public function hasNumbers() { + return new HasNumbers($this->data_factory); + } + + /** + * Get the constraint that a password has special chars. + * + * @return Password + */ + public function hasSpecialChars() { + return new HasSpecialChars($this->data_factory); + } +} \ No newline at end of file diff --git a/src/Validation/Constraints/Password/HasLowerChars.php b/src/Validation/Constraints/Password/HasLowerChars.php new file mode 100644 index 000000000000..5458bc894cc9 --- /dev/null +++ b/src/Validation/Constraints/Password/HasLowerChars.php @@ -0,0 +1,23 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Validation\Constraints; +use ILIAS\Validation\Constraint; +use ILIAS\Data; + + +class HasLowerChars extends Constraints\Custom implements Constraint { + public function __construct(Data\Factory $data_factory) { + parent::__construct( function (Data\Password $value) { + return (bool) preg_match('/[a-z]/', $value->toString()); + }, + function ($value) { + return "Password must contain lower-case characters."; + }, + $data_factory + ); + } + +} \ No newline at end of file diff --git a/src/Validation/Constraints/Password/HasMinLength.php b/src/Validation/Constraints/Password/HasMinLength.php new file mode 100644 index 000000000000..059d3a9c6f92 --- /dev/null +++ b/src/Validation/Constraints/Password/HasMinLength.php @@ -0,0 +1,30 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Validation\Constraints; +use ILIAS\Validation\Constraint; +use ILIAS\Data; + + +class HasMinLength extends Constraints\Custom implements Constraint { + /** + * @var int + */ + protected $min_length; + + public function __construct($min_length, Data\Factory $data_factory) { + assert('is_int($min_length)'); + $this->min_length = $min_length; + parent::__construct( function (Data\Password $value) { + return strlen($value->toString()) >= $this->min_length; + }, + function ($value) { + return "Password has a length less than '{$this->min_length}'."; + }, + $data_factory + ); + } + +} \ No newline at end of file diff --git a/src/Validation/Constraints/Password/HasNumbers.php b/src/Validation/Constraints/Password/HasNumbers.php new file mode 100644 index 000000000000..1c8999bca395 --- /dev/null +++ b/src/Validation/Constraints/Password/HasNumbers.php @@ -0,0 +1,23 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Validation\Constraints; +use ILIAS\Validation\Constraint; +use ILIAS\Data; + + +class HasNumbers extends Constraints\Custom implements Constraint { + public function __construct(Data\Factory $data_factory) { + parent::__construct( function (Data\Password $value) { + return (bool) preg_match('/[0-9]/', $value->toString()); + }, + function ($value) { + return "Password must contain numbers."; + }, + $data_factory + ); + } + +} \ No newline at end of file diff --git a/src/Validation/Constraints/Password/HasSpecialChars.php b/src/Validation/Constraints/Password/HasSpecialChars.php new file mode 100644 index 000000000000..edf111b69235 --- /dev/null +++ b/src/Validation/Constraints/Password/HasSpecialChars.php @@ -0,0 +1,26 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Validation\Constraints; +use ILIAS\Validation\Constraint; +use ILIAS\Data; + + +class HasSpecialChars extends Constraints\Custom implements Constraint { + + protected static $ALLOWED_CHARS = '/[,_.\-#\+\*?!%§\(\)\$]/'; + + public function __construct(Data\Factory $data_factory) { + parent::__construct( function (Data\Password $value) { + return (bool) preg_match(static::$ALLOWED_CHARS, $value->toString()); + }, + function ($value) { + return "Password must contain special chars."; + }, + $data_factory + ); + } + +} \ No newline at end of file diff --git a/src/Validation/Constraints/Password/HasUpperChars.php b/src/Validation/Constraints/Password/HasUpperChars.php new file mode 100644 index 000000000000..3b117c2b40d4 --- /dev/null +++ b/src/Validation/Constraints/Password/HasUpperChars.php @@ -0,0 +1,23 @@ + Extended GPL, see docs/LICENSE */ + +namespace ILIAS\Validation\Constraints\Password; + +use ILIAS\Validation\Constraints; +use ILIAS\Validation\Constraint; +use ILIAS\Data; + + +class HasUpperChars extends Constraints\Custom implements Constraint { + public function __construct(Data\Factory $data_factory) { + parent::__construct( function (Data\Password $value) { + return (bool) preg_match('/[A-Z]/', $value->toString()); + }, + function ($value) { + return "Password must contain upper-case characters."; + }, + $data_factory + ); + } + +} \ No newline at end of file diff --git a/src/Validation/Factory.php b/src/Validation/Factory.php index ffdd379a3ac1..cdf964106afb 100644 --- a/src/Validation/Factory.php +++ b/src/Validation/Factory.php @@ -117,4 +117,13 @@ public function hasMinLength($min_length) { public function custom(callable $is_ok, $error) { return new Constraints\Custom($is_ok, $error, $this->data_factory); } + + /** + * Get the factory for password constraints. + * + * @return ILIAS\Validation\Constraints\Password\Factory; + */ + public function password() { + return new Constraints\Password\Factory($this->data_factory); + } } From df22b7cba008b54fedc554be7a430eb87dc32133 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Mon, 4 Jun 2018 17:23:17 +0200 Subject: [PATCH 003/166] convenience-function to quickly get constraints for a pass-field --- Services/Style/System/data/data.json | 2 +- .../Component/Input/Field/Password.php | 43 ++++++++++++++++++- .../Input/Field/Password/with_contraints.php | 9 ++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Services/Style/System/data/data.json b/Services/Style/System/data/data.json index 0451b507726f..6fab24a7f150 100755 --- a/Services/Style/System/data/data.json +++ b/Services/Style/System/data/data.json @@ -1 +1 @@ -{"FactoryUIComponent":{"id":"FactoryUIComponent","title":"UIComponent","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"What is to be done by this control","composition":"What happens if the control is operated","effect":"What happens if the control is operated","rivals":{"Rival 1":"What other controls are similar, what is their distinction"}},"background ":"Relevant academic information","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Where and when an element is to be used or not."},"composition":[],"interaction":{"2":"How the interaction with this object takes place."},"wording":{"3":"How the wording of labels or captions must be."},"ordering":{"5":"How different elements of this instance are to be ordered."},"style":{"4":"How this element should look like."},"responsiveness":{"6":"How this element behaves on changing screen sizes"},"accessibility":{"7":"How this element is made accessible"}},"parent":false,"children":["IconFactoryIcon","CounterFactoryCounter","ImageFactoryImage","DividerFactoryDivider","LinkFactoryLink","GlyphFactoryGlyph","ButtonFactoryButton","DropdownFactoryDropdown","BreadcrumbsBreadcrumbsBreadcrumbs","ViewControlFactoryViewControl","ChartFactoryChart","InputFactoryInput","CardCardCard","DeckDeckDeck","ListingFactoryListing","PanelFactoryPanel","ItemFactoryItem","ModalFactoryModal","PopoverFactoryPopover","DropzoneFactoryDropzone","LegacyLegacyLegacy","TableFactoryTable"],"less_variables":[],"path":"src\/UI\/Factory"},"IconFactoryIcon":{"id":"IconFactoryIcon","title":"Icon","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Icons are quickly comprehensible and recognizable graphics. They indicate the functionality or nature of a text-element or context: Icons will mainly be used in front of object-titles, e.g. in the header, the tree and in repository listing.","composition":"Icons come in three fixed sizes: small, medium and large. They can be configured with an additional \"abbreviation\", a text of a few characters that will be rendered on top of the image.","effect":"Icons themselves are not interactive; however they are allowed within interactive containers.","rivals":{"1":"Glyphs are typographical characters that act as a trigger for some action.","2":"Images belong to the content and can be purely decorative."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Icons MUST be used to represent objects or context.","2":"Icons MUST be used in combination with a title or label.","3":"An unique Icon MUST always refer to the same thing."},"composition":[],"interaction":[],"wording":{"1":"The aria-label MUST state the represented object-type.","2":"The abbreviation SHOULD consist of one or two letters."},"ordering":[],"style":{"1":"Icons MUST have a class indicating their usage.","2":"Icons MUST be tagged with a CSS-class indicating their size."},"responsiveness":[],"accessibility":{"1":"Icons MUST use aria-label."}},"parent":"FactoryUIComponent","children":["IconStandardStandard","IconCustomCustom"],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Factory"},"CounterFactoryCounter":{"id":"CounterFactoryCounter","title":"Counter","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Counter inform users about the quantity of items indicated by a glyph.","composition":"Counters consist of a number and some background color and are placed one the 'end of the line' in reading direction of the item they state the count for.","effect":"Counters convey information, they are not interactive.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3854_1357.html"],"rules":{"usage":{"1":"A counter MUST only be used in combination with a glyph."},"composition":{"1":"A counter MUST contain exactly one number greater than zero and no other characters."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["CounterCounterStatus","CounterCounterNovelty"],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Factory"},"ImageFactoryImage":{"id":"ImageFactoryImage","title":"Image","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Image component is used to display images of various sources.","composition":"An Image is composed of the image and an alternative text for screen readers.","effect":"Images may be included in interacted components but not interactive on their own.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Images MUST contain the alt attribute. This attribute MAY be left empty (alt=\"\") if the image is of decorative nature. According to the WAI, decorative images don\u2019t add information to the content of a page. For example, the information provided by the image might already be given using adjacent text, or the image might be included to make the website more visually attractive (see https:\/\/www.w3.org\/WAI\/tutorials\/images\/decorative\/<\/a>)."}},"parent":"FactoryUIComponent","children":["ImageImageStandard","ImageImageResponsive"],"less_variables":[],"path":"src\/UI\/Component\/Image\/Factory"},"DividerFactoryDivider":{"id":"DividerFactoryDivider","title":"Divider","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A divider marks a thematic change in a sequence of other components. A Horizontal Divider is used to mark a thematic change in sequence of elements that are stacked from top to bottom, e.g. in a Dropdown. A Vertical Divider is used to mark a thematic change in a sequence of elements that are lined up from left to right, e.g. a Toolbar.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dividers MUST only be used in container components that explicitly state and define the usage of Dividers within the container."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["DividerHorizontalHorizontal","DividerVerticalVertical"],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Factory"},"LinkFactoryLink":{"id":"LinkFactoryLink","title":"Link","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Links are used navigate to other resources or views of the system. Clicking on a link does not change the systems status.","composition":"Link is a clickable, graphically minimal obtrusive control element. It can bear text or other content. Links always contain a valid href tag which should not not just contain a hash sign.","effect":"On-click, the resource or view indicated by the link is requested and presented. Links are not used to trigger Javascript events.","rivals":{"buttons":"Buttons are used to trigger Interactions that usually change the systems status. Buttons are much more obtrusive than links and may fire JS events."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Links MAY be used inline in a Textual Paragraphs."},"composition":[],"interaction":{"1":"Hovering an active link should indicate a possible interaction.","2":"Links MUST not be used to fire Javascript events."},"wording":{"1":"The wording of the link SHOULD name the target view or resource."},"ordering":[],"style":{"1":"Links SHOULD not be presented with a separate background color."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"a\" MUST be used to properly identify an element."}},"parent":"FactoryUIComponent","children":["LinkStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Link\/Factory"},"GlyphFactoryGlyph":{"id":"GlyphFactoryGlyph","title":"Glyph","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Glyphs map a generally known concept or symbol to a specific concept in ILIAS. Glyphs are used when space is scarce.","composition":"A glyph is a typographical character that represents something else. As any other typographical character, they can be manipulated by regular CSS. If hovered they change their background to indicate possible interactions.","effect":"Glyphs act as trigger for some action such as opening a certain Overlay type or as shortcut.","rivals":{"icon":"Standalone Icons are not interactive. Icons can be in an interactive container however. Icons merely serve as additional hint of the functionality described by some title. Glyphs are visually distinguished from object icons: they are monochrome."}},"background ":"\"In typography, a glyph is an elemental symbol within an agreed set of symbols, intended to represent a readable character for the purposes of writing and thereby expressing thoughts, ideas and concepts.\" (https:\/\/en.wikipedia.org\/wiki\/Glyph) Lidwell states that such symbols are used \"to improve the recognition and recall of signs and controls\".","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Glyphs MUST NOT be used in content titles.","2":"Glyphs MUST be used for cross-sectional functionality such as mail for example and NOT for representing objects.","3":"Glyphs SHOULD be used for very simple tasks that are repeated at many places throughout the system.","4":"Services such as mail MAY be represented by a glyph AND an icon."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"All Glyphs MUST be taken from the Bootstrap Glyphicon Halflings set. Exceptions MUST be approved by the JF."},"responsiveness":[],"accessibility":{"1":"The functionality triggered by the Glyph must be indicated to screen readers with by the attribute aria-label or aria-labelledby attribute."}},"parent":"FactoryUIComponent","children":["GlyphGlyphSettings","GlyphGlyphCollapse","GlyphGlyphExpand","GlyphGlyphAdd","GlyphGlyphRemove","GlyphGlyphUp","GlyphGlyphDown","GlyphGlyphBack","GlyphGlyphNext","GlyphGlyphSortAscending","GlyphGlyphSortDescending","GlyphGlyphBriefcase","GlyphGlyphUser","GlyphGlyphMail","GlyphGlyphNotification","GlyphGlyphTag","GlyphGlyphNote","GlyphGlyphComment","GlyphGlyphLike","GlyphGlyphLove","GlyphGlyphDislike","GlyphGlyphLaugh","GlyphGlyphAstounded","GlyphGlyphSad","GlyphGlyphAngry"],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Factory"},"ButtonFactoryButton":{"id":"ButtonFactoryButton","title":"Button","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Buttons trigger interactions that change the system\u2019s or view's status. Acceptable changes to the current view are those that do not result in a complete replacement of the overall screen (e.g. modals).","composition":"Button is a clickable, graphically obtrusive control element. It can bear text.","effect":"On-click, the action indicated by the button is carried out.","rivals":{"glyph":"Glyphs are used if the enclosing Container Collection can not provide enough space for textual information or if such an information would clutter the screen.","links":"Links are used to trigger Interactions that do not change the systems status. They are usually contained inside a Navigational Collection."}},"background ":"Wording rules have been inspired by the iOS Human Interface Guidelines (UI-Elements->Controls->System Button) Style rules have been inspired from the GNOME Human Interface Guidelines->Buttons.","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Buttons MUST NOT be used inside a Textual Paragraph."},"composition":[],"interaction":{"2":"If an action is temporarily not available, Buttons MUST be disabled by setting as type 'disabled'.","3":"A button MUST NOT be used for navigational purpose."},"wording":{"1":"The caption of a Button SHOULD contain no more than two words.","2":"The wording of the button SHOULD describe the action the button performs by using a verb or a verb phrase.","3":"Every word except articles, coordinating conjunctions and prepositions of four or fewer letters MUST be capitalized.","4":"For standard events such as saving or canceling the existing standard terms MUST be used if possible: Save, Cancel, Delete, Cut, Copy.","5":"There are cases where a non-standard label such as \u201cSend Mail\u201d for saving and sending the input of a specific form might deviate from the standard. These cases MUST however specifically justified."},"ordering":[],"style":{"1":"If Text is used inside a Button, the Button MUST be at least six characters wide."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"button\" MUST be used to properly identify an element as a Button if there is no good reason to do otherwise.","2":"Button DOM elements MUST either be of type \"button\", of type \"a\" accompanied with the aria-role \u201cButton\u201d or input along with the type attribute \u201cbutton\u201d or \"submit\"."}},"parent":"FactoryUIComponent","children":["ButtonStandardStandard","ButtonPrimaryPrimary","ButtonCloseClose","ButtonShyShy","ButtonMonthMonth","ButtonTagTag","ButtonBulkyBulky"],"less_variables":[],"path":"src\/UI\/Component\/Button\/Factory"},"DropdownFactoryDropdown":{"id":"DropdownFactoryDropdown","title":"Dropdown","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Dropdowns reveal a list of interactions that change the system\u2019s status or navigate to a different view.","composition":"Dropdown is a clickable, graphically obtrusive control element. It can bear text. On-click a list of Shy Buttons and optional Dividers is shown.","effect":"On-click, a list of actions is revealed. Clicking an item will trigger the action indicated. Clicking outside of an opened Dropdown will close the list of items.","rivals":{"button":"Buttons are used, if single actions should be presented directly in the user interface.","links":"Links are used to trigger actions that do not change the systems status. They are usually contained inside a Navigational Collection.","popovers":"Dropdowns only provide a list of possible actions. Popovers can include more diverse and flexible content."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dropdowns MUST NOT be used standalone. They are only parts of more complex UI elements. These elements MUST define their use of Dropdown. E.g. a List or a Table MAY define that a certain kind of Dropdown is used as part of the UI element."},"composition":[],"interaction":{"1":"Only Dropdown Items MUST trigger an action or change a view. The Dropdown trigger element is only used to show and hide the list of Dropdown Items."},"wording":{"1":"The label of a Dropdown SHOULD contain no more than two words.","2":"Every word except articles, coordinating conjunctions and prepositions of four or fewer letters MUST be capitalized.","3":"For standard events such as saving or canceling the existing standard terms MUST be used if possible: Delete, Cut, Copy.","4":"There are cases where a non-standard label such as \u201cSend Mail\u201d for saving and sending the input of a specific form might deviate from the standard. These cases MUST however specifically justified."},"ordering":[],"style":{"1":"If Text is used inside a Dropdown label, the Dropdown MUST be at least six characters wide."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"button\" MUST be used to properly identify an element as a Dropdown.","2":"Dropdown items MUST be implemented as \"ul\" list with a set of \"li\" elements and nested Shy Button elements for the actions.","3":"Triggers of Dropdowns MUST indicate their effect by the aria-haspopup attribute set to true.","4":"Triggers of Dropdowns MUST indicate the current state of the Dropdown by the aria-expanded label.","5":"Dropdowns MUST be accessible by keyboard by focusing the trigger element and clicking the return key.","6":"Entries in a Dropdown MUST be accessible by the tab-key if opened.","7":"The focus MAY leave the Dropdown if tab is pressed while focusing the last element. This differs from the behaviour in Popovers and Modals."}},"parent":"FactoryUIComponent","children":["DropdownStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Dropdown\/Factory"},"BreadcrumbsBreadcrumbsBreadcrumbs":{"id":"BreadcrumbsBreadcrumbsBreadcrumbs","title":"Breadcrumbs","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Breadcrumbs is a supplemental navigation scheme. It eases the user's navigation to higher items in hierarchical structures. Breadcrumbs also serve as an effective visual aid indicating the user's location on a website.","composition":"Breadcrumbs-entries are rendered as horizontally arranged UI Links with a seperator in-between.","effect":"Clicking on an entry will get the user to the respective location.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Crumbs MUST trigger navigation to other resources of the system."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Breadcrumbs\/Breadcrumbs"},"ViewControlFactoryViewControl":{"id":"ViewControlFactoryViewControl","title":"View Control","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"View Controls switch between different visualisation of data.","composition":"View Controls are composed mainly of buttons, they are often found in toolbars.","effect":"Interacting with a view control changes to display in some content area.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["ViewControlModeMode","ViewControlSectionSection","ViewControlSortationSortation","ViewControlPaginationPagination"],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Factory"},"ChartFactoryChart":{"id":"ChartFactoryChart","title":"Chart","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Charts are used to graphically represent data in various forms such as maps, graphs or diagrams.","composition":"Charts are composed of various graphical and textual elements representing the raw data.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Charts SHOULD not rely on colors to convey information."},"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ChartScaleBarScaleBar","ChartProgressMeterFactoryProgressMeter"],"less_variables":[],"path":"src\/UI\/Component\/Chart\/Factory"},"InputFactoryInput":{"id":"InputFactoryInput","title":"Input","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"In opposite to components with a purely receptive or at most navigational character, input elements are used to relay user-induced data to the system.","composition":"An input consists of fields that define the way data is entered and a container around those fields that defines the way the data is submitted to the system.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["InputFieldFactoryField","InputContainerFactoryContainer"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Factory"},"CardCardCard":{"id":"CardCardCard","title":"Card","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A card is a flexible content container for small chunks of structured data. Cards are often used in so-called Decks which are a gallery of Cards.","composition":"Cards contain a header, which often includes an Image or Icon and a Title as well as possible actions as Default Buttons and 0 to n sections that may contain further textual descriptions, links and buttons. The size of the cards in decks may be set to extra small (12 cards per row), small (6 cards per row, default), medium (4 cards per row), large (3 cards per row), extra large (2 cards per row) and full (1 card per row). The number of cards per row is responsively adapted, if the size of the screen is changed.","effect":"Cards may contain Interaction Triggers.","rivals":{"Heading Panel":"Heading Panels fill up the complete available width in the Center Content Section. Multiple Heading Panels are stacked vertically.","Block Panels":"Block Panels are used in Sidebars"}},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3208_1357.html"],"rules":{"usage":[],"composition":{"1":"Cards MUST contain a title.","2":"Cards SHOULD contain an Image or Icon in the header section.","3":"Cards MAY contain Interaction Triggers."},"interaction":[],"wording":[],"ordering":[],"style":{"1":"Sections of Cards MUST be separated by Dividers."},"responsiveness":[],"accessibility":{"1":"If multiple Cards are used, they MUST be contained in a Deck."}},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Card\/Card"},"DeckDeckDeck":{"id":"DeckDeckDeck","title":"Deck","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Decks are used to display multiple Cards in a grid. They should be used if a page contains many content items that have similar style and importance. A Deck gives each item equal horizontal space indicating that they are of equal importance.","composition":"Decks are composed only of Cards arranged in a grid. The cards displayed by decks are all of equal size. This Size ranges very small (XS) to very large (XL).","effect":"The Deck is a mere scaffolding element, is has no effect.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3992_1357.html"],"rules":{"usage":{"1":"Decks MUST only be used to display multiple Cards."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"The number of cards displayed per row MUST adapt to the screen size."},"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Deck\/Deck"},"ListingFactoryListing":{"id":"ListingFactoryListing","title":"Listing","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listings are used to structure itemised textual information.","composition":"Listings may contain ordered, unordered, or labeled items.","effect":"Listings hold only textual information. They may contain Links but no Buttons.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Listings MUST NOT contain Buttons."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ListingUnorderedUnordered","ListingOrderedOrdered","ListingDescriptiveDescriptive"],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Factory"},"PanelFactoryPanel":{"id":"PanelFactoryPanel","title":"Panel","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Panels are used to group titled content.","composition":"Panels consist of a header and content section. They form one Gestalt and so build a perceivable cluster of information. Additionally an optional Dropdown that offers actions on the entity being represented by the panel is shown at the top of the Panel.","effect":"The effect of interaction with panels heavily depends on their content.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":{"1":"Panels MUST contain a title."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["PanelStandardStandard","PanelSubSub","PanelReportReport","PanelListingFactoryListing"],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Factory"},"ItemFactoryItem":{"id":"ItemFactoryItem","title":"Item","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An item displays a unique entity within the system. It shows information about that entity in a structured way.","composition":"Items contain the name of the entity as a title. The title MAY be interactive by using a Shy Button. The item contains three sections, where one section contains important information about the item, the second section shows the content of the item and another section shows metadata about the entity.","effect":"Items may contain Interaction Triggers such as Glyphs, Buttons or Tags.","rivals":{"Card":"Cards define the look of items in a deck. Todo: We need to refactor cards."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Items MUST contain the name of the displayed entity as a title.","2":"Items SHOULD contain a section with it's content.","3":"Items MAY contain Interaction Triggers.","4":"Items MAY contain a section with metadata."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ItemStandardStandard","ItemGroupGroup"],"less_variables":[],"path":"src\/UI\/Component\/Item\/Factory"},"ModalFactoryModal":{"id":"ModalFactoryModal","title":"Modal","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Modal forces users to focus on the task at hand.","composition":"A Modal is a full-screen dialog on top of the greyed-out ILIAS screen. The Modal consists of a header with a close button and a typography modal title, a content section and might have a footer.","effect":"All controls of the original context are inaccessible until the Modal is completed. Upon completion the user returns to the original context.","rivals":{"1":"Modals have some relations to popovers. The main difference between the two is the disruptive nature of the Modal and the larger amount of data that might be displayed inside a modal. Also popovers perform mostly action to add or consult metadata of an item while Modals manipulate or focus items or their sub-items directly."}},"background ":"http:\/\/quince.infragistics.com\/Patterns\/Modal%20Panel.aspx","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The main purpose of the Modals MUST NOT be navigational. But Modals MAY be dialogue of one or two steps and thus encompass \"next\"-buttons or the like.","2":"Modals MUST NOT contain other modals (Modal in Modal).","3":"Modals SHOULD not be used to perform complex workflows.","4":"Modals MUST be closable by a little \u201cx\u201d-button on the right side of the header.","5":"Modals MUST contain a title in the header."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ModalInterruptiveInterruptive","InterruptiveItemInterruptiveItem","ModalRoundTripRoundtrip","ModalLightboxLightbox","LightboxImagePageLightboxImagePage"],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Factory"},"PopoverFactoryPopover":{"id":"PopoverFactoryPopover","title":"Popover","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Popovers can be used when space is scarce i.e. within List GUI items, table cells or menus in the Header section. They offer either secondary information on object like a preview or rating to be displayed or entered. They display information about ongoing processes","composition":"Popovers consist of a layer displayed above all other content. The content of the Popover depends on the functionality it performs. A Popover MAY display a title above its content. All Popovers contain a pointer pointing from the Popover to the Triggerer of the Popover.","effect":"Popovers are shown by clicking a Triggerer component such as a Button or Glyph. The position of the Popover is calculated automatically be default. However, it is possible to specify if the popover appears horizontal (left, right) or vertical (top, bottom) relative to its Triggerer component. Popovers disappear by clicking anywhere outside the Popover or by pressing the ESC key.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Popovers MUST NOT contain horizontal scrollbars.","2":"Popovers MAY contain vertical scrollbars. The content component is responsible to define its own height and show vertical scrollbars.","3":"If Popovers are used to present secondary information of an object, they SHOULD display a title representing the object."},"composition":[],"interaction":{"1":"A Popover MUST only be displayed if the Trigger component is clicked. This behaviour is different from Tooltips that appear on hovering. Popovers disappear by clicking anywhere outside the Popover or by pressing the ESC key."},"wording":[],"ordering":[],"style":{"1":"Popovers MUST always relate to the Trigger component by a little pointer."},"responsiveness":[],"accessibility":{"1":"There MUST be a way to open the Popover by only using the keyboard.","2":"The focus MUST be inside the Popover, once it is open if it contains at least one interactive item. Otherwise the focus MUST remain on the Triggerer component.","3":"The focus MUST NOT leave the Popover for as long as it is open.","4":"There MUST be a way to reach every control in the Popover by only using the keyboard.","5":"The Popover MUST be closable by pressing the ESC key.","6":"Once the Popover is closed, the focus MUST return to the element triggering the opening of the Popover or the element being clicked if the Popover was closed on click."}},"parent":"FactoryUIComponent","children":["PopoverStandardStandard","PopoverListingListing"],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Factory"},"DropzoneFactoryDropzone":{"id":"DropzoneFactoryDropzone","title":"Dropzone","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Dropzones are containers used to drop either files or other HTML elements.","composition":"A dropzone is a container on the page. Depending on the type of the dropzone, the container is visible by default or it gets highlighted once the user starts to drag the elements over the browser window.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dropzones MUST be highlighted if the user is dragging compatible elements inside or over the browser window."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["DropzoneFileFactoryFile"],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/Factory"},"LegacyLegacyLegacy":{"id":"LegacyLegacyLegacy","title":"Legacy","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"This component is used to wrap an existing ILIAS UI element into a UI component. This is useful if a container of the UI components needs to contain content that is not yet implement in the centralized UI components.","composition":"The legacy component contains html or any other content as string.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"This component MUST only be used to ensure backwards compatibility with existing UI elements in ILIAS, therefore it SHOULD only contain Elements which cannot be generated using other UI Components from the UI Service."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Legacy\/Legacy"},"TableFactoryTable":{"id":"TableFactoryTable","title":"Table","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Tables present a set of uniformly structured data.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["TablePresentationPresentation"],"less_variables":[],"path":"src\/UI\/Component\/Table\/Factory"},"IconStandardStandard":{"id":"IconStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Icons represent ILIAS Objects.","composition":"A Standard Icon is displayed as a block-element with a background-graphic. By default, a fallback icon will be rendered; this is until a background image is defined in the icon's CSS-class.","effect":"","rivals":{"1":"Custom Icons are constructed with a path to an (uploaded) image."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"IconFactoryIcon","children":[],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Standard"},"IconCustomCustom":{"id":"IconCustomCustom","title":"Custom","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"ILIAS allows users to upload icons for repository objects. Those, in opposite to the standard icons, need to be constructed with a path.","composition":"Instead of setting a background image via CSS-class, an image-tag is contained in the icons's div.","effect":"","rivals":{"1":"Standard Icons MUST be used for core-objects."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Custom Icons MAY still use an abbreviation."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Custom Icons MUST use SVG as graphic.","2":"Icons MUST have a transparent background so they could be put on all kinds of backgrounds.","3":"Images used for Custom Icons SHOULD have equal width and height (=be quadratic) in order not to be distorted."},"responsiveness":[],"accessibility":[]},"parent":"IconFactoryIcon","children":[],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Custom"},"CounterCounterStatus":{"id":"CounterCounterStatus","title":"Status","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Status counter is used to display information about the total number of some items like users active on the system or total number of comments.","composition":"The Status Counter is a non-obstrusive Counter.","effect":"Status Counters convey information, they are not interactive.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"The Status Counter MUST be displayed on the lower right of the item it accompanies.","2":"The Status Counter SHOULD have a non-obstrusive background color, such as grey."},"responsiveness":[],"accessibility":[]},"parent":"CounterFactoryCounter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Counter"},"CounterCounterNovelty":{"id":"CounterCounterNovelty","title":"Novelty","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Novelty counters inform users about the arrival or creation of new items of the kind indicated by the accompanying glyph.","composition":"A Novelty Counter is an obtrusive counter.","effect":"They count down \/ disappear as soon as the change has been consulted by the user.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Novelty Counter MAY be used with the Status Counter."},"composition":[],"interaction":{"2":"There MUST be a way for the user to consult the changes indicated by the counter.","3":"After the consultation, the Novelty Counter SHOULD disappear or the number it contains is reduced by one.","4":"Depending on the content, the reduced number MAY be added in an additional Status Counter."},"wording":[],"ordering":[],"style":{"5":"The Novelty Counter MUST be displayed on the top at the 'end of the line' in reading direction of the item it accompanies. This would be top right for latin script and top left for arabic script.","6":"The Novelty Counter SHOULD have an obstrusive background color, such as red or orange."},"responsiveness":[],"accessibility":[]},"parent":"CounterFactoryCounter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Counter"},"ImageImageStandard":{"id":"ImageImageStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard image is used if the image is to be rendered in it's the original size.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ImageFactoryImage","children":[],"less_variables":[],"path":"src\/UI\/Component\/Image\/Image"},"ImageImageResponsive":{"id":"ImageImageResponsive","title":"Responsive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A responsive image is to be used if the image needs to adapt to changing amount of space available.","composition":"Responsive images scale nicely to the parent element.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ImageFactoryImage","children":[],"less_variables":[],"path":"src\/UI\/Component\/Image\/Image"},"DividerHorizontalHorizontal":{"id":"DividerHorizontalHorizontal","title":"Horizontal","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Horizontal Divider is used to mark a thematic change in a sequence of elements that are stacked from top to bottom.","composition":"Horiztonal dividers consists of a horizontal ruler which may comprise a label.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Horizontal Dividers MUST only be used in container components that render a sequence of items from top to bottom."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"Horizontal Dividers MUST always have a succeeding element in a sequence of elments, which MUST NOT be another Horizontal Divider.","2":"Horizontal Dividers without label MUST always have a preceding element in a sequence of elments, which MUST NOT be another Horizontal Divider."},"style":[],"responsiveness":[],"accessibility":[]},"parent":"DividerFactoryDivider","children":[],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Horizontal"},"DividerVerticalVertical":{"id":"DividerVerticalVertical","title":"Vertical","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Vertical Divider is used to mark a thematic or functional change in a sequence of elements that are stacked from left to right.","composition":"Vertical Dividers consists of a glyph-like character.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Vertical Dividers MUST only be used in container components that render a sequence of items from left to right."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"Vertical Dividers MUST always have a succeeding element in a sequence of elments, which MUST NOT be another Vertical Divider."},"style":[],"responsiveness":[],"accessibility":[]},"parent":"DividerFactoryDivider","children":[],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Vertical"},"LinkStandardStandard":{"id":"LinkStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A standard link is a link with a text label as content of the link.","composition":"The standard link uses the default link color as text color an no background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard links MUST be used if there is no good reason using another instance.","2":"Links to ILIAS screens that contain the general ILIAS navigation MUST NOT be opened in a new viewport."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"LinkFactoryLink","children":[],"less_variables":[],"path":"src\/UI\/Component\/Link\/Standard"},"GlyphGlyphSettings":{"id":"GlyphGlyphSettings","title":"Settings","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Settings Glyph triggers opening a Dropdown to edit settings of the displayed block.","composition":"The Settings Glyph uses the glyphicon-cog.","effect":"Upon clicking a settings Dropdown is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Settings Glyph MUST only be used in Blocks."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u201cSettings\u201d."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphCollapse":{"id":"GlyphGlyphCollapse","title":"Collapse","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Collapse Glyph is used to trigger the collapsing of some neighbouring Container Collection such as a the content of a Dropdown or an Accordion currently shown.","composition":"The Collapse Glyph is composed of a triangle pointing to the bottom indicating that content is currently shown.","effect":"Clicking the Collapse Glyph hides the display of some Container Collection.","rivals":{"Expand Glyph":"The Expand Glyphs triggers the display of some Container Collection.","Previous Glyph":"The Previous\/Next Glyph opens a completely new view. It serves a navigational purpose."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Collapse Glyph MUST indicate if the toggled Container Collection is visible or not."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Collapse Content'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphExpand":{"id":"GlyphGlyphExpand","title":"Expand","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Expand Glyph is used to trigger the display of some neighbouring Container Collection such as a the content of a Dropdown or an Accordion currently shown.","composition":"The Expand Glyph is composed of a triangle pointing to the right indicating that content is currently shown.","effect":"Clicking the Expand Glyph displays some Container Collection.","rivals":{"Collapse Glyph":"The Collapse Glyphs hides the display of some Container Collection.","Previous Glyph":"The Previous\/Next Glyph opens a completely new view. It serves a navigational purpose."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Expand Glyph MUST indicate if the toggled Container Collection is visible or not."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Expand Content'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAdd":{"id":"GlyphGlyphAdd","title":"Add","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The glyphed add-button serves as stand-in for the respective textual buttons in very crowded screens. It allows adding a new item.","composition":"The Add Glyph uses the glyphicon-plus-sign.","effect":"Clicking on the Add Glyph adds a new input to a form or an event to the calendar.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Add Glyph SHOULD not come without a Remove Glyph and vice versa. Because either there is not enough place for textual buttons or there is place. Exceptions to this rule, such as the Calendar, where only elements can be added in a certain place are possible, are to be run through the Jour Fixe.","2":"The Add Glyph stands for an Action and SHOULD be placed in the action column of a form.","3":"The Add Glyph MUST not be used to add lines to tables."},"composition":[],"interaction":{"1":"Newly added items MUST be placed below the line in which the Add Glyph has been clicked"},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Add'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphRemove":{"id":"GlyphGlyphRemove","title":"Remove","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Remove Glyph serves as stand-in for the respective textual buttons in very crowded screens. It allows removing an item.","composition":"The Remove Glyph uses the glyphicon-plus-sign.","effect":"Clicking on the Remove Glyph adds a new input to a form or an event to the calendar.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Remove Glyph SHOULD not come without a glyphed Add Glyph and vice versa. Because either there is not enough place for textual buttons or there is place. Exceptions to this rule, such as the Calendar, where only elements can be added in a certain place are possible, are to be run through the Jour Fixe.","2":"The Remove Glyph stands for an Action and SHOULD be placed in the action column of a form.","3":"The Remove Glyph MUST not be used to add lines to tables."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Remove'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphUp":{"id":"GlyphGlyphUp","title":"Up","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Up Glyph allows for manually arranging rows in tables embedded in forms. It allows moving an item up.","composition":"The Up Glyph uses the glyphicon-circle-arrow-up. The Up Glyph can be combined with the Add\/Remove Glyph.","effect":"Clicking on the Up Glyph moves an item up.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_813_1357.html"],"rules":{"usage":{"1":"The Up Glyph MUST NOT be used to sort tables. There is an established sorting control for that.","2":"The Up Glyph SHOULD not come without a Down and vice versa.","3":"The Up Glyph is an action and SHOULD be listed in the action column of a form."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Up'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphDown":{"id":"GlyphGlyphDown","title":"Down","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Down Glyph allows for manually arranging rows in tables embedded in forms. It allows moving an item down.","composition":"The Down Glyph uses the glyphicon-circle-arrow-down. The Down Glyph can be combined with the Add\/Remove Glyph.","effect":"Clicking on the Down Glyph moves an item up.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_813_1357.html"],"rules":{"usage":{"1":"The Down Glyph MUST NOT be used to sort tables. There is an established sorting control for that.","2":"The Down Glyph SHOULD not come without a Up and vice versa.","3":"The Down Glyph is an action and SHOULD be listed in the action column of a form."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Down'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphBack":{"id":"GlyphGlyphBack","title":"Back","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Back Glyph indicates a possible change of the view. The view change leads back to some previous view.","composition":"The chevron-left glyphicon is used.","effect":"The click on a Back Glyph leads back to a previous view.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Back and Next Buttons MUST be accompanied by the respective Back\/Next Glyph."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"If clicking on the Back\/Next GLYPH opens a new view of an object, the Next Glyph MUST be used.","2":"If clicking on the Back\/Next GLYPH opens a previous view of an object, the Back Glyph MUST be used."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Back'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNext":{"id":"GlyphGlyphNext","title":"Next","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Next Glyph indicates a possible change of the view. The view change leads back to some previous view.","composition":"The chevron-right glyphicon is used.","effect":"The click on a Next Glyph opens a new view.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Back and Next Buttons MUST be accompanied by the respective Back\/Next Glyph."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"If clicking on the Back\/Next GLYPH opens a new view of an object, the Next Glyph MUST be used.","2":"If clicking on the Back\/Next GLYPH opens a previous view of an object, the Back Glyph MUST be used."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Next'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSortAscending":{"id":"GlyphGlyphSortAscending","title":"Sort Ascending","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Sorting Glyphs indicate the sorting direction of a column in a table as ascending (up) or descending (down). It is a toggle reversing the ordering of a column.","composition":"The Sort Ascending Glyph uses glyphicon-arrow-up.","effect":"Clicking the Sort Ascending Glyph reverses the direction of ordering in a table.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Sort Ascending'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSortDescending":{"id":"GlyphGlyphSortDescending","title":"Sort Descending","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Sorting Glyphs indicate the sorting direction of a column in a table as ascending (up) or descending (down). It is a toggle reversing the ordering of a column.","composition":"The Sort Descending Glyph uses glyphicon-arrow-descending.","effect":"Clicking the Sort Descending Glyph reverses the direction of ordering in a table.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Sort Descending'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphBriefcase":{"id":"GlyphGlyphBriefcase","title":"Briefcase","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The briefcase symbolize some ongoing work that is done. Momentarily in the background tasks.","composition":"The briefcase Glyph uses glyphicon-briefcase.","effect":"The click on the briefcase opens a popup to the background tasks.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Background Tasks'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphUser":{"id":"GlyphGlyphUser","title":"User","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The User Glyph triggers the \u201cWho is online?\u201d Popover in the Top Navigation. The User Glyph indicates the number of pending contact requests and users online via the the Novelty Counter and Status Counter respectively.","composition":"The User Glyph uses the glyphicon-user.","effect":"Clicking the User Glyph opens the \u201cWho is online?\u201d Popover.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Show who is online'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphMail":{"id":"GlyphGlyphMail","title":"Mail","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Mail Glyph provides a shortcut to the mail service. The Mail Glyph indicates the number of new mails received.","composition":"The Mail Glyph uses the glyphicon-envelope.","effect":"Upon clicking on the Mail Glyph the user is transferred to the full-screen mail service.","rivals":{"Mail Icon":"The Mail Icon is used to indicate the user is currently located in the Mail service The Mail Glyph acts as shortcut to the Mail service."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Mail'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNotification":{"id":"GlyphGlyphNotification","title":"Notification","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Notification Glyph allows users to activate \/ deactivate the notification service for a specific object or sub-item. It is a toggle indicating by colour whether it is activated or not.","composition":"The Notification Glyph uses the glyphicon-bell in link-color if notifications are not active or brand-warning color if they are.","effect":"Upon clicking the notification activation is toggled: Clicking the Notification Glyph activates respectively deactivates the notification service for the current object or sub-item.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Notification Glyph MUST only be used in the Content Top Actions."},"composition":[],"interaction":{"1":"Clicking the Notification Glyph MUST toggle the activation of Notifications."},"wording":[],"ordering":[],"style":{"1":"If notifications are activated the Notification Glyph MUST use the brand-warning color."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Notifications'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphTag":{"id":"GlyphGlyphTag","title":"Tag","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Tag Glyph is used to indicate the possibility of adding tags to an object.","composition":"The Tag Glyph uses the glyphicon-tag.","effect":"Upon clicking the Round Trip Modal to add new Tags is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of tags that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Tags'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNote":{"id":"GlyphGlyphNote","title":"Note","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Note Glyph is used to indicate the possibilty of adding notes to an object.","composition":"The Note Glyph uses the glyphicon-pushpin.","effect":"Upon clicking the Round Trip Modal to add new notes is opened","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of notes that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Notes'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphComment":{"id":"GlyphGlyphComment","title":"Comment","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Comment Glyph is used to indicate the possibilty of adding comments to an object.","composition":"The Comment Glyph uses the glyphicon-comment.","effect":"Upon clicking the Round Trip Modal to add new comments is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of comments that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Comments'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLike":{"id":"GlyphGlyphLike","title":"Like","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Like Glyph indicates a user affirms an item, e.g. a posting.","composition":"The Like Glyph uses the \"thumbs up\" unicode emoji U+1F44D, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of like expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Like'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLove":{"id":"GlyphGlyphLove","title":"Love","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Love Glyph indicates a user adores an item, e.g. a posting.","composition":"The Love Glyph uses the \"red heart\" unicode emoji U+2764, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of love expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Love'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphDislike":{"id":"GlyphGlyphDislike","title":"Dislike","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Dislike Glyph indicates a user objects an item, e.g. a posting.","composition":"The Dislike Glyph uses the \"thumbs down\" unicode emoji U+1F44E, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of dislike expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Dislike'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLaugh":{"id":"GlyphGlyphLaugh","title":"Laugh","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Laugh Glyph indicates a user finds an item hilarious, e.g. a posting.","composition":"The Laugh Glyph uses the \"grinning face with smiling eyes\" unicode emoji U+1F604, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of laugh expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Laugh'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAstounded":{"id":"GlyphGlyphAstounded","title":"Astounded","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Astounded Glyph indicates a user finds an item surprising, e.g. a posting.","composition":"The Astounded Glyph uses the \"face with open mouth\" unicode emoji U+1F62E, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of astounded expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Astounded'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSad":{"id":"GlyphGlyphSad","title":"Sad","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Sad Glyph indicates a user finds an item disconcerting, e.g. a posting.","composition":"The Sad Glyph uses the \"sad but relieved face\" unicode emoji U+1F625, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of sad expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Sad'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAngry":{"id":"GlyphGlyphAngry","title":"Angry","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Angry Glyph indicates a user finds an item outraging, e.g. a posting.","composition":"The Angry Glyph uses the \"angry face\" unicode emoji U+1F620, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of angry expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Angry'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"ButtonStandardStandard":{"id":"ButtonStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard button is the default button to be used in ILIAS. If there is no good reason using another button instance in ILIAS, this is the one that should be used.","composition":"The standard button uses the primary color as background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard buttons MUST be used if there is no good reason using another instance."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"The most important standard button SHOULD be first in reading direction if there are several buttons.","2":"In the toolbar and in forms special regulations for the ordering of the buttons MAY apply."},"style":[],"responsiveness":{"1":"The most important standard button in multi-action bars MUST be sticky (stay visible on small screens)."},"accessibility":{"1":"Standard buttons MAY define aria-label attribute. Use it in cases where a text label is not visible on the screen or when the label does not provide enough information about the action.","2":"Standard buttons MAY define aria-checked attribute. Use it to inform which is the currently active button."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Standard"},"ButtonPrimaryPrimary":{"id":"ButtonPrimaryPrimary","title":"Primary","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The primary button indicates the most important action on a screen. By definition there can only be one single \u201cmost important\u201d action on any given screen and thus only one single primary button per screen.","composition":"The background color is the btn-primary-color. This screen-unique button-color ensures that it stands out and attracts the user\u2019s attention while there are several buttons competing for attention.","effect":"In toolbars the primary button are required to be sticky, meaning they stay in view in the responsive view.","rivals":[]},"background ":"Tiddwell refers to the primary button as \u201cprominent done button\u201d and describes that \u201cthe button that finishes a transaction should be placed at the end of the visual flow; and is to be made big and well labeled.\u201d She explains that \u201cA well-understood, obvious last step gives your users a sense of closure. There\u2019s no doubt that the transaction will be done when that button is clicked; don\u2019t leave them hanging, wondering whether their work took effect\u201d. The GNOME Human Interface Guidelines -> Buttons also describes a button indicated as most important for dialogs.","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Most pages SHOULD NOT have any Primary Button at all.","2":"There MUST be no more than one Primary Button per page in ILIAS.","3":"The decision to make a Button a Primary Button MUST be confirmed by the JF."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Primary"},"ButtonCloseClose":{"id":"ButtonCloseClose","title":"Close","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The close button triggers the closing of some collection displayed temporarily such as an overlay.","composition":"The close button is displayed without border.","effect":"Clicking the close button closes the enclosing collection.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":{"1":"The Close Button MUST always be positioned in the top right of a collection."},"style":[],"responsiveness":[],"accessibility":{"1":"The functionality of the close button MUST be indicated for screen readers by an aria-label."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Close"},"ButtonShyShy":{"id":"ButtonShyShy","title":"Shy","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Shy buttons are used in contexts that need a less obtrusive presentation than usual buttons have, e.g. in UI collections like Dropdowns.","composition":"Shy buttons do not come with a separte background color.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Shy buttons MUST only be used, if a standard button presentation is not appropriate. E.g. if usual buttons destroy the presentation of an outer UI component or if there is not enough space for a standard button presentation."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Shy"},"ButtonMonthMonth":{"id":"ButtonMonthMonth","title":"Month","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Month Button enables to select a specific month to fire some action (probably a change of view).","composition":"The Month Button is composed of a Button showing the default month directly (probably the month currently rendered by some view). A dropdown contains an interface enabling the selection of a month from the future or the past.","effect":"Selecting a month from the dropdown directly fires the according action (e.g. switching the view to the selected month). Technically this is currently a Javascript event being fired.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":{"1":"Selecting a month from the dropdown MUST directly fire the according action."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Month"},"ButtonTagTag":{"id":"ButtonTagTag","title":"Tag","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Tags classify entities. Thus, their primary purpose is the visualization of those classifications for one entity. However, tags are usually clickable - either to edit associations or list related entities, i.e. objects with the same tag.","composition":"Tags are a colored area with text on it. When used in a tag-cloud (a list of tags), tags can be visually \"weighted\" according to the number of their occurences, be it with different (font-)sizes, different colors or all of them.","effect":"Tags may trigger an action or change the view when clicked. There is no visual difference (besides the cursor) between clickable tags and tags with unavailable action.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Tags SHOULD be used with an additonal class to adjust colors.","2":"The font-color SHOULD be set with high contrast to the chosen background color."},"responsiveness":[],"accessibility":{"1":"The functionality of the tag button MUST be indicated for screen readers by an aria-label."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Tag"},"ButtonBulkyBulky":{"id":"ButtonBulkyBulky","title":"Bulky","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The bulky button is highly obtrusive. It combines the recognisability of a graphical element with an explicit textual label on an unusually sized button. It is hard to overlook and indicates an important action on the screen.","composition":"The Bulky Button consists of an icon or glyph and a (very short) text.","effect":"The button has an \"engaged\"-state: When the button is used to toggle the visibility of a component, it stays engaged until the component is hidden again.","rivals":{"Primary Button":"Primary Buttons indicate the most important action among a collection of actions, e.g. in a tool bar, controls of a form or in a modal. Bulky Buttons make it hard to miss the indicated action by occupying space."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Since Bulky Buttons are so obtrusive they MUST only be used to indicate important actions on the screen."},"composition":[],"interaction":[],"wording":{"1":"The icon\/glyph and the text on the Bulky Button MUST be corresponding."},"ordering":[],"style":{"1":"Bulky Buttons MUST occupy as much space as their container leaves them.","2":"When used to toggle the visibility of another component, the button MUST reflect the componentes state of visibility."},"responsiveness":[],"accessibility":{"1":"The functionality of the Bulky Button MUST be indicated for screen readers by an aria-label.","2":"Bulky Buttons MUST define aria-pressed attribute."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Bulky"},"DropdownStandardStandard":{"id":"DropdownStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Standard Dropdown is the default Dropdown to be used in ILIAS. If there is no good reason using another Dropdown instance in ILIAS, this is the one that should be used.","composition":"The Standard Dropdown uses the primary color as background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Dropdown MUST be used if there is no good reason using another instance."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"DropdownFactoryDropdown","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropdown\/Standard"},"ViewControlModeMode":{"id":"ViewControlModeMode","title":"Mode","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Mode View Controls enable the switching between different aspects of some data. The different modes are mutually exclusive and can therefore not be activated at once.","composition":"Mode View Controls are composed of Buttons switching between active and inactive states.","effect":"Clicking on an inactive Button turns this button active and all other inactive. Clicking on an active button has no effect.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Exactly one Button MUST always be active."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The HTML container enclosing the buttons of the Mode View Control MUST cary the role-attribute \"group\".","2":"The HTML container enclosing the buttons of the Mode View Control MUST set an aria-label describing the element. Eg. \"Mode View Control\"","3":"The Buttons of the Mode View Control MUST set an aria-label clearly describing what the button shows if clicked. E.g. \"List View\", \"Month View\", ...","4":"The currently active Button must be labeled by setting aria-checked to \"true\"."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Mode"},"ViewControlSectionSection":{"id":"ViewControlSectionSection","title":"Section","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Section View Controls enable the switching between different sections of some data. Examples are subsequent days\/weeks\/months in a calendar or entries in a blog.","composition":"Section View Controls are composed of three Buttons. The Button on the left caries a Back Glyph, the Button in the middle is either a Default- or Split Button labeling the data displayed below and the Button on the right carries a next Glyph.","effect":"Clicking on the Buttons left or right changes the selection of the displayed data by a fixed interval. Clicking the Button in the middle opens the sections hinted by the label of the button (e.g. \"Today\").","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Section"},"ViewControlSortationSortation":{"id":"ViewControlSortationSortation","title":"Sortation","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The sortation view control enables users to change the order in which some data is presented. This control applies to all sorts of _structured_ data, like tables and lists.","composition":"Sortation uses a Dropdown to display a collection of shy-buttons.","effect":"A click on an option will change the ordering of the associated data-list by calling a page with a parameter according to the selected option or triggering a signal. The label displayed in the dropdown will be set to the selected sorting.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A Sortation MUST NOT be used standalone.","2":"Sortations MUST BE visually close to the list or table their operation will have effect upon.","3":"There SHOULD NOT be more than one Sortation per view."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Sortation MUST be operable via keyboard only."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Sortation"},"ViewControlPaginationPagination":{"id":"ViewControlPaginationPagination","title":"Pagination","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Pagination allows structured data being displayed in chunks by limiting the number of entries shown. It provides the user with controls to leaf through the chunks of entries.","composition":"Pagination is a collection of shy-buttons to access distinct chunks of data, framed by next\/back glyphs. When used with the \"DropdownAt\" option, a dropdown is rendered if the number of chunks exceeds the option's value.","effect":"A click on an chunk-option will change the offset of the displayed data-list, thus displaying the respective chunk of entries. The active option is rendered as an unavailable shy-button. Clicking the next\/back-glyphs, the previous (respectively: the next) chunk of entries is being displayed. If a previous\/next chunk is not available, the glyph is rendered unavailable. If the pagination is used with a maximum of chunk-options to be shown, both first and last options are always displayed.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A Pagination MUST only be used for structured data, like tables and lists.","2":"A Pagination MUST NOT be used standalone.","3":"Paginations MUST be visually close to the list or table their operation will have effect upon. They MAY be placed directly above and\/or below the list."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Pagination MUST be operable via keyboard only."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Pagination"},"ChartScaleBarScaleBar":{"id":"ChartScaleBarScaleBar","title":"Scale Bar","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Scale Bars are used to display a set of items some of which especially highlighted. E.g. they can be used to inform about a score or target on a rank ordered scale.","composition":"Scale Bars are composed of of a set of bars of equal size. Each bar contains a title. The highlighted elements differ from the others through their darkened background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Each Bar of the Scale Bars MUST bear a title.","2":"The title of Scale Bars MUST NOT contain any other content than text."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartFactoryChart","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ScaleBar"},"ChartProgressMeterFactoryProgressMeter":{"id":"ChartProgressMeterFactoryProgressMeter","title":"Progress Meter","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Progress Meters are used to display a progress or performance. E.g. they can be used to inform about a progress in a learning objective or to compare the performance between the initial and final test in a course.","composition":"Progress Meters are composed of one or two bars inside a horseshoe-like container. The bars change between two colors, to identify a specific reached value. It additionally may show a percentage of the values and also an identifying text.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Progress Meters MUST contain a maximum value. It MUST be numeric and represents the maximum value.","2":"Progress Meters MUST contain a main value. It MUST be a numeric value between 0 and the maximum. It is represented as the main bar.","3":"Progress Meters SHOULD contain a required value. It MUST be a numeric value between 0 and the maximum. It represents the required value that has to be reached."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartFactoryChart","children":["ChartProgressMeterStandardStandard","ChartProgressMeterFixedSizeFixedSize","ChartProgressMeterMiniMini"],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Factory"},"ChartProgressMeterStandardStandard":{"id":"ChartProgressMeterStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Standard Progress Meter is usually the tool of choice. The Progress Meter informs users about their Progress compared to a the required maximum.","composition":"The Standard Progress Meter is composed of one bar representing a value achieved in relation to a maximum and a required value indicated by some pointer. The comparison value is represented by a second bar below the first one. Also the percentage values of main and required are shown as text.","effect":"On changing screen size they decrease their size including font size in various steps.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Standard Progress Meters MAY contain a comparison value. If there is a comparison value it MUST be a numeric value between 0 and the maximum. It is represented as the second bar.","2":"Standard Progress Meters MAY contain a main value text.","3":"Standard Progress Meters MAY contain a required value text."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Standard"},"ChartProgressMeterFixedSizeFixedSize":{"id":"ChartProgressMeterFixedSizeFixedSize","title":"Fixed Size","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Fixed Size Progress Meter ensures that the element is rendered exactly as set regardless of the screen size.","composition":"See composition description for Standard Progress Meter.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"See composition rules for Standard Progress Meter."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/FixedSize"},"ChartProgressMeterMiniMini":{"id":"ChartProgressMeterMiniMini","title":"Mini","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Mini Progress Meter is used, if it needs to be as small as possible, like in an heading. It is used to display only a single Progress or performance indicator.","composition":"Other than the Standard and Fixed Size Progress Meter it does not allow a comparison value and only displays a single bar. It also does not display any text.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"See composition rules for Progress Meter."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Mini"},"InputFieldFactoryField":{"id":"InputFieldFactoryField","title":"Field","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Inputs fields are different from other UI components. They bundle two things: First, they are used for displaying, as similar to other components. Second, they are used to define the server side processing of data that is received from the client. Thus, an input field defines which visual input elements a user will see, which constraints are put on the data entered in these fields and which data developers on the server side retrieve from these inputs. Fields need to be enclosed by a container which defines the means of submitting the data collected by the fields and the way those inputs are arranged to be displayed for some client.","composition":"Fields are either individuals or groups of inputs. Both, individual fields and groups, share the same basic input interface. Input-Fields may have a label and byline.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A byline (explanatory text) MAY be added to input fields."},"interaction":[],"wording":{"1":"If a label is set, it MUST be composed of one single term or a very short phrase. The identifier is an eye catcher for users skimming over a potentially large set of fields.","2":"If a label is set, it MUST avoid lingo. Intelligibility by occasional users is prioritized over technical accuracy. The accurate technical expression is to be mentioned in the byline.","3":"If a label is set, it MUST make a positive statement. If the purpose of the setting is inherently negative, use Verbs as \u201cLimit..\u201d, \u201cLock..\u201d.","4":"If bylines are provided they MUST be informative, not merely repeating the identifier\u2019s or input element\u2019s content. If no informative description can be devised, no description is needed.","5":"A byline MUST clearly state what effect the fields produces and explain, why this might be important and what it can be used for.","6":"Bulk bylines underneath a stack of option explaining all of the options in one paragraph MUST NOT be used. Use individual bylines instead.","7":"A byline SHOULD NOT address the user directly. Addressing users directly is reserved for cases of high risk of severe mis-configuration.","8":"A byline MUST be grammatically complete sentence with a period (.) at the end.","9":"Bylines SHOULD be short with no more than 25 words.","10":"Bylines SHOULD NOT use any formatting in descriptions (bold, italic or similar).","11":"If bylines refer to other tabs or options or tables by name, that reference should be made in quotation marks: \u2018Info\u2019-tab, button \u2018Show Test Results\u2019, \u2018Table of Detailed Test Results\u2019. Use proper quotation marks, not apostrophes. Use single quotation marks for english language and double quotation marks for german language.","12":"By-lines MUST NOT feature parentheses since they greatly diminish readability.","13":"By-lines SHOULD NOT start with terms such as: If this option is set \u2026 If this setting is active \u2026 Choose this setting if \u2026 This setting \u2026 Rather state what happens directly: Participants get \/ make \/ can \u2026 Point in time after which\u2026. ILIAS will monitor\u2026 Sub-items xy are automatically whatever ... Xy will be displayed at place."},"ordering":[],"style":{"1":"Disabled input elements MUST be indicated by setting the \u201cdisabled\u201d attribute.","2":"If focused, the input elements MUST change their input-border-color to the input-focus-border-color."},"responsiveness":[],"accessibility":{"1":"All fields visible in a view MUST be accessible by keyboard by using the \u2018Tab\u2019-Key."}},"parent":"InputFactoryInput","children":["InputFieldTextText","InputFieldNumericNumeric","InputFieldGroupGroup","InputFieldSectionSection","InputFieldDependantGroupDependantGroup","InputFieldCheckboxCheckbox","InputFieldPasswordPassword"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Factory"},"InputContainerFactoryContainer":{"id":"InputContainerFactoryContainer","title":"Container","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An input container defines which means are used to submit the data to the system and how input-fields are being displayed in the UI. Furthermore containers will process received data according to the transformations and constraints of its fields.","composition":"A Container holds one ore more fields.","effect":"","rivals":{"Group Field Input":"Groups are used within containers to functionally bundle input-fields.","Section Field Input":"Sections are used within containers to visually tie fields together."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"InputFactoryInput","children":["InputContainerFormFactoryForm"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Factory"},"InputFieldTextText":{"id":"InputFieldTextText","title":"Text","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A text-field is intended for entering short single-line texts.","composition":"Text fields will render an input-tag with type=\"text\".","effect":"Text inputs are restricted to one line of text.","rivals":{"numeric field":"Use a numeric field if users should input numbers.","alphabet field":"Use an alphabet field if the user should input single letters."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Text Input MUST NOT be used for choosing from predetermined options.","2":"Text input MUST NOT be used for numeric input, a Numeric Field is to be used instead.","3":"Text Input MUST NOT be used for letter-only input, an Alphabet Field is to be used instead."},"composition":[],"interaction":{"1":"Text Input MUST limit the number of characters, if a certain length of text-input may not be exceeded (e.g. due to database-limitations)."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Text"},"InputFieldNumericNumeric":{"id":"InputFieldNumericNumeric","title":"Numeric","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A numeric field is used to retrieve numeric values from the user.","composition":"Numeric inputs will render an input-tag with type=\"number\".","effect":"The field does not accept any data other than numeric values. When focused most browser will show a small vertical rocker to increase and decrease the value in the field.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Number Inputs MUST NOT be used for binary choices.","2":"Magic numbers such as -1 or 0 to specify \u201climitless\u201d or smoother options MUST NOT be used.","3":"A valid input range SHOULD be specified."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Numeric"},"InputFieldGroupGroup":{"id":"InputFieldGroupGroup","title":"Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Input groups are an unlabeled collection of inputs. They are used to build logical units of other fields. Such units might be used to attach some constraints or transformations for multiple fields.","composition":"Groups are composed of inputs. They do not contain a label. The grouping remains invisible for the client.","effect":"There is no visible effect using groups.","rivals":{"sections":"Sections are used to generate a visible relation of fields."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Group"},"InputFieldSectionSection":{"id":"InputFieldSectionSection","title":"Section","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Sections are used to visually group inputs to a common context.","composition":"Sections are composed of inputs. They carry a label and are visible for the client.","effect":"","rivals":{"Groups":"Groups are used as purely logical units, while sections visualize the correlation of fields."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Sections SHOULD comprise 2 to 5 Settings.","2":"More than 5 Settings SHOULD be split into two areas unless this would tamper with the \u201cfamiliar\u201d information architecture of forms.","3":"In standard forms, there MUST NOT be a Setting without an enclosing Titled Form Section. If necessary a Titled Form Section MAY contain only one single Setting."},"interaction":[],"wording":{"1":"The label SHOULD summarize the contained settings accurately from a user\u2019s perspective.","2":"The title SHOULD contain less than 30 characters.","3":"The titles MUST be cross-checked with similar sections in other objects or services to ensure consistency throughout ILIAS.","4":"In doubt consistency SHOULD be prioritized over accuracy in titles."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Section"},"InputFieldDependantGroupDependantGroup":{"id":"InputFieldDependantGroupDependantGroup","title":"Dependant Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Fields can be nested by using dependant groups (formerly known as subforms) allowing for settings-dependent configurations.","composition":"Dependant groups are like groups composed of a set of input fields.","effect":"The display of dependent group is triggered by enabling some other input field which has an attached dependant group. Note that not all fields allow this (e.g. Checkboxes do). Look at the interface whether and how dependant groups can be attached.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"There MUST NOT be a nesting of more than one dependant group. The only exception to this rule is the required quantification of a subsetting by a date or number. These exceptions MUST individually accepted by the Jour Fixe."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/DependantGroup"},"InputFieldCheckboxCheckbox":{"id":"InputFieldCheckboxCheckbox","title":"Checkbox","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A checkbox is used to govern a state, action, or set \/ not to set a value. Checkboxes are typically used to switch on some additional behaviour or services.","composition":"Each Checkbox is labeled by an identifier stating something positive to describe the effect of checking the Checkbox.","effect":"If used in a form, a checkbox may open a dependant section (formerly known as sub form).","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A checkbox MUST NOT be used whenever a user has to perform a binary choice where option is not automatically the inverse of the other (such as 'Order by Date' and 'Order by Name'). A Select Input or a Radio Group in MUST be used in this case."},"composition":[],"interaction":[],"wording":{"1":"The checkbox\u2019s identifier MUST always state something positive."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Checkbox"},"InputFieldPasswordPassword":{"id":"InputFieldPasswordPassword","title":"Password","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A password-field is intended for entering passwords.","composition":"Text password will render an input-tag with type=\"password\".","effect":"Text password is restricted to one line of text and will mask the entered characters.","rivals":{"text field":"Use a text field for discloseable information (i.e. information that can safely be displayed to an audience)"}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Password Input MUST be used for passwords."},"composition":[],"interaction":{"1":"Password Input SHOULD NOT limit the number of characters."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Password"},"InputContainerFormFactoryForm":{"id":"InputContainerFormFactoryForm","title":"Form","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Forms are used to let the user enter or modify data, check her inputs and submit them to the system. Forms arrange their contents (i.e. fields) in an explanatory rather than space-saving way.","composition":"Forms are composed of input fields, displaying their labels and bylines.","effect":"","rivals":{"filter":"Filters are used to limit search results; they never modify data in the system."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"InputContainerFactoryContainer","children":["InputContainerFormStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Form\/Factory"},"InputContainerFormStandardStandard":{"id":"InputContainerFormStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Forms are used for creating content of sub-items or for configuring objects or services.","composition":"Standard forms provide a submit-button.","effect":"The users manipulates input-values and saves the form to apply the settings to the object or service or create new entities in the system.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Forms MUST NOT be used on the same page as tables.","2":"Standard Forms MUST NOT be used on the same page as toolbars."},"composition":{"1":"Each form SHOULD contain at least one section displaying a title.","2":"Standard Forms MUST only be submitted by their submit-button. They MUST NOT be submitted by anything else.","3":"Wording of labels of the fields the form contains and their ordering MUST be consistent with identifiers in other objects if some for is used there for a similar purpose. If you feel a wording or ordering needs to be changed, then you MUST propose it to the JF.","4":"On top and bottom of a standard form there SHOULD be the \u201cSave\u201d button for the form.","5":"In some rare exceptions the Buttons MAY be named differently: if \u201cSave\u201d is clearly a misleading since the action is more than storing the data into the database. \u201cSend Mail\u201d would be an example of this."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputContainerFormFactoryForm","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Form\/Standard"},"ListingUnorderedUnordered":{"id":"ListingUnorderedUnordered","title":"Unordered","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Unordered Lists are used to display a unordered set of textual elements.","composition":"Unordered Lists are composed of a set of bullets labeling the listed items.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Unordered"},"ListingOrderedOrdered":{"id":"ListingOrderedOrdered","title":"Ordered","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Ordered Lists are used to displayed a numbered set of textual elements. They are used if the order of the elements is relevant.","composition":"Ordered Lists are composed of a set of numbers labeling the items enumerated.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Ordered"},"ListingDescriptiveDescriptive":{"id":"ListingDescriptiveDescriptive","title":"Descriptive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Descriptive Lists are used to display key-value doubles of textual-information.","composition":"Descriptive Lists are composed of a key acting as title describing the type of information being displayed underneath.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Descriptive"},"PanelStandardStandard":{"id":"PanelStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Panels are used in the Center Content section to group content.","composition":"Standard Panels consist of a title and a content section. The structure of this content might be varying from Standard Panel to Standard Panel. Standard Panels may contain Sub Panels.","effect":"","rivals":{"Cards":"Often Cards are used in Decks to display multiple uniformly structured chunks of Data horizontally and vertically."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"In Forms Standard Panels MUST be used to group different sections into Form Parts.","2":"Standard Panels SHOULD be used in the Center Content as primary Container for grouping content of varying content."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Standard"},"PanelSubSub":{"id":"PanelSubSub","title":"Sub","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Sub Panels are used to structure the content of Standard panels further into titled sections.","composition":"Sub Panels consist of a title and a content section. They may contain a Card on their right side to display meta information about the content displayed.","effect":"","rivals":{"Standard Panel":"The Standard Panel might contain a Sub Panel.","Card":"The Sub Panels may contain one Card."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Sub Panels MUST only be inside Standard Panels"},"composition":{"1":"Sub Panels MUST NOT contain Sub Panels or Standard Panels as content."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Sub"},"PanelReportReport":{"id":"PanelReportReport","title":"Report","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Report Panels display user-generated data combining text in lists, tables and sometimes charts. Report Panels always draw from two distinct sources: the structure \/ scaffolding of the Report Panels stems from user-generated content (i.e a question of a survey, a competence with levels) and is filled with user-generated content harvested by that very structure (i.e. participants\u2019 answers to the question, self-evaluation of competence).","composition":"They are composed of a Standard Panel which contains several Sub Panels. They might also contain a card to display information meta information in their first block.","effect":"Report Panels are predominantly used for displaying data. They may however comprise links or buttons.","rivals":{"Standard Panels":"The Report Panels contains sub panels used to structure information.","Presentation Table":"Presentation Tables display only a subset of the data at first glance; their entries can then be expanded to show detailed information."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Report Panels SHOULD be used when user generated content of two sources (i.e results, guidelines in a template) is to be displayed alongside each other."},"composition":[],"interaction":{"1":"Links MAY open new views.","2":"Buttons MAY trigger actions or inline editing."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Report"},"PanelListingFactoryListing":{"id":"PanelListingFactoryListing","title":"Listing","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listing Panels are used to list items following all one single template.","composition":"Listing Panels are composed of several titled Item Groups. They further may contain a filter.","effect":"The List Items of Listing Panels may contain a dropdown offering options to interact with the item. Further Listing Panels may be filtered and the number of sections or items to be displayed may be configurable.","rivals":{"Report Panels":"Report Panels contain sections as Sub Panels each displaying different aspects of one item.","Presentation Table":"Use Presentation Table if you have a data set at hand that you want to make explorable and\/or present as a wholeness. Also use Presentation Table if your dataset does not contain Items that represent entities."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Listing Panels SHOULD be used, if a large number of items using the same template are to be displayed in an inviting way not using a Table."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":["PanelListingStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Listing\/Factory"},"PanelListingStandardStandard":{"id":"PanelListingStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard item lists present lists of items with similar presentation. All items are passed by using Item Groups.","composition":"This Listing is composed of title and a set of Item Groups. Additionally an optional dropdown to select the number\/types of items to be shown at the top of the Listing.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"PanelListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Listing\/Standard"},"ItemStandardStandard":{"id":"ItemStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"This is a standard item to be used in lists or similar contexts.","composition":"A list item consists of a title and the following optional elements: description, action drop down, properties (name\/value), a text or image lead and a color. Property values MAY be interactive by using Shy Buttons.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Information MUST NOT be provided by color alone. The same information could be presented, e.g. in a property to enable screen reader access."}},"parent":"ItemFactoryItem","children":[],"less_variables":[],"path":"src\/UI\/Component\/Item\/Standard"},"ItemGroupGroup":{"id":"ItemGroupGroup","title":"Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An Item Group groups items of a certain type.","composition":"An Item Group consists of a header with an optional action Dropdown and a list if Items.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ItemFactoryItem","children":[],"less_variables":[],"path":"src\/UI\/Component\/Item\/Group"},"ModalInterruptiveInterruptive":{"id":"ModalInterruptiveInterruptive","title":"Interruptive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An Interruptive modal disrupts the user in critical situation, forcing him or her to focus on the task at hand.","composition":"The modal states why this situation needs attention and may point out consequences.","effect":"All controls of the original context are inaccessible until the modal is completed. Upon completion the user returns to the original context.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Due to the heavily disruptive nature of this type of modal it MUST be restricted to critical situations (e.g. loss of data).","2":"All actions where data is deleted from the system are considered to be critical situations and SHOULD be implemented as an Interruptive modal. Exceptions are possible if items from lists in forms are to be deleted or if the modal would heavily disrupt the workflow.","3":"Interruptive modals MUST contain a primary button continuing the action that initiated the modal (e.g. Delete the item) on the left side of the footer of the modal and a default button canceling the action on the right side of the footer.","4":"The cancel button in the footer and the close button in the header MUST NOT perform any additional action than closing the Interruptive modal."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Interruptive"},"InterruptiveItemInterruptiveItem":{"id":"InterruptiveItemInterruptiveItem","title":"Interruptive Item","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Interruptive items are displayed in an Interruptive modal and represent the object(s) being affected by the critical action, e.g. deleting.","composition":"An Interruptive item is composed of an Id, title, description and an icon.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"An interruptive item MUST have an ID and title.","2":"An interruptive item SHOULD have an icon representing the affected object.","3":"An interruptive item MAY have a description which helps to further identify the object. If an Interruptive modal displays multiple items having the the same title, the description MUST be used in order to distinct these objects from each other.","4":"If an interruptive item represents an ILIAS object, e.g. a course, then the Id, title, description and icon of the item MUST correspond to the Id, title, description and icon from the ILIAS object."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"InterruptiveItem"},"ModalRoundTripRoundtrip":{"id":"ModalRoundTripRoundtrip","title":"Roundtrip","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Round-Trip modals are to be used if the context would be lost by performing this action otherwise. Round-Trip modals accommodate sub-workflows within an overriding workflow. The Round-Trip modal ensures that the user does not leave the trajectory of the overriding workflow. This is typically the case if an ILIAS service is being called while working in an object.","composition":"Round-Trip modals are completed by a well defined sequence of only a few steps that might be displayed on a sequence of different modals connected through some \"next\" button.","effect":"Round-Trip modals perform sub-workflow involving some kind of user input. Sub-workflow is completed and user is returned to starting point allowing for continuing the overriding workflow.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Round-Trip modals MUST contain at least two buttons at the bottom of the modals: a button to cancel (right) the workflow and a button to finish or reach the next step in the workflow (left).","2":"Round-Trip modals SHOULD be used, if the user would lose the context otherwise. If the action can be performed within the same context (e.g. add a post in a forum, edit a wiki page), a Round-Trip modal MUST NOT be used.","3":"When the workflow is completed, Round-Trip modals SHOULD show the same view that was displayed when initiating the modal.","4":"Round-Trip modals SHOULD NOT be used to add new items of any kind since adding item is a linear workflow redirecting to the newly added item setting- or content-tab.","5":"Round-Trip modals SHOULD NOT be used to perform complex workflows."},"composition":[],"interaction":[],"wording":{"1":"The label of the Button used to close the Round-Trip-Modal MAY be adapted, if the default label (cancel) does not fit the workflow presented on the screen."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/RoundTrip"},"ModalLightboxLightbox":{"id":"ModalLightboxLightbox","title":"Lightbox","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Lightbox modal displays media data such as images or videos.","composition":"A Lightbox modal consists of one or multiple lightbox pages representing the media together with a title and description.","effect":"Lightbox modals are activated by clicking the full view glyphicon, the title of the object or it's thumbnail. If multiple pages are to be displayed, they can flipped through.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Lightbox modals MUST contain a title above the presented item.","2":"Lightbox modals SHOULD contain a descriptional text below the presented items.","3":"Multiple media items inside a Lightbox modal MUST be presented in carousel like manner allowing to flickr through items."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Lightbox"},"LightboxImagePageLightboxImagePage":{"id":"LightboxImagePageLightboxImagePage","title":"Lightbox Image Page","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Lightbox image page represents an image inside a Lightbox modal.","composition":"The page consists of the image, a title and optional description.","effect":"The image is displayed in the content section of the Lightbox modal and the title is used as modal title. If a description is present, it will be displayed below the image.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"2":"A Lighbox image page MUST have an image and a short title.","1":"A Lightbox image page SHOULD have short a description, describing the presented image. If the description is omitted, the Lightbox image page falls back to the alt tag of the image."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"LightboxImagePage"},"PopoverStandardStandard":{"id":"PopoverStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Popovers are used to display other components. Whenever you want to use the standard-popover, please hand in a PullRequest and discuss it.","composition":"The content of a Standard Popover displays the components together with an optional title.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Popovers MUST NOT be used to render lists, use a Listing Popover for this purpose.","2":"Standard Popovers SHOULD NOT contain complex or large components.","3":"Usages of Standard Popovers MUST be accepted by JourFixe.","4":"Popovers with fixed Position MUST only be attached to triggerers with fixed position."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PopoverFactoryPopover","children":[],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Standard"},"PopoverListingListing":{"id":"PopoverListingListing","title":"Listing","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listing Popovers are used to display list items.","composition":"The content of a Listing Popover displays the list together with an optional title.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Listing Popovers MUST be used if one needs to display lists inside a Popover.","2":"Popovers with fixed Position MUST only be attached to triggerers with fixed position."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PopoverFactoryPopover","children":[],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Listing"},"DropzoneFileFactoryFile":{"id":"DropzoneFileFactoryFile","title":"File","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"File dropzones are used to drop files from outside the browser window. The dropped files are presented to the user and can be uploaded to the server. File dropzones offer additional convenience beside manually selecting files over the file browser.","composition":"File dropzones are areas to drop the files. They contain either a message (standard file dropzone) or other ILIAS UI components (wrapper file dropzone).","effect":"A dropzone is highlighted when the user drags files over it.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"There MUST be alternative ways in the system to upload the files due to the limited accessibility of file dropzones."}},"parent":"DropzoneFactoryDropzone","children":["DropzoneFileStandardStandard","DropzoneFileWrapperWrapper"],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Factory"},"DropzoneFileStandardStandard":{"id":"DropzoneFileStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard dropzone is used to drop files dragged from outside the browser window. The dropped files are presented to the user and can be uploaded to the server.","composition":"Standard dropzones consist of a visible area where files can be dropped. They MUST contain a message explaining that it is possible to drop files inside. The dropped files are presented to the user, optionally with some button to start the upload process.","effect":"A standard dropzone is highlighted when the user is dragging files over the dropzone. After dropping, the dropped files are presented to the user with some meta information of the files such the file name and file size.","rivals":{"Rival 1":"A wrapper dropzone can hold other ILIAS UI components instead of a message."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard dropzones MUST contain a message.","2":"The upload button MUST be disabled if there are no files to be uploaded. Only true if the dropzone is NOT used in a form containing other form elements.","3":"Standard dropzones MAY be used in forms."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Standard dropzones MUST offer the possibility to select files manually from the computer."}},"parent":"DropzoneFileFactoryFile","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Standard"},"DropzoneFileWrapperWrapper":{"id":"DropzoneFileWrapperWrapper","title":"Wrapper","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A wrapper dropzone is used to display other ILIAS UI components inside it. In contrast to the standard dropzone, the wrapper dropzone is not visible by default. Only the wrapped components are visible. Any wrapper dropzone gets highlighted once the user is dragging files over the browser window. Thus, a user needs to have the knowledge that there are wrapper dropzones present. They can be introduced to offer additional approaches to complete some workflow more conveniently. Especially in situation where space is scarce such as appointments in the calendar.","composition":"A wrapper dropzone contains one or multiple ILIAS UI components. A roundtrip modal is used to present the dropped files and to initialize the upload process.","effect":"All wrapper dropzones on the page are highlighted when the user dragging files over the browser window. After dropping the files, the roundtrip modal is opened showing all files. The modal contains a button to start the upload process.","rivals":{"Rival 1":"A standard dropzone displays a message instead of other ILIAS UI components."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Most pages SHOULD NOT contain a wrapper dropzone. Whenever you want to introduce a new usage of the Wrapper-Dropzone, propose it to the Jour Fixe.","2":"Wrapper dropzones MUST contain one or more ILIAS UI components.","3":"Wrapper dropzones MUST NOT contain any other file dropzones.","4":"Wrapper dropzones MUST NOT be used in modals.","5":"The upload button in the modal MUST be disabled if there are no files to be uploaded."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"DropzoneFileFactoryFile","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Wrapper"},"TablePresentationPresentation":{"id":"TablePresentationPresentation","title":"Presentation","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Presentation Table lists some tabular data in a pleasant way. The user can get a quick overview over the records in the dataset, the Presentation Table only shows the most relevant fields of the records at first glance. The records can be expanded to show more extensive information, i.e. additional fields and further information. The Presentation Table represents the displayed dataset an entirety rather than a list of single rows. The table facilitates exploring the dataset, where the purpose of this exploration is known and supported. Single records may be derived and composed from all kind of sources and do not necessarily reference a persistent entity like an ilObject.","composition":"The Presentation Table consists of a title, a slot for View Controls and Presentation Rows. The rows will be prefixed by an Expand Glyph and consist of a headline, a subheadline and a choice of record-fields. The expanded row will show a lists of further fields and, optionally, a button or dropdown for actions. The table is visually represented as a wholeness and does not decompose into several parts.","effect":"Rows can be expanded and collapsed to show\/hide more extensive and detailed information per record. A click on the Expand Glyph will enlarge the row vertically to show the complete record and exchange the Expand Glyph by a Collapse Glyph. Fields that were shown in the collapsed row will be hidden except for headline and subheadline. The ordering among the records in the table, the ordering of the fields in one row or the visible contents of the table itself can be adjusted with View Controls. In contrast to the accordions known from the page editor, it is possible to have multiple expanded rows in the table.","rivals":{"1":"Data Table: A data-table shows some dataset and offers tools to explore it in a user defined way. Instead of aiming at simplicity the Presentation Table aims at maximum explorability. Datasets that contain long content fields, e.g. free text or images, are hard to fit into a Data Table but can indeed be displayed in a Presentation Table.","2":"Listing Panel: Listing Panels list items, where an item is a unique entity in the system, i.e. an identifyable, persistently stored object. This is not necessarily the case for Presentation Tables, where records can be composed of any data from any source in the system."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Rows in the table MUST be of the same structure."},"composition":[],"interaction":{"1":"View Controls used here MUST only affect the table itself.","2":"Clicking the Expand Glyph MUST only expand the row. It MUST NOT trigger any other action."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The expandable content, especially the contained buttons, MUST be accessible by only using the keyboard."}},"parent":"TableFactoryTable","children":[],"less_variables":[],"path":"src\/UI\/Component\/Table\/Presentation"}} \ No newline at end of file +{"FactoryUIComponent":{"id":"FactoryUIComponent","title":"UIComponent","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"What is to be done by this control","composition":"What happens if the control is operated","effect":"What happens if the control is operated","rivals":{"Rival 1":"What other controls are similar, what is their distinction"}},"background ":"Relevant academic information","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Where and when an element is to be used or not."},"composition":[],"interaction":{"2":"How the interaction with this object takes place."},"wording":{"3":"How the wording of labels or captions must be."},"ordering":{"5":"How different elements of this instance are to be ordered."},"style":{"4":"How this element should look like."},"responsiveness":{"6":"How this element behaves on changing screen sizes"},"accessibility":{"7":"How this element is made accessible"}},"parent":false,"children":["IconFactoryIcon","CounterFactoryCounter","ImageFactoryImage","DividerFactoryDivider","LinkFactoryLink","GlyphFactoryGlyph","ButtonFactoryButton","DropdownFactoryDropdown","BreadcrumbsBreadcrumbsBreadcrumbs","ViewControlFactoryViewControl","ChartFactoryChart","InputFactoryInput","CardCardCard","DeckDeckDeck","ListingFactoryListing","PanelFactoryPanel","ItemFactoryItem","ModalFactoryModal","PopoverFactoryPopover","DropzoneFactoryDropzone","LegacyLegacyLegacy","TableFactoryTable"],"less_variables":[],"path":"src\/UI\/Factory"},"IconFactoryIcon":{"id":"IconFactoryIcon","title":"Icon","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Icons are quickly comprehensible and recognizable graphics. They indicate the functionality or nature of a text-element or context: Icons will mainly be used in front of object-titles, e.g. in the header, the tree and in repository listing.","composition":"Icons come in three fixed sizes: small, medium and large. They can be configured with an additional \"abbreviation\", a text of a few characters that will be rendered on top of the image.","effect":"Icons themselves are not interactive; however they are allowed within interactive containers.","rivals":{"1":"Glyphs are typographical characters that act as a trigger for some action.","2":"Images belong to the content and can be purely decorative."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Icons MUST be used to represent objects or context.","2":"Icons MUST be used in combination with a title or label.","3":"An unique Icon MUST always refer to the same thing."},"composition":[],"interaction":[],"wording":{"1":"The aria-label MUST state the represented object-type.","2":"The abbreviation SHOULD consist of one or two letters."},"ordering":[],"style":{"1":"Icons MUST have a class indicating their usage.","2":"Icons MUST be tagged with a CSS-class indicating their size."},"responsiveness":[],"accessibility":{"1":"Icons MUST use aria-label."}},"parent":"FactoryUIComponent","children":["IconStandardStandard","IconCustomCustom"],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Factory"},"CounterFactoryCounter":{"id":"CounterFactoryCounter","title":"Counter","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Counter inform users about the quantity of items indicated by a glyph.","composition":"Counters consist of a number and some background color and are placed one the 'end of the line' in reading direction of the item they state the count for.","effect":"Counters convey information, they are not interactive.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3854_1357.html"],"rules":{"usage":{"1":"A counter MUST only be used in combination with a glyph."},"composition":{"1":"A counter MUST contain exactly one number greater than zero and no other characters."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["CounterCounterStatus","CounterCounterNovelty"],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Factory"},"ImageFactoryImage":{"id":"ImageFactoryImage","title":"Image","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Image component is used to display images of various sources.","composition":"An Image is composed of the image and an alternative text for screen readers.","effect":"Images may be included in interacted components but not interactive on their own.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Images MUST contain the alt attribute. This attribute MAY be left empty (alt=\"\") if the image is of decorative nature. According to the WAI, decorative images don\u2019t add information to the content of a page. For example, the information provided by the image might already be given using adjacent text, or the image might be included to make the website more visually attractive (see https:\/\/www.w3.org\/WAI\/tutorials\/images\/decorative\/<\/a>)."}},"parent":"FactoryUIComponent","children":["ImageImageStandard","ImageImageResponsive"],"less_variables":[],"path":"src\/UI\/Component\/Image\/Factory"},"DividerFactoryDivider":{"id":"DividerFactoryDivider","title":"Divider","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A divider marks a thematic change in a sequence of other components. A Horizontal Divider is used to mark a thematic change in sequence of elements that are stacked from top to bottom, e.g. in a Dropdown. A Vertical Divider is used to mark a thematic change in a sequence of elements that are lined up from left to right, e.g. a Toolbar.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dividers MUST only be used in container components that explicitly state and define the usage of Dividers within the container."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["DividerHorizontalHorizontal","DividerVerticalVertical"],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Factory"},"LinkFactoryLink":{"id":"LinkFactoryLink","title":"Link","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Links are used navigate to other resources or views of the system. Clicking on a link does not change the systems status.","composition":"Link is a clickable, graphically minimal obtrusive control element. It can bear text or other content. Links always contain a valid href tag which should not not just contain a hash sign.","effect":"On-click, the resource or view indicated by the link is requested and presented. Links are not used to trigger Javascript events.","rivals":{"buttons":"Buttons are used to trigger Interactions that usually change the systems status. Buttons are much more obtrusive than links and may fire JS events."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Links MAY be used inline in a Textual Paragraphs."},"composition":[],"interaction":{"1":"Hovering an active link should indicate a possible interaction.","2":"Links MUST not be used to fire Javascript events."},"wording":{"1":"The wording of the link SHOULD name the target view or resource."},"ordering":[],"style":{"1":"Links SHOULD not be presented with a separate background color."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"a\" MUST be used to properly identify an element."}},"parent":"FactoryUIComponent","children":["LinkStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Link\/Factory"},"GlyphFactoryGlyph":{"id":"GlyphFactoryGlyph","title":"Glyph","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Glyphs map a generally known concept or symbol to a specific concept in ILIAS. Glyphs are used when space is scarce.","composition":"A glyph is a typographical character that represents something else. As any other typographical character, they can be manipulated by regular CSS. If hovered they change their background to indicate possible interactions.","effect":"Glyphs act as trigger for some action such as opening a certain Overlay type or as shortcut.","rivals":{"icon":"Standalone Icons are not interactive. Icons can be in an interactive container however. Icons merely serve as additional hint of the functionality described by some title. Glyphs are visually distinguished from object icons: they are monochrome."}},"background ":"\"In typography, a glyph is an elemental symbol within an agreed set of symbols, intended to represent a readable character for the purposes of writing and thereby expressing thoughts, ideas and concepts.\" (https:\/\/en.wikipedia.org\/wiki\/Glyph) Lidwell states that such symbols are used \"to improve the recognition and recall of signs and controls\".","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Glyphs MUST NOT be used in content titles.","2":"Glyphs MUST be used for cross-sectional functionality such as mail for example and NOT for representing objects.","3":"Glyphs SHOULD be used for very simple tasks that are repeated at many places throughout the system.","4":"Services such as mail MAY be represented by a glyph AND an icon."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"All Glyphs MUST be taken from the Bootstrap Glyphicon Halflings set. Exceptions MUST be approved by the JF."},"responsiveness":[],"accessibility":{"1":"The functionality triggered by the Glyph must be indicated to screen readers with by the attribute aria-label or aria-labelledby attribute."}},"parent":"FactoryUIComponent","children":["GlyphGlyphSettings","GlyphGlyphCollapse","GlyphGlyphExpand","GlyphGlyphAdd","GlyphGlyphRemove","GlyphGlyphUp","GlyphGlyphDown","GlyphGlyphBack","GlyphGlyphNext","GlyphGlyphSortAscending","GlyphGlyphSortDescending","GlyphGlyphBriefcase","GlyphGlyphUser","GlyphGlyphMail","GlyphGlyphNotification","GlyphGlyphTag","GlyphGlyphNote","GlyphGlyphComment","GlyphGlyphLike","GlyphGlyphLove","GlyphGlyphDislike","GlyphGlyphLaugh","GlyphGlyphAstounded","GlyphGlyphSad","GlyphGlyphAngry"],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Factory"},"ButtonFactoryButton":{"id":"ButtonFactoryButton","title":"Button","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Buttons trigger interactions that change the system\u2019s or view's status. Acceptable changes to the current view are those that do not result in a complete replacement of the overall screen (e.g. modals).","composition":"Button is a clickable, graphically obtrusive control element. It can bear text.","effect":"On-click, the action indicated by the button is carried out.","rivals":{"glyph":"Glyphs are used if the enclosing Container Collection can not provide enough space for textual information or if such an information would clutter the screen.","links":"Links are used to trigger Interactions that do not change the systems status. They are usually contained inside a Navigational Collection."}},"background ":"Wording rules have been inspired by the iOS Human Interface Guidelines (UI-Elements->Controls->System Button) Style rules have been inspired from the GNOME Human Interface Guidelines->Buttons.","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Buttons MUST NOT be used inside a Textual Paragraph."},"composition":[],"interaction":{"2":"If an action is temporarily not available, Buttons MUST be disabled by setting as type 'disabled'.","3":"A button MUST NOT be used for navigational purpose."},"wording":{"1":"The caption of a Button SHOULD contain no more than two words.","2":"The wording of the button SHOULD describe the action the button performs by using a verb or a verb phrase.","3":"Every word except articles, coordinating conjunctions and prepositions of four or fewer letters MUST be capitalized.","4":"For standard events such as saving or canceling the existing standard terms MUST be used if possible: Save, Cancel, Delete, Cut, Copy.","5":"There are cases where a non-standard label such as \u201cSend Mail\u201d for saving and sending the input of a specific form might deviate from the standard. These cases MUST however specifically justified."},"ordering":[],"style":{"1":"If Text is used inside a Button, the Button MUST be at least six characters wide."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"button\" MUST be used to properly identify an element as a Button if there is no good reason to do otherwise.","2":"Button DOM elements MUST either be of type \"button\", of type \"a\" accompanied with the aria-role \u201cButton\u201d or input along with the type attribute \u201cbutton\u201d or \"submit\"."}},"parent":"FactoryUIComponent","children":["ButtonStandardStandard","ButtonPrimaryPrimary","ButtonCloseClose","ButtonShyShy","ButtonMonthMonth","ButtonTagTag","ButtonBulkyBulky"],"less_variables":[],"path":"src\/UI\/Component\/Button\/Factory"},"DropdownFactoryDropdown":{"id":"DropdownFactoryDropdown","title":"Dropdown","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Dropdowns reveal a list of interactions that change the system\u2019s status or navigate to a different view.","composition":"Dropdown is a clickable, graphically obtrusive control element. It can bear text. On-click a list of Shy Buttons and optional Dividers is shown.","effect":"On-click, a list of actions is revealed. Clicking an item will trigger the action indicated. Clicking outside of an opened Dropdown will close the list of items.","rivals":{"button":"Buttons are used, if single actions should be presented directly in the user interface.","links":"Links are used to trigger actions that do not change the systems status. They are usually contained inside a Navigational Collection.","popovers":"Dropdowns only provide a list of possible actions. Popovers can include more diverse and flexible content."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dropdowns MUST NOT be used standalone. They are only parts of more complex UI elements. These elements MUST define their use of Dropdown. E.g. a List or a Table MAY define that a certain kind of Dropdown is used as part of the UI element."},"composition":[],"interaction":{"1":"Only Dropdown Items MUST trigger an action or change a view. The Dropdown trigger element is only used to show and hide the list of Dropdown Items."},"wording":{"1":"The label of a Dropdown SHOULD contain no more than two words.","2":"Every word except articles, coordinating conjunctions and prepositions of four or fewer letters MUST be capitalized.","3":"For standard events such as saving or canceling the existing standard terms MUST be used if possible: Delete, Cut, Copy.","4":"There are cases where a non-standard label such as \u201cSend Mail\u201d for saving and sending the input of a specific form might deviate from the standard. These cases MUST however specifically justified."},"ordering":[],"style":{"1":"If Text is used inside a Dropdown label, the Dropdown MUST be at least six characters wide."},"responsiveness":[],"accessibility":{"1":"DOM elements of type \"button\" MUST be used to properly identify an element as a Dropdown.","2":"Dropdown items MUST be implemented as \"ul\" list with a set of \"li\" elements and nested Shy Button elements for the actions.","3":"Triggers of Dropdowns MUST indicate their effect by the aria-haspopup attribute set to true.","4":"Triggers of Dropdowns MUST indicate the current state of the Dropdown by the aria-expanded label.","5":"Dropdowns MUST be accessible by keyboard by focusing the trigger element and clicking the return key.","6":"Entries in a Dropdown MUST be accessible by the tab-key if opened.","7":"The focus MAY leave the Dropdown if tab is pressed while focusing the last element. This differs from the behaviour in Popovers and Modals."}},"parent":"FactoryUIComponent","children":["DropdownStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Dropdown\/Factory"},"BreadcrumbsBreadcrumbsBreadcrumbs":{"id":"BreadcrumbsBreadcrumbsBreadcrumbs","title":"Breadcrumbs","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Breadcrumbs is a supplemental navigation scheme. It eases the user's navigation to higher items in hierarchical structures. Breadcrumbs also serve as an effective visual aid indicating the user's location on a website.","composition":"Breadcrumbs-entries are rendered as horizontally arranged UI Links with a seperator in-between.","effect":"Clicking on an entry will get the user to the respective location.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Crumbs MUST trigger navigation to other resources of the system."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Breadcrumbs\/Breadcrumbs"},"ViewControlFactoryViewControl":{"id":"ViewControlFactoryViewControl","title":"View Control","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"View Controls switch between different visualisation of data.","composition":"View Controls are composed mainly of buttons, they are often found in toolbars.","effect":"Interacting with a view control changes to display in some content area.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["ViewControlModeMode","ViewControlSectionSection","ViewControlSortationSortation","ViewControlPaginationPagination"],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Factory"},"ChartFactoryChart":{"id":"ChartFactoryChart","title":"Chart","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Charts are used to graphically represent data in various forms such as maps, graphs or diagrams.","composition":"Charts are composed of various graphical and textual elements representing the raw data.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Charts SHOULD not rely on colors to convey information."},"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ChartScaleBarScaleBar","ChartProgressMeterFactoryProgressMeter"],"less_variables":[],"path":"src\/UI\/Component\/Chart\/Factory"},"InputFactoryInput":{"id":"InputFactoryInput","title":"Input","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"In opposite to components with a purely receptive or at most navigational character, input elements are used to relay user-induced data to the system.","composition":"An input consists of fields that define the way data is entered and a container around those fields that defines the way the data is submitted to the system.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["InputFieldFactoryField","InputContainerFactoryContainer"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Factory"},"CardCardCard":{"id":"CardCardCard","title":"Card","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A card is a flexible content container for small chunks of structured data. Cards are often used in so-called Decks which are a gallery of Cards.","composition":"Cards contain a header, which often includes an Image or Icon and a Title as well as possible actions as Default Buttons and 0 to n sections that may contain further textual descriptions, links and buttons. The size of the cards in decks may be set to extra small (12 cards per row), small (6 cards per row, default), medium (4 cards per row), large (3 cards per row), extra large (2 cards per row) and full (1 card per row). The number of cards per row is responsively adapted, if the size of the screen is changed.","effect":"Cards may contain Interaction Triggers.","rivals":{"Heading Panel":"Heading Panels fill up the complete available width in the Center Content Section. Multiple Heading Panels are stacked vertically.","Block Panels":"Block Panels are used in Sidebars"}},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3208_1357.html"],"rules":{"usage":[],"composition":{"1":"Cards MUST contain a title.","2":"Cards SHOULD contain an Image or Icon in the header section.","3":"Cards MAY contain Interaction Triggers."},"interaction":[],"wording":[],"ordering":[],"style":{"1":"Sections of Cards MUST be separated by Dividers."},"responsiveness":[],"accessibility":{"1":"If multiple Cards are used, they MUST be contained in a Deck."}},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Card\/Card"},"DeckDeckDeck":{"id":"DeckDeckDeck","title":"Deck","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Decks are used to display multiple Cards in a grid. They should be used if a page contains many content items that have similar style and importance. A Deck gives each item equal horizontal space indicating that they are of equal importance.","composition":"Decks are composed only of Cards arranged in a grid. The cards displayed by decks are all of equal size. This Size ranges very small (XS) to very large (XL).","effect":"The Deck is a mere scaffolding element, is has no effect.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_3992_1357.html"],"rules":{"usage":{"1":"Decks MUST only be used to display multiple Cards."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"The number of cards displayed per row MUST adapt to the screen size."},"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Deck\/Deck"},"ListingFactoryListing":{"id":"ListingFactoryListing","title":"Listing","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listings are used to structure itemised textual information.","composition":"Listings may contain ordered, unordered, or labeled items.","effect":"Listings hold only textual information. They may contain Links but no Buttons.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Listings MUST NOT contain Buttons."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ListingUnorderedUnordered","ListingOrderedOrdered","ListingDescriptiveDescriptive"],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Factory"},"PanelFactoryPanel":{"id":"PanelFactoryPanel","title":"Panel","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Panels are used to group titled content.","composition":"Panels consist of a header and content section. They form one Gestalt and so build a perceivable cluster of information. Additionally an optional Dropdown that offers actions on the entity being represented by the panel is shown at the top of the Panel.","effect":"The effect of interaction with panels heavily depends on their content.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":{"1":"Panels MUST contain a title."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["PanelStandardStandard","PanelSubSub","PanelReportReport","PanelListingFactoryListing"],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Factory"},"ItemFactoryItem":{"id":"ItemFactoryItem","title":"Item","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An item displays a unique entity within the system. It shows information about that entity in a structured way.","composition":"Items contain the name of the entity as a title. The title MAY be interactive by using a Shy Button. The item contains three sections, where one section contains important information about the item, the second section shows the content of the item and another section shows metadata about the entity.","effect":"Items may contain Interaction Triggers such as Glyphs, Buttons or Tags.","rivals":{"Card":"Cards define the look of items in a deck. Todo: We need to refactor cards."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Items MUST contain the name of the displayed entity as a title.","2":"Items SHOULD contain a section with it's content.","3":"Items MAY contain Interaction Triggers.","4":"Items MAY contain a section with metadata."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ItemStandardStandard","ItemGroupGroup"],"less_variables":[],"path":"src\/UI\/Component\/Item\/Factory"},"ModalFactoryModal":{"id":"ModalFactoryModal","title":"Modal","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Modal forces users to focus on the task at hand.","composition":"A Modal is a full-screen dialog on top of the greyed-out ILIAS screen. The Modal consists of a header with a close button and a typography modal title, a content section and might have a footer.","effect":"All controls of the original context are inaccessible until the Modal is completed. Upon completion the user returns to the original context.","rivals":{"1":"Modals have some relations to popovers. The main difference between the two is the disruptive nature of the Modal and the larger amount of data that might be displayed inside a modal. Also popovers perform mostly action to add or consult metadata of an item while Modals manipulate or focus items or their sub-items directly."}},"background ":"http:\/\/quince.infragistics.com\/Patterns\/Modal%20Panel.aspx","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The main purpose of the Modals MUST NOT be navigational. But Modals MAY be dialogue of one or two steps and thus encompass \"next\"-buttons or the like.","2":"Modals MUST NOT contain other modals (Modal in Modal).","3":"Modals SHOULD not be used to perform complex workflows.","4":"Modals MUST be closable by a little \u201cx\u201d-button on the right side of the header.","5":"Modals MUST contain a title in the header."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["ModalInterruptiveInterruptive","InterruptiveItemInterruptiveItem","ModalRoundTripRoundtrip","ModalLightboxLightbox","LightboxImagePageLightboxImagePage"],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Factory"},"PopoverFactoryPopover":{"id":"PopoverFactoryPopover","title":"Popover","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Popovers can be used when space is scarce i.e. within List GUI items, table cells or menus in the Header section. They offer either secondary information on object like a preview or rating to be displayed or entered. They display information about ongoing processes","composition":"Popovers consist of a layer displayed above all other content. The content of the Popover depends on the functionality it performs. A Popover MAY display a title above its content. All Popovers contain a pointer pointing from the Popover to the Triggerer of the Popover.","effect":"Popovers are shown by clicking a Triggerer component such as a Button or Glyph. The position of the Popover is calculated automatically be default. However, it is possible to specify if the popover appears horizontal (left, right) or vertical (top, bottom) relative to its Triggerer component. Popovers disappear by clicking anywhere outside the Popover or by pressing the ESC key.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Popovers MUST NOT contain horizontal scrollbars.","2":"Popovers MAY contain vertical scrollbars. The content component is responsible to define its own height and show vertical scrollbars.","3":"If Popovers are used to present secondary information of an object, they SHOULD display a title representing the object."},"composition":[],"interaction":{"1":"A Popover MUST only be displayed if the Trigger component is clicked. This behaviour is different from Tooltips that appear on hovering. Popovers disappear by clicking anywhere outside the Popover or by pressing the ESC key."},"wording":[],"ordering":[],"style":{"1":"Popovers MUST always relate to the Trigger component by a little pointer."},"responsiveness":[],"accessibility":{"1":"There MUST be a way to open the Popover by only using the keyboard.","2":"The focus MUST be inside the Popover, once it is open if it contains at least one interactive item. Otherwise the focus MUST remain on the Triggerer component.","3":"The focus MUST NOT leave the Popover for as long as it is open.","4":"There MUST be a way to reach every control in the Popover by only using the keyboard.","5":"The Popover MUST be closable by pressing the ESC key.","6":"Once the Popover is closed, the focus MUST return to the element triggering the opening of the Popover or the element being clicked if the Popover was closed on click."}},"parent":"FactoryUIComponent","children":["PopoverStandardStandard","PopoverListingListing"],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Factory"},"DropzoneFactoryDropzone":{"id":"DropzoneFactoryDropzone","title":"Dropzone","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Dropzones are containers used to drop either files or other HTML elements.","composition":"A dropzone is a container on the page. Depending on the type of the dropzone, the container is visible by default or it gets highlighted once the user starts to drag the elements over the browser window.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Dropzones MUST be highlighted if the user is dragging compatible elements inside or over the browser window."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":["DropzoneFileFactoryFile"],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/Factory"},"LegacyLegacyLegacy":{"id":"LegacyLegacyLegacy","title":"Legacy","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"This component is used to wrap an existing ILIAS UI element into a UI component. This is useful if a container of the UI components needs to contain content that is not yet implement in the centralized UI components.","composition":"The legacy component contains html or any other content as string.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"This component MUST only be used to ensure backwards compatibility with existing UI elements in ILIAS, therefore it SHOULD only contain Elements which cannot be generated using other UI Components from the UI Service."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"FactoryUIComponent","children":[],"less_variables":[],"path":"src\/UI\/Component\/Legacy\/Legacy"},"TableFactoryTable":{"id":"TableFactoryTable","title":"Table","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Tables present a set of uniformly structured data.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"FactoryUIComponent","children":["TablePresentationPresentation"],"less_variables":[],"path":"src\/UI\/Component\/Table\/Factory"},"IconStandardStandard":{"id":"IconStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Icons represent ILIAS Objects.","composition":"A Standard Icon is displayed as a block-element with a background-graphic. By default, a fallback icon will be rendered; this is until a background image is defined in the icon's CSS-class.","effect":"","rivals":{"1":"Custom Icons are constructed with a path to an (uploaded) image."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"IconFactoryIcon","children":[],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Standard"},"IconCustomCustom":{"id":"IconCustomCustom","title":"Custom","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"ILIAS allows users to upload icons for repository objects. Those, in opposite to the standard icons, need to be constructed with a path.","composition":"Instead of setting a background image via CSS-class, an image-tag is contained in the icons's div.","effect":"","rivals":{"1":"Standard Icons MUST be used for core-objects."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Custom Icons MAY still use an abbreviation."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Custom Icons MUST use SVG as graphic.","2":"Icons MUST have a transparent background so they could be put on all kinds of backgrounds.","3":"Images used for Custom Icons SHOULD have equal width and height (=be quadratic) in order not to be distorted."},"responsiveness":[],"accessibility":[]},"parent":"IconFactoryIcon","children":[],"less_variables":[],"path":"src\/UI\/Component\/Icon\/Custom"},"CounterCounterStatus":{"id":"CounterCounterStatus","title":"Status","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Status counter is used to display information about the total number of some items like users active on the system or total number of comments.","composition":"The Status Counter is a non-obstrusive Counter.","effect":"Status Counters convey information, they are not interactive.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"The Status Counter MUST be displayed on the lower right of the item it accompanies.","2":"The Status Counter SHOULD have a non-obstrusive background color, such as grey."},"responsiveness":[],"accessibility":[]},"parent":"CounterFactoryCounter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Counter"},"CounterCounterNovelty":{"id":"CounterCounterNovelty","title":"Novelty","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Novelty counters inform users about the arrival or creation of new items of the kind indicated by the accompanying glyph.","composition":"A Novelty Counter is an obtrusive counter.","effect":"They count down \/ disappear as soon as the change has been consulted by the user.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Novelty Counter MAY be used with the Status Counter."},"composition":[],"interaction":{"2":"There MUST be a way for the user to consult the changes indicated by the counter.","3":"After the consultation, the Novelty Counter SHOULD disappear or the number it contains is reduced by one.","4":"Depending on the content, the reduced number MAY be added in an additional Status Counter."},"wording":[],"ordering":[],"style":{"5":"The Novelty Counter MUST be displayed on the top at the 'end of the line' in reading direction of the item it accompanies. This would be top right for latin script and top left for arabic script.","6":"The Novelty Counter SHOULD have an obstrusive background color, such as red or orange."},"responsiveness":[],"accessibility":[]},"parent":"CounterFactoryCounter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Counter\/Counter"},"ImageImageStandard":{"id":"ImageImageStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard image is used if the image is to be rendered in it's the original size.","composition":"","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ImageFactoryImage","children":[],"less_variables":[],"path":"src\/UI\/Component\/Image\/Image"},"ImageImageResponsive":{"id":"ImageImageResponsive","title":"Responsive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A responsive image is to be used if the image needs to adapt to changing amount of space available.","composition":"Responsive images scale nicely to the parent element.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ImageFactoryImage","children":[],"less_variables":[],"path":"src\/UI\/Component\/Image\/Image"},"DividerHorizontalHorizontal":{"id":"DividerHorizontalHorizontal","title":"Horizontal","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Horizontal Divider is used to mark a thematic change in a sequence of elements that are stacked from top to bottom.","composition":"Horiztonal dividers consists of a horizontal ruler which may comprise a label.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Horizontal Dividers MUST only be used in container components that render a sequence of items from top to bottom."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"Horizontal Dividers MUST always have a succeeding element in a sequence of elments, which MUST NOT be another Horizontal Divider.","2":"Horizontal Dividers without label MUST always have a preceding element in a sequence of elments, which MUST NOT be another Horizontal Divider."},"style":[],"responsiveness":[],"accessibility":[]},"parent":"DividerFactoryDivider","children":[],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Horizontal"},"DividerVerticalVertical":{"id":"DividerVerticalVertical","title":"Vertical","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Vertical Divider is used to mark a thematic or functional change in a sequence of elements that are stacked from left to right.","composition":"Vertical Dividers consists of a glyph-like character.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Vertical Dividers MUST only be used in container components that render a sequence of items from left to right."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"Vertical Dividers MUST always have a succeeding element in a sequence of elments, which MUST NOT be another Vertical Divider."},"style":[],"responsiveness":[],"accessibility":[]},"parent":"DividerFactoryDivider","children":[],"less_variables":[],"path":"src\/UI\/Component\/Divider\/Vertical"},"LinkStandardStandard":{"id":"LinkStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A standard link is a link with a text label as content of the link.","composition":"The standard link uses the default link color as text color an no background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard links MUST be used if there is no good reason using another instance.","2":"Links to ILIAS screens that contain the general ILIAS navigation MUST NOT be opened in a new viewport."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"LinkFactoryLink","children":[],"less_variables":[],"path":"src\/UI\/Component\/Link\/Standard"},"GlyphGlyphSettings":{"id":"GlyphGlyphSettings","title":"Settings","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Settings Glyph triggers opening a Dropdown to edit settings of the displayed block.","composition":"The Settings Glyph uses the glyphicon-cog.","effect":"Upon clicking a settings Dropdown is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Settings Glyph MUST only be used in Blocks."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u201cSettings\u201d."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphCollapse":{"id":"GlyphGlyphCollapse","title":"Collapse","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Collapse Glyph is used to trigger the collapsing of some neighbouring Container Collection such as a the content of a Dropdown or an Accordion currently shown.","composition":"The Collapse Glyph is composed of a triangle pointing to the bottom indicating that content is currently shown.","effect":"Clicking the Collapse Glyph hides the display of some Container Collection.","rivals":{"Expand Glyph":"The Expand Glyphs triggers the display of some Container Collection.","Previous Glyph":"The Previous\/Next Glyph opens a completely new view. It serves a navigational purpose."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Collapse Glyph MUST indicate if the toggled Container Collection is visible or not."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Collapse Content'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphExpand":{"id":"GlyphGlyphExpand","title":"Expand","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Expand Glyph is used to trigger the display of some neighbouring Container Collection such as a the content of a Dropdown or an Accordion currently shown.","composition":"The Expand Glyph is composed of a triangle pointing to the right indicating that content is currently shown.","effect":"Clicking the Expand Glyph displays some Container Collection.","rivals":{"Collapse Glyph":"The Collapse Glyphs hides the display of some Container Collection.","Previous Glyph":"The Previous\/Next Glyph opens a completely new view. It serves a navigational purpose."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Expand Glyph MUST indicate if the toggled Container Collection is visible or not."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Expand Content'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAdd":{"id":"GlyphGlyphAdd","title":"Add","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The glyphed add-button serves as stand-in for the respective textual buttons in very crowded screens. It allows adding a new item.","composition":"The Add Glyph uses the glyphicon-plus-sign.","effect":"Clicking on the Add Glyph adds a new input to a form or an event to the calendar.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Add Glyph SHOULD not come without a Remove Glyph and vice versa. Because either there is not enough place for textual buttons or there is place. Exceptions to this rule, such as the Calendar, where only elements can be added in a certain place are possible, are to be run through the Jour Fixe.","2":"The Add Glyph stands for an Action and SHOULD be placed in the action column of a form.","3":"The Add Glyph MUST not be used to add lines to tables."},"composition":[],"interaction":{"1":"Newly added items MUST be placed below the line in which the Add Glyph has been clicked"},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Add'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphRemove":{"id":"GlyphGlyphRemove","title":"Remove","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Remove Glyph serves as stand-in for the respective textual buttons in very crowded screens. It allows removing an item.","composition":"The Remove Glyph uses the glyphicon-plus-sign.","effect":"Clicking on the Remove Glyph adds a new input to a form or an event to the calendar.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Remove Glyph SHOULD not come without a glyphed Add Glyph and vice versa. Because either there is not enough place for textual buttons or there is place. Exceptions to this rule, such as the Calendar, where only elements can be added in a certain place are possible, are to be run through the Jour Fixe.","2":"The Remove Glyph stands for an Action and SHOULD be placed in the action column of a form.","3":"The Remove Glyph MUST not be used to add lines to tables."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Remove'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphUp":{"id":"GlyphGlyphUp","title":"Up","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Up Glyph allows for manually arranging rows in tables embedded in forms. It allows moving an item up.","composition":"The Up Glyph uses the glyphicon-circle-arrow-up. The Up Glyph can be combined with the Add\/Remove Glyph.","effect":"Clicking on the Up Glyph moves an item up.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_813_1357.html"],"rules":{"usage":{"1":"The Up Glyph MUST NOT be used to sort tables. There is an established sorting control for that.","2":"The Up Glyph SHOULD not come without a Down and vice versa.","3":"The Up Glyph is an action and SHOULD be listed in the action column of a form."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Up'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphDown":{"id":"GlyphGlyphDown","title":"Down","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Down Glyph allows for manually arranging rows in tables embedded in forms. It allows moving an item down.","composition":"The Down Glyph uses the glyphicon-circle-arrow-down. The Down Glyph can be combined with the Add\/Remove Glyph.","effect":"Clicking on the Down Glyph moves an item up.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":["http:\/\/www.ilias.de\/docu\/goto_docu_wiki_wpage_813_1357.html"],"rules":{"usage":{"1":"The Down Glyph MUST NOT be used to sort tables. There is an established sorting control for that.","2":"The Down Glyph SHOULD not come without a Up and vice versa.","3":"The Down Glyph is an action and SHOULD be listed in the action column of a form."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Down'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphBack":{"id":"GlyphGlyphBack","title":"Back","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Back Glyph indicates a possible change of the view. The view change leads back to some previous view.","composition":"The chevron-left glyphicon is used.","effect":"The click on a Back Glyph leads back to a previous view.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Back and Next Buttons MUST be accompanied by the respective Back\/Next Glyph."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"If clicking on the Back\/Next GLYPH opens a new view of an object, the Next Glyph MUST be used.","2":"If clicking on the Back\/Next GLYPH opens a previous view of an object, the Back Glyph MUST be used."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Back'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNext":{"id":"GlyphGlyphNext","title":"Next","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Next Glyph indicates a possible change of the view. The view change leads back to some previous view.","composition":"The chevron-right glyphicon is used.","effect":"The click on a Next Glyph opens a new view.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Back and Next Buttons MUST be accompanied by the respective Back\/Next Glyph."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"If clicking on the Back\/Next GLYPH opens a new view of an object, the Next Glyph MUST be used.","2":"If clicking on the Back\/Next GLYPH opens a previous view of an object, the Back Glyph MUST be used."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Next'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSortAscending":{"id":"GlyphGlyphSortAscending","title":"Sort Ascending","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Sorting Glyphs indicate the sorting direction of a column in a table as ascending (up) or descending (down). It is a toggle reversing the ordering of a column.","composition":"The Sort Ascending Glyph uses glyphicon-arrow-up.","effect":"Clicking the Sort Ascending Glyph reverses the direction of ordering in a table.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Sort Ascending'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSortDescending":{"id":"GlyphGlyphSortDescending","title":"Sort Descending","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Sorting Glyphs indicate the sorting direction of a column in a table as ascending (up) or descending (down). It is a toggle reversing the ordering of a column.","composition":"The Sort Descending Glyph uses glyphicon-arrow-descending.","effect":"Clicking the Sort Descending Glyph reverses the direction of ordering in a table.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Sort Descending'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphBriefcase":{"id":"GlyphGlyphBriefcase","title":"Briefcase","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The briefcase symbolize some ongoing work that is done. Momentarily in the background tasks.","composition":"The briefcase Glyph uses glyphicon-briefcase.","effect":"The click on the briefcase opens a popup to the background tasks.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Background Tasks'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphUser":{"id":"GlyphGlyphUser","title":"User","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The User Glyph triggers the \u201cWho is online?\u201d Popover in the Top Navigation. The User Glyph indicates the number of pending contact requests and users online via the the Novelty Counter and Status Counter respectively.","composition":"The User Glyph uses the glyphicon-user.","effect":"Clicking the User Glyph opens the \u201cWho is online?\u201d Popover.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Show who is online'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphMail":{"id":"GlyphGlyphMail","title":"Mail","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Mail Glyph provides a shortcut to the mail service. The Mail Glyph indicates the number of new mails received.","composition":"The Mail Glyph uses the glyphicon-envelope.","effect":"Upon clicking on the Mail Glyph the user is transferred to the full-screen mail service.","rivals":{"Mail Icon":"The Mail Icon is used to indicate the user is currently located in the Mail service The Mail Glyph acts as shortcut to the Mail service."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Mail'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNotification":{"id":"GlyphGlyphNotification","title":"Notification","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Notification Glyph allows users to activate \/ deactivate the notification service for a specific object or sub-item. It is a toggle indicating by colour whether it is activated or not.","composition":"The Notification Glyph uses the glyphicon-bell in link-color if notifications are not active or brand-warning color if they are.","effect":"Upon clicking the notification activation is toggled: Clicking the Notification Glyph activates respectively deactivates the notification service for the current object or sub-item.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"The Notification Glyph MUST only be used in the Content Top Actions."},"composition":[],"interaction":{"1":"Clicking the Notification Glyph MUST toggle the activation of Notifications."},"wording":[],"ordering":[],"style":{"1":"If notifications are activated the Notification Glyph MUST use the brand-warning color."},"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Notifications'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphTag":{"id":"GlyphGlyphTag","title":"Tag","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Tag Glyph is used to indicate the possibility of adding tags to an object.","composition":"The Tag Glyph uses the glyphicon-tag.","effect":"Upon clicking the Round Trip Modal to add new Tags is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of tags that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Tags'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphNote":{"id":"GlyphGlyphNote","title":"Note","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Note Glyph is used to indicate the possibilty of adding notes to an object.","composition":"The Note Glyph uses the glyphicon-pushpin.","effect":"Upon clicking the Round Trip Modal to add new notes is opened","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of notes that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Notes'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphComment":{"id":"GlyphGlyphComment","title":"Comment","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Comment Glyph is used to indicate the possibilty of adding comments to an object.","composition":"The Comment Glyph uses the glyphicon-comment.","effect":"Upon clicking the Round Trip Modal to add new comments is opened.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Novelty and Status Counter MUST show the amount of comments that has been given for an specific object."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be \u2018Comments'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLike":{"id":"GlyphGlyphLike","title":"Like","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Like Glyph indicates a user affirms an item, e.g. a posting.","composition":"The Like Glyph uses the \"thumbs up\" unicode emoji U+1F44D, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of like expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Like'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLove":{"id":"GlyphGlyphLove","title":"Love","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Love Glyph indicates a user adores an item, e.g. a posting.","composition":"The Love Glyph uses the \"red heart\" unicode emoji U+2764, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of love expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Love'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphDislike":{"id":"GlyphGlyphDislike","title":"Dislike","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Dislike Glyph indicates a user objects an item, e.g. a posting.","composition":"The Dislike Glyph uses the \"thumbs down\" unicode emoji U+1F44E, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of dislike expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Dislike'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphLaugh":{"id":"GlyphGlyphLaugh","title":"Laugh","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Laugh Glyph indicates a user finds an item hilarious, e.g. a posting.","composition":"The Laugh Glyph uses the \"grinning face with smiling eyes\" unicode emoji U+1F604, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of laugh expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Laugh'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAstounded":{"id":"GlyphGlyphAstounded","title":"Astounded","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Astounded Glyph indicates a user finds an item surprising, e.g. a posting.","composition":"The Astounded Glyph uses the \"face with open mouth\" unicode emoji U+1F62E, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of astounded expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Astounded'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphSad":{"id":"GlyphGlyphSad","title":"Sad","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Sad Glyph indicates a user finds an item disconcerting, e.g. a posting.","composition":"The Sad Glyph uses the \"sad but relieved face\" unicode emoji U+1F625, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of sad expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Sad'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"GlyphGlyphAngry":{"id":"GlyphGlyphAngry","title":"Angry","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Clicking the Angry Glyph indicates a user finds an item outraging, e.g. a posting.","composition":"The Angry Glyph uses the \"angry face\" unicode emoji U+1F620, see https:\/\/unicode.org\/emoji\/charts\/full-emoji-list.html.","effect":"Clicking toggles the state of the user expression. Clicking also updates the respective counter.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A Status Counter MUST indicate the overall amount of angry expressions."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The aria-label MUST be 'Angry'."}},"parent":"GlyphFactoryGlyph","children":[],"less_variables":[],"path":"src\/UI\/Component\/Glyph\/Glyph"},"ButtonStandardStandard":{"id":"ButtonStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard button is the default button to be used in ILIAS. If there is no good reason using another button instance in ILIAS, this is the one that should be used.","composition":"The standard button uses the primary color as background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard buttons MUST be used if there is no good reason using another instance."},"composition":[],"interaction":[],"wording":[],"ordering":{"1":"The most important standard button SHOULD be first in reading direction if there are several buttons.","2":"In the toolbar and in forms special regulations for the ordering of the buttons MAY apply."},"style":[],"responsiveness":{"1":"The most important standard button in multi-action bars MUST be sticky (stay visible on small screens)."},"accessibility":{"1":"Standard buttons MAY define aria-label attribute. Use it in cases where a text label is not visible on the screen or when the label does not provide enough information about the action.","2":"Standard buttons MAY define aria-checked attribute. Use it to inform which is the currently active button."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Standard"},"ButtonPrimaryPrimary":{"id":"ButtonPrimaryPrimary","title":"Primary","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The primary button indicates the most important action on a screen. By definition there can only be one single \u201cmost important\u201d action on any given screen and thus only one single primary button per screen.","composition":"The background color is the btn-primary-color. This screen-unique button-color ensures that it stands out and attracts the user\u2019s attention while there are several buttons competing for attention.","effect":"In toolbars the primary button are required to be sticky, meaning they stay in view in the responsive view.","rivals":[]},"background ":"Tiddwell refers to the primary button as \u201cprominent done button\u201d and describes that \u201cthe button that finishes a transaction should be placed at the end of the visual flow; and is to be made big and well labeled.\u201d She explains that \u201cA well-understood, obvious last step gives your users a sense of closure. There\u2019s no doubt that the transaction will be done when that button is clicked; don\u2019t leave them hanging, wondering whether their work took effect\u201d. The GNOME Human Interface Guidelines -> Buttons also describes a button indicated as most important for dialogs.","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Most pages SHOULD NOT have any Primary Button at all.","2":"There MUST be no more than one Primary Button per page in ILIAS.","3":"The decision to make a Button a Primary Button MUST be confirmed by the JF."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Primary"},"ButtonCloseClose":{"id":"ButtonCloseClose","title":"Close","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The close button triggers the closing of some collection displayed temporarily such as an overlay.","composition":"The close button is displayed without border.","effect":"Clicking the close button closes the enclosing collection.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":{"1":"The Close Button MUST always be positioned in the top right of a collection."},"style":[],"responsiveness":[],"accessibility":{"1":"The functionality of the close button MUST be indicated for screen readers by an aria-label."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Close"},"ButtonShyShy":{"id":"ButtonShyShy","title":"Shy","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Shy buttons are used in contexts that need a less obtrusive presentation than usual buttons have, e.g. in UI collections like Dropdowns.","composition":"Shy buttons do not come with a separte background color.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Shy buttons MUST only be used, if a standard button presentation is not appropriate. E.g. if usual buttons destroy the presentation of an outer UI component or if there is not enough space for a standard button presentation."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Shy"},"ButtonMonthMonth":{"id":"ButtonMonthMonth","title":"Month","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Month Button enables to select a specific month to fire some action (probably a change of view).","composition":"The Month Button is composed of a Button showing the default month directly (probably the month currently rendered by some view). A dropdown contains an interface enabling the selection of a month from the future or the past.","effect":"Selecting a month from the dropdown directly fires the according action (e.g. switching the view to the selected month). Technically this is currently a Javascript event being fired.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":{"1":"Selecting a month from the dropdown MUST directly fire the according action."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Month"},"ButtonTagTag":{"id":"ButtonTagTag","title":"Tag","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Tags classify entities. Thus, their primary purpose is the visualization of those classifications for one entity. However, tags are usually clickable - either to edit associations or list related entities, i.e. objects with the same tag.","composition":"Tags are a colored area with text on it. When used in a tag-cloud (a list of tags), tags can be visually \"weighted\" according to the number of their occurences, be it with different (font-)sizes, different colors or all of them.","effect":"Tags may trigger an action or change the view when clicked. There is no visual difference (besides the cursor) between clickable tags and tags with unavailable action.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":{"1":"Tags SHOULD be used with an additonal class to adjust colors.","2":"The font-color SHOULD be set with high contrast to the chosen background color."},"responsiveness":[],"accessibility":{"1":"The functionality of the tag button MUST be indicated for screen readers by an aria-label."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Tag"},"ButtonBulkyBulky":{"id":"ButtonBulkyBulky","title":"Bulky","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The bulky button is highly obtrusive. It combines the recognisability of a graphical element with an explicit textual label on an unusually sized button. It is hard to overlook and indicates an important action on the screen.","composition":"The Bulky Button consists of an icon or glyph and a (very short) text.","effect":"The button has an \"engaged\"-state: When the button is used to toggle the visibility of a component, it stays engaged until the component is hidden again.","rivals":{"Primary Button":"Primary Buttons indicate the most important action among a collection of actions, e.g. in a tool bar, controls of a form or in a modal. Bulky Buttons make it hard to miss the indicated action by occupying space."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Since Bulky Buttons are so obtrusive they MUST only be used to indicate important actions on the screen."},"composition":[],"interaction":[],"wording":{"1":"The icon\/glyph and the text on the Bulky Button MUST be corresponding."},"ordering":[],"style":{"1":"Bulky Buttons MUST occupy as much space as their container leaves them.","2":"When used to toggle the visibility of another component, the button MUST reflect the componentes state of visibility."},"responsiveness":[],"accessibility":{"1":"The functionality of the Bulky Button MUST be indicated for screen readers by an aria-label.","2":"Bulky Buttons MUST define aria-pressed attribute."}},"parent":"ButtonFactoryButton","children":[],"less_variables":[],"path":"src\/UI\/Component\/Button\/Bulky"},"DropdownStandardStandard":{"id":"DropdownStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Standard Dropdown is the default Dropdown to be used in ILIAS. If there is no good reason using another Dropdown instance in ILIAS, this is the one that should be used.","composition":"The Standard Dropdown uses the primary color as background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Dropdown MUST be used if there is no good reason using another instance."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"DropdownFactoryDropdown","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropdown\/Standard"},"ViewControlModeMode":{"id":"ViewControlModeMode","title":"Mode","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Mode View Controls enable the switching between different aspects of some data. The different modes are mutually exclusive and can therefore not be activated at once.","composition":"Mode View Controls are composed of Buttons switching between active and inactive states.","effect":"Clicking on an inactive Button turns this button active and all other inactive. Clicking on an active button has no effect.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Exactly one Button MUST always be active."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The HTML container enclosing the buttons of the Mode View Control MUST cary the role-attribute \"group\".","2":"The HTML container enclosing the buttons of the Mode View Control MUST set an aria-label describing the element. Eg. \"Mode View Control\"","3":"The Buttons of the Mode View Control MUST set an aria-label clearly describing what the button shows if clicked. E.g. \"List View\", \"Month View\", ...","4":"The currently active Button must be labeled by setting aria-checked to \"true\"."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Mode"},"ViewControlSectionSection":{"id":"ViewControlSectionSection","title":"Section","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Section View Controls enable the switching between different sections of some data. Examples are subsequent days\/weeks\/months in a calendar or entries in a blog.","composition":"Section View Controls are composed of three Buttons. The Button on the left caries a Back Glyph, the Button in the middle is either a Default- or Split Button labeling the data displayed below and the Button on the right carries a next Glyph.","effect":"Clicking on the Buttons left or right changes the selection of the displayed data by a fixed interval. Clicking the Button in the middle opens the sections hinted by the label of the button (e.g. \"Today\").","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Section"},"ViewControlSortationSortation":{"id":"ViewControlSortationSortation","title":"Sortation","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The sortation view control enables users to change the order in which some data is presented. This control applies to all sorts of _structured_ data, like tables and lists.","composition":"Sortation uses a Dropdown to display a collection of shy-buttons.","effect":"A click on an option will change the ordering of the associated data-list by calling a page with a parameter according to the selected option or triggering a signal. The label displayed in the dropdown will be set to the selected sorting.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A Sortation MUST NOT be used standalone.","2":"Sortations MUST BE visually close to the list or table their operation will have effect upon.","3":"There SHOULD NOT be more than one Sortation per view."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Sortation MUST be operable via keyboard only."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Sortation"},"ViewControlPaginationPagination":{"id":"ViewControlPaginationPagination","title":"Pagination","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Pagination allows structured data being displayed in chunks by limiting the number of entries shown. It provides the user with controls to leaf through the chunks of entries.","composition":"Pagination is a collection of shy-buttons to access distinct chunks of data, framed by next\/back glyphs. When used with the \"DropdownAt\" option, a dropdown is rendered if the number of chunks exceeds the option's value.","effect":"A click on an chunk-option will change the offset of the displayed data-list, thus displaying the respective chunk of entries. The active option is rendered as an unavailable shy-button. Clicking the next\/back-glyphs, the previous (respectively: the next) chunk of entries is being displayed. If a previous\/next chunk is not available, the glyph is rendered unavailable. If the pagination is used with a maximum of chunk-options to be shown, both first and last options are always displayed.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A Pagination MUST only be used for structured data, like tables and lists.","2":"A Pagination MUST NOT be used standalone.","3":"Paginations MUST be visually close to the list or table their operation will have effect upon. They MAY be placed directly above and\/or below the list."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Pagination MUST be operable via keyboard only."}},"parent":"ViewControlFactoryViewControl","children":[],"less_variables":[],"path":"src\/UI\/Component\/ViewControl\/Pagination"},"ChartScaleBarScaleBar":{"id":"ChartScaleBarScaleBar","title":"Scale Bar","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Scale Bars are used to display a set of items some of which especially highlighted. E.g. they can be used to inform about a score or target on a rank ordered scale.","composition":"Scale Bars are composed of of a set of bars of equal size. Each bar contains a title. The highlighted elements differ from the others through their darkened background.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Each Bar of the Scale Bars MUST bear a title.","2":"The title of Scale Bars MUST NOT contain any other content than text."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartFactoryChart","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ScaleBar"},"ChartProgressMeterFactoryProgressMeter":{"id":"ChartProgressMeterFactoryProgressMeter","title":"Progress Meter","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Progress Meters are used to display a progress or performance. E.g. they can be used to inform about a progress in a learning objective or to compare the performance between the initial and final test in a course.","composition":"Progress Meters are composed of one or two bars inside a horseshoe-like container. The bars change between two colors, to identify a specific reached value. It additionally may show a percentage of the values and also an identifying text.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Progress Meters MUST contain a maximum value. It MUST be numeric and represents the maximum value.","2":"Progress Meters MUST contain a main value. It MUST be a numeric value between 0 and the maximum. It is represented as the main bar.","3":"Progress Meters SHOULD contain a required value. It MUST be a numeric value between 0 and the maximum. It represents the required value that has to be reached."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartFactoryChart","children":["ChartProgressMeterStandardStandard","ChartProgressMeterFixedSizeFixedSize","ChartProgressMeterMiniMini"],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Factory"},"ChartProgressMeterStandardStandard":{"id":"ChartProgressMeterStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Standard Progress Meter is usually the tool of choice. The Progress Meter informs users about their Progress compared to a the required maximum.","composition":"The Standard Progress Meter is composed of one bar representing a value achieved in relation to a maximum and a required value indicated by some pointer. The comparison value is represented by a second bar below the first one. Also the percentage values of main and required are shown as text.","effect":"On changing screen size they decrease their size including font size in various steps.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Standard Progress Meters MAY contain a comparison value. If there is a comparison value it MUST be a numeric value between 0 and the maximum. It is represented as the second bar.","2":"Standard Progress Meters MAY contain a main value text.","3":"Standard Progress Meters MAY contain a required value text."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Standard"},"ChartProgressMeterFixedSizeFixedSize":{"id":"ChartProgressMeterFixedSizeFixedSize","title":"Fixed Size","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Fixed Size Progress Meter ensures that the element is rendered exactly as set regardless of the screen size.","composition":"See composition description for Standard Progress Meter.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"See composition rules for Standard Progress Meter."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/FixedSize"},"ChartProgressMeterMiniMini":{"id":"ChartProgressMeterMiniMini","title":"Mini","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Mini Progress Meter is used, if it needs to be as small as possible, like in an heading. It is used to display only a single Progress or performance indicator.","composition":"Other than the Standard and Fixed Size Progress Meter it does not allow a comparison value and only displays a single bar. It also does not display any text.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"See composition rules for Progress Meter."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ChartProgressMeterFactoryProgressMeter","children":[],"less_variables":[],"path":"src\/UI\/Component\/Chart\/ProgressMeter\/Mini"},"InputFieldFactoryField":{"id":"InputFieldFactoryField","title":"Field","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Inputs fields are different from other UI components. They bundle two things: First, they are used for displaying, as similar to other components. Second, they are used to define the server side processing of data that is received from the client. Thus, an input field defines which visual input elements a user will see, which constraints are put on the data entered in these fields and which data developers on the server side retrieve from these inputs. Fields need to be enclosed by a container which defines the means of submitting the data collected by the fields and the way those inputs are arranged to be displayed for some client.","composition":"Fields are either individuals or groups of inputs. Both, individual fields and groups, share the same basic input interface. Input-Fields may have a label and byline.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"A byline (explanatory text) MAY be added to input fields."},"interaction":[],"wording":{"1":"If a label is set, it MUST be composed of one single term or a very short phrase. The identifier is an eye catcher for users skimming over a potentially large set of fields.","2":"If a label is set, it MUST avoid lingo. Intelligibility by occasional users is prioritized over technical accuracy. The accurate technical expression is to be mentioned in the byline.","3":"If a label is set, it MUST make a positive statement. If the purpose of the setting is inherently negative, use Verbs as \u201cLimit..\u201d, \u201cLock..\u201d.","4":"If bylines are provided they MUST be informative, not merely repeating the identifier\u2019s or input element\u2019s content. If no informative description can be devised, no description is needed.","5":"A byline MUST clearly state what effect the fields produces and explain, why this might be important and what it can be used for.","6":"Bulk bylines underneath a stack of option explaining all of the options in one paragraph MUST NOT be used. Use individual bylines instead.","7":"A byline SHOULD NOT address the user directly. Addressing users directly is reserved for cases of high risk of severe mis-configuration.","8":"A byline MUST be grammatically complete sentence with a period (.) at the end.","9":"Bylines SHOULD be short with no more than 25 words.","10":"Bylines SHOULD NOT use any formatting in descriptions (bold, italic or similar).","11":"If bylines refer to other tabs or options or tables by name, that reference should be made in quotation marks: \u2018Info\u2019-tab, button \u2018Show Test Results\u2019, \u2018Table of Detailed Test Results\u2019. Use proper quotation marks, not apostrophes. Use single quotation marks for english language and double quotation marks for german language.","12":"By-lines MUST NOT feature parentheses since they greatly diminish readability.","13":"By-lines SHOULD NOT start with terms such as: If this option is set \u2026 If this setting is active \u2026 Choose this setting if \u2026 This setting \u2026 Rather state what happens directly: Participants get \/ make \/ can \u2026 Point in time after which\u2026. ILIAS will monitor\u2026 Sub-items xy are automatically whatever ... Xy will be displayed at place."},"ordering":[],"style":{"1":"Disabled input elements MUST be indicated by setting the \u201cdisabled\u201d attribute.","2":"If focused, the input elements MUST change their input-border-color to the input-focus-border-color."},"responsiveness":[],"accessibility":{"1":"All fields visible in a view MUST be accessible by keyboard by using the \u2018Tab\u2019-Key."}},"parent":"InputFactoryInput","children":["InputFieldTextText","InputFieldNumericNumeric","InputFieldGroupGroup","InputFieldSectionSection","InputFieldDependantGroupDependantGroup","InputFieldCheckboxCheckbox","InputFieldPasswordPassword"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Factory"},"InputContainerFactoryContainer":{"id":"InputContainerFactoryContainer","title":"Container","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An input container defines which means are used to submit the data to the system and how input-fields are being displayed in the UI. Furthermore containers will process received data according to the transformations and constraints of its fields.","composition":"A Container holds one ore more fields.","effect":"","rivals":{"Group Field Input":"Groups are used within containers to functionally bundle input-fields.","Section Field Input":"Sections are used within containers to visually tie fields together."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"InputFactoryInput","children":["InputContainerFormFactoryForm"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Factory"},"InputFieldTextText":{"id":"InputFieldTextText","title":"Text","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A text-field is intended for entering short single-line texts.","composition":"Text fields will render an input-tag with type=\"text\".","effect":"Text inputs are restricted to one line of text.","rivals":{"numeric field":"Use a numeric field if users should input numbers.","alphabet field":"Use an alphabet field if the user should input single letters."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Text Input MUST NOT be used for choosing from predetermined options.","2":"Text input MUST NOT be used for numeric input, a Numeric Field is to be used instead.","3":"Text Input MUST NOT be used for letter-only input, an Alphabet Field is to be used instead."},"composition":[],"interaction":{"1":"Text Input MUST limit the number of characters, if a certain length of text-input may not be exceeded (e.g. due to database-limitations)."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Text"},"InputFieldNumericNumeric":{"id":"InputFieldNumericNumeric","title":"Numeric","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A numeric field is used to retrieve numeric values from the user.","composition":"Numeric inputs will render an input-tag with type=\"number\".","effect":"The field does not accept any data other than numeric values. When focused most browser will show a small vertical rocker to increase and decrease the value in the field.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Number Inputs MUST NOT be used for binary choices.","2":"Magic numbers such as -1 or 0 to specify \u201climitless\u201d or smoother options MUST NOT be used.","3":"A valid input range SHOULD be specified."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Numeric"},"InputFieldGroupGroup":{"id":"InputFieldGroupGroup","title":"Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Input groups are an unlabeled collection of inputs. They are used to build logical units of other fields. Such units might be used to attach some constraints or transformations for multiple fields.","composition":"Groups are composed of inputs. They do not contain a label. The grouping remains invisible for the client.","effect":"There is no visible effect using groups.","rivals":{"sections":"Sections are used to generate a visible relation of fields."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Group"},"InputFieldSectionSection":{"id":"InputFieldSectionSection","title":"Section","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Sections are used to visually group inputs to a common context.","composition":"Sections are composed of inputs. They carry a label and are visible for the client.","effect":"","rivals":{"Groups":"Groups are used as purely logical units, while sections visualize the correlation of fields."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":{"1":"Sections SHOULD comprise 2 to 5 Settings.","2":"More than 5 Settings SHOULD be split into two areas unless this would tamper with the \u201cfamiliar\u201d information architecture of forms.","3":"In standard forms, there MUST NOT be a Setting without an enclosing Titled Form Section. If necessary a Titled Form Section MAY contain only one single Setting."},"interaction":[],"wording":{"1":"The label SHOULD summarize the contained settings accurately from a user\u2019s perspective.","2":"The title SHOULD contain less than 30 characters.","3":"The titles MUST be cross-checked with similar sections in other objects or services to ensure consistency throughout ILIAS.","4":"In doubt consistency SHOULD be prioritized over accuracy in titles."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Section"},"InputFieldDependantGroupDependantGroup":{"id":"InputFieldDependantGroupDependantGroup","title":"Dependant Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Fields can be nested by using dependant groups (formerly known as subforms) allowing for settings-dependent configurations.","composition":"Dependant groups are like groups composed of a set of input fields.","effect":"The display of dependent group is triggered by enabling some other input field which has an attached dependant group. Note that not all fields allow this (e.g. Checkboxes do). Look at the interface whether and how dependant groups can be attached.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"There MUST NOT be a nesting of more than one dependant group. The only exception to this rule is the required quantification of a subsetting by a date or number. These exceptions MUST individually accepted by the Jour Fixe."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/DependantGroup"},"InputFieldCheckboxCheckbox":{"id":"InputFieldCheckboxCheckbox","title":"Checkbox","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A checkbox is used to govern a state, action, or set \/ not to set a value. Checkboxes are typically used to switch on some additional behaviour or services.","composition":"Each Checkbox is labeled by an identifier stating something positive to describe the effect of checking the Checkbox.","effect":"If used in a form, a checkbox may open a dependant section (formerly known as sub form).","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"A checkbox MUST NOT be used whenever a user has to perform a binary choice where option is not automatically the inverse of the other (such as 'Order by Date' and 'Order by Name'). A Select Input or a Radio Group in MUST be used in this case."},"composition":[],"interaction":[],"wording":{"1":"The checkbox\u2019s identifier MUST always state something positive."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Checkbox"},"InputFieldPasswordPassword":{"id":"InputFieldPasswordPassword","title":"Password","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A password-field is intended for entering passwords.","composition":"Text password will render an input-tag with type=\"password\".","effect":"Text password is restricted to one line of text and will mask the entered characters.","rivals":{"text field":"Use a text field for discloseable information (i.e. information that can safely be displayed to an audience)"}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Password Input MUST be used for passwords."},"composition":[],"interaction":{"1":"Password Input SHOULD NOT limit the number of characters.","2":"When used for authentication, Password Input MUST NOT reveal any settings by placing constraints on it.","3":"On the other hand, when setting a password, Password Input SHOULD enforce strong passwords by appropiate contraints."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputFieldFactoryField","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Field\/Password"},"InputContainerFormFactoryForm":{"id":"InputContainerFormFactoryForm","title":"Form","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Forms are used to let the user enter or modify data, check her inputs and submit them to the system. Forms arrange their contents (i.e. fields) in an explanatory rather than space-saving way.","composition":"Forms are composed of input fields, displaying their labels and bylines.","effect":"","rivals":{"filter":"Filters are used to limit search results; they never modify data in the system."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"InputContainerFactoryContainer","children":["InputContainerFormStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Form\/Factory"},"InputContainerFormStandardStandard":{"id":"InputContainerFormStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Forms are used for creating content of sub-items or for configuring objects or services.","composition":"Standard forms provide a submit-button.","effect":"The users manipulates input-values and saves the form to apply the settings to the object or service or create new entities in the system.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Forms MUST NOT be used on the same page as tables.","2":"Standard Forms MUST NOT be used on the same page as toolbars."},"composition":{"1":"Each form SHOULD contain at least one section displaying a title.","2":"Standard Forms MUST only be submitted by their submit-button. They MUST NOT be submitted by anything else.","3":"Wording of labels of the fields the form contains and their ordering MUST be consistent with identifiers in other objects if some for is used there for a similar purpose. If you feel a wording or ordering needs to be changed, then you MUST propose it to the JF.","4":"On top and bottom of a standard form there SHOULD be the \u201cSave\u201d button for the form.","5":"In some rare exceptions the Buttons MAY be named differently: if \u201cSave\u201d is clearly a misleading since the action is more than storing the data into the database. \u201cSend Mail\u201d would be an example of this."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"InputContainerFormFactoryForm","children":[],"less_variables":[],"path":"src\/UI\/Component\/Input\/Container\/Form\/Standard"},"ListingUnorderedUnordered":{"id":"ListingUnorderedUnordered","title":"Unordered","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Unordered Lists are used to display a unordered set of textual elements.","composition":"Unordered Lists are composed of a set of bullets labeling the listed items.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Unordered"},"ListingOrderedOrdered":{"id":"ListingOrderedOrdered","title":"Ordered","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Ordered Lists are used to displayed a numbered set of textual elements. They are used if the order of the elements is relevant.","composition":"Ordered Lists are composed of a set of numbers labeling the items enumerated.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Ordered"},"ListingDescriptiveDescriptive":{"id":"ListingDescriptiveDescriptive","title":"Descriptive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Descriptive Lists are used to display key-value doubles of textual-information.","composition":"Descriptive Lists are composed of a key acting as title describing the type of information being displayed underneath.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Listing\/Descriptive"},"PanelStandardStandard":{"id":"PanelStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Panels are used in the Center Content section to group content.","composition":"Standard Panels consist of a title and a content section. The structure of this content might be varying from Standard Panel to Standard Panel. Standard Panels may contain Sub Panels.","effect":"","rivals":{"Cards":"Often Cards are used in Decks to display multiple uniformly structured chunks of Data horizontally and vertically."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"In Forms Standard Panels MUST be used to group different sections into Form Parts.","2":"Standard Panels SHOULD be used in the Center Content as primary Container for grouping content of varying content."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Standard"},"PanelSubSub":{"id":"PanelSubSub","title":"Sub","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Sub Panels are used to structure the content of Standard panels further into titled sections.","composition":"Sub Panels consist of a title and a content section. They may contain a Card on their right side to display meta information about the content displayed.","effect":"","rivals":{"Standard Panel":"The Standard Panel might contain a Sub Panel.","Card":"The Sub Panels may contain one Card."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Sub Panels MUST only be inside Standard Panels"},"composition":{"1":"Sub Panels MUST NOT contain Sub Panels or Standard Panels as content."},"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Sub"},"PanelReportReport":{"id":"PanelReportReport","title":"Report","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Report Panels display user-generated data combining text in lists, tables and sometimes charts. Report Panels always draw from two distinct sources: the structure \/ scaffolding of the Report Panels stems from user-generated content (i.e a question of a survey, a competence with levels) and is filled with user-generated content harvested by that very structure (i.e. participants\u2019 answers to the question, self-evaluation of competence).","composition":"They are composed of a Standard Panel which contains several Sub Panels. They might also contain a card to display information meta information in their first block.","effect":"Report Panels are predominantly used for displaying data. They may however comprise links or buttons.","rivals":{"Standard Panels":"The Report Panels contains sub panels used to structure information.","Presentation Table":"Presentation Tables display only a subset of the data at first glance; their entries can then be expanded to show detailed information."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Report Panels SHOULD be used when user generated content of two sources (i.e results, guidelines in a template) is to be displayed alongside each other."},"composition":[],"interaction":{"1":"Links MAY open new views.","2":"Buttons MAY trigger actions or inline editing."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Report"},"PanelListingFactoryListing":{"id":"PanelListingFactoryListing","title":"Listing","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listing Panels are used to list items following all one single template.","composition":"Listing Panels are composed of several titled Item Groups. They further may contain a filter.","effect":"The List Items of Listing Panels may contain a dropdown offering options to interact with the item. Further Listing Panels may be filtered and the number of sections or items to be displayed may be configurable.","rivals":{"Report Panels":"Report Panels contain sections as Sub Panels each displaying different aspects of one item.","Presentation Table":"Use Presentation Table if you have a data set at hand that you want to make explorable and\/or present as a wholeness. Also use Presentation Table if your dataset does not contain Items that represent entities."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Listing Panels SHOULD be used, if a large number of items using the same template are to be displayed in an inviting way not using a Table."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PanelFactoryPanel","children":["PanelListingStandardStandard"],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Listing\/Factory"},"PanelListingStandardStandard":{"id":"PanelListingStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard item lists present lists of items with similar presentation. All items are passed by using Item Groups.","composition":"This Listing is composed of title and a set of Item Groups. Additionally an optional dropdown to select the number\/types of items to be shown at the top of the Listing.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"PanelListingFactoryListing","children":[],"less_variables":[],"path":"src\/UI\/Component\/Panel\/Listing\/Standard"},"ItemStandardStandard":{"id":"ItemStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"This is a standard item to be used in lists or similar contexts.","composition":"A list item consists of a title and the following optional elements: description, action drop down, properties (name\/value), a text or image lead and a color. Property values MAY be interactive by using Shy Buttons.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Information MUST NOT be provided by color alone. The same information could be presented, e.g. in a property to enable screen reader access."}},"parent":"ItemFactoryItem","children":[],"less_variables":[],"path":"src\/UI\/Component\/Item\/Standard"},"ItemGroupGroup":{"id":"ItemGroupGroup","title":"Group","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An Item Group groups items of a certain type.","composition":"An Item Group consists of a header with an optional action Dropdown and a list if Items.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":null,"parent":"ItemFactoryItem","children":[],"less_variables":[],"path":"src\/UI\/Component\/Item\/Group"},"ModalInterruptiveInterruptive":{"id":"ModalInterruptiveInterruptive","title":"Interruptive","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"An Interruptive modal disrupts the user in critical situation, forcing him or her to focus on the task at hand.","composition":"The modal states why this situation needs attention and may point out consequences.","effect":"All controls of the original context are inaccessible until the modal is completed. Upon completion the user returns to the original context.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Due to the heavily disruptive nature of this type of modal it MUST be restricted to critical situations (e.g. loss of data).","2":"All actions where data is deleted from the system are considered to be critical situations and SHOULD be implemented as an Interruptive modal. Exceptions are possible if items from lists in forms are to be deleted or if the modal would heavily disrupt the workflow.","3":"Interruptive modals MUST contain a primary button continuing the action that initiated the modal (e.g. Delete the item) on the left side of the footer of the modal and a default button canceling the action on the right side of the footer.","4":"The cancel button in the footer and the close button in the header MUST NOT perform any additional action than closing the Interruptive modal."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Interruptive"},"InterruptiveItemInterruptiveItem":{"id":"InterruptiveItemInterruptiveItem","title":"Interruptive Item","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Interruptive items are displayed in an Interruptive modal and represent the object(s) being affected by the critical action, e.g. deleting.","composition":"An Interruptive item is composed of an Id, title, description and an icon.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"An interruptive item MUST have an ID and title.","2":"An interruptive item SHOULD have an icon representing the affected object.","3":"An interruptive item MAY have a description which helps to further identify the object. If an Interruptive modal displays multiple items having the the same title, the description MUST be used in order to distinct these objects from each other.","4":"If an interruptive item represents an ILIAS object, e.g. a course, then the Id, title, description and icon of the item MUST correspond to the Id, title, description and icon from the ILIAS object."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"InterruptiveItem"},"ModalRoundTripRoundtrip":{"id":"ModalRoundTripRoundtrip","title":"Roundtrip","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Round-Trip modals are to be used if the context would be lost by performing this action otherwise. Round-Trip modals accommodate sub-workflows within an overriding workflow. The Round-Trip modal ensures that the user does not leave the trajectory of the overriding workflow. This is typically the case if an ILIAS service is being called while working in an object.","composition":"Round-Trip modals are completed by a well defined sequence of only a few steps that might be displayed on a sequence of different modals connected through some \"next\" button.","effect":"Round-Trip modals perform sub-workflow involving some kind of user input. Sub-workflow is completed and user is returned to starting point allowing for continuing the overriding workflow.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Round-Trip modals MUST contain at least two buttons at the bottom of the modals: a button to cancel (right) the workflow and a button to finish or reach the next step in the workflow (left).","2":"Round-Trip modals SHOULD be used, if the user would lose the context otherwise. If the action can be performed within the same context (e.g. add a post in a forum, edit a wiki page), a Round-Trip modal MUST NOT be used.","3":"When the workflow is completed, Round-Trip modals SHOULD show the same view that was displayed when initiating the modal.","4":"Round-Trip modals SHOULD NOT be used to add new items of any kind since adding item is a linear workflow redirecting to the newly added item setting- or content-tab.","5":"Round-Trip modals SHOULD NOT be used to perform complex workflows."},"composition":[],"interaction":[],"wording":{"1":"The label of the Button used to close the Round-Trip-Modal MAY be adapted, if the default label (cancel) does not fit the workflow presented on the screen."},"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/RoundTrip"},"ModalLightboxLightbox":{"id":"ModalLightboxLightbox","title":"Lightbox","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Lightbox modal displays media data such as images or videos.","composition":"A Lightbox modal consists of one or multiple lightbox pages representing the media together with a title and description.","effect":"Lightbox modals are activated by clicking the full view glyphicon, the title of the object or it's thumbnail. If multiple pages are to be displayed, they can flipped through.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Lightbox modals MUST contain a title above the presented item.","2":"Lightbox modals SHOULD contain a descriptional text below the presented items.","3":"Multiple media items inside a Lightbox modal MUST be presented in carousel like manner allowing to flickr through items."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"src\/UI\/Component\/Modal\/Lightbox"},"LightboxImagePageLightboxImagePage":{"id":"LightboxImagePageLightboxImagePage","title":"Lightbox Image Page","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A Lightbox image page represents an image inside a Lightbox modal.","composition":"The page consists of the image, a title and optional description.","effect":"The image is displayed in the content section of the Lightbox modal and the title is used as modal title. If a description is present, it will be displayed below the image.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"2":"A Lighbox image page MUST have an image and a short title.","1":"A Lightbox image page SHOULD have short a description, describing the presented image. If the description is omitted, the Lightbox image page falls back to the alt tag of the image."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"ModalFactoryModal","children":[],"less_variables":[],"path":"LightboxImagePage"},"PopoverStandardStandard":{"id":"PopoverStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Standard Popovers are used to display other components. Whenever you want to use the standard-popover, please hand in a PullRequest and discuss it.","composition":"The content of a Standard Popover displays the components together with an optional title.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard Popovers MUST NOT be used to render lists, use a Listing Popover for this purpose.","2":"Standard Popovers SHOULD NOT contain complex or large components.","3":"Usages of Standard Popovers MUST be accepted by JourFixe.","4":"Popovers with fixed Position MUST only be attached to triggerers with fixed position."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PopoverFactoryPopover","children":[],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Standard"},"PopoverListingListing":{"id":"PopoverListingListing","title":"Listing","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"Listing Popovers are used to display list items.","composition":"The content of a Listing Popover displays the list together with an optional title.","effect":"","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Listing Popovers MUST be used if one needs to display lists inside a Popover.","2":"Popovers with fixed Position MUST only be attached to triggerers with fixed position."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"PopoverFactoryPopover","children":[],"less_variables":[],"path":"src\/UI\/Component\/Popover\/Listing"},"DropzoneFileFactoryFile":{"id":"DropzoneFileFactoryFile","title":"File","abstract":1,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"File dropzones are used to drop files from outside the browser window. The dropped files are presented to the user and can be uploaded to the server. File dropzones offer additional convenience beside manually selecting files over the file browser.","composition":"File dropzones are areas to drop the files. They contain either a message (standard file dropzone) or other ILIAS UI components (wrapper file dropzone).","effect":"A dropzone is highlighted when the user drags files over it.","rivals":[]},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":[],"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"There MUST be alternative ways in the system to upload the files due to the limited accessibility of file dropzones."}},"parent":"DropzoneFactoryDropzone","children":["DropzoneFileStandardStandard","DropzoneFileWrapperWrapper"],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Factory"},"DropzoneFileStandardStandard":{"id":"DropzoneFileStandardStandard","title":"Standard","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The standard dropzone is used to drop files dragged from outside the browser window. The dropped files are presented to the user and can be uploaded to the server.","composition":"Standard dropzones consist of a visible area where files can be dropped. They MUST contain a message explaining that it is possible to drop files inside. The dropped files are presented to the user, optionally with some button to start the upload process.","effect":"A standard dropzone is highlighted when the user is dragging files over the dropzone. After dropping, the dropped files are presented to the user with some meta information of the files such the file name and file size.","rivals":{"Rival 1":"A wrapper dropzone can hold other ILIAS UI components instead of a message."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Standard dropzones MUST contain a message.","2":"The upload button MUST be disabled if there are no files to be uploaded. Only true if the dropzone is NOT used in a form containing other form elements.","3":"Standard dropzones MAY be used in forms."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"Standard dropzones MUST offer the possibility to select files manually from the computer."}},"parent":"DropzoneFileFactoryFile","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Standard"},"DropzoneFileWrapperWrapper":{"id":"DropzoneFileWrapperWrapper","title":"Wrapper","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"A wrapper dropzone is used to display other ILIAS UI components inside it. In contrast to the standard dropzone, the wrapper dropzone is not visible by default. Only the wrapped components are visible. Any wrapper dropzone gets highlighted once the user is dragging files over the browser window. Thus, a user needs to have the knowledge that there are wrapper dropzones present. They can be introduced to offer additional approaches to complete some workflow more conveniently. Especially in situation where space is scarce such as appointments in the calendar.","composition":"A wrapper dropzone contains one or multiple ILIAS UI components. A roundtrip modal is used to present the dropped files and to initialize the upload process.","effect":"All wrapper dropzones on the page are highlighted when the user dragging files over the browser window. After dropping the files, the roundtrip modal is opened showing all files. The modal contains a button to start the upload process.","rivals":{"Rival 1":"A standard dropzone displays a message instead of other ILIAS UI components."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Most pages SHOULD NOT contain a wrapper dropzone. Whenever you want to introduce a new usage of the Wrapper-Dropzone, propose it to the Jour Fixe.","2":"Wrapper dropzones MUST contain one or more ILIAS UI components.","3":"Wrapper dropzones MUST NOT contain any other file dropzones.","4":"Wrapper dropzones MUST NOT be used in modals.","5":"The upload button in the modal MUST be disabled if there are no files to be uploaded."},"composition":[],"interaction":[],"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":[]},"parent":"DropzoneFileFactoryFile","children":[],"less_variables":[],"path":"src\/UI\/Component\/Dropzone\/File\/Wrapper"},"TablePresentationPresentation":{"id":"TablePresentationPresentation","title":"Presentation","abstract":0,"status_entry":"Proposed","status_implementation":"Partly implemented","description":{"purpose":"The Presentation Table lists some tabular data in a pleasant way. The user can get a quick overview over the records in the dataset, the Presentation Table only shows the most relevant fields of the records at first glance. The records can be expanded to show more extensive information, i.e. additional fields and further information. The Presentation Table represents the displayed dataset an entirety rather than a list of single rows. The table facilitates exploring the dataset, where the purpose of this exploration is known and supported. Single records may be derived and composed from all kind of sources and do not necessarily reference a persistent entity like an ilObject.","composition":"The Presentation Table consists of a title, a slot for View Controls and Presentation Rows. The rows will be prefixed by an Expand Glyph and consist of a headline, a subheadline and a choice of record-fields. The expanded row will show a lists of further fields and, optionally, a button or dropdown for actions. The table is visually represented as a wholeness and does not decompose into several parts.","effect":"Rows can be expanded and collapsed to show\/hide more extensive and detailed information per record. A click on the Expand Glyph will enlarge the row vertically to show the complete record and exchange the Expand Glyph by a Collapse Glyph. Fields that were shown in the collapsed row will be hidden except for headline and subheadline. The ordering among the records in the table, the ordering of the fields in one row or the visible contents of the table itself can be adjusted with View Controls. In contrast to the accordions known from the page editor, it is possible to have multiple expanded rows in the table.","rivals":{"1":"Data Table: A data-table shows some dataset and offers tools to explore it in a user defined way. Instead of aiming at simplicity the Presentation Table aims at maximum explorability. Datasets that contain long content fields, e.g. free text or images, are hard to fit into a Data Table but can indeed be displayed in a Presentation Table.","2":"Listing Panel: Listing Panels list items, where an item is a unique entity in the system, i.e. an identifyable, persistently stored object. This is not necessarily the case for Presentation Tables, where records can be composed of any data from any source in the system."}},"background ":"","selector":"","feature_wiki_references ":[],"rules":{"usage":{"1":"Rows in the table MUST be of the same structure."},"composition":[],"interaction":{"1":"View Controls used here MUST only affect the table itself.","2":"Clicking the Expand Glyph MUST only expand the row. It MUST NOT trigger any other action."},"wording":[],"ordering":[],"style":[],"responsiveness":[],"accessibility":{"1":"The expandable content, especially the contained buttons, MUST be accessible by only using the keyboard."}},"parent":"TableFactoryTable","children":[],"less_variables":[],"path":"src\/UI\/Component\/Table\/Presentation"}} \ No newline at end of file diff --git a/src/UI/Implementation/Component/Input/Field/Password.php b/src/UI/Implementation/Component/Input/Field/Password.php index e7da8e5eeb1d..91174cb59d34 100644 --- a/src/UI/Implementation/Component/Input/Field/Password.php +++ b/src/UI/Implementation/Component/Input/Field/Password.php @@ -6,16 +6,18 @@ use ILIAS\UI\Component as C; use ILIAS\Data\Password as PWD; - use ILIAS\Data\Factory as DataFactory; use ILIAS\Transformation\Factory as TransformationFactory; use ILIAS\Validation\Factory as ValidationFactory; +use ILIAS\UI\Implementation\Component\ComponentHelper; /** * This implements the password input. */ class Password extends Input implements C\Input\Field\Password { + use ComponentHelper; + public function __construct( DataFactory $data_factory, ValidationFactory $validation_factory, @@ -42,4 +44,43 @@ protected function isClientSideValueOk($value) { protected function getConstraintForRequirement() { return $this->validation_factory->hasMinLength(1); } + + /** + * This is a shortcut to quickly get a Passwordfiled with desired contraints. + * + * @param int $min_length + * @param bool $lower + * @param bool $upper + * @param bool $numbers + * @param bool $special + * @return Password + */ + public function withStandardConstraints($min_length=8, $lower=true, $upper=true, $numbers=true, $special=true) { + $this->checkIntArg('min_length', $min_length); + $this->checkBoolArg('lower', $lower); + $this->checkBoolArg('upper', $upper); + $this->checkBoolArg('numbers', $numbers); + $this->checkBoolArg('special', $special); + + $data = new \ILIAS\Data\Factory(); + $validation = new \ILIAS\Validation\Factory($data); + $pw_validation = $validation->password(); + $constraints = [ + $pw_validation->hasMinLength($min_length), + ]; + + if($lower) { + $constraints[] = $pw_validation->hasLowerChars(); + } + if($upper) { + $constraints[] = $pw_validation->hasUpperChars(); + } + if($numbers) { + $constraints[] = $pw_validation->hasNumbers(); + } + if($special) { + $constraints[] = $pw_validation->hasSpecialChars(); + } + return $this->withAdditionalConstraint($validation->parallel($constraints)); + } } diff --git a/src/UI/examples/Input/Field/Password/with_contraints.php b/src/UI/examples/Input/Field/Password/with_contraints.php index 8e8c7b08fd55..791a76e110ec 100644 --- a/src/UI/examples/Input/Field/Password/with_contraints.php +++ b/src/UI/examples/Input/Field/Password/with_contraints.php @@ -14,16 +14,19 @@ function with_contraints() { //Step 1: Define the input field //and add some constraints. - $pwd_input = $ui->input()->field()->password("Password", "contraints in place.") + $pwd_input = $ui->input()->field()->password("Password", "constraints in place.") ->withAdditionalConstraint( $validation->parallel([ $pw_validation->hasMinLength(8), - $pw_validation->hasUpperChars(), $pw_validation->hasLowerChars(), + $pw_validation->hasUpperChars(), $pw_validation->hasNumbers(), $pw_validation->hasSpecialChars() ]) ); + //the above can be shortcut into: + $pwd_input2 = $ui->input()->field()->password("Password", "constraints in place.") + ->withStandardConstraints(8, true, true, true, true); //Step 2: Define the form and attach the field. $DIC->ctrl()->setParameterByClass( @@ -32,7 +35,7 @@ function with_contraints() { 'password' ); $form_action = $DIC->ctrl()->getFormActionByClass('ilsystemstyledocumentationgui'); - $form = $ui->input()->container()->form()->standard($form_action, ['pwd'=>$pwd_input]); + $form = $ui->input()->container()->form()->standard($form_action, ['pwd'=>$pwd_input, 'pwd2'=>$pwd_input2]); //Step 3: Define some data processing. $result = ''; From 2c0083f3d1e49832c804ca0b3272e9d1cce53261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Wed, 13 Jun 2018 15:16:36 +0200 Subject: [PATCH 004/166] DBUPDATE new test setting (follow up question previous answer fixation) --- Modules/Test/DBupdate_KeyFeature.php | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Modules/Test/DBupdate_KeyFeature.php diff --git a/Modules/Test/DBupdate_KeyFeature.php b/Modules/Test/DBupdate_KeyFeature.php new file mode 100644 index 000000000000..9b4a4c22e902 --- /dev/null +++ b/Modules/Test/DBupdate_KeyFeature.php @@ -0,0 +1,29 @@ +checkAccess('read', '', SYSTEM_FOLDER_ID) ) +{ + die('administrative privileges only!'); +} + +/* @var \ILIAS\DI\Container $DIC */ +try +{ + if( !$DIC->database()->tableColumnExists('tst_tests', 'follow_qst_answer_fixation') ) + { + $DIC->database()->addTableColumn('tst_tests', 'follow_qst_answer_fixation', array( + 'type' => 'integer', 'notnull' => false, 'length' => 1, 'default' => 0 + )); + + $DIC->database()->manipulateF( + 'UPDATE tst_tests SET follow_qst_answer_fixation = %s', array('integer'), array(0) + ); + } +} +catch(ilException $e) +{ + +} From 7f8f0238d3d6376b1a0c2f4ec0b4d0cc3dc34a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Wed, 13 Jun 2018 15:27:36 +0200 Subject: [PATCH 005/166] restructured settings for instant feedback and answer fixation, incl. language variables, setting templates, export/import --- Modules/Test/classes/class.ilObjTest.php | 55 ++++- Modules/Test/classes/class.ilObjTestGUI.php | 32 ++- .../class.ilObjTestSettingsGeneralGUI.php | 190 +++++++++++------- .../class.ilTestSettingsTemplateConfig.php | 31 ++- lang/ilias_de.lang | 25 ++- lang/ilias_en.lang | 25 ++- 6 files changed, 251 insertions(+), 107 deletions(-) diff --git a/Modules/Test/classes/class.ilObjTest.php b/Modules/Test/classes/class.ilObjTest.php index 21e2d4da6d8d..c9279af855e8 100755 --- a/Modules/Test/classes/class.ilObjTest.php +++ b/Modules/Test/classes/class.ilObjTest.php @@ -560,7 +560,12 @@ class ilObjTest extends ilObject implements ilMarkSchemaAware, ilEctsGradesEnabl * @var bool */ protected $showGradingMarkEnabled; - + + /** + * @var bool + */ + protected $followupQuestionAnswerFixationEnabled; + /** * @var bool */ @@ -696,6 +701,7 @@ public function __construct($a_id = 0,$a_call_by_reference = true) $this->showGradingStatusEnabled = true; $this->showGradingMarkEnabled = true; + $this->followupQuestionAnswerFixationEnabled = false; $this->instantFeedbackAnswerFixationEnabled = false; $this->testFinalBroken = false; @@ -1327,6 +1333,7 @@ public function saveToDb($properties_only = FALSE) 'result_tax_filters' => array('text', serialize((array)$this->getResultFilterTaxIds())), 'show_grading_status' => array('integer', (int)$this->isShowGradingStatusEnabled()), 'show_grading_mark' => array('integer', (int)$this->isShowGradingMarkEnabled()), + 'follow_qst_answer_fixation' => array('integer', (int)$this->isFollowupQuestionAnswerFixationEnabled()), 'inst_fb_answer_fixation' => array('integer', (int)$this->isInstantFeedbackAnswerFixationEnabled()), 'force_inst_fb' => array('integer', (int)$this->isForceInstantFeedbackEnabled()), 'broken' => array('integer', (int)$this->isTestFinalBroken()), @@ -1449,6 +1456,7 @@ public function saveToDb($properties_only = FALSE) 'result_tax_filters' => array('text', serialize((array)$this->getResultFilterTaxIds())), 'show_grading_status' => array('integer', (int)$this->isShowGradingStatusEnabled()), 'show_grading_mark' => array('integer', (int)$this->isShowGradingMarkEnabled()), + 'follow_qst_answer_fixation' => array('integer', (int)$this->isFollowupQuestionAnswerFixationEnabled()), 'inst_fb_answer_fixation' => array('integer', (int)$this->isInstantFeedbackAnswerFixationEnabled()), 'force_inst_fb' => array('integer', (int)$this->isForceInstantFeedbackEnabled()), 'broken' => array('integer', (int)$this->isTestFinalBroken()), @@ -1971,6 +1979,7 @@ public function loadFromDb() $this->setResultFilterTaxIds(strlen($data->result_tax_filters) ? unserialize($data->result_tax_filters) : array()); $this->setShowGradingStatusEnabled((bool)$data->show_grading_status); $this->setShowGradingMarkEnabled((bool)$data->show_grading_mark); + $this->setFollowupQuestionAnswerFixationEnabled((bool)$data->follow_qst_answer_fixation); $this->setInstantFeedbackAnswerFixationEnabled((bool)$data->inst_fb_answer_fixation); $this->setForceInstantFeedbackEnabled((bool)$data->force_inst_fb); $this->setTestFinalBroken((bool)$data->broken); @@ -5988,6 +5997,9 @@ public function fromXML(ilQTIAssessment $assessment) case "instant_verification": $this->setInstantFeedbackSolution($metadata["entry"]); break; + case "follow_qst_answer_fixation": + $this->setFollowupQuestionAnswerFixationEnabled((bool)$metadata["entry"]); + break; case "instant_feedback_answer_fixation": $this->setInstantFeedbackAnswerFixationEnabled((bool)$metadata["entry"]); break; @@ -6429,6 +6441,12 @@ public function toXML() $a_xml_writer->xmlElement("fieldentry", NULL, sprintf("%d", $this->getAnswerFeedbackPoints())); $a_xml_writer->xmlEndTag("qtimetadatafield"); + // followup question previous answer freezing + $a_xml_writer->xmlStartTag("qtimetadatafield"); + $a_xml_writer->xmlElement("fieldlabel", NULL, "follow_qst_answer_fixation"); + $a_xml_writer->xmlElement("fieldentry", NULL, (int)$this->isFollowupQuestionAnswerFixationEnabled()); + $a_xml_writer->xmlEndTag("qtimetadatafield"); + // instant response answer freezing $a_xml_writer->xmlStartTag("qtimetadatafield"); $a_xml_writer->xmlElement("fieldlabel", NULL, "instant_feedback_answer_fixation"); @@ -7351,6 +7369,7 @@ public function cloneObject($a_target_id,$a_copy_id = 0, $a_omit_tree = false) $newObj->setCharSelectorDefinition($this->getCharSelectorDefinition()); $newObj->setSkillServiceEnabled($this->isSkillServiceEnabled()); $newObj->setResultFilterTaxIds($this->getResultFilterTaxIds()); + $newObj->setFollowupQuestionAnswerFixationEnabled($this->isFollowupQuestionAnswerFixationEnabled()); $newObj->setInstantFeedbackAnswerFixationEnabled($this->isInstantFeedbackAnswerFixationEnabled()); $newObj->setForceInstantFeedbackEnabled($this->isForceInstantFeedbackEnabled()); $newObj->setAutosave($this->getAutosave()); @@ -9974,6 +9993,7 @@ function addDefaults($a_name) 'show_grading_status' => (int)$this->isShowGradingStatusEnabled(), 'show_grading_mark' => (int)$this->isShowGradingMarkEnabled(), + 'follow_qst_answer_fixation' => $this->isFollowupQuestionAnswerFixationEnabled(), 'inst_fb_answer_fixation' => $this->isInstantFeedbackAnswerFixationEnabled(), 'force_inst_fb' => $this->isForceInstantFeedbackEnabled(), 'redirection_mode' => $this->getRedirectionMode(), @@ -10137,6 +10157,7 @@ public function applyDefaults($test_defaults) $this->setShowGradingStatusEnabled((bool)$testsettings['show_grading_status']); $this->setShowGradingMarkEnabled((bool)$testsettings['show_grading_mark']); + $this->setFollowupQuestionAnswerFixationEnabled($testsettings['follow_qst_answer_fixation']); $this->setInstantFeedbackAnswerFixationEnabled($testsettings['inst_fb_answer_fixation']); $this->setForceInstantFeedbackEnabled($testsettings['force_inst_fb']); $this->setRedirectionMode($testsettings['redirection_mode']); @@ -10994,8 +11015,28 @@ public function moveQuestionAfterOLD($previous_question_id, $new_question_id) { ); } } + + public function isAnyInstantFeedbackOptionEnabled() + { + return ( + $this->getSpecificAnswerFeedback() || $this->getGenericAnswerFeedback() || + $this->getAnswerFeedbackPoints() || $this->getInstantFeedbackSolution() + ); + } + + public function getInstantFeedbackOptionsAsArray() + { + $values = array(); + + if( $this->getSpecificAnswerFeedback() ) $values[] = 'instant_feedback_specific'; + if( $this->getGenericAnswerFeedback() ) $values[] = 'instant_feedback_generic'; + if( $this->getAnswerFeedbackPoints() ) $values[] = 'instant_feedback_points'; + if( $this->getInstantFeedbackSolution() ) $values[] = 'instant_feedback_solution'; + + return $values; + } - public function setScoringFeedbackOptionsByArray($options) + public function setInstantFeedbackOptionsByArray($options) { if (is_array($options)) { @@ -12285,6 +12326,16 @@ public function isShowGradingMarkEnabled() { return $this->showGradingMarkEnabled; } + + public function setFollowupQuestionAnswerFixationEnabled($followupQuestionAnswerFixationEnabled) + { + $this->followupQuestionAnswerFixationEnabled = $followupQuestionAnswerFixationEnabled; + } + + public function isFollowupQuestionAnswerFixationEnabled() + { + return $this->followupQuestionAnswerFixationEnabled; + } public function setInstantFeedbackAnswerFixationEnabled($instantFeedbackAnswerFixationEnabled) { diff --git a/Modules/Test/classes/class.ilObjTestGUI.php b/Modules/Test/classes/class.ilObjTestGUI.php index d60d83e4ce4a..a2847475d3a8 100755 --- a/Modules/Test/classes/class.ilObjTestGUI.php +++ b/Modules/Test/classes/class.ilObjTestGUI.php @@ -3526,7 +3526,7 @@ function createQuestionpoolTargetObject($cmd) } // begin-patch lok - public function applyTemplate($templateData, $object) + public function applyTemplate($templateData, ilObjTest $object) // end-patch lok { // map formFieldName => setterName @@ -3558,7 +3558,9 @@ public function applyTemplate($templateData, $object) 'autosave' => null, // handled specially in loop below 'chb_shuffle_questions' => 'setShuffleQuestions', 'offer_hints' => 'setOfferingQuestionHintsEnabled', - 'instant_feedback' => 'setScoringFeedbackOptionsByArray', + 'instant_feedback_contents' => 'setInstantFeedbackOptionsByArray', + 'instant_feedback_trigger' => 'setForceInstantFeedbackEnabled', + 'answer_fixation_handling' => null, // handled specially in loop below 'obligations_enabled' => 'setObligationsEnabled', // test sequence properties @@ -3648,6 +3650,32 @@ public function applyTemplate($templateData, $object) $object->setRedirectionMode(REDIRECT_NONE); $object->setRedirectionUrl(''); } + break; + + case 'answer_fixation_handling': + switch($templateData[$field]['value']) + { + case ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_NONE: + $object->setInstantFeedbackAnswerFixationEnabled(false); + $object->setFollowupQuestionAnswerFixationEnabled(false); + break; + + case ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_INSTANT_FEEDBACK: + $object->setInstantFeedbackAnswerFixationEnabled(true); + $object->setFollowupQuestionAnswerFixationEnabled(false); + break; + + case ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION: + $object->setInstantFeedbackAnswerFixationEnabled(false); + $object->setFollowupQuestionAnswerFixationEnabled(true); + break; + + case ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_IFB_OR_FUQST: + $object->setInstantFeedbackAnswerFixationEnabled(true); + $object->setFollowupQuestionAnswerFixationEnabled(true); + break; + } + break; } } } diff --git a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php index afeaf17ad463..367826ead9ea 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php @@ -28,10 +28,13 @@ class ilObjTestSettingsGeneralGUI extends ilTestSettingsGUI const CMD_SHOW_RESET_TPL_CONFIRM = 'showResetTemplateConfirmation'; const CMD_CONFIRMED_RESET_TPL = 'confirmedResetTemplate'; - const INST_FB_HANDLING_OPT_NONE = 'none'; - const INST_FB_HANDLING_OPT_FREEZE = 'freeze'; - const INST_FB_HANDLING_OPT_FORCE = 'force'; - const INST_FB_HANDLING_OPT_FORCE_AND_FREEZE = 'force_freeze'; + const ANSWER_FIXATION_NONE = 'none'; + const ANSWER_FIXATION_ON_INSTANT_FEEDBACK = 'instant_feedback'; + const ANSWER_FIXATION_ON_FOLLOWUP_QUESTION = 'followup_question'; + const ANSWER_FIXATION_ON_IFB_OR_FUQST = 'ifb_or_fuqst'; + + const INSTANT_FEEDBACK_TRIGGER_MANUAL = 0; + const INSTANT_FEEDBACK_TRIGGER_FORCED = 1; /** @var ilCtrl $ctrl */ protected $ctrl = null; @@ -1109,7 +1112,8 @@ private function addQuestionBehaviourProperties(ilPropertyFormGUI $form) { $fields = array( 'title_output', 'autosave', 'chb_shuffle_questions', 'chb_shuffle_questions', - 'offer_hints', 'instant_feedback', 'obligations_enabled', + 'offer_hints', 'instant_feedback_contents', 'instant_feedback_trigger', + 'answer_fixation_handling', 'obligations_enabled' ); if( $this->isSectionHeaderRequired($fields) || $this->isCharSelectorPropertyRequired() ) @@ -1151,83 +1155,83 @@ private function addQuestionBehaviourProperties(ilPropertyFormGUI $form) $checkBoxOfferHints = new ilCheckboxInputGUI($this->lng->txt('tst_setting_offer_hints_label'), 'offer_hints'); $checkBoxOfferHints->setChecked($this->testOBJ->isOfferingQuestionHintsEnabled()); $checkBoxOfferHints->setInfo($this->lng->txt('tst_setting_offer_hints_info')); - if( $this->testOBJ->participantDataExist() ) - { - $checkBoxOfferHints->setDisabled(true); - } $form->addItem($checkBoxOfferHints); // instant feedback - $instant_feedback = new ilCheckboxGroupInputGUI($this->lng->txt('tst_instant_feedback'), 'instant_feedback'); - $instant_feedback->addOption(new ilCheckboxOption( + $instant_feedback_enabled = new ilCheckboxInputGUI($this->lng->txt('tst_instant_feedback'), 'instant_feedback_enabled'); + $instant_feedback_enabled->setInfo($this->lng->txt('tst_instant_feedback_desc')); + $instant_feedback_enabled->setChecked($this->testOBJ->isAnyInstantFeedbackOptionEnabled()); + $form->addItem($instant_feedback_enabled); + $instant_feedback_contents = new ilCheckboxGroupInputGUI($this->lng->txt('tst_instant_feedback_contents'), 'instant_feedback_contents'); + $instant_feedback_contents->setRequired(true); + $instant_feedback_contents->addOption(new ilCheckboxOption( $this->lng->txt('tst_instant_feedback_results'), 'instant_feedback_points', $this->lng->txt('tst_instant_feedback_results_desc') )); - $instant_feedback->addOption(new ilCheckboxOption( + $instant_feedback_contents->addOption(new ilCheckboxOption( $this->lng->txt('tst_instant_feedback_answer_generic'), 'instant_feedback_generic', $this->lng->txt('tst_instant_feedback_answer_generic_desc') )); - $instant_feedback->addOption(new ilCheckboxOption( + $instant_feedback_contents->addOption(new ilCheckboxOption( $this->lng->txt('tst_instant_feedback_answer_specific'), 'instant_feedback_specific', $this->lng->txt('tst_instant_feedback_answer_specific_desc') )); - $instant_feedback->addOption(new ilCheckboxOption( + $instant_feedback_contents->addOption(new ilCheckboxOption( $this->lng->txt('tst_instant_feedback_solution'), 'instant_feedback_solution', $this->lng->txt('tst_instant_feedback_solution_desc') )); - $values = array(); - if ($this->testOBJ->getSpecificAnswerFeedback()) array_push($values, 'instant_feedback_specific'); - if ($this->testOBJ->getGenericAnswerFeedback()) array_push($values, 'instant_feedback_generic'); - if ($this->testOBJ->getAnswerFeedbackPoints()) array_push($values, 'instant_feedback_points'); - if ($this->testOBJ->getInstantFeedbackSolution()) array_push($values, 'instant_feedback_solution'); - $instant_feedback->setValue($values); - $form->addItem($instant_feedback); + $instant_feedback_contents->setValue($this->testOBJ->getInstantFeedbackOptionsAsArray()); + $instant_feedback_enabled->addSubItem($instant_feedback_contents); + $instant_feedback_trigger = new ilRadioGroupInputGUI( + $this->lng->txt('tst_instant_feedback_trigger'), 'instant_feedback_trigger' + ); + $ifbTriggerOpt = new ilRadioOption( + $this->lng->txt('tst_instant_feedback_trigger_manual'), self::INSTANT_FEEDBACK_TRIGGER_MANUAL + ); + $ifbTriggerOpt->setInfo($this->lng->txt('tst_instant_feedback_trigger_manual_desc')); + $instant_feedback_trigger->addOption($ifbTriggerOpt); + $ifbTriggerOpt = new ilRadioOption( + $this->lng->txt('tst_instant_feedback_trigger_forced'), self::INSTANT_FEEDBACK_TRIGGER_FORCED + ); + $ifbTriggerOpt->setInfo($this->lng->txt('tst_instant_feedback_trigger_forced_desc')); + $instant_feedback_trigger->addOption($ifbTriggerOpt); + $instant_feedback_trigger->setValue($this->testOBJ->isForceInstantFeedbackEnabled()); + $instant_feedback_enabled->addSubItem($instant_feedback_trigger); - $radioGroup = new ilRadioGroupInputGUI( - $this->lng->txt('tst_instant_feedback_handling'), 'instant_feedback_handling' + $answerFixation = new ilRadioGroupInputGUI( + $this->lng->txt('tst_answer_fixation_handling'), 'answer_fixation_handling' ); - if( $this->testOBJ->participantDataExist() ) - { - $radioGroup->setDisabled(true); - } $radioOption = new ilRadioOption( - $this->lng->txt('tst_instant_feedback_handling_none'), - self::INST_FB_HANDLING_OPT_NONE + $this->lng->txt('tst_answer_fixation_none'), + self::ANSWER_FIXATION_NONE ); - $radioOption->setInfo($this->lng->txt('tst_instant_feedback_handling_none_desc')); - $radioGroup->addOption($radioOption); + $radioOption->setInfo($this->lng->txt('tst_answer_fixation_none_desc')); + $answerFixation->addOption($radioOption); $radioOption = new ilRadioOption( - $this->lng->txt('tst_instant_feedback_handling_freeze'), - self::INST_FB_HANDLING_OPT_FREEZE + $this->lng->txt('tst_answer_fixation_on_instant_feedback'), + self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK ); - $radioOption->setInfo($this->lng->txt('tst_instant_feedback_handling_freeze_desc')); - $radioGroup->addOption($radioOption); + $radioOption->setInfo($this->lng->txt('tst_answer_fixation_on_instant_feedback_desc')); + $answerFixation->addOption($radioOption); $radioOption = new ilRadioOption( - $this->lng->txt('tst_instant_feedback_handling_force_and_freeze'), - self::INST_FB_HANDLING_OPT_FORCE_AND_FREEZE + $this->lng->txt('tst_answer_fixation_on_followup_question'), + self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION ); - $radioOption->setInfo($this->lng->txt('tst_instant_feedback_handling_force_and_freeze_desc')); - $radioGroup->addOption($radioOption); + $radioOption->setInfo($this->lng->txt('tst_answer_fixation_on_followup_question_desc')); + $answerFixation->addOption($radioOption); $radioOption = new ilRadioOption( - $this->lng->txt('tst_instant_feedback_handling_force'), - self::INST_FB_HANDLING_OPT_FORCE + $this->lng->txt('tst_answer_fixation_on_instantfb_or_followupqst'), + self::ANSWER_FIXATION_ON_IFB_OR_FUQST ); - $radioOption->setInfo($this->lng->txt('tst_instant_feedback_handling_force_desc')); - $radioGroup->addOption($radioOption); - $radioGroup->setValue($this->getInstFbHandlingValue( - $this->testOBJ->isInstantFeedbackAnswerFixationEnabled(), - $this->testOBJ->isForceInstantFeedbackEnabled() - )); - $form->addItem($radioGroup); + $radioOption->setInfo($this->lng->txt('tst_answer_fixation_on_instantfb_or_followupqst_desc')); + $answerFixation->addOption($radioOption); + $answerFixation->setValue($this->getAnswerFixationSettingsAsFormValue()); + $form->addItem($answerFixation); // enable obligations $checkBoxEnableObligations = new ilCheckboxInputGUI($this->lng->txt('tst_setting_enable_obligations_label'), 'obligations_enabled'); $checkBoxEnableObligations->setChecked($this->testOBJ->areObligationsEnabled()); $checkBoxEnableObligations->setInfo($this->lng->txt('tst_setting_enable_obligations_info')); - if( $this->testOBJ->participantDataExist() ) - { - $checkBoxEnableObligations->setDisabled(true); - } $form->addItem($checkBoxEnableObligations); // selector for unicode characters @@ -1240,6 +1244,17 @@ private function addQuestionBehaviourProperties(ilPropertyFormGUI $form) $char_selector->addFormProperties($form); $char_selector->setFormValues($form); } + + if( $this->testOBJ->participantDataExist() ) + { + $checkBoxOfferHints->setDisabled(true); + $instant_feedback_enabled->setDisabled(true); + $instant_feedback_trigger->setDisabled(true); + $instant_feedback_contents->setDisabled(true); + $answerFixation->setDisabled(true); + $checkBoxEnableObligations->setDisabled(true); + } + } /** @@ -1268,14 +1283,33 @@ private function saveQuestionBehaviourProperties(ilPropertyFormGUI $form) $this->testOBJ->setOfferingQuestionHintsEnabled($form->getItemByPostVar('offer_hints')->getChecked()); } - if ($this->formPropertyExists($form, 'instant_feedback')) + if (!$this->testOBJ->participantDataExist() && $this->formPropertyExists($form, 'instant_feedback_enabled')) { - $this->testOBJ->setScoringFeedbackOptionsByArray($form->getItemByPostVar('instant_feedback')->getValue()); + if( $form->getItemByPostVar('instant_feedback_enabled')->getChecked() ) + { + if( $this->formPropertyExists($form, 'instant_feedback_content') ) + { + $this->testOBJ->setInstantFeedbackOptionsByArray( + $form->getItemByPostVar('instant_feedback_content')->getValue() + ); + } + if( $this->formPropertyExists($form, 'instant_feedback_trigger') ) + { + $this->testOBJ->setForceInstantFeedbackEnabled( + (bool)$form->getItemByPostVar('instant_feedback_trigger')->getValue() + ); + } + } + else + { + $this->testOBJ->setInstantFeedbackOptionsByArray( array() ); + $this->testOBJ->setForceInstantFeedbackEnabled(false); + } } - if (!$this->testOBJ->participantDataExist() && $this->formPropertyExists($form, 'instant_feedback_handling')) + if (!$this->testOBJ->participantDataExist() && $this->formPropertyExists($form, 'answer_fixation_handling')) { - $this->saveInstFbHandlingSettings($form->getItemByPostVar('instant_feedback_handling')->getValue()); + $this->setAnswerFixationSettingsByFormValue($form->getItemByPostVar('answer_fixation_handling')->getValue()); } if (!$this->testOBJ->participantDataExist() && $this->formPropertyExists($form, 'obligations_enabled')) @@ -1530,40 +1564,46 @@ private function saveTestFinishProperties(ilPropertyFormGUI $form) } } - private function saveInstFbHandlingSettings($instantFeedbackHandlingValue) + protected function setAnswerFixationSettingsByFormValue($formValue) { - switch($instantFeedbackHandlingValue) + switch($formValue) { - case self::INST_FB_HANDLING_OPT_NONE: + case self::ANSWER_FIXATION_NONE: $this->testOBJ->setInstantFeedbackAnswerFixationEnabled(false); - $this->testOBJ->setForceInstantFeedbackEnabled(false); + $this->testOBJ->setFollowupQuestionAnswerFixationEnabled(false); break; - - case self::INST_FB_HANDLING_OPT_FREEZE: + case self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK: $this->testOBJ->setInstantFeedbackAnswerFixationEnabled(true); - $this->testOBJ->setForceInstantFeedbackEnabled(false); + $this->testOBJ->setFollowupQuestionAnswerFixationEnabled(false); break; - - case self::INST_FB_HANDLING_OPT_FORCE: + case self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION: $this->testOBJ->setInstantFeedbackAnswerFixationEnabled(false); - $this->testOBJ->setForceInstantFeedbackEnabled(true); + $this->testOBJ->setFollowupQuestionAnswerFixationEnabled(true); break; - - case self::INST_FB_HANDLING_OPT_FORCE_AND_FREEZE: + case self::ANSWER_FIXATION_ON_IFB_OR_FUQST: $this->testOBJ->setInstantFeedbackAnswerFixationEnabled(true); - $this->testOBJ->setForceInstantFeedbackEnabled(true); + $this->testOBJ->setFollowupQuestionAnswerFixationEnabled(true); break; } } - private function getInstFbHandlingValue($freezeAnswersEnabled, $forceInstFbEnabled) + protected function getAnswerFixationSettingsAsFormValue() { - switch( true ) + if( $this->testOBJ->isInstantFeedbackAnswerFixationEnabled() && $this->testOBJ->isFollowupQuestionAnswerFixationEnabled() ) { - case !$freezeAnswersEnabled && !$forceInstFbEnabled: return self::INST_FB_HANDLING_OPT_NONE; - case $freezeAnswersEnabled && !$forceInstFbEnabled: return self::INST_FB_HANDLING_OPT_FREEZE; - case !$freezeAnswersEnabled && $forceInstFbEnabled: return self::INST_FB_HANDLING_OPT_FORCE; - case $freezeAnswersEnabled && $forceInstFbEnabled: return self::INST_FB_HANDLING_OPT_FORCE_AND_FREEZE; + return self::ANSWER_FIXATION_ON_IFB_OR_FUQST; } + + if( $this->testOBJ->isFollowupQuestionAnswerFixationEnabled() ) + { + return self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION; + } + + if( $this->testOBJ->isInstantFeedbackAnswerFixationEnabled() ) + { + return self::ANSWER_FIXATION_ON_INSTANT_FEEDBACK; + } + + return self::ANSWER_FIXATION_NONE; } } diff --git a/Modules/Test/classes/class.ilTestSettingsTemplateConfig.php b/Modules/Test/classes/class.ilTestSettingsTemplateConfig.php index f30f3dc12141..1e45a3708074 100644 --- a/Modules/Test/classes/class.ilTestSettingsTemplateConfig.php +++ b/Modules/Test/classes/class.ilTestSettingsTemplateConfig.php @@ -226,7 +226,7 @@ private function addQuestionBehaviourProperties() ); $this->addSetting( - "instant_feedback", + "instant_feedback_contents", ilSettingsTemplateConfig::CHECKBOX, $this->lng->txt("tst_instant_feedback"), true, @@ -235,22 +235,33 @@ private function addQuestionBehaviourProperties() 'instant_feedback_points' => $this->lng->txt("tst_instant_feedback_results"), 'instant_feedback_generic' => $this->lng->txt("tst_instant_feedback_answer_generic"), 'instant_feedback_specific' => $this->lng->txt("tst_instant_feedback_answer_specific"), - 'instant_feedback_solution' => $this->lng->txt("tst_instant_feedback_solution"), - 'force_instant_feedback' => $this->lng->txt("tst_instant_feedback_forced") + 'instant_feedback_solution' => $this->lng->txt("tst_instant_feedback_solution") ) ); $this->addSetting( - 'instant_feedback_handling', + "instant_feedback_trigger", ilSettingsTemplateConfig::SELECT, - $this->lng->txt('tst_instant_feedback_handling'), + $this->lng->txt("tst_instant_feedback_trigger"), true, - ilObjTestSettingsGeneralGUI::INST_FB_HANDLING_OPT_NONE, + 0, + array( + ilObjTestSettingsGeneralGUI::INSTANT_FEEDBACK_TRIGGER_MANUAL => $this->lng->txt("tst_instant_feedback_trigger_manual"), + ilObjTestSettingsGeneralGUI::INSTANT_FEEDBACK_TRIGGER_FORCED => $this->lng->txt("tst_instant_feedback_trigger_forced") + ) + ); + + $this->addSetting( + 'answer_fixation_handling', + ilSettingsTemplateConfig::SELECT, + $this->lng->txt('tst_answer_fixation_handling'), + true, + 0, array( - ilObjTestSettingsGeneralGUI::INST_FB_HANDLING_OPT_NONE => $this->lng->txt('tst_instant_feedback_handling_none'), - ilObjTestSettingsGeneralGUI::INST_FB_HANDLING_OPT_FREEZE => $this->lng->txt('tst_instant_feedback_handling_freeze'), - ilObjTestSettingsGeneralGUI::INST_FB_HANDLING_OPT_FORCE_AND_FREEZE => $this->lng->txt('tst_instant_feedback_handling_force_and_freeze'), - ilObjTestSettingsGeneralGUI::INST_FB_HANDLING_OPT_FORCE => $this->lng->txt('tst_instant_feedback_handling_force'), + ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_NONE => $this->lng->txt('tst_answer_fixation_none'), + ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_INSTANT_FEEDBACK => $this->lng->txt('tst_answer_fixation_on_instant_feedback'), + ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION => $this->lng->txt('tst_answer_fixation_on_followup_question'), + ilObjTestSettingsGeneralGUI::ANSWER_FIXATION_ON_IFB_OR_FUQST => $this->lng->txt('tst_answer_fixation_on_instantfb_or_followupqst') ) ); diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 568fd41884f1..d02e492e0346 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -1016,15 +1016,20 @@ assessment#:#search_results#:#Suchergebnisse assessment#:#search_roles#:#nach Rollen assessment#:#search_term#:#Suchbegriff assessment#:#search_users#:#nach Benutzern -assessment#:#tst_instant_feedback_handling#:#Antworten nach Rückmeldung festschreiben -assessment#:#tst_instant_feedback_handling_none#:#Antworten nicht festschreiben -assessment#:#tst_instant_feedback_handling_none_desc#:#Teilnehmer können über einen Button "Rückmeldung anfordern" nach Bedarf eine Rückmeldung anzeigen lassen. Rückmeldungen werden in der Option "Direkte Rückmeldung" konfiguriert. Danach können sie Ihre Antwort im Rahmen der erlaubten Testdurchläufe ändern. -assessment#:#tst_instant_feedback_handling_freeze#:#Antworten festschreiben, wenn Rückmeldung angefordert wird -assessment#:#tst_instant_feedback_handling_freeze_desc#:#Teilnehmer können über einen Button "Antwort festschreiben und prüfen" ihre Antwort festschreiben und eine Rückmeldung anzeigen lassen. Danach kann die Antwort nicht mehr geändert werden. -assessment#:#tst_instant_feedback_handling_force_and_freeze#:#Automatische Rückmeldung und Antwort festschreiben -assessment#:#tst_instant_feedback_handling_force_and_freeze_desc#:#Klicken Teilnehmer auf "Antwort speichern und Rückmeldung anfordern oder speichern Sie Ihre Antwort beim Wechsel zu einer anderen Frage, so können Sie Ihre Antworten danach nicht mehr ändern. ILIAS präsentiert sofort die in der Option "Direkte Rückmeldung" konfigurierte Rückmeldung. -assessment#:#tst_instant_feedback_handling_force#:#Automatische Rückmeldung, aber Antwort nicht festschreiben -assessment#:#tst_instant_feedback_handling_force_desc#:#Klicken Teilnehmer auf "Rückmeldung anfordern", oder speichern sie Ihre Antwort beim Wechsel zu einer anderen Frage, so präsentiert ILIAS sofort die in der Option "Direkte Rückmeldung" konfigurierte Rückmeldung. Teilnehmer können danach ihre Antworten im Rahmen der erlaubten Testdurchläufe ändern. +assessment#:#tst_answer_fixation_handling#:#Teilnehmerantworten +assessment#:#tst_answer_fixation_none#:#Antworten während des Testdurchlaufs nicht festschreiben +assessment#:#tst_answer_fixation_none_desc#:#Solange ein Testdurchlauf nicht beendet ist, können Teilnehmer ihre Antworten beliebig verändern. +assessment#:#tst_answer_fixation_on_instant_feedback#:#Antworten bei Anzeige der Rückmeldung festschreiben +assessment#:#tst_answer_fixation_on_instant_feedback_desc#:#Wenn die Rückmeldung zu einer Frage gezeigt wird, können Teilnehmer ihre Antworten nicht mehr verändern. +assessment#:#tst_answer_fixation_on_followup_question#:#Antworten bei Anzeige der Folgefrage festschreiben +assessment#:#tst_answer_fixation_on_followup_question_desc#:#Nach dem Anzeigen der Folgefrage können Teilnehmer die Antwort auf die vorherige Frage nicht mehr verändern. +assessment#:#tst_answer_fixation_on_instantfb_or_followupqst#:#Antworten mit der Anzeige von Rückmeldungen oder der Folgefrage festschreiben +assessment#:#tst_answer_fixation_on_instantfb_or_followupqst_desc#:#Teilnehmer können Antworten nicht mehr verändern, nachdem die Rückmeldung zur Frage oder die Folgefrage gezeigt wurde. +assessment#:#tst_instant_feedback_trigger#:#Auslöser der Rückmeldung +assessment#:#tst_instant_feedback_trigger_manual#:#Teilnehmer können die Rückmeldung selbst auslösen +assessment#:#tst_instant_feedback_trigger_manual_desc#:#Die Rückmeldung steht Teilnehmern zur Verfügung, wird aber erst gezeigt, wenn der Teilnehmer diese auslöst. +assessment#:#tst_instant_feedback_trigger_forced#:#Die Rückmeldung wird bei Beantwortung von Fragen gezeigt +assessment#:#tst_instant_feedback_trigger_forced_desc#:#Durch Beantwortung einer Frage wird die Rückmeldung automatisch ausgelöst. assessment#:#select_gap#:#Auswahl-Lücke assessment#:#select_max_one_item#:#Bitte wählen Sie nur ein Objekt aus. assessment#:#select_one_user#:#Bitte wählen Sie mindestens einen Benutzer aus. @@ -1160,6 +1165,8 @@ assessment#:#tst_insert_missing_question#:#Bitte wählen Sie mindestens eine Fra assessment#:#tst_insert_questions#:#Sind Sie sicher, dass Sie die folgende(n) Frage(n) dem Test hinzufügen wollen? assessment#:#tst_insert_questions_and_results#:#Dieser Test wurde bereits von %s Benutzern durchgeführt. Das Hinzufügen von Fragen wird dazu führen, dass alle bestehenden Testergebnisse dieses Tests gelöscht werden! Sind Sie sicher, dass Sie die folgende(n) Frage(n) hinzufügen wollen? assessment#:#tst_instant_feedback#:#Direkte Rückmeldung +assessment#:#tst_instant_feedback_contents#:#Inhalte der Rückmeldung +assessment#:#tst_instant_feedback_desc#:#Sofern Fragen mit Rückmeldungen versehen sind, stehen diese den Teilnehmern während des Tests zur Verfügung. assessment#:#tst_instant_feedback_answer_specific#:#Unterschiedliche Rückmeldungen pro gegebener Antwort assessment#:#tst_instant_feedback_answer_specific_desc#:#Bei Anklicken des 'Rückmeldung anfordern'-Buttons zeigt ILIAS zu jeder abgegebenen Antwort die jeweils hinterlegte Rückmeldung an. Nicht alle Fragetypen unterstützen diese Art von Rückmeldungen. Die Rückmeldungen müssen bei der Frage im Reiter 'Rückmeldung' hinterlegt werden. assessment#:#tst_instant_feedback_answer_generic#:#Rückmeldung zur richtigen Lösung diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index e9b52ae9931f..f1de911c9cb0 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -904,15 +904,20 @@ assessment#:#search_results#:#Search Results assessment#:#search_roles#:#Search Roles assessment#:#search_term#:#Search Term assessment#:#search_users#:#Search Users -assessment#:#tst_instant_feedback_handling#:#Lock Answers -assessment#:#tst_instant_feedback_handling_none#:#Do Not Lock Answers -assessment#:#tst_instant_feedback_handling_none_desc#:#Participants can click 'Request Feedback' to get a feedback on their answer as configured in the option 'Instant feedback'. They may however change their answer afterwards.###fau: testNav -assessment#:#tst_instant_feedback_handling_freeze#:#Lock Answers and give Feedback###fau: testNav -assessment#:#tst_instant_feedback_handling_freeze_desc#:#Participants can click 'Save Answer, Freeze and Check' to lock their answer and get feedback as configured in the option 'Instant feedback'. Afterwards they can no longer change their answer.###fau: testNav -assessment#:#tst_instant_feedback_handling_force_and_freeze#:#Lock Answers and Force Feedback -assessment#:#tst_instant_feedback_handling_force_and_freeze_desc#:#After participants click 'Save Answer, Freeze and Check' or save the answer when they navigate away from the question, they can no longer change their answer. Participants will be automatically presented with feedback as configured in the option 'Instant feedback'.###fau: testNav -assessment#:#tst_instant_feedback_handling_force#:#Do Not Lock Answers but Force Feedback -assessment#:#tst_instant_feedback_handling_force_desc#:#After participants click 'Request Feedback' or save the answer when they navigate away from the question, they will be automatically presented with feedback as configured in the option 'Instant feedback'. They may however change their answers afterwards.###fau: testNav +assessment#:#tst_answer_fixation_handling#:#Participant Answers +assessment#:#tst_answer_fixation_none#:#Do not Lock Participants Answers during Test Passes +assessment#:#tst_answer_fixation_none_desc#:#As long as a test pass was not finished, participants can change their answers at any time. +assessment#:#tst_answer_fixation_on_instant_feedback#:#Lock Answers with the Presentation of Feedback +assessment#:#tst_answer_fixation_on_instant_feedback_desc#:#After the feedback for a question is shown participant answers are locked, participants cannot change these answers any longer. +assessment#:#tst_answer_fixation_on_followup_question#:#Lock Answers with the Presentation of Follow-Up Questions +assessment#:#tst_answer_fixation_on_followup_question_desc#:#Showing the follow-up question will lock participant answers for the previous question, participants cannot change these answers any longer. +assessment#:#tst_answer_fixation_on_instantfb_or_followupqst#:#Lock Answers with the Presentation of Feedback or Follow-Up Questions +assessment#:#tst_answer_fixation_on_instantfb_or_followupqst_desc#:#Participant Answers for a question will be locked either with the presentation of the questions's feedback or when the follow-up question is shown. +assessment#:#tst_instant_feedback_trigger#:#Feedback Trigger +assessment#:#tst_instant_feedback_trigger_manual#:#Participants can Trigger the Feedback Manually +assessment#:#tst_instant_feedback_trigger_manual_desc#:#The feedback is availble but will only be shown when a participant triggers the presentation manually. +assessment#:#tst_instant_feedback_trigger_forced#:#The Feedback is Triggered by Answering Questions +assessment#:#tst_instant_feedback_trigger_forced_desc#:#The presentation of feedback is forced to all participants with answering a question. assessment#:#select_gap#:#Select Gap assessment#:#select_max_one_item#:#Please select one item only assessment#:#select_one_user#:#Please select at least one user. @@ -1033,6 +1038,8 @@ assessment#:#tst_instant_feedback_results_desc#:#On clicking the ‘Check’-but assessment#:#tst_instant_feedback_solution#:#Show Best Possible Answer assessment#:#tst_instant_feedback_solution_desc#:#On clicking the ‘Check’-button ILIAS will display the best possible answer to the question. assessment#:#tst_instant_feedback#:#Instant Feedback +assessment#:#tst_instant_feedback_desc#:#When questions are configured with feedback it is provided to participants during the test pass. +assessment#:#tst_instant_feedback_contents#:#Included Contents assessment#:#tst_introduction#:#Introductory Message assessment#:#tst_introduction_desc#:#Shows an introductory message on the tab ‘Info’ of the test. This text is accessible already before the test can be started. assessment#:#tst_invited_nobody#:#No users, groups or roles have been added as fixed test participants From f15c42ad84a5680b8ccdd558e8ef2b1209a64ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Thu, 14 Jun 2018 10:28:11 +0200 Subject: [PATCH 006/166] changed existing settings conflict handling to alert instead of reset settings, added conflict handling for postpone and shuffle vs. answer freezing --- .../class.ilObjTestSettingsGeneralGUI.php | 68 ++++++++++++------- lang/ilias_de.lang | 5 +- lang/ilias_en.lang | 5 +- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php index 367826ead9ea..eb7ec1980d7c 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php @@ -236,6 +236,50 @@ private function saveFormCmd($isConfirmedSave = false) ilUtil::sendFailure($this->lng->txt('form_input_not_valid')); return $this->showFormCmd($form); } + + // avoid settings conflict "obligate questions" and "freeze answer" + + $obligationsSetting = $form->getItemByPostVar('obligations_enabled'); + $answerFixationSetting = $form->getItemByPostVar('answer_fixation_handling'); + + if( $obligationsSetting->getChecked() && $answerFixationSetting->getValue() != self::ANSWER_FIXATION_NONE ) + { + $obligationsSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + $answerFixationSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + + ilUtil::sendFailure($this->lng->txt('tst_settings_conflict_message')); + return $this->showFormCmd($form); + } + + // avoid settings conflict "freeze answer on followup question" and "question postponing" + + $postponeSetting = $form->getItemByPostVar('postpone'); + $answerFixationSetting = $form->getItemByPostVar('answer_fixation_handling'); + $conflictModes = array(self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION, self::ANSWER_FIXATION_ON_IFB_OR_FUQST); + + if( $postponeSetting->getValue() && in_array($answerFixationSetting->getValue(), $conflictModes) ) + { + $postponeSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + $answerFixationSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + + ilUtil::sendFailure($this->lng->txt('tst_settings_conflict_message')); + return $this->showFormCmd($form); + } + + // avoid settings conflict "freeze answer on followup question" and "question shuffling" + + $shuffleSetting = $form->getItemByPostVar('chb_shuffle_questions'); + $answerFixationSetting = $form->getItemByPostVar('answer_fixation_handling'); + $conflictModes = array(self::ANSWER_FIXATION_ON_FOLLOWUP_QUESTION, self::ANSWER_FIXATION_ON_IFB_OR_FUQST); + + if( $shuffleSetting->getChecked() && in_array($answerFixationSetting->getValue(), $conflictModes) ) + { + $shuffleSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + $answerFixationSetting->setAlert($this->lng->txt('tst_conflicting_setting')); + + ilUtil::sendFailure($this->lng->txt('tst_settings_conflict_message')); + return $this->showFormCmd($form); + } $infoMsg = array(); @@ -295,35 +339,13 @@ private function saveFormCmd($isConfirmedSave = false) $newQuestionSetType = $oldQuestionSetType; } - // adjust settiue to desired question set type + // adjust settings due to chosen question set type if( $newQuestionSetType != ilObjTest::QUESTION_SET_TYPE_FIXED ) { $form->getItemByPostVar('chb_use_previous_answers')->setChecked(false); } - // avoid settings conflict "obligate questions" and "freeze answer" - - if( $form->getItemByPostVar('obligations_enabled')->getChecked() ) - { - switch( $form->getItemByPostVar('instant_feedback_handling')->getValue() ) - { - case self::INST_FB_HANDLING_OPT_FREEZE: - - $form->getItemByPostVar('instant_feedback_handling')->setValue(self::INST_FB_HANDLING_OPT_NONE); - $infoMsg[] = $this->lng->txt("tst_conflict_fbh_oblig_quest"); - $infoMsg[] = $this->lng->txt("tst_conflict_reset_non_fbh"); - break; - - case self::INST_FB_HANDLING_OPT_FORCE_AND_FREEZE: - - $form->getItemByPostVar('instant_feedback_handling')->setValue(self::INST_FB_HANDLING_OPT_FORCE); - $infoMsg[] = $this->lng->txt("tst_conflict_fbh_oblig_quest"); - $infoMsg[] = $this->lng->txt("tst_conflict_reset_fbh_force"); - break; - } - } - // perform saving the form data $this->performSaveForm($form); diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index d02e492e0346..7593a8786ccc 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -576,9 +576,8 @@ assessment#:#edit_test_questions#:#Listenansicht assessment#:#element_height#:#Minimale Höhe assessment#:#element_height_info#:#Dieser Wert legt die minimale Höhe von Termen und Definitionen/Bildern während der Testausgabe fest. assessment#:#end_tag#:#Endmarkierung -assessment#:#tst_conflict_fbh_oblig_quest#:#Die Kombination der Verwendung von verpflichtenden Fragen und dem Festschreiben von Antworten ist nicht erlaubt. -assessment#:#tst_conflict_reset_non_fbh#:#Die Einstellung "Antworten nach Rückmeldung festschreiben" wurde auf die Option "Antworten nicht festschreiben" angepasst. -assessment#:#tst_conflict_reset_fbh_force#:#Die Einstellung "Antworten nach Rückmeldung festschreiben" wurde auf die Option "Automatische Rückmeldung, aber Antwort nicht festschreiben" angepasst. +assessment#:#tst_settings_conflict_message#:#Es wurden Kombinationen von Einstellungen gewählt, die nicht erlaubt sind. +assessment#:#tst_conflicting_setting#:#Diese Einstellung steht in Konflikt mit einer anderen Einstellung. assessment#:#enlarge#:#vergrößern assessment#:#enter_anonymous_code#:#Zugangsschlüssel für anonymen Zugang assessment#:#enter_enough_positive_points#:#Die maximale Punktezahl für die Frage muss größer als Null sein! Bitte geben Sie ausreichend viele nicht-negative Punkte für die Antworten ein. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index f1de911c9cb0..959b3107b43f 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -476,9 +476,8 @@ assessment#:#edit_test_questions#:#List View assessment#:#element_height_info#:#This is the minimum height in pixels for term and definition/picture items during the test output. assessment#:#element_height#:#Minimum Height assessment#:#end_tag#:#End Tag -assessment#:#tst_conflict_fbh_oblig_quest#:#The combination of using compulsory questions and locked answers is not allowed. -assessment#:#tst_conflict_reset_non_fbh#:#The setting "Lock Answers" was adjusted to "Do Not Lock Answers". -assessment#:#tst_conflict_reset_fbh_force#:#The setting "Lock Answers" was adjusted to "Do Not Lock Answers but Force Feedback". +assessment#:#tst_settings_conflict_message#:#The current configuration uses settings combinations that are not allowed. +assessment#:#tst_conflicting_setting#:#This setting does conflict with another setting. assessment#:#enlarge#:#Enlarge assessment#:#enter_anonymous_code#:#Enter Anonymous Access Code assessment#:#enter_enough_positive_points#:#The maximum available points must be greater than 0! Please enter sufficient positive points for your answers. From 72a97254eb3cd38f9f2d0de595a9a37c411b2061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Thu, 14 Jun 2018 19:21:53 +0200 Subject: [PATCH 007/166] populated followup qst fixation flag to player edit control js engine, added modals for changed/unchanged answer intended to be shown on nav next, language variables for new modals, generic do not show again langvar for all confirmation modal checkbox labels --- .../classes/class.ilTestPlayerAbstractGUI.php | 84 ++++++- .../class.ilTestPlayerConfirmationModal.php | 229 ++++++++++++++++++ .../js/ilTestPlayerQuestionEditControl.js | 3 +- .../default/tpl.il_as_tst_output.html | 2 + lang/ilias_de.lang | 7 +- lang/ilias_en.lang | 7 +- 6 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 Modules/Test/classes/class.ilTestPlayerConfirmationModal.php diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index 284b3518cd98..3a2dd35c5eec 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -2607,6 +2607,12 @@ protected function populateModals() $this->populateNavWhenChangedModal(); // fau. + if( $this->object->isFollowupQuestionAnswerFixationEnabled() ) + { + $this->populateNextLocksChangedModal(); + $this->populateNextLocksUnchangedModal(); + } + if( $this->object->getKioskMode() ) { $this->tpl->addJavaScript(ilUIFramework::BOWER_BOOTSTRAP_JS, true); @@ -2750,7 +2756,7 @@ protected function populateNavWhenChangedModal() $tpl->setCurrentBlock('checkbox'); $tpl->setVariable('CONFIRMATION_CHECKBOX_NAME','save_on_navigation_prevent_confirmation'); - $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL',$this->lng->txt('save_on_navigation_prevent_confirmation')); + $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL',$this->lng->txt('tst_dont_show_msg_again_in_current_session')); $tpl->parseCurrentBlock(); $modal = ilModalGUI::getInstance(); @@ -2763,7 +2769,80 @@ protected function populateNavWhenChangedModal() $this->tpl->parseCurrentBlock(); } // fau. + + protected function populateNextLocksUnchangedModal() + { + require_once 'Modules/Test/classes/class.ilTestPlayerConfirmationModal.php'; + $modal = new ilTestPlayerConfirmationModal(); + $modal->setModalId('tst_next_locks_unchanged_modal'); + $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_empty_answer_header')); + $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_empty_answer_confirm')); + + $button = $modal->buildModalButtonInstance('tst_nav_next_empty_answer_button'); + $button->setCaption('tst_proceed'); + $button->setPrimary(false); + $modal->addButton($button); + + $button = $modal->buildModalButtonInstance('tst_cancel_next_empty_answer_button'); + $button->setCaption('cancel'); + $button->setPrimary(true); + $modal->addButton($button); + + $this->tpl->setCurrentBlock('next_locks_unchanged_modal'); + $this->tpl->setVariable('NEXT_LOCKS_UNCHANGED_MODAL', $modal->getHTML()); + $this->tpl->parseCurrentBlock(); + } + + protected function populateNextLocksChangedModal() + { + if( $this->isFollowUpQuestionLocksConfirmationPrevented() ) + { + return; + } + + require_once 'Modules/Test/classes/class.ilTestPlayerConfirmationModal.php'; + $modal = new ilTestPlayerConfirmationModal(); + $modal->setModalId('tst_next_locks_changed_modal'); + + $modal->setHeaderText($this->lng->txt('tst_nav_next_locks_current_answer_header')); + $modal->setConfirmationText($this->lng->txt('tst_nav_next_locks_current_answer_confirm')); + + $modal->setConfirmationCheckboxName(self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM); + $modal->setConfirmationCheckboxLabel($this->lng->txt('tst_dont_show_msg_again_in_current_session')); + + $button = $modal->buildModalButtonInstance('tst_nav_next_changed_answer_button'); + $button->setCaption('tst_save_and_proceed'); + $button->setPrimary(true); + $modal->addButton($button); + + $button = $modal->buildModalButtonInstance('tst_cancel_next_changed_answer_button'); + $button->setCaption('cancel'); + $button->setPrimary(false); + $modal->addButton($button); + + $this->tpl->setCurrentBlock('next_locks_changed_modal'); + $this->tpl->setVariable('NEXT_LOCKS_CHANGED_MODAL', $modal->getHTML()); + $this->tpl->parseCurrentBlock(); + } + + const FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM = 'followup_qst_locks_prevent_confirmation'; + + protected function setFollowUpQuestionLocksConfirmationPrevented() + { + $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM] = true; + } + + protected function isFollowUpQuestionLocksConfirmationPrevented() + { + if( !isset($_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM]) ) + { + return false; + } + + return $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM]; + } + // fau: testNav - new function populateQuestionEditControl /** * Populate the navigation and saving control for editable questions @@ -2812,7 +2891,8 @@ protected function populateQuestionEditControl($questionGUI) // Forced feedback will change the navigation saving command $config['forcedInstantFeedback'] = $this->object->isForceInstantFeedbackEnabled(); - + $config['nextQuestionLocks'] = $this->object->isFollowupQuestionAnswerFixationEnabled(); + $this->tpl->addJavascript('./Modules/Test/js/ilTestPlayerQuestionEditControl.js'); $this->tpl->addOnLoadCode('il.TestPlayerQuestionEditControl.init('.json_encode($config).')'); } diff --git a/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php b/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php new file mode 100644 index 000000000000..600608bdc330 --- /dev/null +++ b/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php @@ -0,0 +1,229 @@ + + * @version $Id$ + * + * @package Modules/Test(QuestionPool) + */ +class ilTestPlayerConfirmationModal +{ + /** + * @var string + */ + protected $modalId = ''; + + /** + * @var string + */ + protected $headerText = ''; + + /** + * @var string + */ + protected $confirmationText = ''; + + /** + * @var string + */ + protected $confirmationCheckboxName = ''; + + /** + * @var string + */ + protected $confirmationCheckboxLabel = ''; + + /** + * @var ilLinkButton[] + */ + protected $buttons = array(); + + /** + * @var ilHiddenInputGUI[] + */ + protected $parameters = array(); + + /** + * @return string + */ + public function getModalId() + { + return $this->modalId; + } + + /** + * @param string $modalId + */ + public function setModalId($modalId) + { + $this->modalId = $modalId; + } + + /** + * @return string + */ + public function getHeaderText() + { + return $this->headerText; + } + + /** + * @param string $headerText + */ + public function setHeaderText($headerText) + { + $this->headerText = $headerText; + } + + /** + * @return string + */ + public function getConfirmationText() + { + return $this->confirmationText; + } + + /** + * @param string $confirmationText + */ + public function setConfirmationText($confirmationText) + { + $this->confirmationText = $confirmationText; + } + + /** + * @return string + */ + public function getConfirmationCheckboxName() + { + return $this->confirmationCheckboxName; + } + + /** + * @param string $confirmationCheckboxName + */ + public function setConfirmationCheckboxName($confirmationCheckboxName) + { + $this->confirmationCheckboxName = $confirmationCheckboxName; + } + + /** + * @return string + */ + public function getConfirmationCheckboxLabel() + { + return $this->confirmationCheckboxLabel; + } + + /** + * @param string $confirmationCheckboxLabel + */ + public function setConfirmationCheckboxLabel($confirmationCheckboxLabel) + { + $this->confirmationCheckboxLabel = $confirmationCheckboxLabel; + } + + /** + * @return ilLinkButton[] + */ + public function getButtons() + { + return $this->buttons; + } + + /** + * @param ilLinkButton $button + */ + public function addButton(ilLinkButton $button) + { + $this->buttons[] = $button; + } + + /** + * @return ilHiddenInputGUI[] + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param ilHiddenInputGUI $hiddenInputGUI + */ + public function addParameter(ilHiddenInputGUI $hiddenInputGUI) + { + $this->parameters[] = $hiddenInputGUI; + } + + /** + * @return bool + */ + public function isConfirmationCheckboxRequired() + { + return strlen($this->getConfirmationCheckboxName()) && strlen($this->getConfirmationCheckboxLabel()); + } + + /** + * @return string + */ + public function buildBody() + { + $tpl = new ilTemplate('tpl.tst_player_confirmation_modal.html', true, true, 'Modules/Test'); + + if( $this->isConfirmationCheckboxRequired() ) + { + $tpl->setCurrentBlock('checkbox'); + $tpl->setVariable('CONFIRMATION_CHECKBOX_NAME', $this->getConfirmationCheckboxName()); + $tpl->setVariable('CONFIRMATION_CHECKBOX_LABEL', $this->getConfirmationCheckboxLabel()); + $tpl->parseCurrentBlock(); + } + + foreach($this->getParameters() as $parameter) + { + $tpl->setCurrentBlock('hidden_inputs'); + $tpl->setVariable('HIDDEN_INPUT', $parameter->getToolbarHTML()); + $tpl->parseCurrentBlock(); + } + + foreach($this->getButtons() as $button) + { + $tpl->setCurrentBlock('buttons'); + $tpl->setVariable('BUTTON', $button->render()); + $tpl->parseCurrentBlock(); + } + + $tpl->setVariable('CONFIRMATION_TEXT', $this->getConfirmationText()); + + return $tpl->get(); + } + + /** + * @return string + */ + public function getHTML() + { + $modal = ilModalGUI::getInstance(); + $modal->setId('tst_save_on_navigation_modal'); + $modal->setHeading($this->getHeaderText()); + $modal->setBody($this->buildBody()); + return $modal->getHTML(); + } + + /** + * @param string $buttonId + * @return ilLinkButton + */ + public function buildModalButtonInstance($buttonId) + { + $button = ilLinkButton::getInstance(); + + $button->setUrl('#'); + $button->setId($buttonId); + + return $button; + } +} \ No newline at end of file diff --git a/Modules/Test/js/ilTestPlayerQuestionEditControl.js b/Modules/Test/js/ilTestPlayerQuestionEditControl.js index 354a8a0ab8c5..04e43de72555 100644 --- a/Modules/Test/js/ilTestPlayerQuestionEditControl.js +++ b/Modules/Test/js/ilTestPlayerQuestionEditControl.js @@ -43,7 +43,8 @@ il.TestPlayerQuestionEditControl = new function() { withFormChangeDetection: true, // form changes should be detected withBackgroundChangeDetection: false, // background changes should be polled from ILIAS backgroundDetectorUrl: '', // url called by the background detector - forcedInstantFeedback: false // forced feedback will change the submit command + forcedInstantFeedback: false, // forced feedback will change the submit command + nextQuestionLocks: false }; /** diff --git a/Modules/Test/templates/default/tpl.il_as_tst_output.html b/Modules/Test/templates/default/tpl.il_as_tst_output.html index 6630df4603df..831f219e3752 100755 --- a/Modules/Test/templates/default/tpl.il_as_tst_output.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_output.html @@ -33,6 +33,8 @@ {QUESTION_OUTPUT} {DISCARD_SOLUTION_MODAL} {NAV_WHILE_EDIT_MODAL} + {NEXT_LOCKS_CHANGED_MODAL} + {NEXT_LOCKS_UNCHANGED_MODAL} id="{INSTANT_RESPONSE_FOCUS_ID}">

{INSTANT_RESPONSE_HEADER}

diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 7593a8786ccc..1690365b9ee6 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -449,6 +449,12 @@ assessment#:#ass_create_export_test_results#:#Erstelle Ergebnisdatei assessment#:#ass_create_export_test_archive#:#Erstelle Archivdatei für Test assessment#:#ass_question#:#Frage assessment#:#ass_size#:#Größe +assessment#:#tst_proceed#:#Fortfahren +assessment#:#tst_dont_show_msg_again_in_current_session#:#Diese Meldung in meiner aktuellen Sitzung nicht mehr anzeigen. +assessment#:#tst_nav_next_locks_empty_answer_header#:#Navigieren ohne Antwort +assessment#:#tst_nav_next_locks_empty_answer_confirm#:#Sie haben keine Antwort eingegeben. Eine leere Lösung wird als Antwort zur aktuellen Frage gespeichert und festgeschrieben. Die Antwort kann nicht mehr verändert werden. +assessment#:#tst_nav_next_locks_current_answer_header#:#Navigieren zur nächsten Frage +assessment#:#tst_nav_next_locks_current_answer_confirm#:#Wenn Sie zur nächsten Frage navigieren wird die Antwort zur aktuellen Frage festgeschrieben und kann nicht nochmals verändert werden. assessment#:#tst_filter_question_type#:#Fragentyp assessment#:#tst_filter_question_type_enabled#:#Fragentyp Filter verwenden assessment#:#tst_objectives_progress_header#:#Fortschritte in den Lernzielen @@ -13360,7 +13366,6 @@ assessment#:#save_on_navigation#:#Navigieren mit Speichern assessment#:#save_on_navigation_confirmation#:#Ihre geänderten Antworten werden beim Navigieren automatisch gespeichert. assessment#:#save_on_navigation_locked_confirmation#:#Ihre geänderten Antworten werden beim Navigieren automatisch gespeichert und festgeschrieben. assessment#:#save_on_navigation_forced_feedback_hint#:#Vorher erhalten Sie Feedback zu Ihrer Antwort. -assessment#:#save_on_navigation_prevent_confirmation#:#Diese Meldung in meiner aktuellen Sitzung nicht mehr anzeigen. assessment#:#tst_answer_status_editing#:#(in Bearbeitung) assessment#:#tst_answer_status_answered#:#Beantwortet assessment#:#tst_answer_status_not_answered#:#Nicht beantwortet diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 959b3107b43f..d648c4a46c9d 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -358,6 +358,12 @@ assessment#:#ass_create_export_test_results#:#Create Test Results Export File assessment#:#ass_create_export_test_archive#:#Create Test Archive File assessment#:#ass_question#:#Question assessment#:#ass_size#:#Size +assessment#:#tst_proceed#:#Proceed +assessment#:#tst_dont_show_msg_again_in_current_session#:#Don't show this message again in my current session. +assessment#:#tst_nav_next_locks_empty_answer_header#:#Navigation Without Answer +assessment#:#tst_nav_next_locks_empty_answer_confirm#:#You did not answer the current question. An empty solution will be saved and locked as answer. This answer cannot be changed later on. +assessment#:#tst_nav_next_locks_current_answer_header#:#Navigation to Next Question +assessment#:#tst_nav_next_locks_current_answer_confirm#:#When you navigate to the next question your answer to the current question will be locked and cannot be changed any longer. assessment#:#tst_filter_question_type#:#Question Type assessment#:#tst_filter_question_type_enabled#:#Use Question Type as Filter assessment#:#tst_objectives_progress_header#:#Learning Objectives Progress @@ -13340,7 +13346,6 @@ assessment#:#save_on_navigation#:#Save on Navigation###fau: testNav assessment#:#save_on_navigation_confirmation#:#Your changed answers will be automatically saved when you navigate.###fau: testNav assessment#:#save_on_navigation_locked_confirmation#:#Your changed answers will be automatically saved and locked when you navigate.###fau: testNav assessment#:#save_on_navigation_forced_feedback_hint#:#Before you get feedback on your given answer.###fau: testNav -assessment#:#save_on_navigation_prevent_confirmation#:#Don't show this message again in my current session. assessment#:#tst_answer_status_editing#:# (editing)###fau: testNav assessment#:#tst_answer_status_answered#:#Answered###fau: testNav assessment#:#tst_answer_status_not_answered#:#Not answered###fau: testNav From 3d3c16099af406ee1b6943823f560c24b0c45d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 15 Jun 2018 11:49:53 +0200 Subject: [PATCH 008/166] connected new confirmation modals for followup qst answer locking to player edit control js engine --- .../classes/class.ilTestPlayerAbstractGUI.php | 6 ++ .../class.ilTestPlayerConfirmationModal.php | 2 +- .../js/ilTestPlayerQuestionEditControl.js | 58 ++++++++++++++++++- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index 3a2dd35c5eec..8c0693dcfd7f 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -2459,6 +2459,11 @@ protected function saveNavigationPreventConfirmation() { $_SESSION['save_on_navigation_prevent_confirmation'] = true; } + + if( !empty($_POST[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM]) ) + { + $_SESSION[self::FOLLOWUP_QST_LOCKS_PREVENT_CONFIRMATION_PARAM] = true; + } } // fau. @@ -2610,6 +2615,7 @@ protected function populateModals() if( $this->object->isFollowupQuestionAnswerFixationEnabled() ) { $this->populateNextLocksChangedModal(); + $this->populateNextLocksUnchangedModal(); } diff --git a/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php b/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php index 600608bdc330..b1c5d35847d4 100644 --- a/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php +++ b/Modules/Test/classes/class.ilTestPlayerConfirmationModal.php @@ -207,7 +207,7 @@ public function buildBody() public function getHTML() { $modal = ilModalGUI::getInstance(); - $modal->setId('tst_save_on_navigation_modal'); + $modal->setId($this->getModalId()); $modal->setHeading($this->getHeaderText()); $modal->setBody($this->buildBody()); return $modal->getHTML(); diff --git a/Modules/Test/js/ilTestPlayerQuestionEditControl.js b/Modules/Test/js/ilTestPlayerQuestionEditControl.js index 04e43de72555..b12bf1bceaa2 100644 --- a/Modules/Test/js/ilTestPlayerQuestionEditControl.js +++ b/Modules/Test/js/ilTestPlayerQuestionEditControl.js @@ -135,6 +135,17 @@ il.TestPlayerQuestionEditControl = new function() { // the final action is done by a submit button in the modal $('#tst_discard_solution_action').click(showDiscardSolutionModal); $('#tst_cancel_discard_button').click(hideDiscardSolutionModal); + + if( config.nextQuestionLocks ) + { + // handle the buttons in next locks current answer confirmation modal + $('#tst_nav_next_changed_answer_button').click(saveWithNavigation); + $('#tst_cancel_next_changed_answer_button').click(hideFollowupQuestionLocksCurrentAnswerModal); + + // handle the buttons in next locks empty answer confirmation modal + $('#tst_nav_next_empty_answer_button').click(saveWithNavigation); + $('#tst_cancel_next_empty_answer_button').click(hideFollowupQuestionLocksEmptyAnswerModal); + } // the checkbox 'use unchanged answer' is only needed for initial empty answers // it exists for few question types only @@ -373,7 +384,30 @@ il.TestPlayerQuestionEditControl = new function() { toggleQuestionMark(); return false; } - else if (answerChanged // answer has been changed + else if( config.nextQuestionLocks && $(this).attr('data-nextcmd') == 'nextQuestion' ) + { + // remember the url for saveWithNavigation() + navUrl = href; + + if( !answerChanged && !answered ) + { + console.log('rubbel die katz x 2'); + + showFollowupQuestionLocksEmptyAnswerModal(); + } + else if( $('#tst_next_locks_changed_modal').length > 0 ) + { + showFollowupQuestionLocksCurrentAnswerModal(); + } + else + { + saveWithNavigation(); + } + + return false; // prevent the default event handler + } + + if (answerChanged // answer has been changed && href // link is not an anchor && href.charAt(0) != '#' // link is not a fragment && id != 'tst_discard_answer_action' // link is not the 'discard answer' button @@ -431,6 +465,28 @@ il.TestPlayerQuestionEditControl = new function() { function hideDiscardSolutionModal() { $('#tst_discard_solution_modal').modal('hide'); } + + function showFollowupQuestionLocksCurrentAnswerModal() + { + $('#tst_next_locks_changed_modal').modal('show'); + $('#followup_qst_locks_prevent_confirmation').attr('checked',false); + } + + function hideFollowupQuestionLocksCurrentAnswerModal() + { + $('#tst_next_locks_changed_modal').modal('hide'); + } + + function showFollowupQuestionLocksEmptyAnswerModal() + { + console.log($('#tst_next_locks_unchanged_modal')); + $('#tst_next_locks_unchanged_modal').modal('show'); + } + + function hideFollowupQuestionLocksEmptyAnswerModal() + { + $('#tst_next_locks_unchanged_modal').modal('hide'); + } /** * Disable and uncheck the 'use unchanged answer' checkbox From 08ed44a7f8d30b715087789ba6ea44866991d210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 15 Jun 2018 14:55:58 +0200 Subject: [PATCH 009/166] dbupdate adding table for presented questions managed by ilTestSequence --- Modules/Test/DBupdate_KeyFeature.php | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Modules/Test/DBupdate_KeyFeature.php b/Modules/Test/DBupdate_KeyFeature.php index 9b4a4c22e902..d6639ec92df3 100644 --- a/Modules/Test/DBupdate_KeyFeature.php +++ b/Modules/Test/DBupdate_KeyFeature.php @@ -22,6 +22,35 @@ 'UPDATE tst_tests SET follow_qst_answer_fixation = %s', array('integer'), array(0) ); } + + if( !$DIC->database()->tableExists('tst_seq_qst_presented') ) + { + $DIC->database()->createTable('tst_seq_qst_presented', array( + 'active_fi' => array( + 'type' => 'integer', + 'length' => 4, + 'notnull' => true, + 'default' => 0 + ), + 'pass' => array( + 'type' => 'integer', + 'length' => 4, + 'notnull' => true, + 'default' => 0 + ), + 'question_fi' => array( + 'type' => 'integer', + 'length' => 4, + 'notnull' => true, + 'default' => 0 + ) + )); + + $DIC->database()->addPrimaryKey('tst_seq_qst_presented', array( + 'active_fi','pass', 'question_fi' + )); + } + } catch(ilException $e) { From 51a25f2e82ae0a68987beddea41e7d7fe853ba69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 15 Jun 2018 14:57:01 +0200 Subject: [PATCH 010/166] backend changes for keyfeature: sidelist disabled links, avoid save solution, presented question tracking --- .../Test/classes/class.ilTestOutputGUI.php | 13 ++++ .../classes/class.ilTestPlayerAbstractGUI.php | 10 +-- .../class.ilTestQuestionSideListGUI.php | 23 +++---- Modules/Test/classes/class.ilTestSequence.php | 65 +++++++++++++++++++ Modules/Test/classes/class.ilTestService.php | 18 ++++- 5 files changed, 110 insertions(+), 19 deletions(-) diff --git a/Modules/Test/classes/class.ilTestOutputGUI.php b/Modules/Test/classes/class.ilTestOutputGUI.php index 813669f7a791..5ff56e3e2ad0 100755 --- a/Modules/Test/classes/class.ilTestOutputGUI.php +++ b/Modules/Test/classes/class.ilTestOutputGUI.php @@ -279,6 +279,12 @@ protected function showQuestionCmd() { $this->handleTearsAndAngerNoObjectiveOrientedQuestion(); } + + if( !$this->testSequence->isQuestionPresented($questionId) ) + { + $this->testSequence->setQuestionPresented($questionId); + $this->testSequence->saveToDb(); + } $isQuestionWorkedThrough = assQuestion::_isWorkedThrough( $this->testSession->getActiveId(), $questionId, $this->testSession->getPass() @@ -633,6 +639,13 @@ public function saveQuestionSolution($authorized = true, $force = false) { // but only if the ending time is not reached $q_id = $this->testSequence->getQuestionForSequence($_GET["sequence"]); + + if( $this->isParticipantsAnswerFixed($q_id) ) + { + // should only be reached by firebugging the disabled form in ui + throw new ilTestException('not allowed request'); + } + if (is_numeric($q_id) && (int)$q_id) { $questionOBJ = $this->getQuestionInstance($q_id); diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index 8c0693dcfd7f..61d6f1807330 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -1879,17 +1879,17 @@ protected function handlePasswordProtectionRedirect() protected function isParticipantsAnswerFixed($questionId) { - if( !$this->object->isInstantFeedbackAnswerFixationEnabled() ) + if( $this->object->isInstantFeedbackAnswerFixationEnabled() && $this->testSequence->isQuestionChecked($questionId) ) { - return false; + return true; } - if( !$this->testSequence->isQuestionChecked($questionId) ) + if( $this->object->isFollowupQuestionAnswerFixationEnabled() && $this->testSequence->isNextQuestionPresented($questionId) ) { - return false; + return true; } - return true; + return false; } /** diff --git a/Modules/Test/classes/class.ilTestQuestionSideListGUI.php b/Modules/Test/classes/class.ilTestQuestionSideListGUI.php index dbc9dad43390..5821934de0f1 100644 --- a/Modules/Test/classes/class.ilTestQuestionSideListGUI.php +++ b/Modules/Test/classes/class.ilTestQuestionSideListGUI.php @@ -178,8 +178,16 @@ private function renderList() $row['worked_through'] ? 'answered'.$active : 'unanswered'.$active ); - /* - if( $this->isDisabled() ) + if ($row['marked']) + { + $tpl->setCurrentBlock("mark_icon"); + $tpl->setVariable("ICON_SRC", ilUtil::getImagePath('marked.svg')); + $tpl->setVariable("ICON_TEXT", $this->lng->txt('tst_question_marked')); + $tpl->setVariable("ICON_CLASS", 'ilTestMarkQuestionIcon'); + $tpl->parseCurrentBlock(); + } + + if( $this->isDisabled() || $row['disabled'] ) { $tpl->setCurrentBlock('disabled_entry'); $tpl->setVariable('CLASS', $class); @@ -189,16 +197,7 @@ private function renderList() } else { - */ // fau: testNav - show mark icon in side list - if ($row['marked']) - { - $tpl->setCurrentBlock("mark_icon"); - $tpl->setVariable("ICON_SRC", ilUtil::getImagePath('marked.svg')); - $tpl->setVariable("ICON_TEXT", $this->lng->txt('tst_question_marked')); - $tpl->setVariable("ICON_CLASS", 'ilTestMarkQuestionIcon'); - $tpl->parseCurrentBlock(); - } // fau. $tpl->setCurrentBlock('linked_entry'); $tpl->setVariable('HREF', $this->buildLink($row['sequence'])); @@ -208,9 +207,7 @@ private function renderList() $tpl->setVariable('ITEM', $title); $tpl->setVariable("DESCRIPTION", $description); $tpl->parseCurrentBlock(); - /* } - */ $tpl->setCurrentBlock('item'); } diff --git a/Modules/Test/classes/class.ilTestSequence.php b/Modules/Test/classes/class.ilTestSequence.php index 4f25babdc2fe..a09f09c20cf9 100644 --- a/Modules/Test/classes/class.ilTestSequence.php +++ b/Modules/Test/classes/class.ilTestSequence.php @@ -49,6 +49,16 @@ class ilTestSequence implements ilTestQuestionSequence, ilTestSequenceSummaryPro * @var boolean */ var $isRandomTest; + + /** + * @var integer[] + */ + protected $alreadyPresentedQuestions = array(); + + /** + * @var int + */ + protected $newlyPresentedQuestion = 0; /** * @var array @@ -160,6 +170,7 @@ public function loadQuestions(ilTestQuestionSetConfig $testQuestionSetConfig = n public function loadFromDb() { $this->loadQuestionSequence(); + $this->loadPresentedQuestions(); $this->loadCheckedQuestions(); $this->loadOptionalQuestions(); } @@ -187,6 +198,21 @@ private function loadQuestionSequence() } } + protected function loadPresentedQuestions() + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + $res = $DIC->database()->queryF( + "SELECT question_fi FROM tst_seq_qst_presented WHERE active_fi = %s AND pass = %s", + array('integer','integer'), array($this->active_id, $this->pass) + ); + + while( $row = $DIC->database()->fetchAssoc($res) ) + { + $this->alreadyPresentedQuestions[ $row['question_fi'] ] = $row['question_fi']; + } + } + private function loadCheckedQuestions() { global $ilDB; @@ -223,6 +249,7 @@ private function loadOptionalQuestions() public function saveToDb() { $this->saveQuestionSequence(); + $this->saveNewlyPresentedQuestion(); $this->saveNewlyCheckedQuestion(); $this->saveOptionalQuestions(); } @@ -257,6 +284,20 @@ private function saveQuestionSequence() 'ans_opt_confirmed' => array('integer', (int)$this->isAnsweringOptionalQuestionsConfirmed()) )); } + + protected function saveNewlyPresentedQuestion() + { + if( (int)$this->newlyPresentedQuestion ) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + $DIC->database()->replace('tst_seq_qst_presented', array( + 'active_fi' => array('integer', (int)$this->active_id), + 'pass' => array('integer', (int)$this->pass), + 'question_fi' => array('integer', (int)$this->newlyPresentedQuestion) + ), array()); + } + } /** * @global ilDBInterface $ilDB @@ -393,6 +434,29 @@ function hideSequence($sequence) } } + public function setQuestionPresented($questionId) + { + $this->newlyPresentedQuestion = $questionId; + } + + public function isQuestionPresented($questionId) + { + return ( + $this->newlyPresentedQuestion == $questionId || in_array($questionId, $this->alreadyPresentedQuestions) + ); + } + + public function isNextQuestionPresented($questionId) + { + $nextQstId = $this->getQuestionForSequence( + $this->getNextSequence( $this->getSequenceForQuestion($questionId) ) + ); + + return ( + $this->newlyPresentedQuestion == $nextQstId || in_array($nextQstId, $this->alreadyPresentedQuestions) + ); + } + public function setQuestionChecked($questionId) { $this->newlyCheckedQuestion = $questionId; @@ -660,6 +724,7 @@ public function getSequenceSummary($obligationsFilterEnabled = false) "nr" => "$key", "title" => $question->getTitle(), "qid" => $question->getId(), + "presented" => $this->isQuestionPresented($question->getId()), "visited" => $worked_through, "solved" => (($solved)?"1":"0"), "description" => $question->getComment(), diff --git a/Modules/Test/classes/class.ilTestService.php b/Modules/Test/classes/class.ilTestService.php index 562c3faf23d6..8bb21f705ae8 100755 --- a/Modules/Test/classes/class.ilTestService.php +++ b/Modules/Test/classes/class.ilTestService.php @@ -271,6 +271,10 @@ public function getQuestionSummaryData(ilTestSequenceSummaryProvider $testSequen foreach($result_array as $key => $value) { + $disableLink = ( + $this->object->isFollowupQuestionAnswerFixationEnabled() && !$value['presented'] + ); + $description = ""; if($this->object->getListOfQuestionsDescription()) { @@ -297,7 +301,19 @@ public function getQuestionSummaryData(ilTestSequenceSummaryProvider $testSequen } // fau: testNav - add number parameter for getQuestionTitle() - $data[] = array('order' => $value["nr"], 'title' => $this->object->getQuestionTitle($value["title"], $value["nr"]), 'description' => $description, 'worked_through' => $value["worked_through"], 'postponed' => $value["postponed"], 'points' => $points, 'marked' => $marked, 'sequence' => $value["sequence"], 'obligatory' => $value['obligatory'], 'isAnswered' => $value['isAnswered']); + $data[] = array( + 'order' => $value["nr"], + 'title' => $this->object->getQuestionTitle($value["title"], $value["nr"]), + 'description' => $description, + 'disabled' => $disableLink, + 'worked_through' => $value["worked_through"], + 'postponed' => $value["postponed"], + 'points' => $points, + 'marked' => $marked, + 'sequence' => $value["sequence"], + 'obligatory' => $value['obligatory'], + 'isAnswered' => $value['isAnswered'] + ); // fau. } From 9f5cd05c9a579aac54f452627fd05b86d9714cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 15 Jun 2018 16:00:48 +0200 Subject: [PATCH 011/166] non linked links for question summary list page in test player on followup qst locking --- .../class.ilListOfQuestionsTableGUI.php | 24 ++++++++++++++----- .../tpl.il_as_tst_list_of_questions_row.html | 6 ++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Modules/Test/classes/tables/class.ilListOfQuestionsTableGUI.php b/Modules/Test/classes/tables/class.ilListOfQuestionsTableGUI.php index bd2c417feb8c..14fbae8a65f6 100644 --- a/Modules/Test/classes/tables/class.ilListOfQuestionsTableGUI.php +++ b/Modules/Test/classes/tables/class.ilListOfQuestionsTableGUI.php @@ -172,17 +172,29 @@ public function fillRow($data) $this->tpl->setVariable("QUESTION_OBLIGATORY", $OBLIGATORY); } - $this->ctrl->setParameter($this->parent_obj, 'sequence', $data['sequence']); - $this->ctrl->setParameter($this->parent_obj, 'pmode', ''); - $href = $this->ctrl->getLinkTarget($this->parent_obj, ilTestPlayerCommands::SHOW_QUESTION); - $postponed = ( $data['postponed'] ? $this->lng->txt('postponed') : '' ); + if( $data['disabled'] ) + { + $this->tpl->setCurrentBlock('static_title'); + $this->tpl->setVariable("STATIC_TITLE", ilUtil::prepareFormOutput($data['title'])); + $this->tpl->parseCurrentBlock(); + } + else + { + $this->ctrl->setParameter($this->parent_obj, 'sequence', $data['sequence']); + $this->ctrl->setParameter($this->parent_obj, 'pmode', ''); + $href = $this->ctrl->getLinkTarget($this->parent_obj, ilTestPlayerCommands::SHOW_QUESTION); + + $this->tpl->setCurrentBlock('linked_title'); + $this->tpl->setVariable("LINKED_TITLE", ilUtil::prepareFormOutput($data['title'])); + $this->tpl->setVariable("HREF", $href); + $this->tpl->parseCurrentBlock(); + } + $this->tpl->setVariable("ORDER", $data['order']); - $this->tpl->setVariable("TITLE", ilUtil::prepareFormOutput($data['title'])); - $this->tpl->setVariable("HREF", $href); $this->tpl->setVariable("POSTPONED", $postponed); if ($data["worked_through"]) { diff --git a/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_row.html b/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_row.html index 0be2a8ce73d8..f5058359014b 100644 --- a/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_row.html +++ b/Modules/Test/templates/default/tpl.il_as_tst_list_of_questions_row.html @@ -1,6 +1,10 @@ {ORDER} -
{TITLE} ({DESCRIPTION}) + + {STATIC_TITLE} + {LINKED_TITLE} + ({DESCRIPTION}) + {QUESTION_OBLIGATORY} From c86149a372f5daf1aebe51b1afae255b459c7904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 15 Jun 2018 16:15:13 +0200 Subject: [PATCH 012/166] fixed wrong field names in general settings forms after followup restructuring --- Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php index eb7ec1980d7c..f74dbd27fdc7 100644 --- a/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php +++ b/Modules/Test/classes/class.ilObjTestSettingsGeneralGUI.php @@ -1309,10 +1309,10 @@ private function saveQuestionBehaviourProperties(ilPropertyFormGUI $form) { if( $form->getItemByPostVar('instant_feedback_enabled')->getChecked() ) { - if( $this->formPropertyExists($form, 'instant_feedback_content') ) + if( $this->formPropertyExists($form, 'instant_feedback_contents') ) { $this->testOBJ->setInstantFeedbackOptionsByArray( - $form->getItemByPostVar('instant_feedback_content')->getValue() + $form->getItemByPostVar('instant_feedback_contents')->getValue() ); } if( $this->formPropertyExists($form, 'instant_feedback_trigger') ) From c3a53922db5ac1742e1fd0e11c5bb26433bb7707 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Mon, 18 Jun 2018 13:59:49 +0200 Subject: [PATCH 013/166] typo --- src/UI/Implementation/Component/Input/Field/Password.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/Implementation/Component/Input/Field/Password.php b/src/UI/Implementation/Component/Input/Field/Password.php index 91174cb59d34..543d5b6c8952 100644 --- a/src/UI/Implementation/Component/Input/Field/Password.php +++ b/src/UI/Implementation/Component/Input/Field/Password.php @@ -46,7 +46,7 @@ protected function getConstraintForRequirement() { } /** - * This is a shortcut to quickly get a Passwordfiled with desired contraints. + * This is a shortcut to quickly get a password-field with desired contraints. * * @param int $min_length * @param bool $lower From 91bab2b6a82bead98ae45e410cb8a98005067afa Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Tue, 19 Jun 2018 13:24:09 +0200 Subject: [PATCH 014/166] Docs: Worked in Feedback from TB --- docs/documentation/maintenance-coordinator.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/documentation/maintenance-coordinator.md b/docs/documentation/maintenance-coordinator.md index eebd2edc388a..e354337900ee 100644 --- a/docs/documentation/maintenance-coordinator.md +++ b/docs/documentation/maintenance-coordinator.md @@ -15,7 +15,7 @@ ## Role of the Coordinator The Coordinator is not the owner of the component but much more the curator. -The coordinator ensures the quality of contributations to the component +The coordinator ensures the quality of contributions to the component and makes it possible for others to commit. The coordinator is responsible that the documentation is kept up to date by the contributors and that the guidelines of the component are met. The coordinator moderates the discussion on @@ -39,7 +39,11 @@ is achieved in the JF, the Technical Board will decide upon the request. Final implementations without further changes on the interface do not need formal approval by the JF. The merge of the implementation is performed -by the coordinator. +by the coordinator or the coordinator may assign somebody to do so. + +Note that the general process for feature requests must be respected. However, +this process is currently under review. The respective document will be linked as soon +as available. ##Issue Management From 0e3f3f34907ffaffb88e0180d14a6495d6def513 Mon Sep 17 00:00:00 2001 From: Benjamin Seglias Date: Tue, 19 Jun 2018 14:57:22 +0200 Subject: [PATCH 015/166] possible fix for mantis bug 0023096, in combination with client ini setting disable_ascii true --- Modules/File/classes/class.ilObjFile.php | 2 +- .../classes/FileHandler/class.ilCalendarFileHandler.php | 4 +++- Services/FileDelivery/classes/Delivery.php | 3 +-- Services/FileDelivery/classes/class.ilPHPOutputDelivery.php | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Modules/File/classes/class.ilObjFile.php b/Modules/File/classes/class.ilObjFile.php index 736784085181..07b4e1a01a3a 100755 --- a/Modules/File/classes/class.ilObjFile.php +++ b/Modules/File/classes/class.ilObjFile.php @@ -705,7 +705,7 @@ public function sendFile($a_hist_entry_id = null) { $ilFileDelivery = new ilFileDelivery($file); $ilFileDelivery->setDisposition($this->isInline() ? ilFileDelivery::DISP_INLINE : ilFileDelivery::DISP_ATTACHMENT); $ilFileDelivery->setMimeType($this->guessFileType($file)); - $ilFileDelivery->setConvertFileNameToAsci(true); + $ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); // also returning the 'real' filename if a history file is delivered if ($ilClientIniFile->readVariable('file_access', 'download_with_uploaded_filename') diff --git a/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php b/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php index bae7d1fd3dbf..71205cf9a620 100644 --- a/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php +++ b/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php @@ -81,11 +81,13 @@ function downloadFilesForEvents($a_events) if (is_file($last_file)) { require_once('./Services/FileDelivery/classes/class.ilFileDelivery.php'); + global $DIC; + $ilClientIniFile = $DIC['ilClientIniFile']; $ilFileDelivery = new ilFileDelivery($last_file); $ilFileDelivery->setDisposition(ilFileDelivery::DISP_ATTACHMENT); //$ilFileDelivery->setMimeType($this->guessFileType($file)); - $ilFileDelivery->setConvertFileNameToAsci(true); + $ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); //$ilFileDelivery->setDownloadFileName(); $ilFileDelivery->deliver(); exit; diff --git a/Services/FileDelivery/classes/Delivery.php b/Services/FileDelivery/classes/Delivery.php index ab9fdaeef256..3e1fa5c38275 100644 --- a/Services/FileDelivery/classes/Delivery.php +++ b/Services/FileDelivery/classes/Delivery.php @@ -143,7 +143,6 @@ public function deliver() { $response = $this->httpService->response()->withHeader('X-ILIAS-FileDelivery-Method', $this->getDeliveryType()); $this->httpService->saveResponse($response); - $this->cleanDownloadFileName(); $this->clearBuffer(); $this->checkCache(); $this->setGeneralHeaders(); @@ -164,7 +163,7 @@ public function setGeneralHeaders() { $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_TYPE, $this->getMimeType()); $this->httpService->saveResponse($response); } - if ($this->isConvertFileNameToAsci()) { + if (!$this->isConvertFileNameToAsci()) { $this->cleanDownloadFileName(); } if ($this->hasHashFilename()) { diff --git a/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php b/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php index be9ea30bbcf5..c862990f4ea8 100644 --- a/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php +++ b/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php @@ -28,11 +28,13 @@ final class ilPHPOutputDelivery { * @param string $mime_type */ public function start($download_file_name, $mime_type = ilMimeTypeUtil::APPLICATION__OCTET_STREAM) { + global $DIC; + $ilClientIniFile = $DIC['ilClientIniFile']; $this->ilFileDelivery = new Delivery(ilFileDelivery::DIRECT_PHP_OUTPUT, self::http()); $this->ilFileDelivery->setMimeType($mime_type); $this->ilFileDelivery->setDownloadFileName($download_file_name); $this->ilFileDelivery->setDisposition(ilFileDelivery::DISP_ATTACHMENT); - $this->ilFileDelivery->setConvertFileNameToAsci(true); + $this->ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); $this->ilFileDelivery->clearBuffer(); $this->ilFileDelivery->checkCache(); $this->ilFileDelivery->setGeneralHeaders(); From 0cfbda8482272610606aa5cc076e2797dd900047 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Wed, 20 Jun 2018 13:57:52 +0200 Subject: [PATCH 016/166] Doc: Coordinator Model space fixed. --- docs/documentation/maintenance-coordinator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/documentation/maintenance-coordinator.md b/docs/documentation/maintenance-coordinator.md index e354337900ee..ea879030ff04 100644 --- a/docs/documentation/maintenance-coordinator.md +++ b/docs/documentation/maintenance-coordinator.md @@ -1,4 +1,4 @@ -#Coordinator Model +# Coordinator Model ## Table of Contents @@ -88,4 +88,4 @@ UI-Service. Such guidelines MUST be accepted by the JF. **Please note:** The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" -in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). \ No newline at end of file +in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). From 9d8f454b11bb0e2cb3d9a260b636450f43738a16 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Wed, 20 Jun 2018 14:27:46 +0200 Subject: [PATCH 017/166] Doc: Coordinator Model space fixed II --- docs/documentation/maintenance-coordinator.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/documentation/maintenance-coordinator.md b/docs/documentation/maintenance-coordinator.md index ea879030ff04..53196bed8efd 100644 --- a/docs/documentation/maintenance-coordinator.md +++ b/docs/documentation/maintenance-coordinator.md @@ -29,7 +29,7 @@ aspects of the component due to the very indepth know how and the listing as coordinator. -##Change Management +## Change Management Everybody may contribute to any aspect of the component. Such contributions are handed in by pull requests or some other source of data if declared so in the components guidelines. Pull requests on the public interface must be accepted by the JF. The coordinator gives a @@ -46,14 +46,14 @@ this process is currently under review. The respective document will be linked a as available. -##Issue Management +## Issue Management Everybody is invited to make proposals on how to tackle any issue by proposing a respective PR. Issues of the component must be reported as bugs. The coordinators are responsible to assign the developer in charge on solving the bug. Due to the focus on code quality, a low amount of bugs should be expected. -##Scenarios +## Scenarios This maintainance model is suited for components that have the potential to grow too large to be handled by one single developer and therefore highly benefit from contributions among different developers and even service providers. @@ -63,7 +63,7 @@ of a critical importance for many other components, since it is designed to allo a collaborative development of the vision for such a key aspect. -##What can be expected of a coordinator? +## What can be expected of a coordinator? * The coordinator MUST moderate the discussion on finding a vision on the development of the component. * The coordinator MUST give recommendations to the JF whether to accept From f5b721df62d2d2077a99dd8ef767ecd1e33cf51a Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 27 Jun 2018 13:03:04 +0200 Subject: [PATCH 018/166] ContentPage: Added translations --- lang/ilias_de.lang | 15 +++++++++++++++ lang/ilias_en.lang | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index d890ed47c335..362093e38cb8 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -14219,4 +14219,19 @@ file#:#file_suffix_custom_white_info#:#Diese Dateiendungen werden der obigen Pos file#:#file_suffix_overall_white#:#Effektive Positivliste file#:#file_suffix_overall_white_info#:#Dies ist die endgültige Liste akzeptierter Dateiendungen. assessment#:#not_enough_unique_answers#:#Sie haben "Identische Bewertung" deaktiviert, benutzten aber in mindestens zwei Lücken eine identische Antwort. +common#:#obj_copa#:#Inhaltsseite +common#:#objs_copa#:#Inhaltsseiten +rbac#:#copa_visible#:#Inhaltsseite ist sichtbar +rbac#:#copa_read#:#Inhaltsseite lesen +rbac#:#copa_copy#:#Inhaltsseite kopieren +rbac#:#copa_write#:#Inhaltsseite bearbeiten +rbac#:#copa_delete#:#Inhaltsseite löschen +rbac#:#copa_edit_permission#:#Rechteeinstellungen ändern +rbac#:#rbac_create_copa#:#Inhaltsseite anlegen +copa#:#copa_new#:#Neue Inhaltsseite anlegen +copa#:#copa_import#:#Inhaltsseite importieren +copa#:#copa_copy#:#Inhaltsseite kopieren +copa#:#copa_add#:#Inhaltsseite hinzufügen +copa#:#copa_edit#:#Inhaltsseite bearbeiten +copa#:#obj_copa_duplicate#:#Inhaltsseite duplizieren wiki#:#wiki_change_notification_body_new#:#die folgende Wiki-Seite wurde neu angelegt diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 8bc0bec9000d..d16ff1e81648 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14217,4 +14217,19 @@ file#:#file_suffix_custom_white_info#:#These suffixes will be added to the defau file#:#file_suffix_overall_white#:#Overal Suffix Whitelist file#:#file_suffix_overall_white_info#:#Resulting overall list of accepted file suffixes. assessment#:#not_enough_unique_answers#:#You have not selected "Identical Scoring", but you are using the same answer in at least to gaps. +common#:#obj_copa#:#Neutral Learning Object +common#:#objs_copa#:#Content Page Objects +rbac#:#copa_visible#:#Content Page Object is visible +rbac#:#copa_read#:#User can read Content Page Object +rbac#:#copa_copy#:#Copy Content Page Object +rbac#:#copa_write#:#User can edit Content Page Object +rbac#:#copa_delete#:#User can move or delete Content Page Object +rbac#:#copa_edit_permission#:#User can change permission settings +rbac#:#rbac_create_copa#:#Create Content Page Object +copa#:#copa_new#:#Create Content Page Object +copa#:#copa_import#:#Import Content Page Object +copa#:#copa_copy#:#Copy Content Page Object +copa#:#copa_add#:#Add Content Page Object +copa#:#copa_edit#:#Edit Content Page Object +copa#:#obj_copa_duplicate#:#Duplicate Content Page Object wiki#:#wiki_change_notification_body_new#:#the following wiki page was created From 7b56452802fda654d868771eb03032efa775612d Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 27 Jun 2018 14:00:18 +0200 Subject: [PATCH 019/166] ContentPage: Added translations --- lang/ilias_en.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index d16ff1e81648..482a2045c4e8 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14217,7 +14217,7 @@ file#:#file_suffix_custom_white_info#:#These suffixes will be added to the defau file#:#file_suffix_overall_white#:#Overal Suffix Whitelist file#:#file_suffix_overall_white_info#:#Resulting overall list of accepted file suffixes. assessment#:#not_enough_unique_answers#:#You have not selected "Identical Scoring", but you are using the same answer in at least to gaps. -common#:#obj_copa#:#Neutral Learning Object +common#:#obj_copa#:#Content Page Object common#:#objs_copa#:#Content Page Objects rbac#:#copa_visible#:#Content Page Object is visible rbac#:#copa_read#:#User can read Content Page Object From bc64eb0b2f49cceaf91da4d2fd5ac57e4ac155d1 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 27 Jun 2018 14:19:59 +0200 Subject: [PATCH 020/166] ContentPage: Added translations --- lang/ilias_de.lang | 2 +- lang/ilias_en.lang | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 362093e38cb8..313ca14a5d02 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2554,7 +2554,7 @@ common#:#enable_calendar#:#Kalender aktivieren common#:#enable_course_group_notifications#:#Tägliche Mail für Gruppen und Kurs News common#:#enable_course_group_notifications_desc#:#Bei Aktivierung können Teilnehmer eine Zusammenfassung der News abonnieren. common#:#enable_custom_icons#:#Anpassbare Symbole aktivieren -common#:#enable_custom_icons_info#:#Damit können Sie eigene Symbole (Icons) für einzelne Kategorien, Kurse und Gruppen definieren. Die entsprechenden Bilddateien laden Sie in den Eigenschaften der entsprechenden Kategorien, Kurse und Gruppen hoch. +common#:#enable_custom_icons_info#:#Damit können Sie eigene Symbole (Icons) für einzelne Container-Objekte definieren. Die entsprechenden Bilddateien laden Sie in den Eigenschaften der Objekte hoch. common#:#enable_disk_quota_reminder_mail#:#Sende Disk-Quota-Erinnerungsnachrichten common#:#enable_disk_quota_summary_mail#:#Sende Disk-Quota-Zusammenfassungen common#:#enable_disk_quota_summary_mail_desc#:#Durch das Aktivieren ist es möglich, bestimmten Benutzern eine tägliche Benachrichtigung über die überschrittenen Disk-Quotas zukommen zu lassen. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 482a2045c4e8..cd933572bfcc 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -2084,7 +2084,7 @@ common#:#editor#:#Editor common#:#email_not_valid#:#The e-mail address you entered is not valid! common#:#email#:#E-Mail common#:#enable_calendar#:#Enable Calendar -common#:#enable_custom_icons_info#:#This allows you to define custom icons for single categories, courses and groups. The properties section of categories, courses and groups will provide an image upload dialog. +common#:#enable_custom_icons_info#:#This allows you to define custom icons for single container objects and the content page object. The properties section of these objects will provide an image upload dialog. common#:#enable_custom_icons#:#Enable custom icons common#:#enable_disk_quota_reminder_mail#:#Send disk quota reminder mails common#:#enable_download_folder_info#:#Allows download of a folder including sub structures as zip file. From afdb429399660320c996317fa75f74f03934af59 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 27 Jun 2018 16:06:21 +0200 Subject: [PATCH 021/166] ContentPage: Added new object type, refactored custom icons --- .../classes/class.ilObjCategoryGUI.php | 101 +--- .../ContentPage/LuceneObjectDefinition.xml | 17 + .../classes/class.ilContentPageDataSet.php | 139 +++++ .../classes/class.ilContentPageExporter.php | 80 +++ .../classes/class.ilContentPageImporter.php | 46 ++ .../classes/class.ilContentPageLP.php | 27 + .../classes/class.ilContentPagePage.php | 16 + ...lass.ilContentPagePageCommandForwarder.php | 199 +++++++ .../classes/class.ilContentPagePageConfig.php | 9 + .../classes/class.ilContentPagePageGUI.php | 37 ++ .../classes/class.ilObjContentPage.php | 77 +++ .../classes/class.ilObjContentPageAccess.php | 43 ++ .../classes/class.ilObjContentPageGUI.php | 558 ++++++++++++++++++ .../classes/class.ilObjContentPageListGUI.php | 43 ++ ...interface.ilContentPageObjectConstants.php | 26 + Modules/ContentPage/module.xml | 20 + .../Course/classes/class.ilObjCourseGUI.php | 131 +--- .../Folder/classes/class.ilObjFolderGUI.php | 109 +--- Modules/Group/classes/class.ilObjGroupGUI.php | 99 +--- .../classes/class.ilObjRootFolderGUI.php | 25 +- .../classes/class.ilObjStudyProgramme.php | 55 +- .../Container/classes/class.ilContainer.php | 121 ---- .../classes/class.ilContainerAccess.php | 4 +- .../classes/class.ilContainerGUI.php | 60 +- .../Init/classes/class.ilInitialisation.php | 12 + .../classes/class.ilObjMediaObject.php | 1 + ...ass.ilContainerCustomIconConfiguration.php | 26 + .../class.ilObjectCustomIconConfiguration.php | 50 ++ ...ass.ilObjectCustomIconConfigurationGUI.php | 146 +++++ .../class.ilObjectCustomIconFactory.php | 81 +++ .../classes/class.ilObjectCustomIconImpl.php | 249 ++++++++ ...erface.ilCustomIconObjectConfiguration.php | 31 + .../interface.ilObjectCustomIcon.php | 55 ++ ....ilObjectCustomIconUploadPostProcessor.php | 13 + Services/Object/classes/class.ilObject.php | 28 +- .../Object/classes/class.ilObjectAccess.php | 19 +- .../class.ilObjectAppEventListener.php | 24 + Services/Object/classes/class.ilObjectGUI.php | 41 ++ Services/Object/classes/class.ilObjectLP.php | 89 +-- Services/Object/service.xml | 4 + .../class.ilPDSelectedItemsBlockGUI.php | 20 +- .../classes/class.ilObjRepositorySettings.php | 70 +-- .../classes/class.ilTrashTableGUI.php | 6 +- .../classes/class.ilLPTableBaseGUI.php | 1 + .../class.ilLearningProgressBaseGUI.php | 1 + ...lass.ilLPCollectionOfRepositoryObjects.php | 55 +- .../classes/status/class.ilLPStatusManual.php | 1 + src/UI/Component/Icon/Standard.php | 1 + .../Component/Icon/Standard.php | 1 + templates/default/images/icon_copa.svg | 25 + xml/ilias_copa_5_4.xsd | 25 + 51 files changed, 2402 insertions(+), 715 deletions(-) create mode 100644 Modules/ContentPage/LuceneObjectDefinition.xml create mode 100644 Modules/ContentPage/classes/class.ilContentPageDataSet.php create mode 100644 Modules/ContentPage/classes/class.ilContentPageExporter.php create mode 100644 Modules/ContentPage/classes/class.ilContentPageImporter.php create mode 100644 Modules/ContentPage/classes/class.ilContentPageLP.php create mode 100644 Modules/ContentPage/classes/class.ilContentPagePage.php create mode 100644 Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php create mode 100644 Modules/ContentPage/classes/class.ilContentPagePageConfig.php create mode 100644 Modules/ContentPage/classes/class.ilContentPagePageGUI.php create mode 100644 Modules/ContentPage/classes/class.ilObjContentPage.php create mode 100644 Modules/ContentPage/classes/class.ilObjContentPageAccess.php create mode 100644 Modules/ContentPage/classes/class.ilObjContentPageGUI.php create mode 100644 Modules/ContentPage/classes/class.ilObjContentPageListGUI.php create mode 100644 Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php create mode 100644 Modules/ContentPage/module.xml create mode 100644 Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php create mode 100644 Services/Object/Icon/classes/class.ilObjectCustomIconConfiguration.php create mode 100644 Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php create mode 100644 Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php create mode 100644 Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php create mode 100644 Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php create mode 100644 Services/Object/Icon/interfaces/interface.ilObjectCustomIcon.php create mode 100644 Services/Object/Icon/interfaces/interface.ilObjectCustomIconUploadPostProcessor.php create mode 100644 Services/Object/classes/class.ilObjectAppEventListener.php create mode 100644 templates/default/images/icon_copa.svg create mode 100644 xml/ilias_copa_5_4.xsd diff --git a/Modules/Category/classes/class.ilObjCategoryGUI.php b/Modules/Category/classes/class.ilObjCategoryGUI.php index 225b2140dbbb..b611625348cb 100755 --- a/Modules/Category/classes/class.ilObjCategoryGUI.php +++ b/Modules/Category/classes/class.ilObjCategoryGUI.php @@ -14,7 +14,7 @@ * @ilCtrl_Calls ilObjCategoryGUI: ilPermissionGUI, ilContainerPageGUI, ilContainerLinkListGUI, ilObjUserGUI, ilObjUserFolderGUI * @ilCtrl_Calls ilObjCategoryGUI: ilInfoScreenGUI, ilObjStyleSheetGUI, ilCommonActionDispatcherGUI, ilObjectTranslationGUI * @ilCtrl_Calls ilObjCategoryGUI: ilColumnGUI, ilObjectCopyGUI, ilUserTableGUI, ilDidacticTemplateGUI, ilExportGUI -* @ilCtrl_Calls ilObjCategoryGUI: ilObjTaxonomyGUI, ilObjectMetaDataGUI +* @ilCtrl_Calls ilObjCategoryGUI: ilObjTaxonomyGUI, ilObjectMetaDataGUI, ilObjectCustomIconConfigurationGUI * * @ingroup ModulesCategory */ @@ -249,6 +249,20 @@ function executeCommand() $this->ctrl->forwardCommand($this->getObjectMetadataGUI()); break; + case 'ilobjectcustomiconconfigurationgui': + if (!$this->checkPermissionBool('write') || !$this->settings->get('custom_icons')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->prepareOutput(); + + $this->setEditTabs('icons'); + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php'; + $gui = new \ilObjectCustomIconConfigurationGUI($GLOBALS['DIC'], $this, $this->object); + $this->ctrl->forwardCommand($gui); + break; + default: if ($cmd == "infoScreen") { @@ -713,12 +727,12 @@ protected function setEditTabs($active_tab = "settings_misc") $this->lng->txt("obj_multilinguality"), $this->ctrl->getLinkTargetByClass("ilobjecttranslationgui", "")); - // custom icon - if ($ilSetting->get("custom_icons")) - { - $this->tabs_gui->addSubTab("icons", - $this->lng->txt("icon_settings"), - $this->ctrl->getLinkTarget($this,'editIcons')); + if ($ilSetting->get('custom_icons')) { + $this->tabs_gui->addSubTab( + 'icons', + $this->lng->txt('icon_settings'), + $this->ctrl->getLinkTargetByClass('ilobjectcustomiconconfigurationgui') + ); } $this->tabs_gui->activateTab("settings"); @@ -1558,79 +1572,6 @@ public static function _goto($a_target) } - - //// - //// Icons - //// - - /** - * Edit folder icons - */ - function editIconsObject($a_form = null) - { - $tpl = $this->tpl; - - $this->checkPermission('write'); - - $this->tabs_gui->setTabActive('settings'); - - if(!$a_form) - { - $a_form = $this->initIconsForm(); - } - - $tpl->setContent($a_form->getHTML()); - } - - function initIconsForm() - { - $this->setEditTabs("icons"); - - include_once "Services/Form/classes/class.ilPropertyFormGUI.php"; - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - - $this->showCustomIconsEditing(1, $form); - - // $form->setTitle($this->lng->txt('edit_grouping')); - $form->addCommandButton('updateIcons', $this->lng->txt('save')); - - return $form; - } - - /** - * update container icons - */ - function updateIconsObject() - { - $ilSetting = $this->settings; - - $this->checkPermission('write'); - - $form = $this->initIconsForm(); - if($form->checkInput()) - { - //save custom icons - if ($ilSetting->get("custom_icons")) - { - if($_POST["cont_icon_delete"]) - { - $this->object->removeCustomIcon(); - } - $this->object->saveIcons($_FILES["cont_icon"]['tmp_name']); - } - if ($_FILES["cont_icon"]['tmp_name'] || $_POST["cont_icon_delete"]) - { - ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"),true); - } - $this->ctrl->redirect($this,"editIcons"); - } - - $form->setValuesByPost(); - $this->editIconsObject($form); - } - - // // taxonomy // diff --git a/Modules/ContentPage/LuceneObjectDefinition.xml b/Modules/ContentPage/LuceneObjectDefinition.xml new file mode 100644 index 000000000000..87b9fd58c11a --- /dev/null +++ b/Modules/ContentPage/LuceneObjectDefinition.xml @@ -0,0 +1,17 @@ + + + + + + + + SELECT content FROM page_object + WHERE page_id = ? AND parent_id = page_id AND parent_type = 'copa' + + + + + + + + \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageDataSet.php b/Modules/ContentPage/classes/class.ilContentPageDataSet.php new file mode 100644 index 000000000000..7eb0d585ad6f --- /dev/null +++ b/Modules/ContentPage/classes/class.ilContentPageDataSet.php @@ -0,0 +1,139 @@ + 'integer', + 'title' => 'text', + 'description' => 'text', + ]; + + default: + return []; + } + } + + /** + * @inheritdoc + */ + public function readData($a_entity, $a_version, $a_ids) + { + $this->data = []; + + if (!is_array($a_ids)) { + $a_ids = [$a_ids]; + } + + $this->readEntityData($a_entity, $a_ids); + } + + + /** + * @param string $entity + * @param array $ids + */ + protected function readEntityData($entity, $ids) + { + switch ($entity) { + case self::OBJ_TYPE: + foreach ($ids as $objId) { + if (\ilObject::_lookupType($objId) == self::OBJ_TYPE) { + /** @var \ilObjContentPage $obj */ + $obj = \ilObjectFactory::getInstanceByObjId($objId); + + $this->data[] = [ + 'id' => $obj->getId(), + 'title' => $obj->getTitle(), + 'description' => $obj->getDescription(), + ]; + } + } + break; + + default: + break; + } + } + + /** + * @param $a_entity + * @param $a_types + * @param $a_rec + * @param \ilImportMapping $a_mapping + * @param $a_schema_version + */ + public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_version) + { + switch ($a_entity) { + case self::OBJ_TYPE: + if ($newObjId = $a_mapping->getMapping('Services/Container', 'objs', $a_rec['id'])) { + $newObject = \ilObjectFactory::getInstanceByObjId($newObjId, false); + } else { + $newObject = new \ilObjContentPage(); + } + + $newObject->setTitle($a_rec['title']); + $newObject->setDescription($a_rec['description']); + + if (!$newObject->getId()) { + $newObject->create(); + } + + $a_mapping->addMapping('Modules/ContentPage', self::OBJ_TYPE, $a_rec['id'], $newObject->getId()); + $a_mapping->addMapping( + 'Services/COPage', + 'pg', + self::OBJ_TYPE . ':' . $a_rec['id'], + self::OBJ_TYPE . ':' . $newObject->getId() + ); + break; + } + } + + /** + * @inheritdoc + */ + protected function getDependencies($a_entity, $a_version, $a_rec, $a_ids) + { + return false; + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageExporter.php b/Modules/ContentPage/classes/class.ilContentPageExporter.php new file mode 100644 index 000000000000..9d744c6c6e80 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilContentPageExporter.php @@ -0,0 +1,80 @@ +ds = new \ilContentPageDataSet(); + $this->ds->setDSPrefix('ds'); + } + + /** + * @inheritdoc + */ + public function getXmlRepresentation($a_entity, $a_schema_version, $a_id) + { + ilUtil::makeDirParents($this->getAbsoluteExportDirectory()); + $this->ds->setExportDirectories($this->dir_relative, $this->dir_absolute); + + return $this->ds->getXmlRepresentation($a_entity, $a_schema_version, $a_id, '', true, true); + } + + /** + * @inheritdoc + */ + public function getValidSchemaVersions($a_entity) + { + return array( + '5.4.0' => array( + 'namespace' => 'http://www.ilias.de/Modules/ContentPage/' . self::OBJ_TYPE . '/5_4', + 'xsd_file' => 'ilias_' . self::OBJ_TYPE . '_5_4.xsd', + 'uses_dataset' => true, + 'min' => '5.4.0', + 'max' => '', + ), + ); + } + + /** + * @inheritdoc + */ + public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_ids) { + $pageObjectIds = []; + + foreach ($a_ids as $copaObjId) { + $copa = \ilObjectFactory::getInstanceByObjId($copaObjId, false); + if (!$copa || !($copa instanceof \ilObjContentPage)) { + continue; + } + + $copaPageObjIds = $copa->getPageObjIds(); + foreach ($copaPageObjIds as $copaPageObjId) { + $pageObjectIds[] = self::OBJ_TYPE . ':' . $copaPageObjId; + } + } + + if (count($pageObjectIds)) { + return [ + [ + 'component' => 'Services/COPage', + 'entity' => 'pg', + 'ids' => $pageObjectIds, + ] + ]; + } + + return []; + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageImporter.php b/Modules/ContentPage/classes/class.ilContentPageImporter.php new file mode 100644 index 000000000000..3fd977daebc2 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilContentPageImporter.php @@ -0,0 +1,46 @@ +ds = new ilContentPageDataSet(); + $this->ds->setDSPrefix('ds'); + $this->ds->setImportDirectory($this->getImportDirectory()); + } + + /** + * @inheritdoc + */ + public function importXmlRepresentation($a_entity, $a_id, $a_xml, $a_mapping) + { + $parser = new ilDataSetImportParser($a_entity, $this->getSchemaVersion(), $a_xml, $this->ds, $a_mapping); + } + + /** + * @inheritdoc + */ + public function finalProcessing($a_mapping) + { + parent::finalProcessing($a_mapping); + + $copaMap = $a_mapping->getMappingsOfEntity('Services/COPage', 'pg'); + foreach ($copaMap as $oldCopaId => $newCopaId) { + $newCopaId = substr($newCopaId, strlen(self::OBJ_TYPE) + 1); + + ilContentPagePage::_writeParentId(self::OBJ_TYPE, $newCopaId, $newCopaId); + } + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageLP.php b/Modules/ContentPage/classes/class.ilContentPageLP.php new file mode 100644 index 000000000000..d9698ce74b7a --- /dev/null +++ b/Modules/ContentPage/classes/class.ilContentPageLP.php @@ -0,0 +1,27 @@ +ctrl = $ctrl; + $this->tabs = $tabs; + $this->lng = $lng; + $this->parentObject = $parentObject; + + $this->tabs->clearTargets(); + $this->lng->loadLanguageModule('content'); + + $this->backUrl = isset($request->getQueryParams()['backurl']) ? $request->getQueryParams()['backurl'] : ''; + + if (strlen($this->backUrl) > 0) { + $this->ctrl->setParameterByClass('ilcontentpagepagegui', 'backurl', rawurlencode($this->backUrl)); + } + } + + /** + * @param string $pageObjectType + * @param int $pageObjectId + * @return \ilContentPagePageGUI + */ + protected function getPageObjectGUI($pageObjectType, $pageObjectId) + { + $pageObjectGUI = new \ilContentPagePageGUI($pageObjectId); + + $pageObjectGUI->obj->addUpdateListener($this->parentObject, 'update'); + + return $pageObjectGUI; + } + + /** + * @param string $pageObjectType + * @param int $pageObjectId + */ + protected function ensurePageObjectExists($pageObjectType, $pageObjectId) + { + if (!\ilContentPagePage::_exists($pageObjectType, $pageObjectId)) { + $pageObject = new \ilContentPagePage(); + $pageObject->setParentId($this->parentObject->getId()); + $pageObject->setId($pageObjectId); + $pageObject->createFromXML(); + } + } + + /** + * + */ + protected function setBackLinkTab() + { + $backUrl = $this->ctrl->getLinkTargetByClass('ilObjContentPageGUI', self::UI_CMD_VIEW); + if (strlen($this->backUrl) > 0) { + $backUrlParts = parse_url(\ilUtil::stripSlashes($this->backUrl)); + + $script = basename($backUrlParts['path']); + + $backUrl = './' . implode('?', [ + $script, $backUrlParts['query'] + ]); + } + + $this->tabs->setBackTarget($this->lng->txt('back'), $backUrl); + } + + /** + * @return \ilContentPagePageGUI + */ + protected function buildEditingPageObjectGUI() + { + $this->setBackLinkTab(); + + $this->ensurePageObjectExists( + $this->parentObject->getType(), $this->parentObject->getId() + ); + + $pageObjectGUI = $this->getPageObjectGUI( + $this->parentObject->getType(), $this->parentObject->getId() + ); + + $pageObjectGUI->setEnabledTabs(true); + + return $pageObjectGUI; + } + + /** + * @return \ilContentPagePageGUI + */ + protected function buildPresentationPageObjectGUI() + { + $this->setBackLinkTab(); + + $this->ensurePageObjectExists( + $this->parentObject->getType(), $this->parentObject->getId() + ); + + $pageObjectGUI = $this->getPageObjectGUI( + $this->parentObject->getType(), $this->parentObject->getId() + ); + + $pageObjectGUI->setEnabledTabs(false); + + return $pageObjectGUI; + } + + /** + * @param string $presentationMode + */ + public function setPresentationMode($presentationMode) + { + $this->presentationMode = $presentationMode; + } + + /** + * @return string + * @throws \ilException + */ + public function forward() + { + switch ($this->presentationMode) { + case self::PRESENTATION_MODE_EDITING: + + $pageObjectGui = $this->buildEditingPageObjectGUI(); + return $this->ctrl->forwardCommand($pageObjectGui); + + case self::PRESENTATION_MODE_PRESENTATION: + + $pageObjectGUI = $this->buildPresentationPageObjectGUI(); + $this->ctrl->setCmd('getHTML'); + + return $this->ctrl->forwardCommand($pageObjectGUI); + + default: + throw new \ilException('Unknown presentation mode given'); + break; + } + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPagePageConfig.php b/Modules/ContentPage/classes/class.ilContentPagePageConfig.php new file mode 100644 index 000000000000..e24174e1d908 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilContentPagePageConfig.php @@ -0,0 +1,9 @@ +setTemplateTargetVar('ADM_CONTENT'); + $this->setTemplateOutput(true); + + require_once 'Services/Style/Content/classes/class.ilObjStyleSheet.php'; + + $tpl->setCurrentBlock("SyntaxStyle"); + $tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", \ilObjStyleSheet::getSyntaxStylePath()); + $tpl->parseCurrentBlock(); + + $tpl->setCurrentBlock("ContentStyle"); + $tpl->setVariable("LOCATION_CONTENT_STYLESHEET", \ilObjStyleSheet::getContentStylePath(0)); + $tpl->parseCurrentBlock(); + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php new file mode 100644 index 000000000000..b6ebc634dd99 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -0,0 +1,77 @@ +type = self::OBJ_TYPE; + } + + /** + * @inheritdoc + */ + protected function doCloneObject($new_obj, $a_target_id, $a_copy_id = null) + { + parent::doCloneObject($new_obj, $a_target_id, $a_copy_id); + + if (\ilContentPagePage::_exists($this->getType(), $this->getId())) { + $originalPageObject = new \ilContentPagePage($this->getId()); + $originalXML = $originalPageObject->getXMLContent(); + + $duplicatePageObject = new \ilContentPagePage(); + $duplicatePageObject->setId($new_obj->getId()); + $duplicatePageObject->setParentId($new_obj->getId()); + $duplicatePageObject->setXMLContent($originalXML); + $duplicatePageObject->createFromXML(); + } + } + + /** + * @inheritdoc + */ + protected function doUpdate() + { + parent::doUpdate(); + } + + /** + * @inheritdoc + */ + protected function doDelete() + { + parent::doDelete(); + + if (\ilContentPagePage::_exists($this->getType(), $this->getId())) { + $originalPageObject = new \ilContentPagePage($this->getId()); + $originalPageObject->delete(); + } + } + + /** + * @return int[] + */ + public function getPageObjIds() + { + $pageObjIds = []; + + $sql = "SELECT page_id FROM page_object WHERE parent_id = %s AND parent_type = %s"; + $res = $this->db->queryF( + $sql, + ['integer', 'text'], + [$this->getId(), $this->getType()] + ); + + while ($row = $this->db->fetchAssoc($res)) { + $pageObjIds[] = $row['page_id']; + } + + return $pageObjIds; + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPageAccess.php b/Modules/ContentPage/classes/class.ilObjContentPageAccess.php new file mode 100644 index 000000000000..25a4da8bd272 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilObjContentPageAccess.php @@ -0,0 +1,43 @@ + 'read', + 'cmd' => self::UI_CMD_VIEW, + 'lang_var' => 'show', + 'default' => true + ], + [ + 'permission'=> 'write', + 'cmd' => 'edit', + 'lang_var' => 'settings' + ], + ]; + + return $commands; + } + + /** + * @inheritdoc + */ + public static function _checkGoto($a_target) { + $targetAttributes = explode('_', $a_target); + + if (2 != count($targetAttributes) || $targetAttributes[0] != self::OBJ_TYPE || ((int)$targetAttributes[1]) <= 0) { + return false; + } + + return parent::_checkGoto($a_target); + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php new file mode 100644 index 000000000000..e1070377b785 --- /dev/null +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -0,0 +1,558 @@ +dic = $DIC; + $this->request = $this->dic->http()->request(); + $this->settings = $this->dic->settings(); + $this->access = $this->dic->access(); + $this->ctrl = $this->dic->ctrl(); + $this->tabs = $this->dic->tabs(); + $this->user = $this->dic->user(); + $this->navHistory = $this->dic['ilNavigationHistory']; + $this->error = $this->dic['ilErr']; + + $this->lng->loadLanguageModule('copa'); + + if ($this->object instanceof \ilObjContentPage) { + $this->infoScreenEnabled = ilContainer::_lookupContainerSetting( + $this->object->getId(), + ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, + true + ); + } + } + + /** + * @inheritdoc + */ + protected function afterSave(ilObject $a_new_object) + { + \ilUtil::sendSuccess($this->lng->txt('object_added'), true); + $this->ctrl->redirect($this, 'edit'); + } + + /** + * @inheritdoc + */ + public function getType() + { + return self::OBJ_TYPE; + } + + /** + * @inheritdoc + */ + public function setTabs() + { + if ($this->checkPermissionBool('read')) { + $this->tabs->addTab( + self::UI_TAB_ID_CONTENT, + $this->lng->txt('content'), + $this->ctrl->getLinkTarget($this, self::UI_CMD_VIEW) + ); + } + + if ($this->infoScreenEnabled && ($this->checkPermissionBool('visible') || $this->checkPermissionBool('read'))) { + $this->tabs->addTab( + self::UI_TAB_ID_INFO, + $this->lng->txt('info_short'), + $this->ctrl->getLinkTargetByClass('ilinfoscreengui', 'showSummary') + ); + } + + if ($this->checkPermissionBool('write')) { + $this->tabs->addTab( + self::UI_TAB_ID_SETTINGS, + $this->lng->txt('settings'), + $this->ctrl->getLinkTarget($this, self::UI_CMD_EDIT) + ); + } + + if (\ilLearningProgressAccess::checkAccess($this->object->getRefId())) { + $this->tabs->addTab( + self::UI_TAB_ID_LP, + $this->lng->txt('learning_progress'), + $this->ctrl->getLinkTargetByClass('illearningprogressgui') + ); + } + + if ($this->checkPermissionBool('write')) { + $this->tabs->addTab( + self::UI_TAB_ID_EXPORT, + $this->lng->txt('export'), + $this->ctrl->getLinkTargetByClass('ilexportgui') + ); + } + + if ($this->checkPermissionBool('edit_permission')) { + $this->tabs->addTab( + self::UI_TAB_ID_PERMISSIONS, + $this->lng->txt('perm_settings'), + $this->ctrl->getLinkTargetByClass('ilpermissiongui', 'perm') + ); + } + } + + /** + * Sub tab configuration of the content area + */ + protected function setContentSubTabs() + { + if ($this->checkPermissionBool('write')) { + $this->tabs->addSubTab( + self::UI_TAB_ID_CONTENT, + $this->lng->txt('view'), + $this->ctrl->getLinkTarget($this, self::UI_CMD_VIEW) + ); + + if (!$this->user->isAnonymous()) { + $this->lng->loadLanguageModule('cntr'); + $this->tabs->addSubTab( + 'page_editor', + $this->lng->txt('cntr_text_media_editor'), + $this->ctrl->getLinkTargetByClass('ilContentPagePageGUI', 'edit') + ); + } + } + } + + /** + * Sub tab configuration of the settings area + * @param string $activeTab + */ + protected function setSettingsSubTabs($activeTab) + { + if ($this->checkPermissionBool('write')) { + $this->tabs->addSubTab( + self::UI_TAB_ID_SETTINGS, + $this->lng->txt('settings'), + $this->ctrl->getLinkTarget($this, self::UI_CMD_EDIT) + ); + + if ($this->settings->get('custom_icons')) { + $this->tabs_gui->addSubTab( + self::UI_TAB_ID_ICON, + $this->lng->txt('icon_settings'), + $this->ctrl->getLinkTargetByClass('ilObjectCustomIconConfigurationGUI') + ); + } + + $this->tabs->setSubTabActive($activeTab); + } + } + + /** + * @inheritdoc + */ + protected function setTitleAndDescription() + { + parent::setTitleAndDescription(); + + $icon = ilObject::_getIcon($this->object->getId(), 'big', $this->object->getType()); + $this->tpl->setTitleIcon($icon, $this->lng->txt('obj_'.$this->object->getType())); + } + + /** + * @inheritdoc + */ + public function executeCommand() + { + $nextClass = $this->ctrl->getNextClass($this); + $cmd = $this->ctrl->getCmd(self::UI_CMD_VIEW); + + $this->addToNavigationHistory(); + + switch (strtolower($nextClass)) { + case 'ilcontentpagepagegui': + if (!$this->checkPermissionBool('write') || $this->user->isAnonymous()) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->prepareOutput(); + + $forwarder = new \ilContentPagePageCommandForwarder($this->request, $this->ctrl, $this->tabs, $this->lng, $this->object); + $forwarder->forward(); + break; + + case 'ilinfoscreengui': + if (!$this->infoScreenEnabled) { + return; + } + $this->prepareOutput(); + + $this->infoScreenForward(); + break; + + case 'ilcommonactiondispatchergui': + $this->ctrl->forwardCommand(\ilCommonActionDispatcherGUI::getInstanceFromAjaxCall()); + break; + + case 'ilpermissiongui': + $this->checkPermission('edit_permission'); + + $this->prepareOutput(); + $this->tabs->activateTab(self::UI_TAB_ID_PERMISSIONS); + + $this->ctrl->forwardCommand(new \ilPermissionGUI($this)); + break; + + case 'illearningprogressgui': + if (!\ilLearningProgressAccess::checkAccess($this->object->getRefId())) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->prepareOutput(); + $this->tabs->activateTab(self::UI_TAB_ID_LP); + + $this->ctrl->forwardCommand(new \ilLearningProgressGUI( + \ilLearningProgressGUI::LP_CONTEXT_REPOSITORY, + $this->object->getRefId(), + isset($this->request->getQueryParams()['user_id']) && is_numeric($this->request->getQueryParams()['user_id']) ? + (int)$this->request->getQueryParams()['user_id'] : + $this->user->getId() + )); + break; + + case 'ilexportgui': + $this->checkPermission('write'); + + $this->prepareOutput(); + $this->tabs->activateTab(self::UI_TAB_ID_EXPORT); + + $gui = new \ilExportGUI($this); + $gui->addFormat('xml'); + $this->ctrl->forwardCommand($gui); + break; + + case 'ilobjectcustomiconconfigurationgui': + if (!$this->checkPermissionBool('write') || !$this->settings->get('custom_icons')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->prepareOutput(); + $this->tabs->activateTab(self::UI_TAB_ID_SETTINGS); + $this->setSettingsSubTabs(self::UI_TAB_ID_ICON); + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php'; + $gui = new \ilObjectCustomIconConfigurationGUI($this->dic, $this, $this->object); + $this->ctrl->forwardCommand($gui); + break; + + case 'ilobjectcopygui': + $this->tpl->getStandardTemplate(); + + $gui = new \ilObjectCopyGUI($this); + $gui->setType($this->object->getType()); + + $this->ctrl->forwardCommand($gui); + break; + + default: + switch (true) { + case in_array(strtolower($cmd), array_map('strtolower', [self::UI_CMD_EDIT, self::UI_CMD_UPDATE])): + $this->setSettingsSubTabs(self::UI_TAB_ID_SETTINGS); + break; + } + + if (in_array(strtolower($cmd), array_map('strtolower', ['addToDesk', 'removeFromDesk']))) { + $this->ctrl->setCmd($cmd . 'Object'); + } + + return parent::executeCommand(); + } + } + + /** + * @inheritdoc + */ + public function addLocatorItems() + { + if ($this->object instanceof \ilObject) { + $this->locator->addItem( + $this->object->getTitle(), + $this->ctrl->getLinkTarget($this, self::UI_CMD_VIEW), + '', + $this->object->getRefId() + ); + } + } + + /** + * + */ + public function addToNavigationHistory() { + if(!$this->getCreationMode()) { + if($this->checkPermissionBool('read')) { + $this->navHistory->addItem( + $this->object->getRefId(), + \ilLink::_getLink($this->object->getRefId(), $this->object->getType()), + $this->object->getType() + ); + $this->addHeaderAction(); + } + } + } + + /** + * + */ + public function infoScreen() + { + $this->ctrl->setCmd('showSummary'); + $this->ctrl->setCmdClass('ilinfoscreengui'); + + $this->infoScreenForward(); + } + + /** + * + */ + public function infoScreenForward() + { + if (!$this->infoScreenEnabled) { + return; + } + + if (!$this->checkPermissionBool('visible') && !$this->checkPermissionBool('read')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->tabs->activateTab(self::UI_TAB_ID_INFO); + + $info = new \ilInfoScreenGUI($this); + $info->enableLearningProgress(true); + $info->enablePrivateNotes(); + $info->addMetaDataSections($this->object->getId(), 0, $this->object->getType()); + + $this->ctrl->forwardCommand($info); + } + + /** + * @inheritdoc + */ + protected function initEditCustomForm(\ilPropertyFormGUI $a_form) + { + $sh = new ilFormSectionHeaderGUI(); + $sh->setTitle($this->lng->txt('obj_features')); + $a_form->addItem($sh); + + \ilObjectServiceSettingsGUI::initServiceSettingsForm( + $this->object->getId(), + $a_form, + array( + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY + ) + ); + } + + /** + * @inheritdoc + */ + protected function getEditFormCustomValues(array &$a_values) + { + $a_values[\ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY] = $this->infoScreenEnabled; + } + + /** + * @inheritdoc + */ + protected function updateCustom(\ilPropertyFormGUI $a_form) + { + \ilObjectServiceSettingsGUI::updateServiceSettingsForm( + $this->object->getId(), + $a_form, + array( + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY + ) + ); + } + + /** + * Deep link + * @param string $target + */ + public static function _goto($target) + { + global $DIC; + + $targetAttributes = explode('_', $target); + $refId = (int)$targetAttributes[0]; + + if ((int) $refId <= 0) { + $DIC['ilErr']->raiseError($DIC->language()->txt('msg_no_perm_read'), $DIC['ilErr']->FATAL); + } + + if ($DIC->access()->checkAccess('read', '', $refId)) { + $DIC->ctrl()->setTargetScript('ilias.php'); + $DIC->ctrl()->initBaseClass('ilRepositoryGUI'); + $DIC->ctrl()->setParameterByClass(__CLASS__, 'ref_id', $refId); + $DIC->ctrl()->redirectByClass(array( + 'ilRepositoryGUI', + __CLASS__, + ), self::UI_CMD_VIEW); + } else if($DIC->access()->checkAccess('read', '', ROOT_FOLDER_ID)) { + \ilUtil::sendInfo(sprintf( + $DIC->language()->txt('msg_no_perm_read_item'), \ilObject::_lookupTitle(\ilObject::_lookupObjId($refId)) + ), true); + + $_GET['target'] = ''; + $_GET['ref_id'] = ROOT_FOLDER_ID; + $_GET['baseClass'] = 'ilRepositoryGUI'; + + include 'ilias.php'; + exit(); + } + + $DIC['ilErr']->raiseError($DIC->language()->txt('msg_no_perm_read'), $DIC['ilErr']->FATAL); + } + + /** + * @inheritdoc + */ + public function addToDeskObject() + { + if ((int)$this->settings->get('disable_my_offers')) { + $this->ctrl->redirect($this, self::UI_CMD_VIEW); + } + + \ilDesktopItemGUI::addToDesktop(); + \ilUtil::sendSuccess($this->lng->txt('added_to_desktop'), true); + $this->ctrl->redirect($this, self::UI_CMD_VIEW); + } + + /** + * @inheritdoc + */ + public function removeFromDeskObject() + { + if ((int)$this->settings->get('disable_my_offers')) { + $this->ctrl->redirect($this, self::UI_CMD_VIEW); + } + + \ilDesktopItemGUI::removeFromDesktop(); + \ilUtil::sendSuccess($this->lng->txt('removed_from_desktop'), true); + $this->ctrl->redirect($this, self::UI_CMD_VIEW); + } + + /** + * @param string $ctrlLink + * @return string + */ + public function getContent($ctrlLink = '') + { + if (\ilContentPagePage::_exists($this->object->getType(), $this->object->getId()) && $this->checkPermissionBool('read')) { + $pageGui = new \ilContentPagePageGUI($this->object->getId()); + $pageGui->setEnabledTabs(false); + + if (is_string($ctrlLink) && strlen($ctrlLink) > 0) { + $pageGui->setFileDownloadLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_FILE); + $pageGui->setFullscreenLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DISPLAY_FULLSCREEN); + $pageGui->setSourcecodeDownloadScript($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_PARAGRAPH); + } + + $html = $pageGui->getHTML(); + + \ilChangeEvent::_recordReadEvent( + $this->object->getType(), + $this->object->getRefId(), + $this->object->getId(), + $this->user->getId() + ); + + return $html; + } + + return ''; + } + + /** + * Shows the content of the object + */ + public function view() + { + $this->checkPermission('read'); + + $this->setContentSubTabs(); + + $this->tabs->activateTab(self::UI_TAB_ID_CONTENT); + $this->tabs->activateSubTab(self::UI_TAB_ID_CONTENT); + + $this->tpl->setPermanentLink($this->object->getType(), $this->object->getRefId(), '', '_top'); + + $this->tpl->setContent($this->getContent()); + } +} \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php new file mode 100644 index 000000000000..a6e1ecba996e --- /dev/null +++ b/Modules/ContentPage/classes/class.ilObjContentPageListGUI.php @@ -0,0 +1,43 @@ +static_link_enabled = true; + $this->delete_enabled = true; + $this->cut_enabled = true; + $this->copy_enabled = true; + $this->subscribe_enabled = true; + $this->link_enabled = true; + $this->info_screen_enabled = true; + $this->type = self::OBJ_TYPE; + $this->gui_class_name = 'ilObjContentPageGUI'; + + $this->commands = ilObjContentPageAccess::_getCommands(); + } + + /** + * @inheritdoc + */ + public function getInfoScreenStatus() + { + if (\ilContainer::_lookupContainerSetting( + $this->obj_id, + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, + true + )) + { + return $this->info_screen_enabled; + } + + return false; + } +} \ No newline at end of file diff --git a/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php b/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php new file mode 100644 index 000000000000..4e1f44b3fe32 --- /dev/null +++ b/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php @@ -0,0 +1,26 @@ + + + + + + + cat + crs + fold + grp + recf + root + + + + + + diff --git a/Modules/Course/classes/class.ilObjCourseGUI.php b/Modules/Course/classes/class.ilObjCourseGUI.php index 1c3cd8675211..b2743b149141 100755 --- a/Modules/Course/classes/class.ilObjCourseGUI.php +++ b/Modules/Course/classes/class.ilObjCourseGUI.php @@ -24,7 +24,7 @@ * @ilCtrl_Calls ilObjCourseGUI: ilLOPageGUI, ilObjectMetaDataGUI, ilNewsTimelineGUI, ilContainerNewsSettingsGUI * @ilCtrl_Calls ilObjCourseGUI: ilCourseMembershipGUI, ilPropertyFormGUI, ilContainerSkillGUI, ilCalendarPresentationGUI * @ilCtrl_Calls ilObjCourseGUI: ilMemberExportSettingsGUI - * @ilCtrl_Calls ilObjCourseGUI: ilLTIProviderObjectSettingGUI + * @ilCtrl_Calls ilObjCourseGUI: ilLTIProviderObjectSettingGUI, ilObjectCustomIconConfigurationGUI * * @extends ilContainerGUI */ @@ -1517,43 +1517,6 @@ protected function getEditFormValues() // values are done in initEditForm() } - /** - * edit container icons - */ - function editCourseIconsObject($a_form = null) - { - global $DIC; - - $tpl = $DIC['tpl']; - - $this->checkPermission('write'); - - $this->setSubTabs("properties"); - $this->tabs_gui->setTabActive('settings'); - $this->tabs_gui->activateSubTab('icon_settings'); - - if(!$a_form) - { - $a_form = $this->initCourseIconsForm(); - } - - $tpl->setContent($a_form->getHTML()); - } - - function initCourseIconsForm() - { - include_once "Services/Form/classes/class.ilPropertyFormGUI.php"; - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - - $this->showCustomIconsEditing(1, $form); - - // $form->setTitle($this->lng->txt('edit_grouping')); - $form->addCommandButton('updateCourseIcons', $this->lng->txt('save')); - - return $form; - } - function sendFileObject() { include_once 'Modules/Course/classes/class.ilCourseFile.php'; @@ -1561,39 +1524,6 @@ function sendFileObject() ilUtil::deliverFile($file->getAbsolutePath(),$file->getFileName(),$file->getFileType()); return true; } - - /** - * update container icons - */ - function updateCourseIconsObject() - { - global $DIC; - - $ilSetting = $DIC['ilSetting']; - - $this->checkPermission('write'); - - $form = $this->initCourseIconsForm(); - if($form->checkInput()) - { - //save custom icons - if ($ilSetting->get("custom_icons")) - { - if($_POST["cont_icon_delete"]) - { - $this->object->removeCustomIcon(); - } - $this->object->saveIcons($_FILES["cont_icon"]['tmp_name']); - } - - ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"),true); - $this->ctrl->redirect($this,"editCourseIcons"); - } - - $form->setValuesByPost(); - $this->editCourseIconsObject($form); - } - /** * set sub tabs @@ -1639,12 +1569,12 @@ function setSubTabs($a_tab) ); } - // custom icon - if ($this->ilias->getSetting("custom_icons")) - { - $this->tabs_gui->addSubTabTarget("icon_settings", - $this->ctrl->getLinkTarget($this,'editCourseIcons'), - "editCourseIcons", get_class($this)); + if ($this->ilias->getSetting('custom_icons')) { + $this->tabs_gui->addSubTabTarget( + 'icon_settings', + $this->ctrl->getLinkTargetByClass('ilObjectCustomIconConfigurationGUI'), + 'editCourseIcons', get_class($this) + ); } // map settings @@ -1712,39 +1642,6 @@ function showPossibleSubObjects() parent::showPossibleSubObjects(); } - /** - * remove small icon - * - * @access public - */ - function removeSmallIconObject() - { - $this->object->removeSmallIcon(); - $this->ctrl->redirect($this,'editCourseIcons'); - } - - /** - * remove big icon - * - * @access public - */ - function removeBigIconObject() - { - $this->object->removeBigIcon(); - $this->ctrl->redirect($this,'editCourseIcons'); - } - - - /** - * remove small icon - * - * @access public - */ - function removeTinyIconObject() - { - $this->object->removeTinyIcon(); - $this->ctrl->redirect($this,'editCourseIcons'); - } /** * save object @@ -2680,6 +2577,20 @@ function executeCommand() $this->ctrl->forwardCommand($gui); break; + case 'ilobjectcustomiconconfigurationgui': + if (!$this->checkPermissionBool('write') || !$this->settings->get('custom_icons')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->setSubTabs('properties'); + $this->tabs_gui->activateTab('settings'); + $this->tabs_gui->activateSubTab('icon_settings'); + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php'; + $gui = new \ilObjectCustomIconConfigurationGUI($GLOBALS['DIC'], $this, $this->object); + $this->ctrl->forwardCommand($gui); + break; + default: /* if(!$this->creation_mode) { diff --git a/Modules/Folder/classes/class.ilObjFolderGUI.php b/Modules/Folder/classes/class.ilObjFolderGUI.php index 4babf6d22100..fa293c250123 100755 --- a/Modules/Folder/classes/class.ilObjFolderGUI.php +++ b/Modules/Folder/classes/class.ilObjFolderGUI.php @@ -13,7 +13,7 @@ * @ilCtrl_Calls ilObjFolderGUI: ilInfoScreenGUI, ilContainerPageGUI, ilColumnGUI * @ilCtrl_Calls ilObjFolderGUI: ilObjectCopyGUI, ilObjStyleSheetGUI * @ilCtrl_Calls ilObjFolderGUI: ilExportGUI, ilCommonActionDispatcherGUI, ilDidacticTemplateGUI -* @ilCtrl_Calls ilObjFolderGUI: ilBackgroundTaskHub +* @ilCtrl_Calls ilObjFolderGUI: ilBackgroundTaskHub, ilObjectCustomIconConfigurationGUI * * @extends ilObjectGUI */ @@ -113,6 +113,22 @@ function executeCommand() $ret =& $this->ctrl->forwardCommand($perm_gui); break; + case 'ilobjectcustomiconconfigurationgui': + if (!$this->checkPermissionBool('write') || !$this->settings->get('custom_icons')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->prepareOutput(); + + $this->setSubTabs('settings'); + $this->tabs_gui->activateTab('settings'); + $this->tabs_gui->activateSubTab('icons'); + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php'; + $gui = new \ilObjectCustomIconConfigurationGUI($GLOBALS['DIC'], $this, $this->object); + $this->ctrl->forwardCommand($gui); + break; + case 'ilcoursecontentgui': $this->prepareOutput(); include_once './Modules/Course/classes/class.ilCourseContentGUI.php'; @@ -562,92 +578,17 @@ function setSubTabs($a_tab) $ilTabs->addSubTab("settings", $lng->txt("fold_settings"), $this->ctrl->getLinkTarget($this,'edit')); - - // custom icon - if ($this->ilias->getSetting("custom_icons")) - { - $ilTabs->addSubTab("icons", - $lng->txt("icon_settings"), - $this->ctrl->getLinkTarget($this,'editIcons')); - } - - $ilTabs->activateSubTab($a_tab); - $ilTabs->activateTab("settings"); - } - - - //// - //// Icons - //// - - /** - * Edit folder icons - */ - function editIconsObject($a_form = null) - { - $tpl = $this->tpl; - $this->checkPermission('write'); - - $this->tabs_gui->setTabActive('settings'); - - if(!$a_form) - { - $a_form = $this->initIconsForm(); - } - - $tpl->setContent($a_form->getHTML()); - } - - function initIconsForm() - { - $this->setSubTabs("icons"); - - include_once "Services/Form/classes/class.ilPropertyFormGUI.php"; - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - - $this->showCustomIconsEditing(1, $form); - - // $form->setTitle($this->lng->txt('edit_grouping')); - $form->addCommandButton('updateIcons', $this->lng->txt('save')); - - return $form; - } - - /** - * update container icons - */ - function updateIconsObject() - { - $ilSetting = $this->settings; - - $this->checkPermission('write'); - - $form = $this->initIconsForm(); - if($form->checkInput()) - { - //save custom icons - if ($ilSetting->get("custom_icons")) - { - if($_POST["cont_icon_delete"]) - { - $this->object->removeCustomIcon(); - } - $this->object->saveIcons($_FILES["cont_icon"]['tmp_name']); - } - if ($_FILES["cont_icon"]['tmp_name'] || $_POST["cont_icon_delete"]) - { - ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"),true); - } - $this->ctrl->redirect($this,"editIcons"); + if ($this->ilias->getSetting('custom_icons')) { + $ilTabs->addSubTab( + 'icons', + $lng->txt('icon_settings'), + $this->ctrl->getLinkTargetByClass('ilobjectcustomiconconfigurationgui') + ); } - $form->setValuesByPost(); - $this->editIconsObject($form); + $ilTabs->activateSubTab($a_tab); + $ilTabs->activateTab("settings"); } - - - } // END class.ilObjFolderGUI ?> diff --git a/Modules/Group/classes/class.ilObjGroupGUI.php b/Modules/Group/classes/class.ilObjGroupGUI.php index 81930f23a40d..36fbf45cbd84 100755 --- a/Modules/Group/classes/class.ilObjGroupGUI.php +++ b/Modules/Group/classes/class.ilObjGroupGUI.php @@ -20,7 +20,7 @@ * @ilCtrl_Calls ilObjGroupGUI: ilCommonActionDispatcherGUI, ilObjectServiceSettingsGUI, ilSessionOverviewGUI * @ilCtrl_Calls ilObjGroupGUI: ilGroupMembershipGUI, ilBadgeManagementGUI, ilMailMemberSearchGUI, ilNewsTimelineGUI, ilContainerNewsSettingsGUI * @ilCtrl_Calls ilObjGroupGUI: ilContainerSkillGUI, ilCalendarPresentationGUI -* @ilCtrl_Calls ilObjGroupGUI: ilLTIProviderObjectSettingGUI +* @ilCtrl_Calls ilObjGroupGUI: ilLTIProviderObjectSettingGUI, ilObjectCustomIconConfigurationGUI * * * @@ -81,6 +81,20 @@ function executeCommand() switch($next_class) { + case 'ilobjectcustomiconconfigurationgui': + if (!$this->checkPermissionBool('write') || !$this->settings->get('custom_icons')) { + $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); + } + + $this->setSubTabs('settings'); + $this->tabs_gui->activateTab('settings'); + $this->tabs_gui->activateSubTab('grp_icon_settings'); + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php'; + $gui = new \ilObjectCustomIconConfigurationGUI($GLOBALS['DIC'], $this, $this->object); + $this->ctrl->forwardCommand($gui); + break; + case 'illtiproviderobjectsettinggui': $this->setSubTabs('properties'); $this->tabs_gui->activateTab('settings'); @@ -691,78 +705,7 @@ public function updateObject() return true; } } - - /** - * edit container icons - */ - public function editGroupIconsObject($a_form = null) - { - global $DIC; - - $tpl = $DIC['tpl']; - - $this->checkPermission('write'); - - $this->setSubTabs("settings"); - $this->tabs_gui->setTabActive('settings'); - $this->tabs_gui->setSubTabActive('grp_icon_settings'); - - if(!$a_form) - { - $a_form = $this->initGroupIconsForm(); - } - - $tpl->setContent($a_form->getHTML()); - } - - function initGroupIconsForm() - { - include_once "Services/Form/classes/class.ilPropertyFormGUI.php"; - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->ctrl->getFormAction($this)); - - $this->showCustomIconsEditing(1, $form); - - // $form->setTitle($this->lng->txt('edit_grouping')); - $form->addCommandButton('updateGroupIcons', $this->lng->txt('save')); - - return $form; - } - - /** - * update group icons - * - * @access public - * @return - */ - public function updateGroupIconsObject() - { - global $DIC; - - $ilSetting = $DIC['ilSetting']; - - $this->checkPermission('write'); - - $form = $this->initGroupIconsForm(); - if($form->checkInput()) - { - //save custom icons - if ($ilSetting->get("custom_icons")) - { - if($_POST["cont_icon_delete"]) - { - $this->object->removeCustomIcon(); - } - $this->object->saveIcons($_FILES["cont_icon"]['tmp_name']); - } - ilUtil::sendSuccess($this->lng->txt("msg_obj_modified"),true); - $this->ctrl->redirect($this,"editGroupIcons"); - } - $form->setValuesByPost(); - $this->editGroupIconsObject($form); - } - /** * Edit Map Settings */ @@ -1850,12 +1793,12 @@ protected function setSubTabs($a_tab) $this->ctrl->getLinkTarget($this,'editInfo'), "editInfo", get_class($this)); - // custom icon - if ($this->ilias->getSetting("custom_icons")) - { - $this->tabs_gui->addSubTabTarget("grp_icon_settings", - $this->ctrl->getLinkTarget($this,'editGroupIcons'), - "editGroupIcons", get_class($this)); + if ($this->ilias->getSetting('custom_icons')) { + $this->tabs_gui->addSubTabTarget( + 'grp_icon_settings', + $this->ctrl->getLinkTargetByClass('ilObjectCustomIconConfigurationGUI'), + 'editGroupIcons', get_class($this) + ); } include_once("./Services/Maps/classes/class.ilMapUtil.php"); diff --git a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php index 5fbce96d0bd0..95bf6edb976d 100755 --- a/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php +++ b/Modules/RootFolder/classes/class.ilObjRootFolderGUI.php @@ -343,15 +343,24 @@ function updateObject() { $this->saveSortingSettings($form); - // save custom icons - //save custom icons - if ($ilSetting->get("custom_icons")) - { - if($_POST["cont_icon_delete"]) - { - $this->object->removeCustomIcon(); + if ($ilSetting->get('custom_icons')) { + global $DIC; + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($this->object->getId(), $this->object->getType()); + + /** @var \ilImageFileInputGUI $item */ + $fileData = (array)$form->getInput('cont_icon'); + $item = $form->getItemByPostVar('cont_icon'); + + if ($item->getDeletionFlag()) { + $customIcon->remove(); + } + + if ($fileData['tmp_name']) { + $customIcon->saveFromHttpRequest(); } - $this->object->saveIcons($_FILES["cont_icon"]['tmp_name']); + // cognos-blu-patch: end } // hide icon/title diff --git a/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php b/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php index 7caadfc0ca86..13a37db62bdd 100644 --- a/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php +++ b/Modules/StudyProgramme/classes/class.ilObjStudyProgramme.php @@ -1275,17 +1275,23 @@ public function getRawSettings() { * */ public function updateCustomIcon() { + global $DIC; + + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($this->getId(), $this->getType()); + $subtype = $this->getSubType(); if($subtype) { if($this->webdir->has($subtype->getIconPath(true))) { $icon = $subtype->getIconPath(true); - $this->saveIcons($icon); + $customIcon->saveFromSourceFile($icon); } else { - $this->removeCustomIcon(); + $customIcon->remove(); } } else { - $this->removeCustomIcon(); + $customIcon->remove(); } } @@ -1328,49 +1334,6 @@ static public function getCreatableSubObjects($a_subobjects, $a_ref_id) { throw new ilException("Undefined mode for study programme: '$mode'"); } - - //////////////////////////////////// - // REWRITES FROM PARENT - //////////////////////////////////// - - /** - * save container icons - */ - function saveIcons($a_custom_icon) - { - $this->createContainerDirectory(); - $cont_dir = $this->getContainerDirectory(); - $file_name = ""; - if ($a_custom_icon != "") - { - $file_name = $cont_dir."/icon_custom.svg"; - if($this->webdir->has($file_name)) - { - $this->webdir->delete($file_name); - } - - $this->webdir->copy($a_custom_icon, $file_name); - - if ($file_name != "" && $this->webdir->has($file_name)) - { - ilContainer::_writeContainerSetting($this->getId(), "icon_custom", 1); - } - else - { - ilContainer::_writeContainerSetting($this->getId(), "icon_custom", 0); - } - } - } - - /** - * Get the container directory. - * - * @return string container directory - */ - function getContainerDirectory() - { - return "container_data/obj_".$this->getId(); - } } ?> diff --git a/Services/Container/classes/class.ilContainer.php b/Services/Container/classes/class.ilContainer.php index c154b27e4159..72b5ad7aa72d 100755 --- a/Services/Container/classes/class.ilContainer.php +++ b/Services/Container/classes/class.ilContainer.php @@ -176,50 +176,6 @@ static function _getContainerDirectory($a_id) { return ilUtil::getWebspaceDir()."/container_data/obj_".$a_id; } - - /** - * Get path for big icon. - * - * @return string icon path - * @deprecated use _lookupIconPath instead - */ - function getBigIconPath() - { - return self::_lookupIconPath($this->getId()); - } - - /** - * Get path for small icon - * - * @return string icon path - * @deprecated use _lookupIconPath instead - */ - function getSmallIconPath() - { - return self::_lookupIconPath($this->getId()); - } - - /** - * Get path for tiny icon - * - * @return string icon path - * @deprecated use _lookupIconPath instead - */ - function getTinyIconPath() - { - return self::_lookupIconPath($this->getId()); - } - - /** - * Get path for custom icon - * - * @return string icon path - */ - function getCustomIconPath() - { - return self::_lookupIconPath($this->getId()); - } - /** * Set Found hidden files (set by getSubItems). @@ -492,68 +448,7 @@ public static function _exportContainerSettings(ilXmlWriter $a_xml, $a_obj_id) $a_xml->xmlEndTag("ContainerSettings"); } } - - /** - * lookup icon path - * - * @param int $a_id container object id - * @param string $a_size "big" | "small" - */ - static function _lookupIconPath($a_id, $a_size = "big") - { - if (ilContainer::_lookupContainerSetting($a_id, "icon_custom")) - { - $cont_dir = ilContainer::_getContainerDirectory($a_id); - - $file_name = $cont_dir."/icon_custom.svg"; - if (is_file($file_name)) - { - return $file_name; - } - } - - return ""; - } - /** - * save container icons - */ - function saveIcons($a_custom_icon) - { - $this->createContainerDirectory(); - $cont_dir = $this->getContainerDirectory(); - - if ($a_custom_icon != "") - { - if (is_file($cont_dir."/icon_custom.svg")) - { - unlink($cont_dir . "/icon_custom.svg"); - } - $file_name = $cont_dir."/icon_custom.svg"; - ilUtil::moveUploadedFile($a_custom_icon, "icon_custom.svg", $file_name); - - if ($file_name != "" && is_file($file_name)) - { - ilContainer::_writeContainerSetting($this->getId(), "icon_custom", 1); - } - else - { - ilContainer::_writeContainerSetting($this->getId(), "icon_custom", 0); - } - } - } - - /** - * remove small icon - */ - function removeCustomIcon() - { - $cont_dir = $this->getContainerDirectory(); - $small_file_name = $cont_dir."/icon_custom.svg"; - @unlink($small_file_name); - ilContainer::_writeContainerSetting($this->getId(), "icon_custom", 0); - } - /** * Clone container settings * @@ -604,22 +499,6 @@ public function cloneObject($a_target_id,$a_copy_id = 0, $a_omit_tree = false) foreach(self::_getContainerSettings($this->getId()) as $keyword => $value) { self::_writeContainerSetting($new_obj->getId(), $keyword, $value); - - // copy custom icons - if($keyword == "icon_custom" && - $value) - { - // see saveIcons() - $new_obj->createContainerDirectory(); - $tgt_dir = $new_obj->getContainerDirectory(); - $src_dir = $this->getContainerDirectory(); - $file = "icon_custom.svg"; - $src_file = $src_dir."/".$file; - if(file_exists($src_file)) - { - copy($src_file, $tgt_dir."/".$file); - } - } } return $new_obj; diff --git a/Services/Container/classes/class.ilContainerAccess.php b/Services/Container/classes/class.ilContainerAccess.php index 9ee8803f4843..7777af31d237 100644 --- a/Services/Container/classes/class.ilContainerAccess.php +++ b/Services/Container/classes/class.ilContainerAccess.php @@ -11,7 +11,7 @@ * * @ingroup ServicesContainer */ -class ilContainerAccess +class ilContainerAccess implements \ilWACCheckingClass { /** * @param ilWACPath $ilWACPath @@ -25,7 +25,7 @@ public function canBeDelivered(ilWACPath $ilWACPath) { preg_match("/\\/obj_([\\d]*)\\//uism", $ilWACPath->getPath(), $results); foreach (ilObject2::_getAllReferences($results[1]) as $ref_id) { - if ($access->checkAccess('read', '', $ref_id)) { + if ($access->checkAccess('visible', '', $ref_id) || $access->checkAccess('read', '', $ref_id)) { return true; } } diff --git a/Services/Container/classes/class.ilContainerGUI.php b/Services/Container/classes/class.ilContainerGUI.php index a20f84fc7a20..9085a5d37e60 100644 --- a/Services/Container/classes/class.ilContainerGUI.php +++ b/Services/Container/classes/class.ilContainerGUI.php @@ -1149,15 +1149,15 @@ function addStandardRow(&$a_tpl, $a_html, $a_item_ref_id = "", $a_item_obj_id = { $icon = ilUtil::getImagePath("icon_".$a_image_type.".svg"); $alt = $this->lng->txt("obj_".$a_image_type); - - // custom icon - if ($ilSetting->get("custom_icons") && - in_array($a_image_type, array("cat","grp","crs"))) - { - require_once("./Services/Container/classes/class.ilContainer.php"); - if (($path = ilContainer::_lookupIconPath($a_item_obj_id, "small")) != "") - { - $icon = $path; + + if ($ilSetting->get('custom_icons')) { + global $DIC; + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($a_item_obj_id, $a_image_type); + + if ($customIcon->exists()) { + $icon = $customIcon->getFullPath(); } } @@ -2652,48 +2652,6 @@ function clipboardObject() return true; } - - /** - * show edit section of custom icons for container - * - */ - function showCustomIconsEditing($a_input_colspan = 1, ilPropertyFormGUI $a_form = null, $a_as_section = true) - { - $ilSetting = $this->settings; - if ($ilSetting->get("custom_icons")) - { - if($a_form) - { - $custom_icon = $this->object->getCustomIconPath(); - - if($a_as_section) - { - $title = new ilFormSectionHeaderGUI(); - $title->setTitle($this->lng->txt("icon_settings")); - } - else - { - $title = new ilCustomInputGUI($this->lng->txt("icon_settings"), ""); - } - $a_form->addItem($title); - - $caption = $this->lng->txt("cont_custom_icon"); - $icon = new ilImageFileInputGUI($caption, "cont_icon"); - $icon->setSuffixes(array("svg")); - $icon->setUseCache(false); - $icon->setImage($custom_icon); - if($a_as_section) - { - $a_form->addItem($icon); - } - else - { - $title->addSubItem($icon); - } - } - } - } - function isActiveAdministrationPanel() { $ilAccess = $this->access; diff --git a/Services/Init/classes/class.ilInitialisation.php b/Services/Init/classes/class.ilInitialisation.php index c8aa937dea8e..5d1be7313cc7 100644 --- a/Services/Init/classes/class.ilInitialisation.php +++ b/Services/Init/classes/class.ilInitialisation.php @@ -621,6 +621,17 @@ protected static function initMail(\ILIAS\DI\Container $c) }; } + /** + * @param \ILIAS\DI\Container $c + */ + protected static function initCustomObjectIcons(\ILIAS\DI\Container $c) + { + $c["object.customicons.factory"] = function ($c) { + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php'; + return new ilObjectCustomIconFactory($c->filesystem()->web(), $c->upload(), $c['ilObjDataCache']); + }; + } + /** * @param \ILIAS\DI\Container $c */ @@ -1181,6 +1192,7 @@ protected static function initClient() self::initSettings(); self::initMail($GLOBALS['DIC']); self::initAvatar($GLOBALS['DIC']); + self::initCustomObjectIcons($GLOBALS['DIC']); // --- needs settings diff --git a/Services/MediaObjects/classes/class.ilObjMediaObject.php b/Services/MediaObjects/classes/class.ilObjMediaObject.php index 6052bc46fdac..935ec03c5211 100755 --- a/Services/MediaObjects/classes/class.ilObjMediaObject.php +++ b/Services/MediaObjects/classes/class.ilObjMediaObject.php @@ -1500,6 +1500,7 @@ static function getParentObjectIdForUsage($a_usage, $a_include_all_access_obj_id case "fold": case "root": case "cont": + case "copa": case "cstr": // repository pages $obj_id = $id; diff --git a/Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php b/Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php new file mode 100644 index 000000000000..71e1a193886a --- /dev/null +++ b/Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php @@ -0,0 +1,26 @@ +dic = $dic; + $this->parentGui = $parentGui; + $this->object = $object; + } + + /** + * @param null|string $uploadFieldInformationText + */ + public function setUploadFieldInformationText($uploadFieldInformationText) + { + $this->uploadFieldInformationText = $uploadFieldInformationText; + } + + public function executeCommand() + { + $nextClass = $this->dic->ctrl()->getNextClass($this); + $cmd = $this->dic->ctrl()->getCmd(self::DEFAULT_CMD); + + switch (true) { + case method_exists($this, $cmd): + $this->{$cmd}(); + break; + + default: + $this->{self::DEFAULT_CMD}(); + break; + } + } + + /** + * @param ilPropertyFormGUI|null $form + */ + protected function showForm(\ilPropertyFormGUI $form = null) + { + if (!$form) { + $form = $this->getForm(); + } + + $this->dic->ui()->mainTemplate()->setContent($form->getHTML()); + } + + /** + * @return \ilPropertyFormGUI + */ + protected function getForm() + { + $this->dic->language()->loadLanguageModule('cntr'); + + $form = new \ilPropertyFormGUI(); + $form->setFormAction($this->dic->ctrl()->getFormAction($this, 'saveForm')); + $form->setTitle($this->dic->language()->txt('icon_settings')); + + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $this->dic['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($this->object->getId(), $this->object->getType()); + + $icon = new \ilImageFileInputGUI($this->dic->language()->txt('cont_custom_icon'), 'icon'); + if (is_string($this->uploadFieldInformationText)) { + $icon->setInfo($this->uploadFieldInformationText); + } + $icon->setSuffixes($customIcon->getSupportedFileExtensions()); + $icon->setUseCache(false); + if ($customIcon->exists()) { + $icon->setImage($customIcon->getFullPath()); + } else { + $icon->setImage(''); + } + $form->addItem($icon); + + $form->addCommandButton('saveForm', $this->dic->language()->txt('save')); + + return $form; + } + + /** + * + */ + protected function saveForm() + { + $form = $this->getForm(); + if ($form->checkInput()) { + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $this->dic['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($this->object->getId(), $this->object->getType()); + + /** @var \ilImageFileInputGUI $item */ + $fileData = (array)$form->getInput('icon'); + $item = $form->getItemByPostVar('icon'); + + if ($item->getDeletionFlag()) { + $customIcon->remove(); + } + + if ($fileData['tmp_name']) { + $customIcon->saveFromHttpRequest(); + } + + ilUtil::sendSuccess($this->dic->language()->txt('msg_obj_modified'), true); + $this->dic->ctrl()->redirect($this, 'showForm'); + } + + $form->setValuesByPost(); + $this->showForm($form); + } +} \ No newline at end of file diff --git a/Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php b/Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php new file mode 100644 index 000000000000..3e3434d23839 --- /dev/null +++ b/Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php @@ -0,0 +1,81 @@ +webDirectory = $webDirectory; + $this->uploadService = $uploadService; + $this->objectCache = $objectCache; + } + + /** + * @var string $type + * @return \ilCustomIconObjectConfiguration + */ + public function getConfigurationByType($type) + { + switch ($type) { + case 'grp': + case 'root': + case 'cat': + case 'fold': + case 'crs': + case 'prg': + require_once 'Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php'; + $configuration = new \ilContainerCustomIconConfiguration(); + break; + + default: + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconConfiguration.php'; + $configuration = new \ilObjectCustomIconConfiguration(); + break; + } + + return $configuration; + } + + /** + * @param string $objId The obj_id of the ILIAS object. + * @param string $objType An optional type of the ILIAS object. If not passed, the type will be determined automatically. + * @return \ilObjectCustomIconImpl + */ + public function getByObjId($objId, $objType = '') + { + if (0 === strlen($objType)) { + $objType = $this->objectCache->lookupType($objId); + } + + require_once 'Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php'; + return new \ilObjectCustomIconImpl( + $this->webDirectory, + $this->uploadService, + $this->getConfigurationByType($objType), + $objId + ); + } +} \ No newline at end of file diff --git a/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php b/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php new file mode 100644 index 000000000000..f9683d67aa2a --- /dev/null +++ b/Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php @@ -0,0 +1,249 @@ +objId = $objId; + + $this->webDirectory = $webDirectory; + $this->upload = $uploadService; + $this->config = $config; + } + + /** + * @return int + */ + protected function getObjId() + { + return $this->objId; + } + + /** + * @inheritdoc + */ + public function copy($targetObjId) + { + if (!$this->exists()) { + \ilContainer::_writeContainerSetting($targetObjId, 'icon_custom', 0); + return; + } + + try { + $this->webDirectory->copy( + $this->getRelativePath(), + preg_replace( + '/(' . $this->config->getSubDirectoryPrefix() . ')(\d*)\/(.*)$/', + '${1}' . $targetObjId . '/${3}', + $this->getRelativePath() + ) + ); + + \ilContainer::_writeContainerSetting($targetObjId, 'icon_custom', 1); + } catch (\Exception $e) { + \ilContainer::_writeContainerSetting($targetObjId, 'icon_custom', 0); + } + } + + /** + * @inheritdoc + */ + public function delete() + { + if ($this->exists()) { + try { + $this->webDirectory->deleteDir($this->getIconDirectory()); + } catch (\Exception $e) { + } + } + + \ilContainer::_deleteContainerSettings($this->getObjId(), 'icon_custom'); + } + + /** + * @return string[] + */ + public function getSupportedFileExtensions() + { + return $this->config->getSupportedFileExtensions(); + } + + /** + * @inheritdoc + */ + public function saveFromSourceFile($sourceFilePath) + { + $this->createCustomIconDirectory(); + + $fileName = $this->getRelativePath(); + + if ($this->webDirectory->has($fileName)) { + $this->webDirectory->delete($fileName); + } + + $this->webDirectory->copy($sourceFilePath, $fileName); + + $this->persistIconState($fileName); + } + + /** + * @inheritdoc + */ + public function saveFromHttpRequest() + { + $this->createCustomIconDirectory(); + + $fileName = $this->getRelativePath(); + + if ($this->webDirectory->has($fileName)) { + $this->webDirectory->delete($fileName); + } + + if ($this->upload->hasUploads() && !$this->upload->hasBeenProcessed()) { + $this->upload->process(); + + /** @var \ILIAS\FileUpload\DTO\UploadResult $result */ + $result = array_values($this->upload->getResults())[0]; + if ($result->getStatus() == \ILIAS\FileUpload\DTO\ProcessingStatus::OK) { + $this->upload->moveOneFileTo( + $result, + $this->getIconDirectory(), + \ILIAS\FileUpload\Location::WEB, + $this->getIconFileName(), + true + ); + } + + foreach ($this->config->getUploadPostProcessors() as $processor) { + $processor->process($fileName); + } + } + + $this->persistIconState($fileName); + } + + /** + * @param string$fileName + */ + protected function persistIconState($fileName) + { + if ($this->webDirectory->has($fileName)) { + \ilContainer::_writeContainerSetting($this->getObjId(), 'icon_custom', 1); + } else { + \ilContainer::_writeContainerSetting($this->getObjId(), 'icon_custom', 0); + } + } + + /** + * @inheritdoc + */ + public function remove() + { + $fileName = $this->getRelativePath(); + + if ($this->webDirectory->has($fileName)) { + $this->webDirectory->delete($fileName); + } + + \ilContainer::_writeContainerSetting($this->getObjId(), 'icon_custom', 0); + } + + /** + * @throws \ILIAS\Filesystem\Exception\IOException + */ + protected function createCustomIconDirectory() + { + $iconDirectory = $this->getIconDirectory(); + + if (!$this->webDirectory->has(dirname($iconDirectory))) { + $this->webDirectory->createDir(dirname($iconDirectory)); + } + + if (!$this->webDirectory->has($iconDirectory)) { + $this->webDirectory->createDir($iconDirectory); + } + } + + /** + * @return string + */ + protected function getIconDirectory() + { + return implode(DIRECTORY_SEPARATOR, [ + $this->config->getBaseDirectory(), + $this->config->getSubDirectoryPrefix() . $this->getObjId() + ]); + } + + /** + * @return string + */ + protected function getIconFileName() + { + return self::ICON_BASENAME . '.' . $this->config->getTargetFileExtension(); + } + + /** + * @return string + */ + protected function getRelativePath() + { + return implode(DIRECTORY_SEPARATOR, [ + $this->getIconDirectory(), + $this->getIconFileName() + ]); + } + + /** + * @inheritdoc + */ + public function exists() + { + if (!\ilContainer::_lookupContainerSetting($this->getObjId(), 'icon_custom', 0)) { + return false; + } + + return $this->webDirectory->has($this->getRelativePath()); + } + + /** + * @inheritdoc + */ + public function getFullPath() + { + // TODO: Currently there is no option to get the relative base directory of a filesystem + return implode(DIRECTORY_SEPARATOR, [ + \ilUtil::getWebspaceDir(), + $this->getRelativePath() + ]); + } +} \ No newline at end of file diff --git a/Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php b/Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php new file mode 100644 index 000000000000..16e58280b948 --- /dev/null +++ b/Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php @@ -0,0 +1,31 @@ +manipulate($query); // END WebDAV: Clone WebDAV properties + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($this->getId(), $this->getType()); + $customIcon->copy($new_obj->getId()); + $ilAppEventHandler->raise('Services/Object', 'cloneObject', array( 'object' => $new_obj, 'cloned_from_object' => $this, @@ -2040,23 +2045,16 @@ public static function _getIcon($a_obj_id = "", $a_size = "big", $a_type = "", $a_size = "big"; } - if ($ilSetting->get("custom_icons") && - in_array($a_type, array("cat","grp","crs", "root", "fold", "prg"))) - { - require_once("./Services/Container/classes/class.ilContainer.php"); - if (ilContainer::_lookupContainerSetting($a_obj_id, "icon_custom")) - { - $cont_dir = ilContainer::_getContainerDirectory($a_obj_id); - - $file_name = $cont_dir."/icon_custom.svg"; - if (is_file($file_name)) - { - // prevent caching - return $file_name."?tmp=".filemtime($file_name); - } + if ($ilSetting->get('custom_icons')) { + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($a_obj_id, $a_type); + if ($customIcon->exists()) { + $filename = $customIcon->getFullPath(); + return $filename . '?tmp=' . filemtime($filename); } } - + if (!$a_offline) { if ($objDefinition->isPluginTypeName($a_type)) diff --git a/Services/Object/classes/class.ilObjectAccess.php b/Services/Object/classes/class.ilObjectAccess.php index 767338b85fa5..8b5cc0a8f45b 100644 --- a/Services/Object/classes/class.ilObjectAccess.php +++ b/Services/Object/classes/class.ilObjectAccess.php @@ -13,7 +13,7 @@ * @version $Id$ * */ -class ilObjectAccess +class ilObjectAccess implements \ilWACCheckingClass { /** * Checks wether a user may invoke a command or not @@ -118,7 +118,22 @@ static function _preloadData($a_obj_ids, $a_ref_ids) { } - + + /** + * @inheritdoc + */ + public function canBeDelivered(ilWACPath $ilWACPath) { + global $ilAccess; + + preg_match("/\\/obj_([\\d]*)\\//uism", $ilWACPath->getPath(), $results); + foreach (ilObject2::_getAllReferences($results[1]) as $ref_id) { + if ($ilAccess->checkAccess('visible', '', $ref_id) || $ilAccess->checkAccess('read', '', $ref_id)) { + return true; + } + } + + return false; + } } ?> diff --git a/Services/Object/classes/class.ilObjectAppEventListener.php b/Services/Object/classes/class.ilObjectAppEventListener.php new file mode 100644 index 000000000000..48d4fe007766 --- /dev/null +++ b/Services/Object/classes/class.ilObjectAppEventListener.php @@ -0,0 +1,24 @@ + + */ +class ilObjectAppEventListener implements \ilAppEventListener +{ + /** + * @inheritdoc + */ + public static function handleEvent($a_component, $a_event, $a_parameter) + { + global $DIC; + + if ('Services/Object' === $a_component && 'beforeDeletion' === $a_event) { + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($a_parameter['object']->getId(), $a_parameter['object']->getType()); + $customIcon->delete(); + } + } +} \ No newline at end of file diff --git a/Services/Object/classes/class.ilObjectGUI.php b/Services/Object/classes/class.ilObjectGUI.php index b9c15932b786..dec747901bb0 100755 --- a/Services/Object/classes/class.ilObjectGUI.php +++ b/Services/Object/classes/class.ilObjectGUI.php @@ -2195,6 +2195,47 @@ protected function handleAutoRating(ilObject $a_new_obj) $a_new_obj->update(); } } + + /** + * show edit section of custom icons for container + * + */ + protected function showCustomIconsEditing($a_input_colspan = 1, ilPropertyFormGUI $a_form = null, $a_as_section = true) + { + if ($this->settings->get("custom_icons")) { + if ($a_form) { + global $DIC; + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + + $customIcon = $customIconFactory->getByObjId($this->object->getId(), $this->object->getType()); + + if ($a_as_section) { + $title = new ilFormSectionHeaderGUI(); + $title->setTitle($this->lng->txt("icon_settings")); + } else { + $title = new ilCustomInputGUI($this->lng->txt("icon_settings"), ""); + } + $a_form->addItem($title); + + $caption = $this->lng->txt("cont_custom_icon"); + $icon = new ilImageFileInputGUI($caption, "cont_icon"); + + $icon->setSuffixes($customIcon->getSupportedFileExtensions()); + $icon->setUseCache(false); + if ($customIcon->exists()) { + $icon->setImage($customIcon->getFullPath()); + } else { + $icon->setImage(''); + } + if ($a_as_section) { + $a_form->addItem($icon); + } else { + $title->addSubItem($icon); + } + } + } + } } // END class.ilObjectGUI (3.10: 2896 loc) ?> \ No newline at end of file diff --git a/Services/Object/classes/class.ilObjectLP.php b/Services/Object/classes/class.ilObjectLP.php index 065346bd1683..33cbb8c5ba9e 100644 --- a/Services/Object/classes/class.ilObjectLP.php +++ b/Services/Object/classes/class.ilObjectLP.php @@ -13,16 +13,16 @@ */ class ilObjectLP { - /** - * @var ilTree - */ - protected $tree; - - /** - * @var ilDB - */ - protected $db; - + /** + * @var ilTree + */ + protected $tree; + + /** + * @var ilDB + */ + protected $db; + protected $obj_id; // [int] protected $collection_instance; // [ilLPCollection] protected $mode; // [int] @@ -31,10 +31,10 @@ class ilObjectLP protected function __construct($a_obj_id) { - global $DIC; - - $this->tree = $DIC->repositoryTree(); - $this->db = $DIC->database(); + global $DIC; + + $this->tree = $DIC->repositoryTree(); + $this->db = $DIC->database(); $this->obj_id = (int)$a_obj_id; } @@ -63,9 +63,9 @@ public static function getInstance($a_obj_id) public static function getTypeClass($a_type) { - global $DIC; - - $objDefinition = $DIC["objDefinition"]; + global $DIC; + + $objDefinition = $DIC["objDefinition"]; if(self::isSupportedObjectType($a_type)) { @@ -133,7 +133,10 @@ public static function getTypeClass($a_type) case "iass": include_once "Modules/IndividualAssessment/classes/class.ilIndividualAssessmentLP.php"; - return "ilIndividualAssessmentLP"; + return "ilIndividualAssessmentLP"; + + case "copa": + return "ilContentPageLP"; // plugin case $objDefinition->isPluginTypeName($a_type): @@ -145,11 +148,11 @@ public static function getTypeClass($a_type) public static function isSupportedObjectType($a_type) { - global $DIC; - - $objDefinition = $DIC["objDefinition"]; + global $DIC; + + $objDefinition = $DIC["objDefinition"]; - $valid = array("crs", "grp", "fold", "lm", "htlm", "sahs", "tst", "exc", "sess", "svy", "file", "mcst", "prg", "iass"); + $valid = array("crs", "grp", "fold", "lm", "htlm", "sahs", "tst", "exc", "sess", "svy", "file", "mcst", "prg", "iass", "copa"); if(in_array($a_type, $valid)) { @@ -275,7 +278,7 @@ public function getCollectionInstance() public function getMembers($a_search = true) { - $tree = $this->tree; + $tree = $this->tree; if(!$a_search) { @@ -368,10 +371,10 @@ protected function gatherLPUsers() final static public function handleMove($a_source_ref_id) { - global $DIC; - - $tree = $DIC->repositoryTree(); - $ilDB = $DIC->database(); + global $DIC; + + $tree = $DIC->repositoryTree(); + $ilDB = $DIC->database(); $ref_ids = $tree->getSubTreeIds($a_source_ref_id); $ref_ids[] = $a_source_ref_id; @@ -451,7 +454,7 @@ final public function handleDelete() final protected function updateParentCollections() { - $ilDB = $this->db; + $ilDB = $this->db; include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php"); @@ -505,9 +508,9 @@ protected static function isLPMember(array &$a_res, $a_usr_id, $a_obj_ids) */ protected static function findMembershipsByPath(array &$a_res, $a_usr_id, $a_parent_ref_id, array $a_obj_ids, $a_mapped_ref_ids = false) { - global $DIC; - - $tree = $DIC->repositoryTree(); + global $DIC; + + $tree = $DIC->repositoryTree(); $found = array(); @@ -572,10 +575,10 @@ protected static function findMembershipsByPath(array &$a_res, $a_usr_id, $a_par */ public static function getLPMemberships($a_usr_id, array $a_obj_ids, $a_parent_ref_id = null, $a_mapped_ref_ids = false) { - global $DIC; - - $ilDB = $DIC->database(); - $tree = $DIC->repositoryTree(); + global $DIC; + + $ilDB = $DIC->database(); + $tree = $DIC->repositoryTree(); // see ilTrQuery::getParticipantsForObject() [single object only] // this is optimized for larger number of objects, e.g. list GUIs @@ -706,7 +709,7 @@ public function getMailTemplateId() public static function supportsSpentSeconds($a_obj_type) { - return !in_array($a_obj_type, array("exc", "file", "mcst", "mob", "htlm")); + return !in_array($a_obj_type, array("exc", "file", "mcst", "mob", "htlm", "copa")); } public static function supportsMark($a_obj_type) @@ -716,7 +719,7 @@ public static function supportsMark($a_obj_type) public static function supportsMatrixView($a_obj_type) { - return !in_array($a_obj_type, array('svy', 'tst', 'htlm', 'exc', 'sess', 'file', 'prg')); + return !in_array($a_obj_type, array('svy', 'tst', 'htlm', 'exc', 'sess', 'file', 'prg', 'copa')); } @@ -734,9 +737,9 @@ public static function getDefaultModes($a_lp_active) protected static function getTypeDefaultFromDB($a_type) { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); if(!is_array(self::$type_defaults)) { @@ -752,9 +755,9 @@ protected static function getTypeDefaultFromDB($a_type) public static function saveTypeDefaults(array $a_data) { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); $ilDB->manipulate("DELETE FROM ut_lp_defaults"); foreach($a_data as $type => $mode) diff --git a/Services/Object/service.xml b/Services/Object/service.xml index aea1400187d5..4bb5297136d0 100644 --- a/Services/Object/service.xml +++ b/Services/Object/service.xml @@ -15,6 +15,10 @@ + + + + diff --git a/Services/PersonalDesktop/ItemsBlock/classes/class.ilPDSelectedItemsBlockGUI.php b/Services/PersonalDesktop/ItemsBlock/classes/class.ilPDSelectedItemsBlockGUI.php index 81cd9adc667a..a238e3dd0919 100755 --- a/Services/PersonalDesktop/ItemsBlock/classes/class.ilPDSelectedItemsBlockGUI.php +++ b/Services/PersonalDesktop/ItemsBlock/classes/class.ilPDSelectedItemsBlockGUI.php @@ -551,18 +551,18 @@ function addStandardRow(&$a_tpl, $a_html, $a_item_ref_id = "", $a_item_obj_id = $icon = ilUtil::getImagePath("icon_lm.svg"); $title = $this->lng->txt("learning_resource"); } - - // custom icon - if ($ilSetting->get("custom_icons") && - in_array($a_image_type, array("cat","grp","crs"))) - { - require_once("./Services/Container/classes/class.ilContainer.php"); - if (($path = ilContainer::_lookupIconPath($a_item_obj_id, "small")) != "") - { - $icon = $path; + + if ($ilSetting->get('custom_icons')) { + global $DIC; + /** @var \ilObjectCustomIconFactory $customIconFactory */ + $customIconFactory = $DIC['object.customicons.factory']; + $customIcon = $customIconFactory->getByObjId($a_item_obj_id, $a_image_type); + + if ($customIcon->exists()) { + $icon = $customIcon->getFullPath(); } } - + $a_tpl->setCurrentBlock("block_row_image"); $a_tpl->setVariable("ROW_IMG", $icon); $a_tpl->setVariable("ROW_ALT", $title); diff --git a/Services/Repository/classes/class.ilObjRepositorySettings.php b/Services/Repository/classes/class.ilObjRepositorySettings.php index fdbc33055140..daba94aa6ad3 100644 --- a/Services/Repository/classes/class.ilObjRepositorySettings.php +++ b/Services/Repository/classes/class.ilObjRepositorySettings.php @@ -36,9 +36,9 @@ function delete() public static function addNewItemGroupSeparator() { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); // append $pos = $ilDB->query("SELECT max(pos) mpos FROM il_new_item_grp"); @@ -59,9 +59,9 @@ public static function addNewItemGroupSeparator() public static function addNewItemGroup(array $a_titles) { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); // append $pos = $ilDB->query("SELECT max(pos) mpos FROM il_new_item_grp"); @@ -83,9 +83,9 @@ public static function addNewItemGroup(array $a_titles) public static function updateNewItemGroup($a_id, array $a_titles) { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); $ilDB->manipulate("UPDATE il_new_item_grp". " SET titles = ".$ilDB->quote(serialize($a_titles), "text"). @@ -95,10 +95,10 @@ public static function updateNewItemGroup($a_id, array $a_titles) public static function deleteNewItemGroup($a_id) { - global $DIC; - - $ilDB = $DIC->database(); - $ilSetting = $DIC->settings(); + global $DIC; + + $ilDB = $DIC->database(); + $ilSetting = $DIC->settings(); // move subitems to unassigned $sub_items = self::getNewItemGroupSubItems(); @@ -124,11 +124,11 @@ public static function deleteNewItemGroup($a_id) public static function getNewItemGroups() { - global $DIC; - - $ilDB = $DIC->database(); - $lng = $DIC->language(); - $ilUser = $DIC->user(); + global $DIC; + + $ilDB = $DIC->database(); + $lng = $DIC->language(); + $ilUser = $DIC->user(); $def_lng = $lng->getDefaultLanguage(); $usr_lng = $ilUser->getLanguage(); @@ -166,9 +166,9 @@ public static function getNewItemGroups() public static function updateNewItemGroupOrder(array $a_order) { - global $DIC; - - $ilDB = $DIC->database(); + global $DIC; + + $ilDB = $DIC->database(); asort($a_order); $pos = 0; @@ -184,10 +184,10 @@ public static function updateNewItemGroupOrder(array $a_order) protected static function getAllObjTypes() { - global $DIC; - - $ilPluginAdmin = $DIC["ilPluginAdmin"]; - $objDefinition = $DIC["objDefinition"]; + global $DIC; + + $ilPluginAdmin = $DIC["ilPluginAdmin"]; + $objDefinition = $DIC["objDefinition"]; $res = array(); @@ -238,9 +238,9 @@ protected static function getAllObjTypes() public static function getNewItemGroupSubItems() { - global $DIC; - - $ilSetting = $DIC->settings(); + global $DIC; + + $ilSetting = $DIC->settings(); $res = array(); @@ -255,9 +255,9 @@ public static function getNewItemGroupSubItems() public static function getDefaultNewItemGrouping() { - global $DIC; - - $lng = $DIC->language(); + global $DIC; + + $lng = $DIC->language(); $res = array(); @@ -265,7 +265,7 @@ public static function getDefaultNewItemGrouping() "organisation" => array("fold", "sess", "cat", "catr", "crs", "crsr", "grp", "grpr", "itgr", "book", "prg"), "communication" => array("frm", "chtr"), "breaker1" => null, - "content" => array("file", "webr", "feed", "wiki", "blog", "lm", "htlm", "sahs", "glo", "dcl", "bibl", "mcst", "mep"), + "content" => array("file", "webr", "feed", "wiki", "blog", "lm", "htlm", "sahs", "glo", "dcl", "bibl", "mcst", "mep", "copa"), "breaker2" => null, "assessment" => array("exc", "tst", "qpl", "iass"), "feedback" => array("poll", "svy", "spl"), @@ -312,9 +312,9 @@ public static function getDefaultNewItemGrouping() public static function deleteObjectType($a_type) { - global $DIC; - - $ilSetting = $DIC->settings(); + global $DIC; + + $ilSetting = $DIC->settings(); // see ilObjRepositorySettingsGUI::saveModules() $ilSetting->delete("obj_dis_creation_".$a_type); diff --git a/Services/Repository/classes/class.ilTrashTableGUI.php b/Services/Repository/classes/class.ilTrashTableGUI.php index 9e15999f0904..6d1ce58f4348 100644 --- a/Services/Repository/classes/class.ilTrashTableGUI.php +++ b/Services/Repository/classes/class.ilTrashTableGUI.php @@ -77,9 +77,9 @@ protected function fillRow($a_set) { $lng = $this->lng; $objDefinition = $this->obj_definition; - - $img = ilObject::_getIcon($obj_id, "small", $a_set["type"]); - if (is_file($img)) + + $img = ilObject::_getIcon($a_set["obj_id"], "small", $a_set["type"]); + if (strlen($img) > 0) { $alt = ($objDefinition->isPlugin($a_set["type"])) ? $lng->txt("icon")." ".ilObjectPlugin::lookupTxtById($a_set["type"], "obj_".$a_set["type"]) diff --git a/Services/Tracking/classes/class.ilLPTableBaseGUI.php b/Services/Tracking/classes/class.ilLPTableBaseGUI.php index 5a25af794e12..6a49baad8ff5 100644 --- a/Services/Tracking/classes/class.ilLPTableBaseGUI.php +++ b/Services/Tracking/classes/class.ilLPTableBaseGUI.php @@ -448,6 +448,7 @@ protected function getPossibleTypes($a_split_learning_resources = false, $a_incl $options['tst'] = $lng->txt('objs_tst'); $options['prg'] = $lng->txt('objs_prg'); $options['iass'] = $lng->txt('objs_iass'); + $options['copa'] = $lng->txt('objs_copa'); if($a_allow_undefined_lp) { diff --git a/Services/Tracking/classes/class.ilLearningProgressBaseGUI.php b/Services/Tracking/classes/class.ilLearningProgressBaseGUI.php index 09ddb491e429..150fd5ec963c 100644 --- a/Services/Tracking/classes/class.ilLearningProgressBaseGUI.php +++ b/Services/Tracking/classes/class.ilLearningProgressBaseGUI.php @@ -527,6 +527,7 @@ function __appendLPDetails(&$info,$item_id,$user_id) case 'sahs': case 'grp': case 'iass': + case 'copa': case 'sess': // display status as image include_once("./Services/Tracking/classes/class.ilLearningProgressBaseGUI.php"); diff --git a/Services/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php b/Services/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php index c132cc54a3b9..295320097e81 100644 --- a/Services/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php +++ b/Services/Tracking/classes/collection/class.ilLPCollectionOfRepositoryObjects.php @@ -20,9 +20,9 @@ class ilLPCollectionOfRepositoryObjects extends ilLPCollection public function getPossibleItems($a_ref_id, $a_full_data = false) { global $DIC; - - $tree = $DIC['tree']; - $objDefinition = $DIC['objDefinition']; + + $tree = $DIC['tree']; + $objDefinition = $DIC['objDefinition']; $cache_idx = $a_ref_id."__".$a_full_data; @@ -76,6 +76,7 @@ public function getPossibleItems($a_ref_id, $a_full_data = false) case 'svy': case "prg": case 'iass': + case 'copa': if(!$a_full_data) { $all_possible[] = $item_ref_id; @@ -200,8 +201,8 @@ public function cloneCollection($a_target_id, $a_copy_id) protected function read($a_obj_id) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $items = array(); @@ -235,8 +236,8 @@ protected function read($a_obj_id) protected function addEntry($a_item_id) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; // only active entries are assigned! if(!$this->isAssignedEntry($a_item_id)) @@ -262,8 +263,8 @@ protected function addEntry($a_item_id) protected function deleteEntry($a_item_id) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $query = "DELETE FROM ut_lp_collections ". " WHERE obj_id = ".$ilDB->quote($this->obj_id, "integer"). @@ -281,8 +282,8 @@ protected function deleteEntry($a_item_id) public static function hasGroupedItems($a_obj_id) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $query = "SELECT item_id FROM ut_lp_collections". " WHERE obj_id = ".$ilDB->quote($a_obj_id, "integer"). @@ -294,8 +295,8 @@ public static function hasGroupedItems($a_obj_id) protected function getGroupingIds(array $a_item_ids) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $grouping_ids = array(); @@ -315,8 +316,8 @@ protected function getGroupingIds(array $a_item_ids) public function deactivateEntries(array $a_item_ids) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; parent::deactivateEntries($a_item_ids); @@ -334,8 +335,8 @@ public function deactivateEntries(array $a_item_ids) public function activateEntries(array $a_item_ids) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; parent::activateEntries($a_item_ids); @@ -353,8 +354,8 @@ public function activateEntries(array $a_item_ids) public function createNewGrouping(array $a_item_ids, $a_num_obligatory = 1) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $this->activateEntries($a_item_ids); @@ -398,8 +399,8 @@ public function createNewGrouping(array $a_item_ids, $a_num_obligatory = 1) public function releaseGrouping(array $a_item_ids) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $grouping_ids = $this->getGroupingIds($a_item_ids); @@ -414,8 +415,8 @@ public function releaseGrouping(array $a_item_ids) public function saveObligatoryMaterials(array $a_obl) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; foreach($a_obl as $grouping_id => $num) { @@ -506,8 +507,8 @@ protected function parseTableGUIItem($a_id, array $a_item) protected function getTableGUItemGroup($item_id) { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $items = array(); @@ -537,8 +538,8 @@ protected function getTableGUItemGroup($item_id) public function getGroupedItemsForLPStatus() { global $DIC; - - $ilDB = $DIC['ilDB']; + + $ilDB = $DIC['ilDB']; $items = $this->getItems(); diff --git a/Services/Tracking/classes/status/class.ilLPStatusManual.php b/Services/Tracking/classes/status/class.ilLPStatusManual.php index b5d1584eb004..705e01f78ec0 100644 --- a/Services/Tracking/classes/status/class.ilLPStatusManual.php +++ b/Services/Tracking/classes/status/class.ilLPStatusManual.php @@ -75,6 +75,7 @@ function determineStatus($a_obj_id, $a_user_id, $a_obj = null) switch ($ilObjDataCache->lookupType($a_obj_id)) { case 'lm': + case 'copa': case 'htlm': include_once("./Services/Tracking/classes/class.ilChangeEvent.php"); if (ilChangeEvent::hasAccessed($a_obj_id, $a_user_id)) diff --git a/src/UI/Component/Icon/Standard.php b/src/UI/Component/Icon/Standard.php index 5fe1c43a5b6a..b032ab58c265 100644 --- a/src/UI/Component/Icon/Standard.php +++ b/src/UI/Component/Icon/Standard.php @@ -116,5 +116,6 @@ interface Standard extends Icon { const BDGA = 'bdga'; //Badge Settings const WFE = 'wfe'; //WorkflowEngine const IASS = 'iass'; //Individual Assessment + const COPA = 'copa'; //Content Page } diff --git a/src/UI/Implementation/Component/Icon/Standard.php b/src/UI/Implementation/Component/Icon/Standard.php index d3aaef517717..9fd7eef151f9 100644 --- a/src/UI/Implementation/Component/Icon/Standard.php +++ b/src/UI/Implementation/Component/Icon/Standard.php @@ -115,6 +115,7 @@ class Standard extends Icon implements C\Icon\Standard { ,self::BDGA ,self::WFE ,self::IASS + ,self::COPA ); public function __construct($name, $aria_label, $size, $is_disabled) { diff --git a/templates/default/images/icon_copa.svg b/templates/default/images/icon_copa.svg new file mode 100644 index 000000000000..e78a82c344de --- /dev/null +++ b/templates/default/images/icon_copa.svg @@ -0,0 +1,25 @@ + + + + + + diff --git a/xml/ilias_copa_5_4.xsd b/xml/ilias_copa_5_4.xsd new file mode 100644 index 000000000000..35a02fe8b498 --- /dev/null +++ b/xml/ilias_copa_5_4.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From ab04da8b00222474f1e537d94ec2af9bfc4d5581 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 08:41:23 +0200 Subject: [PATCH 022/166] ContentPage: Added translations --- lang/ilias_en.lang | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index cd933572bfcc..e50c05ece298 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14217,19 +14217,19 @@ file#:#file_suffix_custom_white_info#:#These suffixes will be added to the defau file#:#file_suffix_overall_white#:#Overal Suffix Whitelist file#:#file_suffix_overall_white_info#:#Resulting overall list of accepted file suffixes. assessment#:#not_enough_unique_answers#:#You have not selected "Identical Scoring", but you are using the same answer in at least to gaps. -common#:#obj_copa#:#Content Page Object -common#:#objs_copa#:#Content Page Objects -rbac#:#copa_visible#:#Content Page Object is visible -rbac#:#copa_read#:#User can read Content Page Object -rbac#:#copa_copy#:#Copy Content Page Object -rbac#:#copa_write#:#User can edit Content Page Object -rbac#:#copa_delete#:#User can move or delete Content Page Object +common#:#obj_copa#:#Content Page +common#:#objs_copa#:#Content Pages +rbac#:#copa_visible#:#Content Page is visible +rbac#:#copa_read#:#User can read Content Page +rbac#:#copa_copy#:#Copy Content Page +rbac#:#copa_write#:#User can edit Content Page +rbac#:#copa_delete#:#User can move or delete Content Page rbac#:#copa_edit_permission#:#User can change permission settings -rbac#:#rbac_create_copa#:#Create Content Page Object -copa#:#copa_new#:#Create Content Page Object -copa#:#copa_import#:#Import Content Page Object -copa#:#copa_copy#:#Copy Content Page Object -copa#:#copa_add#:#Add Content Page Object -copa#:#copa_edit#:#Edit Content Page Object -copa#:#obj_copa_duplicate#:#Duplicate Content Page Object +rbac#:#rbac_create_copa#:#Create Content Page +copa#:#copa_new#:#Create Content Page +copa#:#copa_import#:#Import Content Page +copa#:#copa_copy#:#Copy Content Page +copa#:#copa_add#:#Add Content Page +copa#:#copa_edit#:#Edit Content Page +copa#:#obj_copa_duplicate#:#Duplicate Content Page wiki#:#wiki_change_notification_body_new#:#the following wiki page was created From 2229d26188cdc7e23511b527c5b2782b7e6b904d Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 10:04:04 +0200 Subject: [PATCH 023/166] ContentPage: Added translations --- lang/ilias_de.lang | 2 ++ lang/ilias_en.lang | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 313ca14a5d02..21ece5f93ec2 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -14227,6 +14227,8 @@ rbac#:#copa_copy#:#Inhaltsseite kopieren rbac#:#copa_write#:#Inhaltsseite bearbeiten rbac#:#copa_delete#:#Inhaltsseite löschen rbac#:#copa_edit_permission#:#Rechteeinstellungen ändern +rbac#:#copa_read_learning_progress#:#Benutzer kann Lernfortschritt anderer Benutzer einsehen +rbac#:#copa_edit_learning_progress#:#Benutzer kann Lernfortschrittseinstellungen bearbeiten rbac#:#rbac_create_copa#:#Inhaltsseite anlegen copa#:#copa_new#:#Neue Inhaltsseite anlegen copa#:#copa_import#:#Inhaltsseite importieren diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index e50c05ece298..0482eb444c91 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14226,6 +14226,8 @@ rbac#:#copa_write#:#User can edit Content Page rbac#:#copa_delete#:#User can move or delete Content Page rbac#:#copa_edit_permission#:#User can change permission settings rbac#:#rbac_create_copa#:#Create Content Page +rbac#:#copa_read_learning_progress#:#User can view learning progress of other users +rbac#:#copa_edit_learning_progress#:#User can edit learning progress settings copa#:#copa_new#:#Create Content Page copa#:#copa_import#:#Import Content Page copa#:#copa_copy#:#Copy Content Page From e627ae0eadd0b52632b878c772f74632e97a4477 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 11:34:17 +0200 Subject: [PATCH 024/166] ContentPage: Enabled learning progress for ilInfoScreenGUI below 'Learning Progress' tab --- .../classes/class.ilInfoScreenGUI.php | 106 +++++++++++++++--- .../class.ilLPListOfProgressGUI.php | 17 +++ 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php index 6c07232936d7..6be4d9fc6b84 100644 --- a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php +++ b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php @@ -61,7 +61,22 @@ class ilInfoScreenGUI var $table_class = "il_InfoScreen"; var $open_form_tag = true; var $close_form_tag = true; - + + /** + * @var int|null + */ + protected $contextRefId = null; + + /** + * @var int|null + */ + protected $contextObjId = null; + + /** + * @var string|null + */ + protected $contentObjType = null; + /** * a form action parameter. if set a form is generated */ @@ -955,12 +970,72 @@ function getHTML() return $tpl->get(); } + /** + * @return int|null + */ + public function getContextRefId(): int + { + if ($this->contextRefId !== null) { + return $this->contextRefId; + } + + return $this->gui_object->object->getRefId(); + } + + /** + * @param int|null $contextRefId + */ + public function setContextRefId(int $contextRefId) + { + $this->contextRefId = $contextRefId; + } + + /** + * @return int|null + */ + public function getContextObjId(): int + { + if ($this->contextObjId !== null) { + return $this->contextObjId; + } + + return $this->gui_object->object->getId(); + } + + /** + * @param int|null $contextObjId + */ + public function setContextObjId(int $contextObjId) + { + $this->contextObjId = $contextObjId; + } + + /** + * @return null|string + */ + public function getContentObjType(): string + { + if ($this->contentObjType !== null) { + return $this->contentObjType; + } + + return $this->gui_object->object->getType(); + } + + /** + * @param null|string $contentObjType + */ + public function setContentObjType(string $contentObjType) + { + $this->contentObjType = $contentObjType; + } + function showLearningProgress($a_tpl) { $ilUser = $this->user; $rbacsystem = $this->rbacsystem; - if(!$rbacsystem->checkAccess('read',$this->gui_object->object->getRefId())) + if(!$rbacsystem->checkAccess('read', $this->getContextRefId())) { return false; } @@ -970,13 +1045,13 @@ function showLearningProgress($a_tpl) } include_once("Services/Tracking/classes/class.ilObjUserTracking.php"); - if (!ilObjUserTracking::_enabledLearningProgress() and $ilUser->getId() != ANONYMOUS_USER_ID) + if (!ilObjUserTracking::_enabledLearningProgress()) { return false; } include_once './Services/Object/classes/class.ilObjectLP.php'; - $olp = ilObjectLP::getInstance($this->gui_object->object->getId()); + $olp = ilObjectLP::getInstance($this->getContextObjId()); if($olp->getCurrentMode() != ilLPObjSettings::LP_MODE_MANUAL) { return false; @@ -997,7 +1072,7 @@ function showLearningProgress($a_tpl) $i_tpl = new ilTemplate("tpl.lp_edit_manual_info_page.html", true, true, "Services/Tracking"); $i_tpl->setVariable("INFO_EDITED", $this->lng->txt("trac_info_edited")); $i_tpl->setVariable("SELECT_STATUS", ilUtil::formSelect((int) ilLPMarks::_hasCompleted($ilUser->getId(), - $this->gui_object->object->getId()), + $this->getContextObjId()), 'lp_edit', array(0 => $this->lng->txt('trac_not_completed'), 1 => $this->lng->txt('trac_completed')), @@ -1014,13 +1089,13 @@ function showLearningProgress($a_tpl) // More infos for lm's - if($this->gui_object->object->getType() == 'lm' || - $this->gui_object->object->getType() == 'htlm') + if($this->getContentObjType() == 'lm' || + $this->getContentObjType() == 'htlm') { $a_tpl->setCurrentBlock("pv"); include_once 'Services/Tracking/classes/class.ilLearningProgress.php'; - $progress = ilLearningProgress::_getProgress($ilUser->getId(),$this->gui_object->object->getId()); + $progress = ilLearningProgress::_getProgress($ilUser->getId(), $this->getContextObjId()); if($progress['access_time']) { $a_tpl->setVariable("TXT_PROPERTY_VALUE", @@ -1049,7 +1124,7 @@ function showLearningProgress($a_tpl) // $a_tpl->touchBlock("row"); - if($this->gui_object->object->getType() == 'lm') + if($this->getContentObjType() == 'lm') { // tags of all users $a_tpl->setCurrentBlock("pv"); @@ -1067,24 +1142,25 @@ function showLearningProgress($a_tpl) $a_tpl->touchBlock("row"); } - function saveProgress() + function saveProgress($redirect = true) { $ilUser = $this->user; include_once 'Services/Tracking/classes/class.ilLPMarks.php'; - $lp_marks = new ilLPMarks($this->gui_object->object->getId(),$ilUser->getId()); + $lp_marks = new ilLPMarks($this->getContextObjId(), $ilUser->getId()); $lp_marks->setCompleted((bool) $_POST['lp_edit']); $lp_marks->update(); require_once 'Services/Tracking/classes/class.ilLPStatusWrapper.php'; - ilLPStatusWrapper::_updateStatus($this->gui_object->object->getId(),$ilUser->getId()); + ilLPStatusWrapper::_updateStatus($this->getContextObjId(), $ilUser->getId()); $this->lng->loadLanguageModule('trac'); ilUtil::sendSuccess($this->lng->txt('trac_updated_status'), true); - $this->ctrl->redirect($this, ""); // #14993 - - // $this->showSummary(); + + if ($redirect) { + $this->ctrl->redirect($this, ""); // #14993 + } } diff --git a/Services/Tracking/classes/repository_statistics/class.ilLPListOfProgressGUI.php b/Services/Tracking/classes/repository_statistics/class.ilLPListOfProgressGUI.php index fd328e3d28a3..7cb73d1188e7 100644 --- a/Services/Tracking/classes/repository_statistics/class.ilLPListOfProgressGUI.php +++ b/Services/Tracking/classes/repository_statistics/class.ilLPListOfProgressGUI.php @@ -83,6 +83,19 @@ function show() $this->__showProgressList(); } + /** + * + */ + protected function saveProgress() + { + $info = new ilInfoScreenGUI($this); + $info->setContextRefId((int)$this->ref_id); + $info->setContextObjId((int)$this->details_obj_id); + $info->setContentObjType($this->obj_type); + $info->saveProgress(false); + $this->ctrl->redirect($this); + } + function details() { global $DIC; @@ -117,6 +130,10 @@ function details() include_once("./Services/InfoScreen/classes/class.ilInfoScreenGUI.php"); $info = new ilInfoScreenGUI($this); + $info->setContextRefId((int)$this->details_id); + $info->setContextObjId((int)$this->details_obj_id); + $info->setContentObjType($this->obj_type); + $info->enableLearningProgress(true); $info->setFormAction($ilCtrl->getFormAction($this)); $this->__appendUserInfo($info, $this->tracked_user); $this->__appendLPDetails($info,$this->details_obj_id,$this->tracked_user->getId()); From ad3ad5fc2566984807c264eaa30bbb4bd3165757 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 12:44:47 +0200 Subject: [PATCH 025/166] ContentPage: Export/Import --- .../classes/class.ilContentPageDataSet.php | 16 ++++++++++++++-- .../classes/class.ilContentPageExporter.php | 4 ++-- .../classes/class.ilContentPageImporter.php | 6 +++--- .../classes/class.ilObjContentPageGUI.php | 6 +++--- xml/ilias_copa_5_4.xsd | 2 ++ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilContentPageDataSet.php b/Modules/ContentPage/classes/class.ilContentPageDataSet.php index 7eb0d585ad6f..5dc8ac886af4 100644 --- a/Modules/ContentPage/classes/class.ilContentPageDataSet.php +++ b/Modules/ContentPage/classes/class.ilContentPageDataSet.php @@ -45,6 +45,7 @@ protected function getTypes($a_entity, $a_version) 'id' => 'integer', 'title' => 'text', 'description' => 'text', + 'info-tab' => 'integer', ]; default: @@ -84,6 +85,11 @@ protected function readEntityData($entity, $ids) 'id' => $obj->getId(), 'title' => $obj->getTitle(), 'description' => $obj->getDescription(), + 'info-tab' => (int)\ilContainer::_lookupContainerSetting( + $obj->getId(), + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, + true + ), ]; } } @@ -111,13 +117,19 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ $newObject = new \ilObjContentPage(); } - $newObject->setTitle($a_rec['title']); - $newObject->setDescription($a_rec['description']); + $newObject->setTitle(\ilUtil::stripSlashes($a_rec['title'])); + $newObject->setDescription(\ilUtil::stripSlashes($a_rec['description'])); if (!$newObject->getId()) { $newObject->create(); } + \ilContainer::_writeContainerSetting( + $newObject->getId(), + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, + (int)$a_rec['info-tab'] + ); + $a_mapping->addMapping('Modules/ContentPage', self::OBJ_TYPE, $a_rec['id'], $newObject->getId()); $a_mapping->addMapping( 'Services/COPage', diff --git a/Modules/ContentPage/classes/class.ilContentPageExporter.php b/Modules/ContentPage/classes/class.ilContentPageExporter.php index 9d744c6c6e80..22de15366dfe 100644 --- a/Modules/ContentPage/classes/class.ilContentPageExporter.php +++ b/Modules/ContentPage/classes/class.ilContentPageExporter.php @@ -25,7 +25,7 @@ public function init() */ public function getXmlRepresentation($a_entity, $a_schema_version, $a_id) { - ilUtil::makeDirParents($this->getAbsoluteExportDirectory()); + \ilUtil::makeDirParents($this->getAbsoluteExportDirectory()); $this->ds->setExportDirectories($this->dir_relative, $this->dir_absolute); return $this->ds->getXmlRepresentation($a_entity, $a_schema_version, $a_id, '', true, true); @@ -65,7 +65,7 @@ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_id } } - if (count($pageObjectIds)) { + if (count($pageObjectIds) > 0) { return [ [ 'component' => 'Services/COPage', diff --git a/Modules/ContentPage/classes/class.ilContentPageImporter.php b/Modules/ContentPage/classes/class.ilContentPageImporter.php index 3fd977daebc2..ce7114eef573 100644 --- a/Modules/ContentPage/classes/class.ilContentPageImporter.php +++ b/Modules/ContentPage/classes/class.ilContentPageImporter.php @@ -16,7 +16,7 @@ class ilContentPageImporter extends \ilXmlImporter implements \ilContentPageObje */ public function init() { - $this->ds = new ilContentPageDataSet(); + $this->ds = new \ilContentPageDataSet(); $this->ds->setDSPrefix('ds'); $this->ds->setImportDirectory($this->getImportDirectory()); } @@ -26,7 +26,7 @@ public function init() */ public function importXmlRepresentation($a_entity, $a_id, $a_xml, $a_mapping) { - $parser = new ilDataSetImportParser($a_entity, $this->getSchemaVersion(), $a_xml, $this->ds, $a_mapping); + $parser = new \ilDataSetImportParser($a_entity, $this->getSchemaVersion(), $a_xml, $this->ds, $a_mapping); } /** @@ -40,7 +40,7 @@ public function finalProcessing($a_mapping) foreach ($copaMap as $oldCopaId => $newCopaId) { $newCopaId = substr($newCopaId, strlen(self::OBJ_TYPE) + 1); - ilContentPagePage::_writeParentId(self::OBJ_TYPE, $newCopaId, $newCopaId); + \ilContentPagePage::_writeParentId(self::OBJ_TYPE, $newCopaId, $newCopaId); } } } \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index e1070377b785..e92a9aef4583 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -88,9 +88,9 @@ public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a $this->lng->loadLanguageModule('copa'); if ($this->object instanceof \ilObjContentPage) { - $this->infoScreenEnabled = ilContainer::_lookupContainerSetting( + $this->infoScreenEnabled = \ilContainer::_lookupContainerSetting( $this->object->getId(), - ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, + \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, true ); } @@ -99,7 +99,7 @@ public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a /** * @inheritdoc */ - protected function afterSave(ilObject $a_new_object) + protected function afterSave(\ilObject $a_new_object) { \ilUtil::sendSuccess($this->lng->txt('object_added'), true); $this->ctrl->redirect($this, 'edit'); diff --git a/xml/ilias_copa_5_4.xsd b/xml/ilias_copa_5_4.xsd index 35a02fe8b498..f767798e2bca 100644 --- a/xml/ilias_copa_5_4.xsd +++ b/xml/ilias_copa_5_4.xsd @@ -14,6 +14,7 @@ + @@ -21,5 +22,6 @@ + \ No newline at end of file From 155f27198d42146b48ee691e679b281251ed8d0f Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 16:03:43 +0200 Subject: [PATCH 026/166] ContentPage: Changed position for "Add new Item" menu --- Services/Repository/classes/class.ilObjRepositorySettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Repository/classes/class.ilObjRepositorySettings.php b/Services/Repository/classes/class.ilObjRepositorySettings.php index daba94aa6ad3..c31a669b57c4 100644 --- a/Services/Repository/classes/class.ilObjRepositorySettings.php +++ b/Services/Repository/classes/class.ilObjRepositorySettings.php @@ -265,7 +265,7 @@ public static function getDefaultNewItemGrouping() "organisation" => array("fold", "sess", "cat", "catr", "crs", "crsr", "grp", "grpr", "itgr", "book", "prg"), "communication" => array("frm", "chtr"), "breaker1" => null, - "content" => array("file", "webr", "feed", "wiki", "blog", "lm", "htlm", "sahs", "glo", "dcl", "bibl", "mcst", "mep", "copa"), + "content" => array("file", "webr", "feed", "copa", "wiki", "blog", "lm", "htlm", "sahs", "glo", "dcl", "bibl", "mcst", "mep"), "breaker2" => null, "assessment" => array("exc", "tst", "qpl", "iass"), "feedback" => array("poll", "svy", "spl"), From f3c82e1c96a74ef1d14e3a6656ee852cb6cb4f91 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 16:56:11 +0200 Subject: [PATCH 027/166] ContentPage: Database updates --- setup/sql/dbupdate_04.php | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/setup/sql/dbupdate_04.php b/setup/sql/dbupdate_04.php index ad7e42d328dd..f2ace7457b96 100644 --- a/setup/sql/dbupdate_04.php +++ b/setup/sql/dbupdate_04.php @@ -22270,3 +22270,52 @@ function writeCtrlClassEntry(ilPluginSlot $slot, array $plugin_data) { )); } ?> +<#5277> + +<#5278> + +<#5278> +getStructure(); +?> \ No newline at end of file From fbb12375f69615be6ccd81b85bf72f718b54101e Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 16:58:16 +0200 Subject: [PATCH 028/166] ContentPage: Added translations --- setup/sql/dbupdate_04.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/sql/dbupdate_04.php b/setup/sql/dbupdate_04.php index f2ace7457b96..3fd9ea909838 100644 --- a/setup/sql/dbupdate_04.php +++ b/setup/sql/dbupdate_04.php @@ -22315,7 +22315,7 @@ function writeCtrlClassEntry(ilPluginSlot $slot, array $plugin_data) { } } ?> -<#5278> +<#5279> getStructure(); ?> \ No newline at end of file From acd4fb0a78b572e264a71b188185a4c77af01985 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 17:24:35 +0200 Subject: [PATCH 029/166] ContentPage: Page editor settings --- .../classes/class.ilPageEditorSettings.php | 3 ++- .../classes/class.ilDBUpdateNewObjectType.php | 20 +++++++++++++++++++ lang/ilias_de.lang | 1 + lang/ilias_en.lang | 1 + 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Services/COPage/classes/class.ilPageEditorSettings.php b/Services/COPage/classes/class.ilPageEditorSettings.php index 8114072ae970..e4c8140c71e2 100644 --- a/Services/COPage/classes/class.ilPageEditorSettings.php +++ b/Services/COPage/classes/class.ilPageEditorSettings.php @@ -18,7 +18,8 @@ class ilPageEditorSettings "scorm" => array("sahs"), "glo" => array("gdf"), "test" => array("qpl"), - "rep" => array("root", "cat", "grp", "crs", "fold") + "rep" => array("root", "cat", "grp", "crs", "fold"), + "copa" => array("copa"), ); /** diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index 62a2477e8b21..55940c269847 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -519,6 +519,26 @@ public static function addRBACTemplate($a_obj_type, $a_id, $a_description, $a_op } } } + + public static function setRolePermission(int $a_rol_id, string $a_type, array $a_ops, int $a_ref_id) + { + global $DIC; + + $ilDB = $DIC['ilDB']; + + foreach ($a_ops as $op) { + $ilDB->replace( + 'rbac_templates', + [ + 'rol_id' => ['integer', $a_rol_id], + 'type' => ['text', $a_type], + 'ops_id' => ['integer', $op], + 'parent' => ['integer', $a_ref_id] + ], + [] + ); + } + } } ?> \ No newline at end of file diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 21ece5f93ec2..2a746df643b8 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -14236,4 +14236,5 @@ copa#:#copa_copy#:#Inhaltsseite kopieren copa#:#copa_add#:#Inhaltsseite hinzufügen copa#:#copa_edit#:#Inhaltsseite bearbeiten copa#:#obj_copa_duplicate#:#Inhaltsseite duplizieren +adve#:#adve_grp_copa#:#Inhaltsseiten wiki#:#wiki_change_notification_body_new#:#die folgende Wiki-Seite wurde neu angelegt diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 0482eb444c91..0b4883401bd7 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14234,4 +14234,5 @@ copa#:#copa_copy#:#Copy Content Page copa#:#copa_add#:#Add Content Page copa#:#copa_edit#:#Edit Content Page copa#:#obj_copa_duplicate#:#Duplicate Content Page +adve#:#adve_grp_copa#:#Content Pages wiki#:#wiki_change_notification_body_new#:#the following wiki page was created From e1837fc10bee31331c8603a53ac0bc431e3487ca Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 28 Jun 2018 17:35:50 +0200 Subject: [PATCH 030/166] ContentPage: Internal links for page editor content --- .../classes/class.ilContentPagePageConfig.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Modules/ContentPage/classes/class.ilContentPagePageConfig.php b/Modules/ContentPage/classes/class.ilContentPagePageConfig.php index e24174e1d908..47567f7df5a8 100644 --- a/Modules/ContentPage/classes/class.ilContentPagePageConfig.php +++ b/Modules/ContentPage/classes/class.ilContentPagePageConfig.php @@ -6,4 +6,14 @@ */ class ilContentPagePageConfig extends \ilPageConfig { + /** + * @inheritdoc + */ + public function init() + { + $this->setEnableInternalLinks(true); + $this->setIntLinkHelpDefaultType('RepositoryItem'); + $this->setSinglePageMode(true); + $this->setEnablePermissionChecks(true); + } } \ No newline at end of file From dd8d59421ead1309133a980eb912a84f9644d79c Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 29 Jun 2018 11:06:31 +0200 Subject: [PATCH 031/166] ContentPage: Added translations --- lang/ilias_de.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 2a746df643b8..3de7b2405c54 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -2554,7 +2554,7 @@ common#:#enable_calendar#:#Kalender aktivieren common#:#enable_course_group_notifications#:#Tägliche Mail für Gruppen und Kurs News common#:#enable_course_group_notifications_desc#:#Bei Aktivierung können Teilnehmer eine Zusammenfassung der News abonnieren. common#:#enable_custom_icons#:#Anpassbare Symbole aktivieren -common#:#enable_custom_icons_info#:#Damit können Sie eigene Symbole (Icons) für einzelne Container-Objekte definieren. Die entsprechenden Bilddateien laden Sie in den Eigenschaften der Objekte hoch. +common#:#enable_custom_icons_info#:#Damit können Sie eigene Symbole (Icons) für einzelne Container-Objekte und die Inhaltsseite definieren. Die entsprechenden Bilddateien laden Sie in den Eigenschaften der Objekte hoch. common#:#enable_disk_quota_reminder_mail#:#Sende Disk-Quota-Erinnerungsnachrichten common#:#enable_disk_quota_summary_mail#:#Sende Disk-Quota-Zusammenfassungen common#:#enable_disk_quota_summary_mail_desc#:#Durch das Aktivieren ist es möglich, bestimmten Benutzern eine tägliche Benachrichtigung über die überschrittenen Disk-Quotas zukommen zu lassen. From b7e230ef3afa6bfea12aaa3723893f86b70b1e21 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 29 Jun 2018 12:36:14 +0200 Subject: [PATCH 032/166] ContentPage: Respected initial permission guideline --- .../classes/class.ilDBUpdateNewObjectType.php | 188 +++++++++++++++++- setup/sql/dbupdate_04.php | 5 + 2 files changed, 191 insertions(+), 2 deletions(-) diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index 55940c269847..fce146a04882 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -18,6 +18,102 @@ class ilDBUpdateNewObjectType const RBAC_OP_WRITE = 4; const RBAC_OP_DELETE = 6; const RBAC_OP_COPY = 99; + + protected static $initialPermissionDefinition = [ + 'role' => [ + 'User' => [ + 'ignore_for_authoring_objects' => true, + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + ] + ] + ], + 'rolt' => [ + 'il_crs_admin' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + self::RBAC_OP_WRITE, + self::RBAC_OP_DELETE, + self::RBAC_OP_COPY, + self::RBAC_OP_EDIT_PERMISSIONS, + ], + 'lp' => true, + 'create' => [ + 'crs', + 'grp', + 'fold', + ] + ], + 'il_crs_tutor' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + self::RBAC_OP_WRITE, + self::RBAC_OP_COPY, + ], + 'create' => [ + 'crs', + 'fold', + ] + ], + 'il_crs_member' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + ] + ], + 'il_grp_admin' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + self::RBAC_OP_WRITE, + self::RBAC_OP_DELETE, + self::RBAC_OP_COPY, + self::RBAC_OP_EDIT_PERMISSIONS, + ], + 'lp' => true, + 'create' => [ + 'grp', + 'fold', + ] + ], + 'il_grp_member' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + ] + ], + 'Author' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_READ, + self::RBAC_OP_WRITE, + self::RBAC_OP_DELETE, + self::RBAC_OP_COPY, + self::RBAC_OP_EDIT_PERMISSIONS, + ], + 'lp' => true, + 'create' => [ + 'cat', + 'crs', + 'grp', + 'fold', + ] + ], + 'Local Administrator' => [ + 'object' => [ + self::RBAC_OP_VISIBLE, + self::RBAC_OP_DELETE, + self::RBAC_OP_EDIT_PERMISSIONS, + ], + 'create' => [ + 'cat', + ] + ], + ] + ]; /** * Add new type to object data @@ -526,19 +622,107 @@ public static function setRolePermission(int $a_rol_id, string $a_type, array $a $ilDB = $DIC['ilDB']; - foreach ($a_ops as $op) { + foreach ($a_ops as $ops_id) { + if ($ops_id == self::RBAC_OP_COPY) { + $ops_id = self::getCustomRBACOperationId('copy'); + } + $ilDB->replace( 'rbac_templates', [ 'rol_id' => ['integer', $a_rol_id], 'type' => ['text', $a_type], - 'ops_id' => ['integer', $op], + 'ops_id' => ['integer', $ops_id], 'parent' => ['integer', $a_ref_id] ], [] ); } } + + + /** + * This method will apply the 'Initial Permissions Guideline' when introducing new object types. + * This method does not apply permissions to existing obejcts in the ILIAS repository ('change existing objects'). + * @param string $objectType + * @param bool $hasLearningProgress + * @param bool $usedForAuthoring + * @see https://www.ilias.de/docu/goto_docu_wiki_wpage_2273_1357.html + */ + public static function applyInitialPermissionGuideline(string $objectType, bool $hasLearningProgress = false, bool $usedForAuthoring = false) { + global $DIC; + + $ilDB = $DIC['ilDB']; + + $objectTypeId = self::getObjectTypeId($objectType); + if (!$objectTypeId) { + die("Something went wrong, there MUST be valid id for object_type " . $objectType); + } + + $contentPageCreateOperationId = ilDBUpdateNewObjectType::getCustomRBACOperationId('create_' . $objectType); + if (!$contentPageCreateOperationId) { + die("Something went wrong, missing CREATE operation id for object type " . $objectType); + } + + $globalRoleFolderId = 8; // Maybe there is another way to determine this id + + $learningProgressPermissions = []; + if ($hasLearningProgress) { + $learningProgressPermissions = array_filter([ + self::getCustomRBACOperationId('read_learning_progress'), + self::getCustomRBACOperationId('edit_learning_progress'), + ]); + } + + foreach (self::$initialPermissionDefinition as $roleType => $roles) { + foreach ($roles as $roleTitle => $definition) { + if ( + true === $usedForAuthoring && + array_key_exists('ignore_for_authoring_objects', $definition) && + true === $definition['ignore_for_authoring_objects'] + ) { + continue; + } + + $query = "SELECT obj_id FROM object_data WHERE type = %s AND title = %s"; + $res = $ilDB->queryF($query, ['text', 'text'], [$roleType, $roleTitle]); + if (1 == $ilDB->numRows($res)) { + $row = $ilDB->fetchAssoc($res); + $roleId = (int)$row['obj_id']; + + $operationIds = []; + + if (array_key_exists('object', $definition) && is_array($definition['object'])) { + $operationIds = array_merge($operationIds, (array)$definition['object']); + } + + if (array_key_exists('lp', $definition) && true === $definition['lp']) { + $operationIds = array_merge($operationIds, $learningProgressPermissions); + } + + ilDBUpdateNewObjectType::setRolePermission( + $roleId, + $objectType, + array_filter(array_map('intval', $operationIds)), + $globalRoleFolderId + ); + + if (array_key_exists('create', $definition) && is_array($definition['create'])) { + foreach ($definition['create'] as $containerObjectType) { + ilDBUpdateNewObjectType::setRolePermission( + $roleId, + $containerObjectType, + [ + $contentPageCreateOperationId + ], + $globalRoleFolderId + ); + } + } + } + } + } + } } ?> \ No newline at end of file diff --git a/setup/sql/dbupdate_04.php b/setup/sql/dbupdate_04.php index 3fd9ea909838..c571d1899ca6 100644 --- a/setup/sql/dbupdate_04.php +++ b/setup/sql/dbupdate_04.php @@ -22318,4 +22318,9 @@ function writeCtrlClassEntry(ilPluginSlot $slot, array $plugin_data) { <#5279> getStructure(); +?> +<#5280> + \ No newline at end of file From 79971921f3f7e5eb1c36ca45c877468582f6b8b7 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 29 Jun 2018 12:40:37 +0200 Subject: [PATCH 033/166] ContentPage: Internal links for page editor content --- .../DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index fce146a04882..19e83d8ff691 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -59,6 +59,7 @@ class ilDBUpdateNewObjectType ] ], 'il_crs_member' => [ + 'ignore_for_authoring_objects' => true, 'object' => [ self::RBAC_OP_VISIBLE, self::RBAC_OP_READ, @@ -80,6 +81,7 @@ class ilDBUpdateNewObjectType ] ], 'il_grp_member' => [ + 'ignore_for_authoring_objects' => true, 'object' => [ self::RBAC_OP_VISIBLE, self::RBAC_OP_READ, From ac8155601171a40f8a12b9ea8df39590953de45c Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 29 Jun 2018 12:42:53 +0200 Subject: [PATCH 034/166] ContentPage: Internal links for page editor content --- .../DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index 19e83d8ff691..908b71d45e46 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -661,8 +661,8 @@ public static function applyInitialPermissionGuideline(string $objectType, bool die("Something went wrong, there MUST be valid id for object_type " . $objectType); } - $contentPageCreateOperationId = ilDBUpdateNewObjectType::getCustomRBACOperationId('create_' . $objectType); - if (!$contentPageCreateOperationId) { + $objectCreateOperationId = ilDBUpdateNewObjectType::getCustomRBACOperationId('create_' . $objectType); + if (!$objectCreateOperationId) { die("Something went wrong, missing CREATE operation id for object type " . $objectType); } @@ -715,7 +715,7 @@ public static function applyInitialPermissionGuideline(string $objectType, bool $roleId, $containerObjectType, [ - $contentPageCreateOperationId + $objectCreateOperationId ], $globalRoleFolderId ); From 00b81a4b9bd44fcbbaa8260ba2020f35efff7062 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 29 Jun 2018 12:43:26 +0200 Subject: [PATCH 035/166] ContentPage: Respected initial permission guideline --- .../DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index 908b71d45e46..26304da6bcdc 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -702,7 +702,7 @@ public static function applyInitialPermissionGuideline(string $objectType, bool $operationIds = array_merge($operationIds, $learningProgressPermissions); } - ilDBUpdateNewObjectType::setRolePermission( + self::setRolePermission( $roleId, $objectType, array_filter(array_map('intval', $operationIds)), @@ -711,7 +711,7 @@ public static function applyInitialPermissionGuideline(string $objectType, bool if (array_key_exists('create', $definition) && is_array($definition['create'])) { foreach ($definition['create'] as $containerObjectType) { - ilDBUpdateNewObjectType::setRolePermission( + self::setRolePermission( $roleId, $containerObjectType, [ From d835e4e877a212d5ee0dba3af87b2ae0efd6a019 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Fri, 29 Jun 2018 14:05:40 +0200 Subject: [PATCH 036/166] fix indents --- src/UI/Implementation/Component/Input/Field/Password.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/Implementation/Component/Input/Field/Password.php b/src/UI/Implementation/Component/Input/Field/Password.php index 543d5b6c8952..4aafddb98577 100644 --- a/src/UI/Implementation/Component/Input/Field/Password.php +++ b/src/UI/Implementation/Component/Input/Field/Password.php @@ -66,7 +66,7 @@ public function withStandardConstraints($min_length=8, $lower=true, $upper=true, $validation = new \ILIAS\Validation\Factory($data); $pw_validation = $validation->password(); $constraints = [ - $pw_validation->hasMinLength($min_length), + $pw_validation->hasMinLength($min_length), ]; if($lower) { From 00e1bec07ce69f74cf99de1f50e9c569e20c58f2 Mon Sep 17 00:00:00 2001 From: Nils Haagen Date: Fri, 29 Jun 2018 14:28:38 +0200 Subject: [PATCH 037/166] added basic tests for password constraints --- .../Constraints/PasswordContraintsTest.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/Validation/Constraints/PasswordContraintsTest.php diff --git a/tests/Validation/Constraints/PasswordContraintsTest.php b/tests/Validation/Constraints/PasswordContraintsTest.php new file mode 100644 index 000000000000..b9d7992bfbb2 --- /dev/null +++ b/tests/Validation/Constraints/PasswordContraintsTest.php @@ -0,0 +1,71 @@ + Extended GPL, see docs/LICENSE */ + +require_once("libs/composer/vendor/autoload.php"); + +use ILIAS\Validation; +use ILIAS\Data; + +/** + * Test standard-constraints of a password. + * + * @author Nils Haagen + */ +class PasswordContraintsTest extends PHPUnit_Framework_TestCase { + + /** + * Test a set of values + * + * @return array[[$constraint,$ok_values,$error_values]] + */ + public function constraintsProvider() { + $d = new \ILIAS\Data\Factory(); + $validation = new \ILIAS\Validation\Factory($d); + $v = $validation->password(); + + return array( + array( + $v->hasMinLength(3), + [$d->password('abc'), $d->password('abcd')], + [$d->password('a'), $d->password('ab')] + ), + array( + $v->hasLowerChars(), + [$d->password('abc'), $d->password('AbC')], + [$d->password('AB'), $d->password('21'), $d->password('#*+')] + ), + + array( + $v->hasUpperChars(), + [$d->password('Abc'), $d->password('ABC')], + [$d->password('abc'), $d->password('21'), $d->password('#*+')] + ), + array( + $v->hasNumbers(), + [$d->password('Ab1'), $d->password('123')], + [$d->password('abc'), $d->password('ABC'), $d->password('#*+')] + ), + + array( + $v->hasSpecialChars(), + [$d->password('Ab+'), $d->password('123#')], + [$d->password('abc'), $d->password('ABC'), $d->password('123')] + ) + ); + } + + /** + * @dataProvider constraintsProvider + */ + public function testAccept($constraint, $ok_values, $error_values) { + foreach($ok_values as $ok_value){ + $this->assertTrue($constraint->accepts($ok_value)); + } + foreach($error_values as $error_value){ + $this->assertFalse($constraint->accepts($error_value)); + } + } + + +} \ No newline at end of file From 99ce7026ad9d064dcaf4f3d1825c452551407ad3 Mon Sep 17 00:00:00 2001 From: mjansen Date: Mon, 2 Jul 2018 11:28:14 +0200 Subject: [PATCH 038/166] ContentPage: Composer files --- .../vendor/composer/autoload_classmap.php | 49 ++++++++++++++++++- .../vendor/composer/autoload_static.php | 49 ++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/libs/composer/vendor/composer/autoload_classmap.php b/libs/composer/vendor/composer/autoload_classmap.php index 406cf997b61a..b931b48cfd73 100644 --- a/libs/composer/vendor/composer/autoload_classmap.php +++ b/libs/composer/vendor/composer/autoload_classmap.php @@ -425,6 +425,7 @@ 'ILIAS\\Data\\DataSize' => $baseDir . '/../../src/Data/DataSize.php', 'ILIAS\\Data\\Factory' => $baseDir . '/../../src/Data/Factory.php', 'ILIAS\\Data\\NotOKException' => $baseDir . '/../../src/Data/NotOKException.php', + 'ILIAS\\Data\\Password' => $baseDir . '/../../src/Data/Password.php', 'ILIAS\\Data\\Result' => $baseDir . '/../../src/Data/Result.php', 'ILIAS\\Data\\Result\\Error' => $baseDir . '/../../src/Data/Result/Error.php', 'ILIAS\\Data\\Result\\Ok' => $baseDir . '/../../src/Data/Result/Ok.php', @@ -531,6 +532,7 @@ 'ILIAS\\Transformation\\Transformation' => $baseDir . '/../../src/Transformation/Transformation.php', 'ILIAS\\Transformation\\Transformations\\AddLabels' => $baseDir . '/../../src/Transformation/Transformations/AddLabels.php', 'ILIAS\\Transformation\\Transformations\\Custom' => $baseDir . '/../../src/Transformation/Transformations/Custom.php', + 'ILIAS\\Transformation\\Transformations\\Data' => $baseDir . '/../../src/Transformation/Transformations/Data.php', 'ILIAS\\Transformation\\Transformations\\SplitString' => $baseDir . '/../../src/Transformation/Transformations/SplitString.php', 'ILIAS\\UI\\Component\\Breadcrumbs\\Breadcrumbs' => $baseDir . '/../../src/UI/Component/Breadcrumbs/Breadcrumbs.php', 'ILIAS\\UI\\Component\\Button\\Bulky' => $baseDir . '/../../src/UI/Component/Button/Bulky.php', @@ -588,6 +590,7 @@ 'ILIAS\\UI\\Component\\Input\\Field\\Group' => $baseDir . '/../../src/UI/Component/Input/Field/Group.php', 'ILIAS\\UI\\Component\\Input\\Field\\Input' => $baseDir . '/../../src/UI/Component/Input/Field/Input.php', 'ILIAS\\UI\\Component\\Input\\Field\\Numeric' => $baseDir . '/../../src/UI/Component/Input/Field/Numeric.php', + 'ILIAS\\UI\\Component\\Input\\Field\\Password' => $baseDir . '/../../src/UI/Component/Input/Field/Password.php', 'ILIAS\\UI\\Component\\Input\\Field\\Section' => $baseDir . '/../../src/UI/Component/Input/Field/Section.php', 'ILIAS\\UI\\Component\\Input\\Field\\Text' => $baseDir . '/../../src/UI/Component/Input/Field/Text.php', 'ILIAS\\UI\\Component\\Item\\Factory' => $baseDir . '/../../src/UI/Component/Item/Factory.php', @@ -604,6 +607,8 @@ 'ILIAS\\UI\\Component\\Listing\\Listing' => $baseDir . '/../../src/UI/Component/Listing/Listing.php', 'ILIAS\\UI\\Component\\Listing\\Ordered' => $baseDir . '/../../src/UI/Component/Listing/Ordered.php', 'ILIAS\\UI\\Component\\Listing\\Unordered' => $baseDir . '/../../src/UI/Component/Listing/Unordered.php', + 'ILIAS\\UI\\Component\\MessageBox\\Factory' => $baseDir . '/../../src/UI/Component/MessageBox/Factory.php', + 'ILIAS\\UI\\Component\\MessageBox\\MessageBox' => $baseDir . '/../../src/UI/Component/MessageBox/MessageBox.php', 'ILIAS\\UI\\Component\\Modal\\Factory' => $baseDir . '/../../src/UI/Component/Modal/Factory.php', 'ILIAS\\UI\\Component\\Modal\\Interruptive' => $baseDir . '/../../src/UI/Component/Modal/Interruptive.php', 'ILIAS\\UI\\Component\\Modal\\InterruptiveItem' => $baseDir . '/../../src/UI/Component/Modal/InterruptiveItem.php', @@ -708,6 +713,7 @@ 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Input' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Input.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\InputInternal' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/InputInternal.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Numeric' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Numeric.php', + 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Password' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Password.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Renderer' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Section' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Section.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Text' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Text.php', @@ -731,6 +737,9 @@ 'ILIAS\\UI\\Implementation\\Component\\Listing\\Ordered' => $baseDir . '/../../src/UI/Implementation/Component/Listing/Ordered.php', 'ILIAS\\UI\\Implementation\\Component\\Listing\\Renderer' => $baseDir . '/../../src/UI/Implementation/Component/Listing/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Listing\\Unordered' => $baseDir . '/../../src/UI/Implementation/Component/Listing/Unordered.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\Factory' => $baseDir . '/../../src/UI/Implementation/Component/MessageBox/Factory.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\MessageBox' => $baseDir . '/../../src/UI/Implementation/Component/MessageBox/MessageBox.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\Renderer' => $baseDir . '/../../src/UI/Implementation/Component/MessageBox/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\Factory' => $baseDir . '/../../src/UI/Implementation/Component/Modal/Factory.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\Interruptive' => $baseDir . '/../../src/UI/Implementation/Component/Modal/Interruptive.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\InterruptiveItem' => $baseDir . '/../../src/UI/Implementation/Component/Modal/InterruptiveItem.php', @@ -989,6 +998,9 @@ 'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', 'Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', 'Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SlackbotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', 'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', 'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', 'Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', @@ -1004,6 +1016,7 @@ 'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', 'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', 'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', 'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', 'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', 'Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', @@ -1218,6 +1231,13 @@ 'POP3' => $vendorDir . '/phpmailer/phpmailer/class.pop3.php', 'Parser' => $baseDir . '/../../Services/Utilities/classes/Parser.php', 'Pimple\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Container.php', + 'Pimple\\Exception\\ExpectedInvokableException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', + 'Pimple\\Exception\\FrozenServiceException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', + 'Pimple\\Exception\\InvalidServiceIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', + 'Pimple\\Exception\\UnknownIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', + 'Pimple\\Psr11\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/Container.php', + 'Pimple\\Psr11\\ServiceLocator' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', + 'Pimple\\ServiceIterator' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceIterator.php', 'Pimple\\ServiceProviderInterface' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', 'Pimple\\Tests\\Fixtures\\Invokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', 'Pimple\\Tests\\Fixtures\\NonInvokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', @@ -1225,6 +1245,9 @@ 'Pimple\\Tests\\Fixtures\\Service' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', 'Pimple\\Tests\\PimpleTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', + 'Pimple\\Tests\\Psr11\\ContainerTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', + 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', + 'Pimple\\Tests\\ServiceIteratorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', @@ -1508,6 +1531,7 @@ 'SurveyTextQuestion' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestion.php', 'SurveyTextQuestionEvaluation' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionEvaluation.php', 'SurveyTextQuestionGUI' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionGUI.php', + 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', 'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php', @@ -1516,6 +1540,7 @@ 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php', 'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php', 'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', 'TCPDF' => $vendorDir . '/tecnickcom/tcpdf/tcpdf.php', @@ -2585,7 +2610,6 @@ 'ilAuthContainerApache' => $baseDir . '/../../Services/AuthApache/classes/class.ilAuthContainerApache.php', 'ilAuthContainerBase' => $baseDir . '/../../Services/Authentication/classes/class.ilAuthContainerBase.php', 'ilAuthContainerCAS' => $baseDir . '/../../Services/CAS/classes/class.ilAuthContainerCAS.php', - 'ilAuthContainerCalendarToken' => $baseDir . '/../../Services/Calendar/classes/class.ilAuthContainerCalendarToken.php', 'ilAuthContainerDecorator' => $baseDir . '/../../Services/Authentication/classes/class.ilAuthContainerDecorator.php', 'ilAuthContainerECS' => $baseDir . '/../../Services/WebServices/ECS/classes/class.ilAuthContainerECS.php', 'ilAuthContainerSOAP' => $baseDir . '/../../Services/SOAPAuth/classes/class.ilAuthContainerSOAP.php', @@ -3107,6 +3131,7 @@ 'ilContainerBlockPropertiesStorage' => $baseDir . '/../../Services/Container/classes/class.ilContainerBlockPropertiesStorage.php', 'ilContainerByTypeContentGUI' => $baseDir . '/../../Services/Container/classes/class.ilContainerByTypeContentGUI.php', 'ilContainerContentGUI' => $baseDir . '/../../Services/Container/classes/class.ilContainerContentGUI.php', + 'ilContainerCustomIconConfiguration' => $baseDir . '/../../Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php', 'ilContainerExporter' => $baseDir . '/../../Services/Container/classes/class.ilContainerExporter.php', 'ilContainerGUI' => $baseDir . '/../../Services/Container/classes/class.ilContainerGUI.php', 'ilContainerImporter' => $baseDir . '/../../Services/Container/classes/class.ilContainerImporter.php', @@ -3144,12 +3169,22 @@ 'ilContainerXmlParser' => $baseDir . '/../../Services/Container/classes/class.ilContainerXmlParser.php', 'ilContainerXmlWriter' => $baseDir . '/../../Services/Container/classes/class.ilContainerXmlWriter.php', 'ilContentObject2FO' => $baseDir . '/../../Services/Transformation/classes/class.ilContentObject2FO.php', + 'ilContentPageDataSet' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPageDataSet.php', + 'ilContentPageExporter' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPageExporter.php', + 'ilContentPageImporter' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPageImporter.php', + 'ilContentPageLP' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPageLP.php', + 'ilContentPageObjectConstants' => $baseDir . '/../../Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php', + 'ilContentPagePage' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPagePage.php', + 'ilContentPagePageCommandForwarder' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php', + 'ilContentPagePageConfig' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPagePageConfig.php', + 'ilContentPagePageGUI' => $baseDir . '/../../Modules/ContentPage/classes/class.ilContentPagePageGUI.php', 'ilContentStyleSettings' => $baseDir . '/../../Services/Style/Content/classes/class.ilContentStyleSettings.php', 'ilContentStyleSettingsGUI' => $baseDir . '/../../Services/Style/Content/classes/class.ilContentStyleSettingsGUI.php', 'ilContentStyleWAC' => $baseDir . '/../../Services/Style/classes/class.ilContentStyleWAC.php', 'ilContentStylesTableGUI' => $baseDir . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => $baseDir . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => $baseDir . '/../../Services/Context/classes/class.ilContextApacheSSO.php', + 'ilContextBehat' => $baseDir . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => $baseDir . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => $baseDir . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => $baseDir . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -3253,6 +3288,7 @@ 'ilCurlConnection' => $baseDir . '/../../Services/WebServices/Curl/classes/class.ilCurlConnection.php', 'ilCurlConnectionException' => $baseDir . '/../../Services/WebServices/Curl/classes/class.ilCurlConnectionException.php', 'ilCustomBlock' => $baseDir . '/../../Services/Block/classes/class.ilCustomBlock.php', + 'ilCustomIconObjectConfiguration' => $baseDir . '/../../Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php', 'ilCustomInputGUI' => $baseDir . '/../../Services/Form/classes/class.ilCustomInputGUI.php', 'ilCustomUserFieldSettingsTableGUI' => $baseDir . '/../../Services/User/classes/class.ilCustomUserFieldSettingsTableGUI.php', 'ilCustomUserFieldsGUI' => $baseDir . '/../../Services/User/classes/class.ilCustomUserFieldsGUI.php', @@ -4580,6 +4616,10 @@ 'ilObjContentObject' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObject.php', 'ilObjContentObjectAccess' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObjectAccess.php', 'ilObjContentObjectGUI' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObjectGUI.php', + 'ilObjContentPage' => $baseDir . '/../../Modules/ContentPage/classes/class.ilObjContentPage.php', + 'ilObjContentPageAccess' => $baseDir . '/../../Modules/ContentPage/classes/class.ilObjContentPageAccess.php', + 'ilObjContentPageGUI' => $baseDir . '/../../Modules/ContentPage/classes/class.ilObjContentPageGUI.php', + 'ilObjContentPageListGUI' => $baseDir . '/../../Modules/ContentPage/classes/class.ilObjContentPageListGUI.php', 'ilObjCourse' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourse.php', 'ilObjCourseAccess' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseAccess.php', 'ilObjCourseAdministration' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseAdministration.php', @@ -4967,6 +5007,7 @@ 'ilObjectActivation' => $baseDir . '/../../Services/Object/classes/class.ilObjectActivation.php', 'ilObjectActivationGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectActivationGUI.php', 'ilObjectAddNewItemGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectAddNewItemGUI.php', + 'ilObjectAppEventListener' => $baseDir . '/../../Services/Object/classes/class.ilObjectAppEventListener.php', 'ilObjectBadgeTableGUI' => $baseDir . '/../../Services/Badge/classes/class.ilObjectBadgeTableGUI.php', 'ilObjectConsumerTableGUI' => $baseDir . '/../../Services/LTI/classes/Consumer/class.ilLTIConsumerTableGUI.php', 'ilObjectCopyCourseGroupSelectionTableGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectCopyCourseGroupSelectionTableGUI.php', @@ -4974,6 +5015,12 @@ 'ilObjectCopyProgressTableGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectCopyProgressTableGUI.php', 'ilObjectCopySearchResultTableGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectCopySearchResultTableGUI.php', 'ilObjectCopySelectionTableGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjectCopySelectionTableGUI.php', + 'ilObjectCustomIcon' => $baseDir . '/../../Services/Object/Icon/interfaces/interface.ilObjectCustomIcon.php', + 'ilObjectCustomIconConfiguration' => $baseDir . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconConfiguration.php', + 'ilObjectCustomIconConfigurationGUI' => $baseDir . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php', + 'ilObjectCustomIconFactory' => $baseDir . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php', + 'ilObjectCustomIconImpl' => $baseDir . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php', + 'ilObjectCustomIconUploadPostProcessor' => $baseDir . '/../../Services/Object/Icon/interfaces/interface.ilObjectCustomIconUploadPostProcessor.php', 'ilObjectCustomUserFieldHistory' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldHistory.php', 'ilObjectCustomUserFieldsGUI' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsGUI.php', 'ilObjectCustomUserFieldsTableGUI' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsTableGUI.php', diff --git a/libs/composer/vendor/composer/autoload_static.php b/libs/composer/vendor/composer/autoload_static.php index 4f9ba687f77c..fa7b49cf4d9a 100644 --- a/libs/composer/vendor/composer/autoload_static.php +++ b/libs/composer/vendor/composer/autoload_static.php @@ -617,6 +617,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\Data\\DataSize' => __DIR__ . '/../..' . '/../../src/Data/DataSize.php', 'ILIAS\\Data\\Factory' => __DIR__ . '/../..' . '/../../src/Data/Factory.php', 'ILIAS\\Data\\NotOKException' => __DIR__ . '/../..' . '/../../src/Data/NotOKException.php', + 'ILIAS\\Data\\Password' => __DIR__ . '/../..' . '/../../src/Data/Password.php', 'ILIAS\\Data\\Result' => __DIR__ . '/../..' . '/../../src/Data/Result.php', 'ILIAS\\Data\\Result\\Error' => __DIR__ . '/../..' . '/../../src/Data/Result/Error.php', 'ILIAS\\Data\\Result\\Ok' => __DIR__ . '/../..' . '/../../src/Data/Result/Ok.php', @@ -723,6 +724,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\Transformation\\Transformation' => __DIR__ . '/../..' . '/../../src/Transformation/Transformation.php', 'ILIAS\\Transformation\\Transformations\\AddLabels' => __DIR__ . '/../..' . '/../../src/Transformation/Transformations/AddLabels.php', 'ILIAS\\Transformation\\Transformations\\Custom' => __DIR__ . '/../..' . '/../../src/Transformation/Transformations/Custom.php', + 'ILIAS\\Transformation\\Transformations\\Data' => __DIR__ . '/../..' . '/../../src/Transformation/Transformations/Data.php', 'ILIAS\\Transformation\\Transformations\\SplitString' => __DIR__ . '/../..' . '/../../src/Transformation/Transformations/SplitString.php', 'ILIAS\\UI\\Component\\Breadcrumbs\\Breadcrumbs' => __DIR__ . '/../..' . '/../../src/UI/Component/Breadcrumbs/Breadcrumbs.php', 'ILIAS\\UI\\Component\\Button\\Bulky' => __DIR__ . '/../..' . '/../../src/UI/Component/Button/Bulky.php', @@ -780,6 +782,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Component\\Input\\Field\\Group' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Group.php', 'ILIAS\\UI\\Component\\Input\\Field\\Input' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Input.php', 'ILIAS\\UI\\Component\\Input\\Field\\Numeric' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Numeric.php', + 'ILIAS\\UI\\Component\\Input\\Field\\Password' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Password.php', 'ILIAS\\UI\\Component\\Input\\Field\\Section' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Section.php', 'ILIAS\\UI\\Component\\Input\\Field\\Text' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Text.php', 'ILIAS\\UI\\Component\\Item\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Component/Item/Factory.php', @@ -796,6 +799,8 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Component\\Listing\\Listing' => __DIR__ . '/../..' . '/../../src/UI/Component/Listing/Listing.php', 'ILIAS\\UI\\Component\\Listing\\Ordered' => __DIR__ . '/../..' . '/../../src/UI/Component/Listing/Ordered.php', 'ILIAS\\UI\\Component\\Listing\\Unordered' => __DIR__ . '/../..' . '/../../src/UI/Component/Listing/Unordered.php', + 'ILIAS\\UI\\Component\\MessageBox\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Component/MessageBox/Factory.php', + 'ILIAS\\UI\\Component\\MessageBox\\MessageBox' => __DIR__ . '/../..' . '/../../src/UI/Component/MessageBox/MessageBox.php', 'ILIAS\\UI\\Component\\Modal\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Component/Modal/Factory.php', 'ILIAS\\UI\\Component\\Modal\\Interruptive' => __DIR__ . '/../..' . '/../../src/UI/Component/Modal/Interruptive.php', 'ILIAS\\UI\\Component\\Modal\\InterruptiveItem' => __DIR__ . '/../..' . '/../../src/UI/Component/Modal/InterruptiveItem.php', @@ -900,6 +905,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Input' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Input.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\InputInternal' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/InputInternal.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Numeric' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Numeric.php', + 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Password' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Password.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Renderer' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Section' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Section.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Text' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Text.php', @@ -923,6 +929,9 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Implementation\\Component\\Listing\\Ordered' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Listing/Ordered.php', 'ILIAS\\UI\\Implementation\\Component\\Listing\\Renderer' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Listing/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Listing\\Unordered' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Listing/Unordered.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/MessageBox/Factory.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\MessageBox' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/MessageBox/MessageBox.php', + 'ILIAS\\UI\\Implementation\\Component\\MessageBox\\Renderer' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/MessageBox/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Modal/Factory.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\Interruptive' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Modal/Interruptive.php', 'ILIAS\\UI\\Implementation\\Component\\Modal\\InterruptiveItem' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Modal/InterruptiveItem.php', @@ -1181,6 +1190,9 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', 'Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', 'Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SlackbotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', 'Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', 'Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', 'Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', @@ -1196,6 +1208,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', 'Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', 'Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', 'Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', 'Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', 'Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', @@ -1410,6 +1423,13 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'POP3' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.pop3.php', 'Parser' => __DIR__ . '/../..' . '/../../Services/Utilities/classes/Parser.php', 'Pimple\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Container.php', + 'Pimple\\Exception\\ExpectedInvokableException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', + 'Pimple\\Exception\\FrozenServiceException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', + 'Pimple\\Exception\\InvalidServiceIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', + 'Pimple\\Exception\\UnknownIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', + 'Pimple\\Psr11\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/Container.php', + 'Pimple\\Psr11\\ServiceLocator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', + 'Pimple\\ServiceIterator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceIterator.php', 'Pimple\\ServiceProviderInterface' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', 'Pimple\\Tests\\Fixtures\\Invokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', 'Pimple\\Tests\\Fixtures\\NonInvokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', @@ -1417,6 +1437,9 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Pimple\\Tests\\Fixtures\\Service' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', 'Pimple\\Tests\\PimpleTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', + 'Pimple\\Tests\\Psr11\\ContainerTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', + 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', + 'Pimple\\Tests\\ServiceIteratorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', @@ -1700,6 +1723,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'SurveyTextQuestion' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestion.php', 'SurveyTextQuestionEvaluation' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionEvaluation.php', 'SurveyTextQuestionGUI' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionGUI.php', + 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', 'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php', @@ -1708,6 +1732,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php', 'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php', 'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', 'TCPDF' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf.php', @@ -2777,7 +2802,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilAuthContainerApache' => __DIR__ . '/../..' . '/../../Services/AuthApache/classes/class.ilAuthContainerApache.php', 'ilAuthContainerBase' => __DIR__ . '/../..' . '/../../Services/Authentication/classes/class.ilAuthContainerBase.php', 'ilAuthContainerCAS' => __DIR__ . '/../..' . '/../../Services/CAS/classes/class.ilAuthContainerCAS.php', - 'ilAuthContainerCalendarToken' => __DIR__ . '/../..' . '/../../Services/Calendar/classes/class.ilAuthContainerCalendarToken.php', 'ilAuthContainerDecorator' => __DIR__ . '/../..' . '/../../Services/Authentication/classes/class.ilAuthContainerDecorator.php', 'ilAuthContainerECS' => __DIR__ . '/../..' . '/../../Services/WebServices/ECS/classes/class.ilAuthContainerECS.php', 'ilAuthContainerSOAP' => __DIR__ . '/../..' . '/../../Services/SOAPAuth/classes/class.ilAuthContainerSOAP.php', @@ -3299,6 +3323,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilContainerBlockPropertiesStorage' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerBlockPropertiesStorage.php', 'ilContainerByTypeContentGUI' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerByTypeContentGUI.php', 'ilContainerContentGUI' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerContentGUI.php', + 'ilContainerCustomIconConfiguration' => __DIR__ . '/../..' . '/../../Services/Object/Icon/classes/class.ilContainerCustomIconConfiguration.php', 'ilContainerExporter' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerExporter.php', 'ilContainerGUI' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerGUI.php', 'ilContainerImporter' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerImporter.php', @@ -3336,12 +3361,22 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilContainerXmlParser' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerXmlParser.php', 'ilContainerXmlWriter' => __DIR__ . '/../..' . '/../../Services/Container/classes/class.ilContainerXmlWriter.php', 'ilContentObject2FO' => __DIR__ . '/../..' . '/../../Services/Transformation/classes/class.ilContentObject2FO.php', + 'ilContentPageDataSet' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPageDataSet.php', + 'ilContentPageExporter' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPageExporter.php', + 'ilContentPageImporter' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPageImporter.php', + 'ilContentPageLP' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPageLP.php', + 'ilContentPageObjectConstants' => __DIR__ . '/../..' . '/../../Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php', + 'ilContentPagePage' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPagePage.php', + 'ilContentPagePageCommandForwarder' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php', + 'ilContentPagePageConfig' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPagePageConfig.php', + 'ilContentPagePageGUI' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilContentPagePageGUI.php', 'ilContentStyleSettings' => __DIR__ . '/../..' . '/../../Services/Style/Content/classes/class.ilContentStyleSettings.php', 'ilContentStyleSettingsGUI' => __DIR__ . '/../..' . '/../../Services/Style/Content/classes/class.ilContentStyleSettingsGUI.php', 'ilContentStyleWAC' => __DIR__ . '/../..' . '/../../Services/Style/classes/class.ilContentStyleWAC.php', 'ilContentStylesTableGUI' => __DIR__ . '/../..' . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextApacheSSO.php', + 'ilContextBehat' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -3445,6 +3480,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilCurlConnection' => __DIR__ . '/../..' . '/../../Services/WebServices/Curl/classes/class.ilCurlConnection.php', 'ilCurlConnectionException' => __DIR__ . '/../..' . '/../../Services/WebServices/Curl/classes/class.ilCurlConnectionException.php', 'ilCustomBlock' => __DIR__ . '/../..' . '/../../Services/Block/classes/class.ilCustomBlock.php', + 'ilCustomIconObjectConfiguration' => __DIR__ . '/../..' . '/../../Services/Object/Icon/interfaces/interface.ilCustomIconObjectConfiguration.php', 'ilCustomInputGUI' => __DIR__ . '/../..' . '/../../Services/Form/classes/class.ilCustomInputGUI.php', 'ilCustomUserFieldSettingsTableGUI' => __DIR__ . '/../..' . '/../../Services/User/classes/class.ilCustomUserFieldSettingsTableGUI.php', 'ilCustomUserFieldsGUI' => __DIR__ . '/../..' . '/../../Services/User/classes/class.ilCustomUserFieldsGUI.php', @@ -4772,6 +4808,10 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjContentObject' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObject.php', 'ilObjContentObjectAccess' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObjectAccess.php', 'ilObjContentObjectGUI' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObjectGUI.php', + 'ilObjContentPage' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilObjContentPage.php', + 'ilObjContentPageAccess' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilObjContentPageAccess.php', + 'ilObjContentPageGUI' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilObjContentPageGUI.php', + 'ilObjContentPageListGUI' => __DIR__ . '/../..' . '/../../Modules/ContentPage/classes/class.ilObjContentPageListGUI.php', 'ilObjCourse' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourse.php', 'ilObjCourseAccess' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseAccess.php', 'ilObjCourseAdministration' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseAdministration.php', @@ -5159,6 +5199,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjectActivation' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectActivation.php', 'ilObjectActivationGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectActivationGUI.php', 'ilObjectAddNewItemGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectAddNewItemGUI.php', + 'ilObjectAppEventListener' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectAppEventListener.php', 'ilObjectBadgeTableGUI' => __DIR__ . '/../..' . '/../../Services/Badge/classes/class.ilObjectBadgeTableGUI.php', 'ilObjectConsumerTableGUI' => __DIR__ . '/../..' . '/../../Services/LTI/classes/Consumer/class.ilLTIConsumerTableGUI.php', 'ilObjectCopyCourseGroupSelectionTableGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectCopyCourseGroupSelectionTableGUI.php', @@ -5166,6 +5207,12 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjectCopyProgressTableGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectCopyProgressTableGUI.php', 'ilObjectCopySearchResultTableGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectCopySearchResultTableGUI.php', 'ilObjectCopySelectionTableGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectCopySelectionTableGUI.php', + 'ilObjectCustomIcon' => __DIR__ . '/../..' . '/../../Services/Object/Icon/interfaces/interface.ilObjectCustomIcon.php', + 'ilObjectCustomIconConfiguration' => __DIR__ . '/../..' . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconConfiguration.php', + 'ilObjectCustomIconConfigurationGUI' => __DIR__ . '/../..' . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconConfigurationGUI.php', + 'ilObjectCustomIconFactory' => __DIR__ . '/../..' . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconFactory.php', + 'ilObjectCustomIconImpl' => __DIR__ . '/../..' . '/../../Services/Object/Icon/classes/class.ilObjectCustomIconImpl.php', + 'ilObjectCustomIconUploadPostProcessor' => __DIR__ . '/../..' . '/../../Services/Object/Icon/interfaces/interface.ilObjectCustomIconUploadPostProcessor.php', 'ilObjectCustomUserFieldHistory' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldHistory.php', 'ilObjectCustomUserFieldsGUI' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsGUI.php', 'ilObjectCustomUserFieldsTableGUI' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsTableGUI.php', From 24c2baed7e43d255a638814f8a751fedcfb67c54 Mon Sep 17 00:00:00 2001 From: mjansen Date: Tue, 3 Jul 2018 12:56:16 +0200 Subject: [PATCH 039/166] ContentPage: Added another definition key to identify roles by id, not by title (according to JF decision) --- .../classes/class.ilDBUpdateNewObjectType.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php index 26304da6bcdc..0ff4b443c768 100644 --- a/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php +++ b/Services/Migration/DBUpdate_3560/classes/class.ilDBUpdateNewObjectType.php @@ -22,6 +22,7 @@ class ilDBUpdateNewObjectType protected static $initialPermissionDefinition = [ 'role' => [ 'User' => [ + 'id' => 4, 'ignore_for_authoring_objects' => true, 'object' => [ self::RBAC_OP_VISIBLE, @@ -647,8 +648,8 @@ public static function setRolePermission(int $a_rol_id, string $a_type, array $a * This method will apply the 'Initial Permissions Guideline' when introducing new object types. * This method does not apply permissions to existing obejcts in the ILIAS repository ('change existing objects'). * @param string $objectType - * @param bool $hasLearningProgress - * @param bool $usedForAuthoring + * @param bool $hasLearningProgress A boolean flag whether or not the object type supports learning progress + * @param bool $usedForAuthoring A boolean flag to tell whether or not the object type is mainly used for authoring * @see https://www.ilias.de/docu/goto_docu_wiki_wpage_2273_1357.html */ public static function applyInitialPermissionGuideline(string $objectType, bool $hasLearningProgress = false, bool $usedForAuthoring = false) { @@ -686,8 +687,18 @@ public static function applyInitialPermissionGuideline(string $objectType, bool continue; } - $query = "SELECT obj_id FROM object_data WHERE type = %s AND title = %s"; - $res = $ilDB->queryF($query, ['text', 'text'], [$roleType, $roleTitle]); + if (array_key_exists('id', $definition) && is_numeric($definition['id'])) { + // According to JF (2018-07-02), some roles have to be selected by if, not by title + $query = "SELECT obj_id FROM object_data WHERE type = %s AND obj_id = %s"; + $queryTypes = ['text', 'integer']; + $queryValues = [$roleType, $definition['id']]; + } else { + $query = "SELECT obj_id FROM object_data WHERE type = %s AND title = %s"; + $queryTypes = ['text', 'text']; + $queryValues = [$roleType, $roleTitle]; + } + + $res = $ilDB->queryF($query, $queryTypes, $queryValues); if (1 == $ilDB->numRows($res)) { $row = $ilDB->fetchAssoc($res); $roleId = (int)$row['obj_id']; From 79ade0bbf43766138e58d62f1e82cffa47f327e3 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 5 Jul 2018 14:08:45 +0200 Subject: [PATCH 040/166] ContentPage: Fixed copy dialogue --- Modules/ContentPage/classes/class.ilObjContentPageGUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index e92a9aef4583..a8e70fe5504c 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -316,7 +316,7 @@ public function executeCommand() $this->tpl->getStandardTemplate(); $gui = new \ilObjectCopyGUI($this); - $gui->setType($this->object->getType()); + $gui->setType(self::OBJ_TYPE); $this->ctrl->forwardCommand($gui); break; From 5877938f59b9286dfff19a27754b7d3e4b2e11de Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 6 Jul 2018 09:37:07 +0200 Subject: [PATCH 041/166] ContentPage: Page styles --- ...lass.ilContentPagePageCommandForwarder.php | 41 ++-- .../classes/class.ilContentPagePageGUI.php | 14 -- .../classes/class.ilObjContentPage.php | 95 +++++++++ .../classes/class.ilObjContentPageGUI.php | 182 ++++++++++++++++++ ...interface.ilContentPageObjectConstants.php | 1 + setup/sql/dbupdate_04.php | 44 +++++ 6 files changed, 338 insertions(+), 39 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php b/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php index 9fdb66306cfc..db5a0b4ce6e0 100644 --- a/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php +++ b/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php @@ -70,7 +70,7 @@ public function __construct( $this->tabs->clearTargets(); $this->lng->loadLanguageModule('content'); - $this->backUrl = isset($request->getQueryParams()['backurl']) ? $request->getQueryParams()['backurl'] : ''; + $this->backUrl = $request->getQueryParams()['backurl'] ?? ''; if (strlen($this->backUrl) > 0) { $this->ctrl->setParameterByClass('ilcontentpagepagegui', 'backurl', rawurlencode($this->backUrl)); @@ -78,13 +78,15 @@ public function __construct( } /** - * @param string $pageObjectType - * @param int $pageObjectId * @return \ilContentPagePageGUI */ - protected function getPageObjectGUI($pageObjectType, $pageObjectId) + protected function getPageObjectGUI() { - $pageObjectGUI = new \ilContentPagePageGUI($pageObjectId); + $pageObjectGUI = new \ilContentPagePageGUI($this->parentObject->getId()); + $pageObjectGUI->setStyleId( + \ilObjStyleSheet::getEffectiveContentStyleId( + $this->parentObject->getStyleSheetId(), $this->parentObject->getType()) + ); $pageObjectGUI->obj->addUpdateListener($this->parentObject, 'update'); @@ -92,15 +94,14 @@ protected function getPageObjectGUI($pageObjectType, $pageObjectId) } /** - * @param string $pageObjectType - * @param int $pageObjectId + * */ - protected function ensurePageObjectExists($pageObjectType, $pageObjectId) - { - if (!\ilContentPagePage::_exists($pageObjectType, $pageObjectId)) { + protected function ensurePageObjectExists() + { + if (!\ilContentPagePage::_exists($this->parentObject->getType(), $this->parentObject->getId())) { $pageObject = new \ilContentPagePage(); $pageObject->setParentId($this->parentObject->getId()); - $pageObject->setId($pageObjectId); + $pageObject->setId($this->parentObject->getId()); $pageObject->createFromXML(); } } @@ -131,14 +132,9 @@ protected function buildEditingPageObjectGUI() { $this->setBackLinkTab(); - $this->ensurePageObjectExists( - $this->parentObject->getType(), $this->parentObject->getId() - ); - - $pageObjectGUI = $this->getPageObjectGUI( - $this->parentObject->getType(), $this->parentObject->getId() - ); + $this->ensurePageObjectExists(); + $pageObjectGUI = $this->getPageObjectGUI(); $pageObjectGUI->setEnabledTabs(true); return $pageObjectGUI; @@ -151,14 +147,9 @@ protected function buildPresentationPageObjectGUI() { $this->setBackLinkTab(); - $this->ensurePageObjectExists( - $this->parentObject->getType(), $this->parentObject->getId() - ); - - $pageObjectGUI = $this->getPageObjectGUI( - $this->parentObject->getType(), $this->parentObject->getId() - ); + $this->ensurePageObjectExists(); + $pageObjectGUI = $this->getPageObjectGUI(); $pageObjectGUI->setEnabledTabs(false); return $pageObjectGUI; diff --git a/Modules/ContentPage/classes/class.ilContentPagePageGUI.php b/Modules/ContentPage/classes/class.ilContentPagePageGUI.php index a905f2ad08fb..f05b157f9a23 100644 --- a/Modules/ContentPage/classes/class.ilContentPagePageGUI.php +++ b/Modules/ContentPage/classes/class.ilContentPagePageGUI.php @@ -16,22 +16,8 @@ class ilContentPagePageGUI extends \ilPageObjectGUI implements \ilContentPageObj */ public function __construct($a_id = 0, $a_old_nr = 0) { - global $DIC; - - $tpl = $DIC['tpl']; - parent::__construct(self::OBJ_TYPE, $a_id, $a_old_nr); $this->setTemplateTargetVar('ADM_CONTENT'); $this->setTemplateOutput(true); - - require_once 'Services/Style/Content/classes/class.ilObjStyleSheet.php'; - - $tpl->setCurrentBlock("SyntaxStyle"); - $tpl->setVariable("LOCATION_SYNTAX_STYLESHEET", \ilObjStyleSheet::getSyntaxStylePath()); - $tpl->parseCurrentBlock(); - - $tpl->setCurrentBlock("ContentStyle"); - $tpl->setVariable("LOCATION_CONTENT_STYLESHEET", \ilObjStyleSheet::getContentStylePath(0)); - $tpl->parseCurrentBlock(); } } \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php index b6ebc634dd99..c7757909d492 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPage.php +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -6,6 +6,11 @@ */ class ilObjContentPage extends \ilObject2 implements \ilContentPageObjectConstants { + /** + * @var int + */ + protected $styleId = 0; + /** * @inheritdoc */ @@ -14,11 +19,44 @@ protected function initType() $this->type = self::OBJ_TYPE; } + /** + * @return int + */ + public function getStyleSheetId(): int + { + return (int)$this->styleId; + } + + /** + * @param int $styleId + */ + public function setStyleSheetId(int $styleId) + { + $this->styleId = $styleId; + } + + /** + * @param int $styleId + */ + public function writeStyleSheetId(int $styleId) + { + $this->db->manipulateF( + 'UPDATE content_object SET stylesheet = %s WHERE id = %s', + ['integer', 'integer'], + [(int) $styleId, $this->getId()] + ); + + $this->setStyleSheetId($styleId); + } + /** * @inheritdoc */ protected function doCloneObject($new_obj, $a_target_id, $a_copy_id = null) { + /** + * @var $new_obj self + */ parent::doCloneObject($new_obj, $a_target_id, $a_copy_id); if (\ilContentPagePage::_exists($this->getType(), $this->getId())) { @@ -31,14 +69,71 @@ protected function doCloneObject($new_obj, $a_target_id, $a_copy_id = null) $duplicatePageObject->setXMLContent($originalXML); $duplicatePageObject->createFromXML(); } + + $styleId = $this->getStyleSheetId(); + if ($styleId > 0 && !\ilObjStyleSheet::_lookupStandard($styleId)) { + $style = \ilObjectFactory::getInstanceByObjId($styleId, false); + if ($style) { + $new_id = $style->ilClone(); + $new_obj->setStyleSheetId($new_id); + $new_obj->update(); + } + } } + /** + * @inheritdoc + */ + protected function doRead() + { + parent::doRead(); + + $res = $this->db->queryF( + 'SELECT * FROM content_page_data WHERE content_page_id = %s', + ['integer'], + [$this->getId()] + ); + + while($data = $this->db->fetchAssoc($res)) { + $this->setStyleSheetId((int)$data['stylesheet']); + } + } + + /** + * @inheritdoc + */ + protected function doCreate() + { + parent::doCreate(); + + $this->db->manipulateF(' + INSERT INTO content_page_data + ( + content_page_id, + stylesheet + ) + VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', + ['integer', 'integer'], + [$this->getId(), 0] + ); + } + + /** * @inheritdoc */ protected function doUpdate() { parent::doUpdate(); + + $this->db->manipulateF(' + UPDATE content_page_data + SET + stylesheet = %s + WHERE content_page_id = %s', + ['integer', 'integer'], + [$this->getStyleSheetId(), $this->getId()] + ); } /** diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index a8e70fe5504c..71526b026da6 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -13,6 +13,7 @@ * @ilCtrl_Calls ilObjContentPageGUI: ilCommonActionDispatcherGUI * @ilCtrl_Calls ilObjContentPageGUI: ilContentPagePageGUI * @ilCtrl_Calls ilObjContentPageGUI: ilObjectCustomIconConfigurationGUI + * @ilCtrl_Calls ilObjContentPageGUI: ilObjStyleSheetGUI */ class ilObjContentPageGUI extends \ilObject2GUI implements \ilContentPageObjectConstants, \ilDesktopItemHandling { @@ -86,6 +87,8 @@ public function __construct($a_id = 0, $a_id_type = self::REPOSITORY_NODE_ID, $a $this->error = $this->dic['ilErr']; $this->lng->loadLanguageModule('copa'); + $this->lng->loadLanguageModule('style'); + $this->lng->loadLanguageModule('content'); if ($this->object instanceof \ilObjContentPage) { $this->infoScreenEnabled = \ilContainer::_lookupContainerSetting( @@ -211,6 +214,12 @@ protected function setSettingsSubTabs($activeTab) ); } + $this->tabs_gui->addSubTab( + self::UI_TAB_ID_STYLE, + $this->lng->txt('cont_style'), + $this->ctrl->getLinkTarget($this, 'editStyleProperties') + ); + $this->tabs->setSubTabActive($activeTab); } } @@ -236,7 +245,37 @@ public function executeCommand() $this->addToNavigationHistory(); + if (strtolower($nextClass) !== 'ilobjstylesheetgui') { + $this->renderHeaderActions(); + } + switch (strtolower($nextClass)) { + case 'ilobjstylesheetgui': + $this->checkPermission('write'); + + $this->setLocator(); + + $this->ctrl->setReturn($this, 'editStyleProperties'); + $style_gui = new \ilObjStyleSheetGUI( + '', + $this->object->getStyleSheetId(), + false, + false + ); + $style_gui->omitLocator(); + if ($cmd == 'create' || $_GET['new_type'] == 'sty') { + $style_gui->setCreationMode(true); + } + $ret = $this->ctrl->forwardCommand($style_gui); + + if ($cmd == 'save' || $cmd == 'copyStyle' || $cmd == 'importStyle') { + $styleId = $ret; + $this->object->setStyleSheetId($styleId); + $this->object->update(); + $this->ctrl->redirectByClass('ilobjstylesheetgui', 'edit'); + } + break; + case 'ilcontentpagepagegui': if (!$this->checkPermissionBool('write') || $this->user->isAnonymous()) { $this->error->raiseError($this->lng->txt('permission_denied'), $this->error->MESSAGE); @@ -244,6 +283,11 @@ public function executeCommand() $this->prepareOutput(); + $this->tpl->setVariable('LOCATION_CONTENT_STYLESHEET', \ilObjStyleSheet::getContentStylePath($this->object->getStyleSheetId())); + $this->tpl->setCurrentBlock('SyntaxStyle'); + $this->tpl->setVariable('LOCATION_SYNTAX_STYLESHEET', \ilObjStyleSheet::getSyntaxStylePath()); + $this->tpl->parseCurrentBlock(); + $forwarder = new \ilContentPagePageCommandForwarder($this->request, $this->ctrl, $this->tabs, $this->lng, $this->object); $forwarder->forward(); break; @@ -362,6 +406,16 @@ public function addToNavigationHistory() { \ilLink::_getLink($this->object->getRefId(), $this->object->getType()), $this->object->getType() ); + } + } + } + + /** + * + */ + public function renderHeaderActions() { + if(!$this->getCreationMode()) { + if($this->checkPermissionBool('read')) { $this->addHeaderAction(); } } @@ -508,6 +562,14 @@ public function removeFromDeskObject() $this->ctrl->redirect($this, self::UI_CMD_VIEW); } + protected function initStyleSheets() + { + $this->tpl->setVariable('LOCATION_CONTENT_STYLESHEET', \ilObjStyleSheet::getContentStylePath($this->object->getStyleSheetId())); + $this->tpl->setCurrentBlock('SyntaxStyle'); + $this->tpl->setVariable('LOCATION_SYNTAX_STYLESHEET', \ilObjStyleSheet::getSyntaxStylePath()); + $this->tpl->parseCurrentBlock(); + } + /** * @param string $ctrlLink * @return string @@ -515,7 +577,12 @@ public function removeFromDeskObject() public function getContent($ctrlLink = '') { if (\ilContentPagePage::_exists($this->object->getType(), $this->object->getId()) && $this->checkPermissionBool('read')) { + $this->initStyleSheets(); + $pageGui = new \ilContentPagePageGUI($this->object->getId()); + $pageGui->setStyleId( + \ilObjStyleSheet::getEffectiveContentStyleId($this->object->getStyleSheetId(), $this->object->getType()) + ); $pageGui->setEnabledTabs(false); if (is_string($ctrlLink) && strlen($ctrlLink) > 0) { @@ -555,4 +622,119 @@ public function view() $this->tpl->setContent($this->getContent()); } + + /** + * @throws \ilObjectException + */ + protected function editStyleProperties() + { + $this->checkPermission('write'); + + $this->tabs->activateTab(self::UI_TAB_ID_SETTINGS); + $this->setSettingsSubTabs(self::UI_TAB_ID_STYLE); + + $form = $this->buildStylePropertiesForm(); + $this->tpl->setContent($form->getHTML()); + } + + /** + * @return \ilPropertyFormGUI + */ + protected function buildStylePropertiesForm() + { + $form = new \ilPropertyFormGUI(); + + $fixedStyle = $this->settings->get('fixed_content_style_id'); + $defaultStyle = $this->settings->get('default_content_style_id'); + $styleId = $this->object->getStyleSheetId(); + + if ($fixedStyle > 0) { + $st = new ilNonEditableValueGUI($this->lng->txt("cont_current_style")); + $st->setValue( + \ilObject::_lookupTitle($fixedStyle) . " (" . $this->lng->txt("global_fixed") . ")" + ); + $form->addItem($st); + } else { + $st_styles = \ilObjStyleSheet::_getStandardStyles( + true, + false, + (int)$_GET["ref_id"] + ); + + if ($defaultStyle > 0) { + $st_styles[0] = \ilObject::_lookupTitle($defaultStyle) . ' (' . $this->lng->txt('default') . ')'; + } else { + $st_styles[0] = $this->lng->txt('default'); + } + ksort($st_styles); + + if ($styleId > 0) { + if (!\ilObjStyleSheet::_lookupStandard($styleId)) { + $st = new \ilNonEditableValueGUI($this->lng->txt('cont_current_style')); + $st->setValue(ilObject::_lookupTitle($styleId)); + $form->addItem($st); + + $form->addCommandButton('editStyle', $this->lng->txt('cont_edit_style')); + $form->addCommandButton('deleteStyle', $this->lng->txt('cont_delete_style')); + } + } + + if ($styleId <= 0 || \ilObjStyleSheet::_lookupStandard($styleId)) { + $style_sel = new \ilSelectInputGUI($this->lng->txt('cont_current_style'), 'style_id'); + $style_sel->setOptions($st_styles); + $style_sel->setValue($styleId); + $form->addItem($style_sel); + $form->addCommandButton('saveStyleSettings', $this->lng->txt('save')); + $form->addCommandButton('createStyle', $this->lng->txt('sty_create_ind_style')); + } + } + + $form->setTitle($this->lng->txt("cont_style")); + $form->setFormAction($this->ctrl->getFormAction($this)); + + return $form; + } + + /** + * Create Style + */ + protected function createStyle() + { + $this->ctrl->redirectByClass('ilobjstylesheetgui', 'create'); + } + + /** + * Edit Style + */ + protected function editStyle() + { + $this->ctrl->redirectByClass('ilobjstylesheetgui', 'edit'); + } + + /** + * Delete Style + */ + protected function deleteStyle() + { + $this->ctrl->redirectByClass('ilobjstylesheetgui', 'delete'); + } + + /** + * Save style settings + */ + protected function saveStyleSettings() + { + $this->checkPermission('write'); + + if ( + $this->settings->get('fixed_content_style_id') <= 0 && + (\ilObjStyleSheet::_lookupStandard($this->object->getStyleSheetId()) || $this->object->getStyleSheetId() == 0)) + { + $this->object->setStyleSheetId(ilUtil::stripSlashes($_POST['style_id'])); + $this->object->update(); + ilUtil::sendSuccess($this->lng->txt('msg_obj_modified'), true); + } + + $this->ctrl->redirect($this, 'editStyleProperties'); + } } \ No newline at end of file diff --git a/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php b/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php index 4e1f44b3fe32..977d310ba019 100644 --- a/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php +++ b/Modules/ContentPage/interfaces/interface.ilContentPageObjectConstants.php @@ -20,6 +20,7 @@ interface ilContentPageObjectConstants const UI_TAB_ID_INFO = 'info_short'; const UI_TAB_ID_SETTINGS = 'settings'; const UI_TAB_ID_ICON = 'icon'; + const UI_TAB_ID_STYLE = 'style'; const UI_TAB_ID_LP = 'learning_progress'; const UI_TAB_ID_EXPORT = 'export'; const UI_TAB_ID_PERMISSIONS = 'perm_settings'; diff --git a/setup/sql/dbupdate_04.php b/setup/sql/dbupdate_04.php index c571d1899ca6..e94e4064c0d6 100644 --- a/setup/sql/dbupdate_04.php +++ b/setup/sql/dbupdate_04.php @@ -22323,4 +22323,48 @@ function writeCtrlClassEntry(ilPluginSlot $slot, array $plugin_data) { +<#5281> +getStructure(); +?> +<#5282> +tableExists('content_page_data')) { + $fields = array( + 'content_page_id' => array( + 'type' => 'integer', + 'length' => 4, + 'notnull' => true, + 'default' => 0 + ), + 'stylesheet' => array( + 'type' => 'integer', + 'notnull' => true, + 'length' => 4, + 'default' => 0 + ) + ); + + $ilDB->createTable('content_page_data', $fields); + $ilDB->addPrimaryKey('content_page_data', array('content_page_id')); +} +?> +<#5283> +queryF( + 'SELECT * FROM object_data WHERE type = %s', + ['text'], + ['copa'] +); + +while($data = $ilDB->fetchAssoc($res)) { + $ilDB->replace( + 'content_page_data', + [ + 'content_page_id' => ['integer', (int)$data['obj_id']] + ], + [] + ); +} ?> \ No newline at end of file From 9d5c781670d9610bd13e027f0c1e09c8da85ce32 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 6 Jul 2018 09:40:31 +0200 Subject: [PATCH 042/166] ContentPage: Fixed placeholders in database query --- Modules/ContentPage/classes/class.ilObjContentPage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php index c7757909d492..00c7245eb5c2 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPage.php +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -112,7 +112,7 @@ protected function doCreate() content_page_id, stylesheet ) - VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', + VALUES(%s, %s)', ['integer', 'integer'], [$this->getId(), 0] ); From 3d94299df88625db80cbf4494d92cc1dae6d9274 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 6 Jul 2018 10:01:18 +0200 Subject: [PATCH 043/166] ContentPage: Page styles --- .../classes/class.ilContentPageDataSet.php | 4 +++ .../classes/class.ilContentPageExporter.php | 27 ++++++++++++++----- .../classes/class.ilContentPageImporter.php | 13 +++++++++ xml/ilias_copa_5_4.xsd | 2 ++ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilContentPageDataSet.php b/Modules/ContentPage/classes/class.ilContentPageDataSet.php index 5dc8ac886af4..7a7e3152c187 100644 --- a/Modules/ContentPage/classes/class.ilContentPageDataSet.php +++ b/Modules/ContentPage/classes/class.ilContentPageDataSet.php @@ -46,6 +46,7 @@ protected function getTypes($a_entity, $a_version) 'title' => 'text', 'description' => 'text', 'info-tab' => 'integer', + 'style-id' => 'integer', ]; default: @@ -90,6 +91,7 @@ protected function readEntityData($entity, $ids) \ilObjectServiceSettingsGUI::INFO_TAB_VISIBILITY, true ), + 'style-id' => $obj->getStyleSheetId(), ]; } } @@ -119,6 +121,7 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ $newObject->setTitle(\ilUtil::stripSlashes($a_rec['title'])); $newObject->setDescription(\ilUtil::stripSlashes($a_rec['description'])); + $newObject->setStyleSheetId((int)\ilUtil::stripSlashes($a_rec['style-id'])); if (!$newObject->getId()) { $newObject->create(); @@ -131,6 +134,7 @@ public function importRecord($a_entity, $a_types, $a_rec, $a_mapping, $a_schema_ ); $a_mapping->addMapping('Modules/ContentPage', self::OBJ_TYPE, $a_rec['id'], $newObject->getId()); + $a_mapping->addMapping('Modules/ContentPage', 'style', $newObject->getId(), $newObject->getStyleSheetId()); $a_mapping->addMapping( 'Services/COPage', 'pg', diff --git a/Modules/ContentPage/classes/class.ilContentPageExporter.php b/Modules/ContentPage/classes/class.ilContentPageExporter.php index 22de15366dfe..b2ae1531d903 100644 --- a/Modules/ContentPage/classes/class.ilContentPageExporter.php +++ b/Modules/ContentPage/classes/class.ilContentPageExporter.php @@ -52,6 +52,7 @@ public function getValidSchemaVersions($a_entity) */ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_ids) { $pageObjectIds = []; + $styleIds = []; foreach ($a_ids as $copaObjId) { $copa = \ilObjectFactory::getInstanceByObjId($copaObjId, false); @@ -63,18 +64,30 @@ public function getXmlExportTailDependencies($a_entity, $a_target_release, $a_id foreach ($copaPageObjIds as $copaPageObjId) { $pageObjectIds[] = self::OBJ_TYPE . ':' . $copaPageObjId; } + + if ($copa->getStyleSheetId() > 0) { + $styleIds[$copa->getStyleSheetId()] = $copa->getStyleSheetId(); + } } + $deps = []; + if (count($pageObjectIds) > 0) { - return [ - [ - 'component' => 'Services/COPage', - 'entity' => 'pg', - 'ids' => $pageObjectIds, - ] + $deps[] = [ + 'component' => 'Services/COPage', + 'entity' => 'pg', + 'ids' => $pageObjectIds, + ]; + } + + if (count($styleIds) > 0) { + $deps[] = [ + 'component' => 'Services/Style', + 'entity' => 'sty', + 'ids' => array_values($styleIds), ]; } - return []; + return $deps; } } \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilContentPageImporter.php b/Modules/ContentPage/classes/class.ilContentPageImporter.php index ce7114eef573..471dfc70a953 100644 --- a/Modules/ContentPage/classes/class.ilContentPageImporter.php +++ b/Modules/ContentPage/classes/class.ilContentPageImporter.php @@ -42,5 +42,18 @@ public function finalProcessing($a_mapping) \ilContentPagePage::_writeParentId(self::OBJ_TYPE, $newCopaId, $newCopaId); } + + $styleMapping = $a_mapping->getMappingsOfEntity('Modules/ContentPage', 'style'); + foreach ($styleMapping as $newCopaId => $oldStyleId) { + $newStyleId = (int) $a_mapping->getMapping('Services/Style', 'sty', $oldStyleId); + if ($newCopaId > 0 && $newStyleId > 0) { + $copa = \ilObjectFactory::getInstanceByObjId($newCopaId, false); + if (!$copa || !($copa instanceof \ilObjContentPage)) { + continue; + } + $copa->writeStyleSheetId($newStyleId); + $copa->update(); + } + } } } \ No newline at end of file diff --git a/xml/ilias_copa_5_4.xsd b/xml/ilias_copa_5_4.xsd index f767798e2bca..092507af0f26 100644 --- a/xml/ilias_copa_5_4.xsd +++ b/xml/ilias_copa_5_4.xsd @@ -15,6 +15,7 @@ + @@ -23,5 +24,6 @@ + \ No newline at end of file From 28ba460e3e4284084c3d0875d3e746c7c753683a Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 12 Jul 2018 11:52:24 +0200 Subject: [PATCH 044/166] ContentPage: Added 'copa' as possible precondition trigger --- .../classes/class.ilObjContentPageAccess.php | 18 +++++++++++++++++- .../classes/class.ilConditionHandler.php | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilObjContentPageAccess.php b/Modules/ContentPage/classes/class.ilObjContentPageAccess.php index 25a4da8bd272..be88b5641138 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageAccess.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageAccess.php @@ -4,7 +4,7 @@ /** * Class ilObjContentPageAccess */ -class ilObjContentPageAccess extends \ilObjectAccess implements \ilContentPageObjectConstants +class ilObjContentPageAccess extends \ilObjectAccess implements \ilContentPageObjectConstants, \ilConditionHandling { /** * @inheritdoc @@ -40,4 +40,20 @@ public static function _checkGoto($a_target) { return parent::_checkGoto($a_target); } + + /** + * @inheritdoc + */ + public static function getConditionOperators() + { + return []; + } + + /** + * @inheritdoc + */ + public static function checkCondition($a_trigger_obj_id, $a_operator, $a_value, $a_usr_id) + { + return false; + } } \ No newline at end of file diff --git a/Services/AccessControl/classes/class.ilConditionHandler.php b/Services/AccessControl/classes/class.ilConditionHandler.php index 34831e28f46d..0daeae07d7c4 100755 --- a/Services/AccessControl/classes/class.ilConditionHandler.php +++ b/Services/AccessControl/classes/class.ilConditionHandler.php @@ -448,7 +448,7 @@ function getTriggerTypes() $objDefinition = $DIC['objDefinition']; - $trigger_types = array('crs','exc','tst','sahs', 'svy', 'lm', 'iass', 'prg'); + $trigger_types = array('crs','exc','tst','sahs', 'svy', 'lm', 'iass', 'prg', 'copa'); foreach($objDefinition->getPlugins() as $p_type => $p_info) { From 9a485a8ed901da53f9adc7b1e1e6a8c82214c895 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 14:16:22 +0200 Subject: [PATCH 045/166] not possible to use isDependencyAvailable --- Services/Database/classes/class.ilDBUpdate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Database/classes/class.ilDBUpdate.php b/Services/Database/classes/class.ilDBUpdate.php index 558c014b8bfc..5876e7808642 100755 --- a/Services/Database/classes/class.ilDBUpdate.php +++ b/Services/Database/classes/class.ilDBUpdate.php @@ -292,7 +292,7 @@ private function initGlobalsRequiredForUpdateSteps(&$ilCtrlStructureReader, &$il if (isset($GLOBALS['ilCtrlStructureReader'])) { $ilCtrlStructureReader = $GLOBALS['ilCtrlStructureReader']; - } elseif ($DIC->isDependencyAvailable('ilCtrlStructureReader')) { + } elseif ($DIC->offsetExists('ilCtrlStructureReader')) { $ilCtrlStructureReader = $DIC['ilCtrlStructureReader']; } else { require_once 'setup/classes/class.ilCtrlStructureReader.php'; @@ -302,7 +302,7 @@ private function initGlobalsRequiredForUpdateSteps(&$ilCtrlStructureReader, &$il $GLOBALS['ilCtrlStructureReader'] = $ilCtrlStructureReader; - if ($DIC->isDependencyAvailable('ilMySQLAbstraction')) { + if ($DIC->offsetExists('ilMySQLAbstraction')) { $ilMySQLAbstraction = $DIC['ilMySQLAbstraction']; } else { $ilMySQLAbstraction = new ilMySQLAbstraction(); From cae727e1010010908c1f3bd3b8bb8fb7ee5ef9f6 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 14:57:35 +0200 Subject: [PATCH 046/166] WIP --- Modules/File/classes/class.ilObjFile.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/File/classes/class.ilObjFile.php b/Modules/File/classes/class.ilObjFile.php index 07b4e1a01a3a..554ff8cdc338 100755 --- a/Modules/File/classes/class.ilObjFile.php +++ b/Modules/File/classes/class.ilObjFile.php @@ -705,7 +705,8 @@ public function sendFile($a_hist_entry_id = null) { $ilFileDelivery = new ilFileDelivery($file); $ilFileDelivery->setDisposition($this->isInline() ? ilFileDelivery::DISP_INLINE : ilFileDelivery::DISP_ATTACHMENT); $ilFileDelivery->setMimeType($this->guessFileType($file)); - $ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); + $ilFileDelivery->setConvertFileNameToAsci((bool)!$ilClientIniFile->readVariable('file_access', 'disable_ascii')); + $ilFileDelivery->setConvertFileNameToAsci(false); // also returning the 'real' filename if a history file is delivered if ($ilClientIniFile->readVariable('file_access', 'download_with_uploaded_filename') From 16fbc8ed07b68d8644c162abe209dc130bb7c82f Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 15:13:05 +0200 Subject: [PATCH 047/166] moved ilFileUtil::deliverFile to ilFiledelivery --- Services/Utilities/classes/class.ilUtil.php | 63 ++++----------------- 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/Services/Utilities/classes/class.ilUtil.php b/Services/Utilities/classes/class.ilUtil.php index 048ae2c93e74..4a9ee89f84f4 100755 --- a/Services/Utilities/classes/class.ilUtil.php +++ b/Services/Utilities/classes/class.ilUtil.php @@ -2197,71 +2197,30 @@ public static function deliverData($a_data, $a_filename, $mime = "application/oc public static function deliverFile($a_file, $a_filename,$a_mime = '', $isInline = false, $removeAfterDelivery = false, $a_exit_after = true) { + global $DIC; // should we fail silently? if(!file_exists($a_file)) { return false; - } + } + $delivery = new ilFileDelivery($a_file); if ($isInline) { - $disposition = "inline"; // "inline" to view file in browser + $delivery->setDisposition(ilFileDelivery::DISP_INLINE); } else { - $disposition = "attachment"; // "attachment" to download to hard disk - //$a_mime = "application/octet-stream"; // override mime type to ensure that no browser tries to show the file anyway. + $delivery->setDisposition(ilFileDelivery::DISP_ATTACHMENT); } - // END WebDAV: Show file in browser or provide it as attachment if(strlen($a_mime)) { - $mime = $a_mime; - } - else - { - $mime = "application/octet-stream"; // or whatever the mime type is - } - // BEGIN WebDAV: Removed broken HTTPS code. - // END WebDAV: Removed broken HTTPS code. - if ($disposition == "attachment") - { - header("Cache-control: private"); - } - else - { - header("Cache-Control: no-cache, must-revalidate"); - header("Pragma: no-cache"); - } - - $ascii_filename = ilUtil::getASCIIFilename($a_filename); - - header("Content-Type: $mime"); - header("Content-Disposition:$disposition; filename=\"".$ascii_filename."\""); - header("Content-Description: ".$ascii_filename); - - // #7271: if notice gets thrown download will fail in IE - $filesize = @filesize($a_file); - if ($filesize) - { - header("Content-Length: ".(string)$filesize); - } - - include_once './Services/Http/classes/class.ilHTTPS.php'; - #if($_SERVER['HTTPS']) - if(ilHTTPS::getInstance()->isDetected()) - { - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Pragma: public'); + $delivery->setMimeType($a_mime); } - header("Connection: close"); - ilUtil::readFile( $a_file ); - if ($removeAfterDelivery) - { - unlink ($a_file); - } - if ($a_exit_after) - { - exit; - } + $delivery->setDownloadFileName($a_filename); + $delivery->setConvertFileNameToAsci((bool)!$DIC->clientIni()->readVariable('file_access', 'disable_ascii')); + $delivery->setConvertFileNameToAsci(false); + $delivery->setDeleteFile($removeAfterDelivery); + $delivery->deliver(); } From 124162741c3449f68ff9d3fda380082218b8ff01 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 15:13:23 +0200 Subject: [PATCH 048/166] correct handling of filename-conversion --- Services/FileDelivery/classes/Delivery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/FileDelivery/classes/Delivery.php b/Services/FileDelivery/classes/Delivery.php index 3e1fa5c38275..e371497bea9f 100644 --- a/Services/FileDelivery/classes/Delivery.php +++ b/Services/FileDelivery/classes/Delivery.php @@ -163,7 +163,7 @@ public function setGeneralHeaders() { $response = $this->httpService->response()->withHeader(ResponseHeader::CONTENT_TYPE, $this->getMimeType()); $this->httpService->saveResponse($response); } - if (!$this->isConvertFileNameToAsci()) { + if ($this->isConvertFileNameToAsci()) { $this->cleanDownloadFileName(); } if ($this->hasHashFilename()) { From 797acc3fb90a05fb099ccdf04e6f14529b654df0 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 15:17:42 +0200 Subject: [PATCH 049/166] removed WIP code --- Modules/File/classes/class.ilObjFile.php | 1 - Services/Utilities/classes/class.ilUtil.php | 1 - 2 files changed, 2 deletions(-) diff --git a/Modules/File/classes/class.ilObjFile.php b/Modules/File/classes/class.ilObjFile.php index 554ff8cdc338..471ba2961a28 100755 --- a/Modules/File/classes/class.ilObjFile.php +++ b/Modules/File/classes/class.ilObjFile.php @@ -706,7 +706,6 @@ public function sendFile($a_hist_entry_id = null) { $ilFileDelivery->setDisposition($this->isInline() ? ilFileDelivery::DISP_INLINE : ilFileDelivery::DISP_ATTACHMENT); $ilFileDelivery->setMimeType($this->guessFileType($file)); $ilFileDelivery->setConvertFileNameToAsci((bool)!$ilClientIniFile->readVariable('file_access', 'disable_ascii')); - $ilFileDelivery->setConvertFileNameToAsci(false); // also returning the 'real' filename if a history file is delivered if ($ilClientIniFile->readVariable('file_access', 'download_with_uploaded_filename') diff --git a/Services/Utilities/classes/class.ilUtil.php b/Services/Utilities/classes/class.ilUtil.php index 4a9ee89f84f4..c589d9cd4823 100755 --- a/Services/Utilities/classes/class.ilUtil.php +++ b/Services/Utilities/classes/class.ilUtil.php @@ -2218,7 +2218,6 @@ public static function deliverFile($a_file, $a_filename,$a_mime = '', $isInline $delivery->setDownloadFileName($a_filename); $delivery->setConvertFileNameToAsci((bool)!$DIC->clientIni()->readVariable('file_access', 'disable_ascii')); - $delivery->setConvertFileNameToAsci(false); $delivery->setDeleteFile($removeAfterDelivery); $delivery->deliver(); } From e9fd20f962691a76715bef6f5522e149d0827502 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Thu, 12 Jul 2018 15:47:39 +0200 Subject: [PATCH 050/166] [FIX] 23244: wrong varibable call --- Services/Component/classes/class.ilCachedComponentData.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Component/classes/class.ilCachedComponentData.php b/Services/Component/classes/class.ilCachedComponentData.php index 5405813baa3d..598df7f2dfd1 100644 --- a/Services/Component/classes/class.ilCachedComponentData.php +++ b/Services/Component/classes/class.ilCachedComponentData.php @@ -516,8 +516,8 @@ public function __destruct() { public function lookupGroupedRepObj($parent) { if (is_array($parent)) { $index = md5(serialize($parent)); - if (isset($cached_results['grpd_repo'][$index])) { - return $cached_results['grpd_repo'][$index]; + if (isset($this->cached_results['grpd_repo'][$index])) { + return $this->cached_results['grpd_repo'][$index]; } $return = array(); From 385a69462bace017b78c42c5c68eea9a0f77e92b Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 13 Jul 2018 08:32:41 +0200 Subject: [PATCH 051/166] Chatroom: Fixed tabs --- Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php b/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php index dc63bb035bd9..3a01d2bfc9fa 100644 --- a/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php +++ b/Modules/Chatroom/classes/gui/class.ilChatroomHistoryGUI.php @@ -10,9 +10,6 @@ */ class ilChatroomHistoryGUI extends ilChatroomGUIHandler { - /** @var ilTabsGUI */ - private $tabs; - /** * {@inheritdoc} */ From 7b9c2511b3f48ad53f9c7a999573ac272050f3f4 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Fri, 13 Jul 2018 11:07:31 +0200 Subject: [PATCH 052/166] 0021286: Session with Waiting list: "With Auto-Fill" - Get mail when automatically get a membership --- .../Session/classes/class.ilObjSession.php | 9 +++++++ .../classes/class.ilSessionParticipants.php | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Modules/Session/classes/class.ilObjSession.php b/Modules/Session/classes/class.ilObjSession.php index 254fa0dd960d..f4ac220badf1 100644 --- a/Modules/Session/classes/class.ilObjSession.php +++ b/Modules/Session/classes/class.ilObjSession.php @@ -828,17 +828,26 @@ public function handleAutoFill() if(in_array($user_id, $parts->getParticipants())) { $this->session_logger->notice('User on waiting list already session member: ' . $user_id); + continue; } if($this->enabledRegistration()) { $this->session_logger->debug('Registration enabled: register user'); $parts->register($user_id); + $parts->sendNotification( + ilSessionMembershipMailNotification::TYPE_ACCEPTED_SUBSCRIPTION_MEMBER, + $user_id + ); } else { $this->session_logger->debug('Registration disabled: set user status to participated.'); $parts->getEventParticipants()->updateParticipation($user_id, true); + $parts->sendNotification( + ilSessionMembershipMailNotification::TYPE_ACCEPTED_SUBSCRIPTION_MEMBER, + $user_id + ); } $session_waiting_list->removeFromList($user_id); diff --git a/Modules/Session/classes/class.ilSessionParticipants.php b/Modules/Session/classes/class.ilSessionParticipants.php index 74c6139d85e1..527e4d0c79c8 100644 --- a/Modules/Session/classes/class.ilSessionParticipants.php +++ b/Modules/Session/classes/class.ilSessionParticipants.php @@ -157,5 +157,31 @@ public function unregister($a_usr_id) return false; } + + /** + * @param int $a_type + * @param int $a_usr_id + * @param bool $a_force_email + */ + public function sendNotification($a_type, $a_usr_id, $a_force_email = false) + { + $mail = new ilSessionMembershipMailNotification(); + + switch($a_type) + { + case ilSessionMembershipMailNotification::TYPE_ACCEPTED_SUBSCRIPTION_MEMBER: + $mail->setType(ilSessionMembershipMailNotification::TYPE_ACCEPTED_SUBSCRIPTION_MEMBER); + $mail->setRefId($this->ref_id); + $mail->setRecipients([$a_usr_id]); + $mail->send(); + break; + + default: + $this->logger->warning('Invalid notfication type given: ' . $a_type); + $this->logger->logStack(ilLogLevel::WARNING); + break; + } + } + } ?> \ No newline at end of file From b6548908830bb18df7d0294f66088c2dbf1e3fde Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Fri, 13 Jul 2018 12:00:23 +0200 Subject: [PATCH 053/166] Fixed 0022169: Kurs mit LZO-Ansicht - Zuordnung von Online-Tests als Lernmaterial --- .../Objectives/class.ilLOEditorGUI.php | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Modules/Course/classes/Objectives/class.ilLOEditorGUI.php b/Modules/Course/classes/Objectives/class.ilLOEditorGUI.php index cf8d34b575dd..cfea884868b8 100644 --- a/Modules/Course/classes/Objectives/class.ilLOEditorGUI.php +++ b/Modules/Course/classes/Objectives/class.ilLOEditorGUI.php @@ -28,6 +28,12 @@ class ilLOEditorGUI const SETTINGS_TEMPLATE_QT = 'il_astpl_loc_qualified'; + /** + * @var \ilLogger + */ + private $logger = null; + + private $parent_obj; private $settings = NULL; private $lng = NULL; @@ -44,8 +50,10 @@ public function __construct($a_parent_obj) { $this->parent_obj = $a_parent_obj; $this->settings = ilLOSettings::getInstanceByObjId($this->getParentObject()->getId()); + $this->lng = $GLOBALS['DIC']['lng']; $this->ctrl = $GLOBALS['DIC']['ilCtrl']; + $this->logger = $GLOBALS['DIC']->logger()->crs(); } /** @@ -968,7 +976,12 @@ protected function saveMultiTestAssignment() $this->applySettingsTemplate($tst); $tst->saveToDb(); } - + + // deassign as objective material + if($tst instanceof ilObjTest) + { + $this->updateMaterialAssignments($tst); + } $this->updateStartObjects(); ilUtil::sendSuccess($this->lng->txt('settings_saved')); @@ -981,6 +994,26 @@ protected function saveMultiTestAssignment() $this->testAssignment($form); } + /** + * @param \ilObjTest $test + */ + protected function updateMaterialAssignments(ilObjTest $test) + { + include_once './Modules/Course/classes/class.ilCourseObjective.php'; + foreach(ilCourseObjective::_getObjectiveIds($this->getParentObject()->getId()) as $objective_id) + { + include_once './Modules/Course/classes/class.ilCourseObjectiveMaterials.php'; + $materials = new ilCourseObjectiveMaterials($objective_id); + foreach($materials->getMaterials() as $key => $material) + { + if($material['ref_id'] == $test->getRefId()) + { + $materials->delete($material['lm_ass_id']); + } + } + } + } + /** * Save Test */ @@ -1043,7 +1076,12 @@ protected function saveTest() $this->applySettingsTemplate($tst); $tst->saveToDb(); } - + + // deassign as objective material + if($tst instanceof ilObjTest) + { + $this->updateMaterialAssignments($tst); + } $this->updateStartObjects(); ilUtil::sendSuccess($this->lng->txt('settings_saved')); From b333dfba9c184115bbe653cf69587697afa7e591 Mon Sep 17 00:00:00 2001 From: Alexander Killing Date: Fri, 13 Jul 2018 13:35:51 +0200 Subject: [PATCH 054/166] 23308, Fixes: Course Icons are blocked for a regular user --- .../classes/class.ilContainerAccess.php | 2 +- .../dist/bootstrap-tagsinput.css | 20 +++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/Services/Container/classes/class.ilContainerAccess.php b/Services/Container/classes/class.ilContainerAccess.php index 9ee8803f4843..931e80f38f38 100644 --- a/Services/Container/classes/class.ilContainerAccess.php +++ b/Services/Container/classes/class.ilContainerAccess.php @@ -25,7 +25,7 @@ public function canBeDelivered(ilWACPath $ilWACPath) { preg_match("/\\/obj_([\\d]*)\\//uism", $ilWACPath->getPath(), $results); foreach (ilObject2::_getAllReferences($results[1]) as $ref_id) { - if ($access->checkAccess('read', '', $ref_id)) { + if ($access->checkAccess('visible', '', $ref_id)) { return true; } } diff --git a/libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.css b/libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.css index 7fced3009d10..55f7c09df02e 100644 --- a/libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.css +++ b/libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.css @@ -1,14 +1,10 @@ -/* - * bootstrap-tagsinput v0.8.0 - * - */ - .bootstrap-tagsinput { background-color: #fff; border: 1px solid #ccc; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); display: inline-block; padding: 4px 6px; + margin-bottom: 10px; color: #555; vertical-align: middle; border-radius: 4px; @@ -21,21 +17,11 @@ box-shadow: none; outline: none; background-color: transparent; - padding: 0 6px; + padding: 0; margin: 0; - width: auto; + width: auto !important; max-width: inherit; } -.bootstrap-tagsinput.form-control input::-moz-placeholder { - color: #777; - opacity: 1; -} -.bootstrap-tagsinput.form-control input:-ms-input-placeholder { - color: #777; -} -.bootstrap-tagsinput.form-control input::-webkit-input-placeholder { - color: #777; -} .bootstrap-tagsinput input:focus { border: none; box-shadow: none; From 9b72de55e2ce5caf9a43302da21984ceb2ab0562 Mon Sep 17 00:00:00 2001 From: Denis Witt Date: Fri, 13 Jul 2018 14:22:30 +0200 Subject: [PATCH 055/166] Moved phantomjs to optional dependencies phantomjs development has been suspended, so it should not be installed by default to avoid security issues. --- docs/configuration/install.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/configuration/install.md b/docs/configuration/install.md index 376c97802277..783be162f249 100755 --- a/docs/configuration/install.md +++ b/docs/configuration/install.md @@ -28,6 +28,7 @@ ILIAS is a powerful Open Source Learning Management System for developing and re 1. [MySQL Perfomance tuning \(OPTIONAL\)](#mysql-perfomance-tuning-optional) 1. [E-Mail Configuration \(OPTIONAL\)](#e-mail-configuration-optional) 1. [Install other Depedencies](#install-other-depedencies) + 1. [Optional Dependencies](#optional-dependencies) 1. [Installation Wizard](#installation-wizard) 1. [Configure ILIAS Java RPC server \(OPTIONAL\)](#configure-ilias-java-rpc-server-optional) 1. [Hardening and Security Guidance](#hardening-and-security-guidance) @@ -388,22 +389,22 @@ FromLineOverride=YES On Debian/Ubuntu 14.04 execute: ``` -apt-get install zip unzip php5-gd php5-mysql php-xsl imagemagick openjdk-7-jdk phantomjs +apt-get install zip unzip php5-gd php5-mysql php-xsl imagemagick openjdk-7-jdk ``` On Ubuntu 16.04 execute: ``` -apt-get install zip unzip php7.0-gd php7.0-mysql php7.0-xsl php7.0-zip imagemagick openjdk-8-jdk phantomjs +apt-get install zip unzip php7.0-gd php7.0-mysql php7.0-xsl php7.0-zip imagemagick openjdk-8-jdk ``` On RHEL/CentOS execute: ``` -yum install zip unzip php-gd libxslt ImageMagick java-1.7.0-openjdk phantomjs +yum install zip unzip php-gd libxslt ImageMagick java-1.7.0-openjdk ``` - -Please ensure that the phantomjs version you use is at least 2.0.0. +### Optional Dependencies Depending on your use case, you MAY want to install further dependencies (exact package names vary by distribution): + * php-curl * php-xmlrpc * php-soap @@ -411,6 +412,9 @@ Depending on your use case, you MAY want to install further dependencies (exact * php-mbstring * ffmpeg * mimetex +* phantomjs + +Please ensure that the phantomjs version you use is at least 2.0.0. Please note that phantomjs development has been suspended until further notice. See https://github.com/ariya/phantomjs/issues/15344 for details. ## Installation Wizard From 052fa19c5f6dd66116bd74127cc3dcc4023a29ed Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Mon, 16 Jul 2018 10:00:46 +0200 Subject: [PATCH 056/166] Added accidentally deleted lang var and improved label --- lang/ilias_en.lang | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 439b9cd68e32..ecfd23adb583 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -10051,8 +10051,9 @@ rtst#:#rtst_call#:#Call ECS Test rbac#:#contribute#:#Contribute dcl#:#dcl_change_notification_subject#:#Data Collection "%s" has been changed dcl#:#dcl_change_notification_link#:#Link to the Data Collection -dcl#:#dcl_change_notification_dcl_new_record#:#The following Entry has been created -dcl#:#dcl_change_notification_dcl_update_record#:#The following Entry has been updated +dcl#:#dcl_change_notification_dcl_new_record#:#The following data record has been created +dcl#:#dcl_change_notification_dcl_update_record#:#The following data record has been updated +dcl#:#dcl_change_notification_dcl_delete_record#:#The following data record has been deleted dcl#:#dcl_change_why_you_receive_this_email#:#You receive this message because you have activated the notification about this data collection. dcl#:#dcl_table#:#Table dcl#:#dcl_changed_by#:#Changed by From 91455e28248fb9e8e2d4fcb6578904c69f4c479c Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Mon, 16 Jul 2018 10:04:13 +0200 Subject: [PATCH 057/166] Re-introduced missing lang var (due to wrong 'deprecated' setting) --- lang/ilias_de.lang | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 15a954f9eec2..da8e7dd188c8 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -9902,6 +9902,7 @@ dcl#:#dcl_change_notification_subject#:#Die Datensatzsammlung "%s" wurde bearbei dcl#:#dcl_change_notification_link#:#Zur Datensammlung gehen dcl#:#dcl_change_notification_dcl_new_record#:#Der folgende Eintrag wurde erzeugt dcl#:#dcl_change_notification_dcl_update_record#:#Der folgende Eintrag wurde aktualisiert +dcl#:#dcl_change_notification_dcl_delete_record#:#Der folgende Eintrag wurde gelöscht dcl#:#dcl_change_why_you_receive_this_email#:#Sie erhalten diese Nachricht weil Sie die Benachrichtigungsfunktion für die oben genannte Datensatzsammlung aktiviert haben. dcl#:#dcl_table#:#Tabelle dcl#:#dcl_changed_by#:#Letzte Änderung durch From f30e4b6ceb01defa6bd7f5d68e234d9ef1fb3758 Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Mon, 16 Jul 2018 10:13:57 +0200 Subject: [PATCH 058/166] Removed duplicate entry for #ecs_member_auth_type# --- lang/ilias_en.lang | 1 - 1 file changed, 1 deletion(-) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index ecfd23adb583..1d8b1ee88409 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -13854,7 +13854,6 @@ mep#:#mobs_black_list_file_types_info#:#Enter a comma separated list of file suf form#:#form_msg_file_type_is_not_allowed#:#This file type is not allowed. common#:#file_no_valid_file_type#:#This file type is not allowed. common#:#file_some_invalid_file_types_removed#:#Some file types are not allowed and have been removed. -ecs#:#ecs_member_auth_type#:#Course Member Auth Mode background_tasks#:#background_tasks#:#Background Tasks background_tasks#:#background_tasks_running#:#Background Tasks background_tasks#:#scheduled#:#Scheduled From d9d2cd60eabc65204cc0d3712355108d50ece8a4 Mon Sep 17 00:00:00 2001 From: Timon Amstutz Date: Tue, 17 Jul 2018 16:47:58 +0200 Subject: [PATCH 059/166] Doc: Updated coordinator readme according JF --- docs/documentation/maintenance-coordinator.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/documentation/maintenance-coordinator.md b/docs/documentation/maintenance-coordinator.md index 53196bed8efd..ac0872db4e53 100644 --- a/docs/documentation/maintenance-coordinator.md +++ b/docs/documentation/maintenance-coordinator.md @@ -26,14 +26,22 @@ The motivation of the coordinator is mostly driven by the need of a reliable component for a certain aspect. Further the coordinator is probably the most attractive contractor for clients aiming to change aspects of the component due to the very indepth know how and the -listing as coordinator. +listing as coordinator. + +The PM and the TB appoint or replace coordinators. The coordinator role belongs to the person, +not the company, since the role builds on social capital in the community and a +vision of the component it will be near impossible to leave that role at a company. ## Change Management Everybody may contribute to any aspect of the component. Such contributions -are handed in by pull requests or some other source of data if declared so in the components guidelines. -Pull requests on the public interface must be accepted by the JF. The coordinator gives a -recommendation to the JF on whether to accept or decline the PR. The decision of the JF may +are handed in by pull requests or some other source of data if declared so in the components guidelines. +Note that the general [contribution guideline](https://github.com/ILIAS-eLearning/ILIAS/blob/release_5-3/docs/documentation/contributing.md) also apply for components managed by the coordinator +model. The coordinator is called upon to define additional criteria and processes +for the component (e.g. UI-components). This is the gain of this model: PRs cannot be rejected arbitrarily. +This allows other developers to build an expectation about the chances of their PR. Pull requests +on the public interface must be accepted by the JF. The coordinator gives a recommendation to the +JF on whether to accept or decline the PR. The decision of the JF may be implicit if no objections to the recommendation of the coordinator is made. If no agreement is achieved in the JF, the Technical Board will decide upon the request. @@ -85,6 +93,10 @@ tests in their own development. * The coordinator SHOULD devise some guidelines concerning the processes around the respective component fitting it's exact needs as done so for the UI-Service. Such guidelines MUST be accepted by the JF. +* The coordinator MUST follow the rules given for the components, especially +for contributing code. The coordinator has no special rights in this regard. +E.g. if a pull request is needed in certain scenarios, the coordinator +would need to create as well. **Please note:** The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" From 0fd8e2eca1667c65b061de070ce467ee73006ef5 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Wed, 18 Jul 2018 09:04:26 +0200 Subject: [PATCH 060/166] [FIX] 0023096: Bug with cyrillic filename Part 2 --- .../classes/FileHandler/class.ilCalendarFileHandler.php | 2 +- Services/FileDelivery/classes/class.ilPHPOutputDelivery.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php b/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php index 71205cf9a620..4c61e6b12a6f 100644 --- a/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php +++ b/Services/Calendar/classes/FileHandler/class.ilCalendarFileHandler.php @@ -87,7 +87,7 @@ function downloadFilesForEvents($a_events) $ilFileDelivery = new ilFileDelivery($last_file); $ilFileDelivery->setDisposition(ilFileDelivery::DISP_ATTACHMENT); //$ilFileDelivery->setMimeType($this->guessFileType($file)); - $ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); + $ilFileDelivery->setConvertFileNameToAsci((bool)!$ilClientIniFile->readVariable('file_access', 'disable_ascii')); //$ilFileDelivery->setDownloadFileName(); $ilFileDelivery->deliver(); exit; diff --git a/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php b/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php index c862990f4ea8..df6eaca827b9 100644 --- a/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php +++ b/Services/FileDelivery/classes/class.ilPHPOutputDelivery.php @@ -34,7 +34,7 @@ public function start($download_file_name, $mime_type = ilMimeTypeUtil::APPLICAT $this->ilFileDelivery->setMimeType($mime_type); $this->ilFileDelivery->setDownloadFileName($download_file_name); $this->ilFileDelivery->setDisposition(ilFileDelivery::DISP_ATTACHMENT); - $this->ilFileDelivery->setConvertFileNameToAsci($ilClientIniFile->readVariable('file_access', 'disable_ascii')); + $this->ilFileDelivery->setConvertFileNameToAsci((bool)!$ilClientIniFile->readVariable('file_access', 'disable_ascii')); $this->ilFileDelivery->clearBuffer(); $this->ilFileDelivery->checkCache(); $this->ilFileDelivery->setGeneralHeaders(); From 9a5cbdfc6c7911fa1eb8eb9afb3ff1d650f7e38b Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 18 Jul 2018 09:15:16 +0200 Subject: [PATCH 061/166] Content Page: Implemented new learning progress mode / Streamline 'Content Visited' mode used for 'file' and 'copa' objects --- .../classes/class.ilContentPageLP.php | 3 +- Modules/File/classes/class.ilFileLP.php | 4 +- .../classes/class.ilLPObjSettings.php | 6 +- .../status/class.ilLPStatusContentVisited.php | 54 +++++++++++++++ .../status/class.ilLPStatusDownloaded.php | 69 ------------------- lang/ilias_de.lang | 4 +- lang/ilias_en.lang | 4 +- libs/composer/vendor/composer/ClassLoader.php | 4 +- .../vendor/composer/autoload_classmap.php | 4 +- .../vendor/composer/autoload_static.php | 4 +- 10 files changed, 73 insertions(+), 83 deletions(-) create mode 100644 Services/Tracking/classes/status/class.ilLPStatusContentVisited.php delete mode 100644 Services/Tracking/classes/status/class.ilLPStatusDownloaded.php diff --git a/Modules/ContentPage/classes/class.ilContentPageLP.php b/Modules/ContentPage/classes/class.ilContentPageLP.php index d9698ce74b7a..7b5015ca6858 100644 --- a/Modules/ContentPage/classes/class.ilContentPageLP.php +++ b/Modules/ContentPage/classes/class.ilContentPageLP.php @@ -21,7 +21,8 @@ public function getValidModes() { return [ ilLPObjSettings::LP_MODE_DEACTIVATED, - ilLPObjSettings::LP_MODE_MANUAL + ilLPObjSettings::LP_MODE_MANUAL, + ilLPObjSettings::LP_MODE_CONTENT_VISITED, ]; } } \ No newline at end of file diff --git a/Modules/File/classes/class.ilFileLP.php b/Modules/File/classes/class.ilFileLP.php index 49f701f014f1..a2366025534b 100644 --- a/Modules/File/classes/class.ilFileLP.php +++ b/Modules/File/classes/class.ilFileLP.php @@ -14,7 +14,7 @@ public static function getDefaultModes($a_lp_active) { return array( ilLPObjSettings::LP_MODE_DEACTIVATED, - ilLPObjSettings::LP_MODE_DOWNLOADED + ilLPObjSettings::LP_MODE_CONTENT_VISITED ); } @@ -27,7 +27,7 @@ public function getValidModes() { return array( ilLPObjSettings::LP_MODE_DEACTIVATED, - ilLPObjSettings::LP_MODE_DOWNLOADED + ilLPObjSettings::LP_MODE_CONTENT_VISITED ); } } \ No newline at end of file diff --git a/Services/Tracking/classes/class.ilLPObjSettings.php b/Services/Tracking/classes/class.ilLPObjSettings.php index a031e0645cbc..69bce75eea82 100755 --- a/Services/Tracking/classes/class.ilLPObjSettings.php +++ b/Services/Tracking/classes/class.ilLPObjSettings.php @@ -42,7 +42,7 @@ class ilLPObjSettings const LP_MODE_QUESTIONS = 17; const LP_MODE_SURVEY_FINISHED = 18; const LP_MODE_VISITED_PAGES = 19; - const LP_MODE_DOWNLOADED = 20; + const LP_MODE_CONTENT_VISITED = 20; const LP_MODE_COLLECTION_MOBS = 21; const LP_MODE_STUDY_PROGRAMME = 22; const LP_MODE_INDIVIDUAL_ASSESSMENT = 23; @@ -110,8 +110,8 @@ class ilLPObjSettings ,self::LP_MODE_VISITED_PAGES => array('ilLPStatusVisitedPages', 'trac_mode_visited_pages', 'trac_mode_visited_pages_info') - ,self::LP_MODE_DOWNLOADED => array('ilLPStatusDownloaded', - 'trac_mode_downloaded', 'trac_mode_downloaded_info') + ,self::LP_MODE_CONTENT_VISITED => array('ilLPStatusContentVisited', + 'trac_mode_content_visited', 'trac_mode_content_visited_info') ,self::LP_MODE_COLLECTION_MOBS => array('ilLPStatusCollectionMobs', 'trac_mode_collection_mobs', 'trac_mode_collection_mobs_info') diff --git a/Services/Tracking/classes/status/class.ilLPStatusContentVisited.php b/Services/Tracking/classes/status/class.ilLPStatusContentVisited.php new file mode 100644 index 000000000000..5b96ddb2fce1 --- /dev/null +++ b/Services/Tracking/classes/status/class.ilLPStatusContentVisited.php @@ -0,0 +1,54 @@ + + * @package ServicesTracking + */ +class ilLPStatusContentVisited extends ilLPStatus +{ + /** + * @inheritdoc + */ + public static function _getCompleted($a_obj_id) + { + $userIds = []; + + $allReadEvents = \ilChangeEvent::_lookupReadEvents($a_obj_id); + foreach ($allReadEvents as $event) { + $userIds[] = $event['usr_id']; + } + + return $userIds; + } + + /** + * @inheritdoc + */ + public function determineStatus($a_obj_id, $a_user_id, $a_obj = null) + { + /** + * @var $ilObjDataCache ilObjectDataCache + */ + global $DIC; + + $ilObjDataCache = $DIC['ilObjDataCache']; + + $status = self::LP_STATUS_NOT_ATTEMPTED_NUM; + + switch ($ilObjDataCache->lookupType($a_obj_id)) { + case 'file': + case 'copa': + if (\ilChangeEvent::hasAccessed($a_obj_id, $a_user_id)) { + $status = self::LP_STATUS_COMPLETED_NUM; + } + break; + } + + return $status; + } +} \ No newline at end of file diff --git a/Services/Tracking/classes/status/class.ilLPStatusDownloaded.php b/Services/Tracking/classes/status/class.ilLPStatusDownloaded.php deleted file mode 100644 index a7919684d2f2..000000000000 --- a/Services/Tracking/classes/status/class.ilLPStatusDownloaded.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @package ServicesTracking - */ -class ilLPStatusDownloaded extends ilLPStatus -{ - /** - * @param int $a_obj_id - */ - public function __construct($a_obj_id) - { - global $DIC; - - $ilDB = $DIC['ilDB']; - - parent::__construct($a_obj_id); - $this->db = $ilDB; - } - - static function _getCompleted($a_obj_id) - { - $usr_ids = array(); - require_once 'Services/Tracking/classes/class.ilChangeEvent.php'; - $all_read_events = ilChangeEvent::_lookupReadEvents($a_obj_id); - foreach($all_read_events as $event) - { - $usr_ids[] = $event['usr_id']; - } - return $usr_ids; - } - - /** - * Determine status - * - * @param integer object id - * @param integer user id - * @param object object (optional depends on object type) - * @return integer status - */ - function determineStatus($a_obj_id, $a_user_id, $a_obj = null) - { - /** - * @var $ilObjDataCache ilObjectDataCache - */ - global $DIC; - - $ilObjDataCache = $DIC['ilObjDataCache']; - - $status = self::LP_STATUS_NOT_ATTEMPTED_NUM; - switch($ilObjDataCache->lookupType($a_obj_id)) - { - case 'file': - include_once './Services/Tracking/classes/class.ilChangeEvent.php'; - if(ilChangeEvent::hasAccessed($a_obj_id, $a_user_id)) - { - $status = self::LP_STATUS_COMPLETED_NUM; - } - break; - } - return $status; - } -} \ No newline at end of file diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 3c287b921430..f6c60bb01c3d 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -12691,8 +12691,8 @@ note#:#note_comment_notification_salutation#:#Hallo %s, note#:#note_comment_notification_link#:#Link lng#:#lng_enable_language_detection#:#Aktiviere automatische Spracherkennung lng#:#lng_disable_language_detection#:#Deaktiviere automatische Spracherkennung -trac#:#trac_mode_downloaded#:#Heruntergeladen -trac#:#trac_mode_downloaded_info#:#Der Lernstand wird über das Herunterladen der Datei bestimmt. +trac#:#trac_mode_content_visited#:#Besuch +trac#:#trac_mode_content_visited_info#:#Der Lernstand wird über das Ausliefern des Inhalts bestimmt. trac#:#trac_mode_collection_mobs#:#Auswahl an Medien-Objekten trac#:#trac_mode_collection_mobs_info#:#Der Lernfortschritt wird automatisch aus der Anzahl der betrachteten Medienobjekte ermittelt. trac#:#trac_lp_determination_info_mob#:#Auswahl der Medienobjekte, anhand derer der Gesamtstatus des Mediacast ermittelt wird. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 8773cf83c9ba..16e0c3f08326 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -12681,8 +12681,8 @@ note#:#note_comment_notification_salutation#:#Hello %s, note#:#note_comment_notification_link#:#Link lng#:#lng_enable_language_detection#:#Enable Language Detection lng#:#lng_disable_language_detection#:#Disable Language Detection -trac#:#trac_mode_downloaded#:#Downloaded -trac#:#trac_mode_downloaded_info#:#The learning progress is determined by the download status of the file. +trac#:#trac_mode_content_visited#:#Visited +trac#:#trac_mode_content_visited_info#:#Learning progress status is set to 'completed' when object has been presented to user. trac#:#trac_mode_collection_mobs#:#Collection of Media Objects trac#:#trac_mode_collection_mobs_info#:#The learning progress status will be determined by the viewing status of selected media objects. trac#:#trac_lp_determination_info_mob#:#Select the items that determine the overall learning progress status of the Mediacast diff --git a/libs/composer/vendor/composer/ClassLoader.php b/libs/composer/vendor/composer/ClassLoader.php index 2c72175e7723..dc02dfb114fb 100644 --- a/libs/composer/vendor/composer/ClassLoader.php +++ b/libs/composer/vendor/composer/ClassLoader.php @@ -379,9 +379,9 @@ private function findFileWithExtension($class, $ext) $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { - $length = $this->prefixLengthsPsr4[$first][$search]; - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + if (file_exists($file = $dir . $pathEnd)) { return $file; } } diff --git a/libs/composer/vendor/composer/autoload_classmap.php b/libs/composer/vendor/composer/autoload_classmap.php index b166152a8a12..a20a523733b0 100644 --- a/libs/composer/vendor/composer/autoload_classmap.php +++ b/libs/composer/vendor/composer/autoload_classmap.php @@ -592,6 +592,7 @@ 'ILIAS\\UI\\Component\\Input\\Field\\Numeric' => $baseDir . '/../../src/UI/Component/Input/Field/Numeric.php', 'ILIAS\\UI\\Component\\Input\\Field\\Password' => $baseDir . '/../../src/UI/Component/Input/Field/Password.php', 'ILIAS\\UI\\Component\\Input\\Field\\Section' => $baseDir . '/../../src/UI/Component/Input/Field/Section.php', + 'ILIAS\\UI\\Component\\Input\\Field\\Select' => $baseDir . '/../../src/UI/Component/Input/Field/Select.php', 'ILIAS\\UI\\Component\\Input\\Field\\Tag' => $baseDir . '/../../src/UI/Component/Input/Field/Tag.php', 'ILIAS\\UI\\Component\\Input\\Field\\Text' => $baseDir . '/../../src/UI/Component/Input/Field/Text.php', 'ILIAS\\UI\\Component\\Item\\Factory' => $baseDir . '/../../src/UI/Component/Item/Factory.php', @@ -717,6 +718,7 @@ 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Password' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Password.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Renderer' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Section' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Section.php', + 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Select' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Select.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Tag' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Tag.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Text' => $baseDir . '/../../src/UI/Implementation/Component/Input/Field/Text.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\NameSource' => $baseDir . '/../../src/UI/Implementation/Component/Input/NameSource.php', @@ -4085,7 +4087,7 @@ 'ilLPStatusCollectionManual' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionManual.php', 'ilLPStatusCollectionMobs' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionMobs.php', 'ilLPStatusCollectionTLT' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionTLT.php', - 'ilLPStatusDownloaded' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusDownloaded.php', + 'ilLPStatusContentVisited' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusContentVisited.php', 'ilLPStatusEvent' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusEvent.php', 'ilLPStatusExerciseReturned' => $baseDir . '/../../Services/Tracking/classes/status/class.ilLPStatusExerciseReturned.php', 'ilLPStatusFactory' => $baseDir . '/../../Services/Tracking/classes/class.ilLPStatusFactory.php', diff --git a/libs/composer/vendor/composer/autoload_static.php b/libs/composer/vendor/composer/autoload_static.php index 98f64f124fce..bf874fd27731 100644 --- a/libs/composer/vendor/composer/autoload_static.php +++ b/libs/composer/vendor/composer/autoload_static.php @@ -784,6 +784,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Component\\Input\\Field\\Numeric' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Numeric.php', 'ILIAS\\UI\\Component\\Input\\Field\\Password' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Password.php', 'ILIAS\\UI\\Component\\Input\\Field\\Section' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Section.php', + 'ILIAS\\UI\\Component\\Input\\Field\\Select' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Select.php', 'ILIAS\\UI\\Component\\Input\\Field\\Tag' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Tag.php', 'ILIAS\\UI\\Component\\Input\\Field\\Text' => __DIR__ . '/../..' . '/../../src/UI/Component/Input/Field/Text.php', 'ILIAS\\UI\\Component\\Item\\Factory' => __DIR__ . '/../..' . '/../../src/UI/Component/Item/Factory.php', @@ -909,6 +910,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Password' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Password.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Renderer' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Renderer.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Section' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Section.php', + 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Select' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Select.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Tag' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Tag.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\Field\\Text' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/Field/Text.php', 'ILIAS\\UI\\Implementation\\Component\\Input\\NameSource' => __DIR__ . '/../..' . '/../../src/UI/Implementation/Component/Input/NameSource.php', @@ -4277,7 +4279,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilLPStatusCollectionManual' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionManual.php', 'ilLPStatusCollectionMobs' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionMobs.php', 'ilLPStatusCollectionTLT' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusCollectionTLT.php', - 'ilLPStatusDownloaded' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusDownloaded.php', + 'ilLPStatusContentVisited' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusContentVisited.php', 'ilLPStatusEvent' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusEvent.php', 'ilLPStatusExerciseReturned' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/status/class.ilLPStatusExerciseReturned.php', 'ilLPStatusFactory' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/class.ilLPStatusFactory.php', From 56646b2cd621724c9a2ba003c5a6a6a084260d31 Mon Sep 17 00:00:00 2001 From: xus Date: Wed, 18 Jul 2018 10:37:58 +0200 Subject: [PATCH 062/166] [Fix 22653]Booking pool not visible using external links if it's offline. --- .../BookingManager/classes/class.ilObjBookingPoolAccess.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/BookingManager/classes/class.ilObjBookingPoolAccess.php b/Modules/BookingManager/classes/class.ilObjBookingPoolAccess.php index a89fe2c2a100..477fc46b80f3 100644 --- a/Modules/BookingManager/classes/class.ilObjBookingPoolAccess.php +++ b/Modules/BookingManager/classes/class.ilObjBookingPoolAccess.php @@ -101,7 +101,9 @@ function _checkAccess($a_cmd, $a_permission, $a_ref_id, $a_obj_id, $a_user_id = // for all RBAC checks use checkAccessOfUser instead the normal checkAccess-method: // $rbacsystem->checkAccessOfUser($a_user_id, $a_permission, $a_ref_id) - if($a_permission == "visible" && !$rbacsystem->checkAccessOfUser($a_user_id,'write',$a_ref_id)) + //TODO refactor this: first check if the object is online and then the permissions. + #22653 + if(($a_permission == "visible" || $a_permission == "read") && !$rbacsystem->checkAccessOfUser($a_user_id,'write',$a_ref_id)) { include_once "Modules/BookingManager/classes/class.ilObjBookingPool.php"; $pool = new ilObjBookingPool($a_ref_id); From 1e791c490769aae73e1125b669edf578db873841 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 18 Jul 2018 12:33:19 +0200 Subject: [PATCH 063/166] User: Fixed 'ilAuth' access (not defined) on DIC --- Services/User/classes/class.ilObjUser.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Services/User/classes/class.ilObjUser.php b/Services/User/classes/class.ilObjUser.php index 721bb72dbc4f..bdfc5a63afc5 100755 --- a/Services/User/classes/class.ilObjUser.php +++ b/Services/User/classes/class.ilObjUser.php @@ -2598,10 +2598,6 @@ function checkUserId() */ private static function getLoginFromAuth() { - global $DIC; - - $ilAuth = $DIC['ilAuth']; - $uid = $GLOBALS['DIC']['ilAuthSession']->getUserId(); $login = ilObjUser::_lookupLogin($uid); From f0b4396cd83b63262af0709d42e10758f6355823 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 18 Jul 2018 15:02:07 +0200 Subject: [PATCH 064/166] Content Page: Changed learning progress mode translations --- lang/ilias_de.lang | 4 ++-- lang/ilias_en.lang | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 770e1f6c2f60..e5f876a0cfc6 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -9137,10 +9137,10 @@ trac#:#trac_mode_deactivated_info_new#:#Der Lernfortschrittsstatus wird nicht an trac#:#trac_mode_event#:#Tutoren erfassen Teilnahme trac#:#trac_mode_exercise_returned#:#Tutor entscheidet über den Status trac#:#trac_mode_exercise_returned_info#:#Tutoren entscheiden anhand der Einreichungen, ob Teilnehmer die Übung gelöst haben. Tutoren setzen händisch den Status auf 'Bearbeitet' oder 'Nicht bestanden'. -trac#:#trac_mode_manual#:#Benutzer entscheidet selbst über den Status des Lernfortschritts für das gesamte Lernmodul. +trac#:#trac_mode_manual#:#Benutzer entscheidet selbst über den Status des Lernfortschritts für das gesamte Objekt. trac#:#trac_mode_manual_by_tutor#:#Tutor entscheidet über den Status des Lernfortschritts trac#:#trac_mode_manual_by_tutor_info#:#Tutoren entscheiden, ob Teilnehmer das Objekt erfolgreich bearbeitet haben. Tutoren setzen händisch den Status auf 'Bearbeitet' oder 'Nicht bestanden'. -trac#:#trac_mode_manual_info#:#Benutzer entscheiden selbst, wann sie das Lernmodul erfolgreich durchgearbeitet haben. Sowie die Bearbeitung abgeschlossen ist, setzen sie den Status im Reiter 'Info' auf 'Bearbeitet'. +trac#:#trac_mode_manual_info#:#Benutzer entscheiden selbst, wann sie das Objekt erfolgreich durchgearbeitet haben. Sowie die Bearbeitung abgeschlossen ist, setzen sie den Status im Reiter 'Info' auf 'Bearbeitet'. trac#:#trac_mode_objectives#:#Lernziele trac#:#trac_mode_objectives_info#:#Der Lernfortschritt wird automatisch aus der Anzahl der erfüllten Lernziele ermittelt. trac#:#trac_mode_scorm#:#Status wird anhand einer Auswahl von SCORM-Kapiteln bestimmt (je SCO) diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 94b03919f04e..c06b847cf08b 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -7441,7 +7441,7 @@ trac#:#trac_mode_exercise_returned_info#:#Tutors decide if users have accomplish trac#:#trac_mode_exercise_returned#:#Tutors Monitor and Set Status trac#:#trac_mode_manual_by_tutor_info#:#Tutors decide if users have accomplished the object. Tutors manually set the overall status i.e. ‘Completed’ or ‘Failed’. trac#:#trac_mode_manual_by_tutor#:#Tutors Monitor and Set Status -trac#:#trac_mode_manual_info#:#Users decide themselves if they have accomplished the learning module. Once they are done, they have to set their status to ‘Completed’ on the 'Info'-tab. +trac#:#trac_mode_manual_info#:#Users decide themselves if they have accomplished the object. Once they are done, they have to set their status to ‘Completed’ on the 'Info'-tab. trac#:#trac_mode_manual#:#Users Monitor and Set Status Themselves (Complete Module) trac#:#trac_mode_objectives_info#:#The learning progress will be automatically determined according to the number of passed learning objectives. trac#:#trac_mode_objectives#:#Status is Determined by Learning Objectives From cc7b1d54f874b8e69def60a8fbd40a65b1316f76 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 18 Jul 2018 17:24:57 +0200 Subject: [PATCH 065/166] Content Page: Possible fix for learning progress determination --- Modules/ContentPage/classes/class.ilObjContentPageGUI.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index 71526b026da6..ef6b2f1d9703 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -600,6 +600,11 @@ public function getContent($ctrlLink = '') $this->user->getId() ); + \ilLPStatusWrapper::_refreshStatus( + $this->object->getId(), + [$this->user->getId()] + ); + return $html; } From 16ba35374f3f7aebd77c65d7ee37ce8b86aa6059 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 19 Jul 2018 09:08:27 +0200 Subject: [PATCH 066/166] Chatroom: Removed obsolete library --- .../classes/gui/class.ilChatroomViewGUI.php | 1 - Modules/Chatroom/js/json2.js | 480 ------------------ 2 files changed, 481 deletions(-) delete mode 100644 Modules/Chatroom/js/json2.js diff --git a/Modules/Chatroom/classes/gui/class.ilChatroomViewGUI.php b/Modules/Chatroom/classes/gui/class.ilChatroomViewGUI.php index 0398d31e1b79..af0ba5267376 100644 --- a/Modules/Chatroom/classes/gui/class.ilChatroomViewGUI.php +++ b/Modules/Chatroom/classes/gui/class.ilChatroomViewGUI.php @@ -64,7 +64,6 @@ private function setupTemplate() $this->mainTpl->addJavaScript('Modules/Chatroom/js/chat.js'); $this->mainTpl->addJavaScript('Modules/Chatroom/js/iliaschat.jquery.js'); $this->mainTpl->addJavaScript('libs/bower/bower_components/jquery-outside-events/jquery.ba-outside-events.min.js'); - $this->mainTpl->addJavaScript('Modules/Chatroom/js/json2.js'); $this->mainTpl->addJavaScript('./Services/UIComponent/AdvancedSelectionList/js/AdvancedSelectionList.js'); diff --git a/Modules/Chatroom/js/json2.js b/Modules/Chatroom/js/json2.js deleted file mode 100644 index 36d3dc393950..000000000000 --- a/Modules/Chatroom/js/json2.js +++ /dev/null @@ -1,480 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2011-01-18 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, strict: false, regexp: false */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -var JSON; -if (!JSON) { - JSON = {}; -} - -(function () { - "use strict"; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) ? - this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); From 98b1b6089ab6d8c93424df58f0c6435c2533b53c Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 19 Jul 2018 11:23:22 +0200 Subject: [PATCH 067/166] Chat Server: Incremented package version (cherry picked from commit 52d9b9f) --- Modules/Chatroom/chat/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Chatroom/chat/package.json b/Modules/Chatroom/chat/package.json index 0064a8ab6c95..2210ce821980 100644 --- a/Modules/Chatroom/chat/package.json +++ b/Modules/Chatroom/chat/package.json @@ -1,7 +1,7 @@ { "name": "ILIAS-Chat", "description": "ILIAS CHAT", - "version": "1.0.1", + "version": "1.1.0", "dependencies": { "express": "3.4.x", "mysql": "^2.7.0", From da58236ff755acc0ec0bbe1032090de444e1480b Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 20 Jul 2018 16:49:41 +0200 Subject: [PATCH 068/166] Content Page: Refactorings --- ...lass.ilContentPagePageCommandForwarder.php | 37 +++++++++++------- .../classes/class.ilObjContentPage.php | 20 +++++++++- .../classes/class.ilObjContentPageGUI.php | 38 +++++-------------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php b/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php index db5a0b4ce6e0..9636271e21c0 100644 --- a/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php +++ b/Modules/ContentPage/classes/class.ilContentPagePageCommandForwarder.php @@ -67,7 +67,6 @@ public function __construct( $this->lng = $lng; $this->parentObject = $parentObject; - $this->tabs->clearTargets(); $this->lng->loadLanguageModule('content'); $this->backUrl = $request->getQueryParams()['backurl'] ?? ''; @@ -80,7 +79,7 @@ public function __construct( /** * @return \ilContentPagePageGUI */ - protected function getPageObjectGUI() + protected function getPageObjectGUI(): \ilContentPagePageGUI { $pageObjectGUI = new \ilContentPagePageGUI($this->parentObject->getId()); $pageObjectGUI->setStyleId( @@ -128,8 +127,10 @@ protected function setBackLinkTab() /** * @return \ilContentPagePageGUI */ - protected function buildEditingPageObjectGUI() + protected function buildEditingPageObjectGUI(): \ilContentPagePageGUI { + $this->tabs->clearTargets(); + $this->setBackLinkTab(); $this->ensurePageObjectExists(); @@ -143,44 +144,54 @@ protected function buildEditingPageObjectGUI() /** * @return \ilContentPagePageGUI */ - protected function buildPresentationPageObjectGUI() + protected function buildPresentationPageObjectGUI(): \ilContentPagePageGUI { - $this->setBackLinkTab(); - $this->ensurePageObjectExists(); $pageObjectGUI = $this->getPageObjectGUI(); $pageObjectGUI->setEnabledTabs(false); + $pageObjectGUI->setStyleId( + \ilObjStyleSheet::getEffectiveContentStyleId( + $this->parentObject->getStyleSheetId(), $this->parentObject->getType() + ) + ); + return $pageObjectGUI; } /** * @param string $presentationMode */ - public function setPresentationMode($presentationMode) + public function setPresentationMode(string $presentationMode) { $this->presentationMode = $presentationMode; } /** + * @param string $ctrlLink * @return string - * @throws \ilException + * @throws ilCtrlException + * @throws ilException */ - public function forward() + public function forward(string $ctrlLink = ''): string { switch ($this->presentationMode) { case self::PRESENTATION_MODE_EDITING: $pageObjectGui = $this->buildEditingPageObjectGUI(); - return $this->ctrl->forwardCommand($pageObjectGui); + return (string)$this->ctrl->forwardCommand($pageObjectGui); case self::PRESENTATION_MODE_PRESENTATION: - $pageObjectGUI = $this->buildPresentationPageObjectGUI(); - $this->ctrl->setCmd('getHTML'); - return $this->ctrl->forwardCommand($pageObjectGUI); + if (is_string($ctrlLink) && strlen($ctrlLink) > 0) { + $pageObjectGUI->setFileDownloadLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_FILE); + $pageObjectGUI->setFullscreenLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DISPLAY_FULLSCREEN); + $pageObjectGUI->setSourcecodeDownloadScript($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_PARAGRAPH); + } + + return $this->ctrl->getHTML($pageObjectGUI); default: throw new \ilException('Unknown presentation mode given'); diff --git a/Modules/ContentPage/classes/class.ilObjContentPage.php b/Modules/ContentPage/classes/class.ilObjContentPage.php index 00c7245eb5c2..377f32f211f2 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPage.php +++ b/Modules/ContentPage/classes/class.ilObjContentPage.php @@ -152,7 +152,7 @@ protected function doDelete() /** * @return int[] */ - public function getPageObjIds() + public function getPageObjIds(): array { $pageObjIds = []; @@ -169,4 +169,22 @@ public function getPageObjIds() return $pageObjIds; } + + /** + * @param int $usrId + */ + public function trackProgress(int $usrId) + { + \ilChangeEvent::_recordReadEvent( + $this->getType(), + $this->getRefId(), + $this->getId(), + $usrId + ); + + \ilLPStatusWrapper::_refreshStatus( + $this->getId(), + [$usrId] + ); + } } \ No newline at end of file diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index ef6b2f1d9703..8c0a0679679a 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -571,41 +571,23 @@ protected function initStyleSheets() } /** - * @param string $ctrlLink + * @param string $ctrlLink A link which describes the target controller for all page object links/actions * @return string + * @throws \ilException */ - public function getContent($ctrlLink = '') + public function getContent(string $ctrlLink = ''): string { - if (\ilContentPagePage::_exists($this->object->getType(), $this->object->getId()) && $this->checkPermissionBool('read')) { + if ($this->checkPermissionBool('read')) { + $this->object->trackProgress((int)$this->user->getId()); + $this->initStyleSheets(); - $pageGui = new \ilContentPagePageGUI($this->object->getId()); - $pageGui->setStyleId( - \ilObjStyleSheet::getEffectiveContentStyleId($this->object->getStyleSheetId(), $this->object->getType()) - ); - $pageGui->setEnabledTabs(false); - - if (is_string($ctrlLink) && strlen($ctrlLink) > 0) { - $pageGui->setFileDownloadLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_FILE); - $pageGui->setFullscreenLink($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DISPLAY_FULLSCREEN); - $pageGui->setSourcecodeDownloadScript($ctrlLink . '&cmd=' . self::UI_CMD_COPAGE_DOWNLOAD_PARAGRAPH); - } - - $html = $pageGui->getHTML(); - - \ilChangeEvent::_recordReadEvent( - $this->object->getType(), - $this->object->getRefId(), - $this->object->getId(), - $this->user->getId() - ); - - \ilLPStatusWrapper::_refreshStatus( - $this->object->getId(), - [$this->user->getId()] + $forwarder = new \ilContentPagePageCommandForwarder( + $this->request, $this->ctrl, $this->tabs, $this->lng, $this->object ); + $forwarder->setPresentationMode(ilContentPagePageCommandForwarder::PRESENTATION_MODE_PRESENTATION); - return $html; + return $forwarder->forward($ctrlLink); } return ''; From 9cf773d632bf9b70eac6653de0e096dbf236678b Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 20 Jul 2018 16:50:41 +0200 Subject: [PATCH 069/166] Content Page: Refactorings --- Modules/ContentPage/classes/class.ilObjContentPageGUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index 8c0a0679679a..509f9ec65a36 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -627,7 +627,7 @@ protected function editStyleProperties() /** * @return \ilPropertyFormGUI */ - protected function buildStylePropertiesForm() + protected function buildStylePropertiesForm(): \ilPropertyFormGUI { $form = new \ilPropertyFormGUI(); From 73969ea4f29b53cd521195a8f00b688587518161 Mon Sep 17 00:00:00 2001 From: mjansen Date: Fri, 20 Jul 2018 16:57:09 +0200 Subject: [PATCH 070/166] Content Page: Got rid of exit(); to prevent architectural violations --- .../ContentPage/classes/class.ilObjContentPageGUI.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php index 509f9ec65a36..ca750cda1e42 100644 --- a/Modules/ContentPage/classes/class.ilObjContentPageGUI.php +++ b/Modules/ContentPage/classes/class.ilObjContentPageGUI.php @@ -523,12 +523,10 @@ public static function _goto($target) $DIC->language()->txt('msg_no_perm_read_item'), \ilObject::_lookupTitle(\ilObject::_lookupObjId($refId)) ), true); - $_GET['target'] = ''; - $_GET['ref_id'] = ROOT_FOLDER_ID; - $_GET['baseClass'] = 'ilRepositoryGUI'; - - include 'ilias.php'; - exit(); + $DIC->ctrl()->setTargetScript('ilias.php'); + $DIC->ctrl()->initBaseClass('ilRepositoryGUI'); + $DIC->ctrl()->setParameterByClass('ilRepositoryGUI', 'ref_id', ROOT_FOLDER_ID); + $DIC->ctrl()->redirectByClass('ilRepositoryGUI'); } $DIC['ilErr']->raiseError($DIC->language()->txt('msg_no_perm_read'), $DIC['ilErr']->FATAL); From fefbe309c8776875bd3b1c9fadda240bdccfd754 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Mon, 23 Jul 2018 10:42:34 +0200 Subject: [PATCH 071/166] 0023251: -rbac_log_operation_rmv- not translated --- lang/ilias_de.lang | 2 ++ lang/ilias_en.lang | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index e5f876a0cfc6..dc0d577b126e 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -7703,9 +7703,11 @@ rbac#:#rbac_log_edit_permissions#:#Rechteinstellungen verändern rbac#:#rbac_log_edit_template#:#Rollenvorlagen editieren rbac#:#rbac_log_edit_template_existing#:#Rollenvorlagen auf Objekte anwenden rbac#:#rbac_log_inheritance_add#:#Vererbung unterbrochen für "%s" +rbac#:#rbac_log_inheritance_rmv#:#Vererbung wiederherstellen für "%s" rbac#:#rbac_log_link_object#:#Objekt verknüpfen rbac#:#rbac_log_move_object#:#Objekt verschieben rbac#:#rbac_log_operation_add#:#Recht hinzugefügt für "%s" +rbac#:#rbac_log_operation_rmv#:#Recht entfernt für "%s" rbac#:#rbac_log_source_object#:#Quell-Objekt rbac#:#rbac_msg_user_already_assigned#:#Die ausgewählten Benutzer sind dieser Rolle bereits zugeordnet. rbac#:#rbac_not_change_existing_objects#:#Vorhandene Objekte NICHT anpassen diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index c06b847cf08b..33fc99ad0376 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -7793,7 +7793,9 @@ rbac#:#log#:#Log rbac#:#rbac_log#:#Log rbac#:#rbac_changes#:#Changes rbac#:#rbac_log_operation_add#:#Added Operation for "%s" +rbac#:#rbac_log_operation_rmv#:#Removed Operation for "%s" rbac#:#rbac_log_inheritance_add#:#Stopped Inheritance for "%s" +rbac#:#rbac_log_inheritance_rmv#:#Allowed Inheritance for "%s" rbac#:#rbac_log_source_object#:#Source object rbac#:#rbac_log_edit_permissions#:#Edit Permissions rbac#:#rbac_log_move_object#:#Move Object From 5f3f191943edbd94803c77f6479205fd63187348 Mon Sep 17 00:00:00 2001 From: mjansen Date: Mon, 23 Jul 2018 13:23:10 +0200 Subject: [PATCH 072/166] Mail: Adds type hint --- Services/Mail/classes/class.ilMimeMail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Mail/classes/class.ilMimeMail.php b/Services/Mail/classes/class.ilMimeMail.php index c7f53c1cffd1..a0e1c0303b5b 100755 --- a/Services/Mail/classes/class.ilMimeMail.php +++ b/Services/Mail/classes/class.ilMimeMail.php @@ -426,7 +426,7 @@ protected function gatherImagesFromDirectory($directory, $clearPrevious = false) * @param $transport \ilMailMimeTransport|null * @return bool A boolean flag whether or not the transport might be successful */ - public function Send($transport = null) + public function Send(\ilMailMimeTransport $transport = null) { if(!($transport instanceof \ilMailMimeTransport)) { From fa4c34d1c11990a243cbcce28b784ca49767e59e Mon Sep 17 00:00:00 2001 From: Stephan Winiker Date: Mon, 23 Jul 2018 15:42:24 +0200 Subject: [PATCH 073/166] Changed Maintainer for WebDAV from smeyer to rheer --- docs/documentation/maintenance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/maintenance.md b/docs/documentation/maintenance.md index 5c3dda97021d..dd3112cc21dd 100644 --- a/docs/documentation/maintenance.md +++ b/docs/documentation/maintenance.md @@ -535,7 +535,7 @@ The code base is deviced in several components: * **WebDAV** * 1st Maintainer: [fawinike](http://www.ilias.de/docu/goto_docu_usr_44474.html) - * 2nd Maintainer: [smeyer](http://www.ilias.de/docu/goto_docu_usr_191.html) + * 2nd Maintainer: [rheer](http://www.ilias.de/docu/goto_docu_usr_47872.html) * Testcases: [TESTERS MISSING](http://www.ilias.de/docu/goto_docu_pg_64423_4793.html) * Tester: [TESTERS MISSING](http://www.ilias.de/docu/goto_docu_pg_64423_4793.html) From 797867e25c5e3b74d42519da9f8c780703bfcc7c Mon Sep 17 00:00:00 2001 From: Raphael Heer Date: Wed, 18 Jul 2018 10:00:40 +0200 Subject: [PATCH 074/166] WebDAV: First implementation of new WebDAV --- .../classes/class.ilObjCategoryAccess.php | 4 +- .../classes/class.ilObjCategoryListGUI.php | 8 +- .../classes/class.ilObjCourseAccess.php | 4 +- .../classes/class.ilObjFolderAccess.php | 4 +- .../classes/class.ilObjFolderListGUI.php | 8 +- .../Group/classes/class.ilObjGroupAccess.php | 4 +- .../Group/classes/class.ilObjGroupListGUI.php | 8 +- .../classes/class.ilContainerGUI.php | 48 +- .../Context/classes/class.ilContextWebdav.php | 4 +- .../classes/class.ilInfoScreenGUI.php | 14 +- .../Object/classes/class.ilObjectListGUI.php | 25 +- Services/UICore/classes/class.ilTemplate.php | 8 +- Services/WebDAV/README.md | 149 + Services/WebDAV/classes/Server.php | 2029 ------------ .../WebDAV/classes/Tools/_parse_lockinfo.php | 237 -- .../WebDAV/classes/Tools/_parse_propfind.php | 178 - .../WebDAV/classes/Tools/_parse_proppatch.php | 214 -- .../auth/class.ilWebDAVAuthentication.php | 56 + Services/WebDAV/classes/class.ilDAVLocks.php | 612 ---- .../WebDAV/classes/class.ilDAVProperties.php | 242 -- Services/WebDAV/classes/class.ilDAVServer.php | 2198 ------------- Services/WebDAV/classes/class.ilDAVUtils.php | 65 - .../WebDAV/classes/class.ilObjCategoryDAV.php | 135 - .../WebDAV/classes/class.ilObjCourseDAV.php | 99 - .../classes/class.ilObjFileAccessSettings.php | 53 +- .../class.ilObjFileAccessSettingsGUI.php | 108 +- .../WebDAV/classes/class.ilObjFileDAV.php | 218 -- .../WebDAV/classes/class.ilObjFolderDAV.php | 78 - .../WebDAV/classes/class.ilObjGroupDAV.php | 90 - .../classes/class.ilObjMountPointDAV.php | 184 -- Services/WebDAV/classes/class.ilObjNull.php | 137 - .../WebDAV/classes/class.ilObjNullDAV.php | 157 - .../WebDAV/classes/class.ilObjRootDAV.php | 145 - Services/WebDAV/classes/class.ilObjectDAV.php | 899 ----- .../class.ilWebDAVMountInstructions.php | 222 ++ .../class.ilWebDAVMountInstructionsGUI.php | 74 + .../classes/class.ilWebDAVRequestHandler.php | 76 + .../WebDAV/classes/class.ilWebDAVTree.php | 127 + .../WebDAV/classes/class.ilWebDAVUtil.php | 644 ++++ .../classes/dav/class.ilClientNodeDAV.php | 169 + .../classes/dav/class.ilMountPointDAV.php | 86 + .../classes/dav/class.ilObjCategoryDAV.php | 16 + .../classes/dav/class.ilObjContainerDAV.php | 241 ++ .../classes/dav/class.ilObjCourseDAV.php | 16 + .../WebDAV/classes/dav/class.ilObjFileDAV.php | 274 ++ .../classes/dav/class.ilObjFolderDAV.php | 17 + .../classes/dav/class.ilObjGroupDAV.php | 16 + .../dav/class.ilObjRepositoryRootDAV.php | 32 + .../WebDAV/classes/dav/class.ilObjectDAV.php | 222 ++ .../classes/db/class.ilWebDAVDBManager.php | 101 + .../lock/class.ilWebDAVLockBackend.php | 157 + .../classes/lock/class.ilWebDAVLockObject.php | 166 + Services/WebDAV/service.xml | 2 +- libs/composer/composer.json | 13 +- libs/composer/composer.lock | 409 ++- libs/composer/vendor/bin/generate_vcards | 1 + libs/composer/vendor/bin/naturalselection | 1 + libs/composer/vendor/bin/sabredav | 1 + libs/composer/vendor/bin/vobject | 1 + .../vendor/composer/autoload_classmap.php | 399 ++- .../vendor/composer/autoload_files.php | 7 + .../vendor/composer/autoload_psr4.php | 9 + .../vendor/composer/autoload_static.php | 451 ++- libs/composer/vendor/composer/installed.json | 419 +++ libs/composer/vendor/sabre/dav/.gitignore | 43 + libs/composer/vendor/sabre/dav/.travis.yml | 36 + libs/composer/vendor/sabre/dav/CHANGELOG.md | 2378 ++++++++++++++ .../composer/vendor/sabre/dav/CONTRIBUTING.md | 87 + libs/composer/vendor/sabre/dav/LICENSE | 27 + libs/composer/vendor/sabre/dav/README.md | 38 + libs/composer/vendor/sabre/dav/bin/build.php | 177 + .../vendor/sabre/dav/bin/googlecode_upload.py | 248 ++ .../vendor/sabre/dav/bin/migrateto20.php | 453 +++ .../vendor/sabre/dav/bin/migrateto21.php | 176 + .../vendor/sabre/dav/bin/migrateto30.php | 171 + .../vendor/sabre/dav/bin/migrateto32.php | 268 ++ .../vendor/sabre/dav/bin/naturalselection | 140 + libs/composer/vendor/sabre/dav/bin/sabredav | 2 + .../vendor/sabre/dav/bin/sabredav.php | 53 + libs/composer/vendor/sabre/dav/composer.json | 68 + .../sabre/dav/examples/addressbookserver.php | 57 + .../sabre/dav/examples/calendarserver.php | 80 + .../vendor/sabre/dav/examples/fileserver.php | 56 + .../sabre/dav/examples/groupwareserver.php | 101 + .../vendor/sabre/dav/examples/minimal.php | 20 + .../dav/examples/sql/mysql.addressbooks.sql | 28 + .../dav/examples/sql/mysql.calendars.sql | 76 + .../sabre/dav/examples/sql/mysql.locks.sql | 12 + .../dav/examples/sql/mysql.principals.sql | 20 + .../examples/sql/mysql.propertystorage.sql | 9 + .../sabre/dav/examples/sql/mysql.users.sql | 9 + .../dav/examples/sql/pgsql.addressbooks.sql | 44 + .../dav/examples/sql/pgsql.calendars.sql | 105 + .../sabre/dav/examples/sql/pgsql.locks.sql | 19 + .../dav/examples/sql/pgsql.principals.sql | 30 + .../examples/sql/pgsql.propertystorage.sql | 13 + .../sabre/dav/examples/sql/pgsql.users.sql | 14 + .../dav/examples/sql/sqlite.addressbooks.sql | 28 + .../dav/examples/sql/sqlite.calendars.sql | 76 + .../sabre/dav/examples/sql/sqlite.locks.sql | 12 + .../dav/examples/sql/sqlite.principals.sql | 20 + .../examples/sql/sqlite.propertystorage.sql | 10 + .../sabre/dav/examples/sql/sqlite.users.sql | 9 + .../examples/webserver/apache2_htaccess.conf | 16 + .../dav/examples/webserver/apache2_vhost.conf | 29 + .../examples/webserver/apache2_vhost_cgi.conf | 21 + .../lib/CalDAV/Backend/AbstractBackend.php | 226 ++ .../lib/CalDAV/Backend/BackendInterface.php | 270 ++ .../CalDAV/Backend/NotificationSupport.php | 61 + .../sabre/dav/lib/CalDAV/Backend/PDO.php | 1511 +++++++++ .../lib/CalDAV/Backend/SchedulingSupport.php | 65 + .../dav/lib/CalDAV/Backend/SharingSupport.php | 60 + .../dav/lib/CalDAV/Backend/SimplePDO.php | 296 ++ .../CalDAV/Backend/SubscriptionSupport.php | 89 + .../dav/lib/CalDAV/Backend/SyncSupport.php | 81 + .../vendor/sabre/dav/lib/CalDAV/Calendar.php | 472 +++ .../sabre/dav/lib/CalDAV/CalendarHome.php | 378 +++ .../sabre/dav/lib/CalDAV/CalendarObject.php | 237 ++ .../dav/lib/CalDAV/CalendarQueryValidator.php | 375 +++ .../sabre/dav/lib/CalDAV/CalendarRoot.php | 80 + .../CalDAV/Exception/InvalidComponentType.php | 35 + .../sabre/dav/lib/CalDAV/ICSExportPlugin.php | 378 +++ .../vendor/sabre/dav/lib/CalDAV/ICalendar.php | 18 + .../sabre/dav/lib/CalDAV/ICalendarObject.php | 21 + .../lib/CalDAV/ICalendarObjectContainer.php | 39 + .../sabre/dav/lib/CalDAV/ISharedCalendar.php | 26 + .../lib/CalDAV/Notifications/Collection.php | 101 + .../lib/CalDAV/Notifications/ICollection.php | 23 + .../dav/lib/CalDAV/Notifications/INode.php | 40 + .../dav/lib/CalDAV/Notifications/Node.php | 121 + .../dav/lib/CalDAV/Notifications/Plugin.php | 180 + .../vendor/sabre/dav/lib/CalDAV/Plugin.php | 1068 ++++++ .../dav/lib/CalDAV/Principal/Collection.php | 33 + .../dav/lib/CalDAV/Principal/IProxyRead.php | 19 + .../dav/lib/CalDAV/Principal/IProxyWrite.php | 19 + .../dav/lib/CalDAV/Principal/ProxyRead.php | 181 ++ .../dav/lib/CalDAV/Principal/ProxyWrite.php | 181 ++ .../sabre/dav/lib/CalDAV/Principal/User.php | 135 + .../sabre/dav/lib/CalDAV/Schedule/IInbox.php | 15 + .../dav/lib/CalDAV/Schedule/IMipPlugin.php | 190 ++ .../sabre/dav/lib/CalDAV/Schedule/IOutbox.php | 15 + .../lib/CalDAV/Schedule/ISchedulingObject.php | 13 + .../sabre/dav/lib/CalDAV/Schedule/Inbox.php | 203 ++ .../sabre/dav/lib/CalDAV/Schedule/Outbox.php | 123 + .../sabre/dav/lib/CalDAV/Schedule/Plugin.php | 1066 ++++++ .../lib/CalDAV/Schedule/SchedulingObject.php | 155 + .../sabre/dav/lib/CalDAV/SharedCalendar.php | 229 ++ .../sabre/dav/lib/CalDAV/SharingPlugin.php | 401 +++ .../CalDAV/Subscriptions/ISubscription.php | 40 + .../dav/lib/CalDAV/Subscriptions/Plugin.php | 120 + .../lib/CalDAV/Subscriptions/Subscription.php | 221 ++ .../lib/CalDAV/Xml/Filter/CalendarData.php | 84 + .../dav/lib/CalDAV/Xml/Filter/CompFilter.php | 97 + .../dav/lib/CalDAV/Xml/Filter/ParamFilter.php | 82 + .../dav/lib/CalDAV/Xml/Filter/PropFilter.php | 98 + .../lib/CalDAV/Xml/Notification/Invite.php | 302 ++ .../CalDAV/Xml/Notification/InviteReply.php | 213 ++ .../Notification/NotificationInterface.php | 45 + .../CalDAV/Xml/Notification/SystemStatus.php | 182 ++ .../Xml/Property/AllowedSharingModes.php | 87 + .../CalDAV/Xml/Property/EmailAddressSet.php | 80 + .../dav/lib/CalDAV/Xml/Property/Invite.php | 128 + .../Xml/Property/ScheduleCalendarTransp.php | 130 + .../SupportedCalendarComponentSet.php | 129 + .../Xml/Property/SupportedCalendarData.php | 60 + .../Xml/Property/SupportedCollationSet.php | 57 + .../Xml/Request/CalendarMultiGetReport.php | 124 + .../Xml/Request/CalendarQueryReport.php | 139 + .../Xml/Request/FreeBusyQueryReport.php | 91 + .../lib/CalDAV/Xml/Request/InviteReply.php | 150 + .../dav/lib/CalDAV/Xml/Request/MkCalendar.php | 79 + .../dav/lib/CalDAV/Xml/Request/Share.php | 111 + .../sabre/dav/lib/CardDAV/AddressBook.php | 357 ++ .../sabre/dav/lib/CardDAV/AddressBookHome.php | 191 ++ .../sabre/dav/lib/CardDAV/AddressBookRoot.php | 80 + .../lib/CardDAV/Backend/AbstractBackend.php | 38 + .../lib/CardDAV/Backend/BackendInterface.php | 190 ++ .../sabre/dav/lib/CardDAV/Backend/PDO.php | 550 ++++ .../dav/lib/CardDAV/Backend/SyncSupport.php | 81 + .../vendor/sabre/dav/lib/CardDAV/Card.php | 216 ++ .../sabre/dav/lib/CardDAV/IAddressBook.php | 18 + .../vendor/sabre/dav/lib/CardDAV/ICard.php | 19 + .../sabre/dav/lib/CardDAV/IDirectory.php | 20 + .../vendor/sabre/dav/lib/CardDAV/Plugin.php | 940 ++++++ .../sabre/dav/lib/CardDAV/VCFExportPlugin.php | 172 + .../lib/CardDAV/Xml/Filter/AddressData.php | 63 + .../lib/CardDAV/Xml/Filter/ParamFilter.php | 89 + .../dav/lib/CardDAV/Xml/Filter/PropFilter.php | 98 + .../Xml/Property/SupportedAddressData.php | 83 + .../Xml/Property/SupportedCollationSet.php | 47 + .../Xml/Request/AddressBookMultiGetReport.php | 113 + .../Xml/Request/AddressBookQueryReport.php | 199 ++ .../lib/DAV/Auth/Backend/AbstractBasic.php | 144 + .../lib/DAV/Auth/Backend/AbstractBearer.php | 138 + .../lib/DAV/Auth/Backend/AbstractDigest.php | 168 + .../sabre/dav/lib/DAV/Auth/Backend/Apache.php | 96 + .../lib/DAV/Auth/Backend/BackendInterface.php | 70 + .../lib/DAV/Auth/Backend/BasicCallBack.php | 58 + .../sabre/dav/lib/DAV/Auth/Backend/File.php | 77 + .../sabre/dav/lib/DAV/Auth/Backend/PDO.php | 57 + .../vendor/sabre/dav/lib/DAV/Auth/Plugin.php | 285 ++ .../dav/lib/DAV/Browser/GuessContentType.php | 101 + .../sabre/dav/lib/DAV/Browser/HtmlOutput.php | 34 + .../dav/lib/DAV/Browser/HtmlOutputHelper.php | 117 + .../dav/lib/DAV/Browser/MapGetToPropFind.php | 60 + .../sabre/dav/lib/DAV/Browser/Plugin.php | 802 +++++ .../sabre/dav/lib/DAV/Browser/PropFindAll.php | 132 + .../dav/lib/DAV/Browser/assets/favicon.ico | Bin 0 -> 4286 bytes .../Browser/assets/openiconic/ICON-LICENSE | 21 + .../Browser/assets/openiconic/open-iconic.css | 510 +++ .../Browser/assets/openiconic/open-iconic.eot | Bin 0 -> 23144 bytes .../Browser/assets/openiconic/open-iconic.otf | Bin 0 -> 21048 bytes .../Browser/assets/openiconic/open-iconic.svg | 543 ++++ .../Browser/assets/openiconic/open-iconic.ttf | Bin 0 -> 25568 bytes .../assets/openiconic/open-iconic.woff | Bin 0 -> 12404 bytes .../dav/lib/DAV/Browser/assets/sabredav.css | 228 ++ .../dav/lib/DAV/Browser/assets/sabredav.png | Bin 0 -> 2825 bytes .../vendor/sabre/dav/lib/DAV/Client.php | 439 +++ .../vendor/sabre/dav/lib/DAV/Collection.php | 109 + .../vendor/sabre/dav/lib/DAV/CorePlugin.php | 959 ++++++ .../vendor/sabre/dav/lib/DAV/Exception.php | 57 + .../dav/lib/DAV/Exception/BadRequest.php | 30 + .../sabre/dav/lib/DAV/Exception/Conflict.php | 30 + .../dav/lib/DAV/Exception/ConflictingLock.php | 36 + .../sabre/dav/lib/DAV/Exception/Forbidden.php | 29 + .../lib/DAV/Exception/InsufficientStorage.php | 29 + .../lib/DAV/Exception/InvalidResourceType.php | 33 + .../lib/DAV/Exception/InvalidSyncToken.php | 38 + .../dav/lib/DAV/Exception/LengthRequired.php | 30 + .../Exception/LockTokenMatchesRequestUri.php | 41 + .../sabre/dav/lib/DAV/Exception/Locked.php | 72 + .../lib/DAV/Exception/MethodNotAllowed.php | 47 + .../lib/DAV/Exception/NotAuthenticated.php | 30 + .../sabre/dav/lib/DAV/Exception/NotFound.php | 29 + .../dav/lib/DAV/Exception/NotImplemented.php | 29 + .../dav/lib/DAV/Exception/PaymentRequired.php | 30 + .../lib/DAV/Exception/PreconditionFailed.php | 71 + .../lib/DAV/Exception/ReportNotSupported.php | 32 + .../RequestedRangeNotSatisfiable.php | 30 + .../lib/DAV/Exception/ServiceUnavailable.php | 30 + .../dav/lib/DAV/Exception/TooManyMatches.php | 38 + .../DAV/Exception/UnsupportedMediaType.php | 30 + .../vendor/sabre/dav/lib/DAV/FS/Directory.php | 151 + .../vendor/sabre/dav/lib/DAV/FS/File.php | 95 + .../vendor/sabre/dav/lib/DAV/FS/Node.php | 80 + .../sabre/dav/lib/DAV/FSExt/Directory.php | 211 ++ .../vendor/sabre/dav/lib/DAV/FSExt/File.php | 152 + .../vendor/sabre/dav/lib/DAV/File.php | 96 + .../vendor/sabre/dav/lib/DAV/ICollection.php | 76 + .../sabre/dav/lib/DAV/IExtendedCollection.php | 43 + .../vendor/sabre/dav/lib/DAV/IFile.php | 81 + .../vendor/sabre/dav/lib/DAV/IMoveTarget.php | 44 + .../vendor/sabre/dav/lib/DAV/IMultiGet.php | 36 + .../vendor/sabre/dav/lib/DAV/INode.php | 46 + .../vendor/sabre/dav/lib/DAV/IProperties.php | 47 + .../vendor/sabre/dav/lib/DAV/IQuota.php | 26 + .../lib/DAV/Locks/Backend/AbstractBackend.php | 18 + .../DAV/Locks/Backend/BackendInterface.php | 50 + .../sabre/dav/lib/DAV/Locks/Backend/File.php | 185 ++ .../sabre/dav/lib/DAV/Locks/Backend/PDO.php | 180 + .../sabre/dav/lib/DAV/Locks/LockInfo.php | 80 + .../vendor/sabre/dav/lib/DAV/Locks/Plugin.php | 589 ++++ .../vendor/sabre/dav/lib/DAV/MkCol.php | 72 + .../vendor/sabre/dav/lib/DAV/Mount/Plugin.php | 86 + .../vendor/sabre/dav/lib/DAV/Node.php | 54 + .../lib/DAV/PartialUpdate/IPatchSupport.php | 47 + .../dav/lib/DAV/PartialUpdate/Plugin.php | 215 ++ .../vendor/sabre/dav/lib/DAV/PropFind.php | 347 ++ .../vendor/sabre/dav/lib/DAV/PropPatch.php | 373 +++ .../Backend/BackendInterface.php | 80 + .../lib/DAV/PropertyStorage/Backend/PDO.php | 246 ++ .../dav/lib/DAV/PropertyStorage/Plugin.php | 185 ++ .../vendor/sabre/dav/lib/DAV/Server.php | 1687 ++++++++++ .../vendor/sabre/dav/lib/DAV/ServerPlugin.php | 110 + .../sabre/dav/lib/DAV/Sharing/ISharedNode.php | 69 + .../sabre/dav/lib/DAV/Sharing/Plugin.php | 342 ++ .../sabre/dav/lib/DAV/SimpleCollection.php | 107 + .../vendor/sabre/dav/lib/DAV/SimpleFile.php | 121 + .../vendor/sabre/dav/lib/DAV/StringUtil.php | 91 + .../dav/lib/DAV/Sync/ISyncCollection.php | 88 + .../vendor/sabre/dav/lib/DAV/Sync/Plugin.php | 277 ++ .../dav/lib/DAV/TemporaryFileFilterPlugin.php | 297 ++ .../vendor/sabre/dav/lib/DAV/Tree.php | 340 ++ .../vendor/sabre/dav/lib/DAV/UUIDUtil.php | 64 + .../vendor/sabre/dav/lib/DAV/Version.php | 19 + .../sabre/dav/lib/DAV/Xml/Element/Prop.php | 116 + .../dav/lib/DAV/Xml/Element/Response.php | 253 ++ .../sabre/dav/lib/DAV/Xml/Element/Sharee.php | 199 ++ .../dav/lib/DAV/Xml/Property/Complex.php | 89 + .../lib/DAV/Xml/Property/GetLastModified.php | 110 + .../sabre/dav/lib/DAV/Xml/Property/Href.php | 165 + .../sabre/dav/lib/DAV/Xml/Property/Invite.php | 70 + .../dav/lib/DAV/Xml/Property/LocalHref.php | 48 + .../lib/DAV/Xml/Property/LockDiscovery.php | 106 + .../dav/lib/DAV/Xml/Property/ResourceType.php | 128 + .../dav/lib/DAV/Xml/Property/ShareAccess.php | 143 + .../lib/DAV/Xml/Property/SupportedLock.php | 54 + .../DAV/Xml/Property/SupportedMethodSet.php | 121 + .../DAV/Xml/Property/SupportedReportSet.php | 154 + .../sabre/dav/lib/DAV/Xml/Request/Lock.php | 81 + .../sabre/dav/lib/DAV/Xml/Request/MkCol.php | 82 + .../dav/lib/DAV/Xml/Request/PropFind.php | 83 + .../dav/lib/DAV/Xml/Request/PropPatch.php | 118 + .../dav/lib/DAV/Xml/Request/ShareResource.php | 81 + .../DAV/Xml/Request/SyncCollectionReport.php | 122 + .../dav/lib/DAV/Xml/Response/MultiStatus.php | 142 + .../vendor/sabre/dav/lib/DAV/Xml/Service.php | 47 + .../vendor/sabre/dav/lib/DAVACL/ACLTrait.php | 100 + .../DAVACL/AbstractPrincipalCollection.php | 181 ++ .../dav/lib/DAVACL/Exception/AceConflict.php | 35 + .../lib/DAVACL/Exception/NeedPrivileges.php | 82 + .../dav/lib/DAVACL/Exception/NoAbstract.php | 35 + .../Exception/NotRecognizedPrincipal.php | 35 + .../Exception/NotSupportedPrivilege.php | 35 + .../sabre/dav/lib/DAVACL/FS/Collection.php | 111 + .../vendor/sabre/dav/lib/DAVACL/FS/File.php | 80 + .../dav/lib/DAVACL/FS/HomeCollection.php | 128 + .../vendor/sabre/dav/lib/DAVACL/IACL.php | 74 + .../sabre/dav/lib/DAVACL/IPrincipal.php | 77 + .../dav/lib/DAVACL/IPrincipalCollection.php | 62 + .../vendor/sabre/dav/lib/DAVACL/Plugin.php | 1636 ++++++++++ .../vendor/sabre/dav/lib/DAVACL/Principal.php | 221 ++ .../PrincipalBackend/AbstractBackend.php | 53 + .../PrincipalBackend/BackendInterface.php | 141 + .../CreatePrincipalSupport.php | 30 + .../dav/lib/DAVACL/PrincipalBackend/PDO.php | 431 +++ .../dav/lib/DAVACL/PrincipalCollection.php | 98 + .../sabre/dav/lib/DAVACL/Xml/Property/Acl.php | 277 ++ .../DAVACL/Xml/Property/AclRestrictions.php | 45 + .../Xml/Property/CurrentUserPrivilegeSet.php | 159 + .../dav/lib/DAVACL/Xml/Property/Principal.php | 196 ++ .../Xml/Property/SupportedPrivilegeSet.php | 160 + .../Xml/Request/AclPrincipalPropSetReport.php | 67 + .../Xml/Request/ExpandPropertyReport.php | 103 + .../Xml/Request/PrincipalMatchReport.php | 107 + .../Request/PrincipalPropertySearchReport.php | 127 + .../PrincipalSearchPropertySetReport.php | 58 + .../Sabre/CalDAV/Backend/AbstractPDOTest.php | 1431 ++++++++ .../Sabre/CalDAV/Backend/AbstractTest.php | 178 + .../dav/tests/Sabre/CalDAV/Backend/Mock.php | 258 ++ .../Sabre/CalDAV/Backend/MockScheduling.php | 91 + .../Sabre/CalDAV/Backend/MockSharing.php | 204 ++ .../Backend/MockSubscriptionSupport.php | 156 + .../Sabre/CalDAV/Backend/PDOMySQLTest.php | 9 + .../Sabre/CalDAV/Backend/PDOPgSqlTest.php | 9 + .../Sabre/CalDAV/Backend/PDOSqliteTest.php | 9 + .../Sabre/CalDAV/Backend/SimplePDOTest.php | 445 +++ .../CalDAV/CalendarHomeNotificationsTest.php | 49 + .../CalendarHomeSharedCalendarsTest.php | 80 + .../CalDAV/CalendarHomeSubscriptionsTest.php | 85 + .../tests/Sabre/CalDAV/CalendarHomeTest.php | 215 ++ .../tests/Sabre/CalDAV/CalendarObjectTest.php | 383 +++ .../Sabre/CalDAV/CalendarQueryVAlarmTest.php | 122 + .../CalDAV/CalendarQueryValidatorTest.php | 829 +++++ .../dav/tests/Sabre/CalDAV/CalendarTest.php | 256 ++ .../ExpandEventsDTSTARTandDTENDTest.php | 113 + .../ExpandEventsDTSTARTandDTENDbyDayTest.php | 102 + .../CalDAV/ExpandEventsDoubleEventsTest.php | 103 + .../CalDAV/ExpandEventsFloatingTimeTest.php | 207 ++ .../tests/Sabre/CalDAV/FreeBusyReportTest.php | 174 + .../Sabre/CalDAV/GetEventsByTimerangeTest.php | 82 + .../Sabre/CalDAV/ICSExportPluginTest.php | 386 +++ .../dav/tests/Sabre/CalDAV/Issue166Test.php | 63 + .../dav/tests/Sabre/CalDAV/Issue172Test.php | 135 + .../dav/tests/Sabre/CalDAV/Issue203Test.php | 137 + .../dav/tests/Sabre/CalDAV/Issue205Test.php | 98 + .../dav/tests/Sabre/CalDAV/Issue211Test.php | 89 + .../dav/tests/Sabre/CalDAV/Issue220Test.php | 99 + .../dav/tests/Sabre/CalDAV/Issue228Test.php | 79 + .../tests/Sabre/CalDAV/JCalTransformTest.php | 262 ++ .../CalDAV/Notifications/CollectionTest.php | 85 + .../Sabre/CalDAV/Notifications/NodeTest.php | 96 + .../Sabre/CalDAV/Notifications/PluginTest.php | 168 + .../dav/tests/Sabre/CalDAV/PluginTest.php | 1086 +++++++ .../Sabre/CalDAV/Principal/CollectionTest.php | 20 + .../Sabre/CalDAV/Principal/ProxyReadTest.php | 102 + .../Sabre/CalDAV/Principal/ProxyWriteTest.php | 40 + .../tests/Sabre/CalDAV/Principal/UserTest.php | 127 + .../CalDAV/Schedule/DeliverNewEventTest.php | 92 + .../CalDAV/Schedule/FreeBusyRequestTest.php | 611 ++++ .../Sabre/CalDAV/Schedule/IMip/MockPlugin.php | 50 + .../Sabre/CalDAV/Schedule/IMipPluginTest.php | 221 ++ .../tests/Sabre/CalDAV/Schedule/InboxTest.php | 136 + .../Sabre/CalDAV/Schedule/OutboxPostTest.php | 134 + .../Sabre/CalDAV/Schedule/OutboxTest.php | 48 + .../Sabre/CalDAV/Schedule/PluginBasicTest.php | 39 + .../CalDAV/Schedule/PluginPropertiesTest.php | 146 + ...PluginPropertiesWithSharedCalendarTest.php | 71 + .../CalDAV/Schedule/ScheduleDeliverTest.php | 666 ++++ .../CalDAV/Schedule/SchedulingObjectTest.php | 378 +++ .../tests/Sabre/CalDAV/SharedCalendarTest.php | 176 + .../tests/Sabre/CalDAV/SharingPluginTest.php | 396 +++ .../Subscriptions/CreateSubscriptionTest.php | 123 + .../Sabre/CalDAV/Subscriptions/PluginTest.php | 50 + .../CalDAV/Subscriptions/SubscriptionTest.php | 131 + .../sabre/dav/tests/Sabre/CalDAV/TestUtil.php | 189 ++ .../tests/Sabre/CalDAV/ValidateICalTest.php | 406 +++ .../Xml/Notification/InviteReplyTest.php | 146 + .../CalDAV/Xml/Notification/InviteTest.php | 165 + .../Xml/Notification/SystemStatusTest.php | 69 + .../Xml/Property/AllowedSharingModesTest.php | 38 + .../Xml/Property/EmailAddressSetTest.php | 40 + .../Sabre/CalDAV/Xml/Property/InviteTest.php | 112 + .../Property/ScheduleCalendarTranspTest.php | 118 + .../SupportedCalendarComponentSetTest.php | 102 + .../Property/SupportedCalendarDataTest.php | 36 + .../Property/SupportedCollationSetTest.php | 37 + .../Xml/Request/CalendarQueryReportTest.php | 369 +++ .../CalDAV/Xml/Request/InviteReplyTest.php | 78 + .../Sabre/CalDAV/Xml/Request/ShareTest.php | 83 + .../Sabre/CardDAV/AbstractPluginTest.php | 43 + .../Sabre/CardDAV/AddressBookHomeTest.php | 159 + .../Sabre/CardDAV/AddressBookQueryTest.php | 355 ++ .../Sabre/CardDAV/AddressBookRootTest.php | 31 + .../tests/Sabre/CardDAV/AddressBookTest.php | 194 ++ .../Sabre/CardDAV/Backend/AbstractPDOTest.php | 373 +++ .../dav/tests/Sabre/CardDAV/Backend/Mock.php | 258 ++ .../Sabre/CardDAV/Backend/PDOMySQLTest.php | 9 + .../Sabre/CardDAV/Backend/PDOPgSqlTest.php | 9 + .../Sabre/CardDAV/Backend/PDOSqliteTest.php | 9 + .../dav/tests/Sabre/CardDAV/CardTest.php | 210 ++ .../tests/Sabre/CardDAV/IDirectoryTest.php | 30 + .../dav/tests/Sabre/CardDAV/MultiGetTest.php | 99 + .../dav/tests/Sabre/CardDAV/PluginTest.php | 102 + .../CardDAV/SogoStripContentTypeTest.php | 56 + .../dav/tests/Sabre/CardDAV/TestUtil.php | 62 + .../dav/tests/Sabre/CardDAV/VCFExportTest.php | 135 + .../Sabre/CardDAV/ValidateFilterTest.php | 209 ++ .../tests/Sabre/CardDAV/ValidateVCardTest.php | 305 ++ .../Xml/Property/SupportedAddressDataTest.php | 38 + .../Property/SupportedCollationSetTest.php | 38 + .../Xml/Request/AddressBookMultiGetTest.php | 47 + .../Request/AddressBookQueryReportTest.php | 350 ++ .../dav/tests/Sabre/DAV/AbstractServer.php | 64 + .../DAV/Auth/Backend/AbstractBasicTest.php | 91 + .../DAV/Auth/Backend/AbstractBearerTest.php | 90 + .../DAV/Auth/Backend/AbstractDigestTest.php | 138 + .../DAV/Auth/Backend/AbstractPDOTest.php | 45 + .../Sabre/DAV/Auth/Backend/ApacheTest.php | 71 + .../DAV/Auth/Backend/BasicCallBackTest.php | 36 + .../tests/Sabre/DAV/Auth/Backend/FileTest.php | 41 + .../dav/tests/Sabre/DAV/Auth/Backend/Mock.php | 87 + .../Sabre/DAV/Auth/Backend/PDOMySQLTest.php | 9 + .../Sabre/DAV/Auth/Backend/PDOPgSqlTest.php | 9 + .../Sabre/DAV/Auth/Backend/PDOSqliteTest.php | 9 + .../dav/tests/Sabre/DAV/Auth/PluginTest.php | 133 + .../dav/tests/Sabre/DAV/BasicNodeTest.php | 235 ++ .../DAV/Browser/GuessContentTypeTest.php | 70 + .../DAV/Browser/MapGetToPropFindTest.php | 44 + .../tests/Sabre/DAV/Browser/PluginTest.php | 186 ++ .../Sabre/DAV/Browser/PropFindAllTest.php | 70 + .../sabre/dav/tests/Sabre/DAV/ClientMock.php | 34 + .../sabre/dav/tests/Sabre/DAV/ClientTest.php | 306 ++ .../dav/tests/Sabre/DAV/CorePluginTest.php | 14 + .../dav/tests/Sabre/DAV/DbTestHelperTrait.php | 143 + .../tests/Sabre/DAV/Exception/LockedTest.php | 67 + .../DAV/Exception/PaymentRequiredTest.php | 14 + .../DAV/Exception/ServiceUnavailableTest.php | 14 + .../DAV/Exception/TooManyMatchesTest.php | 35 + .../dav/tests/Sabre/DAV/ExceptionTest.php | 30 + .../tests/Sabre/DAV/FSExt/DirectoryTest.php | 30 + .../dav/tests/Sabre/DAV/FSExt/FileTest.php | 110 + .../dav/tests/Sabre/DAV/FSExt/ServerTest.php | 246 ++ .../tests/Sabre/DAV/GetIfConditionsTest.php | 337 ++ .../tests/Sabre/DAV/HTTPPreferParsingTest.php | 188 ++ .../dav/tests/Sabre/DAV/HttpCopyTest.php | 199 ++ .../dav/tests/Sabre/DAV/HttpDeleteTest.php | 137 + .../sabre/dav/tests/Sabre/DAV/HttpGetTest.php | 158 + .../dav/tests/Sabre/DAV/HttpHeadTest.php | 97 + .../dav/tests/Sabre/DAV/HttpMoveTest.php | 119 + .../sabre/dav/tests/Sabre/DAV/HttpPutTest.php | 349 ++ .../sabre/dav/tests/Sabre/DAV/Issue33Test.php | 106 + .../Sabre/DAV/Locks/Backend/AbstractTest.php | 196 ++ .../Sabre/DAV/Locks/Backend/FileTest.php | 24 + .../tests/Sabre/DAV/Locks/Backend/Mock.php | 139 + .../Sabre/DAV/Locks/Backend/PDOMySQLTest.php | 9 + .../Sabre/DAV/Locks/Backend/PDOPgSqlTest.php | 9 + .../Sabre/DAV/Locks/Backend/PDOSqliteTest.php | 9 + .../tests/Sabre/DAV/Locks/Backend/PDOTest.php | 20 + .../dav/tests/Sabre/DAV/Locks/MSWordTest.php | 124 + .../dav/tests/Sabre/DAV/Locks/Plugin2Test.php | 69 + .../dav/tests/Sabre/DAV/Locks/PluginTest.php | 1003 ++++++ .../dav/tests/Sabre/DAV/Mock/Collection.php | 168 + .../sabre/dav/tests/Sabre/DAV/Mock/File.php | 163 + .../Sabre/DAV/Mock/PropertiesCollection.php | 94 + .../dav/tests/Sabre/DAV/Mock/SharedNode.php | 125 + .../tests/Sabre/DAV/Mock/StreamingFile.php | 102 + .../sabre/dav/tests/Sabre/DAV/MockLogger.php | 36 + .../dav/tests/Sabre/DAV/Mount/PluginTest.php | 58 + .../dav/tests/Sabre/DAV/ObjectTreeTest.php | 100 + .../sabre/dav/tests/Sabre/DAV/PSR3Test.php | 87 + .../Sabre/DAV/PartialUpdate/FileMock.php | 122 + .../Sabre/DAV/PartialUpdate/PluginTest.php | 135 + .../DAV/PartialUpdate/SpecificationTest.php | 94 + .../dav/tests/Sabre/DAV/PropFindTest.php | 76 + .../dav/tests/Sabre/DAV/PropPatchTest.php | 351 ++ .../Backend/AbstractPDOTest.php | 193 ++ .../DAV/PropertyStorage/Backend/Mock.php | 117 + .../PropertyStorage/Backend/PDOMysqlTest.php | 9 + .../PropertyStorage/Backend/PDOPgSqlTest.php | 9 + .../PropertyStorage/Backend/PDOSqliteTest.php | 9 + .../Sabre/DAV/PropertyStorage/PluginTest.php | 117 + .../dav/tests/Sabre/DAV/ServerEventsTest.php | 126 + .../dav/tests/Sabre/DAV/ServerMKCOLTest.php | 366 +++ .../dav/tests/Sabre/DAV/ServerPluginTest.php | 108 + .../Sabre/DAV/ServerPreconditionTest.php | 344 ++ .../DAV/ServerPropsInfiniteDepthTest.php | 163 + .../dav/tests/Sabre/DAV/ServerPropsTest.php | 201 ++ .../dav/tests/Sabre/DAV/ServerRangeTest.php | 262 ++ .../dav/tests/Sabre/DAV/ServerSimpleTest.php | 475 +++ .../Sabre/DAV/ServerUpdatePropertiesTest.php | 102 + .../tests/Sabre/DAV/Sharing/PluginTest.php | 190 ++ .../Sabre/DAV/Sharing/ShareResourceTest.php | 210 ++ .../dav/tests/Sabre/DAV/SimpleFileTest.php | 19 + .../dav/tests/Sabre/DAV/StringUtilTest.php | 129 + .../Sabre/DAV/Sync/MockSyncCollection.php | 169 + .../dav/tests/Sabre/DAV/Sync/PluginTest.php | 523 +++ .../tests/Sabre/DAV/SyncTokenPropertyTest.php | 106 + .../Sabre/DAV/TemporaryFileFilterTest.php | 199 ++ .../sabre/dav/tests/Sabre/DAV/TestPlugin.php | 37 + .../sabre/dav/tests/Sabre/DAV/TreeTest.php | 242 ++ .../dav/tests/Sabre/DAV/UUIDUtilTest.php | 25 + .../tests/Sabre/DAV/Xml/Element/PropTest.php | 154 + .../Sabre/DAV/Xml/Element/ResponseTest.php | 313 ++ .../Sabre/DAV/Xml/Element/ShareeTest.php | 98 + .../tests/Sabre/DAV/Xml/Property/HrefTest.php | 109 + .../Sabre/DAV/Xml/Property/InviteTest.php | 76 + .../DAV/Xml/Property/LastModifiedTest.php | 59 + .../Sabre/DAV/Xml/Property/LocalHrefTest.php | 69 + .../DAV/Xml/Property/LockDiscoveryTest.php | 86 + .../DAV/Xml/Property/ShareAccessTest.php | 121 + .../Xml/Property/SupportedMethodSetTest.php | 45 + .../Xml/Property/SupportedReportSetTest.php | 115 + .../Sabre/DAV/Xml/Request/PropFindTest.php | 48 + .../Sabre/DAV/Xml/Request/PropPatchTest.php | 53 + .../DAV/Xml/Request/ShareResourceTest.php | 75 + .../DAV/Xml/Request/SyncCollectionTest.php | 94 + .../sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php | 48 + .../dav/tests/Sabre/DAVACL/ACLMethodTest.php | 337 ++ .../DAVACL/AclPrincipalPropSetReportTest.php | 69 + .../tests/Sabre/DAVACL/AllowAccessTest.php | 132 + .../tests/Sabre/DAVACL/BlockAccessTest.php | 215 ++ .../DAVACL/Exception/AceConflictTest.php | 39 + .../Exception/NeedPrivilegesExceptionTest.php | 49 + .../Sabre/DAVACL/Exception/NoAbstractTest.php | 39 + .../Exception/NotRecognizedPrincipalTest.php | 39 + .../Exception/NotSupportedPrivilegeTest.php | 39 + .../Sabre/DAVACL/ExpandPropertiesTest.php | 317 ++ .../tests/Sabre/DAVACL/FS/CollectionTest.php | 44 + .../dav/tests/Sabre/DAVACL/FS/FileTest.php | 73 + .../Sabre/DAVACL/FS/HomeCollectionTest.php | 116 + .../dav/tests/Sabre/DAVACL/MockACLNode.php | 55 + .../dav/tests/Sabre/DAVACL/MockPrincipal.php | 64 + .../tests/Sabre/DAVACL/PluginAdminTest.php | 79 + .../Sabre/DAVACL/PluginPropertiesTest.php | 415 +++ .../DAVACL/PluginUpdatePropertiesTest.php | 116 + .../PrincipalBackend/AbstractPDOTest.php | 217 ++ .../Sabre/DAVACL/PrincipalBackend/Mock.php | 168 + .../DAVACL/PrincipalBackend/PDOMySQLTest.php | 9 + .../DAVACL/PrincipalBackend/PDOPgSqlTest.php | 9 + .../DAVACL/PrincipalBackend/PDOSqliteTest.php | 9 + .../Sabre/DAVACL/PrincipalCollectionTest.php | 57 + .../tests/Sabre/DAVACL/PrincipalMatchTest.php | 123 + .../DAVACL/PrincipalPropertySearchTest.php | 397 +++ .../DAVACL/PrincipalSearchPropertySetTest.php | 140 + .../dav/tests/Sabre/DAVACL/PrincipalTest.php | 208 ++ .../tests/Sabre/DAVACL/SimplePluginTest.php | 321 ++ .../Sabre/DAVACL/Xml/Property/ACLTest.php | 342 ++ .../Xml/Property/AclRestrictionsTest.php | 30 + .../Property/CurrentUserPrivilegeSetTest.php | 86 + .../DAVACL/Xml/Property/PrincipalTest.php | 191 ++ .../Property/SupportedPrivilegeSetTest.php | 103 + .../Request/AclPrincipalPropSetReportTest.php | 30 + .../Xml/Request/PrincipalMatchReportTest.php | 51 + .../sabre/dav/tests/Sabre/DAVServerTest.php | 306 ++ .../dav/tests/Sabre/HTTP/ResponseMock.php | 22 + .../sabre/dav/tests/Sabre/HTTP/SapiMock.php | 30 + .../vendor/sabre/dav/tests/Sabre/TestUtil.php | 71 + .../vendor/sabre/dav/tests/bootstrap.php | 38 + .../vendor/sabre/dav/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/dav/tests/phpunit.xml.dist | 46 + libs/composer/vendor/sabre/event/.gitignore | 14 + libs/composer/vendor/sabre/event/.travis.yml | 26 + libs/composer/vendor/sabre/event/CHANGELOG.md | 78 + libs/composer/vendor/sabre/event/LICENSE | 27 + libs/composer/vendor/sabre/event/README.md | 50 + libs/composer/vendor/sabre/event/bin/.empty | 0 .../composer/vendor/sabre/event/composer.json | 47 + .../vendor/sabre/event/examples/promise.php | 100 + .../vendor/sabre/event/examples/tail.php | 28 + .../vendor/sabre/event/lib/EventEmitter.php | 18 + .../sabre/event/lib/EventEmitterInterface.php | 100 + .../sabre/event/lib/EventEmitterTrait.php | 211 ++ .../vendor/sabre/event/lib/Loop/Loop.php | 386 +++ .../vendor/sabre/event/lib/Loop/functions.php | 183 ++ .../vendor/sabre/event/lib/Promise.php | 320 ++ .../sabre/event/lib/Promise/functions.php | 135 + .../lib/PromiseAlreadyResolvedException.php | 15 + .../vendor/sabre/event/lib/Version.php | 19 + .../vendor/sabre/event/lib/coroutine.php | 120 + .../vendor/sabre/event/phpunit.xml.dist | 18 + .../event/tests/ContinueCallbackTest.php | 76 + .../sabre/event/tests/CoroutineTest.php | 262 ++ .../sabre/event/tests/EventEmitterTest.php | 318 ++ .../sabre/event/tests/Loop/FunctionsTest.php | 160 + .../sabre/event/tests/Loop/LoopTest.php | 180 + .../event/tests/Promise/FunctionsTest.php | 184 ++ .../sabre/event/tests/Promise/PromiseTest.php | 341 ++ .../vendor/sabre/event/tests/PromiseTest.php | 386 +++ .../sabre/event/tests/benchmark/bench.php | 116 + libs/composer/vendor/sabre/http/.gitignore | 15 + libs/composer/vendor/sabre/http/.travis.yml | 28 + libs/composer/vendor/sabre/http/CHANGELOG.md | 269 ++ libs/composer/vendor/sabre/http/LICENSE | 27 + libs/composer/vendor/sabre/http/README.md | 746 +++++ libs/composer/vendor/sabre/http/bin/.empty | 0 libs/composer/vendor/sabre/http/composer.json | 44 + .../sabre/http/examples/asyncclient.php | 65 + .../vendor/sabre/http/examples/basicauth.php | 55 + .../vendor/sabre/http/examples/client.php | 38 + .../vendor/sabre/http/examples/digestauth.php | 56 + .../sabre/http/examples/reverseproxy.php | 50 + .../vendor/sabre/http/examples/stringify.php | 51 + .../vendor/sabre/http/lib/Auth/AWS.php | 234 ++ .../sabre/http/lib/Auth/AbstractAuth.php | 73 + .../vendor/sabre/http/lib/Auth/Basic.php | 63 + .../vendor/sabre/http/lib/Auth/Bearer.php | 56 + .../vendor/sabre/http/lib/Auth/Digest.php | 231 ++ .../composer/vendor/sabre/http/lib/Client.php | 601 ++++ .../vendor/sabre/http/lib/ClientException.php | 15 + .../sabre/http/lib/ClientHttpException.php | 58 + .../vendor/sabre/http/lib/HttpException.php | 30 + .../vendor/sabre/http/lib/Message.php | 314 ++ .../sabre/http/lib/MessageDecoratorTrait.php | 251 ++ .../sabre/http/lib/MessageInterface.php | 178 + .../vendor/sabre/http/lib/Request.php | 316 ++ .../sabre/http/lib/RequestDecorator.php | 231 ++ .../sabre/http/lib/RequestInterface.php | 147 + .../vendor/sabre/http/lib/Response.php | 193 ++ .../sabre/http/lib/ResponseDecorator.php | 84 + .../sabre/http/lib/ResponseInterface.php | 45 + libs/composer/vendor/sabre/http/lib/Sapi.php | 202 ++ .../vendor/sabre/http/lib/URLUtil.php | 103 + libs/composer/vendor/sabre/http/lib/Util.php | 74 + .../vendor/sabre/http/lib/Version.php | 19 + .../vendor/sabre/http/lib/functions.php | 445 +++ .../sabre/http/tests/HTTP/Auth/AWSTest.php | 235 ++ .../sabre/http/tests/HTTP/Auth/BasicTest.php | 69 + .../sabre/http/tests/HTTP/Auth/BearerTest.php | 57 + .../sabre/http/tests/HTTP/Auth/DigestTest.php | 191 ++ .../sabre/http/tests/HTTP/ClientTest.php | 474 +++ .../sabre/http/tests/HTTP/FunctionsTest.php | 121 + .../http/tests/HTTP/MessageDecoratorTest.php | 93 + .../sabre/http/tests/HTTP/MessageTest.php | 246 ++ .../http/tests/HTTP/RequestDecoratorTest.php | 112 + .../sabre/http/tests/HTTP/RequestTest.php | 167 + .../http/tests/HTTP/ResponseDecoratorTest.php | 37 + .../sabre/http/tests/HTTP/ResponseTest.php | 48 + .../vendor/sabre/http/tests/HTTP/SapiTest.php | 167 + .../sabre/http/tests/HTTP/URLUtilTest.php | 187 ++ .../vendor/sabre/http/tests/HTTP/UtilTest.php | 206 ++ .../vendor/sabre/http/tests/bootstrap.php | 8 + .../vendor/sabre/http/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/http/tests/phpunit.xml | 18 + libs/composer/vendor/sabre/uri/.gitignore | 13 + libs/composer/vendor/sabre/uri/.travis.yml | 14 + libs/composer/vendor/sabre/uri/CHANGELOG.md | 57 + libs/composer/vendor/sabre/uri/LICENSE | 27 + libs/composer/vendor/sabre/uri/README.md | 47 + libs/composer/vendor/sabre/uri/composer.json | 41 + .../sabre/uri/lib/InvalidUriException.php | 17 + .../composer/vendor/sabre/uri/lib/Version.php | 19 + .../vendor/sabre/uri/lib/functions.php | 373 +++ .../vendor/sabre/uri/tests/BuildTest.php | 41 + .../vendor/sabre/uri/tests/NormalizeTest.php | 42 + .../vendor/sabre/uri/tests/ParseTest.php | 193 ++ .../vendor/sabre/uri/tests/ResolveTest.php | 99 + .../vendor/sabre/uri/tests/SplitTest.php | 41 + .../vendor/sabre/uri/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/uri/tests/phpunit.xml.dist | 18 + libs/composer/vendor/sabre/vobject/.gitignore | 21 + .../composer/vendor/sabre/vobject/.travis.yml | 20 + .../vendor/sabre/vobject/CHANGELOG.md | 814 +++++ libs/composer/vendor/sabre/vobject/LICENSE | 27 + libs/composer/vendor/sabre/vobject/README.md | 55 + .../vendor/sabre/vobject/bin/bench.php | 12 + .../vobject/bin/bench_freebusygenerator.php | 62 + .../vobject/bin/bench_manipulatevcard.php | 69 + .../sabre/vobject/bin/fetch_windows_zones.php | 51 + .../vendor/sabre/vobject/bin/generate_vcards | 241 ++ .../vobject/bin/generateicalendardata.php | 88 + .../sabre/vobject/bin/mergeduplicates.php | 184 ++ .../vendor/sabre/vobject/bin/rrulebench.php | 32 + .../composer/vendor/sabre/vobject/bin/vobject | 27 + .../vendor/sabre/vobject/composer.json | 88 + .../vobject/lib/BirthdayCalendarGenerator.php | 191 ++ .../composer/vendor/sabre/vobject/lib/Cli.php | 771 +++++ .../vendor/sabre/vobject/lib/Component.php | 715 ++++ .../sabre/vobject/lib/Component/Available.php | 126 + .../sabre/vobject/lib/Component/VAlarm.php | 142 + .../vobject/lib/Component/VAvailability.php | 156 + .../sabre/vobject/lib/Component/VCalendar.php | 561 ++++ .../sabre/vobject/lib/Component/VCard.php | 558 ++++ .../sabre/vobject/lib/Component/VEvent.php | 153 + .../sabre/vobject/lib/Component/VFreeBusy.php | 102 + .../sabre/vobject/lib/Component/VJournal.php | 107 + .../sabre/vobject/lib/Component/VTimeZone.php | 66 + .../sabre/vobject/lib/Component/VTodo.php | 193 ++ .../sabre/vobject/lib/DateTimeParser.php | 580 ++++ .../vendor/sabre/vobject/lib/Document.php | 270 ++ .../vendor/sabre/vobject/lib/ElementList.php | 54 + .../vendor/sabre/vobject/lib/EofException.php | 15 + .../vendor/sabre/vobject/lib/FreeBusyData.php | 193 ++ .../sabre/vobject/lib/FreeBusyGenerator.php | 604 ++++ .../vendor/sabre/vobject/lib/ITip/Broker.php | 1002 ++++++ .../sabre/vobject/lib/ITip/ITipException.php | 15 + .../vendor/sabre/vobject/lib/ITip/Message.php | 141 + ...SameOrganizerForAllComponentsException.php | 18 + .../vobject/lib/InvalidDataException.php | 14 + .../vendor/sabre/vobject/lib/Node.php | 265 ++ .../sabre/vobject/lib/PHPUnitAssertions.php | 82 + .../vendor/sabre/vobject/lib/Parameter.php | 394 +++ .../sabre/vobject/lib/ParseException.php | 13 + .../vendor/sabre/vobject/lib/Parser/Json.php | 197 ++ .../sabre/vobject/lib/Parser/MimeDir.php | 697 ++++ .../sabre/vobject/lib/Parser/Parser.php | 80 + .../vendor/sabre/vobject/lib/Parser/XML.php | 428 +++ .../lib/Parser/XML/Element/KeyValue.php | 70 + .../vendor/sabre/vobject/lib/Property.php | 661 ++++ .../sabre/vobject/lib/Property/Binary.php | 128 + .../sabre/vobject/lib/Property/Boolean.php | 84 + .../sabre/vobject/lib/Property/FlatText.php | 50 + .../sabre/vobject/lib/Property/FloatValue.php | 142 + .../lib/Property/ICalendar/CalAddress.php | 61 + .../vobject/lib/Property/ICalendar/Date.php | 18 + .../lib/Property/ICalendar/DateTime.php | 404 +++ .../lib/Property/ICalendar/Duration.php | 85 + .../vobject/lib/Property/ICalendar/Period.php | 155 + .../vobject/lib/Property/ICalendar/Recur.php | 359 ++ .../vobject/lib/Property/IntegerValue.php | 88 + .../sabre/vobject/lib/Property/Text.php | 413 +++ .../sabre/vobject/lib/Property/Time.php | 144 + .../sabre/vobject/lib/Property/Unknown.php | 44 + .../vendor/sabre/vobject/lib/Property/Uri.php | 122 + .../sabre/vobject/lib/Property/UtcOffset.php | 77 + .../sabre/vobject/lib/Property/VCard/Date.php | 43 + .../lib/Property/VCard/DateAndOrTime.php | 405 +++ .../vobject/lib/Property/VCard/DateTime.php | 30 + .../lib/Property/VCard/LanguageTag.php | 60 + .../vobject/lib/Property/VCard/TimeStamp.php | 86 + .../vendor/sabre/vobject/lib/Reader.php | 98 + .../sabre/vobject/lib/Recur/EventIterator.php | 513 +++ .../Recur/MaxInstancesExceededException.php | 16 + .../lib/Recur/NoInstancesException.php | 18 + .../sabre/vobject/lib/Recur/RDateIterator.php | 182 ++ .../sabre/vobject/lib/Recur/RRuleIterator.php | 1013 ++++++ .../vendor/sabre/vobject/lib/Settings.php | 56 + .../sabre/vobject/lib/Splitter/ICalendar.php | 113 + .../lib/Splitter/SplitterInterface.php | 39 + .../sabre/vobject/lib/Splitter/VCard.php | 78 + .../vendor/sabre/vobject/lib/StringUtil.php | 66 + .../vendor/sabre/vobject/lib/TimeZoneUtil.php | 276 ++ .../vendor/sabre/vobject/lib/UUIDUtil.php | 69 + .../sabre/vobject/lib/VCardConverter.php | 467 +++ .../vendor/sabre/vobject/lib/Version.php | 19 + .../vendor/sabre/vobject/lib/Writer.php | 81 + .../lib/timezonedata/exchangezones.php | 94 + .../vobject/lib/timezonedata/lotuszones.php | 101 + .../sabre/vobject/lib/timezonedata/php-bc.php | 153 + .../lib/timezonedata/php-workaround.php | 46 + .../vobject/lib/timezonedata/windowszones.php | 143 + .../sabre/vobject/resources/schema/xcal.rng | 1192 +++++++ .../sabre/vobject/resources/schema/xcard.rng | 388 +++ .../vobject/tests/VObject/AttachIssueTest.php | 24 + .../VObject/BirthdayCalendarGeneratorTest.php | 564 ++++ .../sabre/vobject/tests/VObject/CliTest.php | 644 ++++ .../tests/VObject/Component/AvailableTest.php | 74 + .../tests/VObject/Component/VAlarmTest.php | 178 + .../VObject/Component/VAvailabilityTest.php | 491 +++ .../tests/VObject/Component/VCalendarTest.php | 783 +++++ .../tests/VObject/Component/VCardTest.php | 312 ++ .../tests/VObject/Component/VEventTest.php | 97 + .../tests/VObject/Component/VFreeBusyTest.php | 67 + .../tests/VObject/Component/VJournalTest.php | 101 + .../tests/VObject/Component/VTimeZoneTest.php | 57 + .../tests/VObject/Component/VTodoTest.php | 179 + .../vobject/tests/VObject/ComponentTest.php | 588 ++++ .../tests/VObject/DateTimeParserTest.php | 700 ++++ .../vobject/tests/VObject/DocumentTest.php | 93 + .../vobject/tests/VObject/ElementListTest.php | 35 + .../vobject/tests/VObject/EmClientTest.php | 57 + .../tests/VObject/EmptyParameterTest.php | 71 + .../tests/VObject/EmptyValueIssueTest.php | 32 + .../tests/VObject/FreeBusyDataTest.php | 320 ++ .../tests/VObject/FreeBusyGeneratorTest.php | 753 +++++ .../tests/VObject/GoogleColonEscapingTest.php | 33 + .../VObject/ICalendar/AttachParseTest.php | 32 + .../VObject/ITip/BrokerAttendeeReplyTest.php | 1146 +++++++ .../VObject/ITip/BrokerDeleteEventTest.php | 344 ++ .../tests/VObject/ITip/BrokerNewEventTest.php | 548 ++++ .../VObject/ITip/BrokerProcessMessageTest.php | 164 + .../VObject/ITip/BrokerProcessReplyTest.php | 496 +++ .../ITip/BrokerSignificantChangesTest.php | 110 + .../tests/VObject/ITip/BrokerTester.php | 97 + ...ezoneInParseEventInfoWithoutMasterTest.php | 78 + .../VObject/ITip/BrokerUpdateEventTest.php | 846 +++++ .../tests/VObject/ITip/EvolutionTest.php | 2653 +++++++++++++++ .../tests/VObject/ITip/MessageTest.php | 34 + .../vobject/tests/VObject/Issue153Test.php | 16 + .../vobject/tests/VObject/Issue259Test.php | 23 + .../tests/VObject/Issue36WorkAroundTest.php | 41 + .../vobject/tests/VObject/Issue40Test.php | 34 + .../vobject/tests/VObject/Issue64Test.php | 21 + .../vobject/tests/VObject/Issue96Test.php | 26 + .../tests/VObject/IssueUndefinedIndexTest.php | 31 + .../sabre/vobject/tests/VObject/JCalTest.php | 151 + .../sabre/vobject/tests/VObject/JCardTest.php | 197 ++ .../tests/VObject/LineFoldingIssueTest.php | 25 + .../vobject/tests/VObject/ParameterTest.php | 137 + .../vobject/tests/VObject/Parser/JsonTest.php | 395 +++ .../tests/VObject/Parser/MimeDirTest.php | 163 + .../VObject/Parser/QuotedPrintableTest.php | 108 + .../vobject/tests/VObject/Parser/XmlTest.php | 2894 +++++++++++++++++ .../tests/VObject/Property/BinaryTest.php | 20 + .../tests/VObject/Property/BooleanTest.php | 23 + .../tests/VObject/Property/CompoundTest.php | 51 + .../tests/VObject/Property/FloatTest.php | 31 + .../Property/ICalendar/CalAddressTest.php | 34 + .../Property/ICalendar/DateTimeTest.php | 372 +++ .../Property/ICalendar/DurationTest.php | 21 + .../VObject/Property/ICalendar/RecurTest.php | 454 +++ .../tests/VObject/Property/TextTest.php | 97 + .../tests/VObject/Property/UriTest.php | 28 + .../Property/VCard/DateAndOrTimeTest.php | 270 ++ .../Property/VCard/LanguageTagTest.php | 49 + .../vobject/tests/VObject/PropertyTest.php | 416 +++ .../vobject/tests/VObject/ReaderTest.php | 493 +++ .../EventIterator/ByMonthInDailyTest.php | 59 + .../Recur/EventIterator/BySetPosHangTest.php | 61 + .../EventIterator/ExpandFloatingTimesTest.php | 123 + .../EventIterator/FifthTuesdayProblemTest.php | 55 + .../EventIterator/HandleRDateExpandTest.php | 61 + .../EventIterator/IncorrectExpandTest.php | 63 + .../EventIterator/InfiniteLoopProblemTest.php | 99 + .../Recur/EventIterator/Issue26Test.php | 35 + .../Recur/EventIterator/Issue48Test.php | 49 + .../Recur/EventIterator/Issue50Test.php | 128 + .../VObject/Recur/EventIterator/MainTest.php | 1453 +++++++++ .../Recur/EventIterator/MaxInstancesTest.php | 42 + .../EventIterator/MissingOverriddenTest.php | 63 + .../Recur/EventIterator/NoInstancesTest.php | 40 + .../EventIterator/OverrideFirstEventTest.php | 122 + .../SameDateForRecurringEventsTest.php | 56 + .../tests/VObject/Recur/RDateIteratorTest.php | 79 + .../tests/VObject/Recur/RRuleIteratorTest.php | 1015 ++++++ .../UntilRespectsTimezoneTest.ics | 39 + .../vobject/tests/VObject/SlashRTest.php | 22 + .../tests/VObject/Splitter/ICalendarTest.php | 326 ++ .../tests/VObject/Splitter/VCardTest.php | 195 ++ .../vobject/tests/VObject/StringUtilTest.php | 57 + .../tests/VObject/TimeZoneUtilTest.php | 379 +++ .../vobject/tests/VObject/UUIDUtilTest.php | 39 + .../vobject/tests/VObject/VCard21Test.php | 54 + .../tests/VObject/VCardConverterTest.php | 535 +++ .../vobject/tests/VObject/VersionTest.php | 16 + .../vobject/tests/VObject/WriterTest.php | 43 + .../sabre/vobject/tests/VObject/issue153.vcf | 352 ++ .../sabre/vobject/tests/VObject/issue64.vcf | 351 ++ .../vendor/sabre/vobject/tests/bootstrap.php | 25 + .../vendor/sabre/vobject/tests/phpunit.xml | 23 + libs/composer/vendor/sabre/xml/.gitignore | 9 + libs/composer/vendor/sabre/xml/.travis.yml | 26 + libs/composer/vendor/sabre/xml/CHANGELOG.md | 228 ++ libs/composer/vendor/sabre/xml/LICENSE | 27 + libs/composer/vendor/sabre/xml/README.md | 25 + libs/composer/vendor/sabre/xml/bin/.empty | 0 libs/composer/vendor/sabre/xml/composer.json | 53 + .../sabre/xml/lib/ContextStackTrait.php | 123 + .../sabre/xml/lib/Deserializer/functions.php | 258 ++ .../composer/vendor/sabre/xml/lib/Element.php | 20 + .../vendor/sabre/xml/lib/Element/Base.php | 91 + .../vendor/sabre/xml/lib/Element/Cdata.php | 64 + .../vendor/sabre/xml/lib/Element/Elements.php | 108 + .../vendor/sabre/xml/lib/Element/KeyValue.php | 108 + .../vendor/sabre/xml/lib/Element/Uri.php | 104 + .../sabre/xml/lib/Element/XmlFragment.php | 147 + .../vendor/sabre/xml/lib/LibXMLException.php | 53 + .../vendor/sabre/xml/lib/ParseException.php | 17 + libs/composer/vendor/sabre/xml/lib/Reader.php | 330 ++ .../sabre/xml/lib/Serializer/functions.php | 249 ++ .../composer/vendor/sabre/xml/lib/Service.php | 297 ++ .../composer/vendor/sabre/xml/lib/Version.php | 19 + libs/composer/vendor/sabre/xml/lib/Writer.php | 266 ++ .../sabre/xml/lib/XmlDeserializable.php | 38 + .../vendor/sabre/xml/lib/XmlSerializable.php | 36 + .../xml/tests/Sabre/Xml/ContextStackTest.php | 44 + .../tests/Sabre/Xml/Deserializer/EnumTest.php | 62 + .../Sabre/Xml/Deserializer/KeyValueTest.php | 112 + .../Deserializer/RepeatingElementsTest.php | 35 + .../Xml/Deserializer/ValueObjectTest.php | 169 + .../xml/tests/Sabre/Xml/Element/CDataTest.php | 58 + .../xml/tests/Sabre/Xml/Element/Eater.php | 78 + .../tests/Sabre/Xml/Element/ElementsTest.php | 129 + .../tests/Sabre/Xml/Element/KeyValueTest.php | 210 ++ .../xml/tests/Sabre/Xml/Element/Mock.php | 60 + .../xml/tests/Sabre/Xml/Element/UriTest.php | 76 + .../Sabre/Xml/Element/XmlFragmentTest.php | 143 + .../xml/tests/Sabre/Xml/InfiteLoopTest.php | 50 + .../sabre/xml/tests/Sabre/Xml/ReaderTest.php | 585 ++++ .../tests/Sabre/Xml/Serializer/EnumTest.php | 36 + .../Xml/Serializer/RepeatingElementsTest.php | 35 + .../sabre/xml/tests/Sabre/Xml/ServiceTest.php | 328 ++ .../sabre/xml/tests/Sabre/Xml/WriterTest.php | 439 +++ .../vendor/sabre/xml/tests/phpcs/ruleset.xml | 56 + .../vendor/sabre/xml/tests/phpunit.xml.dist | 17 + webdav.php | 54 +- 916 files changed, 146120 insertions(+), 8158 deletions(-) create mode 100644 Services/WebDAV/README.md delete mode 100644 Services/WebDAV/classes/Server.php delete mode 100644 Services/WebDAV/classes/Tools/_parse_lockinfo.php delete mode 100644 Services/WebDAV/classes/Tools/_parse_propfind.php delete mode 100644 Services/WebDAV/classes/Tools/_parse_proppatch.php create mode 100644 Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php delete mode 100644 Services/WebDAV/classes/class.ilDAVLocks.php delete mode 100644 Services/WebDAV/classes/class.ilDAVProperties.php delete mode 100644 Services/WebDAV/classes/class.ilDAVServer.php delete mode 100644 Services/WebDAV/classes/class.ilDAVUtils.php delete mode 100644 Services/WebDAV/classes/class.ilObjCategoryDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjCourseDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjFileDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjFolderDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjGroupDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjMountPointDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjNull.php delete mode 100644 Services/WebDAV/classes/class.ilObjNullDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjRootDAV.php delete mode 100644 Services/WebDAV/classes/class.ilObjectDAV.php create mode 100644 Services/WebDAV/classes/class.ilWebDAVMountInstructions.php create mode 100644 Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php create mode 100644 Services/WebDAV/classes/class.ilWebDAVRequestHandler.php create mode 100644 Services/WebDAV/classes/class.ilWebDAVTree.php create mode 100644 Services/WebDAV/classes/class.ilWebDAVUtil.php create mode 100644 Services/WebDAV/classes/dav/class.ilClientNodeDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilMountPointDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjContainerDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjCourseDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjFileDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjFolderDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjGroupDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjRepositoryRootDAV.php create mode 100644 Services/WebDAV/classes/dav/class.ilObjectDAV.php create mode 100644 Services/WebDAV/classes/db/class.ilWebDAVDBManager.php create mode 100644 Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php create mode 100644 Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php create mode 120000 libs/composer/vendor/bin/generate_vcards create mode 120000 libs/composer/vendor/bin/naturalselection create mode 120000 libs/composer/vendor/bin/sabredav create mode 120000 libs/composer/vendor/bin/vobject create mode 100644 libs/composer/vendor/sabre/dav/.gitignore create mode 100644 libs/composer/vendor/sabre/dav/.travis.yml create mode 100644 libs/composer/vendor/sabre/dav/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/dav/CONTRIBUTING.md create mode 100644 libs/composer/vendor/sabre/dav/LICENSE create mode 100644 libs/composer/vendor/sabre/dav/README.md create mode 100755 libs/composer/vendor/sabre/dav/bin/build.php create mode 100755 libs/composer/vendor/sabre/dav/bin/googlecode_upload.py create mode 100755 libs/composer/vendor/sabre/dav/bin/migrateto20.php create mode 100755 libs/composer/vendor/sabre/dav/bin/migrateto21.php create mode 100755 libs/composer/vendor/sabre/dav/bin/migrateto30.php create mode 100755 libs/composer/vendor/sabre/dav/bin/migrateto32.php create mode 100755 libs/composer/vendor/sabre/dav/bin/naturalselection create mode 100755 libs/composer/vendor/sabre/dav/bin/sabredav create mode 100755 libs/composer/vendor/sabre/dav/bin/sabredav.php create mode 100644 libs/composer/vendor/sabre/dav/composer.json create mode 100644 libs/composer/vendor/sabre/dav/examples/addressbookserver.php create mode 100644 libs/composer/vendor/sabre/dav/examples/calendarserver.php create mode 100644 libs/composer/vendor/sabre/dav/examples/fileserver.php create mode 100644 libs/composer/vendor/sabre/dav/examples/groupwareserver.php create mode 100644 libs/composer/vendor/sabre/dav/examples/minimal.php create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.calendars.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.locks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.principals.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/mysql.users.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.calendars.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.locks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.principals.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/pgsql.users.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.calendars.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.locks.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.principals.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/sql/sqlite.users.sql create mode 100644 libs/composer/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf create mode 100644 libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost.conf create mode 100644 libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Calendar.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarHome.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarObject.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendar.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendarObject.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/User.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBook.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Card.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/IAddressBook.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/ICard.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/IDirectory.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Auth/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Client.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Collection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/CorePlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/BadRequest.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/Conflict.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/Locked.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotFound.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/FS/Directory.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/FS/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/FS/Node.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/FSExt/Directory.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/FSExt/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/ICollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IExtendedCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IFile.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IMoveTarget.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IMultiGet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/INode.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IProperties.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/IQuota.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Locks/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/MkCol.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Mount/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Node.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PropFind.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PropPatch.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Server.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/ServerPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/SimpleCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/SimpleFile.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/StringUtil.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Sync/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Tree.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/UUIDUtil.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Version.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAV/Xml/Service.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/ACLTrait.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/FS/Collection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/FS/File.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/IACL.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/IPrincipal.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/IPrincipalCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Plugin.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Principal.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php create mode 100644 libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/SimplePDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/CardTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookMultiGetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBearerTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/CorePluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/DbTestHelperTrait.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/DirectoryTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpCopyTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/SharedNode.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/MockLogger.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PSR3Test.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/ShareResourceTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ShareeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/InviteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LocalHrefTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/ShareAccessTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/ShareResourceTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AclPrincipalPropSetReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOPgSqlTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalMatchTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/AclPrincipalPropSetReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/PrincipalMatchReportTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/DAVServerTest.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php create mode 100644 libs/composer/vendor/sabre/dav/tests/Sabre/TestUtil.php create mode 100644 libs/composer/vendor/sabre/dav/tests/bootstrap.php create mode 100644 libs/composer/vendor/sabre/dav/tests/phpcs/ruleset.xml create mode 100644 libs/composer/vendor/sabre/dav/tests/phpunit.xml.dist create mode 100644 libs/composer/vendor/sabre/event/.gitignore create mode 100644 libs/composer/vendor/sabre/event/.travis.yml create mode 100644 libs/composer/vendor/sabre/event/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/event/LICENSE create mode 100644 libs/composer/vendor/sabre/event/README.md create mode 100644 libs/composer/vendor/sabre/event/bin/.empty create mode 100644 libs/composer/vendor/sabre/event/composer.json create mode 100755 libs/composer/vendor/sabre/event/examples/promise.php create mode 100755 libs/composer/vendor/sabre/event/examples/tail.php create mode 100644 libs/composer/vendor/sabre/event/lib/EventEmitter.php create mode 100644 libs/composer/vendor/sabre/event/lib/EventEmitterInterface.php create mode 100644 libs/composer/vendor/sabre/event/lib/EventEmitterTrait.php create mode 100644 libs/composer/vendor/sabre/event/lib/Loop/Loop.php create mode 100644 libs/composer/vendor/sabre/event/lib/Loop/functions.php create mode 100644 libs/composer/vendor/sabre/event/lib/Promise.php create mode 100644 libs/composer/vendor/sabre/event/lib/Promise/functions.php create mode 100644 libs/composer/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php create mode 100644 libs/composer/vendor/sabre/event/lib/Version.php create mode 100644 libs/composer/vendor/sabre/event/lib/coroutine.php create mode 100644 libs/composer/vendor/sabre/event/phpunit.xml.dist create mode 100644 libs/composer/vendor/sabre/event/tests/ContinueCallbackTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/CoroutineTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/EventEmitterTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/Loop/FunctionsTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/Loop/LoopTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/Promise/FunctionsTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/Promise/PromiseTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/PromiseTest.php create mode 100644 libs/composer/vendor/sabre/event/tests/benchmark/bench.php create mode 100644 libs/composer/vendor/sabre/http/.gitignore create mode 100644 libs/composer/vendor/sabre/http/.travis.yml create mode 100644 libs/composer/vendor/sabre/http/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/http/LICENSE create mode 100644 libs/composer/vendor/sabre/http/README.md create mode 100644 libs/composer/vendor/sabre/http/bin/.empty create mode 100644 libs/composer/vendor/sabre/http/composer.json create mode 100644 libs/composer/vendor/sabre/http/examples/asyncclient.php create mode 100644 libs/composer/vendor/sabre/http/examples/basicauth.php create mode 100644 libs/composer/vendor/sabre/http/examples/client.php create mode 100644 libs/composer/vendor/sabre/http/examples/digestauth.php create mode 100644 libs/composer/vendor/sabre/http/examples/reverseproxy.php create mode 100644 libs/composer/vendor/sabre/http/examples/stringify.php create mode 100644 libs/composer/vendor/sabre/http/lib/Auth/AWS.php create mode 100644 libs/composer/vendor/sabre/http/lib/Auth/AbstractAuth.php create mode 100644 libs/composer/vendor/sabre/http/lib/Auth/Basic.php create mode 100644 libs/composer/vendor/sabre/http/lib/Auth/Bearer.php create mode 100644 libs/composer/vendor/sabre/http/lib/Auth/Digest.php create mode 100644 libs/composer/vendor/sabre/http/lib/Client.php create mode 100644 libs/composer/vendor/sabre/http/lib/ClientException.php create mode 100644 libs/composer/vendor/sabre/http/lib/ClientHttpException.php create mode 100644 libs/composer/vendor/sabre/http/lib/HttpException.php create mode 100644 libs/composer/vendor/sabre/http/lib/Message.php create mode 100644 libs/composer/vendor/sabre/http/lib/MessageDecoratorTrait.php create mode 100644 libs/composer/vendor/sabre/http/lib/MessageInterface.php create mode 100644 libs/composer/vendor/sabre/http/lib/Request.php create mode 100644 libs/composer/vendor/sabre/http/lib/RequestDecorator.php create mode 100644 libs/composer/vendor/sabre/http/lib/RequestInterface.php create mode 100644 libs/composer/vendor/sabre/http/lib/Response.php create mode 100644 libs/composer/vendor/sabre/http/lib/ResponseDecorator.php create mode 100644 libs/composer/vendor/sabre/http/lib/ResponseInterface.php create mode 100644 libs/composer/vendor/sabre/http/lib/Sapi.php create mode 100644 libs/composer/vendor/sabre/http/lib/URLUtil.php create mode 100644 libs/composer/vendor/sabre/http/lib/Util.php create mode 100644 libs/composer/vendor/sabre/http/lib/Version.php create mode 100644 libs/composer/vendor/sabre/http/lib/functions.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/ClientTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/FunctionsTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/MessageTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/RequestTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/ResponseTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/SapiTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/URLUtilTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/HTTP/UtilTest.php create mode 100644 libs/composer/vendor/sabre/http/tests/bootstrap.php create mode 100644 libs/composer/vendor/sabre/http/tests/phpcs/ruleset.xml create mode 100644 libs/composer/vendor/sabre/http/tests/phpunit.xml create mode 100644 libs/composer/vendor/sabre/uri/.gitignore create mode 100644 libs/composer/vendor/sabre/uri/.travis.yml create mode 100644 libs/composer/vendor/sabre/uri/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/uri/LICENSE create mode 100644 libs/composer/vendor/sabre/uri/README.md create mode 100644 libs/composer/vendor/sabre/uri/composer.json create mode 100644 libs/composer/vendor/sabre/uri/lib/InvalidUriException.php create mode 100644 libs/composer/vendor/sabre/uri/lib/Version.php create mode 100644 libs/composer/vendor/sabre/uri/lib/functions.php create mode 100644 libs/composer/vendor/sabre/uri/tests/BuildTest.php create mode 100644 libs/composer/vendor/sabre/uri/tests/NormalizeTest.php create mode 100644 libs/composer/vendor/sabre/uri/tests/ParseTest.php create mode 100644 libs/composer/vendor/sabre/uri/tests/ResolveTest.php create mode 100644 libs/composer/vendor/sabre/uri/tests/SplitTest.php create mode 100644 libs/composer/vendor/sabre/uri/tests/phpcs/ruleset.xml create mode 100644 libs/composer/vendor/sabre/uri/tests/phpunit.xml.dist create mode 100644 libs/composer/vendor/sabre/vobject/.gitignore create mode 100644 libs/composer/vendor/sabre/vobject/.travis.yml create mode 100644 libs/composer/vendor/sabre/vobject/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/vobject/LICENSE create mode 100644 libs/composer/vendor/sabre/vobject/README.md create mode 100755 libs/composer/vendor/sabre/vobject/bin/bench.php create mode 100644 libs/composer/vendor/sabre/vobject/bin/bench_freebusygenerator.php create mode 100644 libs/composer/vendor/sabre/vobject/bin/bench_manipulatevcard.php create mode 100755 libs/composer/vendor/sabre/vobject/bin/fetch_windows_zones.php create mode 100755 libs/composer/vendor/sabre/vobject/bin/generate_vcards create mode 100755 libs/composer/vendor/sabre/vobject/bin/generateicalendardata.php create mode 100755 libs/composer/vendor/sabre/vobject/bin/mergeduplicates.php create mode 100644 libs/composer/vendor/sabre/vobject/bin/rrulebench.php create mode 100755 libs/composer/vendor/sabre/vobject/bin/vobject create mode 100644 libs/composer/vendor/sabre/vobject/composer.json create mode 100644 libs/composer/vendor/sabre/vobject/lib/BirthdayCalendarGenerator.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Cli.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/Available.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VAlarm.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VAvailability.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VCalendar.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VCard.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VEvent.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VFreeBusy.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VJournal.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VTimeZone.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Component/VTodo.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/DateTimeParser.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Document.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ElementList.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/EofException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/FreeBusyData.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/FreeBusyGenerator.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ITip/Broker.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ITip/ITipException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ITip/Message.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/InvalidDataException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Node.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/PHPUnitAssertions.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parameter.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/ParseException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parser/Json.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parser/MimeDir.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parser/Parser.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parser/XML.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Binary.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Boolean.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/FlatText.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/FloatValue.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Date.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Period.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/IntegerValue.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Text.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Time.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Unknown.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/Uri.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/UtcOffset.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/VCard/Date.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateTime.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Reader.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Recur/EventIterator.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Recur/MaxInstancesExceededException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Recur/NoInstancesException.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Recur/RDateIterator.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Recur/RRuleIterator.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Settings.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Splitter/ICalendar.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Splitter/VCard.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/StringUtil.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/TimeZoneUtil.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/UUIDUtil.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/VCardConverter.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Version.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/Writer.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/timezonedata/exchangezones.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/timezonedata/lotuszones.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/timezonedata/php-bc.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/timezonedata/php-workaround.php create mode 100644 libs/composer/vendor/sabre/vobject/lib/timezonedata/windowszones.php create mode 100644 libs/composer/vendor/sabre/vobject/resources/schema/xcal.rng create mode 100644 libs/composer/vendor/sabre/vobject/resources/schema/xcard.rng create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/BirthdayCalendarGeneratorTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/CliTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/AvailableTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ComponentTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/DocumentTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ElementListTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/EmClientTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyDataTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerSignificantChangesTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue153Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue259Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue40Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue64Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Issue96Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/IssueUndefinedIndexTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/JCalTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/JCardTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ParameterTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Parser/XmlTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/TextTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/UriTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/PropertyTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/ReaderTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue26Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MaxInstancesTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/SameDateForRecurringEventsTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/SlashRTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/StringUtilTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/VCard21Test.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/VersionTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/WriterTest.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/issue153.vcf create mode 100644 libs/composer/vendor/sabre/vobject/tests/VObject/issue64.vcf create mode 100644 libs/composer/vendor/sabre/vobject/tests/bootstrap.php create mode 100644 libs/composer/vendor/sabre/vobject/tests/phpunit.xml create mode 100644 libs/composer/vendor/sabre/xml/.gitignore create mode 100644 libs/composer/vendor/sabre/xml/.travis.yml create mode 100644 libs/composer/vendor/sabre/xml/CHANGELOG.md create mode 100644 libs/composer/vendor/sabre/xml/LICENSE create mode 100644 libs/composer/vendor/sabre/xml/README.md create mode 100644 libs/composer/vendor/sabre/xml/bin/.empty create mode 100644 libs/composer/vendor/sabre/xml/composer.json create mode 100644 libs/composer/vendor/sabre/xml/lib/ContextStackTrait.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Deserializer/functions.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/Base.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/Cdata.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/Elements.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/KeyValue.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/Uri.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Element/XmlFragment.php create mode 100644 libs/composer/vendor/sabre/xml/lib/LibXMLException.php create mode 100644 libs/composer/vendor/sabre/xml/lib/ParseException.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Reader.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Serializer/functions.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Service.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Version.php create mode 100644 libs/composer/vendor/sabre/xml/lib/Writer.php create mode 100644 libs/composer/vendor/sabre/xml/lib/XmlDeserializable.php create mode 100644 libs/composer/vendor/sabre/xml/lib/XmlSerializable.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php create mode 100644 libs/composer/vendor/sabre/xml/tests/phpcs/ruleset.xml create mode 100644 libs/composer/vendor/sabre/xml/tests/phpunit.xml.dist diff --git a/Modules/Category/classes/class.ilObjCategoryAccess.php b/Modules/Category/classes/class.ilObjCategoryAccess.php index 58725f177e16..70747fd0d702 100644 --- a/Modules/Category/classes/class.ilObjCategoryAccess.php +++ b/Modules/Category/classes/class.ilObjCategoryAccess.php @@ -51,8 +51,8 @@ static function _getCommands() require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - include_once './Services/WebDAV/classes/class.ilDAVUtils.php'; - if(ilDAVUtils::getInstance()->isLocalPasswordInstructionRequired()) + include_once './Services/WebDAV/classes/class.ilWebDAVUtil.php'; + if(ilWebDAVUtil::getInstance()->isLocalPasswordInstructionRequired()) { $commands[] = array('permission' => 'read', 'cmd' => 'showPasswordInstruction', 'lang_var' => 'mount_webfolder', 'enable_anonymous' => 'false'); } diff --git a/Modules/Category/classes/class.ilObjCategoryListGUI.php b/Modules/Category/classes/class.ilObjCategoryListGUI.php index 1e093c8529ea..b3de8951529f 100644 --- a/Modules/Category/classes/class.ilObjCategoryListGUI.php +++ b/Modules/Category/classes/class.ilObjCategoryListGUI.php @@ -127,15 +127,15 @@ function getCommandLink($a_cmd) require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - require_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + require_once ('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); // FIXME: The following is a very dirty, ugly trick. // To mount URI needs to be put into two attributes: // href and folder. This hack returns both attributes // like this: http://...mount_uri..." folder="http://...folder_uri... - $cmd_link = $davServer->getMountURI($this->ref_id). - '" folder="'.$davServer->getFolderURI($this->ref_id); + $cmd_link = $dav_util->getMountURI($this->ref_id). + '" folder="'.$dav_util->getFolderURI($this->ref_id); } break; default : diff --git a/Modules/Course/classes/class.ilObjCourseAccess.php b/Modules/Course/classes/class.ilObjCourseAccess.php index 6afee2804d0a..8b57c9c100a8 100644 --- a/Modules/Course/classes/class.ilObjCourseAccess.php +++ b/Modules/Course/classes/class.ilObjCourseAccess.php @@ -236,8 +236,8 @@ static function _getCommands() include_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - include_once './Services/WebDAV/classes/class.ilDAVUtils.php'; - if(ilDAVUtils::getInstance()->isLocalPasswordInstructionRequired()) + include_once './Services/WebDAV/classes/class.ilWebDAVUtil.php'; + if(ilWebDAVUtil::getInstance()->isLocalPasswordInstructionRequired()) { $commands[] = array('permission' => 'read', 'cmd' => 'showPasswordInstruction', 'lang_var' => 'mount_webfolder', 'enable_anonymous' => 'false'); } diff --git a/Modules/Folder/classes/class.ilObjFolderAccess.php b/Modules/Folder/classes/class.ilObjFolderAccess.php index 075ca48c7c3b..f2ae8d09b312 100644 --- a/Modules/Folder/classes/class.ilObjFolderAccess.php +++ b/Modules/Folder/classes/class.ilObjFolderAccess.php @@ -64,8 +64,8 @@ static function _getCommands() include_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - include_once './Services/WebDAV/classes/class.ilDAVUtils.php'; - if(ilDAVUtils::getInstance()->isLocalPasswordInstructionRequired()) + include_once './Services/WebDAV/classes/class.ilWebDAVUtil.php'; + if(ilWebDAVUtil::getInstance()->isLocalPasswordInstructionRequired()) { $commands[] = array('permission' => 'read', 'cmd' => 'showPasswordInstruction', 'lang_var' => 'mount_webfolder', 'enable_anonymous' => 'false'); } diff --git a/Modules/Folder/classes/class.ilObjFolderListGUI.php b/Modules/Folder/classes/class.ilObjFolderListGUI.php index 3c9143f214af..e8947f15cffc 100644 --- a/Modules/Folder/classes/class.ilObjFolderListGUI.php +++ b/Modules/Folder/classes/class.ilObjFolderListGUI.php @@ -89,15 +89,15 @@ function getCommandLink($a_cmd) require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - require_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + require_once ('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); // XXX: The following is a very dirty, ugly trick. // To mount URI needs to be put into two attributes: // href and folder. This hack returns both attributes // like this: http://...mount_uri..." folder="http://...folder_uri... - $cmd_link = $davServer->getMountURI($this->ref_id, $this->title, $this->parent). - '" folder="'.$davServer->getFolderURI($this->ref_id, $this->title, $this->parent); + $cmd_link = $dav_util->getMountURI($this->ref_id, $this->title, $this->parent). + '" folder="'.$dav_util->getFolderURI($this->ref_id, $this->title, $this->parent); break; } // Fall through, when plugin is inactive. default : diff --git a/Modules/Group/classes/class.ilObjGroupAccess.php b/Modules/Group/classes/class.ilObjGroupAccess.php index 92f96b63256a..ceeeda570ed2 100644 --- a/Modules/Group/classes/class.ilObjGroupAccess.php +++ b/Modules/Group/classes/class.ilObjGroupAccess.php @@ -158,8 +158,8 @@ static function _getCommands() include_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - include_once './Services/WebDAV/classes/class.ilDAVUtils.php'; - if(ilDAVUtils::getInstance()->isLocalPasswordInstructionRequired()) + include_once './Services/WebDAV/classes/class.ilWebDAVUtil.php'; + if(ilWebDAVUtil::getInstance()->isLocalPasswordInstructionRequired()) { $commands[] = array('permission' => 'read', 'cmd' => 'showPasswordInstruction', 'lang_var' => 'mount_webfolder', 'enable_anonymous' => 'false'); } diff --git a/Modules/Group/classes/class.ilObjGroupListGUI.php b/Modules/Group/classes/class.ilObjGroupListGUI.php index c473fd45eef6..ccef0d365b1d 100644 --- a/Modules/Group/classes/class.ilObjGroupListGUI.php +++ b/Modules/Group/classes/class.ilObjGroupListGUI.php @@ -79,15 +79,15 @@ function getCommandLink($a_cmd) require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - require_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + require_once ('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); // XXX: The following is a very dirty, ugly trick. // To mount URI needs to be put into two attributes: // href and folder. This hack returns both attributes // like this: http://...mount_uri..." folder="http://...folder_uri... - $cmd_link = $davServer->getMountURI($this->ref_id). - '" folder="'.$davServer->getFolderURI($this->ref_id); + $cmd_link = $dav_util->getMountURI($this->ref_id). + '" folder="'.$dav_util->getFolderURI($this->ref_id); break; } // fall through if plugin is not active // END Mount Webfolder. diff --git a/Services/Container/classes/class.ilContainerGUI.php b/Services/Container/classes/class.ilContainerGUI.php index 9085a5d37e60..5ca7fc05a702 100644 --- a/Services/Container/classes/class.ilContainerGUI.php +++ b/Services/Container/classes/class.ilContainerGUI.php @@ -1461,45 +1461,6 @@ function isMultiDownloadEnabled() return $this->multi_download_enabled; } - // BEGIN WebDAV: Lock/Unlock objects - function lockObject() - { - $tree = $this->tree; - $ilUser = $this->user; - $rbacsystem = $this->rbacsystem; - - if (!$rbacsystem->checkAccess("write",$_GET['item_ref_id'])) - { - $this->ilErr->raiseError($this->lng->txt('err_no_permission'),$this->ilErr->MESSAGE); - } - - - require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); - if (ilDAVActivationChecker::_isActive()) - { - require_once 'Services/WebDAV/classes/class.ilDAVServer.php'; - if (ilDAVServer::_isActionsVisible()) - { - require_once 'Services/WebDAV/classes/class.ilDAVLocks.php'; - $locks = new ilDAVLocks(); - - $result = $locks->lockRef($_GET['item_ref_id'], - $ilUser->getId(), $ilUser->getLogin(), - 'ref_'.$_GET['item_ref_id'].'_usr_'.$ilUser->getId(), - time() + /*30*24*60**/60, 0, 'exclusive' - ); - - ilUtil::sendInfo( - $this->lng->txt( - ($result === true) ? 'object_locked' : $result - ), - true); - } - } - $this->renderObject(); - } - // END WebDAV: Lock/Unlock objects - /** * cut object(s) out from a container and write the information to clipboard * @@ -3276,16 +3237,17 @@ protected function showPasswordInstructionObject($a_init = true) $this->initFormPasswordInstruction(); } - include_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + include_once ('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); $ilToolbar->addButton( $this->lng->txt('mount_webfolder'), - $davServer->getMountURI($this->object->getRefId()), + $dav_util->getMountURI($this->object->getRefId()), '_blank', '', - $davServer->getFolderURI($this->object->getRefId()) + $dav_util->getFolderURI($this->object->getRefId()) ); + $tpl->setContent($this->form->getHTML()); } diff --git a/Services/Context/classes/class.ilContextWebdav.php b/Services/Context/classes/class.ilContextWebdav.php index a86cd61a0cb4..dcac97c2126e 100644 --- a/Services/Context/classes/class.ilContextWebdav.php +++ b/Services/Context/classes/class.ilContextWebdav.php @@ -81,7 +81,7 @@ public static function initClient() */ public static function doAuthentication() { - return true; + return false; } /** @@ -90,7 +90,7 @@ public static function doAuthentication() */ public static function supportsPersistentSessions() { - return true; + return false; } /** diff --git a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php index 6be4d9fc6b84..7e4cd6028a51 100644 --- a/Services/InfoScreen/classes/class.ilInfoScreenGUI.php +++ b/Services/InfoScreen/classes/class.ilInfoScreenGUI.php @@ -656,26 +656,24 @@ function addObjectSections() { if ($ilUser->getId() != ANONYMOUS_USER_ID) { - require_once 'Services/WebDAV/classes/class.ilDAVServer.php'; - $davLocks = new ilDAVLocks(); + require_once 'Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php'; + $webdav_lock_backend = new ilWebDAVLockBackend(); // Show lock info if ($ilUser->getId() != ANONYMOUS_USER_ID) { - $locks =& $davLocks->getLocksOnObjectObj($a_obj->getId()); - if (count($locks) > 0) + if ($lock = $webdav_lock_backend->getLocksOnObjectId($this->gui_object->object->getId())) { - $lockUser = new ilObjUser($locks[0]['ilias_owner']); + $lock_user = new ilObjUser($lock->getIliasOwner()); $this->addProperty($this->lng->txt("in_use_by"), - $lockUser->getPublicName() + $lock_user->getPublicName() , - "./ilias.php?user=".$locks[0]['ilias_owner'].'&cmd=showUserProfile&cmdClass=ilpersonaldesktopgui&cmdNode=1&baseClass=ilPersonalDesktopGUI' + "./ilias.php?user=".$lock_user->getId().'&cmd=showUserProfile&cmdClass=ilpersonaldesktopgui&cmdNode=1&baseClass=ilPersonalDesktopGUI' ); } } } } - } // END ChangeEvent: Display standard object info /** diff --git a/Services/Object/classes/class.ilObjectListGUI.php b/Services/Object/classes/class.ilObjectListGUI.php index ded2f30a1eb4..ce141b1ccf3d 100644 --- a/Services/Object/classes/class.ilObjectListGUI.php +++ b/Services/Object/classes/class.ilObjectListGUI.php @@ -1112,15 +1112,15 @@ function getCommandLink($a_cmd) require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if ($a_cmd == 'mount_webfolder' && ilDAVActivationChecker::_isActive()) { - require_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + require_once ('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); // XXX: The following is a very dirty, ugly trick. // To mount URI needs to be put into two attributes: // href and folder. This hack returns both attributes // like this: http://...mount_uri..." folder="http://...folder_uri... - return $davServer->getMountURI($this->ref_id). - '" folder="'.$davServer->getFolderURI($this->ref_id); + return $dav_util->getMountURI($this->ref_id). + '" folder="'.$dav_util->getFolderURI($this->ref_id); } // END WebDAV Get mount webfolder link. @@ -1214,23 +1214,20 @@ public function getProperties() require_once ('Services/WebDAV/classes/class.ilDAVActivationChecker.php'); if (ilDAVActivationChecker::_isActive()) { - require_once ('Services/WebDAV/classes/class.ilDAVServer.php'); - // Show lock info - require_once('Services/WebDAV/classes/class.ilDAVLocks.php'); - $davLocks = new ilDAVLocks(); + require_once('Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php'); + $webdav_lock_backend = new ilWebDAVLockBackend(); if ($ilUser->getId() != ANONYMOUS_USER_ID) { - $locks =& $davLocks->getLocksOnObjectObj($this->obj_id); - if (count($locks) > 0) + if ($lock = $webdav_lock_backend->getLocksOnObjectId($this->obj_id)) { - $lockUser = new ilObjUser($locks[0]['ilias_owner']); + $lock_user = new ilObjUser($lock->getIliasOwner()); $props[] = array( "alert" => false, "property" => $lng->txt("in_use_by"), - "value" => $lockUser->getLogin(), - "link" => "./ilias.php?user=".$locks[0]['ilias_owner'].'&cmd=showUserProfile&cmdClass=ilpersonaldesktopgui&cmdNode=1&baseClass=ilPersonalDesktopGUI', + "value" => $lock_user->getLogin(), + "link" => "./ilias.php?user=".$lock_user->getId().'&cmd=showUserProfile&cmdClass=ilpersonaldesktopgui&baseClass=ilPersonalDesktopGUI', ); } } @@ -3892,4 +3889,4 @@ public function insertFileUpload() } } -?> \ No newline at end of file +?> diff --git a/Services/UICore/classes/class.ilTemplate.php b/Services/UICore/classes/class.ilTemplate.php index 2656319bdff2..5c6536f6f7a5 100755 --- a/Services/UICore/classes/class.ilTemplate.php +++ b/Services/UICore/classes/class.ilTemplate.php @@ -2248,11 +2248,11 @@ function fillSideIcons() // mount webfolder if ($this->mount_webfolder != "") { - require_once('Services/WebDAV/classes/class.ilDAVServer.php'); - $davServer = ilDAVServer::getInstance(); + require_once('Services/WebDAV/classes/class.ilWebDAVUtil.php'); + $dav_util = ilWebDAVUtil::getInstance(); $a_ref_id = $this->mount_webfolder; - $a_link = $davServer->getMountURI($a_ref_id); - $a_folder = $davServer->getFolderURI($a_ref_id); + $a_link = $dav_util->getMountURI($a_ref_id); + $a_folder = $dav_util->getFolderURI($a_ref_id); $this->setCurrentBlock("mount_webfolder"); $this->setVariable("LINK_MOUNT_WEBFOLDER", $a_link); diff --git a/Services/WebDAV/README.md b/Services/WebDAV/README.md new file mode 100644 index 000000000000..05b8177629ef --- /dev/null +++ b/Services/WebDAV/README.md @@ -0,0 +1,149 @@ +# WebDAV Service +WebDAV or Web Distributed Authoring and Versioning is an extension to HTTP. This service implements a WebDAV interface to the ILIAS-Repository. Since ILIAS version 5.4, the sabreDAV library from sabre-io is used to handle the HTTP-Requests. This services implements functionality behind this requests. + +## Table of Contents +* [WebDAV Service](#webdav-service) + * [Table of Contents](#table-of-contents) + * [Introduction](#introduction) + * [A short overview to WebDAV](#a-short-overview-to-webdav) + * [How to read this documentation?](#how-to-read-this-documentation) + * [SabreDAV and its interfaces](#sabredav-and-its-interfaces) + * [SabreDAV server](#sabredav-server) + * [Virtual filesystem](#virtual-filesystem) + * [SabreDAV locks vs. ILIAS locks](#sabredav-locks-vs-ilias-locks) + * [classes/](#classes) + * [dav/](#dav) + * [ilMountPointDAV](#ilmountpointdav) + * [ilClientDAV](#ilclientdav) + * [ilObjectDAV](#ilobjectdav) + * [auth/](#auth) + * [db/](#db) + * [lock/](#lock) + * [HTTP-Methods](#http-methods) + * [propfind](#propfind) + * [get](#get) + * [put (create file)](#put-create-file) + * [mkcol](#mkcol) + * [remove](#remove) + * [move and move](#move-and-move) + * [lock / unlock](#lock--unlock) + * [Terminology](#terminology) + * [WebDAV Service](#webdav-service) + * [WebDAV request](#webdav-request) + * [SabreDAV](#sabredav) + + +## Introduction +**TL;DR** The WebDAV Service is used to add the ILIAS-Repository to your explorer like an external drive. Instead of opening a Webbrowser to browse through the ILIAS-Repositry, you can do it with any WebDAV client. For example: + +* Explorer on Windows +* Finder on Mac +* WinSCP + +But everything has its price. Since WebDAV ist just an abstraction to the ILIAS-Repositry, there are some limitations. That means, WebDAV does not replace the use of the ILIAS-Website. It is just meant for simple interaction with the repository like: + +* _Browse_ through the files and folder strcutures +* _Download_ files and folder structures +* _Upload_ files and folder structures +* _Rename_ files and folder structures +* _Cope_ files and folder structures +* _Move_ files and folder structures +* _Lock_ files and edit them + +For more about the limitations, see chapter xyz + +### A short overview to WebDAV + +**WebDAV** (short for **Web**-based **D**istributed **A**uthoring and **V**ersioning) is an extension of HTTP that allows clients to perform remote web content authoring operations. It is defined in [RFC 4918](https://tools.ietf.org/html/rfc4918). Following additional requests are defined by WebDAV (normal context = defined by wikipedias): + +* **COPY** + * Normal context: Copy resource from one URI to another + * ILIAS context: Copy object / contaienr from one container (ref_id) to another +* **LOCK** + * Normal context: Put a lock on a resource. WebDAV supports both shared and exclusive locks + * ILIAS context: Put a lock on an ILIAS object +* **MKCOL** + * Normal context: Create collections (a.k.a. a directory) + * ILIAS context: Create a container (category inside a category, folder inside other containers) +* **MOVE** + * Normal context: Move a resource from one URI to another + * ILIAS context: Move object / container from one container (ref_id) to another +* **PROPFIND** + * Normal context: Get properties, stored as XML, from a web resource. It is also overloaded to allow one to retrieve the collection structure (also known as directory hierarchy) of a remote system + * ILIAS context: Get information about objects and the structure in a container +* **PROPPATCH** + * Normal context: Change and delete multiple properties on a resource + * ILIAS context: Not used +* **UNLOCK** + * Normal context: Remove a lock from a resource + * ILIAS context: Remove a lock from an object + + +### How to read this documentation? + +There are different ways to read this documentation. Depending on which information you are looking for, there are different chapters that could be interessting to you. + +Are you interested in... + +* knowing how ILIAS interacts with sabreDAV? Then [SabreDAV and its interfaces](#sabredav-and-its-interfaces) is the right chapter for you +* the file structure of the WebDAV-Service? Then [classes/](#classes/) is the right chapter for you +* the different HTTP-Methods and how they are implemented for the WebDAV-Service in ILIAS? Then [HTTP-Methods](#http-methods) is the right chapter for you + +## SabreDAV and its interfaces + +### SabreDAV server + +### Virtual filesystem + +### SabreDAV locks vs. ILIAS locks + +## classes/ + +### dav/ +The dav-folder contains all the classes that are needed for the virtual filesystem. + +#### ilMountPointDAV + +#### ilClientDAV + +#### ilObjectDAV + + + + +### auth/ +The auth-folder contains all classes that are needed to authenticate the user in a webdav request. + +### db/ + +### lock/ + +## HTTP-Methods + +### propfind + +### get + +### put (create file) + +### mkcol + +### remove + +### move and move + +### lock / unlock + + +## Terminology + +The keywords in this documentation are defined like in [RFC 4918](https://tools.ietf.org/html/rfc4918). + +### WebDAV Service +This means the ILIAS implementation of the WebDAV interface + +### WebDAV request +All requests on webdav.php in the ILIAS-Root are handled as WebDAV requests + +### SabreDAV +Library used to handle WebDAV requests \ No newline at end of file diff --git a/Services/WebDAV/classes/Server.php b/Services/WebDAV/classes/Server.php deleted file mode 100644 index 350051d83085..000000000000 --- a/Services/WebDAV/classes/Server.php +++ /dev/null @@ -1,2029 +0,0 @@ - | -// | Christian Stocker | -// +----------------------------------------------------------------------+ -// -// $Id: Server.php,v 1.28 2005/04/05 22:51:09 hholzgra Exp $ -// - -require_once "Services/WebDAV/classes/Tools/_parse_propfind.php"; -require_once "Services/WebDAV/classes/Tools/_parse_proppatch.php"; -require_once "Services/WebDAV/classes/Tools/_parse_lockinfo.php"; - - -/** - * Virtual base class for implementing WebDAV servers - * - * WebDAV server base class, needs to be extended to do useful work - * - * @package HTTP_WebDAV_Server - * @author Hartmut Holzgraefe - * @version 0.99.1dev - */ -class HTTP_WebDAV_Server -{ - // {{{ Member Variables - - /** - * complete URI for this request - * - * @var string - */ - var $uri; - - - /** - * base URI for this request - * - * @var string - */ - var $base_uri; - - - /** - * URI path for this request - * - * @var string - */ - var $path; - - /** - * Realm string to be used in authentification popups - * - * @var string - */ - var $http_auth_realm = "PHP WebDAV"; - - /** - * String to be used in "X-Dav-Powered-By" header - * - * @var string - */ - var $dav_powered_by = ""; - - /** - * Remember parsed If: (RFC2518/9.4) header conditions - * - * @var array - */ - var $_if_header_uris = array(); - - /** - * HTTP response status/message - * - * @var string - */ - var $_http_status = "200 OK"; - - /** - * encoding of property values passed in - * - * @var string - */ - var $_prop_encoding = "utf-8"; - - // }}} - - // {{{ Constructor - - /** - * Constructor - * - * @param void - */ - private function __construct() - { - // PHP messages destroy XML output -> switch them off - //ini_set("display_errors", 0); - } - - // }}} - - // {{{ ServeRequest() - /** - * Serve WebDAV HTTP request - * - * dispatch WebDAV HTTP request to the apropriate method handler - * - * @param void - * @return void - */ - function serveRequest() - { - // default uri is the complete request uri - // FIXME: use ilHTTPS::isDetected - $uri = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:"); - $uri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; - - $this->base_uri = $uri; - $this->uri = $uri . $_SERVER[PATH_INFO]; - - // identify ourselves - if (empty($this->dav_powered_by)) { - header("X-Dav-Powered-By: PHP class: ".get_class($this)); - } else { - header("X-Dav-Powered-By: ".$this->dav_powered_by ); - } - - $this->writelog(__METHOD__.': Using uri: '.$this->uri); - - // check authentication - if (!$this->_check_auth()) { - // RFC2518 says we must use Digest instead of Basic - // but Microsoft Clients do not support Digest - // and we don't support NTLM and Kerberos - // so we are stuck with Basic here - header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"'); - - // Windows seems to require this being the last header sent - // (changed according to PECL bug #3138) - $this->http_status('401 Unauthorized'); - $this->writelog('Check auth failed'); - - return; - } - - // check - if(! $this->_check_if_header_conditions()) { - $this->writelog(__METHOD__.': Precondition failed.'); - $this->http_status("412 Precondition failed"); - return; - } - - // set path - $this->path = $this->_urldecode($_SERVER["PATH_INFO"]); - if (!strlen($this->path)) { - header("Location: ".$this->base_uri."/"); - $this->writelog('HTTP_WebDAV_Server.ServeRequest() missing path info'); - $this->path = '/'; - //exit; - } - // BEGIN WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename! - /* - if(ini_get("magic_quotes_gpc")) { - $this->path = stripslashes($this->path); - }*/ - // END PATCH WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename! - - - // detect requested method names - $method = strtolower($_SERVER["REQUEST_METHOD"]); - $wrapper = "http_".$method; - - $this->writelog(__METHOD__.': Using request method: '.$method); - - // activate HEAD emulation by GET if no HEAD method found - if ($method == "head" && !method_exists($this, "head")) - { - $method = "get"; - $this->writelog(__METHOD__.': Using head emulation by get.'); - } - - if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) - { - $this->writelog(__METHOD__.': Calling wrapper: '.$wrapper); - $this->$wrapper(); // call method by name - } - else - { // method not found/implemented - if ($_SERVER["REQUEST_METHOD"] == "LOCK") - { - $this->writelog(__METHOD__.': Method not found/implemented. Sending 412'); - $this->http_status("412 Precondition failed"); - } - else - { - $this->writelog(__METHOD__.': Method not found/implemented. Sending allowd methods'); - $this->http_status("405 Method not allowed"); - header("Allow: ".join(", ", $this->_allow())); // tell client what's allowed - } - } - } - - // }}} - - // {{{ abstract WebDAV methods - - // {{{ GET() - /** - * GET implementation - * - * overload this method to retrieve resources from your server - *
- * - * - * @abstract - * @param array &$params Array of input and output parameters - *
input
    - *
  • path - - *
- *
output
    - *
  • size - - *
- * @returns int HTTP-Statuscode - */ - - /* abstract - function GET(&$params) - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ PUT() - /** - * PUT implementation - * - * PUT implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function PUT() - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ COPY() - - /** - * COPY implementation - * - * COPY implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function COPY() - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ MOVE() - - /** - * MOVE implementation - * - * MOVE implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function MOVE() - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ DELETE() - - /** - * DELETE implementation - * - * DELETE implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function DELETE() - { - // dummy entry for PHPDoc - } - */ - // }}} - - // {{{ PROPFIND() - - /** - * PROPFIND implementation - * - * PROPFIND implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function PROPFIND() - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ PROPPATCH() - - /** - * PROPPATCH implementation - * - * PROPPATCH implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function PROPPATCH() - { - // dummy entry for PHPDoc - } - */ - // }}} - - // {{{ LOCK() - - /** - * LOCK implementation - * - * LOCK implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function LOCK() - { - // dummy entry for PHPDoc - } - */ - // }}} - - // {{{ UNLOCK() - - /** - * UNLOCK implementation - * - * UNLOCK implementation - * - * @abstract - * @param array &$params - * @returns int HTTP-Statuscode - */ - - /* abstract - function UNLOCK() - { - // dummy entry for PHPDoc - } - */ - // }}} - - // }}} - - // {{{ other abstract methods - - // {{{ check_auth() - - /** - * check authentication - * - * overload this method to retrieve and confirm authentication information - * - * @abstract - * @param string type Authentication type, e.g. "basic" or "digest" - * @param string username Transmitted username - * @param string passwort Transmitted password - * @returns bool Authentication status - */ - - /* abstract - function checkAuth($type, $username, $password) - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // {{{ checklock() - - /** - * check lock status for a resource - * - * overload this method to return shared and exclusive locks - * active for this resource - * - * @abstract - * @param string resource Resource path to check - * @returns array An array of lock entries each consisting - * of 'type' ('shared'/'exclusive'), 'token' and 'timeout' - */ - - /* abstract - function checklock($resource) - { - // dummy entry for PHPDoc - } - */ - - // }}} - - // }}} - - // {{{ WebDAV HTTP method wrappers - - // {{{ http_OPTIONS() - - /** - * OPTIONS method handler - * - * The OPTIONS method handler creates a valid OPTIONS reply - * including Dav: and Allowed: headers - * based on the implemented methods found in the actual instance - * - * @param void - * @return void - */ - function http_OPTIONS() - { - // Microsoft clients default to the Frontpage protocol - // unless we tell them to use WebDAV - header("MS-Author-Via: DAV"); - - // get allowed methods - $allow = $this->_allow(); - - // dav header - $dav = array(1); // assume we are always dav class 1 compliant - if (isset($allow['LOCK'])) { - $dav[] = 2; // dav class 2 requires that locking is supported - } - - // tell clients what we found - $this->http_status("200 OK"); - header("DAV: " .join("," , $dav)); - header("Allow: ".join(", ", $allow)); - $this->writelog(__METHOD__.': dav='.var_export($dav,true).' allow='.var_export($allow,true)); - header("Content-length: 0"); - } - - // }}} - - - // {{{ http_PROPFIND() - - /** - * PROPFIND method handler - * - * @param void - * @return void - */ - function http_PROPFIND() - { - $options = Array(); - $options["path"] = $this->path; - - // search depth from header (default is "infinity) - if (isset($_SERVER['HTTP_DEPTH'])) { - $options["depth"] = $_SERVER["HTTP_DEPTH"]; - } else { - $options["depth"] = "infinity"; - } - - // analyze request payload - $propinfo = new _parse_propfind("php://input"); - if (!$propinfo->success) { - $this->http_status("400 Error"); - return; - } - $options['props'] = $propinfo->props; - - // call user handler - $files = array(); - if (!$this->propfind($options, $files)) { - $this->http_status("404 Not Found"); - return; - } - - // collect namespaces here - $ns_hash = array(); - - // Microsoft Clients need this special namespace for date and time values - $ns_defs = "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\""; - - // now we loop over all returned file entries - foreach($files["files"] as $filekey => $file) { - - // nothing to do if no properties were returend for a file - if (!isset($file["props"]) || !is_array($file["props"])) { - continue; - } - - // now loop over all returned properties - foreach($file["props"] as $key => $prop) { - // as a convenience feature we do not require that user handlers - // restrict returned properties to the requested ones - // here we strip all unrequested entries out of the response - - switch($options['props']) { - case "all": - // nothing to remove - break; - - case "names": - // only the names of all existing properties were requested - // so we remove all values - unset($files["files"][$filekey]["props"][$key]["val"]); - break; - - default: - $found = false; - - // search property name in requested properties - foreach((array)$options["props"] as $reqprop) { - if ( $reqprop["name"] == $prop["name"] - && $reqprop["xmlns"] == $prop["ns"]) { - $found = true; - break; - } - } - - // unset property and continue with next one if not found/requested - if (!$found) { - $files["files"][$filekey]["props"][$key]=""; - continue(2); - } - break; - } - - // namespace handling - if (empty($prop["ns"])) continue; // no namespace - $ns = $prop["ns"]; - if ($ns == "DAV:") continue; // default namespace - if (isset($ns_hash[$ns])) continue; // already known - - // register namespace - $ns_name = "ns".(count($ns_hash) + 1); - $ns_hash[$ns] = $ns_name; - $ns_defs .= " xmlns:$ns_name=\"$ns\""; - } - - // we also need to add empty entries for properties that were requested - // but for which no values where returned by the user handler - if (is_array($options['props'])) { - foreach($options["props"] as $reqprop) { - if($reqprop['name']=="") continue; // skip empty entries - - $found = false; - - // check if property exists in result - foreach($file["props"] as $prop) { - if ( $reqprop["name"] == $prop["name"] - && $reqprop["xmlns"] == $prop["ns"]) { - $found = true; - break; - } - } - - if (!$found) { - if($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") { - // lockdiscovery is handled by the base class - $files["files"][$filekey]["props"][] - = $this->mkprop("DAV:", - "lockdiscovery" , - $this->lockdiscovery($files["files"][$filekey]['path'])); - } else { - // add empty value for this property - $files["files"][$filekey]["noprops"][] = - $this->mkprop($reqprop["xmlns"], $reqprop["name"], ""); - - // register property namespace if not known yet - if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) { - $ns_name = "ns".(count($ns_hash) + 1); - $ns_hash[$reqprop["xmlns"]] = $ns_name; - $ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\""; - } - } - } - } - } - } - - // now we generate the reply header ... - $this->http_status("207 Multi-Status"); - header('Content-Type: text/xml; charset="utf-8"'); - - // ... and payload - echo "\n"; - echo "\n"; - - foreach($files["files"] as $file) { - // ignore empty or incomplete entries - if(!is_array($file) || empty($file) || !isset($file["path"])) continue; - $path = $file['path']; - if(!is_string($path) || $path==="") continue; - - echo " \n"; - - // BEGIN WebDAV W. Randelshofer Don't slashify path because it confuses Mac OS X - //$href = $this->_slashify($_SERVER['SCRIPT_NAME'] . $path); - $href = $_SERVER['SCRIPT_NAME'] . $path; - //END PATCH WebDAV W. Randelshofer - - echo " $href\n"; - - // report all found properties and their values (if any) - if (isset($file["props"]) && is_array($file["props"])) { - echo " \n"; - echo " \n"; - - foreach($file["props"] as $key => $prop) { - - if (!is_array($prop)) continue; - if (!isset($prop["name"])) continue; - if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) { - // empty properties (cannot use empty() for check as "0" is a legal value here) - if($prop["ns"]=="DAV:") { - echo " \n"; - } else if(!empty($prop["ns"])) { - echo " <".$ns_hash[$prop["ns"]].":$prop[name]/>\n"; - } else { - echo " <$prop[name] xmlns=\"\"/>"; - } - } else if ($prop["ns"] == "DAV:") { - // some WebDAV properties need special treatment - switch ($prop["name"]) { - case "creationdate": - echo " " - // BEGIN WebDAV W. Randelshofer - . gmdate("Y-m-d\\TH:i:s\\Z",$prop['val']) - // . gmdate("D, d M Y H:i:s ", $prop['val']) - // END PATCH WebDAV W. Randelshofer - . "\n"; - break; - case "getlastmodified": - echo " " - . gmdate("D, d M Y H:i:s ", $prop['val']) - . "GMT\n"; - break; - case "resourcetype": - echo " \n"; - break; - case "supportedlock": - echo " $prop[val]\n"; - break; - case "lockdiscovery": - echo " \n"; - echo $prop["val"]; - echo " \n"; - break; - default: - echo " " - . $this->_prop_encode(htmlspecialchars($prop['val'])) - . "\n"; - break; - } - } else { - // properties from namespaces != "DAV:" or without any namespace - if ($prop["ns"]) { - echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>" - . $this->_prop_encode(htmlspecialchars($prop['val'])) - . "\n"; - } else { - echo " <$prop[name] xmlns=\"\">" - . $this->_prop_encode(htmlspecialchars($prop['val'])) - . "\n"; - } - } - } - - echo " \n"; - echo " HTTP/1.1 200 OK\n"; - echo " \n"; - } - - // now report all properties requested but not found - if (isset($file["noprops"])) { - echo " \n"; - echo " \n"; - - foreach($file["noprops"] as $key => $prop) { - if ($prop["ns"] == "DAV:") { - echo " \n"; - } else if ($prop["ns"] == "") { - echo " <$prop[name] xmlns=\"\"/>\n"; - } else { - echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n"; - } - } - - echo " \n"; - echo " HTTP/1.1 404 Not Found\n"; - echo " \n"; - } - - echo " \n"; - } - - echo "\n"; - } - - - // }}} - - // {{{ http_PROPPATCH() - - /** - * PROPPATCH method handler - * - * @param void - * @return void - */ - function http_PROPPATCH() - { - if($this->_check_lock_status($this->path)) { - $options = Array(); - $options["path"] = $this->path; - - $propinfo = new _parse_proppatch("php://input"); - - if (!$propinfo->success) { - $this->http_status("400 Error"); - return; - } - - $options['props'] = $propinfo->props; - - $responsedescr = $this->proppatch($options); - - $this->http_status("207 Multi-Status"); - header('Content-Type: text/xml; charset="utf-8"'); - - echo "\n"; - - echo "\n"; - echo " \n"; - echo " ".$this->_urlencode($_SERVER["SCRIPT_NAME"].$this->path)."\n"; - - foreach($options["props"] as $prop) { - echo " \n"; - echo " <$prop[name] xmlns=\"$prop[ns]\"/>\n"; - echo " HTTP/1.1 $prop[status]\n"; - echo " \n"; - } - - if ($responsedescr) { - echo " ". - $this->_prop_encode(htmlspecialchars($responsedescr)). - "\n"; - } - - echo " \n"; - echo "\n"; - } else { - $this->http_status("423 Locked"); - } - } - - // }}} - - - // {{{ http_MKCOL() - - /** - * MKCOL method handler - * - * @param void - * @return void - */ - function http_MKCOL() - { - $options = Array(); - $options["path"] = $this->path; - - $stat = $this->mkcol($options); - - $this->http_status($stat); - } - - // }}} - - - // {{{ http_GET() - - /** - * GET method handler - * - * @param void - * @returns void - */ - function http_GET() - { - // TODO check for invalid stream - $options = Array(); - $options["path"] = $this->path; - - $this->_get_ranges($options); - - if (true === ($status = $this->get($options))) { - if (!headers_sent()) { - $status = "200 OK"; - - if (!isset($options['mimetype'])) { - $options['mimetype'] = "application/octet-stream"; - } - header("Content-type: $options[mimetype]"); - - if (isset($options['mtime'])) { - header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT"); - } - - if (isset($options['stream'])) { - // GET handler returned a stream - if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) { - // partial request and stream is seekable - - if (count($options['ranges']) === 1) { - $range = $options['ranges'][0]; - - if (isset($range['start'])) { - fseek($options['stream'], $range['start'], SEEK_SET); - if (feof($options['stream'])) { - $this->http_status("416 Requested range not satisfiable"); - exit; - } - - if (isset($range['end'])) { - $size = $range['end']-$range['start']+1; - $this->http_status("206 partial"); - header("Content-length: $size"); - header("Content-range: $range[start]-$range[end]/" - . (isset($options['size']) ? $options['size'] : "*")); - while ($size && !feof($options['stream'])) { - $buffer = fread($options['stream'], 4096); - $size -= strlen($buffer); - echo $buffer; - } - } else { - $this->http_status("206 partial"); - if (isset($options['size'])) { - header("Content-length: ".($options['size'] - $range['start'])); - header("Content-range: $start-$end/" - . (isset($options['size']) ? $options['size'] : "*")); - } - fpassthru($options['stream']); - } - } else { - header("Content-length: ".$range['last']); - fseek($options['stream'], -$range['last'], SEEK_END); - fpassthru($options['stream']); - } - } else { - $this->_multipart_byterange_header(); // init multipart - foreach ($options['ranges'] as $range) { - // TODO what if size unknown? 500? - if (isset($range['start'])) { - $from = $range['start']; - $to = !empty($range['end']) ? $range['end'] : $options['size']-1; - } else { - $from = $options['size'] - $range['last']-1; - $to = $options['size'] -1; - } - $total = isset($options['size']) ? $options['size'] : "*"; - $size = $to - $from + 1; - $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total); - - - fseek($options['stream'], $start, SEEK_SET); - while ($size && !feof($options['stream'])) { - $buffer = fread($options['stream'], 4096); - $size -= strlen($buffer); - echo $buffer; - } - } - $this->_multipart_byterange_header(); // end multipart - } - } else { - // normal request or stream isn't seekable, return full content - if (isset($options['size'])) { - header("Content-length: ".$options['size']); - } - - // BEGIN WebDAV W. Randelshofer - // fpassthru apparently only delivers up to 2 million bytes. - // use fread instead - //fpassthru($options['stream']); - while (! feof($options['stream'])) { - $buffer = fread($options['stream'], 4096); - echo $buffer; - } - // END PATCH WebDAV W. Randelshofer - - return; // no more headers - } - } elseif (isset($options['data'])) { - if (is_array($options['data'])) { - // reply to partial request - } else { - header("Content-length: ".strlen($options['data'])); - echo $options['data']; - } - } - } - } - - if (false === $status) { - // BEGIN WebDAV Randelshofer - $status = '404 Not Found'; - //$this->http_status("404 not found"); - // END PATCH WebDAV Randelshofer - } - - if (!headers_sent()) { - // TODO: check setting of headers in various code pathes above - $this->http_status("$status"); - } - } - - - /** - * parse HTTP Range: header - * - * @param array options array to store result in - * @return void - */ - function _get_ranges(&$options) - { - // process Range: header if present - if (isset($_SERVER['HTTP_RANGE'])) { - - // we only support standard "bytes" range specifications for now - if (preg_match("/bytes[[:space:]]*=[[:space:]]*(.+)/", $_SERVER['HTTP_RANGE'], $matches)) { - $options["ranges"] = array(); - - // ranges are comma separated - foreach (explode(",", $matches[1]) as $range) { - // ranges are either from-to pairs or just end positions - list($start, $end) = explode("-", $range); - $options["ranges"][] = ($start==="") - ? array("last"=>$end) - : array("start"=>$start, "end"=>$end); - } - } - } - } - - /** - * generate separator headers for multipart response - * - * first and last call happen without parameters to generate - * the initial header and closing sequence, all calls inbetween - * require content mimetype, start and end byte position and - * optionaly the total byte length of the requested resource - * - * @param string mimetype - * @param int start byte position - * @param int end byte position - * @param int total resource byte size - */ - function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false) - { - if ($mimetype === false) { - if (!isset($this->multipart_separator)) { - // initial - - // a little naive, this sequence *might* be part of the content - // but it's really not likely and rather expensive to check - $this->multipart_separator = "SEPARATOR_".md5(microtime()); - - // generate HTTP header - header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator); - } else { - // final - - // generate closing multipart sequence - echo "\n--{$this->multipart_separator}--"; - } - } else { - // generate separator and header for next part - echo "\n--{$this->multipart_separator}\n"; - echo "Content-type: $mimetype\n"; - echo "Content-range: $from-$to/". ($total === false ? "*" : $total); - echo "\n\n"; - } - } - - - - // }}} - - // {{{ http_HEAD() - - /** - * HEAD method handler - * - * @param void - * @return void - */ - function http_HEAD() - { - $status = false; - $options = Array(); - $options["path"] = $this->path; - - if (method_exists($this, "HEAD")) { - $status = $this->head($options); - } else if (method_exists($this, "GET")) { - ob_start(); - $status = $this->GET($options); - ob_end_clean(); - } - - if($status===true) $status = "200 OK"; - if($status===false) $status = "404 Not found"; - - $this->http_status($status); - } - - // }}} - - // {{{ http_PUT() - - /** - * PUT method handler - * - * @param void - * @return void - */ - function http_PUT() - { - if ($this->_check_lock_status($this->path)) { - $options = Array(); - $options["path"] = $this->path; - $options["content_length"] = $_SERVER["CONTENT_LENGTH"]; - - // get the Content-type - if (isset($_SERVER["CONTENT_TYPE"])) { - // for now we do not support any sort of multipart requests - if (!strncmp($_SERVER["CONTENT_TYPE"], "multipart/", 10)) { - $this->http_status("501 not implemented"); - echo "The service does not support mulipart PUT requests"; - return; - } - $options["content_type"] = $_SERVER["CONTENT_TYPE"]; - } else { - // default content type if none given - $options["content_type"] = "application/octet-stream"; - } - - /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT - ignore any Content-* (e.g. Content-Range) headers that it - does not understand or implement and MUST return a 501 - (Not Implemented) response in such cases." - */ - foreach ($_SERVER as $key => $val) { - if (strncmp($key, "HTTP_CONTENT", 11)) continue; - switch ($key) { - case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11 - // TODO support this if ext/zlib filters are available - $this->http_status("501 not implemented"); - echo "The service does not support '$val' content encoding"; - return; - - case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12 - // we assume it is not critical if this one is ignored - // in the actual PUT implementation ... - $options["content_language"] = $value; - break; - - case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14 - /* The meaning of the Content-Location header in PUT - or POST requests is undefined; servers are free - to ignore it in those cases. */ - break; - - case 'HTTP_CONTENT_RANGE': // RFC 2616 14.16 - // single byte range requests are supported - // the header format is also specified in RFC 2616 14.16 - // TODO we have to ensure that implementations support this or send 501 instead - if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $value, $matches)) { - $this->http_status("400 bad request"); - echo "The service does only support single byte ranges"; - return; - } - - $range = array("start"=>$matches[1], "end"=>$matches[2]); - if (is_numeric($matches[3])) { - $range["total_length"] = $matches[3]; - } - $option["ranges"][] = $range; - - // TODO make sure the implementation supports partial PUT - // this has to be done in advance to avoid data being overwritten - // on implementations that do not support this ... - break; - - case 'HTTP_CONTENT_MD5': // RFC 2616 14.15 - // TODO: maybe we can just pretend here? - $this->http_status("501 not implemented"); - echo "The service does not support content MD5 checksum verification"; - return; - - case 'HTTP_CONTENT_LENGTH': - // defined on IIS and has the same value as CONTENT_LENGTH - break; - - default: - // any other unknown Content-* headers - $this->http_status("501 not implemented"); - echo "The service does not support '$key'"; - return; - } - } - - $options["stream"] = fopen("php://input", "r"); - - $stat = $this->PUT($options); - - if ($stat == false) { - $stat = "403 Forbidden"; - } else if (is_resource($stat) && get_resource_type($stat) == "stream") { - $stream = $stat; - - $stat = $options["new"] ? "201 Created" : "204 No Content"; - - if (!empty($options["ranges"])) { - // TODO multipart support is missing (see also above) - if (0 == fseek($stream, $range[0]["start"], SEEK_SET)) { - $length = $range[0]["end"]-$range[0]["start"]+1; - if (!fwrite($stream, fread($options["stream"], $length))) { - $stat = "403 Forbidden"; - } - } else { - $stat = "403 Forbidden"; - } - } else { - while (!feof($options["stream"])) { - // BEGIN WebDAV W. Randelshofer explicitly compare with false. - if (false === ($written = fwrite($stream, fread($options["stream"], 4096)))) { - // END WebDAV W. Randelshofer explicitly compare with false. - $stat = "403 Forbidden"; - break; - } - $count += $written; - } - } - - fclose($stream); - //$this->writelog('PUT wrote '.$written.' bytes'); - // BEGIN WebDAV W. Randelshofer finish the put-operation - $this->PUTfinished($options); - // END WebDAV W. Randelshofer finish the put-operation - } - - $this->http_status($stat); - } else { - $this->http_status("423 Locked"); - } - } - - // }}} - - - // {{{ http_DELETE() - - /** - * DELETE method handler - * - * @param void - * @return void - */ - function http_DELETE() - { - // check RFC 2518 Section 9.2, last paragraph - if (isset($_SERVER["HTTP_DEPTH"])) { - if ($_SERVER["HTTP_DEPTH"] != "infinity") { - $this->http_status("400 Bad Request"); - return; - } - } - - // check lock status - if ($this->_check_lock_status($this->path)) { - // ok, proceed - $options = Array(); - $options["path"] = $this->path; - - $stat = $this->delete($options); - - $this->http_status($stat); - } else { - // sorry, its locked - $this->http_status("423 Locked"); - } - } - - // }}} - - // {{{ http_COPY() - - /** - * COPY method handler - * - * @param void - * @return void - */ - function http_COPY() - { - // no need to check source lock status here - // destination lock status is always checked by the helper method - $this->_copymove("copy"); - } - - // }}} - - // {{{ http_MOVE() - - /** - * MOVE method handler - * - * @param void - * @return void - */ - function http_MOVE() - { - //$this->writelog('MOVE()'); - if ($this->_check_lock_status($this->path)) { - // destination lock status is always checked by the helper method - $this->_copymove("move"); - } else { - //$this->writelog('MOVE():423 Locked'); - $this->http_status("423 Locked"); - } - } - - // }}} - - - // {{{ http_LOCK() - - /** - * LOCK method handler - * - * @param void - * @return void - */ - function http_LOCK() - { - $options = Array(); - $options["path"] = $this->path; - - if (isset($_SERVER['HTTP_DEPTH'])) { - $options["depth"] = $_SERVER["HTTP_DEPTH"]; - } else { - $options["depth"] = "infinity"; - } - - if (isset($_SERVER["HTTP_TIMEOUT"])) { - $options["timeout"] = explode(",", $_SERVER["HTTP_TIMEOUT"]); - } - - if(empty($_SERVER['CONTENT_LENGTH']) && !empty($_SERVER['HTTP_IF'])) { - // check if locking is possible - if(!$this->_check_lock_status($this->path)) { - $this->http_status("423 Locked"); - return; - } - - // refresh lock - $options["update"] = substr($_SERVER['HTTP_IF'], 2, -2); - $stat = $this->lock($options); - } else { - // extract lock request information from request XML payload - $lockinfo = new _parse_lockinfo("php://input"); - if (!$lockinfo->success) { - $this->http_status("400 bad request"); - } - - // check if locking is possible - if(!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) { - $this->http_status("423 Locked"); - return; - } - - // new lock - $options["scope"] = $lockinfo->lockscope; - $options["type"] = $lockinfo->locktype; - $options["owner"] = $lockinfo->owner; - - $options["locktoken"] = $this->_new_locktoken(); - - $stat = $this->lock($options); - } - - if(is_bool($stat)) { - $http_stat = $stat ? "200 OK" : "423 Locked"; - } else { - $http_stat = $stat; - } - - $this->http_status($http_stat); - - if ($http_stat{0} == 2) { // 2xx states are ok - if($options["timeout"]) { - // more than a million is considered an absolute timestamp - // less is more likely a relative value - if($options["timeout"]>1000000) { - $timeout = "Second-".($options['timeout']-time()); - } else { - $timeout = "Second-$options[timeout]"; - } - } else { - $timeout = "Infinite"; - } - /* - $this->writelog( - 'Content-Type: text/xml; charset="utf-8"' - ."Lock-Token: <$options[locktoken]>" - . "\n" - . "\n" - . " \n" - . " \n" - . " \n" - . " \n" - . " $options[depth]\n" - . " $options[owner]\n" - . " $timeout\n" - . " $options[locktoken]\n" - . " \n" - . " \n" - . "\n\n" - );*/ - header('Content-Type: text/xml; charset="utf-8"'); - header("Lock-Token: <$options[locktoken]>"); - echo "\n"; - echo "\n"; - echo " \n"; - echo " \n"; - echo " \n"; - echo " \n"; - echo " $options[depth]\n"; - echo " $options[owner]\n"; - echo " $timeout\n"; - echo " $options[locktoken]\n"; - echo " \n"; - echo " \n"; - echo "\n\n"; - } - } - - - // }}} - - // {{{ http_UNLOCK() - - /** - * UNLOCK method handler - * - * @param void - * @return void - */ - function http_UNLOCK() - { - $options = Array(); - $options["path"] = $this->path; - - if (isset($_SERVER['HTTP_DEPTH'])) { - $options["depth"] = $_SERVER["HTTP_DEPTH"]; - } else { - $options["depth"] = "infinity"; - } - - // strip surrounding <> - $options["token"] = substr(trim($_SERVER["HTTP_LOCK_TOKEN"]), 1, -1); -//$this->writelog('http_UNLOCK HTTP_LOCK_TOKEN='.$_SERVER["HTTP_LOCK_TOKEN"]); - // call user method - $stat = $this->unlock($options); - - $this->http_status($stat); - } - - // }}} - - // }}} - - // {{{ _copymove() - - function _copymove($what) - { - //$this->writelog('_copymove('.$what.')'); - $options = Array(); - $options["path"] = $this->path; - - if (isset($_SERVER["HTTP_DEPTH"])) { - $options["depth"] = $_SERVER["HTTP_DEPTH"]; - } else { - $options["depth"] = "infinity"; - } -//$this->writelog('_copymove dest='.$_SERVER["HTTP_DESTINATION"]); - extract(parse_url($_SERVER["HTTP_DESTINATION"])); - // BEGIN WebDAV: decode path (bereits in PEAR CVS gefixt) - // We must decode the target path too. - $path = $this->_urldecode($path); - // END Patch WebDAV: decode path - $http_host = $host; - if (isset($port) && $port != 80) - $http_host.= ":$port"; - - list($http_header_host,$http_header_port) = explode(":",$_SERVER["HTTP_HOST"]); - if (isset($http_header_port) && $http_header_port != 80) { - $http_header_host .= ":".$http_header_port; - } - - if ($http_host == $http_header_host && - !strncmp($_SERVER["SCRIPT_NAME"], $path, - strlen($_SERVER["SCRIPT_NAME"]))) { - $options["dest"] = substr($path, strlen($_SERVER["SCRIPT_NAME"])); - //$this->writelog('_copymove() dest='.$options['dest']); - if (!$this->_check_lock_status($options["dest"])) { - //$this->writelog('_copymove():423 Locked'); - $this->http_status("423 Locked"); - return; - } - //$this->writelog('_copymove() ...'); - - } else { - $options["dest_url"] = $_SERVER["HTTP_DESTINATION"]; - } - - // see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3 - if (isset($_SERVER["HTTP_OVERWRITE"])) { - $options["overwrite"] = $_SERVER["HTTP_OVERWRITE"] == "T"; - } else { - $options["overwrite"] = true; - } - - $stat = $this->$what($options); - $this->http_status($stat); - } - - // }}} - - // {{{ _allow() - - /** - * check for implemented HTTP methods - * - * @param void - * @return array something - */ - function _allow() - { - // OPTIONS is always there - $allow = array("OPTIONS" =>"OPTIONS"); - - // all other METHODS need both a http_method() wrapper - // and a method() implementation - // the base class supplies wrappers only - foreach(get_class_methods($this) as $method) { - if (!strncmp("http_", $method, 5)) { - $method = strtoupper(substr($method, 5)); - if (method_exists($this, $method)) { - $allow[$method] = $method; - } - } - } - - // we can emulate a missing HEAD implemetation using GET - if (isset($allow["GET"])) - $allow["HEAD"] = "HEAD"; - - // no LOCK without checklok() - if (!method_exists($this, "checklock")) { - unset($allow["LOCK"]); - unset($allow["UNLOCK"]); - } - - return $allow; - } - - // }}} - - /** - * helper for property element creation - * - * @param string XML namespace (optional) - * @param string property name - * @param string property value - * @return array property array - */ - function mkprop() - { - $args = func_get_args(); - if (count($args) == 3) { - return array("ns" => $args[0], - "name" => $args[1], - "val" => $args[2]); - } else { - return array("ns" => "DAV:", - "name" => $args[0], - "val" => $args[1]); - } - } - - // {{{ _check_auth - - /** - * check authentication if check is implemented - * - * @param void - * @return bool true if authentication succeded or not necessary - */ - function _check_auth() - { - if (method_exists($this, "checkAuth")) { - // PEAR style method name - return $this->checkAuth(@$_SERVER["AUTH_TYPE"], - @$_SERVER["PHP_AUTH_USER"], - @$_SERVER["PHP_AUTH_PW"]); - } else if (method_exists($this, "check_auth")) { - // old (pre 1.0) method name - return $this->check_auth(@$_SERVER["AUTH_TYPE"], - @$_SERVER["PHP_AUTH_USER"], - @$_SERVER["PHP_AUTH_PW"]); - } else { - // no method found -> no authentication required - return true; - } - } - - // }}} - - // {{{ UUID stuff - - /** - * generate Unique Universal IDentifier for lock token - * - * @param void - * @return string a new UUID - */ - function _new_uuid() - { - // use uuid extension from PECL if available - if (function_exists("uuid_create")) { - return uuid_create(); - } - - // fallback - $uuid = md5(microtime().getmypid()); // this should be random enough for now - - // set variant and version fields for 'true' random uuid - $uuid{12} = "4"; - $n = 8 + (ord($uuid{16}) & 3); - $hex = "0123456789abcdef"; - $uuid{16} = $hex{$n}; - - // return formated uuid - return substr($uuid, 0, 8)."-" - . substr($uuid, 8, 4)."-" - . substr($uuid, 12, 4)."-" - . substr($uuid, 16, 4)."-" - . substr($uuid, 20); - } - - /** - * create a new opaque lock token as defined in RFC2518 - * - * @param void - * @return string new RFC2518 opaque lock token - */ - function _new_locktoken() - { - return "opaquelocktoken:".$this->_new_uuid(); - } - - // }}} - - // {{{ WebDAV If: header parsing - - /** - * - * - * @param string header string to parse - * @param int current parsing position - * @return array next token (type and value) - */ - function _if_header_lexer($string, &$pos) - { - // skip whitespace - while (ctype_space($string{$pos})) { - ++$pos; - } - - // already at end of string? - if (strlen($string) <= $pos) { - return false; - } - - // get next character - $c = $string{$pos++}; - - // now it depends on what we found - switch ($c) { - case "<": - // URIs are enclosed in <...> - $pos2 = strpos($string, ">", $pos); - $uri = substr($string, $pos, $pos2 - $pos); - $pos = $pos2 + 1; - return array("URI", $uri); - - case "[": - //Etags are enclosed in [...] - if ($string{$pos} == "W") { - $type = "ETAG_WEAK"; - $pos += 2; - } else { - $type = "ETAG_STRONG"; - } - $pos2 = strpos($string, "]", $pos); - $etag = substr($string, $pos + 1, $pos2 - $pos - 2); - $pos = $pos2 + 1; - return array($type, $etag); - - case "N": - // "N" indicates negation - $pos += 2; - return array("NOT", "Not"); - - default: - // anything else is passed verbatim char by char - return array("CHAR", $c); - } - } - - /** - * parse If: header - * - * @param string header string - * @return array URIs and their conditions - */ - function _if_header_parser($str) - { - $pos = 0; - $len = strlen($str); - - $uris = array(); - - // parser loop - while ($pos < $len) { - // get next token - $token = $this->_if_header_lexer($str, $pos); - - // check for URI - if ($token[0] == "URI") { - $uri = $token[1]; // remember URI - $token = $this->_if_header_lexer($str, $pos); // get next token - } else { - $uri = ""; - } - - // sanity check - if ($token[0] != "CHAR" || $token[1] != "(") { - return false; - } - - $list = array(); - $level = 1; - $not = ""; - while ($level) { - $token = $this->_if_header_lexer($str, $pos); - if ($token[0] == "NOT") { - $not = "!"; - continue; - } - switch ($token[0]) { - case "CHAR": - switch ($token[1]) { - case "(": - $level++; - break; - case ")": - $level--; - break; - default: - return false; - } - break; - - case "URI": - $list[] = $not."<$token[1]>"; - break; - - case "ETAG_WEAK": - $list[] = $not."[W/'$token[1]']>"; - break; - - case "ETAG_STRONG": - $list[] = $not."['$token[1]']>"; - break; - - default: - return false; - } - $not = ""; - } - - if (@is_array($uris[$uri])) { - $uris[$uri] = array_merge($uris[$uri],$list); - } else { - $uris[$uri] = $list; - } - } - - return $uris; - } - - /** - * check if conditions from "If:" headers are meat - * - * the "If:" header is an extension to HTTP/1.1 - * defined in RFC 2518 section 9.4 - * - * @param void - * @return void - */ - function _check_if_header_conditions() - { - if (isset($_SERVER["HTTP_IF"])) { - $this->_if_header_uris = - $this->_if_header_parser($_SERVER["HTTP_IF"]); - - foreach($this->_if_header_uris as $uri => $conditions) { - if ($uri == "") { - $uri = $this->uri; - } - // all must match - $state = true; - foreach($conditions as $condition) { - // lock tokens may be free form (RFC2518 6.3) - // but if opaquelocktokens are used (RFC2518 6.4) - // we have to check the format (litmus tests this) - if (!strncmp($condition, "$/", $condition)) { - return false; - } - } - if (!$this->_check_uri_condition($uri, $condition)) { - $state = false; - break; - } - } - - // any match is ok - if ($state == true) { - return true; - } - } - return false; - } - return true; - } - - /** - * Check a single URI condition parsed from an if-header - * - * Check a single URI condition parsed from an if-header - * - * @abstract - * @param string $uri URI to check - * @param string $condition Condition to check for this URI - * @returns bool Condition check result - */ - function _check_uri_condition($uri, $condition) - { - // not really implemented here, - // implementations must override - return true; - } - - - /** - * - * - * @param string path of resource to check - * @param bool exclusive lock? - */ - function _check_lock_status($path, $exclusive_only = false) - { - // FIXME depth -> ignored for now - if (method_exists($this, "checkLock")) { - // is locked? - $lock = $this->checkLock($path); - - // ... and lock is not owned? - if (is_array($lock) && count($lock)) { - // FIXME doesn't check uri restrictions yet - if (!strstr($_SERVER["HTTP_IF"], $lock["token"])) { - if (!$exclusive_only || ($lock["scope"] !== "shared")) - return false; - } - } - } - return true; - } - - - // }}} - - - /** - * Generate lockdiscovery reply from checklock() result - * - * @param string resource path to check - * @return string lockdiscovery response - */ - function lockdiscovery($path) - { - // no lock support without checklock() method - if (!method_exists($this, "checklock")) { - return ""; - } - - // collect response here - $activelocks = ""; - - // get checklock() reply - $lock = $this->checklock($path); - - // generate block for returned data - if (is_array($lock) && count($lock)) { - // check for 'timeout' or 'expires' - if (!empty($lock["expires"])) { - $timeout = "Second-".($lock["expires"] - time()); - } else if (!empty($lock["timeout"])) { - $timeout = "Second-$lock[timeout]"; - } else { - $timeout = "Infinite"; - } - - // genreate response block - $activelocks.= " - - - - $lock[depth] - $lock[owner] - $timeout - $lock[token] - - "; - } - //$this->writelog('lockdiscovery('.$path.'):'.$activeclocks); - - // return generated response - return $activelocks; - } - - /** - * set HTTP return status and mirror it in a private header - * - * @param string status code and message - * @return void - */ - function http_status($status) - { - // simplified success case - if($status === true) { - $status = "200 OK"; - } - //$this->writelog('http_status('.$status.')'); - - // remember status - $this->_http_status = $status; - - // generate HTTP status response - header("HTTP/1.1 $status"); - header("X-WebDAV-Status: $status", true); - } - - /** - * private minimalistic version of PHP urlencode() - * - * only blanks and XML special chars must be encoded here - * full urlencode() encoding confuses some clients ... - * - * @param string URL to encode - * @return string encoded URL - */ - function _urlencode($url) - { - return strtr($url, array(" "=>"%20", - "&"=>"%26", - "<"=>"%3C", - ">"=>"%3E", - )); - } - - /** - * private version of PHP urldecode - * - * not really needed but added for completenes - * - * @param string URL to decode - * @return string decoded URL - */ - function _urldecode($path) - { - // BEGIN WebDAV - // urldecode wrongly replaces '+' characters by ' ' characters. - // We replace '+' into '%2b' before passing the path through urldecode. - //return urldecode($path); - $result =& urldecode(str_replace('+','%2b',$path)); - //$this->writelog('_urldecode('.$path.'):'.$result); - return $result; - // END PATCH WebDAV - } - - /** - * UTF-8 encode property values if not already done so - * - * @param string text to encode - * @return string utf-8 encoded text - */ - function _prop_encode($text) - { - switch (strtolower($this->_prop_encoding)) { - case "utf-8": - return $text; - case "iso-8859-1": - case "iso-8859-15": - case "latin-1": - default: - return utf8_encode($text); - } - } - - /** - * Slashify - make sure path ends in a slash - * - * @param string directory path - * @returns string directory path wiht trailing slash - */ - function _slashify($path) { - if ($path[strlen($path)-1] != '/') { - $path = $path."/"; - } - return $path; - } -// BEGIN WebDAV - /** - * Writes a message to the logfile., - * - * @param message String. - * @return void. - */ - private function writelog($message) - { - global $DIC; - $log = $DIC['log']; - $ilUser = $DIC['ilUser']; - - $log->write( - $ilUser->getLogin() - .' DAV Server.'.str_replace("\n",";",$message) - ); - } -// END PATCH WebDAV -} -?> diff --git a/Services/WebDAV/classes/Tools/_parse_lockinfo.php b/Services/WebDAV/classes/Tools/_parse_lockinfo.php deleted file mode 100644 index 21a0af8f7d5e..000000000000 --- a/Services/WebDAV/classes/Tools/_parse_lockinfo.php +++ /dev/null @@ -1,237 +0,0 @@ - | -// | Christian Stocker | -// +----------------------------------------------------------------------+ -// -// $Id: _parse_lockinfo.php,v 1.2 2004/01/05 12:32:40 hholzgra Exp $ -// - -/** - * helper class for parsing LOCK request bodies - * - * @package HTTP_WebDAV_Server - * @author Hartmut Holzgraefe - * @version 0.99.1dev - */ -class _parse_lockinfo -{ - /** - * success state flag - * - * @var bool - * @access public - */ - var $success = false; - - /** - * lock type, currently only "write" - * - * @var string - * @access public - */ - var $locktype = ""; - - /** - * lock scope, "shared" or "exclusive" - * - * @var string - * @access public - */ - var $lockscope = ""; - - /** - * lock owner information - * - * @var string - * @access public - */ - var $owner = ""; - - /** - * flag that is set during lock owner read - * - * @var bool - * @access private - */ - var $collect_owner = false; - - /** - * constructor - * - * @param string path of stream to read - * @access public - */ - function __construct($path) - { - // we assume success unless problems occur - $this->success = true; - - // remember if any input was parsed - $had_input = false; - - // open stream - $f_in = fopen($path, "r"); - if (!$f_in) { - $this->success = false; - return; - } - - // create namespace aware parser - $xml_parser = xml_parser_create_ns("UTF-8", " "); - - // set tag and data handlers - xml_set_element_handler($xml_parser, - array(&$this, "_startElement"), - array(&$this, "_endElement")); - xml_set_character_data_handler($xml_parser, - array(&$this, "_data")); - - // we want a case sensitive parser - xml_parser_set_option($xml_parser, - XML_OPTION_CASE_FOLDING, false); - - // parse input - while($this->success && !feof($f_in)) { - $line = fgets($f_in); - if (is_string($line)) { - $had_input = true; - $this->success &= xml_parse($xml_parser, $line, false); - } - } - - // finish parsing - if($had_input) { - $this->success &= xml_parse($xml_parser, "", true); - } - - // check if required tags where found - $this->success &= !empty($this->locktype); - $this->success &= !empty($this->lockscope); - - // free parser resource - xml_parser_free($xml_parser); - - // close input stream - fclose($f_in); - } - - - /** - * tag start handler - * - * @param resource parser - * @param string tag name - * @param array tag attributes - * @return void - * @access private - */ - function _startElement($parser, $name, $attrs) - { - // namespace handling - if (strstr($name, " ")) { - list($ns, $tag) = explode(" ", $name); - } else { - $ns = ""; - $tag = $name; - } - - - if ($this->collect_owner) { - // everything within the tag needs to be collected - $ns_short = ""; - $ns_attr = ""; - if ($ns) { - if ($ns == "DAV:") { - $ns_short = "D:"; - } else { - $ns_attr = " xmlns='$ns'"; - } - } - $this->owner .= "<$ns_short$tag$ns_attr>"; - } else if ($ns == "DAV:") { - // parse only the essential tags - switch ($tag) { - case "write": - $this->locktype = $tag; - break; - case "exclusive": - case "shared": - $this->lockscope = $tag; - break; - case "owner": - $this->collect_owner = true; - break; - } - } - } - - /** - * data handler - * - * @param resource parser - * @param string data - * @return void - * @access private - */ - function _data($parser, $data) - { - // only the tag has data content - if ($this->collect_owner) { - $this->owner .= $data; - } - } - - /** - * tag end handler - * - * @param resource parser - * @param string tag name - * @return void - * @access private - */ - function _endElement($parser, $name) - { - // namespace handling - if (strstr($name, " ")) { - list($ns, $tag) = explode(" ", $name); - } else { - $ns = ""; - $tag = $name; - } - - // finished? - if (($ns == "DAV:") && ($tag == "owner")) { - $this->collect_owner = false; - } - - // within we have to collect everything - if ($this->collect_owner) { - $ns_short = ""; - $ns_attr = ""; - if ($ns) { - if ($ns == "DAV:") { - $ns_short = "D:"; - } else { - $ns_attr = " xmlns='$ns'"; - } - } - $this->owner .= ""; - } - } -} - -?> \ No newline at end of file diff --git a/Services/WebDAV/classes/Tools/_parse_propfind.php b/Services/WebDAV/classes/Tools/_parse_propfind.php deleted file mode 100644 index 077d4b437572..000000000000 --- a/Services/WebDAV/classes/Tools/_parse_propfind.php +++ /dev/null @@ -1,178 +0,0 @@ - | -// | Christian Stocker | -// +----------------------------------------------------------------------+ -// -// $Id: _parse_propfind.php,v 1.2 2004/01/05 12:33:22 hholzgra Exp $ -// - -/** - * helper class for parsing PROPFIND request bodies - * - * @package HTTP_WebDAV_Server - * @author Hartmut Holzgraefe - * @version 0.99.1dev - */ -class _parse_propfind -{ - /** - * success state flag - * - * @var bool - * @access public - */ - var $success = false; - - /** - * found properties are collected here - * - * @var array - * @access public - */ - var $props = false; - - /** - * internal tag nesting depth counter - * - * @var int - * @access private - */ - var $depth = 0; - - - /** - * constructor - * - * @access public - */ - function __construct($path) - { - // success state flag - $this->success = true; - - // property storage array - $this->props = array(); - - // internal tag depth counter - $this->depth = 0; - - // remember if any input was parsed - $had_input = false; - - // open input stream - $f_in = fopen($path, "r"); - if (!$f_in) { - $this->success = false; - return; - } - - // create XML parser - $xml_parser = xml_parser_create_ns("UTF-8", " "); - - // set tag and data handlers - xml_set_element_handler($xml_parser, - array(&$this, "_startElement"), - array(&$this, "_endElement")); - - // we want a case sensitive parser - xml_parser_set_option($xml_parser, - XML_OPTION_CASE_FOLDING, false); - - - // parse input - while($this->success && !feof($f_in)) { - $line = fgets($f_in); - if (is_string($line)) { - $had_input = true; - $this->success &= xml_parse($xml_parser, $line, false); - } - } - - // finish parsing - if($had_input) { - $this->success &= xml_parse($xml_parser, "", true); - } - - // free parser - xml_parser_free($xml_parser); - - // close input stream - fclose($f_in); - - // if no input was parsed it was a request - if(!count($this->props)) $this->props = "all"; // default - } - - - /** - * start tag handler - * - * @access private - * @param resource parser - * @param string tag name - * @param array tag attributes - */ - function _startElement($parser, $name, $attrs) - { - // name space handling - if (strstr($name, " ")) { - list($ns, $tag) = explode(" ", $name); - if ($ns == "") - $this->success = false; - } else { - $ns = ""; - $tag = $name; - } - - // special tags at level 1: and - if ($this->depth == 1) { - if ($tag == "allprop") - $this->props = "all"; - - if ($tag == "propname") - $this->props = "names"; - } - - // requested properties are found at level 2 - if ($this->depth == 2) { - $prop = array("name" => $tag); - if ($ns) - $prop["xmlns"] = $ns; - $this->props[] = $prop; - } - - // increment depth count - $this->depth++; - } - - - /** - * end tag handler - * - * @access private - * @param resource parser - * @param string tag name - */ - function _endElement($parser, $name) - { - // here we only need to decrement the depth count - $this->depth--; - } -} - - -?> \ No newline at end of file diff --git a/Services/WebDAV/classes/Tools/_parse_proppatch.php b/Services/WebDAV/classes/Tools/_parse_proppatch.php deleted file mode 100644 index afda24ae216d..000000000000 --- a/Services/WebDAV/classes/Tools/_parse_proppatch.php +++ /dev/null @@ -1,214 +0,0 @@ - | -// | Christian Stocker | -// +----------------------------------------------------------------------+ -// -// $Id: _parse_proppatch.php,v 1.3 2004/01/05 12:41:34 hholzgra Exp $ -// - -/** - * helper class for parsing PROPPATCH request bodies - * - * @package HTTP_WebDAV_Server - * @author Hartmut Holzgraefe - * @version 0.99.1dev - */ -class _parse_proppatch -{ - /** - * - * - * @var - * @access - */ - var $success; - - /** - * - * - * @var - * @access - */ - var $props; - - /** - * - * - * @var - * @access - */ - var $depth; - - /** - * - * - * @var - * @access - */ - var $mode; - - /** - * - * - * @var - * @access - */ - var $current; - - /** - * constructor - * - * @param string path of input stream - * @access public - */ - function __construct($path) - { - $this->success = true; - - $this->depth = 0; - $this->props = array(); - $had_input = false; - - $f_in = fopen($path, "r"); - if (!$f_in) { - $this->success = false; - return; - } - - $xml_parser = xml_parser_create_ns("UTF-8", " "); - - xml_set_element_handler($xml_parser, - array(&$this, "_startElement"), - array(&$this, "_endElement")); - - xml_set_character_data_handler($xml_parser, - array(&$this, "_data")); - - xml_parser_set_option($xml_parser, - XML_OPTION_CASE_FOLDING, false); - - while($this->success && !feof($f_in)) { - $line = fgets($f_in); - if (is_string($line)) { - $had_input = true; - $this->success &= xml_parse($xml_parser, $line, false); - } - } - - if($had_input) { - $this->success &= xml_parse($xml_parser, "", true); - } - - xml_parser_free($xml_parser); - - fclose($f_in); - } - - /** - * tag start handler - * - * @param resource parser - * @param string tag name - * @param array tag attributes - * @return void - * @access private - */ - function _startElement($parser, $name, $attrs) - { - if (strstr($name, " ")) { - list($ns, $tag) = explode(" ", $name); - if ($ns == "") - $this->success = false; - } else { - $ns = ""; - $tag = $name; - } - - if ($this->depth == 1) { - $this->mode = $tag; - } - - if ($this->depth == 3) { - $prop = array("name" => $tag); - $this->current = array("name" => $tag, "ns" => $ns, "status"=> 200); - if ($this->mode == "set") { - $this->current["val"] = ""; // default set val - } - } - - if ($this->depth >= 4) { - $this->current["val"] .= "<$tag"; - foreach ($attr as $key => $val) { - $this->current["val"] .= ' '.$key.'="'.str_replace('"','"', $val).'"'; - } - $this->current["val"] .= ">"; - } - - - - $this->depth++; - } - - /** - * tag end handler - * - * @param resource parser - * @param string tag name - * @return void - * @access private - */ - function _endElement($parser, $name) - { - if (strstr($name, " ")) { - list($ns, $tag) = explode(" ", $name); - if ($ns == "") - $this->success = false; - } else { - $ns = ""; - $tag = $name; - } - - $this->depth--; - - if ($this->depth >= 4) { - $this->current["val"] .= ""; - } - - if ($this->depth == 3) { - if (isset($this->current)) { - $this->props[] = $this->current; - unset($this->current); - } - } - } - - /** - * input data handler - * - * @param resource parser - * @param string data - * @return void - * @access private - */ - function _data($parser, $data) { - if (isset($this->current)) { - $this->current["val"] .= $data; - } - } -} - -?> \ No newline at end of file diff --git a/Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php b/Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php new file mode 100644 index 000000000000..7068780ac95d --- /dev/null +++ b/Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php @@ -0,0 +1,56 @@ +isAuthenticated() && $DIC->user()->getId() != 0) + { + ilLoggerFactory::getLogger('webdav')->debug('User authenticated through session. UserID = ' . $DIC->user()->getId()); + return true; + } + + include_once './Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php'; + $credentials = new ilAuthFrontendCredentialsHTTP(); + $credentials->setUsername($a_username); + $credentials->setPassword($a_password); + + include_once './Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php'; + $provider_factory = new ilAuthProviderFactory(); + $providers = $provider_factory->getProviders($credentials); + + include_once './Services/Authentication/classes/class.ilAuthStatus.php'; + $status = ilAuthStatus::getInstance(); + + include_once './Services/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php'; + $frontend_factory = new ilAuthFrontendFactory(); + $frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_HTTP); + $frontend = $frontend_factory->getFrontend( + $GLOBALS['DIC']['ilAuthSession'], + $status, + $credentials, + $providers + ); + + $frontend->authenticate(); + + switch($status->getStatus()) + { + case ilAuthStatus::STATUS_AUTHENTICATED: + ilLoggerFactory::getLogger('webdav')->debug('User authenticated through basic authentication. UserId = ' . $DIC->user()->getId()); + return true; + + case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED: + ilLoggerFactory::getLogger('webdav')->info('Basic authentication failed; Account migration required.'); + return false; + + case ilAuthStatus::STATUS_AUTHENTICATION_FAILED: + ilLoggerFactory::getLogger('webdav')->info('Basic authentication failed; Wrong login, password.'); + return false; + } + + return false; + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilDAVLocks.php b/Services/WebDAV/classes/class.ilDAVLocks.php deleted file mode 100644 index 0aff69037c42..000000000000 --- a/Services/WebDAV/classes/class.ilDAVLocks.php +++ /dev/null @@ -1,612 +0,0 @@ -writelog('lockRef('.$refId.','.$iliasUserId.','.$davUser.','.$token.','.$expires.','.$depth.','.$scope.')'); - global $DIC; - $tree = $DIC['tree']; - $txt = $DIC['txt']; - - $result = true; - $data = $tree->getNodeData($refId); - - // Check whether a lock on the path to the object prevents the creation - // of a new lock - $locksOnPath = $this->getLocksOnPathRef($refId); - - if ($scope == 'exclusive' && count($locksOnPath) > 0) { - $result = 'couldnt create exclusive lock due to existing lock on path '.var_export($locksOnPath,true); - } - - foreach ($locksOnPath as $lock) - { - if ($lock['token'] == $token && - $lock['obj_id'] == $data['obj_id'] && - $lock['ilias_owner'] == $iliasUserId) - { - if ($this->updateLockWithoutCheckingObj($data['obj_id'], 0, $token, $expires)) - { - return true; - } - else - { - return 'couldnt update lock'; - } - } - } - - if ($result === true) - { - foreach ($locksOnPath as $lock) - { - if ($lock['scope'] == 'exclusive' && - ($lock['depth'] == 'infinity' || $lock['obj_id'] == $data['obj_id']) && - $lock['ilias_owner'] != $iliasUserId) - { - $result = 'couldnt create lock due to exclusive lock on path '.var_export($lock,true); - break; - } - } - } - - // Check whether a lock on the children (subtree) of the object prevents - // the creation of a new lock - if ($result === true && $depth == 'infinity') - { - // XXX - if lock has depth infinity, we must check for locks in the subtree - } - - if ($result === true) - { - $result = $this->lockWithoutCheckingObj( - $data['obj_id'], 0, - $iliasUserId, $davUser, $token, $expires, $depth, $scope - ); - } - return $result; - } - - /** - * Creates a write lock. - * - * Important: This is a low-level function, which does not check on existing - * locks, before creating the lock data. - * - * As described in RFC2518, chapter 7.1, a write lock prevents all principals whithout - * the lock from successfully executing a PUT, POST, PROPPATCH, LOCK, UNLOCK, MOVE, - * DELETE, or MKCOL on the locked resource. All other current methods, GET in particular, - * function independently of the lock. - * For a collection, the lock also affects the ability to add and remove members. - * - * @param $objDAV DAV object to be locked. - * @param int ILIAS user id of the lock owner. - * @param string DAV user of the lock owner. - * @param string Lock token. - * @param int expiration timestamp for the lock. - * @param bool Depth of the lock. Must be 0 or 'infinity'. - * @param bool Scope of the lock. Must be 'exclusive' or 'shared'. - * - * @return true, if creation of lock succeeded. - */ - public function lockWithoutCheckingDAV(&$objDAV, $iliasUserId, $davUser, $token, $expires, $depth, $scope) - { - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - return $this->lockWithoutCheckingObj($objId, $nodeId, $iliasUserId, $davUser, $token, $expires, $depth, $scope); - } - /** - * Creates a write lock. - * - * Important: This is a low-level function, which does not check on existing - * locks, before creating the lock data. - * - * As described in RFC2518, chapter 7.1, a write lock prevents all principals whithout - * the lock from successfully executing a PUT, POST, PROPPATCH, LOCK, UNLOCK, MOVE, - * DELETE, or MKCOL on the locked resource. All other current methods, GET in particular, - * function independently of the lock. - * For a collection, the lock also affects the ability to add and remove members. - * - * @param int id of the object to be locked. - * @param int node The id of a node of the object. For example the id of a page of a - * learning module. Specify 0 if the object does not have multiple nodes. -. * @param int ILIAS user id of the lock owner. - * @param string DAV user of the lock owner. - * @param string Lock token. - * @param int expiration timestamp for the lock. - * @param bool Depth of the lock. Must be 0 or 'infinity'. - * @param bool Scope of the lock. Must be 'exclusive' or 'shared'. - * - * @return true, if creation of lock succeeded. - */ - public function lockWithoutCheckingObj($objId, $nodeId, $iliasUserId, $davUser, $token, $expires, $depth, $scope) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - switch ($depth) - { - case 'infinity' : $depth = -1; - break; - case 0 : - $depth = 0; - break; - default : - trigger_error('invalid depth '.$depth,E_ERROR); - return; - } - - switch ($scope) - { - case 'exclusive' : $scope = 'x'; break; - case 'shared' : $scope = 's'; break; - default : trigger_error('invalid scope '.$scope,E_ERROR); return; - } - - $q = 'INSERT INTO '.$this->table - .' SET obj_id = '.$ilDB->quote($objId,'integer') - .', node_id = '.$ilDB->quote($nodeId,'integer') - .', ilias_owner = '.$ilDB->quote($iliasUserId,'text') - .', dav_owner = '.$ilDB->quote($davUser,'text') - .', token = '.$ilDB->quote($token,'text') - .', expires = '.$ilDB->quote($expires,'integer') - .', depth = '.$ilDB->quote($depth,'integer') - .', type = \'w\'' - .', scope = '.$ilDB->quote($scope,'text') - ; - $this->writelog('lock query='.$q); - $result = $ilDB->manipulate($q); - return ! PEAR::isError($result); - } - /** - * Updates a write lock. - * - * Important: This is a low-level function, which does not check on existing - * locks, before updating the lock data. - * - * @param string Lock token. - * @param int expiration timestamp for the lock. - * - * @return true on success. - */ - public function updateLockWithoutCheckingDAV(&$objDAV, $token, $expires) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - return $this->updateLockWithoutCheckingObj($objId, $nodeId, $token, $expires); - } - /** - * Updates a write lock. - * - * Important: This is a low-level function, which does not check on existing - * locks, before updating the lock data. - * - * @param string Lock token. - * @param int expiration timestamp for the lock. - * - * @return true on success. - */ - public function updateLockWithoutCheckingObj($objId, $nodeId, $token, $expires) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $q = 'UPDATE '.$this->table - .' SET expires = '.$ilDB->quote($expires,'integer') - .' WHERE token = '.$ilDB->quote($token,'text') - .' AND obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id = '.$ilDB->quote($nodeId,'integer') - ; - $aff = $ilDB->manipulate($q); - return $aff > 0; - } - /** - * Discards a write lock. - * - * Important: This is a low-level function, which does not check on existing - * locks, before deleting the lock data. - * - * @param $objDAV DAV object to be locked. - * @param string Lock token. - * - * @return true on success. - */ - public function unlockWithoutCheckingDAV(&$objDAV, $token) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $this->writelog('unlock('.$objDAV.','.$token.')'); - - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - // Unlock object - // FIXME - Maybe we should delete all rows with the same token, not - // just the ones with the same token, obj_id and node_id. - $q = 'DELETE FROM '.$this->table - .' WHERE token = '.$ilDB->quote($token,'text') - .' AND obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id = '.$ilDB->quote($nodeId,'integer') - ; - $this->writelog('unlock query='.$q); - $aff = $ilDB->manipulate($q); - $success = $aff > 0; - - // clean up expired locks in 1 out of 100 unlock requests - if (rand(1,100) == 1) - { - $this->cleanUp(); - } - - return $success; - } - - /** - * Returns the lock with the specified token on the specified DAV object. - * - * @param $objDAV DAV object to get the lock for. - * @param string Lock token. - * @return An array of associative arrays for all the locks that were found. - * Each associative array has the following keys: - * 'ilias_owner' => user id, - * 'dav_owner' => user name, - * 'token' => locktoken - * 'expires' => expiration timestamp - * 'depth' => 0 or 'infinity' - * 'scope' => 'exclusive' or 'shared' - */ - public function getLockDAV(&$objDAV,$token) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $this->writelog('getLocks('.$objDAV.')'); - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - $q = 'SELECT ilias_owner, dav_owner, expires, depth, scope' - .' FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id = '.$ilDB->quote($nodeId,'integer') - .' AND token = '.$ilDB->quote($token,'text') - ; - $this->writelog('getLocks('.$objDAV.') query='.$q); - $r = $ilDB->query($q); - - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - if ($row['depth'] == -1) $row['depth'] = 'infinity'; - $row['scope'] = ($row['scope'] == 'x') ? 'exclusive' : 'shared'; - $row['token'] = $token; - $result = $row; - } - return $result; - } - /** - * Returns all locks on the specified object. This method does not take into - * account inherited locks from parent objects. - * - * @param $objDAV DAV object to get the locks for. - * @return An array of associative arrays for all the locks that were found. - * Each associative array has the following keys: - * 'ilias_owner' => user id, - * 'dav_owner' => user name, - * 'token' => locktoken - * 'expires' => expiration timestamp - * 'depth' => 0 or 'infinity' - * 'scope' => 'exclusive' or 'shared' - */ - public function getLocksOnObjectDAV(&$objDAV) - { - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - return $this->getLocksOnObjectObj($objId, $nodeId); - } - /** - * Returns all locks on the specified object id. This method does not take into - * account inherited locks from parent objects. - * - * @param $objId object ID to get the locks for. - * @param int node a node of the object. For example the id of a page of a - * learning module. Specify 0 if the object does not have multiple nodes. - * @return An array of associative arrays for all the locks that were found. - * Each associative array has the following keys: - * 'ilias_owner' => user id, - * 'dav_owner' => user name, - * 'token' => locktoken - * 'expires' => expiration timestamp - * 'depth' => 0 or 'infinity' - * 'scope' => 'exclusive' or 'shared' - */ - public function getLocksOnObjectObj($objId, $nodeId = 0) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $this->writelog('getLocks('.$objDAV.')'); - $nodeId = 0; - $q = 'SELECT ilias_owner, dav_owner, token, expires, depth, scope' - .' FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id = '.$ilDB->quote($nodeId,'integer') - .' AND expires > '.$ilDB->quote(time(),'integer') - ; - $this->writelog('getLocks('.$objDAV.') query='.$q); - $r = $ilDB->query($q); - - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - if ($row['depth'] == -1) $row['depth'] = 'infinity'; - $row['scope'] = ($row['scope'] == 'x') ? 'exclusive' : 'shared'; - $result[] = $row; - } - return $result; - } - /** - * Returns all locks on the specified object path. - * - * @param $pathDAV Array with DAV objects to get the locks for. - * @return An array of associative arrays for all the locks that were found. - * Each associative array has the following keys: - * 'obj_id' => object id - * 'node_id' => node id - * 'ilias_owner' => user id, - * 'dav_owner' => user name, - * 'token' => locktoken - * 'expires' => expiration timestamp - * 'depth' => 0 or 'infinity' - * 'scope' => 'exclusive' or 'shared' - */ - public function getLocksOnPathDAV(&$pathDAV) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $this->writelog('getLocksOnPathDAV'); - - $q = 'SELECT obj_id, node_id, ilias_owner, dav_owner, token, expires, depth, scope' - .' FROM '.$this->table - .' WHERE expires > '.$ilDB->quote(time(),'integer') - .' AND (' - ; - $isFirst = true; - foreach ($pathDAV as $objDAV) - { - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - if ($isFirst) - { - $isFirst = false; - } else { - $q .= ' OR '; - } - $q .= '(obj_id = '.$ilDB->quote($objId,'integer').' AND node_id = '.$ilDB->quote($nodeId,'integer').')'; - } - $q .= ')'; - - $this->writelog('getLocksOnPathDAV('.$objDAV.') query='.$q); - $r = $ilDB->query($q); - - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - if ($row['depth'] == -1) $row['depth'] = 'infinity'; - $row['scope'] = ($row['scope'] == 'x') ? 'exclusive' : 'shared'; - $result[] = $row; - } - $this->writelog('getLocksOnPathDAV:'.var_export($result,true)); - return $result; - } - /** - * Returns all locks on the specified object, specified by a reference id. - * - * @param $refId The reference id of the object - * @return An array of associative arrays for all the locks that were found. - * Each associative array has the following keys: - * 'obj_id' => object id - * 'node_id' => node id - * 'ilias_owner' => user id, - * 'dav_owner' => user name, - * 'token' => locktoken - * 'expires' => expiration timestamp - * 'depth' => 0 or 'infinity' - * 'scope' => 'exclusive' or 'shared' - */ - public function getLocksOnPathRef($refId) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $tree = $DIC['tree']; - $this->writelog('getLocksOnPathRef('.$refId.')'); - - $pathFull = $tree->getPathFull($refId); - - $q = 'SELECT obj_id, node_id, ilias_owner, dav_owner, token, expires, depth, scope' - .' FROM '.$this->table - .' WHERE expires > '.$ilDB->quote(time(),'integer') - .' AND (' - ; - $isFirst = true; - foreach ($pathFull as $pathItem) - { - $objId = $pathItem['obj_id']; - $nodeId = 0; - if ($isFirst) - { - $isFirst = false; - } else { - $q .= ' OR '; - } - $q .= '(obj_id = '.$ilDB->quote($objId,'integer').' AND node_id = '.$ilDB->quote($nodeId,'integer').')'; - } - $q .= ')'; - - $this->writelog('getLocksOnPathRef('.$refId.') query='.$q); - $r = $ilDB->query($q); - - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - if ($row['depth'] == -1) $row['depth'] = 'infinity'; - $row['scope'] = ($row['scope'] == 'x') ? 'exclusive' : 'shared'; - $result[] = $row; - } - return $result; - } - /** - * System maintenance: get rid of locks that have expired over - * an hour ago. Since we have no index over the 'expires' column, - * this causes a (very slow) table space scan. - */ - public function cleanUp() - { - global $DIC; - $ilDB = $DIC['ilDB']; - $tree = $DIC['tree']; - - // 1. Get rid of locks that have expired over an hour ago - $old = time() - 3600; - $q = 'DELETE' - .' FROM '.$this->table - .' WHERE expires < '.$ilDB->quote($old,'integer') - ; - $ilDB->manipulate($q); - - // 2. Get rid of null resources which are not associated to - // a lock due to step 1, or due to a database inconsistency - // because we are working with non-transactional tables - $q = 'SELECT dat.obj_id ' - .' FROM object_data AS dat' - .' LEFT JOIN '.$this->table.' lck' - .' ON dat.obj_id = lck.obj_id' - .' WHERE dat.type = '.$ilDB->quote('null','text') - .' AND lck.obj_id IS NULL' - ; -/* TODO: smeyer.' FOR UPDATE' */ - - $r = $ilDB->query($q); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - $references = ilObject::_getAllReferences($row['obj_id']); - $obj = new ilObjNull($row['obj_id'], false); - if (count($references) == 0) - { - $obj->delete(); - } else { - foreach ($references as $refId) - { - $obj->setRefId($refId); - $obj->delete(); - $nodeData = $tree->getNodeData($refId); - $tree->deleteTree($nodeData); - } - } - } - } - /** - * Writes a message to the logfile., - * - * @param message String. - * @return void. - */ - protected function writelog($message) - { - global $DIC; - $log = $DIC['log']; - $ilias = $DIC['ilias']; - if ($this->isDebug) - { - $log->write( - $ilias->account->getLogin() - .' DAV ilDAVLocks.'.str_replace("\n",";",$message) - ); - } - /* - if ($this->logFile) - { - $fh = fopen($this->logFile, 'a'); - fwrite($fh, date('Y-m-d h:i:s ')); - fwrite($fh, str_replace("\n",";",$message)); - fwrite($fh, "\n\n"); - fclose($fh); - }*/ - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilDAVProperties.php b/Services/WebDAV/classes/class.ilDAVProperties.php deleted file mode 100644 index a02b6c8f93e0..000000000000 --- a/Services/WebDAV/classes/class.ilDAVProperties.php +++ /dev/null @@ -1,242 +0,0 @@ -writelog('put ns='.$namespace.' name='.$name.' value='.$value); - global $DIC; - $ilDB = $DIC['ilDB']; - - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - if(isset($value)) - { - $ilDB->replace($this->table, - array( - 'obj_id' => array('integer',$objId), - 'node_id' => array('integer',$nodeId), - 'ns' => array('text',$namespace), - 'name' => array('text',$name) - ), - array('value' => array('clob',$value)) - ); - - /* - $q = 'REPLACE INTO '.$this->table - .' SET obj_id = '.$ilDB->quote($objId) - .', node_id = '.$ilDB->quote($nodeId) - .', ns = '.$ilDB->quote($namespace) - .', name = '.$ilDB->quote($name) - .', value = '.$ilDB->quote($value) - ; - */ - } - else - { - $q = 'DELETE FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id = '.$ilDB->quote($nodeId,'integer') - .' AND ns = '.$ilDB->quote($namespace,'text') - .' AND name = '.$ilDB->quote($name,'text') - ; - $ilDB->manipulate($q); - } - //$this->writelog('put query='.$q); - #$r = $ilDB->query($q); - } - - /** - * Gets a property from the specified DAV object. - * - * @param ilObjectDAV DAV object for which the property is put - * @param string Namespace of the property - * @param string Name of the property - * @return string Value of the property. Returns null if the property is empty. - */ - public function get($objDAV, $namespace, $name, $value) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - $q = 'SELECT value FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id ='.$ilDB->quote($nodeId,'integer') - .' AND ns = '.$ilDB->quote($namespace,'text') - .' AND name = '.$ilDB->quote($name,'text') - ; - $r = $ilDB->query($q); - if ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - $value = $row['value']; - } else { - $value = null; - } - return $value; - } - /** - * Gets all properties from the specified DAV object. - * - * @param ilObjectDAV DAV object for which the property is put - * @return array of assicative arrays. Each associative array contains the fields - * 'namespace','name' and 'value'. - */ - public function getAll($objDAV) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $objId = $objDAV->getObjectId(); - $nodeId = $objDAV->getNodeId(); - - $q = 'SELECT ns, name, value' - .' FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id ='.$ilDB->quote($nodeId,'integer') - ; - $r = $ilDB->query($q); - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - $result[] = array( - 'namespace' => $row['ns'], - 'name' => $row['name'], - 'value' => $row['value'] - ); - } - return $result; - } - - /** - * Moves all properties from one dav object to another. - * / - public function move($fromObjDAV, $toObjDAV) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $fromObjId = $fromObjDAV->getObjectId(); - $fromNodeId = $fromObjDAV->getNodeId(); - $toObjId = $toObjDAV->getObjectId(); - $toNodeId = $toObjDAV->getNodeId(); - - $q = 'UPDATE '.$this->table - .' SET obj_id = '.$ilDB->quote($toObjId) - .', node_id = '.$ilDB->quote($toNodeId) - .' WHERE obj_id = '.$ilDB->quote($fromObjId) - .' AND node_id ='.$ilDB->quote($toNodeId) - ; - $r = $ilDB->query($q); - }*/ - /** - * Copies all properties from one dav object to another. - */ - public function copy($fromObjDAV, $toObjDAV) - { - global $DIC; - $ilDB = $DIC['ilDB']; - - $fromObjId = $fromObjDAV->getObjectId(); - $fromNodeId = $fromObjDAV->getNodeId(); - $toObjId = $toObjDAV->getObjectId(); - $toNodeId = $toObjDAV->getNodeId(); - - $q = 'SELECT ns, name, value FROM '.$this->table - .' WHERE obj_id = '.$ilDB->quote($objId,'integer') - .' AND node_id ='.$ilDB->quote($nodeId,'integer'); -/* .' FOR UPDATE' */ - $r = $ilDB->query($q); - $result = array(); - while ($row = $r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) - { - $q2 = 'INSERT INTO '.$this->table - .' (obj_id, node_id, ns, name, value)' - .' VALUES' - .'('.$ilDB->quote($row['obj_id']) - .', '.$ilDB->quote($row['node_id']) - .', '.$ilDB->quote($row['ns']) - .', '.$ilDB->quote($row['name']) - .', '.$ilDB->quote($row['value']) - .')' - ; - $r2 = $ilDB->manipulate($q2); - } - } - - /** - * Writes a message to the logfile., - * - * @param message String. - * @return void. - */ - protected function writelog($message) - { - global $DIC; - $log = $DIC['log']; - $ilias = $DIC['ilias']; - $log->write( - $ilias->account->getLogin() - .' DAV ilDAVProperties.'.str_replace("\n",";",$message) - ); - /* - if ($this->logFile) - { - $fh = fopen($this->logFile, 'a'); - fwrite($fh, date('Y-m-d h:i:s ')); - fwrite($fh, str_replace("\n",";",$message)); - fwrite($fh, "\n\n"); - fclose($fh); - }*/ - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilDAVServer.php b/Services/WebDAV/classes/class.ilDAVServer.php deleted file mode 100644 index 177544df3419..000000000000 --- a/Services/WebDAV/classes/class.ilDAVServer.php +++ /dev/null @@ -1,2198 +0,0 @@ -ilDAVServer::getInstance() to get an instance from outside - * - */ - private function __construct() - { - $this->writelog(""); - - // Initialize the WebDAV server and create - // locking and property support objects - $this->locks = new ilDAVLocks(); - $this->properties = new ilDAVProperties(); - //$this->locks->createTable(); - //$this->properties->createTable(); - - // Guess operating system, operating system flavor and browser of the webdav client - // - // - We need to know the operating system in order to properly - // hide hidden resources in directory listings. - // - // - We need the operating system flavor and the browser to - // properly support mounting of a webdav folder. - // - $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']); - $this->writelog('userAgent='.$userAgent); - if (strpos($userAgent,'windows') !== false - || strpos($userAgent,'microsoft') !== false) - { - $this->clientOS = 'windows'; - if(strpos($userAgent,'nt 5.1') !== false){ - $this->clientOSFlavor = 'xp'; - }else{ - $this->clientOSFlavor = 'nichtxp'; - } - - } else if (strpos($userAgent,'darwin') !== false - || strpos($userAgent,'macintosh') !== false - || strpos($userAgent,'linux') !== false - || strpos($userAgent,'solaris') !== false - || strpos($userAgent,'aix') !== false - || strpos($userAgent,'unix') !== false - || strpos($userAgent,'gvfs') !== false // nautilus browser uses this ID - ) - { - $this->clientOS = 'unix'; - if (strpos($userAgent,'linux') !== false) - { - $this->clientOSFlavor = 'linux'; - } - else if (strpos($userAgent,'macintosh') !== false) - { - $this->clientOSFlavor = 'osx'; - } - } - if (strpos($userAgent,'konqueror') !== false) - { - $this->clientBrowser = 'konqueror'; - } - } - - /** - * Get singelton instance - * @return - */ - public static function getInstance() - { - if(self::$instance != NULL) - { - return self::$instance; - } - return self::$instance = new ilDAVServer(); - } - - /** - * Try authentication - */ - public function tryAuthentication() - { - if($GLOBALS['DIC']['ilAuthSession']->isAuthenticated()) - { - ilLoggerFactory::getLogger('init')->debug('User session is valid'); - return true; - } - - ilLoggerFactory::getLogger('init')->debug('Trying http (webdav) authentication.'); - - include_once './Services/Authentication/classes/Frontend/class.ilAuthFrontendCredentialsHTTP.php'; - $credentials = new ilAuthFrontendCredentialsHTTP(); - $credentials->initFromRequest(); - - include_once './Services/Authentication/classes/Provider/class.ilAuthProviderFactory.php'; - $provider_factory = new ilAuthProviderFactory(); - $providers = $provider_factory->getProviders($credentials); - - include_once './Services/Authentication/classes/class.ilAuthStatus.php'; - $status = ilAuthStatus::getInstance(); - - include_once './Services/Authentication/classes/Frontend/class.ilAuthFrontendFactory.php'; - $frontend_factory = new ilAuthFrontendFactory(); - $frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_HTTP); - $frontend = $frontend_factory->getFrontend( - $GLOBALS['DIC']['ilAuthSession'], - $status, - $credentials, - $providers - ); - - $frontend->authenticate(); - - switch($status->getStatus()) - { - case ilAuthStatus::STATUS_AUTHENTICATED: - ilLoggerFactory::getLogger('auth')->debug('Authentication successful. Serving request'); - ilLoggerFactory::getLogger('auth')->info('Authenticated user id: ' . $GLOBALS['DIC']['ilAuthSession']->getUserId()); - ilLoggerFactory::getLogger('auth')->debug('Auth info authenticated: ' .$GLOBALS['DIC']['ilAuthSession']->isAuthenticated()); - ilLoggerFactory::getLogger('auth')->debug('Auth info expired: ' .$GLOBALS['DIC']['ilAuthSession']->isExpired()); - ilInitialisation::initUserAccount(); - return true; - - case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED: - ilLoggerFactory::getLogger('auth')->debug('Authentication failed; Account migration required.'); - return false; - - case ilAuthStatus::STATUS_AUTHENTICATION_FAILED: - ilLoggerFactory::getLogger('auth')->debug('Authentication failed; Wrong login, password.'); - return false; - } - - return false; - } - - - /** - * Serves a WebDAV request. - */ - public function serveRequest() - { - if(!$this->tryAuthentication()) - { - return false; - } - - // die quickly if plugin is deactivated - if (!self::_isActive()) - { - $this->writelog(__METHOD__.' WebDAV disabled. Aborting'); - $this->http_status('403 Forbidden'); - echo '

Sorry

'. - '

Please enable the WebDAV plugin in the ILIAS Administration panel.

'. - '

You can only access this page, if WebDAV is enabled on this server.

'. - ''; - return; - } - - try { - $start = time(); - $this->writelog('serveRequest():'.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'].' ...'); - parent::serveRequest(); - $end = time(); - $this->writelog('serveRequest():'.$_SERVER['REQUEST_METHOD'].' done status='.$this->_http_status.' elapsed='.($end - $start)); - $this->writelog('---'); - } - catch (Exception $e) - { - $this->writelog('serveRequest():'.$_SERVER['REQUEST_METHOD'].' caught exception: '.$e->getMessage().'\n'.$e->getTraceAsString()); - } - } - - /** - * We do not implement this method, because authentication is done by - * ilias3/webdav.php. - * - * @access private - * @param string HTTP Authentication type (Basic, Digest, ...) - * @param string Username - * @param string Password - * @return bool true on successful authentication - * / - public function check_auth($type, $user, $pass) - { - $this->writelog('check_auth type='.$type.' user='.$user.' pass='.$pass); - - if (! $user) - { - return false; - } - return true; - }*/ - - /** - * Encodes an URL. - * This function differs from the PHP urlencode() function in the following - * way: - * - Unicode characters are composed into Unicode Normal Form NFC - * This ensures that WebDAV clients running on Windows and Mac OS X - * treat resource names that contain diacritic marks in the same way. - * - Slash characters '/' are preserved - * This ensures that path components are properly recognized by - * WebDAV clients. - * - Space characters are encoded as '%20' instead of '+'. - * This ensures proper handling of spaces by WebDAV clients. - */ - private function davUrlEncode($path) - { - // We compose the path to Unicode Normal Form NFC - // This ensures that diaeresis and other special characters - // are treated uniformly on Windows and on Mac OS X - $path = UtfNormal::toNFC($path); - - $c = explode('/',$path); - for ($i = 0; $i < count($c); $i++) - { - $c[$i] = str_replace('+','%20',urlencode($c[$i])); - } - return implode('/',$c); - } - - /** - * PROPFIND method handler - * - * @param array general parameter passing array - * @param array return array for file properties - * @return bool true on success - */ - public function PROPFIND(&$options, &$files) - { - // Activate tree cache - global $tree; - //$tree->useCache(true); - - $this->writelog('PROPFIND(options:'.var_export($options, true).' files:'.var_export($files, true).'.)'); - $this->writelog('PROPFIND '.$options['path']); - - // get dav object for path - $path =& $this->davDeslashify($options['path']); - $objDAV =& $this->getObject($path); - - // prepare property array - $files['files'] = array(); - - // sanity check - if (is_null($objDAV)) { - return false; - } - if (! $objDAV->isPermitted('visible,read')) { - return '403 Forbidden'; - } - - // store information for the requested path itself - // FIXME : create display name for object. - $encodedPath = $this->davUrlEncode($path); - - $GLOBALS['ilLog']->write(print_r($encodedPath,true)); - - $files['files'][] =& $this->fileinfo($encodedPath, $encodedPath, $objDAV); - - // information for contained resources requested? - if (!empty($options['depth'])) { - // The breadthFirst list holds the collections which we have not - // processed yet. If depth is infinity we append unprocessed collections - // to the end of this list, and remove processed collections from - // the beginning of this list. - $breadthFirst = array($objDAV); - $objDAV->encodedPath = $encodedPath; - - while (count($breadthFirst) > 0) { - // remove a collection from the beginning of the breadthFirst list - $collectionDAV = array_shift($breadthFirst); - $childrenDAV =& $collectionDAV->childrenWithPermission('visible,read'); - foreach ($childrenDAV as $childDAV) - { - // On duplicate names, work with the older object (the one with the - // smaller object id). - foreach ($childrenDAV as $duplChildDAV) - { - if ($duplChildDAV->getObjectId() < $childDAV->getObjectId() && - $duplChildDAV->getResourceName() == $childDAV->getResourceName()) - { - continue 2; - } - } - - // only add visible objects to the file list - if (!$this->isFileHidden($childDAV)) - { - $this->writelog('PROPFIND() child ref_id='.$childDAV->getRefId()); - $files['files'][] =& $this->fileinfo( - $collectionDAV->encodedPath.'/'.$this->davUrlEncode($childDAV->getResourceName()), - $collectionDAV->encodedPath.'/'.$this->davUrlEncode($childDAV->getDisplayName()), - $childDAV - ); - if ($options['depth']=='infinity' && $childDAV->isCollection()) { - // add a collection to the end of the breadthFirst list - $breadthFirst[] = $childDAV; - $childDAV->encodedPath = $collectionDAV->encodedPath.'/'.$this->davUrlEncode($childDAV->getResourceName()); - } - } - } - } - } - - // Record read event but don't catch up with write events, because - // with WebDAV, a user can not see all objects contained in a folder. - global $ilUser; - ilChangeEvent::_recordReadEvent($objDAV->getILIASType(), $objDAV->getRefId(), - $objDAV->getObjectId(), $ilUser->getId(), false); - - // ok, all done - $this->writelog('PROPFIND():true options='.var_export($options, true).' files='.var_export($files,true)); - return true; - } - - /** - * Returns true, if the resource has a file name which is hidden from the user. - * Note, that resources with a hidden file name can still be accessed by a - * WebDAV client, if the client knows the resource name. - * - * - We hide all Null Resources who haven't got an active lock - * - We hide all files with the prefix "." from Windows DAV Clients. - * - We hide all files which contain characters that are not allowed on Windows from Windows DAV Clients. - * - We hide the files with the prefix " ~$" or the name "Thumbs.db" from Unix DAV Clients. - */ - private function isFileHidden(&$objDAV) - { - // Hide null resources which haven't got an active lock - if ($objDAV->isNullResource()) { - if (count($this->locks->getLocksOnObjectDAV($objDAV)) == 0) { - return; - } - } - - $name = $objDAV->getResourceName(); - $isFileHidden = false; - switch ($this->clientOS) - { - case 'unix' : - // Hide Windows thumbnail files, and files which start with '~$'. - $isFileHidden = - $name == 'Thumbs.db' - || substr($name, 0, 2) == '~$'; - // Hide files which contain / - $isFileHidden |= preg_match('/\\//', $name); - break; - case 'windows' : - // Hide files that start with '.'. - $isFileHidden = substr($name, 0, 1) == '.'; - // Hide files which contain \ / : * ? " < > | - $isFileHidden |= preg_match('/\\\\|\\/|:|\\*|\\?|"|<|>|\\|/', $name); - break; - default : - // Hide files which contain / - $isFileHidden |= preg_match('/\\//', $name); - break; - } - $this->writelog($this->clientOS.' '.$name.' isHidden:'.$isFileHidden.' clientOS:'.$this->clientOS); - return $isFileHidden; - } - - /** - * Creates file info properties for a single file/resource - * - * @param string resource path - * @param ilObjectDAV resource DAV object - * @return array resource properties - */ - private function fileinfo($resourcePath, $displayPath, &$objDAV) - { - global $ilias; - - $this->writelog('fileinfo('.$resourcePath.')'); - // create result array - $info = array(); - /* Some clients, for example WebDAV-Sync, need a trailing slash at the - * end of a resource path to a collection. - * However Mac OS X does not like this! - */ - if ($objDAV->isCollection() && $this->clientOSFlavor != 'osx') { - $info['path'] = $resourcePath.'/'; - } else { - $info['path'] = $resourcePath; - } - - $info['props'] = array(); - - // no special beautified displayname here ... - $info["props"][] =& $this->mkprop("displayname", $displayPath); - - // creation and modification time - $info["props"][] =& $this->mkprop("creationdate", $objDAV->getCreationTimestamp()); - $info["props"][] =& $this->mkprop("getlastmodified", $objDAV->getModificationTimestamp()); - - // directory (WebDAV collection) - $info["props"][] =& $this->mkprop("resourcetype", $objDAV->getResourceType()); - $info["props"][] =& $this->mkprop("getcontenttype", $objDAV->getContentType()); - $info["props"][] =& $this->mkprop("getcontentlength", $objDAV->getContentLength()); - - // Only show supported locks for users who have write permission - if ($objDAV->isPermitted('write')) - { - $info["props"][] =& $this->mkprop("supportedlock", - '' - .'' - .'' - .'' - .'' - .'' - .'' - .'' - ); - } - - // Maybe we should only show locks on objects for users who have write permission. - // But if we don't show these locks, users who have write permission in an object - // further down in a hierarchy can't see who is locking their object. - $locks = $this->locks->getLocksOnObjectDAV($objDAV); - $lockdiscovery = ''; - foreach ($locks as $lock) - { - // DAV Clients expects to see their own owner name in - // the locks. Since these names are not unique (they may - // just be the name of the local user running the DAV client) - // we return the ILIAS user name in all other cases. - if ($lock['ilias_owner'] == $ilias->account->getId()) - { - $owner = $lock['dav_owner']; - } else { - $owner = ''.$this->getLogin($lock['ilias_owner']).''; - } - $this->writelog('lockowner='.$owner.' ibi:'.$lock['ilias_owner'].' davi:'.$lock['dav_owner']); - - $lockdiscovery .= - '' - .'' - //.'' - .'' - .''.$lock['depth'].'' - .''.$owner.'' - - // more than a million is considered an absolute timestamp - // less is more likely a relative value - .'Second-'.(($lock['expires'] > 1000000) ? $lock['expires']-time():$lock['expires']).'' - .''.$lock['token'].'' - .'' - ; - } - if (strlen($lockdiscovery) > 0) - { - $info["props"][] =& $this->mkprop("lockdiscovery", $lockdiscovery); - } - - // get additional properties from database - $properties = $this->properties->getAll($objDAV); - foreach ($properties as $prop) - { - $info["props"][] = $this->mkprop($prop['namespace'], $prop['name'], $prop['value']); - } - - //$this->writelog('fileinfo():'.var_export($info, true)); - return $info; - } - - /** - * GET method handler. - * - * If the path denotes a directory, and if URL contains the query string "mount", - * a WebDAV mount-request is sent to the client. - * If the path denotes a directory, and if URL contains the query string "mount-instructions", - * instructions for mounting the directory are sent to the client. - * - * @param array parameter passing array - * @return bool true on success - */ - public function GET(&$options) - { - global $ilUser; - - $this->writelog('GET('.var_export($options, true).')'); - $this->writelog('GET('.$options['path'].')'); - - // get dav object for path - $path = $this->davDeslashify($options['path']); - $objDAV =& $this->getObject($path); - - // sanity check - if (is_null($objDAV) || $objDAV->isNullResource()) - { - return false; - } - - if (! $objDAV->isPermitted('visible,read')) - { - return '403 Forbidden'; - } - - // is this a collection? - if ($objDAV->isCollection()) - { - if (isset($_GET['mount'])) - { - return $this->mountDir($objDAV, $options); - } - else if (isset($_GET['mount-instructions'])) - { - return $this->showMountInstructions($objDAV, $options); - } - else - { - return $this->getDir($objDAV, $options); - } - } - // detect content type - $options['mimetype'] =& $objDAV->getContentType(); - // detect modification time - // see rfc2518, section 13.7 - // some clients seem to treat this as a reverse rule - // requiring a Last-Modified header if the getlastmodified header was set - $options['mtime'] =& $objDAV->getModificationTimestamp(); - - // detect content length - $options['size'] =& $objDAV->getContentLength(); - - // get content as stream or as data array - $options['stream'] =& $objDAV->getContentStream(); - if (is_null($options['stream'])) - { - $options['data'] =& $objDAV->getContentData(); - } - - // Record read event and catch up write events - ilChangeEvent::_recordReadEvent($objDAV->getILIASType(), $objDAV->getRefId(), - $objDAV->getObjectId(), $ilUser->getId()); - - $this->writelog('GET:'.var_export($options, true)); - - return true; - } - /** - * Mount method handler for directories - * - * Mounting is done according to the internet draft RFC 4709 "Mounting WebDAV servers" - * "draft-reschke-webdav-mount-latest". - * See - * http://greenbytes.de/tech/webdav/draft-reschke-webdav-mount-latest.html - * - * @param ilObjectDAV dav object handler - * @return This function does not return. It exits PHP. - */ - private function mountDir(&$objDAV, &$options) - { - $path = $this->davDeslashify($options['path']); - - header('Content-Type: application/davmount+xml'); - - echo "\n"; - echo " ".$this->base_uri."\n"; - - $xmlPath = str_replace('&','&',$path); - $xmlPath = str_replace('<','<',$xmlPath); - $xmlPath = str_replace('>','>',$xmlPath); - - echo " $xmlPath\n"; - echo "\n"; - - exit; - - } - /** - * Mount instructions method handler for directories - * - * @param ilObjectDAV dav object handler - * @return This function does not return. It exits PHP. - */ - private function showMountInstructions(&$objDAV, &$options) - { - global $lng,$ilUser; - - $path = $this->davDeslashify($options['path']); - - // The $path variable may contain a full or a shortened DAV path. - // We convert it into an object path, which we can then use to - // construct a new full DAV path. - $objectPath = $this->toObjectPath($path); - - // Construct a (possibly) full DAV path from the object path. - $fullPath = ''; - foreach ($objectPath as $object) - { - if ($object->getRefId() == 1 && $this->isFileHidden($object)) - { - // If the repository root object is hidden, we can not - // create a full path, because nothing would appear in the - // webfolder. We resort to a shortened path instead. - $fullPath .= '/ref_1'; - } - else - { - $fullPath .= '/'.$this->davUrlEncode($object->getResourceName()); - } - } - - // Construct a shortened DAV path from the object path. - $shortenedPath = '/ref_'. - $objectPath[count($objectPath) - 1]->getRefId(); - - if ($objDAV->isCollection()) - { - $shortenedPath .= '/'; - $fullPath .= '/'; - } - - // Prepend client id to path - $shortenedPath = '/'.CLIENT_ID.$shortenedPath; - $fullPath = '/'.CLIENT_ID.$fullPath; - - // Construct webfolder URI's. The URI's are used for mounting the - // webfolder. Since mounting using URI's is not standardized, we have - // to create different URI's for different browsers. - $webfolderURI = $this->base_uri.$shortenedPath; - $webfolderURI_Konqueror = ($this->isWebDAVoverHTTPS() ? "webdavs" : "webdav"). - substr($this->base_uri, strrpos($this->base_uri,':')). - $shortenedPath; - ; - $webfolderURI_Nautilus = ($this->isWebDAVoverHTTPS() ? "davs" : "dav"). - substr($this->base_uri, strrpos($this->base_uri,':')). - $shortenedPath - ; - $webfolderURI_IE = $this->base_uri.$shortenedPath; - - $webfolderTitle = $objectPath[count($objectPath) - 1]->getResourceName(); - - header('Content-Type: text/html; charset=UTF-8'); - echo "\n"; - echo "\n"; - echo "\n"; - echo " \n"; - echo " ".sprintf($lng->txt('webfolder_instructions_titletext'), $webfolderTitle)."\n"; - echo " \n"; - echo " \n"; - - echo ilDAVServer::_getWebfolderInstructionsFor($webfolderTitle, - $webfolderURI, $webfolderURI_IE, $webfolderURI_Konqueror, $webfolderURI_Nautilus, - $this->clientOS,$this->clientOSFlavor); - - echo " \n"; - echo "\n"; - - // Logout anonymous user to force authentication after calling mount uri - if($ilUser->getId() == ANONYMOUS_USER_ID) - { - $GOBALS['DIC']['ilAuthSession']->logout(); - } - - exit; - } - /** - * GET method handler for directories - * - * This is a very simple mod_index lookalike. - * See RFC 2518, Section 8.4 on GET/HEAD for collections - * - * @param ilObjectDAV dav object handler - * @return void function has to handle HTTP response itself - */ - private function getDir(&$objDAV, &$options) - { - global $ilias, $lng; - - // Activate tree cache - global $tree; - //$tree->useCache(true); - - $path = $this->davDeslashify($options['path']); - - // The URL of a directory must end with a slash. - // If it does not we are redirecting the browser. - // The slash is required, because we are using relative links in the - // HTML code we are generating below. - if ($path.'/' != $options['path']) - { - header('Location: '.$this->base_uri.$path.'/'); - exit; - } - - header('Content-Type: text/html; charset=UTF-8'); - - // fixed width directory column format - $format = "%15s %-19s %-s\n"; - - echo "\n"; - echo "\n"; - echo "\n"; - echo "\n"; - echo "".sprintf($lng->txt('webfolder_index_of'), $path)."\n"; - - // Create "anchorClick" behavior for for Internet Explorer - // This allows to create a link to a webfolder - echo "\n"; - - echo "\n"; - - $hrefPath = ''; - $pathComponents = explode('/',$path); - $uriComponents = array(); - foreach ($pathComponents as $component) - { - $uriComponents[] = $this->davUrlEncode($component); - } - for ($i = 0; $i < count($pathComponents); $i++) - { - $displayName = htmlspecialchars($pathComponents[$i]); - if ($i != 0) { - $hrefPath .= '/'; - } - $uriPath = implode('/', array_slice($uriComponents,0,$i + 1)); - if ($i < 2) - { - // The first two path elements consist of the webdav.php script - // and the client id. These elements are not part of the - // directory structure and thus are not represented as links. - $hrefPath .= $displayName; - } - else - { - $hrefPath .= ''.$displayName.''; - } - } - echo "

".sprintf($lng->txt('webfolder_index_of'), $hrefPath)."

\n"; - - // Display user id - if ($ilias->account->getLogin() == 'anonymous') - { - echo "

".$lng->txt('not_logged_in')."
\n"; - } else { - echo "

".$lng->txt('login_as')." " - .$ilias->account->getFirstname().' ' - .$ilias->account->getLastname().' ' - .' '.$ilias->account->getLogin().' ' - .', '.$lng->txt('client').' '.$ilias->getClientId().'.' - ."

\n"; - } - - // Create "open as webfolder" link - $href = $this->base_uri.$uriPath; - // IE can not mount long paths. If the path has more than one element, we - // create a relative path with a ref-id. - if (count($pathComponents) > 2) - { - $hrefIE = $this->base_uri.'/'.CLIENT_ID.'/ref_'.$objDAV->getRefId(); - } else { - $hrefIE = $href; - } - echo "

". - sprintf($lng->txt('webfolder_dir_info'), "$href?mount-instructions"). - "

\n"; - echo "

". - sprintf($lng->txt('webfolder_mount_dir_with'), - "$hrefIE\" folder=\"$hrefIE", // Internet Explorer - 'webdav'.substr($href,4), // Konqueror - 'dav'.substr($href,4), // Nautilus - $href.'?mount' // RFC 4709 - ) - ."

\n"; - - echo "
";
-		printf($format, $lng->txt('size'), $lng->txt('last_change'), $lng->txt('filename'));
-		echo "
"; - - $collectionCount = 0; - $fileCount = 0; - $children =& $objDAV->childrenWithPermission('visible,read'); - foreach ($children as $childDAV) { - if ($childDAV->isCollection() && !$this->isFileHidden($childDAV)) - { - $collectionCount++; - $name = $this->davUrlEncode($childDAV->getResourceName()); - printf($format, - '-', - strftime("%Y-%m-%d %H:%M:%S", $childDAV->getModificationTimestamp()), - ''.$childDAV->getDisplayName().""); - } - } - foreach ($children as $childDAV) { - if ($childDAV->isFile() && !$this->isFileHidden($childDAV)) - { - $fileCount++; - $name = $this->davUrlEncode($childDAV->getResourceName()); - printf($format, - number_format($childDAV->getContentLength()), - strftime("%Y-%m-%d %H:%M:%S", $childDAV->getModificationTimestamp()), - ''.$childDAV->getDisplayName().""); - } - } - foreach ($children as $childDAV) { - if ($childDAV->isNullResource() && !$this->isFileHidden($childDAV)) - { - $name = $this->davUrlEncode($childDAV->getResourceName()); - printf($format, - 'Lock', - strftime("%Y-%m-%d %H:%M:%S", $childDAV->getModificationTimestamp()), - ''.$childDAV->getDisplayName().""); - } - } - echo "
"; - echo $collectionCount.' '.$lng->txt(($collectionCount == 1) ? 'folder' : 'folders').', '; - echo $fileCount.' '.$lng->txt(($fileCount == 1) ? 'file' : 'files').'.'; - echo "
"; - echo "\n"; - - exit; - } - - - /** - * PUT method handler - * - * @param array parameter passing array - * @return bool true on success - */ - public function PUT(&$options) - { - global $ilUser; - - $this->writelog('PUT('.var_export($options, true).')'); - - $path = $this->davDeslashify($options['path']); - $parent = dirname($path); - $name = $this->davBasename($path); - - // get dav object for path - $parentDAV =& $this->getObject($parent); - - // sanity check - if (is_null($parentDAV) || ! $parentDAV->isCollection()) { - return '409 Conflict'; - } - - // Prevent putting of files which exceed upload limit - // FIXME: since this is an optional parameter, we should to do the - // same check again in function PUTfinished. - if ($options['content_length'] != null && - $options['content_length'] > $this->getUploadMaxFilesize()) { - - $this->writelog('PUT is forbidden, because content length='. - $options['content_length'].' is larger than upload_max_filesize='. - $this->getUploadMaxFilesize().'in php.ini'); - - return '403 Forbidden'; - } - - // determine mime type - include_once("./Services/Utilities/classes/class.ilMimeTypeUtil.php"); - $mime = ilMimeTypeUtil::lookupMimeType($name); - - $objDAV =& $this->getObject($path); - if (is_null($objDAV)) - { - $ttype = $parentDAV->getILIASFileType(); - $isperm = $parentDAV->isPermitted('create', $ttype); - if (! $isperm) - { - $this->writelog('PUT is forbidden, because user has no create permission'); - - return '403 Forbidden'; - } - $options["new"] = true; - $objDAV =& $parentDAV->createFile($name); - $this->writelog('PUT obj='.$objDAV.' name='.$name.' content_type='.$options['content_type']); - //$objDAV->setContentType($options['content_type']); - $objDAV->setContentType($mime); - if ($options['content_length'] != null) - { - $objDAV->setContentLength($options['content_length']); - } - $objDAV->write(); - // Record write event - ilChangeEvent::_recordWriteEvent($objDAV->getObjectId(), $ilUser->getId(), 'create', $parentDAV->getObjectId()); - } - else if ($objDAV->isNullResource()) - { - if (! $parentDAV->isPermitted('create', $parentDAV->getILIASFileType())) - { - $this->writelog('PUT is forbidden, because user has no create permission'); - return '403 Forbidden'; - } - $options["new"] = false; - $objDAV =& $parentDAV->createFileFromNull($name, $objDAV); - $this->writelog('PUT obj='.$objDAV.' name='.$name.' content_type='.$options['content_type']); - //$objDAV->setContentType($options['content_type']); - $objDAV->setContentType($mime); - if ($options['content_length'] != null) - { - $objDAV->setContentLength($options['content_length']); - } - $objDAV->write(); - - // Record write event - ilChangeEvent::_recordWriteEvent($objDAV->getObjectId(), $ilUser->getId(), 'create', $parentDAV->getObjectId()); - } - else - { - if (! $objDAV->isPermitted('write')) - { - $this->writelog('PUT is forbidden, because user has no write permission'); - return '403 Forbidden'; - } - $options["new"] = false; - $this->writelog('PUT obj='.$objDAV.' name='.$name.' content_type='.$options['content_type'].' content_length='.$options['content_length']); - - // Create a new version if the previous version is not empty - if ($objDAV->getContentLength() != 0) { - $objDAV->createNewVersion(); - } - - //$objDAV->setContentType($options['content_type']); - $objDAV->setContentType($mime); - if ($options['content_length'] != null) - { - $objDAV->setContentLength($options['content_length']); - } - $objDAV->write(); - - // Record write event - ilChangeEvent::_recordWriteEvent($objDAV->getObjectId(), $ilUser->getId(), 'update'); - ilChangeEvent::_catchupWriteEvents($objDAV->getObjectId(), $ilUser->getId(), 'update'); - } - // store this object, we reuse it in method PUTfinished - $this->putObjDAV = $objDAV; - - $out =& $objDAV->getContentOutputStream(); - $this->writelog('PUT outputstream='.$out); - - return $out; - } - - /** - * PUTfinished handler - * - * @param array parameter passing array - * @return bool true on success - */ - public function PUTfinished(&$options) - { - $this->writelog('PUTfinished('.var_export($options, true).')'); - - if($this->putObjDAV->getResourceType()==""){ - $vir = ilUtil::virusHandling($this->putObjDAV->obj->getDirectory($this->putObjDAV->obj->version).'/'.$this->putObjDAV->obj->filename, $this->putObjDAV->obj->filename); - if ($vir[0] == false) - { - $this->writelog('PUTfinished Virus found: '.$vir[1]); - //delete file - ilDAVServer::DELETE($options); - return false; - } - } - - // Update the content length in the file object, if the - // the client did not specify a content_length - if ($options['content_length'] == null || $this->putObjDAV->getContentLength() == 0) - { - $objDAV = $this->putObjDAV; - if ($objDAV->getContentOutputStreamLength() != null) { - $objDAV->setContentLength($objDAV->getContentOutputStreamLength()); - } else { - $objDAV->write(); - $objDAV->setContentLength(filesize($objDAV->obj->getDirectory($objDAV->obj->version).'/'.$objDAV->obj->filename)); - } - $objDAV->write(); - $this->putObjDAV = null; - } - return true; - } - - - /** - * MKCOL method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function MKCOL($options) - { - global $ilUser; - - $this->writelog('MKCOL('.var_export($options, true).')'); - $this->writelog('MKCOL '.$options['path']); - - $path =& $this->davDeslashify($options['path']); - $parent =& dirname($path); - $name =& $this->davBasename($path); - - // No body parsing yet - if(!empty($_SERVER["CONTENT_LENGTH"])) { - return "415 Unsupported media type"; - } - - // Check if an object with the path already exists. - $objDAV =& $this->getObject($path); - if (! is_null($objDAV)) - { - return '405 Method not allowed'; - } - - // get parent dav object for path - $parentDAV =& $this->getObject($parent); - - // sanity check - if (is_null($parentDAV) || ! $parentDAV->isCollection()) - { - return '409 Conflict'; - } - - if (! $parentDAV->isPermitted('create',$parentDAV->getILIASCollectionType())) - { - return '403 Forbidden'; - } - - // XXX Implement code that Handles null resource here - - $objDAV = $parentDAV->createCollection($name); - - if ($objDAV != null) - { - // Record write event - ilChangeEvent::_recordWriteEvent((int) $objDAV->getObjectId(), $ilUser->getId(), 'create', $parentDAV->getObjectId()); - } - - $result = ($objDAV != null) ? "201 Created" : "409 Conflict"; - return $result; - } - - - /** - * DELETE method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function DELETE($options) - { - global $ilUser; - - $this->writelog('DELETE('.var_export($options, true).')'); - $this->writelog('DELETE '.$options['path']); - - // get dav object for path - $path =& $this->davDeslashify($options['path']); - $parentDAV =& $this->getObject(dirname($path)); - $objDAV =& $this->getObject($path); - - // sanity check - if (is_null($objDAV) || $objDAV->isNullResource()) - { - return '404 Not Found'; - } - if (! $objDAV->isPermitted('delete')) - { - return '403 Forbidden'; - } - - $parentDAV->remove($objDAV); - - // Record write event - ilChangeEvent::_recordWriteEvent($objDAV->getObjectId(), $ilUser->getId(), 'delete', $parentDAV->getObjectId()); - - return '204 No Content'; - } - - /** - * MOVE method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function MOVE($options) - { - global $ilUser; - - $this->writelog('MOVE('.var_export($options, true).')'); - $this->writelog('MOVE '.$options['path'].' '.$options['dest']); - - // Get path names - $src = $this->davDeslashify($options['path']); - $srcParent = dirname($src); - $srcName = $this->davBasename($src); - $dst = $this->davDeslashify($options['dest']); - - $dstParent = dirname($dst); - $dstName = $this->davBasename($dst); - $this->writelog('move '.$dst.' dstname='.$dstName); - // Source and destination must not be the same - if ($src == $dst) - { - return '409 Conflict (source and destination are the same)'; - } - - // Destination must not be in a subtree of source - if (substr($dst,strlen($src)+1) == $src.'/') - { - return '409 Conflict (destination is in subtree of source)'; - } - - // Get dav objects for path - $srcDAV =& $this->getObject($src); - $dstDAV =& $this->getObject($dst); - $srcParentDAV =& $this->getObject($srcParent); - $dstParentDAV =& $this->getObject($dstParent); - - // Source must exist - if ($srcDAV == null) - { - return '409 Conflict (source does not exist)'; - } - - // Overwriting is only allowed, if overwrite option is set to 'T' - $isOverwritten = false; - if ($dstDAV != null) - { - if ($options['overwrite'] == 'T') - { - // Delete the overwritten destination - if ($dstDAV->isPermitted('delete')) - { - $dstParentDAV->remove($dstDAV); - $dstDAV = null; - $isOverwritten = true; - } else { - return '403 Not Permitted'; - } - } else { - return '412 Precondition Failed'; - } - } - - // Parents of destination must exist - if ($dstParentDAV == null) - { - return '409 Conflict (parent of destination does not exist)'; - } - - if ($srcParent == $dstParent) - { - // Rename source, if source and dest are in same parent - - // Check permission - if (! $srcDAV->isPermitted('write')) - { - return '403 Forbidden'; - } - $this->writelog('rename dstName='.$dstName); - $srcDAV->setResourceName($dstName); - $srcDAV->write(); - } else { - // Move source, if source and dest are in same parent - - - if (! $srcDAV->isPermitted('delete')) - { - return '403 Forbidden'; - } - - if (! $dstParentDAV->isPermitted('create', $srcDAV->getILIASType())) - { - return '403 Forbidden'; - } - $dstParentDAV->addMove($srcDAV, $dstName); - } - - // Record write event - if ($isOverwritten) - { - ilChangeEvent::_recordWriteEvent($srcDAV->getObjectId(), $ilUser->getId(), 'rename'); - } - else - { - ilChangeEvent::_recordWriteEvent($srcDAV->getObjectId(), $ilUser->getId(), 'remove', $srcParentDAV->getObjectId()); - ilChangeEvent::_recordWriteEvent($srcDAV->getObjectId(), $ilUser->getId(), 'add', $dstParentDAV->getObjectId()); - } - - return ($isOverwritten) ? '204 No Content' : '201 Created'; - } - - /** - * COPY method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function COPY($options, $del=false) - { - global $ilUser; - $this->writelog('COPY('.var_export($options, true).' ,del='.$del.')'); - $this->writelog('COPY '.$options['path'].' '.$options['dest']); - - // no copying to different WebDAV Servers - if (isset($options["dest_url"])) { - return "502 bad gateway"; - } - - $src = $this->davDeslashify($options['path']); - $srcParent = dirname($src); - $srcName = $this->davBasename($src); - $dst = $this->davDeslashify($options['dest']); - $dstParent = dirname($dst); - $dstName = $this->davBasename($dst); - - // sanity check - if ($src == $dst) - { - return '409 Conflict'; // src and dst are the same - } - - if (substr($dst,strlen($src)+1) == $src.'/') - { - return '409 Conflict'; // dst is in subtree of src - } - - $this->writelog('COPY src='.$src.' dst='.$dst); - // get dav object for path - $srcDAV =& $this->getObject($src); - $dstDAV =& $this->getObject($dst); - $dstParentDAV =& $this->getObject($dstParent); - - if (is_null($srcDAV) || $srcDAV->isNullResource()) - { - return '409 Conflict'; // src does not exist - } - if (is_null($dstParentDAV) || $dstParentDAV->isNullResource()) - { - return '409 Conflict'; // parent of dst does not exist - } - $isOverwritten = false; - - // XXX Handle nulltype for dstDAV - if (! is_null($dstDAV)) - { - if ($options['overwrite'] == 'T') - { - if ($dstDAV->isPermitted('delete')) - { - $dstParentDAV->remove($dstDAV); - ilChangeEvent::_recordWriteEvent($dstDAV->getObjectId(), $ilUser->getId(), 'delete', $dstParentDAV->getObjectId()); - - $dstDAV = null; - $isOverwritten = true; - } else { - return '403 Forbidden'; - } - } else { - return '412 Precondition Failed'; - } - } - - if (! $dstParentDAV->isPermitted('create', $srcDAV->getILIASType())) - { - return '403 Forbidden'; - } - $dstDAV = $dstParentDAV->addCopy($srcDAV, $dstName); - - // Record write event - ilChangeEvent::_recordReadEvent($srcDAV->getILIASType(), $srcDAV->getRefId(), - $srcDAV->getObjectId(), $ilUser->getId()); - ilChangeEvent::_recordWriteEvent($dstDAV->getObjectId(), $ilUser->getId(), 'create', $dstParentDAV->getObjectId()); - - return ($isOverwritten) ? '204 No Content' : '201 Created'; - } - - /** - * PROPPATCH method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function PROPPATCH(&$options) - { - $this->writelog('PROPPATCH(options='.var_export($options, true).')'); - $this->writelog('PROPPATCH '.$options['path']); - - // get dav object for path - $path =& $this->davDeslashify($options['path']); - $objDAV =& $this->getObject($path); - - // sanity check - if (is_null($objDAV) || $objDAV->isNullResource()) return false; - - $isPermitted = $objDAV->isPermitted('write'); - foreach($options['props'] as $key => $prop) { - if (!$isPermitted || $prop['ns'] == 'DAV:') - { - $options['props'][$key]['status'] = '403 Forbidden'; - } else { - $this->properties->put($objDAV, $prop['ns'],$prop['name'],$prop['val']); - } - } - - return ""; - } - - - /** - * LOCK method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function LOCK(&$options) - { - global $ilias; - $this->writelog('LOCK('.var_export($options, true).')'); - $this->writelog('LOCK '.$options['path']); - - // Check if an object with the path exists. - $path =& $this->davDeslashify($options['path']); - $objDAV =& $this->getObject($path); - // Handle null-object locking - // -------------------------- - if (is_null($objDAV)) - { - $this->writelog('LOCK handling null-object locking...'); - - // If the name does not exist, we create a null-object for it - if (isset($options["update"])) - { - $this->writelog('LOCK lock-update failed on non-existing null-object.'); - return '412 Precondition Failed'; - } - - $parent = dirname($path); - $parentDAV =& $this->getObject($parent); - if (is_null($parentDAV)) - { - $this->writelog('LOCK lock failed on non-existing path to null-object.'); - return '404 Not Found'; - } - if (! $parentDAV->isPermitted('create', $parentDAV->getILIASFileType()) && - ! $parentDAV->isPermitted('create', $parentDAV->getILIASCollectionType())) - { - $this->writelog('LOCK lock failed - creation of null object not permitted.'); - return '403 Forbidden'; - } - - $objDAV =& $parentDAV->createNull($this->davBasename($path)); - $this->writelog('created null resource for '.$path); - } - - // --------------------- - if (! $objDAV->isNullResource() && ! $objDAV->isPermitted('write')) - { - $this->writelog('LOCK lock failed - user has no write permission.'); - return '403 Forbidden'; - } - - // XXX - Check if there are other locks on the resource - if (!isset($options['timeout']) || is_array($options['timeout'])) - { - $options["timeout"] = time()+360; // 6min. - } - - if(isset($options["update"])) { // Lock Update - $this->writelog('LOCK update token='.var_export($options,true)); - $success = $this->locks->updateLockWithoutCheckingDAV( - $objDAV, - $options['update'], - $options['timeout'] - ); - if ($success) - { - $data = $this->locks->getLockDAV($objDAV, $options['update']); - if ($data['ilias_owner'] == $ilias->account->getId()) - { - $owner = $data['dav_owner']; - } else { - $owner = ''.$this->getLogin($data['ilias_owner']).''; - } - $options['owner'] = $owner; - $options['locktoken'] = $data['token']; - $options['timeout'] = $data['expires']; - $options['depth'] = $data['depth']; - $options['scope'] = $data['scope']; - $options['type'] = $data['scope']; - } - - } else { - $this->writelog('LOCK create new lock'); - - // XXX - Attempting to create a recursive exclusive lock - // on a collection must fail, if any of nodes in the subtree - // of the collection already has a lock. - // XXX - Attempting to create a recursive shared lock - // on a collection must fail, if any of nodes in the subtree - // of the collection already has an exclusive lock. - //$owner = (strlen(trim($options['owner'])) == 0) ? $ilias->account->getLogin() : $options['owner']; - $this->writelog('lock owner='.$owner); - $success = $this->locks->lockWithoutCheckingDAV( - $objDAV, - $ilias->account->getId(), - trim($options['owner']), - $options['locktoken'], - $options['timeout'], - $options['depth'], - $options['scope'] - ); - } - - // Note: As a workaround for the Microsoft WebDAV Client, we return - // true/false here (resulting in the status '200 OK') instead of - // '204 No Content'). - //return ($success) ? '204 No Content' : false; - return $success; - } - - /** - * UNLOCK method handler - * - * @param array general parameter passing array - * @return bool true on success - */ - public function UNLOCK(&$options) - { - global $log, $ilias; - $this->writelog('UNLOCK(options='.var_export($options, true).')'); - $this->writelog('UNLOCK '.$options['path']); - - // Check if an object with the path exists. - $path =& $this->davDeslashify($options['path']); - $objDAV =& $this->getObject($path); - if (is_null($objDAV)) { - return '404 Not Found'; - } - if (! $objDAV->isPermitted('write')) { - return '403 Forbidden'; - } - - $success = $this->locks->unlockWithoutCheckingDAV( - $objDAV, - $options['token'] - ); - - // Delete null resource object if there are no locks associated to - // it anymore - if ($objDAV->isNullResource() - && count($this->locks->getLocksOnObjectDAV($objDAV)) == 0) - { - $parent = dirname($this->davDeslashify($options['path'])); - $parentDAV =& $this->getObject($parent); - $parentDAV->remove($objDAV); - } - - // Workaround for Mac OS X: We must return 200 here instead of - // 204. - //return ($success) ? '204 No Content' : '412 Precondition Failed'; - return ($success) ? '200 OK' : '412 Precondition Failed'; - } - - /** - * checkLock() helper - * - * @param string resource path to check for locks - * @return array with the following entries: { - * type => "write" - * scope => "exclusive" | "shared" - * depth => 0 | -1 - * owner => string - * token => string - * expires => timestamp - */ - protected function checkLock($path) - { - global $ilias; - - $this->writelog('checkLock('.$path.')'); - $result = null; - - // get dav object for path - //$objDAV = $this->getObject($path); - - // convert DAV path into ilObjectDAV path - $objPath = $this->toObjectPath($path); - if (! is_null($objPath)) - { - $objDAV = $objPath[count($objPath) - 1]; - $locks = $this->locks->getLocksOnPathDAV($objPath); - foreach ($locks as $lock) - { - $isLastPathComponent = $lock['obj_id'] == $objDAV->getObjectId() - && $lock['node_id'] == $objDAV->getNodeId(); - - // Check all locks on last object in path, - // but only locks with depth infinity on parent objects. - if ($isLastPathComponent || $lock['depth'] == 'infinity') - { - // DAV Clients expects to see their own owner name in - // the locks. Since these names are not unique (they may - // just be the name of the local user running the DAV client) - // we return the ILIAS user name in all other cases. - if ($lock['ilias_owner'] == $ilias->account->getId()) - { - $owner = $lock['dav_owner']; - } else { - $owner = $this->getLogin($lock['ilias_owner']); - } - - // FIXME - Shouldn't we collect all locks instead of - // using an arbitrary one? - $result = array( - "type" => "write", - "obj_id" => $lock['obj_id'], - "node_id" => $lock['node_id'], - "scope" => $lock['scope'], - "depth" => $lock['depth'], - "owner" => $owner, - "token" => $lock['token'], - "expires" => $lock['expires'] - ); - if ($lock['scope'] == 'exclusive') - { - // If there is an exclusive lock in the path, it - // takes precedence over all non-exclusive locks in - // parent nodes. Therefore we can can finish collecting - // locks. - break; - } - } - } - } - $this->writelog('checkLock('.$path.'):'.var_export($result,true)); - - return $result; - } - - /** - * Returns the login for the specified user id, or null if - * the user does not exist. - */ - protected function getLogin($userId) - { - $login = ilObjUser::_lookupLogin($userId); - $this->writelog('getLogin('.$userId.'):'.var_export($login,true)); - return $login; - } - - - /** - * Gets a DAV object for the specified path. - * - * @param String davPath A DAV path expression. - * @return ilObjectDAV object or null, if the path does not denote an object. - */ - private function getObject($davPath) - { - global $tree; - - // If the second path elements starts with 'file_', the following - // characters of the path element directly identify the ref_id of - // a file object. - $davPathComponents = explode('/',substr($davPath,1)); - - if (count($davPathComponents) > 1 && - substr($davPathComponents[1],0,5) == 'file_') - { - $ref_id = substr($davPathComponents[1],5); - $nodePath = $tree->getNodePath($ref_id, $tree->root_id); - - // Poor IE needs this, in order to successfully display - // PDF documents - header('Pragma: private'); - } - else - { - $nodePath = $this->toNodePath($davPath); - if ($nodePath == null && count($davPathComponents) == 1) - { - return ilObjectDAV::createObject(-1,'mountPoint'); - } - } - if (is_null($nodePath)) - { - return null; - } else { - $top = $nodePath[count($nodePath) - 1]; - return ilObjectDAV::createObject($top['child'],$top['type']); - } - } - /** - * Converts a DAV path into an array of DAV objects. - * - * @param String davPath A DAV path expression. - * @return array object or null, if the path does not denote an object. - */ - private function toObjectPath($davPath) - { - $this->writelog('toObjectPath('.$davPath); - global $tree; - - $nodePath = $this->toNodePath($davPath); - - if (is_null($nodePath)) - { - return null; - } else { - $objectPath = array(); - foreach ($nodePath as $node) - { - $pathElement = ilObjectDAV::createObject($node['child'],$node['type']); - if (is_null($pathElement)) - { - break; - } - $objectPath[] = $pathElement; - } - return $objectPath; - } - } - - /** - * Converts a DAV path into a node path. - * The returned array is granted to represent an absolute path. - * - * The first component of a DAV Path is the ILIAS client id. The following - * component either denote an absolute path, or a relative path starting at - * a ref_id. - * - * @param String davPath A DAV path expression. - * @return Array An Array of path titles. - */ - public function toNodePath($davPath) - { - global $tree; - $this->writelog('toNodePath('.$davPath.')...'); - - // Split the davPath into path titles - $titlePath = explode('/',substr($davPath,1)); - - // Remove the client id from the beginning of the title path - if (count($titlePath) > 0) - { - array_shift($titlePath); - } - - // If the last path title is empty, remove it - if (count($titlePath) > 0 && $titlePath[count($titlePath) - 1] == '') - { - array_pop($titlePath); - } - - // If the path is empty, return null - if (count($titlePath) == 0) - { - $this->writelog('toNodePath('.$davPath.'):null, because path is empty.'); - return null; - } - - // If the path is an absolute path, ref_id is null. - $ref_id = null; - - // If the path is a relative folder path, convert it into an absolute path - if (count($titlePath) > 0 && substr($titlePath[0],0,4) == 'ref_') - { - $ref_id = substr($titlePath[0],4); - array_shift($titlePath); - } - - $nodePath = $tree->getNodePathForTitlePath($titlePath, $ref_id); - - $this->writelog('toNodePath():'.var_export($nodePath,true)); - return $nodePath; - } - - /** - * davDeslashify - make sure path does not end in a slash - * - * @param string directory path - * @returns string directory path without trailing slash - */ - private function davDeslashify($path) - { - $path = UtfNormal::toNFC($path); - - if ($path[strlen($path)-1] == '/') { - $path = substr($path,0, strlen($path) - 1); - } - $this->writelog('davDeslashify:'.$path); - return $path; - } - - /** - * Private implementation of PHP basename() function. - * The PHP basename() function does not work properly with filenames that contain - * international characters. - * e.g. basename('/x/ö') returns 'x' instead of 'ö' - */ - private function davBasename($path) - { - $components = explode('/',$path); - return count($components) == 0 ? '' : $components[count($components) - 1]; - } - - /** - * Writes a message to the logfile., - * - * @param message String. - * @return void. - */ - protected function writelog($message) - { - // Only write log message, if we are in debug mode - if ($this->isDebug) - { - global $ilLog, $ilias; - if ($ilLog) - { - if ($message == '---') - { - $ilLog->write(''); - } else { - $ilLog->write( - $ilias->account->getLogin() - .' '.$_SERVER['REMOTE_ADDR'].':'.$_SERVER['REMOTE_PORT'] - .' ilDAVServer.'.str_replace("\n",";",$message) - ); - } - } - else - { - $fh = fopen('/opt/ilias/log/ilias.log', 'a'); - fwrite($fh, date('Y-m-d H:i:s ')); - fwrite($fh, str_replace("\n",";",$message)); - fwrite($fh, "\n\n"); - fclose($fh); - } - } - } - - /** - * Returns an URI for mounting the repository object as a webfolder. - * The URI can be used as the value of a "href" attribute attribute - * inside of an HTML anchor tag "". - * - * @param refId of the repository object. - * @param nodeId of a childnode of the repository object. - * @param ressourceName ressource name (if known), to reduce SQL queries - * @param parentRefId refId of parent object (if known), to reduce SQL queries - * @param genericURI boolean Returns a generic mount URI, which works on - * all platforms which support WebDAV as in the IETF specification. - */ - function getMountURI($refId, $nodeId = 0, $ressourceName = null, $parentRefId = null, $genericURI = false) - { - if ($genericURI) { - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"); - $query = null; - } else if ($this->clientOS == 'windows') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"); - $query = 'mount-instructions'; - } else if ($this->clientBrowser == 'konqueror') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "webdavs:" : "webdav:"); - $query = null; - } else if ($this->clientBrowser == 'nautilus') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "davs:" : "dav:"); - $query = null; - } else { - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"); - $query = 'mount-instructions'; - } - $baseUri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; - $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; - - $uri = $baseUri.'/ref_'.$refId.'/'; - if ($query != null) - { - $uri .= '?'.$query; - } - - return $uri; - } - /** - * Returns an URI for mounting the repository object as a webfolder using Internet Explorer - * and Firefox with the "openwebfolder" plugin. - * The FolderURI is only in effect on Windows. Therefore we don't need to deal with other - * pecularities. - * - * The URI can be used as the value of a "folder" attribute - * inside of an HTML anchor tag "". - * - * @param refId of the repository object. - * @param nodeId of a childnode of the repository object. - * @param ressourceName ressource name (if known), to reduce SQL queries - * @param parentRefId refId of parent object (if known), to reduce SQL queries - */ - function getFolderURI($refId, $nodeId = 0, $ressourceName = null, $parentRefId = null) - { - if ($this->clientOS == 'windows') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"); - $query = null; - } else if ($this->clientBrowser == 'konqueror') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "webdavs:" : "webdav:"); - $query = null; - } else if ($this->clientBrowser == 'nautilus') { - $baseUri = ($this->isWebDAVoverHTTPS() ? "davs:" : "dav:"); - $query = null; - } else { - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"); - $query = null; - } - $baseUri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; - $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; - - $uri = $baseUri.'/ref_'.$refId.'/'; - if ($query != null) - { - $uri .= '?'.$query; - } - - return $uri; - } - /** - * Returns an URI for getting a object using WebDAV by its name. - * - * WebDAV clients can use this URI to access the object from ILIAS. - * - * @param refId of the object. - * @param ressourceName object title (if known), to reduce SQL queries - * @param parentRefId refId of parent object (if known), to reduce SQL queries - * - * @return Returns the URI or null if the URI can not be constructed. - */ - public function getObjectURI($refId, $ressourceName = null, $parentRefId = null) - { - $nodeId = 0; - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"). - "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; - $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; - - if (! is_null($ressourceName) && ! is_null($parentRefId)) - { - // Quickly create URI from the known data without needing SQL queries - $uri = $baseUri.'/ref_'.$parentRefId.'/'.$this->davUrlEncode($ressourceName); - } else { - // Create URI and use some SQL queries to get the missing data - global $tree; - $nodePath = $tree->getNodePath($refId); - - if (is_null($nodePath) || count($nodePath) < 2) - { - // No object path? Return null - file is not in repository. - $uri = null; - } else { - $uri = $baseUri.'/ref_'.$nodePath[count($nodePath) - 2]['child'].'/'. - $this->davUrlEncode($nodePath[count($nodePath) - 1]['title']); - } - } - return $uri; - } - - /** - * Returns an URI for getting a file object using WebDAV. - * - * Browsers can use this URI to download a file from ILIAS. - * - * Note: This could be the same URI that is returned by getObjectURI. - * But we use a different URI, because we want to use the regular - * ILIAS authentication method, if no session exists, and we - * want to be able to download a file from the repository, even if - * the name of the file object is not unique. - * - * @param refId of the file object. - * @param ressourceName title of the file object (if known), to reduce SQL queries - * @param parentRefId refId of parent object (if known), to reduce SQL queries - * - * @return Returns the URI or null if the URI can not be constructed. - */ - public function getFileURI($refId, $ressourceName = null, $parentRefId = null) - { - $nodeId = 0; - $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"). - "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; - $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; - - if (! is_null($ressourceName) && ! is_null($parentRefId)) - { - // Quickly create URI from the known data without needing SQL queries - $uri = $baseUri.'/file_'.$refId.'/'.$this->davUrlEncode($ressourceName); - } else { - // Create URI and use some SQL queries to get the missing data - global $tree; - $nodePath = $tree->getNodePath($refId); - - if (is_null($nodePath) || count($nodePath) < 2) - { - // No object path? Return null - file is not in repository. - $uri = null; - } else { - $uri = $baseUri.'/file_'.$nodePath[count($nodePath) - 1]['child'].'/'. - $this->davUrlEncode($nodePath[count($nodePath) - 1]['title']); - } - } - return $uri; - } - - /** - * Returns true, if the WebDAV server transfers data over HTTPS. - * - * @return boolean Returns true if HTTPS is active. - */ - public function isWebDAVoverHTTPS() { - if ($this->isHTTPS == null) { - global $ilSetting; - require_once './Services/Http/classes/class.ilHTTPS.php'; - $https = new ilHTTPS(); - $this->isHTTPS = $https->isDetected() || $ilSetting->get('https'); - } - return $this->isHTTPS; - } - - /** - * Static getter. Returns true, if the WebDAV server is active. - * - * THe WebDAV Server is active, if the variable file_access::webdav_enabled - * is set in the client ini file. (Removed wit 08.2016: , and if PEAR Auth_HTTP is installed). - * - * @return boolean value - */ - public static function _isActive() - { - global $ilClientIniFile; - return $ilClientIniFile->readVariable('file_access','webdav_enabled') == '1'; - } - /** - * Static getter. Returns true, if WebDAV actions are visible for repository items. - * - * @return boolean value - */ - public static function _isActionsVisible() - { - global $ilClientIniFile; - return $ilClientIniFile->readVariable('file_access','webdav_actions_visible') == '1'; - } - - /** - * Gets instructions for the usage of webfolders. - * - * The instructions consist of HTML text with placeholders. - * See _getWebfolderInstructionsFor for a description of the supported - * placeholders. - * - * @return String HTML text with placeholders. - */ - public static function _getDefaultWebfolderInstructions() - { - global $lng; - return $lng->txt('webfolder_instructions_text'); - } - - /** - * Gets Webfolder mount instructions for the specified webfolder. - * - * - * The following placeholders are currently supported: - * - * [WEBFOLDER_TITLE] - the title of the webfolder - * [WEBFOLDER_URI] - the URL for mounting the webfolder with standard - * compliant WebDAV clients - * [WEBFOLDER_URI_IE] - the URL for mounting the webfolder with Internet Explorer - * [WEBFOLDER_URI_KONQUEROR] - the URL for mounting the webfolder with Konqueror - * [WEBFOLDER_URI_NAUTILUS] - the URL for mounting the webfolder with Nautilus - * [IF_WINDOWS]...[/IF_WINDOWS] - conditional contents, with instructions for Windows - * [IF_MAC]...[/IF_MAC] - conditional contents, with instructions for Mac OS X - * [IF_LINUX]...[/IF_LINUX] - conditional contents, with instructions for Linux - * [ADMIN_MAIL] - the mailbox address of the system administrator - - * @param String Title of the webfolder - * @param String Mount URI of the webfolder for standards compliant WebDAV clients - * @param String Mount URI of the webfolder for IE - * @param String Mount URI of the webfolder for Konqueror - * @param String Mount URI of the webfolder for Nautilus - * @param String Operating system: 'windows', 'unix' or 'unknown'. - * @param String Operating system flavor: 'xp', 'vista', 'osx', 'linux' or 'unknown'. - * @return String HTML text. - */ - public static function _getWebfolderInstructionsFor($webfolderTitle, - $webfolderURI, $webfolderURI_IE, $webfolderURI_Konqueror, $webfolderURI_Nautilus, - $os = 'unknown', $osFlavor = 'unknown') - { - global $ilSetting; - - $settings = new ilSetting('file_access'); - $str = $settings->get('custom_webfolder_instructions', ''); - if (strlen($str) == 0 || ! $settings->get('custom_webfolder_instructions_enabled')) - { - $str = ilDAVServer::_getDefaultWebfolderInstructions(); - } - if(is_file('Customizing/clients/'.CLIENT_ID.'/webdavtemplate.htm')){ - $str = fread(fopen('Customizing/clients/'.CLIENT_ID.'/webdavtemplate.htm', "rb"),filesize('Customizing/clients/'.CLIENT_ID.'/webdavtemplate.htm')); - } - $str=utf8_encode($str); - - preg_match_all('/(\\d+)/', $webfolderURI, $matches); - $refID=end($matches[0]); - - $str = str_replace("[WEBFOLDER_ID]", $refID, $str); - $str = str_replace("[WEBFOLDER_TITLE]", $webfolderTitle, $str); - $str = str_replace("[WEBFOLDER_URI]", $webfolderURI, $str); - $str = str_replace("[WEBFOLDER_URI_IE]", $webfolderURI_IE, $str); - $str = str_replace("[WEBFOLDER_URI_KONQUEROR]", $webfolderURI_Konqueror, $str); - $str = str_replace("[WEBFOLDER_URI_NAUTILUS]", $webfolderURI_Nautilus, $str); - $str = str_replace("[ADMIN_MAIL]", $ilSetting->get("admin_email"), $str); - - if(strpos($_SERVER['HTTP_USER_AGENT'],'MSIE')!==false){ - $str = preg_replace('/\[IF_IEXPLORE\](?:(.*))\[\/IF_IEXPLORE\]/s','\1', $str); - }else{ - $str = preg_replace('/\[IF_NOTIEXPLORE\](?:(.*))\[\/IF_NOTIEXPLORE\]/s','\1', $str); - } - - switch ($os) - { - case 'windows' : - $operatingSystem = 'WINDOWS'; - break; - case 'unix' : - switch ($osFlavor) - { - case 'osx' : - $operatingSystem = 'MAC'; - break; - case 'linux' : - $operatingSystem = 'LINUX'; - break; - default : - $operatingSystem = 'LINUX'; - break; - } - break; - default : - $operatingSystem = 'UNKNOWN'; - break; - } - - if ($operatingSystem != 'UNKNOWN') - { - $str = preg_replace('/\[IF_'.$operatingSystem.'\](?:(.*))\[\/IF_'.$operatingSystem.'\]/s','\1', $str); - $str = preg_replace('/\[IF_([A-Z_]+)\](?:(.*))\[\/IF_\1\]/s','', $str); - } - else - { - $str = preg_replace('/\[IF_([A-Z_]+)\](?:(.*))\[\/IF_\1\]/s','\2', $str); - } - return $str; - } - - /** - * Gets the maximum permitted upload filesize from php.ini in bytes. - * - * @return int Upload Max Filesize in bytes. - */ - private function getUploadMaxFilesize() { - $val = ini_get('upload_max_filesize'); - - $val = trim($val); - $last = strtolower($val[strlen($val)-1]); - switch($last) { - // The 'G' modifier is available since PHP 5.1.0 - case 'g': - $val *= 1024; - case 'm': - $val *= 1024; - case 'k': - $val *= 1024; - } - - return $val; - } -} -// END WebDAV -?> \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilDAVUtils.php b/Services/WebDAV/classes/class.ilDAVUtils.php deleted file mode 100644 index db548004310c..000000000000 --- a/Services/WebDAV/classes/class.ilDAVUtils.php +++ /dev/null @@ -1,65 +0,0 @@ - -* -* @version $Id$ -* -* @ingroup ServicesWebDAV -*/ -class ilDAVUtils -{ - private static $instance = null; - - private $pwd_instruction = null; - - /** - * Singleton constructor - * @return - */ - private function __construct() - { - - } - - /** - * Get singleton instance - * @return object ilDAVUtils - */ - public static function getInstance() - { - if(self::$instance) - { - return self::$instance; - } - return self::$instance = new ilDAVUtils(); - } - - /** - * - * @return - */ - public function isLocalPasswordInstructionRequired() - { - global $DIC; - $ilUser = $DIC['ilUser']; - - if($this->pwd_instruction !== NULL) - { - return $this->pwd_instruction; - } - include_once './Services/Authentication/classes/class.ilAuthUtils.php'; - $status = ilAuthUtils::supportsLocalPasswordValidation($ilUser->getAuthMode(true)); - if($status != ilAuthUtils::LOCAL_PWV_USER) - { - return $this->pwd_instruction = false; - } - // Check if user has local password - return $this->pwd_instruction = (bool) !strlen($ilUser->getPasswd()); - } - -} -?> \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilObjCategoryDAV.php b/Services/WebDAV/classes/class.ilObjCategoryDAV.php deleted file mode 100644 index 868eb52e15cc..000000000000 --- a/Services/WebDAV/classes/class.ilObjCategoryDAV.php +++ /dev/null @@ -1,135 +0,0 @@ -obj->getTranslations(); - - // .. then delete old translation entries, ... - $this->obj->removeTranslations(); - - // ...and finally write new translations to object_translation - for ($i = 0; $i < count($trans["Fobject"]); $i++) - { - // first entry is always the default language - $default = ($i == 0) ? 1 : 0; - $val = $trans["Fobject"][$i]; - - $this->obj->addTranslation($this->obj->getTitle(),$val["desc"],$val["lang"],$default); - } - } - /** - * Creates a dav collection as a child of this object. - * - * @param string the name of the collection. - * @return ilObjectDAV returns the created collection, or null if creation failed. - */ - function createCollection($name) - { - global $DIC; - $lng = $DIC['lng']; - $tree = $DIC['tree']; - - $this->lng =& $lng; - - $newObj = new ilObjCategory(0); - $newObj->setType($this->getILIASCollectionType()); - $newObj->setTitle($name); - //$newObj->setDescription(''); - $newObj->create(); - $newObj->createReference(); - $newObj->setPermissions($this->getRefId()); - $newObj->putInTree($this->getRefID()); - $newObj->addTranslation($name,'',$lng->getLangKey(),1); - return new ilObjCategoryDAV($newObj->getRefId(), $newObj); - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjCourseDAV.php b/Services/WebDAV/classes/class.ilObjCourseDAV.php deleted file mode 100644 index b96d2010754a..000000000000 --- a/Services/WebDAV/classes/class.ilObjCourseDAV.php +++ /dev/null @@ -1,99 +0,0 @@ -obj->getOfflineStatus(); - } - - /** - * Reads the object data. - * @return void. - */ - function read() - { - if (is_null($this->obj)) - { - $this->obj = new ilObjCourse($this->getRefId(),true); - $this->obj->read(); - } - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjFileAccessSettings.php b/Services/WebDAV/classes/class.ilObjFileAccessSettings.php index 77913f2ae67f..a44e031fcb55 100644 --- a/Services/WebDAV/classes/class.ilObjFileAccessSettings.php +++ b/Services/WebDAV/classes/class.ilObjFileAccessSettings.php @@ -39,7 +39,7 @@ include_once "./Services/Object/classes/class.ilObject.php"; class ilObjFileAccessSettings extends ilObject -{ +{ /** * Boolean property. Set this to true, to enable WebDAV access to files. */ @@ -128,8 +128,7 @@ public function isWebdavActionsVisible() { return $this->webdavActionsVisible; } - - + /** * Sets the customWebfolderInstructions property. * @@ -153,8 +152,7 @@ public function getCustomWebfolderInstructions() { if (strlen($this->customWebfolderInstructions) == 0) { - require_once 'Services/WebDAV/classes/class.ilDAVServer.php'; - $this->customWebfolderInstructions = ilDAVServer::_getDefaultWebfolderInstructions(); + $this->customWebfolderInstructions = self::_getDefaultWebfolderInstructions(); } return $this->customWebfolderInstructions; } @@ -166,7 +164,7 @@ public function getCustomWebfolderInstructions() */ public function getDefaultWebfolderInstructions() { - return ilDAVServer::_getDefaultWebfolderInstructions(); + return self::_getDefaultWebfolderInstructions(); } /** * Gets the customWebfolderInstructionsEnabled property. @@ -302,10 +300,51 @@ public function read() $settings = new ilSetting('file_access'); $this->inlineFileExtensions = $settings->get('inline_file_extensions',''); $this->customWebfolderInstructionsEnabled = $settings->get('custom_webfolder_instructions_enabled', '0') == '1'; + //$this->webdavSpecialCharsHandling = $settings->get(''); $this->customWebfolderInstructions = $settings->get('custom_webfolder_instructions', ''); } - + /** + * TODO: Check if needed and refactor + * + * Gets instructions for the usage of webfolders. + * + * The instructions consist of HTML text with placeholders. + * See _getWebfolderInstructionsFor for a description of the supported + * placeholders. + * + * @return String HTML text with placeholders. + */ + public static function _getDefaultWebfolderInstructions() + { + global $lng; + return $lng->txt('webfolder_instructions_text'); + } + + /** + * TODO: Check if needed and refactor + * + * Gets the maximum permitted upload filesize from php.ini in bytes. + * + * @return int Upload Max Filesize in bytes. + */ + private function getUploadMaxFilesize() { + $val = ini_get('upload_max_filesize'); + + $val = trim($val); + $last = strtolower($val[strlen($val)-1]); + switch($last) { + // The 'G' modifier is available since PHP 5.1.0 + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } } // END class.ilObjFileAccessSettings // END WebDAV diff --git a/Services/WebDAV/classes/class.ilObjFileAccessSettingsGUI.php b/Services/WebDAV/classes/class.ilObjFileAccessSettingsGUI.php index db56df4fa121..f83a2ac52fd5 100644 --- a/Services/WebDAV/classes/class.ilObjFileAccessSettingsGUI.php +++ b/Services/WebDAV/classes/class.ilObjFileAccessSettingsGUI.php @@ -37,6 +37,8 @@ class ilObjFileAccessSettingsGUI extends ilObjectGUI { const CMD_EDIT_DOWNLOADING_SETTINGS = 'editDownloadingSettings'; + const CMD_EDIT_WEBDAV_SETTINGS = 'editWebDAVSettings'; + /** * @var \ilSetting */ @@ -301,6 +303,45 @@ public function saveDownloadingSettings() { $this->editDownloadingSettings($form); } + protected function initWebDAVSettingsForm() { + global $DIC; + + $lng = $DIC->language(); + + require_once("./Services/Form/classes/class.ilPropertyFormGUI.php"); + require_once("./Services/Form/classes/class.ilCheckboxInputGUI.php"); + require_once("./Services/Form/classes/class.ilRadioGroupInputGUI.php"); + require_once("./Services/Form/classes/class.ilRadioOption.php"); + require_once("./Services/Form/classes/class.ilTextAreaInputGUI.php"); + + $form = new ilPropertyFormGUI(); + $form->setFormAction($DIC->ctrl()->getFormAction($this)); + $form->setTitle($lng->txt("settings")); + + // Enable webdav + $cb_prop = new ilCheckboxInputGUI($lng->txt("enable_webdav"), "enable_webdav"); + $cb_prop->setValue('1'); + $cb_prop->setChecked($this->object->isWebdavEnabled()); + $form->addItem($cb_prop); + + $rgi_prop = new ilRadioGroupInputGUI($lng->txt('webfolder_instructions'), 'custom_webfolder_instructions_choice'); + $rgi_prop->addOption(new ilRadioOption($lng->txt('use_default_instructions'), 'default')); + $rgi_prop->addOption(new ilRadioOption($lng->txt('use_customized_instructions'), 'custom')); + $rgi_prop->setValue($this->object->isCustomWebfolderInstructionsEnabled() ? 'custom' : 'default'); + $form->addItem($rgi_prop); + $tai_prop = new ilTextAreaInputGUI('', 'custom_webfolder_instructions'); + $tai_prop->setValue($this->object->getCustomWebfolderInstructions()); + $tai_prop->setInfo($lng->txt("webfolder_instructions_info")); + $tai_prop->setRows(20); + $form->addItem($tai_prop); + + // command buttons + $form->addCommandButton('saveWebDAVSettings', $lng->txt('save')); + $form->addCommandButton('view', $lng->txt('cancel')); + + return $form; + } + /** * Edit settings. @@ -319,38 +360,7 @@ public function editWebDAVSettings() { $ilErr->raiseError($lng->txt("no_permission"), $ilErr->WARNING); } - require_once("./Services/Form/classes/class.ilPropertyFormGUI.php"); - require_once("./Services/Form/classes/class.ilCheckboxInputGUI.php"); - require_once("./Services/Form/classes/class.ilRadioGroupInputGUI.php"); - require_once("./Services/Form/classes/class.ilRadioOption.php"); - require_once("./Services/Form/classes/class.ilTextAreaInputGUI.php"); - require_once("./Services/WebDAV/classes/class.ilDAVServer.php"); - - $form = new ilPropertyFormGUI(); - $form->setFormAction($ilCtrl->getFormAction($this)); - $form->setTitle($lng->txt("settings")); - - // Enable webdav - $ilDAVServer = ilDAVServer::getInstance(); - $cb_prop = new ilCheckboxInputGUI($lng->txt("enable_webdav"), "enable_webdav"); - $cb_prop->setValue('1'); - $cb_prop->setChecked($this->object->isWebdavEnabled()); - $form->addItem($cb_prop); - - $rgi_prop = new ilRadioGroupInputGUI($lng->txt('webfolder_instructions'), 'custom_webfolder_instructions_choice'); - $rgi_prop->addOption(new ilRadioOption($lng->txt('use_default_instructions'), 'default')); - $rgi_prop->addOption(new ilRadioOption($lng->txt('use_customized_instructions'), 'custom')); - $rgi_prop->setValue($this->object->isCustomWebfolderInstructionsEnabled() ? 'custom' : 'default'); - $form->addItem($rgi_prop); - $tai_prop = new ilTextAreaInputGUI('', 'custom_webfolder_instructions'); - $tai_prop->setValue($this->object->getCustomWebfolderInstructions()); - $tai_prop->setInfo($lng->txt("webfolder_instructions_info")); - $tai_prop->setRows(20); - $form->addItem($tai_prop); - - // command buttons - $form->addCommandButton('saveWebDAVSettings', $lng->txt('save')); - $form->addCommandButton('view', $lng->txt('cancel')); + $form = $this->initWebDAVSettingsForm(); $tpl->setContent($form->getHTML()); } @@ -359,25 +369,34 @@ public function editWebDAVSettings() { /** * Save settings */ - public function saveWebDAVSettings() { - global $DIC; + public function saveWebDAVSettings() { + global $DIC; $rbacsystem = $DIC['rbacsystem']; $ilErr = $DIC['ilErr']; $ilCtrl = $DIC['ilCtrl']; $lng = $DIC['lng']; if (!$rbacsystem->checkAccess("write", $this->object->getRefId())) { - $ilErr->raiseError($lng->txt("no_permission"), $ilErr->WARNING); + ilUtil::sendFailure($DIC->language()->txt('no_permission'), true); + $DIC->ctrl()->redirect($this, self::CMD_EDIT_WEBDAV_SETTINGS); + } + + $form = $this->initWebDAVSettingsForm(); + if($form->checkInput()) + { + $this->object->setWebdavEnabled($_POST['enable_webdav'] == '1'); + // $this->object->setWebdavActionsVisible($_POST['webdav_actions_visible'] == '1'); + $this->object->setCustomWebfolderInstructionsEnabled($_POST['custom_webfolder_instructions_choice'] == 'custom'); + $this->object->setCustomWebfolderInstructions(ilUtil::stripSlashes($_POST['custom_webfolder_instructions'], false)); + $this->object->update(); + ilUtil::sendSuccess($lng->txt('settings_saved'), true); + $ilCtrl->redirect($this, self::CMD_EDIT_WEBDAV_SETTINGS); + } + else + { + $form->setValuesByPost(); + $tpl->setContent($form->getHTML()); } - - $this->object->setWebdavEnabled($_POST['enable_webdav'] == '1'); - // $this->object->setWebdavActionsVisible($_POST['webdav_actions_visible'] == '1'); - $this->object->setCustomWebfolderInstructionsEnabled($_POST['custom_webfolder_instructions_choice'] == 'custom'); - $this->object->setCustomWebfolderInstructions(ilUtil::stripSlashes($_POST['custom_webfolder_instructions'], false)); - $this->object->update(); - - ilUtil::sendInfo($lng->txt('settings_saved'), true); - $ilCtrl->redirect($this, "editWebDAVSettings"); } @@ -441,7 +460,6 @@ public function editDiskQuotaSettings() { require_once("./Services/Form/classes/class.ilRadioGroupInputGUI.php"); require_once("./Services/Form/classes/class.ilRadioOption.php"); require_once("./Services/Form/classes/class.ilTextAreaInputGUI.php"); - require_once("./Services/WebDAV/classes/class.ilDAVServer.php"); $lng->loadLanguageModule("file"); diff --git a/Services/WebDAV/classes/class.ilObjFileDAV.php b/Services/WebDAV/classes/class.ilObjFileDAV.php deleted file mode 100644 index 799efc971a88..000000000000 --- a/Services/WebDAV/classes/class.ilObjFileDAV.php +++ /dev/null @@ -1,218 +0,0 @@ -isDebug = true; - } - - /** - * Initializes the object after it has been converted from the NULL type. - * We create all the additonal object data that is needed, to make the object work. - * - * @return void. - */ - function initFromNull() - { - $this->obj->setFileName($this->getResourceName()); - $this->obj->setFileType($this->obj->guessFileType()); - $this->write(); - $this->obj->setPermissions($this->getRefId()); - } - /** - * Creates a new version of the object. - * Only objects which support versioning need to implement this method. - */ - function createNewVersion() { - $this->obj->setVersion($this->obj->getVersion() + 1); - ilHistory::_createEntry($this->obj->getId(), "replace", - $this->obj->getFileName().",".$this->obj->getVersion()); - } - - - /** - * Returns the display name of this object. - * Precondition: Object must have been read. - * @return String. - * FIXME - Method deactivated. We don't display the file name as - * display name. Because the file name is not necessarily - * known to the user. - * / - function getDisplayName() - { - return $this->obj->getFileName(); - }*/ - - /** - * Returns the DAV resource type of this object. - * - * @return String "collection" or "". - */ - function getResourceType() - { - return ""; - } - - /** - * Returns the mime type of the content of this object. - * @return String. - */ - function getContentType() - { - //return $this->obj->getFileType(); - return $this->obj->guessFileType(); - } - /** - * Sets the mime type of the content of this object. - * @param String. - */ - function setContentType($type) - { - $this->obj->setFileType($type); - } - /** - * Sets the length (in bytes) of the content of this object. - * @param Integer. - */ - function setContentLength($length) - { - $this->writeLog('setContentLength('.$length.')'); - $this->obj->setFileSize($length); - } - /** - * Returns the number of bytes of the content. - * @return int. - */ - function getContentLength() - { - return ilObjFile::_lookupFileSize($this->obj->getId()); - } - /** - * Returns the content of the object as a stream. - * @return Stream or null, if the content does not support streaming. - */ - function getContentStream() - { - $file = $this->obj->getFile(); - return (file_exists($file)) ? fopen($file,'r') : null; - } - /** - * Returns an output stream to the content. - * @return Stream or null, if the content does not support streaming. - */ - function getContentOutputStream() - { - $file = $this->obj->getFile(); - $parent = dirname($file); - if (! file_exists($parent)) - { - ilUtil::makeDirParents($parent); - } - - return fopen($file,'w'); - } - /** - * Returns the length of the content output stream. - *

- * This method is used by the ilDAVServer, if a PUT operation - * has been performed for which the client did not specify the - * content length. - * - * @param Integer. - */ - function getContentOutputStreamLength() - { - $file = $this->obj->getFile(); - return file_exists($file) ? filesize($file) : 0; - - } - /** - * Returns the content of the object as a byte array. - * @return Array, String. Return null if the content can not be delivered - * as data. - */ - function getContentData() - { - return null; - } - /** - * Reads the object data. - * @return void. - */ - function read() - { - if (is_null($this->obj)) - { - $this->obj = new ilObjFile($this->getRefId(),true); - $this->obj->read(); - } - } - /** - * Writes the object data. - * @return void. - */ - function write() - { - $this->isNewFile = $this->obj->getVersion() == 0; - if ($this->isNewFile) - { - $this->obj->setVersion(1); - } - parent::write(); - /* - ilHistory::_createEntry($this->getObjectId(), 'update', '', '','', true); - */ - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjFolderDAV.php b/Services/WebDAV/classes/class.ilObjFolderDAV.php deleted file mode 100644 index eb54a7cc924a..000000000000 --- a/Services/WebDAV/classes/class.ilObjFolderDAV.php +++ /dev/null @@ -1,78 +0,0 @@ - diff --git a/Services/WebDAV/classes/class.ilObjGroupDAV.php b/Services/WebDAV/classes/class.ilObjGroupDAV.php deleted file mode 100644 index a56938026b9d..000000000000 --- a/Services/WebDAV/classes/class.ilObjGroupDAV.php +++ /dev/null @@ -1,90 +0,0 @@ -obj)) - { - $this->obj = new ilObjGroup($this->getRefId(),true); - $this->obj->read(); - } - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjMountPointDAV.php b/Services/WebDAV/classes/class.ilObjMountPointDAV.php deleted file mode 100644 index 38bf519768b6..000000000000 --- a/Services/WebDAV/classes/class.ilObjMountPointDAV.php +++ /dev/null @@ -1,184 +0,0 @@ -. Returns an empty array, if this object is not - * a collection.. - */ - function children() - { - global $DIC; - $tree = $DIC['tree']; - - $childrenDAV = array(); - $data =& $tree->getNodeData($tree->getRootId()); - $childDAV =& ilObjectDAV::createObject($data['ref_id'],'root'); - if (! is_null($childDAV)) - { - // Note: We must not assign with =& here, because this will cause trouble - // when other functions attempt to work with the $childrenDAV array. - $childrenDAV[] = $childDAV; - } - return $childrenDAV; - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjNull.php b/Services/WebDAV/classes/class.ilObjNull.php deleted file mode 100644 index 42a7d2aaeacd..000000000000 --- a/Services/WebDAV/classes/class.ilObjNull.php +++ /dev/null @@ -1,137 +0,0 @@ -type = "null"; - parent::__construct($a_id,$a_call_by_reference); - } - /** - * update object in db - * - * Note: This is mostly the same as method update in class.ilObject.php. - * In addition to updating descriptional properties, we also update the - * "type" property. This is needed, because a Null Resource can be converted - * into another resource type using a PUT or a MKCOL request by a WebDAV client. - * - * @access public - * @return boolean true on success - */ - function update() - { - parent::update(); - - $q = "UPDATE object_data" - ." SET" - ." type = '".ilUtil::prepareDBString($this->getType())."'" - ." WHERE obj_id = '".$this->getId()."'"; - $this->ilias->db->query($q); - - return true; - } - -} // END class.ilObjNull -// END WebDAV: Null Object. -?> diff --git a/Services/WebDAV/classes/class.ilObjNullDAV.php b/Services/WebDAV/classes/class.ilObjNullDAV.php deleted file mode 100644 index 0740067fcb1a..000000000000 --- a/Services/WebDAV/classes/class.ilObjNullDAV.php +++ /dev/null @@ -1,157 +0,0 @@ -obj)) - { - $this->obj = new ilObjNull($this->getRefId(),true); - $this->obj->read(); - } - } - - /** - * Converts this object to the specified ILIAS type. - * - * @param refid of the parent object - * @param ILIAS type - */ - function convertToILIASType($refId, $type) - { - $this->obj->setType($type); - $this->write(); - $this->obj->setPermissions($refId); - $this->writelog('convertToILIASType '.$type.' obj='.$this->getObjectId()); - $converted =& ilObjectDAV::createObject($this->getRefId(), $type); - $converted->obj->createProperties(); - return $converted; - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjRootDAV.php b/Services/WebDAV/classes/class.ilObjRootDAV.php deleted file mode 100644 index 67d2f8ddd8d2..000000000000 --- a/Services/WebDAV/classes/class.ilObjRootDAV.php +++ /dev/null @@ -1,145 +0,0 @@ -data['obj_id']; - } - /** - * Returns the file name of this object. - * Precondition: Object must have been read. - * @return String. - */ - function getResourceName() - { - //return ''; - return $this->data['title']; - } - /** - * Returns the file name of this object. - * Precondition: Object must have been read. - * @return String. - */ - function getDisplayName() - { - //return 'ILIAS'; - return $this->data['title']; - } - /** - * Returns the creation timestamp of this object. - * Precondition: Object must have been read. - * @return int Unix timestamp. - */ - function getCreationTimestamp() - { - return strtotime($this->data['create_date']); - } - - /** - * Returns the modification timestamp of this object. - * Precondition: Object must have been read. - * @return int Unix timestamp. - */ - function getModificationTimestamp() - { - return strtotime($this->data['last_update']); - } - /** - * Returns the DAV resource type of this object. - * - * @return String "collection" or "". - */ - function getResourceType() - { - return "collection"; - } - /** - * Returns 'cat' as the ilias object type for collections that can be - * created as children of this object. - */ - function getILIASCollectionType() - { - return 'cat'; - } - - /** - * Returns the mime type of the content of this object. - * @return String. - */ - function getContentType() - { - return 'httpd/unix-directory'; - } - /** - * Returns the number of bytes of the content. - * @return int. - */ - function getContentLength() - { - return 0; - } - /** - * Reads the object data. - * @return void. - */ - function read() - { - global $DIC; - $tree = $DIC['tree']; - $this->data = $tree->getNodeData($this->getRefId()); - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilObjectDAV.php b/Services/WebDAV/classes/class.ilObjectDAV.php deleted file mode 100644 index 9d76e3270b9b..000000000000 --- a/Services/WebDAV/classes/class.ilObjectDAV.php +++ /dev/null @@ -1,899 +0,0 @@ -writelog('('.$refId.','.get_class($obj).')'); - } - $this->refId = $refId; - $this->obj =& $obj; - } - - - /** - * Returns the ref id of this object. - * @return int. - */ - function getRefId() - { - return $this->refId; - } - /** - * Returns the object id of this object. - * @return int. - */ - function getObjectId() - { - return ($this->obj == null) ? null : $this->obj->getId(); - } - - /** - * Returns the node id of this object. - * This only used by objects that are represented as a single object in RBAC, but - * as multiple objects in WebDAV. - * @return int. - */ - function getNodeId() - { - return 0; - } - - /** - * Initializes the object after it has been converted from NULL. - * We create all the additonal object data that is needed, to make the object work. - * - * @return void. - */ - function initFromNull() - { - $this->obj->setPermissions($this->getRefId()); - } - - - - - /** - * Reads the object data. - * @return void. - */ - function read() - { - global $DIC; - $ilias = $DIC['ilias']; - - if (is_null($this->obj)) - { - $this->obj =& $ilias->obj_factory->getInstanceByRefId($this->getRefId()); - $this->obj->read(); - } - } - /** - * Writes the object data. - * @return void. - */ - function write() - { - $this->writelog('write() refid='.$this->refId); - $this->obj->update(); - } - - - /** - * Returns the resource name of this object. - * Precondition: Object must have been read. - * @return String. - */ - function getResourceName() - { - return $this->obj->getUntranslatedTitle(); - } - /** - * Sets the resource name of this object. - * Precondition: Object must have been read. - * @parm String. - */ - function setResourceName($name) - { - $this->writelog('setResourceName('.$name.')'); - return $this->obj->setTitle($name); - } - /** - * Returns the display name of this object. - * Precondition: Object must have been read. - * @return String. - */ - function getDisplayName() - { - return $this->obj->getTitle(); - } - - /** - * Returns the creation date of this object as a Unix timestamp. - * Precondition: Object must have been read. - * @return int. - */ - function getCreationTimestamp() - { - return strtotime($this->obj->getCreateDate()); - } - - /** - * Returns the modification date of this object as a Unix timestamp. - * Precondition: Object must have been read. - * @return int. - */ - function getModificationTimestamp() - { - return strtotime($this->obj->getLastUpdateDate()); - } - - /** - * Returns the DAV resource type of this object. - * - * @return String "collection", "" (file) or "null". - */ - function getResourceType() - { - return ""; - } - - /** - * Returns true if this object is a DAV collection. - * - * @return bool. - */ - function isCollection() - { - return $this->getResourceType() == 'collection'; - } - /** - * Returns true if this object is a DAV file. - * - * @return bool. - */ - function isFile() - { - return $this->getResourceType() == ''; - } - /** - * Returns true if this is a null resource. - * Null objects are used for locking names. - */ - function isNullResource() - { - return $this->getResourceType() == 'null'; - } - - /** - * Returns the mime type of the content of this object. - * @return String. - */ - function getContentType() - { - return 'application/x-non-readable';//'application/octet-stream'; - } - /** - * Sets the mime type of the content of this object. - * @param String. - */ - function setContentType($type) - { - // subclass responsibility - } - /** - * Sets the length (number of bytes) of the content of this object. - * @param Integer. - */ - function setContentLength($length) - { - // subclass responsibility - } - /** - * Returns the number of bytes of the content. - * @return int. - */ - function getContentLength() - { - return 0; - } - /** - * Returns the content of the object as a stream. - * @return Stream or null, if the content does not support streaming. - */ - function getContentStream() - { - return null; - } - /** - * Returns an output stream to the content. - * @return Stream or null, if the content does not support streaming. - */ - function getContentOutputStream() - { - return null; - } - /** - * Returns the length of the content output stream. - *

- * This method is used by the ilDAVServer, if a PUT operation - * has been performed for which the client did not specify the - * content length. - * - * @param Integer. - */ - function getContentOutputStreamLength() - { - // subclass responsibility - } - /** - * Returns the content of the object as a byte array. - * @return Array, String. Return null if the content can not be delivered - * as data. - */ - function getContentData() - { - return null; - } - - /** - * Returns true if the object is online. - */ - function isOnline() - { - return true; - } - - /** - * Returns whether a specific operation is permitted for the current user. - * This method takes all conditions into account that are required to perform - * the specified action on behalf of the current user. - * - * @param string one or more operations, separated by commas (i.e.: visible,read,join) - * @param string the ILIAS type definition abbreviation (i.e.: frm,grp,crs) - * (only needed for 'create' operation'. - * @return boolean returns true if ALL passed operations are given, otherwise false - */ - function isPermitted($operations, $type = '') - { - // Mount instructions are always visible - if(isset($_GET['mount-instructions'])) - { - return true; - } - - // The 'visible' operation is only permitted if the object is online, - // or if the user is also permitted the perform the 'write' operation. - if (false) // old implementation deactivated - { - $ops = explode(',',$operations); - if (in_array('visible',$ops) && ! in_array('write',$ops)) - { - if (! $this->isOnline()) { - $operations .= ',write'; - } - } - - global $DIC; - $rbacsystem = $DIC['rbacsystem']; - return $rbacsystem->checkAccess($operations, $this->getRefId(), $type); - } - else // this one fixes bug #5367 - { - $GLOBALS['DIC']['ilLog']->write('Checking permission for ref_id: '.$this->getRefId()); - $GLOBALS['DIC']['ilLog']->write("Operations: ".print_r($operations,true)); - - global $DIC; - $ilAccess = $DIC['ilAccess']; - $operations = explode(",",$operations.""); - foreach ($operations as $operation) - { - if (!$ilAccess->checkAccess($operation, '', $this->getRefId(), $type)) - { - $GLOBALS['DIC']['ilLog']->write(__METHOD__.': Permission denied for user '.$GLOBALS['DIC']['ilUser']->getId()); - return false; - } - } - return true; - } - } - - /** - * Returns the ilias type of the current object. - */ - function getILIASType() - { - if($this->obj instanceof ilObject) - { - return $this->obj->getType(); - } - $GLOBALS['DIC']['ilLog']->write(__METHOD__.': Invalid object given, class='.get_class($this->obj)); - $GLOBALS['DIC']['ilLog']->logStack(); - } - /** - * Returns the ilias type for collections that can be created as children of this object. - */ - function getILIASCollectionType() - { - return 'fold'; - } - /** - * Returns the ilias type for files that can be created as children of this object. - */ - function getILIASFileType() - { - return 'file'; - } - - /** - * Creates a new version of the object. - * Only objects which support versioning need to implement this method. - */ - function createNewVersion() { - } - - - /** - * Creates a dav collection as a child of this object. - * - * @param string the name of the collection. - * @return ilObjectDAV returns the created collection, or null if creation failed. - */ - function createCollection($name) - { - global $DIC; - $tree = $DIC['tree']; - - // create and insert Folder in tree - require_once 'Modules/Folder/classes/class.ilObjFolder.php'; - $newObj = new ilObjFolder(0); - $newObj->setType($this->getILIASCollectionType()); - $newObj->setTitle($name); - //$newObj->setDescription(''); - $newObj->create(); - $newObj->createReference(); - $newObj->setPermissions($this->getRefId()); - $newObj->putInTree($this->getRefId()); - - require_once 'class.ilObjFolderDAV.php'; - return new ilObjFolderDAV($newObj->getRefId(), $newObj); - } - /** - * Creates a dav file as a child of this object. - * - * @param string the name of the file. - * @return ilObjectDAV returns the created object, or null if creation failed. - */ - function createFile($name) - { - global $DIC; - $tree = $DIC['tree']; - - // create and insert Folder in tree - require_once 'Modules/File/classes/class.ilObjFile.php'; - $newObj = new ilObjFile(0); - $newObj->setType($this->getILIASFileType()); - $newObj->setTitle($name); - $newObj->setFileName($name); - include_once("./Services/Utilities/classes/class.ilMimeTypeUtil.php"); - $mime = ilMimeTypeUtil::getMimeType("", $name, 'application/octet-stream'); - //$newObj->setFileType('application/octet-stream'); - $newObj->setFileType($mime); - //$newObj->setDescription(''); - $newObj->create(); - $newObj->createReference(); - $newObj->setPermissions($this->getRefId()); - $newObj->putInTree($this->getRefId()); - //$newObj->createDirectory(); - - require_once 'class.ilObjFileDAV.php'; - $objDAV = new ilObjFileDAV($newObj->getRefId(), $newObj); - /* - $fs = $objDAV->getContentOutputStream(); - fwrite($fs,' '); - fclose($fs); - */ - return $objDAV; - } - /** - * Creates a dav file as a child of this object. - * - * @param string the name of the file. - * @return ilObjectDAV returns the created object, or null if creation failed. - */ - function createFileFromNull($name, &$nullDAV) - { - global $DIC; - $tree = $DIC['tree']; - - // create and insert Folder in tree - require_once 'Modules/File/classes/class.ilObjFile.php'; - $objDAV =& $nullDAV->convertToILIASType($this->getRefId(), $this->getILIASFileType()); - $objDAV->initFromNull(); - return $objDAV; - } - /** - * Creates a dav null object as a child of this object. - * null objects are used for locking names. - * - * @param string the name of the null object. - * @return ilObjectDAV returns the created object, or null if creation failed. - */ - function createNull($name) - { - global $DIC; - $tree = $DIC['tree']; - - // create and insert Folder in tree - require_once './Services/Object/classes/class.ilObject.php'; - $newObj = new ilObject(0); - $newObj->setType('null'); - $newObj->setTitle($name); - $newObj->create(); - $newObj->createReference(); - $newObj->setPermissions($this->getRefId()); - $newObj->putInTree($this->getRefId()); - - require_once 'class.ilObjNullDAV.php'; - $objDAV = new ilObjNullDAV($newObj->getRefId(), $newObj); - - return $objDAV; - } - - - - /** - * Removes the specified child from this object. - * - * @param ilObjectDAV the child to be removed. - */ - function remove($objDAV) - { - global $DIC; - $tree = $DIC['tree']; - $rbacadmin = $DIC['rbacadmin']; - - $subnodes = $tree->getSubTree($tree->getNodeData($objDAV->getRefId())); - foreach ($subnodes as $node) - { - $rbacadmin->revokePermission($node["child"]); - $affectedUsers = ilUtil::removeItemFromDesktops($node["child"]); - } - $tree->saveSubTree($objDAV->getRefId()); - $tree->deleteTree($tree->getNodeData($objDAV->getRefId())); - } - - /** - * Adds a copy of the specified object as a child to this object. - * - * @param ilObjectDAV the object to be copied. - * @param string the new name of the copy (optional). - * @return A new ilObjectDAV object representing the cloned object. - */ - function addCopy(&$objDAV, $newName = null) - { - $this->writelog("addCopy($objDAV,$newName) ...."); - global $DIC; - $rbacadmin = $DIC['rbacadmin']; - $tree = $DIC['tree']; - $revIdMapping = array(); - $newRef = $this->cloneNodes($objDAV->getRefId(),$this->getRefId(),$revIdMapping, $newName); - //$rbacadmin->adjustMovedObjectPermissions($newRef, $tree->getParentId($objDAV->getRefId())); - return self::createObject($newRef, $objDAV->getILIASType()); - $this->writelog('... addCopy done.'); - } - - /** - * Recursively clones all nodes of the RBAC tree. - * - * @access private - * @param integer ref_id of source object - * @param integer ref_id of destination object - * @param array mapping new_ref_id => old_ref_id - * @param string the new name of the copy (optional). - * @return The ref_id pointing to the cloned object. - */ - function cloneNodes($srcRef,$dstRef,&$mapping, $newName=null) - { - $this->writelog("cloneNodes($srcRef,$dstRef,$mapping,$newName)"); - global $DIC; - $tree = $DIC['tree']; - global $DIC; - $ilias = $DIC['ilias']; - - // clone the source node - $srcObj =& $ilias->obj_factory->getInstanceByRefId($srcRef); - $this->writelog('cloneNodes cloning srcRef='.$srcRef.' dstRef='.$dstRef.'...'); - $newObj = $srcObj->cloneObject($dstRef); - $newRef = $newObj->getRefId(); - - // We must immediately apply a new name to the object, to - // prevent confusion of WebDAV clients about having two objects with identical - // name in the repository. - $this->writelog("cloneNodes newname not null? ".(! is_null($newName))); - if (! is_null($newName)) - { - $newObjDAV = self::createObject($newRef, $srcObj->getType()); - $newObjDAV->setResourceName($newName); - $newObjDAV->write(); - } - unset($srcObj); - $mapping[$newRef] = $srcRef; - - // clone all children of the source node - $children = $tree->getChilds($srcRef); - foreach ($tree->getChilds($srcRef) as $child) - { - // Don't clone role folders, because it does not make sense to clone local roles - // FIXME - Maybe it does make sense (?) - if ($child["type"] != 'rolf') - { - $this->cloneNodes($child["ref_id"],$newRef,$mapping,null); - } - else - { - if (count($rolf = $tree->getChildsByType($newRef,"rolf"))) - { - $mapping[$rolf[0]["ref_id"]] = $child["ref_id"]; - } - } - } - $this->writelog('cloneNodes ...cloned srcRef='.$srcRef.' dstRef='.$dstRef.' newRef='.$newRef); - return $newRef; - } - - /** - * Adds (moves) the specified object as a child to this object. - * The object is removed from its former parent. - * - * @param ilObjectDAV the object to be moved. - * @param string the new name (optional). - */ - function addMove(&$objDAV, $newName = null) - { - global $DIC; - $tree = $DIC['tree']; - global $DIC; - $rbacadmin = $DIC['rbacadmin']; - global $DIC; - $ilias = $DIC['ilias']; - global $DIC; - $log = $DIC['log']; - - $this->writelog('addMove('.$objDAV->getRefId().' to '.$this->getRefId().', newName='.$newName.')'); - - // Step 0:Assign new name to moved object - if (! is_null($newName)) - { - $objDAV->setResourceName($newName); - $objDAV->write(); - } - - // Step 1: Store old parent - $old_parent = $tree->getParentId($objDAV->getRefId()); - - // Step 2: Move the tree - $tree->moveTree($objDAV->getRefId(),$this->getRefId()); - - // Step 3: Repair permissions - $rbacadmin->adjustMovedObjectPermissions($objDAV->getRefId(), $old_parent); - - /* - // STEP 1: Move subtree to trash - $this->writelog('addMove('.$objDAV->getRefId().' to '.$this->getRefId().') step 1: move subtree to trash'); - $subnodes = $tree->getSubTree($tree->getNodeData($objDAV->getRefId())); - foreach ($subnodes as $node) - { - $rbacadmin->revokePermission($node["child"]); - $affectedUsers = ilUtil::removeItemFromDesktops($node["child"]); - } - $tree->saveSubTree($objDAV->getRefId()); - $tree->deleteTree($tree->getNodeData($objDAV->getRefId())); - - // STEP 2: Move subtree to new location - // TODO: this whole put in place again stuff needs revision. Permission settings get lost. - $this->writelog('addMove() step 2: move subtree to new location'); - // put top node to dest - $rbacadmin->revokePermission($subnodes[0]['child']); - $obj_data =& $ilias->obj_factory->getInstanceByRefId($subnodes[0]['child']); - $obj_data->putInTree($this->getRefId()); - $obj_data->setPermissions($this->getRefId()); - array_shift($subnodes); - - // put all sub nodes to their parent (of which we have moved top already to dest). - foreach ($subnodes as $node) - { - $rbacadmin->revokePermission($node['child']); - $obj_data =& $ilias->obj_factory->getInstanceByRefId($node['child']); - $obj_data->putInTree($node['parent']); - $obj_data->setPermissions($node['parent']); - } - - // STEP 3: Remove trashed objects from system - $this->writelog('addMove('.$objDAV->getRefID().') step 3: remove trashed objects from system'); - require_once 'Services/Tree/classes/class.ilTree.php'; - $trashTree = new ilTree(- (int) $objDAV->getRefId()); - $node = $trashTree->getNodeData($objDAV->getRefId()); - $subnodes = $trashTree->getSubTree($node); - - // remember already checked deleted node_ids - $checked[] = -(int) $objDAV->getRefId(); - - // dive in recursive manner in each already deleted subtrees and remove these objects too - $this->removeDeletedNodes($objDAV->getRefId(), $checked, false); - - // delete trash tree - $tree->deleteTree($node); - $this->writelog('addMove('.$objDAV->getRefID().') all 3 steps done'); - */ - } - - /** - * remove already deleted objects within the objects in trash - * recursive function - * - * @access public - * @param integer ref_id of source object - * @param boolean - */ - function removeDeletedNodes($a_node_id, $a_checked, $a_delete_objects = true) - { - global $DIC; - $ilDB = $DIC['ilDB']; - $log = $DIC['log']; - $ilias = $DIC['ilias']; - $tree = $DIC['tree']; - - $query = "SELECT tree FROM tree WHERE parent = ? AND tree < 0 "; - $sta = $ilDB->prepare($query,array('integer','integer')); - $res = $ilDB->execute($sta,array( - $a_node_id, - 0)); - - - while($row = $ilDB->fetchObject($res)) - { - // only continue recursion if fetched node wasn't touched already! - if (!in_array($row->tree,$a_checked)) - { - $deleted_tree = new ilTree($row->tree); - $a_checked[] = $row->tree; - - $row->tree = $row->tree * (-1); - $del_node_data = $deleted_tree->getNodeData($row->tree); - $del_subtree_nodes = $deleted_tree->getSubTree($del_node_data); - - $this->removeDeletedNodes($row->tree,$a_checked); - - if ($a_delete_objects) - { - foreach ($del_subtree_nodes as $node) - { - $node_obj =& $ilias->obj_factory->getInstanceByRefId($node["ref_id"]); - - // write log entry - /*$this->writelog("removeDeletedNodes(), delete obj_id: ".$node_obj->getId(). - ", ref_id: ".$node_obj->getRefId().", type: ".$node_obj->getType().", ". - "title: ".$node_obj->getTitle()); - */ - $node_obj->delete(); - } - } - - $tree->deleteTree($del_node_data); - - // write log entry - //$this->writelog("removeDeletedNodes(), deleted tree, tree_id: ".$del_node_data["tree"].", child: ".$del_node_data["child"]); - } - } - - return true; - } - /** - * Returns the children of this object. - * - * @return Array. Returns an empty array, if this object is not - * a collection.. - */ - function children() - { - // FIXME: Remove duplicate entries from this list, because of RFC2518, chapter 5.2 - // If a duplicate is found, the older object must win. We use the object - // id to determine this. This is based on the assumption, that new objects - // have higher object id's then older objects. - - global $DIC; - $tree = $DIC['tree']; - - $childrenDAV = array(); - // Performance optimization. We sort the children using PHP instead of using the database. - //$childrenData =& $tree->getChilds($this->getRefId(),'title'); - $childrenData =& $tree->getChilds($this->getRefId(),''); - foreach ($childrenData as $data) - { - $childDAV =& self::createObject($data['ref_id'],$data['type']); - if (! is_null($childDAV)) - { - // Note: We must not assign with =& here, because this will cause trouble - // when other functions attempt to work with the $childrenDAV array. - $childrenDAV[] = $childDAV; - } - - } - return $childrenDAV; - } - /** - * Returns the children of this object with the specified permissions. - * - * @param string one or more operations, separated by commas (i.e.: visible,read,join) - * @param string the ILIAS type definition abbreviation (i.e.: frm,grp,crs) - * (only needed for 'create' operation'. - * @return Array. Returns an empty array, if this object is not - * a collection.. - */ - function childrenWithPermission($operations, $type ='') - { - //$this->writelog('@'.$this->getRefId().'.childrenWithPermission('.$operations.','.$type.')'); - $childrenDAV = $this->children(); - $permittedChildrenDAV = array(); - foreach ($childrenDAV as $childDAV) - { - if ($childDAV->isPermitted($operations, $type)) - { - $permittedChildrenDAV[] = $childDAV; - } - - } - //$this->writelog('@'.$this->getRefId().'.childrenWithPermission():'.count($permittedChildrenDAV).' children'); - return $permittedChildrenDAV; - } - - /** - * Static factory method to create a DAV object for a given refId and type. - * - * @param int refID. - * @param String type The ILIAS object type. - * @return ilObjectDAV. Returns null, if no DAV object can be constructed for - * the specified type. - */ - public static function createObject($refId, $type) - { - $newObj = null; - - switch ($type) - { - case 'mountPoint' : - require_once 'class.ilObjMountPointDAV.php'; - $newObj = new ilObjMountPointDAV($refId,null); - break; - case 'root' : - require_once 'class.ilObjRootDAV.php'; - $newObj = new ilObjRootDAV($refId,null); - break; - case 'cat' : - require_once 'class.ilObjCategoryDAV.php'; - $newObj = new ilObjCategoryDAV($refId,null); - break; - case 'fold' : - require_once 'class.ilObjFolderDAV.php'; - $newObj = new ilObjFolderDAV($refId,null); - break; - case 'crs' : - require_once 'class.ilObjCourseDAV.php'; - $newObj = new ilObjCourseDAV($refId,null); - break; - case 'grp' : - require_once 'class.ilObjGroupDAV.php'; - $newObj = new ilObjGroupDAV($refId,null); - break; - case 'file' : - require_once 'class.ilObjFileDAV.php'; - $newObj = new ilObjFileDAV($refId,null); - break; - case 'null' : - require_once 'class.ilObjNullDAV.php'; - $newObj = new ilObjNullDAV($refId,null); - break; - default : - break; - } - if (! is_null($newObj)) - { - $newObj->read(); - } - return $newObj; - } - /** - * Writes a message to the logfile., - * - * @param message String. - * @return void. - */ - function writelog($message) - { - if ($this->isDebug) - { - global $DIC; - $log = $DIC['log']; - $ilias = $DIC['ilias']; - $log->write( - $ilias->account->getLogin() - .' DAV .'.get_class($this).' '.str_replace("\n",";",$message) - ); - /* - $fh = fopen('/opt/ilias/log/ilias.log', 'a'); - fwrite($fh, date('Y-m-d h:i:s ')); - fwrite($fh, str_replace("\n",";",$message)); - fwrite($fh, "\n\n"); - fclose($fh); - */ - } - } - - /** - * This method is needed, because the object class in PHP 5.2 does not - * have a default implementation of this method anymore. - */ - function __toString() { - return get_class($this).'#'.$this->getObjectId(); - } -} -// END WebDAV -?> diff --git a/Services/WebDAV/classes/class.ilWebDAVMountInstructions.php b/Services/WebDAV/classes/class.ilWebDAVMountInstructions.php new file mode 100644 index 000000000000..95bd64b6a806 --- /dev/null +++ b/Services/WebDAV/classes/class.ilWebDAVMountInstructions.php @@ -0,0 +1,222 @@ +settings = $DIC->settings(); + $this->lng = $DIC->language(); + + $this->user_agent = $a_user_agent == '' ? strtolower($_SERVER['HTTP_USER_AGENT']) : $a_user_agent; + $this->request_uri = $a_request_uri == '' ? $_SERVER['REQUEST_URI'] : $a_request_uri; + $this->http_host = $a_http_host == '' ? $_SERVER['HTTP_HOST'] : $a_http_host; + $this->script_name = $a_http_host == '' ? $_SERVER['SCRIPT_NAME'] : $a_script_name; + $this->client_id = $a_http_host == '' ? CLIENT_ID : $a_client_id; + $this->path_to_template = 'Customizing/clients/'.$this->client_id.'/webdavtemplate.htm'; + + $this->ref_id = 0; + foreach(explode('/', $this->request_uri) as $uri_part) + { + if(strpos($uri_part, 'ref_') !== false && $this->ref_id == 0) + { + $this->ref_id = (int)explode('_', $uri_part)[1]; + } + } + if($this->ref_id == 0) + { + throw new Exception('Bad Request: No ref id given!'); + } + else + { + $this->obj_id = ilObject::_lookupObjectId($this->ref_id); + $this->obj_title = ilObject::_lookupTitle($this->obj_id); + } + + $this->base_uri = $this->http_host.$this->script_name.'/'.$this->client_id. '/ref_' . $this->ref_id . '/'; + + $this->protocol_prefixes = array( + 'default' => 'https://', + 'nautilus' => 'webdavs://', + 'konqueror' => 'davs://' + ); + + $this->setValuesFromUserAgent($this->user_agent); + } + + protected function setValuesFromUserAgent($a_user_agent) + { + // Guess operating system, operating system flavor and browser of the webdav client + // + // - We need to know the operating system in order to properly + // hide hidden resources in directory listings. + // + // - We need the operating system flavor and the browser to + // properly support mounting of a webdav folder. + // + if (strpos($a_user_agent,'windows') !== false + || strpos($a_user_agent,'microsoft') !== false) + { + $this->clientOS = 'windows'; + if(strpos($a_user_agent,'nt 5.1') !== false){ + $this->clientOSFlavor = 'xp'; + }else{ + $this->clientOSFlavor = 'nichtxp'; + } + + } else if (strpos($this->user_agent,'darwin') !== false + || strpos($a_user_agent,'macintosh') !== false + || strpos($a_user_agent,'linux') !== false + || strpos($a_user_agent,'solaris') !== false + || strpos($a_user_agent,'aix') !== false + || strpos($a_user_agent,'unix') !== false + || strpos($a_user_agent,'gvfs') !== false // nautilus browser uses this ID + ) + { + $this->clientOS = 'unix'; + if (strpos($a_user_agent,'linux') !== false) + { + $this->clientOSFlavor = 'linux'; + } + else if (strpos($a_user_agent,'macintosh') !== false) + { + $this->clientOSFlavor = 'osx'; + } + } + } + + public function instructionsTplFileExists() + { + return is_file($this->path_to_template); + } + + public function getInstructionsFromTplFile() + { + return fread(fopen($this->path_to_template, "rb"),filesize($this->path_to_template)); + } + + public function getCustomInstructions() + { + return $this->settings->get('custom_webfolder_instructions'); + } + + public function getDefaultInstruction() + { + return $this->lng->txt('webfolder_instructions_text'); + } + + public function getWebfolderTitle() + { + return $this->obj_title; + } + + public function getDefaultUri() + { + return $this->protocol_prefixes['default'].$this->base_uri; + } + + public function getIEUri() + { + // Was in the old webdav the same like default uri and is now still the same + return $this->protocol_prefixes['default'].$this->base_uri; + } + + public function getKonquerorUri() + { + return $this->protocol_prefixes['konqueror'].$this->base_uri; + } + + public function getNautilusUri() + { + return $this->protocol_prefixes['nautilus'].$this->base_uri; + } + + /** + * Gets Webfolder mount instructions for the specified webfolder. + * + * The following placeholders are currently supported: + * + * [WEBFOLDER_TITLE] - the title of the webfolder + * [WEBFOLDER_URI] - the URL for mounting the webfolder with standard + * compliant WebDAV clients + * [WEBFOLDER_URI_IE] - the URL for mounting the webfolder with Internet Explorer + * [WEBFOLDER_URI_KONQUEROR] - the URL for mounting the webfolder with Konqueror + * [WEBFOLDER_URI_NAUTILUS] - the URL for mounting the webfolder with Nautilus + * [IF_WINDOWS]...[/IF_WINDOWS] - conditional contents, with instructions for Windows + * [IF_MAC]...[/IF_MAC] - conditional contents, with instructions for Mac OS X + * [IF_LINUX]...[/IF_LINUX] - conditional contents, with instructions for Linux + * [ADMIN_MAIL] - the mailbox address of the system administrator + * + * @param unknown $a_instruction_tpl + * @return mixed + */ + public function setInstructionPlaceholders($a_instruction_tpl) + { + $a_instruction_tpl = str_replace("[WEBFOLDER_ID]", $this->ref_id, $a_instruction_tpl); + $a_instruction_tpl = str_replace("[WEBFOLDER_TITLE]", $this->obj_title, $a_instruction_tpl); + $a_instruction_tpl = str_replace("[WEBFOLDER_URI]", $this->getDefaultUri(), $a_instruction_tpl); + $a_instruction_tpl = str_replace("[WEBFOLDER_URI_IE]", $this->getIEUri(), $a_instruction_tpl); + $a_instruction_tpl = str_replace("[WEBFOLDER_URI_KONQUEROR]", $this->getKonquerorUri(), $a_instruction_tpl); + $a_instruction_tpl = str_replace("[WEBFOLDER_URI_NAUTILUS]", $this->getNautilusUri(), $a_instruction_tpl); + $a_instruction_tpl = str_replace("[ADMIN_MAIL]", $this->settings->get("admin_email"), $a_instruction_tpl); + + if(strpos($this->user_agent,'MSIE')!==false){ + $a_instruction_tpl = preg_replace('/\[IF_IEXPLORE\](?:(.*))\[\/IF_IEXPLORE\]/s','\1', $a_instruction_tpl); + }else{ + $a_instruction_tpl = preg_replace('/\[IF_NOTIEXPLORE\](?:(.*))\[\/IF_NOTIEXPLORE\]/s','\1', $a_instruction_tpl); + } + + switch ($this->clientOS) + { + case 'windows' : + $operatingSystem = 'WINDOWS'; + break; + case 'unix' : + switch ($this->clientOSFlavor) + { + case 'osx' : + $operatingSystem = 'MAC'; + break; + case 'linux' : + $operatingSystem = 'LINUX'; + break; + default : + $operatingSystem = 'LINUX'; + break; + } + break; + default : + $operatingSystem = 'UNKNOWN'; + break; + } + if ($operatingSystem != 'UNKNOWN') + { + $a_instruction_tpl = preg_replace('/\[IF_'.$operatingSystem.'\](?:(.*))\[\/IF_'.$operatingSystem.'\]/s','\1', $a_instruction_tpl); + $a_instruction_tpl = preg_replace('/\[IF_([A-Z_]+)\](?:(.*))\[\/IF_\1\]/s','', $a_instruction_tpl); + } + else + { + $a_instruction_tpl = preg_replace('/\[IF_([A-Z_]+)\](?:(.*))\[\/IF_\1\]/s','\2', $a_instruction_tpl); + } + + return $a_instruction_tpl; + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php b/Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php new file mode 100644 index 000000000000..f03e9d4ecb4f --- /dev/null +++ b/Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php @@ -0,0 +1,74 @@ +mount_instruction = new ilWebDAVMountInstructions(); + } + + public function showMountInstructionPage() + { + global $DIC; + + $instruction_tpl = $this->getInstructionTemplate(); + $instruction_text = $this->mount_instruction->setInstructionPlaceholders($instruction_tpl); + $this->displayInstructionPage($instruction_text); + + exit; + } + + protected function displayInstructionPage($instruction_text) + { + global $DIC; + + header('Content-Type: text/html; charset=UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + echo " \n"; + echo " ".sprintf($DIC->language()->txt('webfolder_instructions_titletext'), $this->mount_instruction->getWebfolderTitle())."\n"; + echo " \n"; + echo " \n"; + echo $instruction_text; + echo " \n"; + echo "\n"; + } + + protected function getInstructionTemplate() + { + global $DIC; + + $settings = $DIC->settings(); + $instruction_tpl = ''; + + if($this->mount_instruction->instructionsTplFileExists()) + { + $instruction_tpl = $this->mount_instruction->getInstructionsFromTplFile(); + + } + else if($settings->get('custom_webfolder_instructions_enabled')) + { + $instruction_tpl = $this->mount_instruction->getCustomInstruction(); + } + + if(strlen($instruction_tpl) == 0) + { + $instruction_tpl = $this->mount_instruction->getDefaultInstruction(); + } + + return utf8_encode($instruction_tpl); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilWebDAVRequestHandler.php b/Services/WebDAV/classes/class.ilWebDAVRequestHandler.php new file mode 100644 index 000000000000..9fdd7dbcc301 --- /dev/null +++ b/Services/WebDAV/classes/class.ilWebDAVRequestHandler.php @@ -0,0 +1,76 @@ +runWebDAVServer(); + } + + protected function runWebDAVServer() + { + $server = new Sabre\DAV\Server($this->getRootDir()); + $this->setPlugins($server); + + $server->exec(); + } + + + /** + * Set server plugins + */ + protected function setPlugins($server) + { + global $DIC; + + // Set authentication plugin + $webdav_auth = new ilWebDAVAuthentication(); + $cal = new Sabre\DAV\Auth\Backend\BasicCallBack(array($webdav_auth, 'authenticate')); + $plugin = new Sabre\DAV\Auth\Plugin($cal); + $server->addPlugin($plugin); + + // Set Lock Plugin + $db_manager = new ilWebDAVDBManager($DIC->database()); + $lock_backend = new ilWebDAVLockBackend($db_manager, $DIC->user(), $DIC->access()); + $lock_plugin = new Sabre\DAV\Locks\Plugin($lock_backend); + $server->addPlugin($lock_plugin); + + } + + /** + * Return the first object to mount on WebDAV + * + * @return ilMountPointDAV + */ + protected function getRootDir() + { + return new ilMountPointDAV(); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilWebDAVTree.php b/Services/WebDAV/classes/class.ilWebDAVTree.php new file mode 100644 index 000000000000..90b2c5bd96a6 --- /dev/null +++ b/Services/WebDAV/classes/class.ilWebDAVTree.php @@ -0,0 +1,127 @@ +/ref_/folder1/folder2 + * Path starts at root: /ilias/foo_container1/course1/ + * @param string $uri + */ + public static function getRefIdForWebDAVPath($a_uri) + { + $a_uri = strtolower(trim($a_uri, '/')); + + /* After this funciton, the array SHOULD look like this: + * $splitted_path[0] = '' + * $splitted_path[1] = 'ref_' or + * $splitted_path[2] = '/// + */ + $splitted_path = explode('/', $a_uri, 3); + + // Early exit for bad request + if(count($splitted_path) < 2) + { + throw new BadRequest(); + } + + $repository_mountpoint = $splitted_path[1]; + $path_in_mountpoint = $splitted_path[2]; + + // Since we already know our client, we only have to check the requested root for our path + // if second string = 'ilias', the request was for the ilias root + if($repository_mountpoint == 'ilias') + { + if($path_in_mountpoint != '') + { + $ref_path = self::getRefIdForGivenRootAndPath(ROOT_FOLDER_ID, $path_in_mountpoint); + $searched_node = $ref_path[count($ref_path)-1]; + $ref_id = $searched_node['child']; + } + else + { + $ref_id = ROOT_FOLDER_ID; + } + } + // if the first 4 letters are 'ref_', we are searching for a ref ID in the tree + else if(substr($splitted_path[1], 0, 4) == 'ref_') + { + // Make a 'ref_1234' to a '1234' + // Since we already tested for 'ref_', we can be sure there is at least one '_' character + $start_node = (int)explode('_',$repository_mountpoint)[1]; + if($path_in_mountpoint != '' && $start_node > 0) + { + $ref_id = self::getRefIdForGivenRootAndPath($start_node, $path_in_mountpoint); + } + else if($path_in_mountpoint == '') + { + $ref_id = $start_node; + } + else + { + throw new NotFound(); + } + } + // if there was no 'ilias' and no 'ref_' in the second string, this was a bad request... + else + { + throw new BadRequest(); + } + + return $ref_id; + } + + public static function getRefIdForGivenRootAndPath(int $start_ref, string $path_from_startnode) + { + return self::iterateRecursiveThroughTree(explode('/',$path_from_startnode), 0, $start_ref); + } + + protected static function iterateRecursiveThroughTree($path_title_array, $current_path_element, $parent_ref_id) + { + global $DIC; + + if($path_title_array[$current_path_element] == '' || count($path_title_array) == $current_path_element) + { + return $parent_ref_id; + } + + foreach($DIC->repositoryTree()->getChildIds($parent_ref_id) as $child_ref) + { + $child_obj_id = ilObject::_lookupObjectId($child_ref); + $child_title = strtolower(ilObject::_lookupTitle($child_obj_id)); + if($path_title_array[$current_path_element] == $child_title) + { + if(count($path_title_array)-1 == $current_path_element) + { + return $child_ref; + } + else + { + return self::iterateRecursiveThroughTree($path_title_array, $current_path_element+1, $child_ref); + } + } + } + + return -1; + } + +} \ No newline at end of file diff --git a/Services/WebDAV/classes/class.ilWebDAVUtil.php b/Services/WebDAV/classes/class.ilWebDAVUtil.php new file mode 100644 index 000000000000..686910fd5a59 --- /dev/null +++ b/Services/WebDAV/classes/class.ilWebDAVUtil.php @@ -0,0 +1,644 @@ +readVariable('file_access','webdav_actions_visible') == '1'; + } + + /** + * TODO: Check if needed and refactor + * Mount instructions method handler for directories + * + * @param ilObjectDAV dav object handler + * @return This function does not return. It exits PHP. + */ + public function showMountInstructions(&$objDAV, &$options) + { + global $lng,$ilUser; + + $path = $this->davDeslashify($options['path']); + + // The $path variable may contain a full or a shortened DAV path. + // We convert it into an object path, which we can then use to + // construct a new full DAV path. + $objectPath = $this->toObjectPath($path); + + // Construct a (possibly) full DAV path from the object path. + $fullPath = ''; + foreach ($objectPath as $object) + { + if ($object->getRefId() == 1 && $this->isFileHidden($object)) + { + // If the repository root object is hidden, we can not + // create a full path, because nothing would appear in the + // webfolder. We resort to a shortened path instead. + $fullPath .= '/ref_1'; + } + else + { + $fullPath .= '/'.$this->davUrlEncode($object->getResourceName()); + } + } + + // Construct a shortened DAV path from the object path. + $shortenedPath = '/ref_'. + $objectPath[count($objectPath) - 1]->getRefId(); + + if ($objDAV->isCollection()) + { + $shortenedPath .= '/'; + $fullPath .= '/'; + } + + // Prepend client id to path + $shortenedPath = '/'.CLIENT_ID.$shortenedPath; + $fullPath = '/'.CLIENT_ID.$fullPath; + + // Construct webfolder URI's. The URI's are used for mounting the + // webfolder. Since mounting using URI's is not standardized, we have + // to create different URI's for different browsers. + $webfolderURI = $this->base_uri.$shortenedPath; + $webfolderURI_Konqueror = ($this->isWebDAVoverHTTPS() ? "webdavs" : "webdav"). + substr($this->base_uri, strrpos($this->base_uri,':')). + $shortenedPath; + ; + $webfolderURI_Nautilus = ($this->isWebDAVoverHTTPS() ? "davs" : "dav"). + substr($this->base_uri, strrpos($this->base_uri,':')). + $shortenedPath + ; + $webfolderURI_IE = $this->base_uri.$shortenedPath; + + $webfolderTitle = $objectPath[count($objectPath) - 1]->getResourceName(); + + header('Content-Type: text/html; charset=UTF-8'); + echo "\n"; + echo "\n"; + echo "\n"; + echo " \n"; + echo " ".sprintf($lng->txt('webfolder_instructions_titletext'), $webfolderTitle)."\n"; + echo " \n"; + echo " \n"; + + echo ilDAVServer::_getWebfolderInstructionsFor($webfolderTitle, + $webfolderURI, $webfolderURI_IE, $webfolderURI_Konqueror, $webfolderURI_Nautilus, + $this->clientOS,$this->clientOSFlavor); + + echo " \n"; + echo "\n"; + + // Logout anonymous user to force authentication after calling mount uri + if($ilUser->getId() == ANONYMOUS_USER_ID) + { + $GOBALS['DIC']['ilAuthSession']->logout(); + } + + exit; + } + + /** + * TODO: Check if needed and refactor + * checkLock() helper + * + * @param string resource path to check for locks + * @return array with the following entries: { + * type => "write" + * scope => "exclusive" | "shared" + * depth => 0 | -1 + * owner => string + * token => string + * expires => timestamp + */ + protected function checkLock($path) + { + global $ilias; + + $this->writelog('checkLock('.$path.')'); + $result = null; + + // get dav object for path + //$objDAV = $this->getObject($path); + + // convert DAV path into ilObjectDAV path + $objPath = $this->toObjectPath($path); + if (! is_null($objPath)) + { + $objDAV = $objPath[count($objPath) - 1]; + $locks = $this->locks->getLocksOnPathDAV($objPath); + foreach ($locks as $lock) + { + $isLastPathComponent = $lock['obj_id'] == $objDAV->getObjectId() + && $lock['node_id'] == $objDAV->getNodeId(); + + // Check all locks on last object in path, + // but only locks with depth infinity on parent objects. + if ($isLastPathComponent || $lock['depth'] == 'infinity') + { + // DAV Clients expects to see their own owner name in + // the locks. Since these names are not unique (they may + // just be the name of the local user running the DAV client) + // we return the ILIAS user name in all other cases. + if ($lock['ilias_owner'] == $ilias->account->getId()) + { + $owner = $lock['dav_owner']; + } else { + $owner = $this->getLogin($lock['ilias_owner']); + } + + // FIXME - Shouldn't we collect all locks instead of + // using an arbitrary one? + $result = array( + "type" => "write", + "obj_id" => $lock['obj_id'], + "node_id" => $lock['node_id'], + "scope" => $lock['scope'], + "depth" => $lock['depth'], + "owner" => $owner, + "token" => $lock['token'], + "expires" => $lock['expires'] + ); + if ($lock['scope'] == 'exclusive') + { + // If there is an exclusive lock in the path, it + // takes precedence over all non-exclusive locks in + // parent nodes. Therefore we can can finish collecting + // locks. + break; + } + } + } + } + $this->writelog('checkLock('.$path.'):'.var_export($result,true)); + + return $result; + } + + /** + * TODO: Check if needed and refactor + * Returns the login for the specified user id, or null if + * the user does not exist. + */ + protected function getLogin($userId) + { + $login = ilObjUser::_lookupLogin($userId); + $this->writelog('getLogin('.$userId.'):'.var_export($login,true)); + return $login; + } + + + /** + * TODO: Check if needed and refactor + * Gets a DAV object for the specified path. + * + * @param String davPath A DAV path expression. + * @return ilObjectDAV object or null, if the path does not denote an object. + */ + private function getObject($davPath) + { + global $tree; + + // If the second path elements starts with 'file_', the following + // characters of the path element directly identify the ref_id of + // a file object. + $davPathComponents = explode('/',substr($davPath,1)); + + if (count($davPathComponents) > 1 && + substr($davPathComponents[1],0,5) == 'file_') + { + $ref_id = substr($davPathComponents[1],5); + $nodePath = $tree->getNodePath($ref_id, $tree->root_id); + + // Poor IE needs this, in order to successfully display + // PDF documents + header('Pragma: private'); + } + else + { + $nodePath = $this->toNodePath($davPath); + if ($nodePath == null && count($davPathComponents) == 1) + { + return ilObjectDAV::createObject(-1,'mountPoint'); + } + } + if (is_null($nodePath)) + { + return null; + } else { + $top = $nodePath[count($nodePath) - 1]; + return ilObjectDAV::createObject($top['child'],$top['type']); + } + } + /** + * TODO: Check if needed and refactor + * Converts a DAV path into an array of DAV objects. + * + * @param String davPath A DAV path expression. + * @return array object or null, if the path does not denote an object. + */ + private function toObjectPath($davPath) + { + $this->writelog('toObjectPath('.$davPath); + global $tree; + + $nodePath = $this->toNodePath($davPath); + + if (is_null($nodePath)) + { + return null; + } else { + $objectPath = array(); + foreach ($nodePath as $node) + { + $pathElement = ilObjectDAV::createObject($node['child'],$node['type']); + if (is_null($pathElement)) + { + break; + } + $objectPath[] = $pathElement; + } + return $objectPath; + } + } + + /** + * TODO: Check if needed and refactor + * Converts a DAV path into a node path. + * The returned array is granted to represent an absolute path. + * + * The first component of a DAV Path is the ILIAS client id. The following + * component either denote an absolute path, or a relative path starting at + * a ref_id. + * + * @param String davPath A DAV path expression. + * @return Array An Array of path titles. + */ + public function toNodePath($davPath) + { + global $tree; + $this->writelog('toNodePath('.$davPath.')...'); + + // Split the davPath into path titles + $titlePath = explode('/',substr($davPath,1)); + + // Remove the client id from the beginning of the title path + if (count($titlePath) > 0) + { + array_shift($titlePath); + } + + // If the last path title is empty, remove it + if (count($titlePath) > 0 && $titlePath[count($titlePath) - 1] == '') + { + array_pop($titlePath); + } + + // If the path is empty, return null + if (count($titlePath) == 0) + { + $this->writelog('toNodePath('.$davPath.'):null, because path is empty.'); + return null; + } + + // If the path is an absolute path, ref_id is null. + $ref_id = null; + + // If the path is a relative folder path, convert it into an absolute path + if (count($titlePath) > 0 && substr($titlePath[0],0,4) == 'ref_') + { + $ref_id = substr($titlePath[0],4); + array_shift($titlePath); + } + + $nodePath = $tree->getNodePathForTitlePath($titlePath, $ref_id); + + $this->writelog('toNodePath():'.var_export($nodePath,true)); + return $nodePath; + } + + /** + * TODO: Check if needed and refactor + * davDeslashify - make sure path does not end in a slash + * + * @param string directory path + * @returns string directory path without trailing slash + */ + private function davDeslashify($path) + { + $path = UtfNormal::toNFC($path); + + if ($path[strlen($path)-1] == '/') { + $path = substr($path,0, strlen($path) - 1); + } + return $path; + } + + /** + * TODO: Check if needed and refactor + * Private implementation of PHP basename() function. + * The PHP basename() function does not work properly with filenames that contain + * international characters. + * e.g. basename('/x/ö') returns 'x' instead of 'ö' + */ + private function davBasename($path) + { + $components = explode('/',$path); + return count($components) == 0 ? '' : $components[count($components) - 1]; + } + + /** + * TODO: Check if needed and refactor + * Returns an URI for mounting the repository object as a webfolder using Internet Explorer + * and Firefox with the "openwebfolder" plugin. + * The FolderURI is only in effect on Windows. Therefore we don't need to deal with other + * pecularities. + * + * The URI can be used as the value of a "folder" attribute + * inside of an HTML anchor tag "". + * + * @param refId of the repository object. + * @param nodeId of a childnode of the repository object. + * @param ressourceName ressource name (if known), to reduce SQL queries + * @param parentRefId refId of parent object (if known), to reduce SQL queries + */ + public static function getFolderURI($refId, $nodeId = 0, $ressourceName = null, $parentRefId = null) + { + if (self::$clientOS == 'windows') { + $baseUri = "https:"; + $query = null; + } else if (self::$clientBrowser == 'konqueror') { + $baseUri = "webdavs:"; + $query = null; + } else if (self::$clientBrowser == 'nautilus') { + $baseUri = "davs:"; + $query = null; + } else { + $baseUri = "https:"; + $query = null; + } + $baseUri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; + $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; + + $uri = $baseUri.'/ref_'.$refId.'/'; + if ($query != null) + { + $uri .= '?'.$query; + } + + return $uri; + } + + + + /** + * TODO: Check if needed and refactor + * Returns an URI for mounting the repository object as a webfolder. + * The URI can be used as the value of a "href" attribute attribute + * inside of an HTML anchor tag "". + * + * @param refId of the repository object. + * @param nodeId of a childnode of the repository object. + * @param ressourceName ressource name (if known), to reduce SQL queries + * @param parentRefId refId of parent object (if known), to reduce SQL queries + * @param genericURI boolean Returns a generic mount URI, which works on + * all platforms which support WebDAV as in the IETF specification. + */ + public static function getMountURI($refId, $nodeId = 0, $ressourceName = null, $parentRefId = null, $genericURI = false) + { + if ($genericURI) { + $baseUri = "https:"; + $query = null; + } else if (self::$clientOS == 'windows') { + $baseUri = "http:"; + $query = 'mount-instructions'; + } else if (self::$clientBrowser == 'konqueror') { + $baseUri = "webdavs:"; + $query = null; + } else if (self::$clientBrowser == 'nautilus') { + $baseUri = "davs:"; + $query = null; + } else { + $baseUri = "https:"; + $query = 'mount-instructions'; + } + $baseUri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; + $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; + + $uri = $baseUri.'/ref_'.$refId.'/'; + if ($query != null) + { + $uri .= '?'.$query; + } + + return $uri; + } + + /** + * TODO: Check if needed and refactor + * Returns an URI for getting a object using WebDAV by its name. + * + * WebDAV clients can use this URI to access the object from ILIAS. + * + * @param refId of the object. + * @param ressourceName object title (if known), to reduce SQL queries + * @param parentRefId refId of parent object (if known), to reduce SQL queries + * + * @return Returns the URI or null if the URI can not be constructed. + */ + public function getObjectURI($refId, $ressourceName = null, $parentRefId = null) + { + $nodeId = 0; + $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"). + "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; + $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; + + if (! is_null($ressourceName) && ! is_null($parentRefId)) + { + // Quickly create URI from the known data without needing SQL queries + $uri = $baseUri.'/ref_'.$parentRefId.'/'.$this->davUrlEncode($ressourceName); + } else { + // Create URI and use some SQL queries to get the missing data + global $tree; + $nodePath = $tree->getNodePath($refId); + + if (is_null($nodePath) || count($nodePath) < 2) + { + // No object path? Return null - file is not in repository. + $uri = null; + } else { + $uri = $baseUri.'/ref_'.$nodePath[count($nodePath) - 2]['child'].'/'. + $this->davUrlEncode($nodePath[count($nodePath) - 1]['title']); + } + } + return $uri; + } + + /** + * TODO: Check if needed and refactor + * Returns an URI for getting a file object using WebDAV. + * + * Browsers can use this URI to download a file from ILIAS. + * + * Note: This could be the same URI that is returned by getObjectURI. + * But we use a different URI, because we want to use the regular + * ILIAS authentication method, if no session exists, and we + * want to be able to download a file from the repository, even if + * the name of the file object is not unique. + * + * @param refId of the file object. + * @param ressourceName title of the file object (if known), to reduce SQL queries + * @param parentRefId refId of parent object (if known), to reduce SQL queries + * + * @return Returns the URI or null if the URI can not be constructed. + */ + public function getFileURI($refId, $ressourceName = null, $parentRefId = null) + { + $nodeId = 0; + $baseUri = ($this->isWebDAVoverHTTPS() ? "https:" : "http:"). + "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]"; + $baseUri = substr($baseUri,0,strrpos($baseUri,'/')).'/webdav.php/'.CLIENT_ID; + + if (! is_null($ressourceName) && ! is_null($parentRefId)) + { + // Quickly create URI from the known data without needing SQL queries + $uri = $baseUri.'/file_'.$refId.'/'.$this->davUrlEncode($ressourceName); + } else { + // Create URI and use some SQL queries to get the missing data + global $tree; + $nodePath = $tree->getNodePath($refId); + + if (is_null($nodePath) || count($nodePath) < 2) + { + // No object path? Return null - file is not in repository. + $uri = null; + } else { + $uri = $baseUri.'/file_'.$nodePath[count($nodePath) - 1]['child'].'/'. + $this->davUrlEncode($nodePath[count($nodePath) - 1]['title']); + } + } + return $uri; + } + + /** + * TODO: Check if needed and refactor + * Returns true, if the WebDAV server transfers data over HTTPS. + * + * @return boolean Returns true if HTTPS is active. + */ + public function isWebDAVoverHTTPS() { + if ($this->isHTTPS == null) { + global $ilSetting; + require_once './Services/Http/classes/class.ilHTTPS.php'; + $https = new ilHTTPS(); + $this->isHTTPS = $https->isDetected() || $ilSetting->get('https'); + } + return $this->isHTTPS; + } + + /** + * TODO: Check if needed and refactor + * Static getter. Returns true, if the WebDAV server is active. + * + * THe WebDAV Server is active, if the variable file_access::webdav_enabled + * is set in the client ini file. (Removed wit 08.2016: , and if PEAR Auth_HTTP is installed). + * + * @return boolean value + */ + public static function _isActive() + { + global $ilClientIniFile; + return $ilClientIniFile->readVariable('file_access','webdav_enabled') == '1'; + } + + /** + * TODO: Check if needed and refactor + * Gets the maximum permitted upload filesize from php.ini in bytes. + * + * @return int Upload Max Filesize in bytes. + */ + private function getUploadMaxFilesize() { + $val = ini_get('upload_max_filesize'); + + $val = trim($val); + $last = strtolower($val[strlen($val)-1]); + switch($last) { + // The 'G' modifier is available since PHP 5.1.0 + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + private static $instance = null; + + private $pwd_instruction = null; + + /** + * Singleton constructor + * @return + */ + private function __construct() + { + + } + + /** + * Get singleton instance + * @return object ilDAVUtils + */ + public static function getInstance() + { + if(self::$instance) + { + return self::$instance; + } + return self::$instance = new ilWebDAVUtil(); + } + + /** + * + * @return + */ + public function isLocalPasswordInstructionRequired() + { + global $DIC; + $ilUser = $DIC['ilUser']; + + if($this->pwd_instruction !== NULL) + { + return $this->pwd_instruction; + } + include_once './Services/Authentication/classes/class.ilAuthUtils.php'; + $status = ilAuthUtils::supportsLocalPasswordValidation($ilUser->getAuthMode(true)); + if($status != ilAuthUtils::LOCAL_PWV_USER) + { + return $this->pwd_instruction = false; + } + // Check if user has local password + return $this->pwd_instruction = (bool) !strlen($ilUser->getPasswd()); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php b/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php new file mode 100644 index 000000000000..5a6d5262a34e --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilClientNodeDAV.php @@ -0,0 +1,169 @@ + webdav.php <- creates the request handler and initialize ilias + * -> ilWebDAVRequestHandler <- setup the webdav server + * -> ilObjMountPointDAV <- This represents the "root" node and is needed for sabreDAV + * -> ilMountPointDAV <- This class represents the used client (for example here it is my_ilias) + * -> child of ilContainerDAV + * + * @author faheer + * + */ +class ilClientNodeDAV implements Sabre\DAV\ICollection +{ + /** @var $access ilAccessHandler */ + protected $access; + + protected $name_of_repository_root; + + /** + * @param string $client_name + */ + public function __construct(string $client_name) + { + global $DIC; + + $this->access = $DIC->access(); + $this->client_name = $client_name; + $this->name_of_repository_root = 'ILIAS'; + } + + public function setName($name) + { + throw new Forbidden("You cant change the client name"); + } + + public function getChildren() + { + return array($this->getRepositoryRootPoint()); + } + + public function getName() + { + return $this->client_name; + } + + public function getLastModified() + { + return strtotime('2000-01-01'); + } + + public function getChild($name) + { + if($name == $this->name_of_repository_root) + { + return $this->getRepositoryRootPoint(); + } + else + { + return $this->getMountPointByReference($name); + } + + } + + /** + * Create DAV-Object from ref_id + * + * @param string $name + * @throws Forbidden + * @throws BadRequest + * @return ilObjCategoryDAV|ilObjCourseDAV|ilObjGroupDAV|ilObjFolderDAV|ilObjFileDAV + */ + protected function getMountPointByReference($name) + { + $ref_id = $this->getRefIdFromName($name); + + if($ref_id > 0) + { + if($this->access->checkAccess('read', '', $ref_id)) + { + return ilObjectDAV::_createDAVObjectForRefId($ref_id); + } + + throw new Forbidden("No read permission for object with reference ID $ref_id "); + } + + throw new BadRequest("Invalid parameter $ref_parts"); + } + + protected function getRepositoryRootPoint() + { + if($this->access->checkAccess('read', '', ROOT_FOLDER_ID)) + return new ilObjRepositoryRootDAV($this->name_of_repository_root); + throw new Forbidden("No read permission for ilias repository root"); + } + + /** + * Either the given name is the name of the repository root of ILIAS + * or it is a reference to a node in the ILIAS-repo + * + * Returns true if name=name of repository root or if given reference + * exists and user has read permissions to this reference + * + */ + public function childExists($name) + { + if($name == $this->name_of_repository_root) + { + return true; + } + + $ref_id = $this->getRefIdFromName($name); + if($ref_id > 0) + { + return ilObject::_exists($ref_id, true) && $this->access->checkAccess('read', '', $ref_id); + } + return false; + } + + /** + * Gets ref_id from name. Name should look like this: ref_ + * + * @param string $name + * + */ + public function getRefIdFromName($name) + { + $ref_parts = explode('_', $name); + if(count($ref_parts) == 2) + { + $ref_id = (int)$ref_parts[1]; + return $this->checkIfRefIdIsValid($ref_id); + } + + return 0; + } + + protected function checkIfRefIdIsValid($ref_id) + { + if($ref_id > 0 && ilObject::_exists($ref_id, true) && ilObjectDAV::_isDAVableObject($ref_id, true)) + { + return $ref_id; + } + } + + public function createDirectory($name) + { + throw new Forbidden(); + } + + public function delete() + { + throw new Forbidden(); + } + + public function createFile($name, $data = null) + { + throw new Forbidden(); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/dav/class.ilMountPointDAV.php b/Services/WebDAV/classes/dav/class.ilMountPointDAV.php new file mode 100644 index 000000000000..ddf3d4dc67d9 --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilMountPointDAV.php @@ -0,0 +1,86 @@ +access = $DIC->access(); + $this->client_id = $DIC['ilias']->getClientId(); + $this->user = $DIC->user(); + $this->username = $DIC->user()->getFullname(); + + } + + public function getName() + { + return 'MountPoint'; + } + + public function getChildren() + { + // TODO: Check for permissions + if($this->user->getId() != ANONYMOUS_USER_ID) + { + return array(new ilClientNodeDAV($this->client_id)); + } + else + { + throw new Forbidden('Only for logged in users'); + } + } + + public function getChild($name) + { + // TODO: Check for permissions AND correct client + if($name == $this->client_id) + return new ilClientNodeDAV($this->client_id); + throw new NotFound(); + } + + public function childExists($name) + { + // TODO: Check for correct client + if($name == $this->client_id) + return true; + return false; + } + + public function getLastModified() + { + return strtotime('2000-01-01'); + } + + public function createDirectory($name) + { + throw new Forbidden("It is not possible to create a directory here"); + } + + public function createFile($name, $data = null) + { + throw new Forbidden("It is not possible to create a file here"); + } + public function setName($name) + { + throw new Forbidden("It is not possible to change the name of the root"); + } + + public function delete() + { + throw new Forbidden("It is not possible to delete the root"); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php b/Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php new file mode 100644 index 000000000000..a0ea67426a7d --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php @@ -0,0 +1,16 @@ +access->checkAccess("write", '', $this->obj->getRefId())) + { + // Check if file has valid extension + include_once("./Services/Utilities/classes/class.ilFileUtils.php"); + if($name != ilFileUtils::getValidFilename($name)) + { + // Throw forbidden if invalid exstension. As far as we know, it is sadly not + // possible to inform the user why this is forbidden. + //ilLoggerFactory::getLogger('WebDAV')->warning(get_class($this). ' ' . $this->obj->getTitle() ." -> invalid File-Extension for file '$name'"); + //throw new Forbidden("Invalid file extension. But you won't see this anyway..."); + $name = ilFileUtils::getValidFilename($name); + } + + // Maybe getChild is more efficient. But hoping for an exception isnt that beautiful + if($this->childExists($name)) + { + $file_dav = $this->getChild($name); + $file_dav->handleFileUpload($data); + } + else + { + $file_obj = new ilObjFile(); + $file_obj->setTitle($name); + $file_obj->setFileName($name); + $file_obj->setVersion(1); + $file_obj->createDirectory(); + $file_obj->create(); + + $file_obj->createReference(); + $file_obj->putInTree($this->obj->getRefId()); + $file_obj->update(); + + $file_dav = new ilObjFileDAV($file_obj); + $file_dav->handleFileUpload($data); + } + } + + else + { + throw new Forbidden("No write access"); + } + + return $file_dav->getETag(); + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + public function createDirectory($name) + { + global $DIC; + + $new_obj; + $type = $this->getChildCollectionType(); + + switch($type) + { + case 'cat': + $new_obj = new ilObjCategory(); + break; + + case 'fold': + $new_obj = new ilObjFolder(); + break; + + default: + ilLoggerFactory::getLogger('WebDAV')->info(get_class($this). ' ' . $this->obj->getTitle() ." -> $type is not supported as webdav directory"); + throw new NotImplemented("Create type '$type' as collection is not implemented yet"); + } + + $new_obj->setType($type); + $new_obj->setOwner($DIC->user()->getId()); + $new_obj->setTitle($name); + $new_obj->create(); + + $new_obj->createReference(); + $new_obj->putInTree($this->obj->getRefId()); + $new_obj->setPermissions($this->obj->getRefId()); + $new_obj->update(); + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @return Sabre\DAV\INode + */ + public function getChild($name) + { + $child_node = NULL; + $child_exists = false; + foreach($this->tree->getChildIds($this->obj->getRefId()) as $child_ref) + { + // Check if a DAV Object exists for this type + if(self::_isDAVableObject($child_ref, true)) + { + // Check if names matches + $child_obj_id = ilObject::_lookupObjectId($child_ref); + $child_title = ilObject::_lookupTitle($child_obj_id); + if($child_title == $name) + { + $child_exists = true; + + // Check if user has permission to read this object + if($this->access->checkAccess("read", "", $child_ref)) + { + $child_node = self::_createDAVObjectForRefId($child_ref); + } + } + } + } + + // There exists 1 or more nodes with this name. Return last found node. + if(!is_null($child_node)) + { + return $child_node; + } + + // There is no davable object with the same name. Sorry for you... + throw new Sabre\DAV\Exception\NotFound("$name not found"); + } + + /** + * Returns an array with all the child nodes + * + * @return ilObject[] + */ + public function getChildren() + { + + $child_nodes = array(); + foreach($this->tree->getChildIds($this->obj->getRefId()) as $child_ref) + { + // Check if is davable object types + if(self::_isDAVableObject($child_ref, true)) + { + // Check if read permission is given + if($this->access->checkAccess("read", "", $child_ref)) + { + // Create DAV-object out of ILIAS-object + $child_obj_dav = self::_createDAVObjectForRefId($child_ref); + if($child_obj_dav != null) + { + $child_nodes[$child_ref] = $child_obj_dav; + } + } + } + } + return $child_nodes; + } + + /** + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool + */ + public function childExists($name) + { + foreach($this->tree->getChildIds($this->obj->getRefId()) as $child_ref) + { + // Only davable object types + if(self::_isDAVableObject($child_ref, true)) + { + // Check if names are the same + $obj_id = ilObject::_lookupObjId($child_ref); + if(ilObject::_lookupTitle($obj_id) == $name) + { + // Check if read permission is given + if($this->access->checkAccess("read", '', $child_ref)) + { + return true; + } + } + } + } + + return false; + } + + /** + * Return the type for child collections of this collection + * For courses, groups and folders the type is 'fold' + * For categories the type is 'cat' + * + * @return string $type + */ + public abstract function getChildCollectionType(); +} \ No newline at end of file diff --git a/Services/WebDAV/classes/dav/class.ilObjCourseDAV.php b/Services/WebDAV/classes/dav/class.ilObjCourseDAV.php new file mode 100644 index 000000000000..a80c96c0ade2 --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilObjCourseDAV.php @@ -0,0 +1,16 @@ +access->checkAccess("write", "", $this->obj->getRefId())) + { + $this->handleFileUpload($data); + return $this->getETag(); + } + throw new Forbidden("Permission denied. No write access for this file"); + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() + { + // TODO: Check permission + if($this->access->checkAccess("read", "", $this->obj->getRefId())) + { + $file = $this->getPathToFile(); + if(file_exists($file)) + { + return fopen($file,'r'); + } + else + { + return null; + } + } + throw new Forbidden("Permission denied. No read access for this file"); + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType() + { + return $this->obj->guessFileType(); + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * + * Return null if the ETag can not effectively be determined. + * + * The ETag must be surrounded by double-quotes, so something like this + * would make a valid ETag: + * + * return '"someetag"'; + * + * @return string|null + */ + public function getETag() + { + if(file_exists($this->getPathToFile())) + { + // This is not a password hash. So I think md5 should do just fine :) + return '"' . hash_file("md5", $this->getPathToFile(), false) . '"'; + } + return null; + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + public function getSize() + { + if(file_exists($this->getPathToFile())) + { + return $this->obj->getFileSize(); + } + return 0; + } + + /** + * + * {@inheritDoc} + * @see ilObjectDAV::delete() + */ + public function delete() + { + if($this->access->checkAccess('delete', '', $this->obj->getRefId())) + { + $this->obj->delete(); + } + else + { + throw new Forbidden('You are not allowed to delete this file!'); + } + } + + /** + * Handle uploaded file. Either it is a new file upload to a directory or it is an + * upload to replace an existing file. + * + * Given data can be a resource or data (given from the sabreDAV library) + * + * @param string | resource $a_data + * @param boolean $a_has_already_a_file + */ + public function handleFileUpload($a_data) + { + global $DIC; + + $file_dest_path = $this->getPathToFile(); + + // If dir does not exist yet -> create it + if(!file_exists($file_dest_path)) + { + ilUtil::makeDirParents($this->getPathToDirectory()); + } + + // File upload + $written_length = 0; + if(is_resource($a_data)) + { + $written_length = $this->fileUploadWithStream($a_data, $file_dest_path); + } + else if(is_string($a_data)) + { + $written_length = $this->fileUploadWithString($a_data, $file_dest_path); + } + else + { + ilLoggerFactory::getLogger('WebDAV')->warning(get_class($this). ' ' . $this->obj->getTitle() ." -> invalid upload data sent"); + throw new BadRequest('Invalid put data sent'); + } + + // Security checks + $this->checkForVirus($file_dest_path); + + // Set last meta data + include_once("./Services/Utilities/classes/class.ilMimeTypeUtil.php"); + $this->obj->setFileType(ilMimeTypeUtil::lookupMimeType($file_dest_path)); + $this->obj->setFileSize($written_length); + $this->obj->update(); + } + + /** + * Write given data (as Resource) to the given file + * + * @param Resource $a_data + * @param string $file_dest_path + * @throws Forbidden + * @return number + */ + protected function fileUploadWithStream($a_data, string $file_dest_path) + { + try { + $write_stream = fopen($file_dest_path,'w'); + + while (!feof($a_data)) { + if (false === ($written = fwrite($write_stream, fread($a_data, 4096)))) { + fclose($write_stream); + throw new Forbidden('Forbidden to write file'); + } + $written_length += $written; + } + + } catch(Exception $e) { + ilLoggerFactory::getLogger('WebDAV')->error("Error on uploading {$this->obj->getTitle()} to path $file_dest_path with message: " . $e->getMessage()); + throw new Exception(); + } finally + { + fclose($write_stream); + } + + return $written_length; + } + + /** + * Write given data (as string) to the given file + * + * @param string $a_data + * @param string $file_dest_path + * @throws Forbidden + * @return number $written_length + */ + protected function fileUploadWithString(string $a_data, string $file_dest_path) + { + $write_stream = fopen($file_dest_path, 'w'); + $written_length = fwrite($write_stream, $a_data); + fclose($write_stream); + if($written_length === false && strlen($a_data) > 0) + { + throw new Forbidden('Forbidden to write file'); + } + return $written_length; + } + + protected function getPathToDirectory() + { + return $this->obj->getDirectory($this->obj->getVersion()); + } + + protected function getPathToFile() + { + return $this->getPathToDirectory() . "/" . $this->obj->getFileName(); + } + + protected function checkForVirus() + { + $vrs = ilUtil::virusHandling($file_dest_path, '', true); + // If vrs[0] == false -> virus found + if($vrs[0] == false) + { + ilLoggerFactory::getLogger('WebDAV')->error(get_class($this). ' ' . $this->obj->getTitle() ." -> virus found on '$file_dest_path'!"); + unlink($file_dest_path); + $this->obj->delete(); + throw new Forbidden('Virus found!'); + } + } +} diff --git a/Services/WebDAV/classes/dav/class.ilObjFolderDAV.php b/Services/WebDAV/classes/dav/class.ilObjFolderDAV.php new file mode 100644 index 000000000000..15308eb39bf1 --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilObjFolderDAV.php @@ -0,0 +1,17 @@ +repository_root_name = $repository_root_name; + parent::__construct(new ilObjRootFolder(ROOT_FOLDER_ID, true)); + } + + public function setName($name) + { + throw new Forbidden("It's not allowed to rename the repository root"); + } + + public function getName() + { + return $this->repository_root_name; + } + + public function getChildCollectionType() + { + return 'cat'; + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/dav/class.ilObjectDAV.php b/Services/WebDAV/classes/dav/class.ilObjectDAV.php new file mode 100644 index 000000000000..50c45f132c4d --- /dev/null +++ b/Services/WebDAV/classes/dav/class.ilObjectDAV.php @@ -0,0 +1,222 @@ +obj =& $a_obj; + $this->ref_id = $a_obj->getRefId(); + + + $this->tree = $DIC->repositoryTree(); + $this->access = $DIC->access(); + } + + /** + * Returns the ref id of this object. + * @return int. + */ + function getRefId() + { + return $this->ref_id; + } + + /** + * Returns the object id of this object. + * @return int. + */ + function getObjectId() + { + return ($this->obj == null) ? null : $this->obj->getId(); + } + + /** + * Returns the last modification time as a unix timestamp. + * + * If the information is not available, return null. + * + * @return int + */ + function getLastModified() { + + return ($this->obj == null) ? null : strtotime($this->obj->getLastUpdateDate()); + } + + /** + * Deletes the current node + * + * @throws Sabre\DAV\Exception\Forbidden + * @return void + */ + public function delete() + { + if($this->access->checkAccess('delete', '', $this->obj->getRefId())) + { + $this->tree->moveToTrash($this->obj->getRefId()); + } + else + { + throw new Forbidden("No delete permission for $this->getName()"); + } + } + + /** + * Renames the node + * + * @param string $a_name The new name + * @throws Sabre\DAV\Exception\Forbidden + * @return void + */ + function setName($a_name) + { + if($this->access->checkAccess("write", '', $this->obj->getRefId())) + { + $this->obj->setTitle($a_name); + $this->obj->update(); + } + else + { + throw new Forbidden('Permission denied'); + } + } + + /** + * SabreDAV interface function + * {@inheritDoc} + * @see \Sabre\DAV\INode::getName() + */ + function getName() + { + return $this->obj->getTitle(); + } + + /** + * Returns ILIAS Object + * + * @return ilObject + */ + function getObject() + { + return $this->obj; + } + + /** + * Creates a DAV Object for the given ref id + * + * @param integer $ref_id + * @param string $type + */ + public static function _createDAVObjectForRefId($ref_id, $type = '') + { + if($type == '') + { + $type = ilObject::_lookupType($ref_id, true); + } + + if(ilObject::_exists($ref_id, true, $type)) + { + switch($type) + { + case 'cat': + return new ilObjCategoryDAV(new ilObjCategory($ref_id, true)); + break; + + case 'crs': + return new ilObjCourseDAV(new ilObjCourse($ref_id, true)); + break; + + case 'grp': + return new ilObjGroupDAV(new ilObjGroup($ref_id, true)); + break; + + case 'fold': + return new ilObjFolderDAV(new ilObjFolder($ref_id, true)); + break; + + case 'file': + return new ilObjFileDAV(new ilObjFile($ref_id, true)); + break; + } + + throw new BadRequest(); + } + return null; + } + + /** + * Checks if there is a DAV-Object for the given type + * + * @param string $type + * @return boolean + */ + public static function _isDAVableObjectType($type) + { + switch($type) + { + case 'cat': + case 'crs': + case 'grp': + case 'fold': + case 'file': + return true; + + default: + return false; + } + } + + /** + * + * @param int $id + * @param boolean $is_reference + * + * @return boolean + */ + public static function _isDAVableObject($id, $is_reference = false) + { + return self::_isDAVableObjectType(ilObject::_lookupType($id, $is_reference)); + } + +} diff --git a/Services/WebDAV/classes/db/class.ilWebDAVDBManager.php b/Services/WebDAV/classes/db/class.ilWebDAVDBManager.php new file mode 100644 index 000000000000..513b541b4e1e --- /dev/null +++ b/Services/WebDAV/classes/db/class.ilWebDAVDBManager.php @@ -0,0 +1,101 @@ +db = $db; + } + + public function checkIfLockExistsInDB($token) + { + $select_query = "SELECT count(*) AS cnt FROM $this->locks_table WHERE token = " . $this->db->quote($ilias_lock->getToken(), 'text'); + $select_result = $this->db->query($select_query); + $row = $this->db->fetchAssoc($select_query); + if(isset($row)){ + return true; + } + return false; + } + + /** + * Returns lock Object from given tocken + * @param string $token + * @return ilWebDAVLockObject|boolean + */ + public function getLockObjectWithTokenFromDB($token) + { + $query = "SELECT * FROM $this->lock_table" + . " WHERE token = " . $this->db->quote($token, 'text') + . " AND expires > " . $this->db->quote(time(),'integer'); + + $select_result = $this->db->query($query); + $row = $this->db->fetchAssoc($select_result); + + if($row) + { + + return ilWebDAVLockObject::createFromAssocArray($row); + } + + return false; + } + + public function getLockObjectWithObjIdFromDB($obj_id) + { + $query = "SELECT * FROM $this->lock_table WHERE obj_id = " + . $this->db->quote($obj_id, 'integer') + . " AND expires > " . $this->db->quote(time(),'integer'); + $select_result = $this->db->query($query); + $row = $this->db->fetchAssoc($select_result); + + if($row) + { + return ilWebDAVLockObject::createFromAssocArray($row); + } + + return false; + } + + public function saveLockToDB(ilWebDAVLockObject $ilias_lock) + { + $this->db->insert($this->lock_table, array( + 'token' => array('text', $ilias_lock->getToken()), + 'obj_id' => array('integer', $ilias_lock->getObjId()), + 'ilias_owner' => array('integer', $ilias_lock->getIliasOwner()), + 'dav_owner' => array('text', $ilias_lock->getDavOwner()), + 'expires' => array('integer', $ilias_lock->getExpires()), + 'depth' => array('integer', $ilias_lock->getDepth()), + 'type' => array('text', $ilias_lock->getType()), + 'scope' => array('integer', $ilias_lock->getScope()) + )); + } + + /** + * Removes one specific lock + * + * @param integer $token + * @return array with affected lock (if there was a lock) + */ + public function removeLockWithTokenFromDB($token) + { + return $this->db->manipulate("DELETE FROM $this->lock_table WHERE token = ".$this->db->quote($token, "integer")); + } + + /** + * Removes all locks from DB that are expired (expires < time()) + * + * @return array with all affected locks + */ + public function purgeExpiredLocksFromDB() + { + return $this->db->manipulate("DELETE FROM $this->lock_table WHERE expires < " . $this->db->quote(time(), 'integer')); + } +} diff --git a/Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php b/Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php new file mode 100644 index 000000000000..dcfe3548a1f2 --- /dev/null +++ b/Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php @@ -0,0 +1,157 @@ +db_manager = $db_manager != null ? $db_manager : new ilWebDAVDBManager($DIC->database()); + $this->user = $user != null ? $user : $DIC->user(); + $this->access = $access != null ? $access : $DIC->access(); + $this->tree = $tree != null ? $tree : $DIC->repositoryTree(); + } + + /** + * This function returns all locks and child locks as SabreDAV lock objects + * It is needed for sabreDAV to see if there are any locks + * + * {@inheritDoc} + * @see \Sabre\DAV\Locks\Backend\BackendInterface::getLocks() + */ + public function getLocks($uri, $returnChildLocks) + { + $sabre_locks = array(); + + // Get locks on given uri + $ref_id = ilWebDAVTree::getRefIdForWebDAVPath($uri); + $obj_id = ilObject::_lookupObjectId($ref_id); + $lock_on_obj = $this->getLocksOnObjectId($obj_id); + if($lock_on_obj != false) + { + $sabre_locks[] = $lock_on_obj->getAsSabreDavLock($uri); + } + + // Get locks on childs + if($returnChildLocks) + { + $sabre_locks = $this->getLocksRecursive($sabre_locks, $ref_id, $uri); + } + + + return $sabre_locks; + } + + /** + * Iterates recursive through the ilias tree to search for locked objects + * + * @param array $sabre_locks + * @param integer $ref_id + * @param string $uri + * @return array + */ + protected function getLocksRecursive($sabre_locks, $ref_id, $uri) + { + foreach($this->tree->getChilds($ref_id) as $child_ref) + { + // Only get locks of DAVable objects. Because not DAVable objects won't be lockable anyway + $child_obj_id = ilObject::_lookupObjectId($child_ref); + if(ilObjectDAV::_isDAVableObject($child_obj_id, false)) + { + // Get Locks of this object + $title = ilObject::_lookupTitle($child_obj_id); + $child_ilias_lock = $this->getLocksOnObjectId($child_obj_id); + if($child_ilias_lock != false) + { + $sabre_locks[] = $child_ilias_lock->getAsSabreDavLock($uri . '/' . $title); + } + + // Get locks of child objects + $sabre_locks = $this->getLocksRecursive($sabre_locks, $child_ref, $uri . $title . '/'); + } + + } + + return $sabre_locks; + } + + /** + * {@inheritDoc} + * @see \Sabre\DAV\Locks\Backend\BackendInterface::unlock() + */ + public function unlock($uri, Sabre\DAV\Locks\LockInfo $lockInfo) + { + $ilias_lock = $this->db_manager->getLockObjectWithTokenFromDB($lockInfo->token); + if($ilias_lock && $ilias_lock->getIliasOwner() == $this->user->getId()) + { + $this->db_manager->removeLockWithTokenFromDB($lockInfo->token); + } + else + { + throw new Forbidden(); + } + } + + /** + * Function for the sabreDAV interface + * + * {@inheritDoc} + * @see \Sabre\DAV\Locks\Backend\BackendInterface::lock() + */ + public function lock($uri, Sabre\DAV\Locks\LockInfo $lock_info) + { + $ref_id = ilWebDAVTree::getRefIdForWebDAVPath($uri); + if($this->access->checkAccess('write', '', $ref_id)) + { + $ilias_lock = ilWebDAVLockObject::createFromSabreLock($lock_info); + $this->db_manager->saveLockToDB($ilias_lock); + } + else + { + throw new Forbidden(); + } + } + + + /** + * Returns lock on given object + * + * @param int $obj_id + * @return array + */ + public function getLocksOnObjectId(int $obj_id) + { + return $this->db_manager->getLockObjectWithObjIdFromDB($obj_id); + } +} \ No newline at end of file diff --git a/Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php b/Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php new file mode 100644 index 000000000000..428fa72cbcad --- /dev/null +++ b/Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php @@ -0,0 +1,166 @@ +token = $token; + $this->obj_id = $obj_id; + $this->ilias_owner = $ilias_owner; + $this->dav_owner = $dav_owner; + $this->expires = $expires; + $this->depth = $depth; + $this->type = $type; + $this->scope = $scope; + } + + protected $token; + protected $obj_id; + protected $ilias_owner; + protected $dav_owner; + protected $expires; + protected $depth; + protected $type; + protected $scope; + + /** + * @return string + */ + public function getToken() + { + return $this->token; + } + + /** + * @return int + */ + public function getObjId() + { + return $this->obj_id; + } + + /** + * @return int + */ + public function getIliasOwner() + { + return $this->ilias_owner; + } + + /** + * @return string + */ + public function getDavOwner() + { + return $this->dav_owner; + } + + /** + * @return int + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @return int + */ + public function getDepth() + { + return $this->depth; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getScope() + { + return $this->scope; + } + + public static function createFromAssocArray($assoc_array) + { + return new ilWebDAVLockObject($assoc_array['token'], + $assoc_array['obj_id'], + $assoc_array['ilias_owner'], + $assoc_array['dav_owner'], + $assoc_array['expires'], + $assoc_array['depth'], + $assoc_array['type'], + $assoc_array['scope']); + } + + /** + * Creates an ILIAS lock object from a sabreDAV lock object + * + * IMPORTANT: This method just creates and initializes an object. It does not + * create any record in the database! + * + * @param Sabre\DAV\Locks\LockInfo $lock_info + */ + public static function createFromSabreLock(Sabre\DAV\Locks\LockInfo $lock_info) + { + global $DIC; + + $ref_id = ilWebDAVTree::getRefIdForWebDAVPath($lock_info->uri); + $obj_id = ilObject::_lookupObjectId($ref_id); + + $ilias_lock = new ilWebDAVLockObject( + $lock_info->token, // token + $obj_id, // obj_id + $DIC->user()->getId(), // ilias_owner + $lock_info->owner, // dav_owner + time() + 3600, // expires + $lock_info->depth, // depth + 'w', // type + $lock_info->scope); // scope + + return $ilias_lock; + } + + public function getAsSabreDavLock($uri) + { + global $DIC; + + $timestamp = time(); + + $sabre_lock = new Sabre\DAV\Locks\LockInfo(); + $sabre_lock->created; + $sabre_lock->depth = $this->depth; + $sabre_lock->owner = $this->dav_owner; + $sabre_lock->scope = $this->scope; + $sabre_lock->timeout = $this->expires - $timestamp; + $sabre_lock->created = $this->expires - 3600; + $sabre_lock->token = $this->token; + $sabre_lock->uri = $uri; + + return $sabre_lock; + } +} \ No newline at end of file diff --git a/Services/WebDAV/service.xml b/Services/WebDAV/service.xml index 8c721bb29c5c..bd7efebdb978 100644 --- a/Services/WebDAV/service.xml +++ b/Services/WebDAV/service.xml @@ -10,4 +10,4 @@ - + \ No newline at end of file diff --git a/libs/composer/composer.json b/libs/composer/composer.json index ddef65613a60..3424e02aca5b 100644 --- a/libs/composer/composer.json +++ b/libs/composer/composer.json @@ -25,7 +25,8 @@ "simplesamlphp/simplesamlphp": "^1.15.4", "ralouphie/getallheaders": "^2.0", "league/flysystem": "^1.0", - "james-heinrich/getid3": "^1.9" + "james-heinrich/getid3": "^1.9", + "sabre/dav": "~3.2.2" }, "require-dev": { "mikey179/vfsStream": "^1.6", @@ -272,6 +273,16 @@ "approved-by": "Only added to ensure PHP 5 compatibility", "approved-date": "2018-01-17" }, + "sabre/dav": { + "source" : "https://github.com/sabre-io/dav.git", + "used_version" : "v3.2.2", + "wrapped_by" : "Services/WebDAV", + "added_by" : "Raphael Heer ", + "last_update" : "2018-03-12", + "last_update_by" : "Raphael Heer ", + "approved-by": "Jour Fixe", + "approved-date": "2018-03-12" + }, "patches": { "tecnickcom/tcpdf": { "ILIAS TCPDF Patches": "patches/tcpdf.patch" diff --git a/libs/composer/composer.lock b/libs/composer/composer.lock index bbde19b50d94..bbce463bd388 100644 --- a/libs/composer/composer.lock +++ b/libs/composer/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "09a3ea6bfe50159a566a17b1ca2f5914", + "content-hash": "632d92b3ce2f64d18fbd3fa8feedc17b", "packages": [ { "name": "cweagans/composer-patches", @@ -1077,6 +1077,413 @@ ], "time": "2017-08-31T09:27:07+00:00" }, + { + "name": "sabre/dav", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/dav.git", + "reference": "e987775e619728f12205606c9cc3ee565ffb1516" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/dav/zipball/e987775e619728f12205606c9cc3ee565ffb1516", + "reference": "e987775e619728f12205606c9cc3ee565ffb1516", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-mbstring": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "lib-libxml": ">=2.7.0", + "php": ">=5.5.0", + "psr/log": "^1.0", + "sabre/event": ">=2.0.0, <4.0.0", + "sabre/http": "^4.2.1", + "sabre/uri": "^1.0.1", + "sabre/vobject": "^4.1.0", + "sabre/xml": "^1.4.0" + }, + "require-dev": { + "evert/phpdoc-md": "~0.1.0", + "monolog/monolog": "^1.18", + "phpunit/phpunit": "> 4.8, <6.0.0", + "sabre/cs": "^1.0.0" + }, + "suggest": { + "ext-curl": "*", + "ext-pdo": "*" + }, + "bin": [ + "bin/sabredav", + "bin/naturalselection" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Sabre\\DAV\\": "lib/DAV/", + "Sabre\\DAVACL\\": "lib/DAVACL/", + "Sabre\\CalDAV\\": "lib/CalDAV/", + "Sabre\\CardDAV\\": "lib/CardDAV/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "WebDAV Framework for PHP", + "homepage": "http://sabre.io/", + "keywords": [ + "CalDAV", + "CardDAV", + "WebDAV", + "framework", + "iCalendar" + ], + "time": "2017-02-15T03:06:08+00:00" + }, + { + "name": "sabre/event", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/event.git", + "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534", + "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + }, + "files": [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "async", + "events", + "hooks", + "plugin", + "promise", + "signal" + ], + "time": "2015-11-05T20:14:39+00:00" + }, + { + "name": "sabre/http", + "version": "v4.2.4", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/http.git", + "reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/http/zipball/acccec4ba863959b2d10c1fa0fb902736c5c8956", + "reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": ">=5.4", + "sabre/event": ">=1.0.0,<4.0.0", + "sabre/uri": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.3", + "sabre/cs": "~0.0.1" + }, + "suggest": { + "ext-curl": " to make http requests with the Client class" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\HTTP\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "The sabre/http library provides utilities for dealing with http requests and responses. ", + "homepage": "https://github.com/fruux/sabre-http", + "keywords": [ + "http" + ], + "time": "2018-02-23T11:10:29+00:00" + }, + { + "name": "sabre/uri", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/uri.git", + "reference": "ada354d83579565949d80b2e15593c2371225e61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/uri/zipball/ada354d83579565949d80b2e15593c2371225e61", + "reference": "ada354d83579565949d80b2e15593c2371225e61", + "shasum": "" + }, + "require": { + "php": ">=5.4.7" + }, + "require-dev": { + "phpunit/phpunit": ">=4.0,<6.0", + "sabre/cs": "~1.0.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\Uri\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "Functions for making sense out of URIs.", + "homepage": "http://sabre.io/uri/", + "keywords": [ + "rfc3986", + "uri", + "url" + ], + "time": "2017-02-20T19:59:28+00:00" + }, + { + "name": "sabre/vobject", + "version": "4.1.6", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/vobject.git", + "reference": "122cacbdea2c6133ac04db86ec05854beef75adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/vobject/zipball/122cacbdea2c6133ac04db86ec05854beef75adf", + "reference": "122cacbdea2c6133ac04db86ec05854beef75adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.5", + "sabre/xml": ">=1.5 <3.0" + }, + "require-dev": { + "phpunit/phpunit": "> 4.8.35, <6.0.0", + "sabre/cs": "^1.0.0" + }, + "suggest": { + "hoa/bench": "If you would like to run the benchmark scripts" + }, + "bin": [ + "bin/vobject", + "bin/generate_vcards" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabre\\VObject\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Dominik Tobschall", + "email": "dominik@fruux.com", + "homepage": "http://tobschall.de/", + "role": "Developer" + }, + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net", + "homepage": "http://mnt.io/", + "role": "Developer" + } + ], + "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "homepage": "http://sabre.io/vobject/", + "keywords": [ + "availability", + "freebusy", + "iCalendar", + "ical", + "ics", + "jCal", + "jCard", + "recurrence", + "rfc2425", + "rfc2426", + "rfc2739", + "rfc4770", + "rfc5545", + "rfc5546", + "rfc6321", + "rfc6350", + "rfc6351", + "rfc6474", + "rfc6638", + "rfc6715", + "rfc6868", + "vCalendar", + "vCard", + "vcf", + "xCal", + "xCard" + ], + "time": "2018-04-20T07:22:50+00:00" + }, + { + "name": "sabre/xml", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/xml.git", + "reference": "59b20e5bbace9912607481634f97d05a776ffca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7", + "reference": "59b20e5bbace9912607481634f97d05a776ffca7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "lib-libxml": ">=2.6.20", + "php": ">=5.5.5", + "sabre/uri": ">=1.0,<3.0.0" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~1.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Xml\\": "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role": "Developer" + } + ], + "description": "sabre/xml is an XML library that you may not hate.", + "homepage": "https://sabre.io/xml/", + "keywords": [ + "XMLReader", + "XMLWriter", + "dom", + "xml" + ], + "time": "2016-10-09T22:57:52+00:00" + }, { "name": "simplesamlphp/saml2", "version": "v3.1.5", diff --git a/libs/composer/vendor/bin/generate_vcards b/libs/composer/vendor/bin/generate_vcards new file mode 120000 index 000000000000..cb76da13a1b2 --- /dev/null +++ b/libs/composer/vendor/bin/generate_vcards @@ -0,0 +1 @@ +../sabre/vobject/bin/generate_vcards \ No newline at end of file diff --git a/libs/composer/vendor/bin/naturalselection b/libs/composer/vendor/bin/naturalselection new file mode 120000 index 000000000000..e6f1b3a2ab8b --- /dev/null +++ b/libs/composer/vendor/bin/naturalselection @@ -0,0 +1 @@ +../sabre/dav/bin/naturalselection \ No newline at end of file diff --git a/libs/composer/vendor/bin/sabredav b/libs/composer/vendor/bin/sabredav new file mode 120000 index 000000000000..3b5e4511dc11 --- /dev/null +++ b/libs/composer/vendor/bin/sabredav @@ -0,0 +1 @@ +../sabre/dav/bin/sabredav \ No newline at end of file diff --git a/libs/composer/vendor/bin/vobject b/libs/composer/vendor/bin/vobject new file mode 120000 index 000000000000..f5b111eacd6a --- /dev/null +++ b/libs/composer/vendor/bin/vobject @@ -0,0 +1 @@ +../sabre/vobject/bin/vobject \ No newline at end of file diff --git a/libs/composer/vendor/composer/autoload_classmap.php b/libs/composer/vendor/composer/autoload_classmap.php index a20a523733b0..e2bd51e523e5 100644 --- a/libs/composer/vendor/composer/autoload_classmap.php +++ b/libs/composer/vendor/composer/autoload_classmap.php @@ -341,7 +341,6 @@ 'HTMLPurifier_VarParser_Flexible' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php', 'HTMLPurifier_VarParser_Native' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php', 'HTMLPurifier_Zipper' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php', - 'HTTP_WebDAV_Server' => $baseDir . '/../../Services/WebDAV/classes/Server.php', 'ILIAS' => $baseDir . '/../../Services/Init/classes/class.ilias.php', 'ILIAS\\BackgroundTasks\\Bucket' => $baseDir . '/../../src/BackgroundTasks/Bucket.php', 'ILIAS\\BackgroundTasks\\BucketMeta' => $baseDir . '/../../src/BackgroundTasks/BucketMeta.php', @@ -833,6 +832,12 @@ 'ILIAS\\Validation\\Constraints\\LessThan' => $baseDir . '/../../src/Validation/Constraints/LessThan.php', 'ILIAS\\Validation\\Constraints\\Not' => $baseDir . '/../../src/Validation/Constraints/Not.php', 'ILIAS\\Validation\\Constraints\\Parallel' => $baseDir . '/../../src/Validation/Constraints/Parallel.php', + 'ILIAS\\Validation\\Constraints\\Password\\Factory' => $baseDir . '/../../src/Validation/Constraints/Password/Factory.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasLowerChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasLowerChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasMinLength' => $baseDir . '/../../src/Validation/Constraints/Password/HasMinLength.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasNumbers' => $baseDir . '/../../src/Validation/Constraints/Password/HasNumbers.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasSpecialChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasSpecialChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasUpperChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasUpperChars.php', 'ILIAS\\Validation\\Constraints\\Sequential' => $baseDir . '/../../src/Validation/Constraints/Sequential.php', 'ILIAS\\Validation\\Factory' => $baseDir . '/../../src/Validation/Factory.php', 'ILIAS\\WebAccessChecker\\HttpServiceAware' => $baseDir . '/../../Services/WebAccessChecker/classes/HttpServiceAware.php', @@ -1005,9 +1010,6 @@ 'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', 'Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', 'Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', - 'Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', - 'Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', - 'Monolog\\Handler\\SlackbotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', 'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', 'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', 'Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', @@ -1023,7 +1025,6 @@ 'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', 'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', 'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', - 'Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', 'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', 'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', 'Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', @@ -1238,13 +1239,6 @@ 'POP3' => $vendorDir . '/phpmailer/phpmailer/class.pop3.php', 'Parser' => $baseDir . '/../../Services/Utilities/classes/Parser.php', 'Pimple\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Container.php', - 'Pimple\\Exception\\ExpectedInvokableException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', - 'Pimple\\Exception\\FrozenServiceException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', - 'Pimple\\Exception\\InvalidServiceIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', - 'Pimple\\Exception\\UnknownIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', - 'Pimple\\Psr11\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/Container.php', - 'Pimple\\Psr11\\ServiceLocator' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', - 'Pimple\\ServiceIterator' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceIterator.php', 'Pimple\\ServiceProviderInterface' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', 'Pimple\\Tests\\Fixtures\\Invokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', 'Pimple\\Tests\\Fixtures\\NonInvokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', @@ -1252,9 +1246,6 @@ 'Pimple\\Tests\\Fixtures\\Service' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', 'Pimple\\Tests\\PimpleTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', - 'Pimple\\Tests\\Psr11\\ContainerTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', - 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', - 'Pimple\\Tests\\ServiceIteratorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', @@ -1433,6 +1424,345 @@ 'SAML2\\XML\\samlp\\Extensions' => $vendorDir . '/simplesamlphp/saml2/src/SAML2/XML/samlp/Extensions.php', 'SAML2\\XML\\shibmd\\Scope' => $vendorDir . '/simplesamlphp/saml2/src/SAML2/XML/shibmd/Scope.php', 'SMTP' => $vendorDir . '/phpmailer/phpmailer/class.smtp.php', + 'Sabre\\CalDAV\\Backend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php', + 'Sabre\\CalDAV\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php', + 'Sabre\\CalDAV\\Backend\\NotificationSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php', + 'Sabre\\CalDAV\\Backend\\PDO' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/PDO.php', + 'Sabre\\CalDAV\\Backend\\SchedulingSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php', + 'Sabre\\CalDAV\\Backend\\SharingSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/SharingSupport.php', + 'Sabre\\CalDAV\\Backend\\SimplePDO' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/SimplePDO.php', + 'Sabre\\CalDAV\\Backend\\SubscriptionSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php', + 'Sabre\\CalDAV\\Backend\\SyncSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/SyncSupport.php', + 'Sabre\\CalDAV\\Calendar' => $vendorDir . '/sabre/dav/lib/CalDAV/Calendar.php', + 'Sabre\\CalDAV\\CalendarHome' => $vendorDir . '/sabre/dav/lib/CalDAV/CalendarHome.php', + 'Sabre\\CalDAV\\CalendarObject' => $vendorDir . '/sabre/dav/lib/CalDAV/CalendarObject.php', + 'Sabre\\CalDAV\\CalendarQueryValidator' => $vendorDir . '/sabre/dav/lib/CalDAV/CalendarQueryValidator.php', + 'Sabre\\CalDAV\\CalendarRoot' => $vendorDir . '/sabre/dav/lib/CalDAV/CalendarRoot.php', + 'Sabre\\CalDAV\\Exception\\InvalidComponentType' => $vendorDir . '/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php', + 'Sabre\\CalDAV\\ICSExportPlugin' => $vendorDir . '/sabre/dav/lib/CalDAV/ICSExportPlugin.php', + 'Sabre\\CalDAV\\ICalendar' => $vendorDir . '/sabre/dav/lib/CalDAV/ICalendar.php', + 'Sabre\\CalDAV\\ICalendarObject' => $vendorDir . '/sabre/dav/lib/CalDAV/ICalendarObject.php', + 'Sabre\\CalDAV\\ICalendarObjectContainer' => $vendorDir . '/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php', + 'Sabre\\CalDAV\\ISharedCalendar' => $vendorDir . '/sabre/dav/lib/CalDAV/ISharedCalendar.php', + 'Sabre\\CalDAV\\Notifications\\Collection' => $vendorDir . '/sabre/dav/lib/CalDAV/Notifications/Collection.php', + 'Sabre\\CalDAV\\Notifications\\ICollection' => $vendorDir . '/sabre/dav/lib/CalDAV/Notifications/ICollection.php', + 'Sabre\\CalDAV\\Notifications\\INode' => $vendorDir . '/sabre/dav/lib/CalDAV/Notifications/INode.php', + 'Sabre\\CalDAV\\Notifications\\Node' => $vendorDir . '/sabre/dav/lib/CalDAV/Notifications/Node.php', + 'Sabre\\CalDAV\\Notifications\\Plugin' => $vendorDir . '/sabre/dav/lib/CalDAV/Notifications/Plugin.php', + 'Sabre\\CalDAV\\Plugin' => $vendorDir . '/sabre/dav/lib/CalDAV/Plugin.php', + 'Sabre\\CalDAV\\Principal\\Collection' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/Collection.php', + 'Sabre\\CalDAV\\Principal\\IProxyRead' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/IProxyRead.php', + 'Sabre\\CalDAV\\Principal\\IProxyWrite' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php', + 'Sabre\\CalDAV\\Principal\\ProxyRead' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/ProxyRead.php', + 'Sabre\\CalDAV\\Principal\\ProxyWrite' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php', + 'Sabre\\CalDAV\\Principal\\User' => $vendorDir . '/sabre/dav/lib/CalDAV/Principal/User.php', + 'Sabre\\CalDAV\\Schedule\\IInbox' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/IInbox.php', + 'Sabre\\CalDAV\\Schedule\\IMipPlugin' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php', + 'Sabre\\CalDAV\\Schedule\\IOutbox' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/IOutbox.php', + 'Sabre\\CalDAV\\Schedule\\ISchedulingObject' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php', + 'Sabre\\CalDAV\\Schedule\\Inbox' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/Inbox.php', + 'Sabre\\CalDAV\\Schedule\\Outbox' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/Outbox.php', + 'Sabre\\CalDAV\\Schedule\\Plugin' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/Plugin.php', + 'Sabre\\CalDAV\\Schedule\\SchedulingObject' => $vendorDir . '/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php', + 'Sabre\\CalDAV\\SharedCalendar' => $vendorDir . '/sabre/dav/lib/CalDAV/SharedCalendar.php', + 'Sabre\\CalDAV\\SharingPlugin' => $vendorDir . '/sabre/dav/lib/CalDAV/SharingPlugin.php', + 'Sabre\\CalDAV\\Subscriptions\\ISubscription' => $vendorDir . '/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php', + 'Sabre\\CalDAV\\Subscriptions\\Plugin' => $vendorDir . '/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php', + 'Sabre\\CalDAV\\Subscriptions\\Subscription' => $vendorDir . '/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php', + 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php', + 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php', + 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php', + 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php', + 'Sabre\\CalDAV\\Xml\\Notification\\Invite' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php', + 'Sabre\\CalDAV\\Xml\\Notification\\InviteReply' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php', + 'Sabre\\CalDAV\\Xml\\Notification\\NotificationInterface' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php', + 'Sabre\\CalDAV\\Xml\\Notification\\SystemStatus' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php', + 'Sabre\\CalDAV\\Xml\\Property\\AllowedSharingModes' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php', + 'Sabre\\CalDAV\\Xml\\Property\\EmailAddressSet' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php', + 'Sabre\\CalDAV\\Xml\\Property\\Invite' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/Invite.php', + 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarData' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCollationSet' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php', + 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\InviteReply' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php', + 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php', + 'Sabre\\CalDAV\\Xml\\Request\\Share' => $vendorDir . '/sabre/dav/lib/CalDAV/Xml/Request/Share.php', + 'Sabre\\CardDAV\\AddressBook' => $vendorDir . '/sabre/dav/lib/CardDAV/AddressBook.php', + 'Sabre\\CardDAV\\AddressBookHome' => $vendorDir . '/sabre/dav/lib/CardDAV/AddressBookHome.php', + 'Sabre\\CardDAV\\AddressBookRoot' => $vendorDir . '/sabre/dav/lib/CardDAV/AddressBookRoot.php', + 'Sabre\\CardDAV\\Backend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php', + 'Sabre\\CardDAV\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/CardDAV/Backend/BackendInterface.php', + 'Sabre\\CardDAV\\Backend\\PDO' => $vendorDir . '/sabre/dav/lib/CardDAV/Backend/PDO.php', + 'Sabre\\CardDAV\\Backend\\SyncSupport' => $vendorDir . '/sabre/dav/lib/CardDAV/Backend/SyncSupport.php', + 'Sabre\\CardDAV\\Card' => $vendorDir . '/sabre/dav/lib/CardDAV/Card.php', + 'Sabre\\CardDAV\\IAddressBook' => $vendorDir . '/sabre/dav/lib/CardDAV/IAddressBook.php', + 'Sabre\\CardDAV\\ICard' => $vendorDir . '/sabre/dav/lib/CardDAV/ICard.php', + 'Sabre\\CardDAV\\IDirectory' => $vendorDir . '/sabre/dav/lib/CardDAV/IDirectory.php', + 'Sabre\\CardDAV\\Plugin' => $vendorDir . '/sabre/dav/lib/CardDAV/Plugin.php', + 'Sabre\\CardDAV\\VCFExportPlugin' => $vendorDir . '/sabre/dav/lib/CardDAV/VCFExportPlugin.php', + 'Sabre\\CardDAV\\Xml\\Filter\\AddressData' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php', + 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php', + 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php', + 'Sabre\\CardDAV\\Xml\\Property\\SupportedAddressData' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php', + 'Sabre\\CardDAV\\Xml\\Property\\SupportedCollationSet' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php', + 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php', + 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport' => $vendorDir . '/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php', + 'Sabre\\DAVACL\\ACLTrait' => $vendorDir . '/sabre/dav/lib/DAVACL/ACLTrait.php', + 'Sabre\\DAVACL\\AbstractPrincipalCollection' => $vendorDir . '/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php', + 'Sabre\\DAVACL\\Exception\\AceConflict' => $vendorDir . '/sabre/dav/lib/DAVACL/Exception/AceConflict.php', + 'Sabre\\DAVACL\\Exception\\NeedPrivileges' => $vendorDir . '/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php', + 'Sabre\\DAVACL\\Exception\\NoAbstract' => $vendorDir . '/sabre/dav/lib/DAVACL/Exception/NoAbstract.php', + 'Sabre\\DAVACL\\Exception\\NotRecognizedPrincipal' => $vendorDir . '/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php', + 'Sabre\\DAVACL\\Exception\\NotSupportedPrivilege' => $vendorDir . '/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php', + 'Sabre\\DAVACL\\FS\\Collection' => $vendorDir . '/sabre/dav/lib/DAVACL/FS/Collection.php', + 'Sabre\\DAVACL\\FS\\File' => $vendorDir . '/sabre/dav/lib/DAVACL/FS/File.php', + 'Sabre\\DAVACL\\FS\\HomeCollection' => $vendorDir . '/sabre/dav/lib/DAVACL/FS/HomeCollection.php', + 'Sabre\\DAVACL\\IACL' => $vendorDir . '/sabre/dav/lib/DAVACL/IACL.php', + 'Sabre\\DAVACL\\IPrincipal' => $vendorDir . '/sabre/dav/lib/DAVACL/IPrincipal.php', + 'Sabre\\DAVACL\\IPrincipalCollection' => $vendorDir . '/sabre/dav/lib/DAVACL/IPrincipalCollection.php', + 'Sabre\\DAVACL\\Plugin' => $vendorDir . '/sabre/dav/lib/DAVACL/Plugin.php', + 'Sabre\\DAVACL\\Principal' => $vendorDir . '/sabre/dav/lib/DAVACL/Principal.php', + 'Sabre\\DAVACL\\PrincipalBackend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php', + 'Sabre\\DAVACL\\PrincipalBackend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php', + 'Sabre\\DAVACL\\PrincipalBackend\\CreatePrincipalSupport' => $vendorDir . '/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php', + 'Sabre\\DAVACL\\PrincipalBackend\\PDO' => $vendorDir . '/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php', + 'Sabre\\DAVACL\\PrincipalCollection' => $vendorDir . '/sabre/dav/lib/DAVACL/PrincipalCollection.php', + 'Sabre\\DAVACL\\Xml\\Property\\Acl' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Property/Acl.php', + 'Sabre\\DAVACL\\Xml\\Property\\AclRestrictions' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php', + 'Sabre\\DAVACL\\Xml\\Property\\CurrentUserPrivilegeSet' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php', + 'Sabre\\DAVACL\\Xml\\Property\\Principal' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Property/Principal.php', + 'Sabre\\DAVACL\\Xml\\Property\\SupportedPrivilegeSet' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php', + 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport' => $vendorDir . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractBasic' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractBearer' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractDigest' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php', + 'Sabre\\DAV\\Auth\\Backend\\Apache' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/Apache.php', + 'Sabre\\DAV\\Auth\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php', + 'Sabre\\DAV\\Auth\\Backend\\BasicCallBack' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php', + 'Sabre\\DAV\\Auth\\Backend\\File' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/File.php', + 'Sabre\\DAV\\Auth\\Backend\\PDO' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Backend/PDO.php', + 'Sabre\\DAV\\Auth\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Auth/Plugin.php', + 'Sabre\\DAV\\Browser\\GuessContentType' => $vendorDir . '/sabre/dav/lib/DAV/Browser/GuessContentType.php', + 'Sabre\\DAV\\Browser\\HtmlOutput' => $vendorDir . '/sabre/dav/lib/DAV/Browser/HtmlOutput.php', + 'Sabre\\DAV\\Browser\\HtmlOutputHelper' => $vendorDir . '/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php', + 'Sabre\\DAV\\Browser\\MapGetToPropFind' => $vendorDir . '/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php', + 'Sabre\\DAV\\Browser\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Browser/Plugin.php', + 'Sabre\\DAV\\Browser\\PropFindAll' => $vendorDir . '/sabre/dav/lib/DAV/Browser/PropFindAll.php', + 'Sabre\\DAV\\Client' => $vendorDir . '/sabre/dav/lib/DAV/Client.php', + 'Sabre\\DAV\\Collection' => $vendorDir . '/sabre/dav/lib/DAV/Collection.php', + 'Sabre\\DAV\\CorePlugin' => $vendorDir . '/sabre/dav/lib/DAV/CorePlugin.php', + 'Sabre\\DAV\\Exception' => $vendorDir . '/sabre/dav/lib/DAV/Exception.php', + 'Sabre\\DAV\\Exception\\BadRequest' => $vendorDir . '/sabre/dav/lib/DAV/Exception/BadRequest.php', + 'Sabre\\DAV\\Exception\\Conflict' => $vendorDir . '/sabre/dav/lib/DAV/Exception/Conflict.php', + 'Sabre\\DAV\\Exception\\ConflictingLock' => $vendorDir . '/sabre/dav/lib/DAV/Exception/ConflictingLock.php', + 'Sabre\\DAV\\Exception\\Forbidden' => $vendorDir . '/sabre/dav/lib/DAV/Exception/Forbidden.php', + 'Sabre\\DAV\\Exception\\InsufficientStorage' => $vendorDir . '/sabre/dav/lib/DAV/Exception/InsufficientStorage.php', + 'Sabre\\DAV\\Exception\\InvalidResourceType' => $vendorDir . '/sabre/dav/lib/DAV/Exception/InvalidResourceType.php', + 'Sabre\\DAV\\Exception\\InvalidSyncToken' => $vendorDir . '/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php', + 'Sabre\\DAV\\Exception\\LengthRequired' => $vendorDir . '/sabre/dav/lib/DAV/Exception/LengthRequired.php', + 'Sabre\\DAV\\Exception\\LockTokenMatchesRequestUri' => $vendorDir . '/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php', + 'Sabre\\DAV\\Exception\\Locked' => $vendorDir . '/sabre/dav/lib/DAV/Exception/Locked.php', + 'Sabre\\DAV\\Exception\\MethodNotAllowed' => $vendorDir . '/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php', + 'Sabre\\DAV\\Exception\\NotAuthenticated' => $vendorDir . '/sabre/dav/lib/DAV/Exception/NotAuthenticated.php', + 'Sabre\\DAV\\Exception\\NotFound' => $vendorDir . '/sabre/dav/lib/DAV/Exception/NotFound.php', + 'Sabre\\DAV\\Exception\\NotImplemented' => $vendorDir . '/sabre/dav/lib/DAV/Exception/NotImplemented.php', + 'Sabre\\DAV\\Exception\\PaymentRequired' => $vendorDir . '/sabre/dav/lib/DAV/Exception/PaymentRequired.php', + 'Sabre\\DAV\\Exception\\PreconditionFailed' => $vendorDir . '/sabre/dav/lib/DAV/Exception/PreconditionFailed.php', + 'Sabre\\DAV\\Exception\\ReportNotSupported' => $vendorDir . '/sabre/dav/lib/DAV/Exception/ReportNotSupported.php', + 'Sabre\\DAV\\Exception\\RequestedRangeNotSatisfiable' => $vendorDir . '/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php', + 'Sabre\\DAV\\Exception\\ServiceUnavailable' => $vendorDir . '/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php', + 'Sabre\\DAV\\Exception\\TooManyMatches' => $vendorDir . '/sabre/dav/lib/DAV/Exception/TooManyMatches.php', + 'Sabre\\DAV\\Exception\\UnsupportedMediaType' => $vendorDir . '/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php', + 'Sabre\\DAV\\FSExt\\Directory' => $vendorDir . '/sabre/dav/lib/DAV/FSExt/Directory.php', + 'Sabre\\DAV\\FSExt\\File' => $vendorDir . '/sabre/dav/lib/DAV/FSExt/File.php', + 'Sabre\\DAV\\FS\\Directory' => $vendorDir . '/sabre/dav/lib/DAV/FS/Directory.php', + 'Sabre\\DAV\\FS\\File' => $vendorDir . '/sabre/dav/lib/DAV/FS/File.php', + 'Sabre\\DAV\\FS\\Node' => $vendorDir . '/sabre/dav/lib/DAV/FS/Node.php', + 'Sabre\\DAV\\File' => $vendorDir . '/sabre/dav/lib/DAV/File.php', + 'Sabre\\DAV\\ICollection' => $vendorDir . '/sabre/dav/lib/DAV/ICollection.php', + 'Sabre\\DAV\\IExtendedCollection' => $vendorDir . '/sabre/dav/lib/DAV/IExtendedCollection.php', + 'Sabre\\DAV\\IFile' => $vendorDir . '/sabre/dav/lib/DAV/IFile.php', + 'Sabre\\DAV\\IMoveTarget' => $vendorDir . '/sabre/dav/lib/DAV/IMoveTarget.php', + 'Sabre\\DAV\\IMultiGet' => $vendorDir . '/sabre/dav/lib/DAV/IMultiGet.php', + 'Sabre\\DAV\\INode' => $vendorDir . '/sabre/dav/lib/DAV/INode.php', + 'Sabre\\DAV\\IProperties' => $vendorDir . '/sabre/dav/lib/DAV/IProperties.php', + 'Sabre\\DAV\\IQuota' => $vendorDir . '/sabre/dav/lib/DAV/IQuota.php', + 'Sabre\\DAV\\Locks\\Backend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php', + 'Sabre\\DAV\\Locks\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php', + 'Sabre\\DAV\\Locks\\Backend\\File' => $vendorDir . '/sabre/dav/lib/DAV/Locks/Backend/File.php', + 'Sabre\\DAV\\Locks\\Backend\\PDO' => $vendorDir . '/sabre/dav/lib/DAV/Locks/Backend/PDO.php', + 'Sabre\\DAV\\Locks\\LockInfo' => $vendorDir . '/sabre/dav/lib/DAV/Locks/LockInfo.php', + 'Sabre\\DAV\\Locks\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Locks/Plugin.php', + 'Sabre\\DAV\\MkCol' => $vendorDir . '/sabre/dav/lib/DAV/MkCol.php', + 'Sabre\\DAV\\Mount\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Mount/Plugin.php', + 'Sabre\\DAV\\Node' => $vendorDir . '/sabre/dav/lib/DAV/Node.php', + 'Sabre\\DAV\\PartialUpdate\\IPatchSupport' => $vendorDir . '/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php', + 'Sabre\\DAV\\PartialUpdate\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/PartialUpdate/Plugin.php', + 'Sabre\\DAV\\PropFind' => $vendorDir . '/sabre/dav/lib/DAV/PropFind.php', + 'Sabre\\DAV\\PropPatch' => $vendorDir . '/sabre/dav/lib/DAV/PropPatch.php', + 'Sabre\\DAV\\PropertyStorage\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php', + 'Sabre\\DAV\\PropertyStorage\\Backend\\PDO' => $vendorDir . '/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php', + 'Sabre\\DAV\\PropertyStorage\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/PropertyStorage/Plugin.php', + 'Sabre\\DAV\\Server' => $vendorDir . '/sabre/dav/lib/DAV/Server.php', + 'Sabre\\DAV\\ServerPlugin' => $vendorDir . '/sabre/dav/lib/DAV/ServerPlugin.php', + 'Sabre\\DAV\\Sharing\\ISharedNode' => $vendorDir . '/sabre/dav/lib/DAV/Sharing/ISharedNode.php', + 'Sabre\\DAV\\Sharing\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Sharing/Plugin.php', + 'Sabre\\DAV\\SimpleCollection' => $vendorDir . '/sabre/dav/lib/DAV/SimpleCollection.php', + 'Sabre\\DAV\\SimpleFile' => $vendorDir . '/sabre/dav/lib/DAV/SimpleFile.php', + 'Sabre\\DAV\\StringUtil' => $vendorDir . '/sabre/dav/lib/DAV/StringUtil.php', + 'Sabre\\DAV\\Sync\\ISyncCollection' => $vendorDir . '/sabre/dav/lib/DAV/Sync/ISyncCollection.php', + 'Sabre\\DAV\\Sync\\Plugin' => $vendorDir . '/sabre/dav/lib/DAV/Sync/Plugin.php', + 'Sabre\\DAV\\TemporaryFileFilterPlugin' => $vendorDir . '/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php', + 'Sabre\\DAV\\Tree' => $vendorDir . '/sabre/dav/lib/DAV/Tree.php', + 'Sabre\\DAV\\UUIDUtil' => $vendorDir . '/sabre/dav/lib/DAV/UUIDUtil.php', + 'Sabre\\DAV\\Version' => $vendorDir . '/sabre/dav/lib/DAV/Version.php', + 'Sabre\\DAV\\Xml\\Element\\Prop' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Element/Prop.php', + 'Sabre\\DAV\\Xml\\Element\\Response' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Element/Response.php', + 'Sabre\\DAV\\Xml\\Element\\Sharee' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Element/Sharee.php', + 'Sabre\\DAV\\Xml\\Property\\Complex' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/Complex.php', + 'Sabre\\DAV\\Xml\\Property\\GetLastModified' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php', + 'Sabre\\DAV\\Xml\\Property\\Href' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/Href.php', + 'Sabre\\DAV\\Xml\\Property\\Invite' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/Invite.php', + 'Sabre\\DAV\\Xml\\Property\\LocalHref' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/LocalHref.php', + 'Sabre\\DAV\\Xml\\Property\\LockDiscovery' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php', + 'Sabre\\DAV\\Xml\\Property\\ResourceType' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/ResourceType.php', + 'Sabre\\DAV\\Xml\\Property\\ShareAccess' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedLock' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedMethodSet' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedReportSet' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php', + 'Sabre\\DAV\\Xml\\Request\\Lock' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/Lock.php', + 'Sabre\\DAV\\Xml\\Request\\MkCol' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/MkCol.php', + 'Sabre\\DAV\\Xml\\Request\\PropFind' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/PropFind.php', + 'Sabre\\DAV\\Xml\\Request\\PropPatch' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/PropPatch.php', + 'Sabre\\DAV\\Xml\\Request\\ShareResource' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/ShareResource.php', + 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php', + 'Sabre\\DAV\\Xml\\Response\\MultiStatus' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php', + 'Sabre\\DAV\\Xml\\Service' => $vendorDir . '/sabre/dav/lib/DAV/Xml/Service.php', + 'Sabre\\Event\\EventEmitter' => $vendorDir . '/sabre/event/lib/EventEmitter.php', + 'Sabre\\Event\\EventEmitterInterface' => $vendorDir . '/sabre/event/lib/EventEmitterInterface.php', + 'Sabre\\Event\\EventEmitterTrait' => $vendorDir . '/sabre/event/lib/EventEmitterTrait.php', + 'Sabre\\Event\\Loop\\Loop' => $vendorDir . '/sabre/event/lib/Loop/Loop.php', + 'Sabre\\Event\\Promise' => $vendorDir . '/sabre/event/lib/Promise.php', + 'Sabre\\Event\\PromiseAlreadyResolvedException' => $vendorDir . '/sabre/event/lib/PromiseAlreadyResolvedException.php', + 'Sabre\\Event\\Version' => $vendorDir . '/sabre/event/lib/Version.php', + 'Sabre\\HTTP\\Auth\\AWS' => $vendorDir . '/sabre/http/lib/Auth/AWS.php', + 'Sabre\\HTTP\\Auth\\AbstractAuth' => $vendorDir . '/sabre/http/lib/Auth/AbstractAuth.php', + 'Sabre\\HTTP\\Auth\\Basic' => $vendorDir . '/sabre/http/lib/Auth/Basic.php', + 'Sabre\\HTTP\\Auth\\Bearer' => $vendorDir . '/sabre/http/lib/Auth/Bearer.php', + 'Sabre\\HTTP\\Auth\\Digest' => $vendorDir . '/sabre/http/lib/Auth/Digest.php', + 'Sabre\\HTTP\\Client' => $vendorDir . '/sabre/http/lib/Client.php', + 'Sabre\\HTTP\\ClientException' => $vendorDir . '/sabre/http/lib/ClientException.php', + 'Sabre\\HTTP\\ClientHttpException' => $vendorDir . '/sabre/http/lib/ClientHttpException.php', + 'Sabre\\HTTP\\HttpException' => $vendorDir . '/sabre/http/lib/HttpException.php', + 'Sabre\\HTTP\\Message' => $vendorDir . '/sabre/http/lib/Message.php', + 'Sabre\\HTTP\\MessageDecoratorTrait' => $vendorDir . '/sabre/http/lib/MessageDecoratorTrait.php', + 'Sabre\\HTTP\\MessageInterface' => $vendorDir . '/sabre/http/lib/MessageInterface.php', + 'Sabre\\HTTP\\Request' => $vendorDir . '/sabre/http/lib/Request.php', + 'Sabre\\HTTP\\RequestDecorator' => $vendorDir . '/sabre/http/lib/RequestDecorator.php', + 'Sabre\\HTTP\\RequestInterface' => $vendorDir . '/sabre/http/lib/RequestInterface.php', + 'Sabre\\HTTP\\Response' => $vendorDir . '/sabre/http/lib/Response.php', + 'Sabre\\HTTP\\ResponseDecorator' => $vendorDir . '/sabre/http/lib/ResponseDecorator.php', + 'Sabre\\HTTP\\ResponseInterface' => $vendorDir . '/sabre/http/lib/ResponseInterface.php', + 'Sabre\\HTTP\\Sapi' => $vendorDir . '/sabre/http/lib/Sapi.php', + 'Sabre\\HTTP\\URLUtil' => $vendorDir . '/sabre/http/lib/URLUtil.php', + 'Sabre\\HTTP\\Util' => $vendorDir . '/sabre/http/lib/Util.php', + 'Sabre\\HTTP\\Version' => $vendorDir . '/sabre/http/lib/Version.php', + 'Sabre\\Uri\\InvalidUriException' => $vendorDir . '/sabre/uri/lib/InvalidUriException.php', + 'Sabre\\Uri\\Version' => $vendorDir . '/sabre/uri/lib/Version.php', + 'Sabre\\VObject\\BirthdayCalendarGenerator' => $vendorDir . '/sabre/vobject/lib/BirthdayCalendarGenerator.php', + 'Sabre\\VObject\\Cli' => $vendorDir . '/sabre/vobject/lib/Cli.php', + 'Sabre\\VObject\\Component' => $vendorDir . '/sabre/vobject/lib/Component.php', + 'Sabre\\VObject\\Component\\Available' => $vendorDir . '/sabre/vobject/lib/Component/Available.php', + 'Sabre\\VObject\\Component\\VAlarm' => $vendorDir . '/sabre/vobject/lib/Component/VAlarm.php', + 'Sabre\\VObject\\Component\\VAvailability' => $vendorDir . '/sabre/vobject/lib/Component/VAvailability.php', + 'Sabre\\VObject\\Component\\VCalendar' => $vendorDir . '/sabre/vobject/lib/Component/VCalendar.php', + 'Sabre\\VObject\\Component\\VCard' => $vendorDir . '/sabre/vobject/lib/Component/VCard.php', + 'Sabre\\VObject\\Component\\VEvent' => $vendorDir . '/sabre/vobject/lib/Component/VEvent.php', + 'Sabre\\VObject\\Component\\VFreeBusy' => $vendorDir . '/sabre/vobject/lib/Component/VFreeBusy.php', + 'Sabre\\VObject\\Component\\VJournal' => $vendorDir . '/sabre/vobject/lib/Component/VJournal.php', + 'Sabre\\VObject\\Component\\VTimeZone' => $vendorDir . '/sabre/vobject/lib/Component/VTimeZone.php', + 'Sabre\\VObject\\Component\\VTodo' => $vendorDir . '/sabre/vobject/lib/Component/VTodo.php', + 'Sabre\\VObject\\DateTimeParser' => $vendorDir . '/sabre/vobject/lib/DateTimeParser.php', + 'Sabre\\VObject\\Document' => $vendorDir . '/sabre/vobject/lib/Document.php', + 'Sabre\\VObject\\ElementList' => $vendorDir . '/sabre/vobject/lib/ElementList.php', + 'Sabre\\VObject\\EofException' => $vendorDir . '/sabre/vobject/lib/EofException.php', + 'Sabre\\VObject\\FreeBusyData' => $vendorDir . '/sabre/vobject/lib/FreeBusyData.php', + 'Sabre\\VObject\\FreeBusyGenerator' => $vendorDir . '/sabre/vobject/lib/FreeBusyGenerator.php', + 'Sabre\\VObject\\ITip\\Broker' => $vendorDir . '/sabre/vobject/lib/ITip/Broker.php', + 'Sabre\\VObject\\ITip\\ITipException' => $vendorDir . '/sabre/vobject/lib/ITip/ITipException.php', + 'Sabre\\VObject\\ITip\\Message' => $vendorDir . '/sabre/vobject/lib/ITip/Message.php', + 'Sabre\\VObject\\ITip\\SameOrganizerForAllComponentsException' => $vendorDir . '/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php', + 'Sabre\\VObject\\InvalidDataException' => $vendorDir . '/sabre/vobject/lib/InvalidDataException.php', + 'Sabre\\VObject\\Node' => $vendorDir . '/sabre/vobject/lib/Node.php', + 'Sabre\\VObject\\PHPUnitAssertions' => $vendorDir . '/sabre/vobject/lib/PHPUnitAssertions.php', + 'Sabre\\VObject\\Parameter' => $vendorDir . '/sabre/vobject/lib/Parameter.php', + 'Sabre\\VObject\\ParseException' => $vendorDir . '/sabre/vobject/lib/ParseException.php', + 'Sabre\\VObject\\Parser\\Json' => $vendorDir . '/sabre/vobject/lib/Parser/Json.php', + 'Sabre\\VObject\\Parser\\MimeDir' => $vendorDir . '/sabre/vobject/lib/Parser/MimeDir.php', + 'Sabre\\VObject\\Parser\\Parser' => $vendorDir . '/sabre/vobject/lib/Parser/Parser.php', + 'Sabre\\VObject\\Parser\\XML' => $vendorDir . '/sabre/vobject/lib/Parser/XML.php', + 'Sabre\\VObject\\Parser\\XML\\Element\\KeyValue' => $vendorDir . '/sabre/vobject/lib/Parser/XML/Element/KeyValue.php', + 'Sabre\\VObject\\Property' => $vendorDir . '/sabre/vobject/lib/Property.php', + 'Sabre\\VObject\\Property\\Binary' => $vendorDir . '/sabre/vobject/lib/Property/Binary.php', + 'Sabre\\VObject\\Property\\Boolean' => $vendorDir . '/sabre/vobject/lib/Property/Boolean.php', + 'Sabre\\VObject\\Property\\FlatText' => $vendorDir . '/sabre/vobject/lib/Property/FlatText.php', + 'Sabre\\VObject\\Property\\FloatValue' => $vendorDir . '/sabre/vobject/lib/Property/FloatValue.php', + 'Sabre\\VObject\\Property\\ICalendar\\CalAddress' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/CalAddress.php', + 'Sabre\\VObject\\Property\\ICalendar\\Date' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/Date.php', + 'Sabre\\VObject\\Property\\ICalendar\\DateTime' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/DateTime.php', + 'Sabre\\VObject\\Property\\ICalendar\\Duration' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/Duration.php', + 'Sabre\\VObject\\Property\\ICalendar\\Period' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/Period.php', + 'Sabre\\VObject\\Property\\ICalendar\\Recur' => $vendorDir . '/sabre/vobject/lib/Property/ICalendar/Recur.php', + 'Sabre\\VObject\\Property\\IntegerValue' => $vendorDir . '/sabre/vobject/lib/Property/IntegerValue.php', + 'Sabre\\VObject\\Property\\Text' => $vendorDir . '/sabre/vobject/lib/Property/Text.php', + 'Sabre\\VObject\\Property\\Time' => $vendorDir . '/sabre/vobject/lib/Property/Time.php', + 'Sabre\\VObject\\Property\\Unknown' => $vendorDir . '/sabre/vobject/lib/Property/Unknown.php', + 'Sabre\\VObject\\Property\\Uri' => $vendorDir . '/sabre/vobject/lib/Property/Uri.php', + 'Sabre\\VObject\\Property\\UtcOffset' => $vendorDir . '/sabre/vobject/lib/Property/UtcOffset.php', + 'Sabre\\VObject\\Property\\VCard\\Date' => $vendorDir . '/sabre/vobject/lib/Property/VCard/Date.php', + 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime' => $vendorDir . '/sabre/vobject/lib/Property/VCard/DateAndOrTime.php', + 'Sabre\\VObject\\Property\\VCard\\DateTime' => $vendorDir . '/sabre/vobject/lib/Property/VCard/DateTime.php', + 'Sabre\\VObject\\Property\\VCard\\LanguageTag' => $vendorDir . '/sabre/vobject/lib/Property/VCard/LanguageTag.php', + 'Sabre\\VObject\\Property\\VCard\\TimeStamp' => $vendorDir . '/sabre/vobject/lib/Property/VCard/TimeStamp.php', + 'Sabre\\VObject\\Reader' => $vendorDir . '/sabre/vobject/lib/Reader.php', + 'Sabre\\VObject\\Recur\\EventIterator' => $vendorDir . '/sabre/vobject/lib/Recur/EventIterator.php', + 'Sabre\\VObject\\Recur\\MaxInstancesExceededException' => $vendorDir . '/sabre/vobject/lib/Recur/MaxInstancesExceededException.php', + 'Sabre\\VObject\\Recur\\NoInstancesException' => $vendorDir . '/sabre/vobject/lib/Recur/NoInstancesException.php', + 'Sabre\\VObject\\Recur\\RDateIterator' => $vendorDir . '/sabre/vobject/lib/Recur/RDateIterator.php', + 'Sabre\\VObject\\Recur\\RRuleIterator' => $vendorDir . '/sabre/vobject/lib/Recur/RRuleIterator.php', + 'Sabre\\VObject\\Settings' => $vendorDir . '/sabre/vobject/lib/Settings.php', + 'Sabre\\VObject\\Splitter\\ICalendar' => $vendorDir . '/sabre/vobject/lib/Splitter/ICalendar.php', + 'Sabre\\VObject\\Splitter\\SplitterInterface' => $vendorDir . '/sabre/vobject/lib/Splitter/SplitterInterface.php', + 'Sabre\\VObject\\Splitter\\VCard' => $vendorDir . '/sabre/vobject/lib/Splitter/VCard.php', + 'Sabre\\VObject\\StringUtil' => $vendorDir . '/sabre/vobject/lib/StringUtil.php', + 'Sabre\\VObject\\TimeZoneUtil' => $vendorDir . '/sabre/vobject/lib/TimeZoneUtil.php', + 'Sabre\\VObject\\UUIDUtil' => $vendorDir . '/sabre/vobject/lib/UUIDUtil.php', + 'Sabre\\VObject\\VCardConverter' => $vendorDir . '/sabre/vobject/lib/VCardConverter.php', + 'Sabre\\VObject\\Version' => $vendorDir . '/sabre/vobject/lib/Version.php', + 'Sabre\\VObject\\Writer' => $vendorDir . '/sabre/vobject/lib/Writer.php', + 'Sabre\\Xml\\ContextStackTrait' => $vendorDir . '/sabre/xml/lib/ContextStackTrait.php', + 'Sabre\\Xml\\Element' => $vendorDir . '/sabre/xml/lib/Element.php', + 'Sabre\\Xml\\Element\\Base' => $vendorDir . '/sabre/xml/lib/Element/Base.php', + 'Sabre\\Xml\\Element\\Cdata' => $vendorDir . '/sabre/xml/lib/Element/Cdata.php', + 'Sabre\\Xml\\Element\\Elements' => $vendorDir . '/sabre/xml/lib/Element/Elements.php', + 'Sabre\\Xml\\Element\\KeyValue' => $vendorDir . '/sabre/xml/lib/Element/KeyValue.php', + 'Sabre\\Xml\\Element\\Uri' => $vendorDir . '/sabre/xml/lib/Element/Uri.php', + 'Sabre\\Xml\\Element\\XmlFragment' => $vendorDir . '/sabre/xml/lib/Element/XmlFragment.php', + 'Sabre\\Xml\\LibXMLException' => $vendorDir . '/sabre/xml/lib/LibXMLException.php', + 'Sabre\\Xml\\ParseException' => $vendorDir . '/sabre/xml/lib/ParseException.php', + 'Sabre\\Xml\\Reader' => $vendorDir . '/sabre/xml/lib/Reader.php', + 'Sabre\\Xml\\Service' => $vendorDir . '/sabre/xml/lib/Service.php', + 'Sabre\\Xml\\Version' => $vendorDir . '/sabre/xml/lib/Version.php', + 'Sabre\\Xml\\Writer' => $vendorDir . '/sabre/xml/lib/Writer.php', + 'Sabre\\Xml\\XmlDeserializable' => $vendorDir . '/sabre/xml/lib/XmlDeserializable.php', + 'Sabre\\Xml\\XmlSerializable' => $vendorDir . '/sabre/xml/lib/XmlSerializable.php', 'Sanitizer' => $baseDir . '/../../Services/Utilities/classes/Sanitizer.php', 'SeqActivity' => $baseDir . '/../../Modules/Scorm2004/classes/adlparser/SeqActivity.php', 'SeqCondition' => $baseDir . '/../../Modules/Scorm2004/classes/adlparser/SeqCondition.php', @@ -1538,7 +1868,6 @@ 'SurveyTextQuestion' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestion.php', 'SurveyTextQuestionEvaluation' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionEvaluation.php', 'SurveyTextQuestionGUI' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionGUI.php', - 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', 'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php', @@ -1547,7 +1876,6 @@ 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php', 'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php', 'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php', - 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', 'TCPDF' => $vendorDir . '/tecnickcom/tcpdf/tcpdf.php', @@ -2004,9 +2332,6 @@ '_DiffOp_Copy' => $baseDir . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', '_DiffOp_Delete' => $baseDir . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', '_HWLDF_WordAccumulator' => $baseDir . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', - '_parse_lockinfo' => $baseDir . '/../../Services/WebDAV/classes/Tools/_parse_lockinfo.php', - '_parse_propfind' => $baseDir . '/../../Services/WebDAV/classes/Tools/_parse_propfind.php', - '_parse_proppatch' => $baseDir . '/../../Services/WebDAV/classes/Tools/_parse_proppatch.php', 'arBuilder' => $baseDir . '/../../Services/ActiveRecord/Fields/Converter/class.arBuilder.php', 'arCalledClassCache' => $baseDir . '/../../Services/ActiveRecord/Cache/class.arCalledClassCache.php', 'arConcat' => $baseDir . '/../../Services/ActiveRecord/Connector/Concat/class.arConcat.php', @@ -3068,6 +3393,7 @@ 'ilClaimingPermissionHelper' => $baseDir . '/../../Services/Component/classes/class.ilClaimingPermissionHelper.php', 'ilClassificationBlockGUI' => $baseDir . '/../../Services/Classification/classes/class.ilClassificationBlockGUI.php', 'ilClassificationProvider' => $baseDir . '/../../Services/Classification/classes/class.ilClassificationProvider.php', + 'ilClientNodeDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilClientNodeDAV.php', 'ilClipboardTableGUI' => $baseDir . '/../../Services/Clipboard/classes/class.ilClipboardTableGUI.php', 'ilCloudConnector' => $baseDir . '/../../Modules/Cloud/classes/class.ilCloudConnector.php', 'ilCloudException' => $baseDir . '/../../Modules/Cloud/exceptions/class.ilCloudException.php', @@ -3191,7 +3517,6 @@ 'ilContentStylesTableGUI' => $baseDir . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => $baseDir . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => $baseDir . '/../../Services/Context/classes/class.ilContextApacheSSO.php', - 'ilContextBehat' => $baseDir . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => $baseDir . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => $baseDir . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => $baseDir . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -3301,10 +3626,6 @@ 'ilCustomUserFieldsGUI' => $baseDir . '/../../Services/User/classes/class.ilCustomUserFieldsGUI.php', 'ilDAVActivationChecker' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVActivationChecker.php', 'ilDAVCronDiskQuota' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVCronDiskQuota.php', - 'ilDAVLocks' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVLocks.php', - 'ilDAVProperties' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVProperties.php', - 'ilDAVServer' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVServer.php', - 'ilDAVUtils' => $baseDir . '/../../Services/WebDAV/classes/class.ilDAVUtils.php', 'ilDB' => $baseDir . '/../../Services/Database/classes/MDB2/class.ilDB.php', 'ilDBAnalyzer' => $baseDir . '/../../Services/Database/classes/class.ilDBAnalyzer.php', 'ilDBConstants' => $baseDir . '/../../Services/Database/classes/class.ilDBConstants.php', @@ -4482,6 +4803,7 @@ 'ilModulesSessionExtractor' => $baseDir . '/../../Services/WorkflowEngine/classes/extractors/class.ilModulesSessionExtractor.php', 'ilModulesTableGUI' => $baseDir . '/../../Services/Repository/classes/class.ilModulesTableGUI.php', 'ilModulesTestTasks' => $baseDir . '/../../Services/WorkflowEngine/classes/tasks/class.ilModulesTestTasks.php', + 'ilMountPointDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilMountPointDAV.php', 'ilMultiFilesSubmitRecursiveSlashesStripper' => $baseDir . '/../../Services/Form/classes/class.ilMultiFilesSubmitRecursiveSlashesStripper.php', 'ilMultiSelectInputGUI' => $baseDir . '/../../Services/Form/classes/class.ilMultiSelectInputGUI.php', 'ilMultiSrtConfirmationTable2GUI' => $baseDir . '/../../Services/MediaObjects/classes/class.ilMultiSrtConfirmationTable2GUI.php', @@ -4592,7 +4914,7 @@ 'ilObjCalendarSettingsGUI' => $baseDir . '/../../Services/Calendar/classes/class.ilObjCalendarSettingsGUI.php', 'ilObjCategory' => $baseDir . '/../../Modules/Category/classes/class.ilObjCategory.php', 'ilObjCategoryAccess' => $baseDir . '/../../Modules/Category/classes/class.ilObjCategoryAccess.php', - 'ilObjCategoryDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjCategoryDAV.php', + 'ilObjCategoryDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php', 'ilObjCategoryGUI' => $baseDir . '/../../Modules/Category/classes/class.ilObjCategoryGUI.php', 'ilObjCategoryListGUI' => $baseDir . '/../../Modules/Category/classes/class.ilObjCategoryListGUI.php', 'ilObjCategoryReference' => $baseDir . '/../../Modules/CategoryReference/classes/class.ilObjCategoryReference.php', @@ -4620,6 +4942,7 @@ 'ilObjContactAdministration' => $baseDir . '/../../Services/Contact/classes/class.ilObjContactAdministration.php', 'ilObjContactAdministrationAccess' => $baseDir . '/../../Services/Contact/classes/class.ilObjContactAdministrationAccess.php', 'ilObjContactAdministrationGUI' => $baseDir . '/../../Services/Contact/classes/class.ilObjContactAdministrationGUI.php', + 'ilObjContainerDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjContainerDAV.php', 'ilObjContentObject' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObject.php', 'ilObjContentObjectAccess' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObjectAccess.php', 'ilObjContentObjectGUI' => $baseDir . '/../../Modules/LearningModule/classes/class.ilObjContentObjectGUI.php', @@ -4632,7 +4955,7 @@ 'ilObjCourseAdministration' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseAdministration.php', 'ilObjCourseAdministrationAccess' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseAdministrationAccess.php', 'ilObjCourseAdministrationGUI' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseAdministrationGUI.php', - 'ilObjCourseDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjCourseDAV.php', + 'ilObjCourseDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjCourseDAV.php', 'ilObjCourseGUI' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseGUI.php', 'ilObjCourseGrouping' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseGrouping.php', 'ilObjCourseGroupingGUI' => $baseDir . '/../../Modules/Course/classes/class.ilObjCourseGroupingGUI.php', @@ -4681,13 +5004,13 @@ 'ilObjFileBasedLMAccess' => $baseDir . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMAccess.php', 'ilObjFileBasedLMGUI' => $baseDir . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php', 'ilObjFileBasedLMListGUI' => $baseDir . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMListGUI.php', - 'ilObjFileDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjFileDAV.php', + 'ilObjFileDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjFileDAV.php', 'ilObjFileGUI' => $baseDir . '/../../Modules/File/classes/class.ilObjFileGUI.php', 'ilObjFileHandlingQuestionType' => $baseDir . '/../../Modules/TestQuestionPool/interfaces/interface.ilObjFileHandlingQuestionType.php', 'ilObjFileListGUI' => $baseDir . '/../../Modules/File/classes/class.ilObjFileListGUI.php', 'ilObjFolder' => $baseDir . '/../../Modules/Folder/classes/class.ilObjFolder.php', 'ilObjFolderAccess' => $baseDir . '/../../Modules/Folder/classes/class.ilObjFolderAccess.php', - 'ilObjFolderDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjFolderDAV.php', + 'ilObjFolderDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjFolderDAV.php', 'ilObjFolderGUI' => $baseDir . '/../../Modules/Folder/classes/class.ilObjFolderGUI.php', 'ilObjFolderListGUI' => $baseDir . '/../../Modules/Folder/classes/class.ilObjFolderListGUI.php', 'ilObjForum' => $baseDir . '/../../Modules/Forum/classes/class.ilObjForum.php', @@ -4710,7 +5033,7 @@ 'ilObjGroupAdministration' => $baseDir . '/../../Modules/Group/classes/class.ilObjGroupAdministration.php', 'ilObjGroupAdministrationAccess' => $baseDir . '/../../Modules/Group/classes/class.ilObjGroupAdministrationAccess.php', 'ilObjGroupAdministrationGUI' => $baseDir . '/../../Modules/Group/classes/class.ilObjGroupAdministrationGUI.php', - 'ilObjGroupDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjGroupDAV.php', + 'ilObjGroupDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjGroupDAV.php', 'ilObjGroupGUI' => $baseDir . '/../../Modules/Group/classes/class.ilObjGroupGUI.php', 'ilObjGroupListGUI' => $baseDir . '/../../Modules/Group/classes/class.ilObjGroupListGUI.php', 'ilObjGroupReference' => $baseDir . '/../../Modules/GroupReference/classes/class.ilObjGroupReference.php', @@ -4780,7 +5103,6 @@ 'ilObjMediaPoolGUI' => $baseDir . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php', 'ilObjMediaPoolListGUI' => $baseDir . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolListGUI.php', 'ilObjMediaPoolSubItemListGUI' => $baseDir . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php', - 'ilObjMountPointDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjMountPointDAV.php', 'ilObjNewsSettings' => $baseDir . '/../../Services/News/classes/class.ilObjNewsSettings.php', 'ilObjNewsSettingsAccess' => $baseDir . '/../../Services/News/classes/class.ilObjNewsSettingsAccess.php', 'ilObjNewsSettingsGUI' => $baseDir . '/../../Services/News/classes/class.ilObjNewsSettingsGUI.php', @@ -4789,8 +5111,6 @@ 'ilObjNotificationAdminGUI' => $baseDir . '/../../Services/Notifications/classes/class.ilObjNotificationAdminGUI.php', 'ilObjNotificationSettings' => $baseDir . '/../../Services/Notification/classes/class.ilObjNotificationSettings.php', 'ilObjNotificationSettingsGUI' => $baseDir . '/../../Services/Notification/classes/class.ilObjNotificationSettingsGUI.php', - 'ilObjNull' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjNull.php', - 'ilObjNullDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjNullDAV.php', 'ilObjObjectFolder' => $baseDir . '/../../Services/Object/classes/class.ilObjObjectFolder.php', 'ilObjObjectFolderAccess' => $baseDir . '/../../Services/Object/classes/class.ilObjObjectFolderAccess.php', 'ilObjObjectFolderGUI' => $baseDir . '/../../Services/Object/classes/class.ilObjObjectFolderGUI.php', @@ -4873,6 +5193,7 @@ 'ilObjRemoteWikiAccess' => $baseDir . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiAccess.php', 'ilObjRemoteWikiGUI' => $baseDir . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiGUI.php', 'ilObjRemoteWikiListGUI' => $baseDir . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiListGUI.php', + 'ilObjRepositoryRootDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjRepositoryRootDAV.php', 'ilObjRepositorySettings' => $baseDir . '/../../Services/Repository/classes/class.ilObjRepositorySettings.php', 'ilObjRepositorySettingsAccess' => $baseDir . '/../../Services/Repository/classes/class.ilObjRepositorySettingsAccess.php', 'ilObjRepositorySettingsGUI' => $baseDir . '/../../Services/Repository/classes/class.ilObjRepositorySettingsGUI.php', @@ -4883,7 +5204,6 @@ 'ilObjRoleGUI' => $baseDir . '/../../Services/AccessControl/classes/class.ilObjRoleGUI.php', 'ilObjRoleTemplate' => $baseDir . '/../../Services/AccessControl/classes/class.ilObjRoleTemplate.php', 'ilObjRoleTemplateGUI' => $baseDir . '/../../Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php', - 'ilObjRootDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjRootDAV.php', 'ilObjRootFolder' => $baseDir . '/../../Modules/RootFolder/classes/class.ilObjRootFolder.php', 'ilObjRootFolderAccess' => $baseDir . '/../../Modules/RootFolder/classes/class.ilObjRootFolderAccess.php', 'ilObjRootFolderGUI' => $baseDir . '/../../Modules/RootFolder/classes/class.ilObjRootFolderGUI.php', @@ -5031,7 +5351,7 @@ 'ilObjectCustomUserFieldHistory' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldHistory.php', 'ilObjectCustomUserFieldsGUI' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsGUI.php', 'ilObjectCustomUserFieldsTableGUI' => $baseDir . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsTableGUI.php', - 'ilObjectDAV' => $baseDir . '/../../Services/WebDAV/classes/class.ilObjectDAV.php', + 'ilObjectDAV' => $baseDir . '/../../Services/WebDAV/classes/dav/class.ilObjectDAV.php', 'ilObjectDataCache' => $baseDir . '/../../Services/Object/classes/class.ilObjectDataCache.php', 'ilObjectDataDeletionLog' => $baseDir . '/../../Services/Object/classes/class.ilObjectDataDeletionLog.php', 'ilObjectDataSet' => $baseDir . '/../../Services/Object/classes/class.ilObjectDataSet.php', @@ -6337,6 +6657,15 @@ 'ilWaitingListTableGUI' => $baseDir . '/../../Services/Membership/classes/class.ilWaitingListTableGUI.php', 'ilWebAccessChecker' => $baseDir . '/../../Services/WebAccessChecker/classes/class.ilWebAccessChecker.php', 'ilWebAccessCheckerDelivery' => $baseDir . '/../../Services/WebAccessChecker/classes/class.ilWebAccessCheckerDelivery.php', + 'ilWebDAVAuthentication' => $baseDir . '/../../Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php', + 'ilWebDAVDBManager' => $baseDir . '/../../Services/WebDAV/classes/db/class.ilWebDAVDBManager.php', + 'ilWebDAVLockBackend' => $baseDir . '/../../Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php', + 'ilWebDAVLockObject' => $baseDir . '/../../Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php', + 'ilWebDAVMountInstructions' => $baseDir . '/../../Services/WebDAV/classes/class.ilWebDAVMountInstructions.php', + 'ilWebDAVMountInstructionsGUI' => $baseDir . '/../../Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php', + 'ilWebDAVRequestHandler' => $baseDir . '/../../Services/WebDAV/classes/class.ilWebDAVRequestHandler.php', + 'ilWebDAVTree' => $baseDir . '/../../Services/WebDAV/classes/class.ilWebDAVTree.php', + 'ilWebDAVUtil' => $baseDir . '/../../Services/WebDAV/classes/class.ilWebDAVUtil.php', 'ilWebLinkXmlParser' => $baseDir . '/../../Modules/WebResource/classes/class.ilWebLinkXmlParser.php', 'ilWebLinkXmlParserException' => $baseDir . '/../../Modules/WebResource/classes/class.ilWebLinkXmlParserException.php', 'ilWebLinkXmlWriter' => $baseDir . '/../../Modules/WebResource/classes/class.ilWebLinkXmlWriter.php', diff --git a/libs/composer/vendor/composer/autoload_files.php b/libs/composer/vendor/composer/autoload_files.php index 3426d185b237..4fecdd6b7c0d 100644 --- a/libs/composer/vendor/composer/autoload_files.php +++ b/libs/composer/vendor/composer/autoload_files.php @@ -6,6 +6,13 @@ $baseDir = dirname($vendorDir); return array( + '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php', + '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php', + 'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php', + 'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php', + '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php', + '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php', + 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php', '6175f5662c2e26de0149cb048cda7209' => $vendorDir . '/simplesamlphp/saml2/src/_autoload.php', '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', diff --git a/libs/composer/vendor/composer/autoload_psr4.php b/libs/composer/vendor/composer/autoload_psr4.php index f2faa26c3201..35b5bbc4cac7 100644 --- a/libs/composer/vendor/composer/autoload_psr4.php +++ b/libs/composer/vendor/composer/autoload_psr4.php @@ -13,6 +13,15 @@ 'Twig\\' => array($vendorDir . '/twig/twig/src'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), 'SimpleSAML\\' => array($vendorDir . '/simplesamlphp/simplesamlphp/lib/SimpleSAML'), + 'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'), + 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'), + 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'), + 'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'), + 'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'), + 'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'), + 'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'), + 'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'), + 'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'), 'RobRichards\\XMLSecLibs\\' => array($vendorDir . '/robrichards/xmlseclibs/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), diff --git a/libs/composer/vendor/composer/autoload_static.php b/libs/composer/vendor/composer/autoload_static.php index bf874fd27731..482e2fe6df49 100644 --- a/libs/composer/vendor/composer/autoload_static.php +++ b/libs/composer/vendor/composer/autoload_static.php @@ -7,6 +7,13 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 { public static $files = array ( + '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php', + '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php', + 'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php', + 'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php', + '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php', + '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php', + 'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php', '6175f5662c2e26de0149cb048cda7209' => __DIR__ . '/..' . '/simplesamlphp/saml2/src/_autoload.php', '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', @@ -33,6 +40,15 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 array ( 'Symfony\\Component\\Yaml\\' => 23, 'SimpleSAML\\' => 11, + 'Sabre\\Xml\\' => 10, + 'Sabre\\VObject\\' => 14, + 'Sabre\\Uri\\' => 10, + 'Sabre\\HTTP\\' => 11, + 'Sabre\\Event\\' => 12, + 'Sabre\\DAV\\' => 10, + 'Sabre\\DAVACL\\' => 13, + 'Sabre\\CardDAV\\' => 14, + 'Sabre\\CalDAV\\' => 13, ), 'R' => array ( @@ -101,6 +117,42 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 array ( 0 => __DIR__ . '/..' . '/simplesamlphp/simplesamlphp/lib/SimpleSAML', ), + 'Sabre\\Xml\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/xml/lib', + ), + 'Sabre\\VObject\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/vobject/lib', + ), + 'Sabre\\Uri\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/uri/lib', + ), + 'Sabre\\HTTP\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/http/lib', + ), + 'Sabre\\Event\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/event/lib', + ), + 'Sabre\\DAV\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV', + ), + 'Sabre\\DAVACL\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL', + ), + 'Sabre\\CardDAV\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV', + ), + 'Sabre\\CalDAV\\' => + array ( + 0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV', + ), 'RobRichards\\XMLSecLibs\\' => array ( 0 => __DIR__ . '/..' . '/robrichards/xmlseclibs/src', @@ -533,7 +585,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'HTMLPurifier_VarParser_Flexible' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php', 'HTMLPurifier_VarParser_Native' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php', 'HTMLPurifier_Zipper' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php', - 'HTTP_WebDAV_Server' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/Server.php', 'ILIAS' => __DIR__ . '/../..' . '/../../Services/Init/classes/class.ilias.php', 'ILIAS\\BackgroundTasks\\Bucket' => __DIR__ . '/../..' . '/../../src/BackgroundTasks/Bucket.php', 'ILIAS\\BackgroundTasks\\BucketMeta' => __DIR__ . '/../..' . '/../../src/BackgroundTasks/BucketMeta.php', @@ -1025,6 +1076,12 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\Validation\\Constraints\\LessThan' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/LessThan.php', 'ILIAS\\Validation\\Constraints\\Not' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Not.php', 'ILIAS\\Validation\\Constraints\\Parallel' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Parallel.php', + 'ILIAS\\Validation\\Constraints\\Password\\Factory' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/Factory.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasLowerChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasLowerChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasMinLength' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasMinLength.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasNumbers' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasNumbers.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasSpecialChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasSpecialChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasUpperChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasUpperChars.php', 'ILIAS\\Validation\\Constraints\\Sequential' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Sequential.php', 'ILIAS\\Validation\\Factory' => __DIR__ . '/../..' . '/../../src/Validation/Factory.php', 'ILIAS\\WebAccessChecker\\HttpServiceAware' => __DIR__ . '/../..' . '/../../Services/WebAccessChecker/classes/HttpServiceAware.php', @@ -1197,9 +1254,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', 'Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', 'Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', - 'Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', - 'Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', - 'Monolog\\Handler\\SlackbotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', 'Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', 'Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', 'Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', @@ -1215,7 +1269,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', 'Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', 'Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', - 'Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', 'Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', 'Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', 'Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', @@ -1430,13 +1483,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'POP3' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.pop3.php', 'Parser' => __DIR__ . '/../..' . '/../../Services/Utilities/classes/Parser.php', 'Pimple\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Container.php', - 'Pimple\\Exception\\ExpectedInvokableException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', - 'Pimple\\Exception\\FrozenServiceException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', - 'Pimple\\Exception\\InvalidServiceIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', - 'Pimple\\Exception\\UnknownIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', - 'Pimple\\Psr11\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/Container.php', - 'Pimple\\Psr11\\ServiceLocator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', - 'Pimple\\ServiceIterator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceIterator.php', 'Pimple\\ServiceProviderInterface' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', 'Pimple\\Tests\\Fixtures\\Invokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', 'Pimple\\Tests\\Fixtures\\NonInvokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', @@ -1444,9 +1490,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Pimple\\Tests\\Fixtures\\Service' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', 'Pimple\\Tests\\PimpleTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', - 'Pimple\\Tests\\Psr11\\ContainerTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', - 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', - 'Pimple\\Tests\\ServiceIteratorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', @@ -1625,6 +1668,345 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'SAML2\\XML\\samlp\\Extensions' => __DIR__ . '/..' . '/simplesamlphp/saml2/src/SAML2/XML/samlp/Extensions.php', 'SAML2\\XML\\shibmd\\Scope' => __DIR__ . '/..' . '/simplesamlphp/saml2/src/SAML2/XML/shibmd/Scope.php', 'SMTP' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.smtp.php', + 'Sabre\\CalDAV\\Backend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php', + 'Sabre\\CalDAV\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php', + 'Sabre\\CalDAV\\Backend\\NotificationSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php', + 'Sabre\\CalDAV\\Backend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/PDO.php', + 'Sabre\\CalDAV\\Backend\\SchedulingSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php', + 'Sabre\\CalDAV\\Backend\\SharingSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/SharingSupport.php', + 'Sabre\\CalDAV\\Backend\\SimplePDO' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/SimplePDO.php', + 'Sabre\\CalDAV\\Backend\\SubscriptionSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php', + 'Sabre\\CalDAV\\Backend\\SyncSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/SyncSupport.php', + 'Sabre\\CalDAV\\Calendar' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Calendar.php', + 'Sabre\\CalDAV\\CalendarHome' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/CalendarHome.php', + 'Sabre\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/CalendarObject.php', + 'Sabre\\CalDAV\\CalendarQueryValidator' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/CalendarQueryValidator.php', + 'Sabre\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/CalendarRoot.php', + 'Sabre\\CalDAV\\Exception\\InvalidComponentType' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php', + 'Sabre\\CalDAV\\ICSExportPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/ICSExportPlugin.php', + 'Sabre\\CalDAV\\ICalendar' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/ICalendar.php', + 'Sabre\\CalDAV\\ICalendarObject' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/ICalendarObject.php', + 'Sabre\\CalDAV\\ICalendarObjectContainer' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php', + 'Sabre\\CalDAV\\ISharedCalendar' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/ISharedCalendar.php', + 'Sabre\\CalDAV\\Notifications\\Collection' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Notifications/Collection.php', + 'Sabre\\CalDAV\\Notifications\\ICollection' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Notifications/ICollection.php', + 'Sabre\\CalDAV\\Notifications\\INode' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Notifications/INode.php', + 'Sabre\\CalDAV\\Notifications\\Node' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Notifications/Node.php', + 'Sabre\\CalDAV\\Notifications\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Notifications/Plugin.php', + 'Sabre\\CalDAV\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Plugin.php', + 'Sabre\\CalDAV\\Principal\\Collection' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/Collection.php', + 'Sabre\\CalDAV\\Principal\\IProxyRead' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/IProxyRead.php', + 'Sabre\\CalDAV\\Principal\\IProxyWrite' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php', + 'Sabre\\CalDAV\\Principal\\ProxyRead' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/ProxyRead.php', + 'Sabre\\CalDAV\\Principal\\ProxyWrite' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php', + 'Sabre\\CalDAV\\Principal\\User' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Principal/User.php', + 'Sabre\\CalDAV\\Schedule\\IInbox' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/IInbox.php', + 'Sabre\\CalDAV\\Schedule\\IMipPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php', + 'Sabre\\CalDAV\\Schedule\\IOutbox' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/IOutbox.php', + 'Sabre\\CalDAV\\Schedule\\ISchedulingObject' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php', + 'Sabre\\CalDAV\\Schedule\\Inbox' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/Inbox.php', + 'Sabre\\CalDAV\\Schedule\\Outbox' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/Outbox.php', + 'Sabre\\CalDAV\\Schedule\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/Plugin.php', + 'Sabre\\CalDAV\\Schedule\\SchedulingObject' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php', + 'Sabre\\CalDAV\\SharedCalendar' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/SharedCalendar.php', + 'Sabre\\CalDAV\\SharingPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/SharingPlugin.php', + 'Sabre\\CalDAV\\Subscriptions\\ISubscription' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php', + 'Sabre\\CalDAV\\Subscriptions\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php', + 'Sabre\\CalDAV\\Subscriptions\\Subscription' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php', + 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php', + 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php', + 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php', + 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php', + 'Sabre\\CalDAV\\Xml\\Notification\\Invite' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php', + 'Sabre\\CalDAV\\Xml\\Notification\\InviteReply' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php', + 'Sabre\\CalDAV\\Xml\\Notification\\NotificationInterface' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php', + 'Sabre\\CalDAV\\Xml\\Notification\\SystemStatus' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php', + 'Sabre\\CalDAV\\Xml\\Property\\AllowedSharingModes' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php', + 'Sabre\\CalDAV\\Xml\\Property\\EmailAddressSet' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php', + 'Sabre\\CalDAV\\Xml\\Property\\Invite' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/Invite.php', + 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarData' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php', + 'Sabre\\CalDAV\\Xml\\Property\\SupportedCollationSet' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php', + 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php', + 'Sabre\\CalDAV\\Xml\\Request\\InviteReply' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php', + 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php', + 'Sabre\\CalDAV\\Xml\\Request\\Share' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Xml/Request/Share.php', + 'Sabre\\CardDAV\\AddressBook' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/AddressBook.php', + 'Sabre\\CardDAV\\AddressBookHome' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/AddressBookHome.php', + 'Sabre\\CardDAV\\AddressBookRoot' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/AddressBookRoot.php', + 'Sabre\\CardDAV\\Backend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php', + 'Sabre\\CardDAV\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Backend/BackendInterface.php', + 'Sabre\\CardDAV\\Backend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Backend/PDO.php', + 'Sabre\\CardDAV\\Backend\\SyncSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Backend/SyncSupport.php', + 'Sabre\\CardDAV\\Card' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Card.php', + 'Sabre\\CardDAV\\IAddressBook' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/IAddressBook.php', + 'Sabre\\CardDAV\\ICard' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/ICard.php', + 'Sabre\\CardDAV\\IDirectory' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/IDirectory.php', + 'Sabre\\CardDAV\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Plugin.php', + 'Sabre\\CardDAV\\VCFExportPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/VCFExportPlugin.php', + 'Sabre\\CardDAV\\Xml\\Filter\\AddressData' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php', + 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php', + 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php', + 'Sabre\\CardDAV\\Xml\\Property\\SupportedAddressData' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php', + 'Sabre\\CardDAV\\Xml\\Property\\SupportedCollationSet' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php', + 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php', + 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport' => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php', + 'Sabre\\DAVACL\\ACLTrait' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/ACLTrait.php', + 'Sabre\\DAVACL\\AbstractPrincipalCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php', + 'Sabre\\DAVACL\\Exception\\AceConflict' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Exception/AceConflict.php', + 'Sabre\\DAVACL\\Exception\\NeedPrivileges' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php', + 'Sabre\\DAVACL\\Exception\\NoAbstract' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Exception/NoAbstract.php', + 'Sabre\\DAVACL\\Exception\\NotRecognizedPrincipal' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php', + 'Sabre\\DAVACL\\Exception\\NotSupportedPrivilege' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php', + 'Sabre\\DAVACL\\FS\\Collection' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/FS/Collection.php', + 'Sabre\\DAVACL\\FS\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/FS/File.php', + 'Sabre\\DAVACL\\FS\\HomeCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/FS/HomeCollection.php', + 'Sabre\\DAVACL\\IACL' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/IACL.php', + 'Sabre\\DAVACL\\IPrincipal' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/IPrincipal.php', + 'Sabre\\DAVACL\\IPrincipalCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/IPrincipalCollection.php', + 'Sabre\\DAVACL\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Plugin.php', + 'Sabre\\DAVACL\\Principal' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Principal.php', + 'Sabre\\DAVACL\\PrincipalBackend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php', + 'Sabre\\DAVACL\\PrincipalBackend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php', + 'Sabre\\DAVACL\\PrincipalBackend\\CreatePrincipalSupport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php', + 'Sabre\\DAVACL\\PrincipalBackend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php', + 'Sabre\\DAVACL\\PrincipalCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/PrincipalCollection.php', + 'Sabre\\DAVACL\\Xml\\Property\\Acl' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Property/Acl.php', + 'Sabre\\DAVACL\\Xml\\Property\\AclRestrictions' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php', + 'Sabre\\DAVACL\\Xml\\Property\\CurrentUserPrivilegeSet' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php', + 'Sabre\\DAVACL\\Xml\\Property\\Principal' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Property/Principal.php', + 'Sabre\\DAVACL\\Xml\\Property\\SupportedPrivilegeSet' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php', + 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php', + 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractBasic' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractBearer' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php', + 'Sabre\\DAV\\Auth\\Backend\\AbstractDigest' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php', + 'Sabre\\DAV\\Auth\\Backend\\Apache' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/Apache.php', + 'Sabre\\DAV\\Auth\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php', + 'Sabre\\DAV\\Auth\\Backend\\BasicCallBack' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php', + 'Sabre\\DAV\\Auth\\Backend\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/File.php', + 'Sabre\\DAV\\Auth\\Backend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Backend/PDO.php', + 'Sabre\\DAV\\Auth\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Auth/Plugin.php', + 'Sabre\\DAV\\Browser\\GuessContentType' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/GuessContentType.php', + 'Sabre\\DAV\\Browser\\HtmlOutput' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/HtmlOutput.php', + 'Sabre\\DAV\\Browser\\HtmlOutputHelper' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php', + 'Sabre\\DAV\\Browser\\MapGetToPropFind' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php', + 'Sabre\\DAV\\Browser\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/Plugin.php', + 'Sabre\\DAV\\Browser\\PropFindAll' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Browser/PropFindAll.php', + 'Sabre\\DAV\\Client' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Client.php', + 'Sabre\\DAV\\Collection' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Collection.php', + 'Sabre\\DAV\\CorePlugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/CorePlugin.php', + 'Sabre\\DAV\\Exception' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception.php', + 'Sabre\\DAV\\Exception\\BadRequest' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/BadRequest.php', + 'Sabre\\DAV\\Exception\\Conflict' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/Conflict.php', + 'Sabre\\DAV\\Exception\\ConflictingLock' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/ConflictingLock.php', + 'Sabre\\DAV\\Exception\\Forbidden' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/Forbidden.php', + 'Sabre\\DAV\\Exception\\InsufficientStorage' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/InsufficientStorage.php', + 'Sabre\\DAV\\Exception\\InvalidResourceType' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/InvalidResourceType.php', + 'Sabre\\DAV\\Exception\\InvalidSyncToken' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php', + 'Sabre\\DAV\\Exception\\LengthRequired' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/LengthRequired.php', + 'Sabre\\DAV\\Exception\\LockTokenMatchesRequestUri' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php', + 'Sabre\\DAV\\Exception\\Locked' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/Locked.php', + 'Sabre\\DAV\\Exception\\MethodNotAllowed' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php', + 'Sabre\\DAV\\Exception\\NotAuthenticated' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/NotAuthenticated.php', + 'Sabre\\DAV\\Exception\\NotFound' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/NotFound.php', + 'Sabre\\DAV\\Exception\\NotImplemented' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/NotImplemented.php', + 'Sabre\\DAV\\Exception\\PaymentRequired' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/PaymentRequired.php', + 'Sabre\\DAV\\Exception\\PreconditionFailed' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/PreconditionFailed.php', + 'Sabre\\DAV\\Exception\\ReportNotSupported' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/ReportNotSupported.php', + 'Sabre\\DAV\\Exception\\RequestedRangeNotSatisfiable' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php', + 'Sabre\\DAV\\Exception\\ServiceUnavailable' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php', + 'Sabre\\DAV\\Exception\\TooManyMatches' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/TooManyMatches.php', + 'Sabre\\DAV\\Exception\\UnsupportedMediaType' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php', + 'Sabre\\DAV\\FSExt\\Directory' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/FSExt/Directory.php', + 'Sabre\\DAV\\FSExt\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/FSExt/File.php', + 'Sabre\\DAV\\FS\\Directory' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/FS/Directory.php', + 'Sabre\\DAV\\FS\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/FS/File.php', + 'Sabre\\DAV\\FS\\Node' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/FS/Node.php', + 'Sabre\\DAV\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/File.php', + 'Sabre\\DAV\\ICollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/ICollection.php', + 'Sabre\\DAV\\IExtendedCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IExtendedCollection.php', + 'Sabre\\DAV\\IFile' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IFile.php', + 'Sabre\\DAV\\IMoveTarget' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IMoveTarget.php', + 'Sabre\\DAV\\IMultiGet' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IMultiGet.php', + 'Sabre\\DAV\\INode' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/INode.php', + 'Sabre\\DAV\\IProperties' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IProperties.php', + 'Sabre\\DAV\\IQuota' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/IQuota.php', + 'Sabre\\DAV\\Locks\\Backend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php', + 'Sabre\\DAV\\Locks\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php', + 'Sabre\\DAV\\Locks\\Backend\\File' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/Backend/File.php', + 'Sabre\\DAV\\Locks\\Backend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/Backend/PDO.php', + 'Sabre\\DAV\\Locks\\LockInfo' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/LockInfo.php', + 'Sabre\\DAV\\Locks\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Locks/Plugin.php', + 'Sabre\\DAV\\MkCol' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/MkCol.php', + 'Sabre\\DAV\\Mount\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Mount/Plugin.php', + 'Sabre\\DAV\\Node' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Node.php', + 'Sabre\\DAV\\PartialUpdate\\IPatchSupport' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php', + 'Sabre\\DAV\\PartialUpdate\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PartialUpdate/Plugin.php', + 'Sabre\\DAV\\PropFind' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PropFind.php', + 'Sabre\\DAV\\PropPatch' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PropPatch.php', + 'Sabre\\DAV\\PropertyStorage\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php', + 'Sabre\\DAV\\PropertyStorage\\Backend\\PDO' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php', + 'Sabre\\DAV\\PropertyStorage\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/PropertyStorage/Plugin.php', + 'Sabre\\DAV\\Server' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Server.php', + 'Sabre\\DAV\\ServerPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/ServerPlugin.php', + 'Sabre\\DAV\\Sharing\\ISharedNode' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Sharing/ISharedNode.php', + 'Sabre\\DAV\\Sharing\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Sharing/Plugin.php', + 'Sabre\\DAV\\SimpleCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/SimpleCollection.php', + 'Sabre\\DAV\\SimpleFile' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/SimpleFile.php', + 'Sabre\\DAV\\StringUtil' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/StringUtil.php', + 'Sabre\\DAV\\Sync\\ISyncCollection' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Sync/ISyncCollection.php', + 'Sabre\\DAV\\Sync\\Plugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Sync/Plugin.php', + 'Sabre\\DAV\\TemporaryFileFilterPlugin' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php', + 'Sabre\\DAV\\Tree' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Tree.php', + 'Sabre\\DAV\\UUIDUtil' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/UUIDUtil.php', + 'Sabre\\DAV\\Version' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Version.php', + 'Sabre\\DAV\\Xml\\Element\\Prop' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Element/Prop.php', + 'Sabre\\DAV\\Xml\\Element\\Response' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Element/Response.php', + 'Sabre\\DAV\\Xml\\Element\\Sharee' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Element/Sharee.php', + 'Sabre\\DAV\\Xml\\Property\\Complex' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/Complex.php', + 'Sabre\\DAV\\Xml\\Property\\GetLastModified' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php', + 'Sabre\\DAV\\Xml\\Property\\Href' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/Href.php', + 'Sabre\\DAV\\Xml\\Property\\Invite' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/Invite.php', + 'Sabre\\DAV\\Xml\\Property\\LocalHref' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/LocalHref.php', + 'Sabre\\DAV\\Xml\\Property\\LockDiscovery' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php', + 'Sabre\\DAV\\Xml\\Property\\ResourceType' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/ResourceType.php', + 'Sabre\\DAV\\Xml\\Property\\ShareAccess' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedLock' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedMethodSet' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php', + 'Sabre\\DAV\\Xml\\Property\\SupportedReportSet' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php', + 'Sabre\\DAV\\Xml\\Request\\Lock' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/Lock.php', + 'Sabre\\DAV\\Xml\\Request\\MkCol' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/MkCol.php', + 'Sabre\\DAV\\Xml\\Request\\PropFind' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/PropFind.php', + 'Sabre\\DAV\\Xml\\Request\\PropPatch' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/PropPatch.php', + 'Sabre\\DAV\\Xml\\Request\\ShareResource' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/ShareResource.php', + 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php', + 'Sabre\\DAV\\Xml\\Response\\MultiStatus' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php', + 'Sabre\\DAV\\Xml\\Service' => __DIR__ . '/..' . '/sabre/dav/lib/DAV/Xml/Service.php', + 'Sabre\\Event\\EventEmitter' => __DIR__ . '/..' . '/sabre/event/lib/EventEmitter.php', + 'Sabre\\Event\\EventEmitterInterface' => __DIR__ . '/..' . '/sabre/event/lib/EventEmitterInterface.php', + 'Sabre\\Event\\EventEmitterTrait' => __DIR__ . '/..' . '/sabre/event/lib/EventEmitterTrait.php', + 'Sabre\\Event\\Loop\\Loop' => __DIR__ . '/..' . '/sabre/event/lib/Loop/Loop.php', + 'Sabre\\Event\\Promise' => __DIR__ . '/..' . '/sabre/event/lib/Promise.php', + 'Sabre\\Event\\PromiseAlreadyResolvedException' => __DIR__ . '/..' . '/sabre/event/lib/PromiseAlreadyResolvedException.php', + 'Sabre\\Event\\Version' => __DIR__ . '/..' . '/sabre/event/lib/Version.php', + 'Sabre\\HTTP\\Auth\\AWS' => __DIR__ . '/..' . '/sabre/http/lib/Auth/AWS.php', + 'Sabre\\HTTP\\Auth\\AbstractAuth' => __DIR__ . '/..' . '/sabre/http/lib/Auth/AbstractAuth.php', + 'Sabre\\HTTP\\Auth\\Basic' => __DIR__ . '/..' . '/sabre/http/lib/Auth/Basic.php', + 'Sabre\\HTTP\\Auth\\Bearer' => __DIR__ . '/..' . '/sabre/http/lib/Auth/Bearer.php', + 'Sabre\\HTTP\\Auth\\Digest' => __DIR__ . '/..' . '/sabre/http/lib/Auth/Digest.php', + 'Sabre\\HTTP\\Client' => __DIR__ . '/..' . '/sabre/http/lib/Client.php', + 'Sabre\\HTTP\\ClientException' => __DIR__ . '/..' . '/sabre/http/lib/ClientException.php', + 'Sabre\\HTTP\\ClientHttpException' => __DIR__ . '/..' . '/sabre/http/lib/ClientHttpException.php', + 'Sabre\\HTTP\\HttpException' => __DIR__ . '/..' . '/sabre/http/lib/HttpException.php', + 'Sabre\\HTTP\\Message' => __DIR__ . '/..' . '/sabre/http/lib/Message.php', + 'Sabre\\HTTP\\MessageDecoratorTrait' => __DIR__ . '/..' . '/sabre/http/lib/MessageDecoratorTrait.php', + 'Sabre\\HTTP\\MessageInterface' => __DIR__ . '/..' . '/sabre/http/lib/MessageInterface.php', + 'Sabre\\HTTP\\Request' => __DIR__ . '/..' . '/sabre/http/lib/Request.php', + 'Sabre\\HTTP\\RequestDecorator' => __DIR__ . '/..' . '/sabre/http/lib/RequestDecorator.php', + 'Sabre\\HTTP\\RequestInterface' => __DIR__ . '/..' . '/sabre/http/lib/RequestInterface.php', + 'Sabre\\HTTP\\Response' => __DIR__ . '/..' . '/sabre/http/lib/Response.php', + 'Sabre\\HTTP\\ResponseDecorator' => __DIR__ . '/..' . '/sabre/http/lib/ResponseDecorator.php', + 'Sabre\\HTTP\\ResponseInterface' => __DIR__ . '/..' . '/sabre/http/lib/ResponseInterface.php', + 'Sabre\\HTTP\\Sapi' => __DIR__ . '/..' . '/sabre/http/lib/Sapi.php', + 'Sabre\\HTTP\\URLUtil' => __DIR__ . '/..' . '/sabre/http/lib/URLUtil.php', + 'Sabre\\HTTP\\Util' => __DIR__ . '/..' . '/sabre/http/lib/Util.php', + 'Sabre\\HTTP\\Version' => __DIR__ . '/..' . '/sabre/http/lib/Version.php', + 'Sabre\\Uri\\InvalidUriException' => __DIR__ . '/..' . '/sabre/uri/lib/InvalidUriException.php', + 'Sabre\\Uri\\Version' => __DIR__ . '/..' . '/sabre/uri/lib/Version.php', + 'Sabre\\VObject\\BirthdayCalendarGenerator' => __DIR__ . '/..' . '/sabre/vobject/lib/BirthdayCalendarGenerator.php', + 'Sabre\\VObject\\Cli' => __DIR__ . '/..' . '/sabre/vobject/lib/Cli.php', + 'Sabre\\VObject\\Component' => __DIR__ . '/..' . '/sabre/vobject/lib/Component.php', + 'Sabre\\VObject\\Component\\Available' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/Available.php', + 'Sabre\\VObject\\Component\\VAlarm' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VAlarm.php', + 'Sabre\\VObject\\Component\\VAvailability' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VAvailability.php', + 'Sabre\\VObject\\Component\\VCalendar' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VCalendar.php', + 'Sabre\\VObject\\Component\\VCard' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VCard.php', + 'Sabre\\VObject\\Component\\VEvent' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VEvent.php', + 'Sabre\\VObject\\Component\\VFreeBusy' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VFreeBusy.php', + 'Sabre\\VObject\\Component\\VJournal' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VJournal.php', + 'Sabre\\VObject\\Component\\VTimeZone' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VTimeZone.php', + 'Sabre\\VObject\\Component\\VTodo' => __DIR__ . '/..' . '/sabre/vobject/lib/Component/VTodo.php', + 'Sabre\\VObject\\DateTimeParser' => __DIR__ . '/..' . '/sabre/vobject/lib/DateTimeParser.php', + 'Sabre\\VObject\\Document' => __DIR__ . '/..' . '/sabre/vobject/lib/Document.php', + 'Sabre\\VObject\\ElementList' => __DIR__ . '/..' . '/sabre/vobject/lib/ElementList.php', + 'Sabre\\VObject\\EofException' => __DIR__ . '/..' . '/sabre/vobject/lib/EofException.php', + 'Sabre\\VObject\\FreeBusyData' => __DIR__ . '/..' . '/sabre/vobject/lib/FreeBusyData.php', + 'Sabre\\VObject\\FreeBusyGenerator' => __DIR__ . '/..' . '/sabre/vobject/lib/FreeBusyGenerator.php', + 'Sabre\\VObject\\ITip\\Broker' => __DIR__ . '/..' . '/sabre/vobject/lib/ITip/Broker.php', + 'Sabre\\VObject\\ITip\\ITipException' => __DIR__ . '/..' . '/sabre/vobject/lib/ITip/ITipException.php', + 'Sabre\\VObject\\ITip\\Message' => __DIR__ . '/..' . '/sabre/vobject/lib/ITip/Message.php', + 'Sabre\\VObject\\ITip\\SameOrganizerForAllComponentsException' => __DIR__ . '/..' . '/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php', + 'Sabre\\VObject\\InvalidDataException' => __DIR__ . '/..' . '/sabre/vobject/lib/InvalidDataException.php', + 'Sabre\\VObject\\Node' => __DIR__ . '/..' . '/sabre/vobject/lib/Node.php', + 'Sabre\\VObject\\PHPUnitAssertions' => __DIR__ . '/..' . '/sabre/vobject/lib/PHPUnitAssertions.php', + 'Sabre\\VObject\\Parameter' => __DIR__ . '/..' . '/sabre/vobject/lib/Parameter.php', + 'Sabre\\VObject\\ParseException' => __DIR__ . '/..' . '/sabre/vobject/lib/ParseException.php', + 'Sabre\\VObject\\Parser\\Json' => __DIR__ . '/..' . '/sabre/vobject/lib/Parser/Json.php', + 'Sabre\\VObject\\Parser\\MimeDir' => __DIR__ . '/..' . '/sabre/vobject/lib/Parser/MimeDir.php', + 'Sabre\\VObject\\Parser\\Parser' => __DIR__ . '/..' . '/sabre/vobject/lib/Parser/Parser.php', + 'Sabre\\VObject\\Parser\\XML' => __DIR__ . '/..' . '/sabre/vobject/lib/Parser/XML.php', + 'Sabre\\VObject\\Parser\\XML\\Element\\KeyValue' => __DIR__ . '/..' . '/sabre/vobject/lib/Parser/XML/Element/KeyValue.php', + 'Sabre\\VObject\\Property' => __DIR__ . '/..' . '/sabre/vobject/lib/Property.php', + 'Sabre\\VObject\\Property\\Binary' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Binary.php', + 'Sabre\\VObject\\Property\\Boolean' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Boolean.php', + 'Sabre\\VObject\\Property\\FlatText' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/FlatText.php', + 'Sabre\\VObject\\Property\\FloatValue' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/FloatValue.php', + 'Sabre\\VObject\\Property\\ICalendar\\CalAddress' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/CalAddress.php', + 'Sabre\\VObject\\Property\\ICalendar\\Date' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/Date.php', + 'Sabre\\VObject\\Property\\ICalendar\\DateTime' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/DateTime.php', + 'Sabre\\VObject\\Property\\ICalendar\\Duration' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/Duration.php', + 'Sabre\\VObject\\Property\\ICalendar\\Period' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/Period.php', + 'Sabre\\VObject\\Property\\ICalendar\\Recur' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/ICalendar/Recur.php', + 'Sabre\\VObject\\Property\\IntegerValue' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/IntegerValue.php', + 'Sabre\\VObject\\Property\\Text' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Text.php', + 'Sabre\\VObject\\Property\\Time' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Time.php', + 'Sabre\\VObject\\Property\\Unknown' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Unknown.php', + 'Sabre\\VObject\\Property\\Uri' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/Uri.php', + 'Sabre\\VObject\\Property\\UtcOffset' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/UtcOffset.php', + 'Sabre\\VObject\\Property\\VCard\\Date' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/VCard/Date.php', + 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/VCard/DateAndOrTime.php', + 'Sabre\\VObject\\Property\\VCard\\DateTime' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/VCard/DateTime.php', + 'Sabre\\VObject\\Property\\VCard\\LanguageTag' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/VCard/LanguageTag.php', + 'Sabre\\VObject\\Property\\VCard\\TimeStamp' => __DIR__ . '/..' . '/sabre/vobject/lib/Property/VCard/TimeStamp.php', + 'Sabre\\VObject\\Reader' => __DIR__ . '/..' . '/sabre/vobject/lib/Reader.php', + 'Sabre\\VObject\\Recur\\EventIterator' => __DIR__ . '/..' . '/sabre/vobject/lib/Recur/EventIterator.php', + 'Sabre\\VObject\\Recur\\MaxInstancesExceededException' => __DIR__ . '/..' . '/sabre/vobject/lib/Recur/MaxInstancesExceededException.php', + 'Sabre\\VObject\\Recur\\NoInstancesException' => __DIR__ . '/..' . '/sabre/vobject/lib/Recur/NoInstancesException.php', + 'Sabre\\VObject\\Recur\\RDateIterator' => __DIR__ . '/..' . '/sabre/vobject/lib/Recur/RDateIterator.php', + 'Sabre\\VObject\\Recur\\RRuleIterator' => __DIR__ . '/..' . '/sabre/vobject/lib/Recur/RRuleIterator.php', + 'Sabre\\VObject\\Settings' => __DIR__ . '/..' . '/sabre/vobject/lib/Settings.php', + 'Sabre\\VObject\\Splitter\\ICalendar' => __DIR__ . '/..' . '/sabre/vobject/lib/Splitter/ICalendar.php', + 'Sabre\\VObject\\Splitter\\SplitterInterface' => __DIR__ . '/..' . '/sabre/vobject/lib/Splitter/SplitterInterface.php', + 'Sabre\\VObject\\Splitter\\VCard' => __DIR__ . '/..' . '/sabre/vobject/lib/Splitter/VCard.php', + 'Sabre\\VObject\\StringUtil' => __DIR__ . '/..' . '/sabre/vobject/lib/StringUtil.php', + 'Sabre\\VObject\\TimeZoneUtil' => __DIR__ . '/..' . '/sabre/vobject/lib/TimeZoneUtil.php', + 'Sabre\\VObject\\UUIDUtil' => __DIR__ . '/..' . '/sabre/vobject/lib/UUIDUtil.php', + 'Sabre\\VObject\\VCardConverter' => __DIR__ . '/..' . '/sabre/vobject/lib/VCardConverter.php', + 'Sabre\\VObject\\Version' => __DIR__ . '/..' . '/sabre/vobject/lib/Version.php', + 'Sabre\\VObject\\Writer' => __DIR__ . '/..' . '/sabre/vobject/lib/Writer.php', + 'Sabre\\Xml\\ContextStackTrait' => __DIR__ . '/..' . '/sabre/xml/lib/ContextStackTrait.php', + 'Sabre\\Xml\\Element' => __DIR__ . '/..' . '/sabre/xml/lib/Element.php', + 'Sabre\\Xml\\Element\\Base' => __DIR__ . '/..' . '/sabre/xml/lib/Element/Base.php', + 'Sabre\\Xml\\Element\\Cdata' => __DIR__ . '/..' . '/sabre/xml/lib/Element/Cdata.php', + 'Sabre\\Xml\\Element\\Elements' => __DIR__ . '/..' . '/sabre/xml/lib/Element/Elements.php', + 'Sabre\\Xml\\Element\\KeyValue' => __DIR__ . '/..' . '/sabre/xml/lib/Element/KeyValue.php', + 'Sabre\\Xml\\Element\\Uri' => __DIR__ . '/..' . '/sabre/xml/lib/Element/Uri.php', + 'Sabre\\Xml\\Element\\XmlFragment' => __DIR__ . '/..' . '/sabre/xml/lib/Element/XmlFragment.php', + 'Sabre\\Xml\\LibXMLException' => __DIR__ . '/..' . '/sabre/xml/lib/LibXMLException.php', + 'Sabre\\Xml\\ParseException' => __DIR__ . '/..' . '/sabre/xml/lib/ParseException.php', + 'Sabre\\Xml\\Reader' => __DIR__ . '/..' . '/sabre/xml/lib/Reader.php', + 'Sabre\\Xml\\Service' => __DIR__ . '/..' . '/sabre/xml/lib/Service.php', + 'Sabre\\Xml\\Version' => __DIR__ . '/..' . '/sabre/xml/lib/Version.php', + 'Sabre\\Xml\\Writer' => __DIR__ . '/..' . '/sabre/xml/lib/Writer.php', + 'Sabre\\Xml\\XmlDeserializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlDeserializable.php', + 'Sabre\\Xml\\XmlSerializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlSerializable.php', 'Sanitizer' => __DIR__ . '/../..' . '/../../Services/Utilities/classes/Sanitizer.php', 'SeqActivity' => __DIR__ . '/../..' . '/../../Modules/Scorm2004/classes/adlparser/SeqActivity.php', 'SeqCondition' => __DIR__ . '/../..' . '/../../Modules/Scorm2004/classes/adlparser/SeqCondition.php', @@ -1730,7 +2112,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'SurveyTextQuestion' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestion.php', 'SurveyTextQuestionEvaluation' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionEvaluation.php', 'SurveyTextQuestionGUI' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyTextQuestionGUI.php', - 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', 'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php', @@ -1739,7 +2120,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php', 'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php', 'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php', - 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', 'TCPDF' => __DIR__ . '/..' . '/tecnickcom/tcpdf/tcpdf.php', @@ -2196,9 +2576,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 '_DiffOp_Copy' => __DIR__ . '/../..' . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', '_DiffOp_Delete' => __DIR__ . '/../..' . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', '_HWLDF_WordAccumulator' => __DIR__ . '/../..' . '/../../Services/COPage/mediawikidiff/class.WordLevelDiff.php', - '_parse_lockinfo' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/Tools/_parse_lockinfo.php', - '_parse_propfind' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/Tools/_parse_propfind.php', - '_parse_proppatch' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/Tools/_parse_proppatch.php', 'arBuilder' => __DIR__ . '/../..' . '/../../Services/ActiveRecord/Fields/Converter/class.arBuilder.php', 'arCalledClassCache' => __DIR__ . '/../..' . '/../../Services/ActiveRecord/Cache/class.arCalledClassCache.php', 'arConcat' => __DIR__ . '/../..' . '/../../Services/ActiveRecord/Connector/Concat/class.arConcat.php', @@ -3260,6 +3637,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilClaimingPermissionHelper' => __DIR__ . '/../..' . '/../../Services/Component/classes/class.ilClaimingPermissionHelper.php', 'ilClassificationBlockGUI' => __DIR__ . '/../..' . '/../../Services/Classification/classes/class.ilClassificationBlockGUI.php', 'ilClassificationProvider' => __DIR__ . '/../..' . '/../../Services/Classification/classes/class.ilClassificationProvider.php', + 'ilClientNodeDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilClientNodeDAV.php', 'ilClipboardTableGUI' => __DIR__ . '/../..' . '/../../Services/Clipboard/classes/class.ilClipboardTableGUI.php', 'ilCloudConnector' => __DIR__ . '/../..' . '/../../Modules/Cloud/classes/class.ilCloudConnector.php', 'ilCloudException' => __DIR__ . '/../..' . '/../../Modules/Cloud/exceptions/class.ilCloudException.php', @@ -3383,7 +3761,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilContentStylesTableGUI' => __DIR__ . '/../..' . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextApacheSSO.php', - 'ilContextBehat' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -3493,10 +3870,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilCustomUserFieldsGUI' => __DIR__ . '/../..' . '/../../Services/User/classes/class.ilCustomUserFieldsGUI.php', 'ilDAVActivationChecker' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVActivationChecker.php', 'ilDAVCronDiskQuota' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVCronDiskQuota.php', - 'ilDAVLocks' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVLocks.php', - 'ilDAVProperties' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVProperties.php', - 'ilDAVServer' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVServer.php', - 'ilDAVUtils' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilDAVUtils.php', 'ilDB' => __DIR__ . '/../..' . '/../../Services/Database/classes/MDB2/class.ilDB.php', 'ilDBAnalyzer' => __DIR__ . '/../..' . '/../../Services/Database/classes/class.ilDBAnalyzer.php', 'ilDBConstants' => __DIR__ . '/../..' . '/../../Services/Database/classes/class.ilDBConstants.php', @@ -4674,6 +5047,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilModulesSessionExtractor' => __DIR__ . '/../..' . '/../../Services/WorkflowEngine/classes/extractors/class.ilModulesSessionExtractor.php', 'ilModulesTableGUI' => __DIR__ . '/../..' . '/../../Services/Repository/classes/class.ilModulesTableGUI.php', 'ilModulesTestTasks' => __DIR__ . '/../..' . '/../../Services/WorkflowEngine/classes/tasks/class.ilModulesTestTasks.php', + 'ilMountPointDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilMountPointDAV.php', 'ilMultiFilesSubmitRecursiveSlashesStripper' => __DIR__ . '/../..' . '/../../Services/Form/classes/class.ilMultiFilesSubmitRecursiveSlashesStripper.php', 'ilMultiSelectInputGUI' => __DIR__ . '/../..' . '/../../Services/Form/classes/class.ilMultiSelectInputGUI.php', 'ilMultiSrtConfirmationTable2GUI' => __DIR__ . '/../..' . '/../../Services/MediaObjects/classes/class.ilMultiSrtConfirmationTable2GUI.php', @@ -4784,7 +5158,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjCalendarSettingsGUI' => __DIR__ . '/../..' . '/../../Services/Calendar/classes/class.ilObjCalendarSettingsGUI.php', 'ilObjCategory' => __DIR__ . '/../..' . '/../../Modules/Category/classes/class.ilObjCategory.php', 'ilObjCategoryAccess' => __DIR__ . '/../..' . '/../../Modules/Category/classes/class.ilObjCategoryAccess.php', - 'ilObjCategoryDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjCategoryDAV.php', + 'ilObjCategoryDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjCategoryDAV.php', 'ilObjCategoryGUI' => __DIR__ . '/../..' . '/../../Modules/Category/classes/class.ilObjCategoryGUI.php', 'ilObjCategoryListGUI' => __DIR__ . '/../..' . '/../../Modules/Category/classes/class.ilObjCategoryListGUI.php', 'ilObjCategoryReference' => __DIR__ . '/../..' . '/../../Modules/CategoryReference/classes/class.ilObjCategoryReference.php', @@ -4812,6 +5186,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjContactAdministration' => __DIR__ . '/../..' . '/../../Services/Contact/classes/class.ilObjContactAdministration.php', 'ilObjContactAdministrationAccess' => __DIR__ . '/../..' . '/../../Services/Contact/classes/class.ilObjContactAdministrationAccess.php', 'ilObjContactAdministrationGUI' => __DIR__ . '/../..' . '/../../Services/Contact/classes/class.ilObjContactAdministrationGUI.php', + 'ilObjContainerDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjContainerDAV.php', 'ilObjContentObject' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObject.php', 'ilObjContentObjectAccess' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObjectAccess.php', 'ilObjContentObjectGUI' => __DIR__ . '/../..' . '/../../Modules/LearningModule/classes/class.ilObjContentObjectGUI.php', @@ -4824,7 +5199,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjCourseAdministration' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseAdministration.php', 'ilObjCourseAdministrationAccess' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseAdministrationAccess.php', 'ilObjCourseAdministrationGUI' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseAdministrationGUI.php', - 'ilObjCourseDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjCourseDAV.php', + 'ilObjCourseDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjCourseDAV.php', 'ilObjCourseGUI' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseGUI.php', 'ilObjCourseGrouping' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseGrouping.php', 'ilObjCourseGroupingGUI' => __DIR__ . '/../..' . '/../../Modules/Course/classes/class.ilObjCourseGroupingGUI.php', @@ -4873,13 +5248,13 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjFileBasedLMAccess' => __DIR__ . '/../..' . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMAccess.php', 'ilObjFileBasedLMGUI' => __DIR__ . '/../..' . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMGUI.php', 'ilObjFileBasedLMListGUI' => __DIR__ . '/../..' . '/../../Modules/HTMLLearningModule/classes/class.ilObjFileBasedLMListGUI.php', - 'ilObjFileDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjFileDAV.php', + 'ilObjFileDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjFileDAV.php', 'ilObjFileGUI' => __DIR__ . '/../..' . '/../../Modules/File/classes/class.ilObjFileGUI.php', 'ilObjFileHandlingQuestionType' => __DIR__ . '/../..' . '/../../Modules/TestQuestionPool/interfaces/interface.ilObjFileHandlingQuestionType.php', 'ilObjFileListGUI' => __DIR__ . '/../..' . '/../../Modules/File/classes/class.ilObjFileListGUI.php', 'ilObjFolder' => __DIR__ . '/../..' . '/../../Modules/Folder/classes/class.ilObjFolder.php', 'ilObjFolderAccess' => __DIR__ . '/../..' . '/../../Modules/Folder/classes/class.ilObjFolderAccess.php', - 'ilObjFolderDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjFolderDAV.php', + 'ilObjFolderDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjFolderDAV.php', 'ilObjFolderGUI' => __DIR__ . '/../..' . '/../../Modules/Folder/classes/class.ilObjFolderGUI.php', 'ilObjFolderListGUI' => __DIR__ . '/../..' . '/../../Modules/Folder/classes/class.ilObjFolderListGUI.php', 'ilObjForum' => __DIR__ . '/../..' . '/../../Modules/Forum/classes/class.ilObjForum.php', @@ -4902,7 +5277,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjGroupAdministration' => __DIR__ . '/../..' . '/../../Modules/Group/classes/class.ilObjGroupAdministration.php', 'ilObjGroupAdministrationAccess' => __DIR__ . '/../..' . '/../../Modules/Group/classes/class.ilObjGroupAdministrationAccess.php', 'ilObjGroupAdministrationGUI' => __DIR__ . '/../..' . '/../../Modules/Group/classes/class.ilObjGroupAdministrationGUI.php', - 'ilObjGroupDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjGroupDAV.php', + 'ilObjGroupDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjGroupDAV.php', 'ilObjGroupGUI' => __DIR__ . '/../..' . '/../../Modules/Group/classes/class.ilObjGroupGUI.php', 'ilObjGroupListGUI' => __DIR__ . '/../..' . '/../../Modules/Group/classes/class.ilObjGroupListGUI.php', 'ilObjGroupReference' => __DIR__ . '/../..' . '/../../Modules/GroupReference/classes/class.ilObjGroupReference.php', @@ -4972,7 +5347,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjMediaPoolGUI' => __DIR__ . '/../..' . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolGUI.php', 'ilObjMediaPoolListGUI' => __DIR__ . '/../..' . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolListGUI.php', 'ilObjMediaPoolSubItemListGUI' => __DIR__ . '/../..' . '/../../Modules/MediaPool/classes/class.ilObjMediaPoolSubItemListGUI.php', - 'ilObjMountPointDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjMountPointDAV.php', 'ilObjNewsSettings' => __DIR__ . '/../..' . '/../../Services/News/classes/class.ilObjNewsSettings.php', 'ilObjNewsSettingsAccess' => __DIR__ . '/../..' . '/../../Services/News/classes/class.ilObjNewsSettingsAccess.php', 'ilObjNewsSettingsGUI' => __DIR__ . '/../..' . '/../../Services/News/classes/class.ilObjNewsSettingsGUI.php', @@ -4981,8 +5355,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjNotificationAdminGUI' => __DIR__ . '/../..' . '/../../Services/Notifications/classes/class.ilObjNotificationAdminGUI.php', 'ilObjNotificationSettings' => __DIR__ . '/../..' . '/../../Services/Notification/classes/class.ilObjNotificationSettings.php', 'ilObjNotificationSettingsGUI' => __DIR__ . '/../..' . '/../../Services/Notification/classes/class.ilObjNotificationSettingsGUI.php', - 'ilObjNull' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjNull.php', - 'ilObjNullDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjNullDAV.php', 'ilObjObjectFolder' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjObjectFolder.php', 'ilObjObjectFolderAccess' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjObjectFolderAccess.php', 'ilObjObjectFolderGUI' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjObjectFolderGUI.php', @@ -5065,6 +5437,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjRemoteWikiAccess' => __DIR__ . '/../..' . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiAccess.php', 'ilObjRemoteWikiGUI' => __DIR__ . '/../..' . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiGUI.php', 'ilObjRemoteWikiListGUI' => __DIR__ . '/../..' . '/../../Modules/RemoteWiki/classes/class.ilObjRemoteWikiListGUI.php', + 'ilObjRepositoryRootDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjRepositoryRootDAV.php', 'ilObjRepositorySettings' => __DIR__ . '/../..' . '/../../Services/Repository/classes/class.ilObjRepositorySettings.php', 'ilObjRepositorySettingsAccess' => __DIR__ . '/../..' . '/../../Services/Repository/classes/class.ilObjRepositorySettingsAccess.php', 'ilObjRepositorySettingsGUI' => __DIR__ . '/../..' . '/../../Services/Repository/classes/class.ilObjRepositorySettingsGUI.php', @@ -5075,7 +5448,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjRoleGUI' => __DIR__ . '/../..' . '/../../Services/AccessControl/classes/class.ilObjRoleGUI.php', 'ilObjRoleTemplate' => __DIR__ . '/../..' . '/../../Services/AccessControl/classes/class.ilObjRoleTemplate.php', 'ilObjRoleTemplateGUI' => __DIR__ . '/../..' . '/../../Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php', - 'ilObjRootDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjRootDAV.php', 'ilObjRootFolder' => __DIR__ . '/../..' . '/../../Modules/RootFolder/classes/class.ilObjRootFolder.php', 'ilObjRootFolderAccess' => __DIR__ . '/../..' . '/../../Modules/RootFolder/classes/class.ilObjRootFolderAccess.php', 'ilObjRootFolderGUI' => __DIR__ . '/../..' . '/../../Modules/RootFolder/classes/class.ilObjRootFolderGUI.php', @@ -5223,7 +5595,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilObjectCustomUserFieldHistory' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldHistory.php', 'ilObjectCustomUserFieldsGUI' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsGUI.php', 'ilObjectCustomUserFieldsTableGUI' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilObjectCustomUserFieldsTableGUI.php', - 'ilObjectDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilObjectDAV.php', + 'ilObjectDAV' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/dav/class.ilObjectDAV.php', 'ilObjectDataCache' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectDataCache.php', 'ilObjectDataDeletionLog' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectDataDeletionLog.php', 'ilObjectDataSet' => __DIR__ . '/../..' . '/../../Services/Object/classes/class.ilObjectDataSet.php', @@ -6529,6 +6901,15 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilWaitingListTableGUI' => __DIR__ . '/../..' . '/../../Services/Membership/classes/class.ilWaitingListTableGUI.php', 'ilWebAccessChecker' => __DIR__ . '/../..' . '/../../Services/WebAccessChecker/classes/class.ilWebAccessChecker.php', 'ilWebAccessCheckerDelivery' => __DIR__ . '/../..' . '/../../Services/WebAccessChecker/classes/class.ilWebAccessCheckerDelivery.php', + 'ilWebDAVAuthentication' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/auth/class.ilWebDAVAuthentication.php', + 'ilWebDAVDBManager' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/db/class.ilWebDAVDBManager.php', + 'ilWebDAVLockBackend' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/lock/class.ilWebDAVLockBackend.php', + 'ilWebDAVLockObject' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/lock/class.ilWebDAVLockObject.php', + 'ilWebDAVMountInstructions' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilWebDAVMountInstructions.php', + 'ilWebDAVMountInstructionsGUI' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php', + 'ilWebDAVRequestHandler' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilWebDAVRequestHandler.php', + 'ilWebDAVTree' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilWebDAVTree.php', + 'ilWebDAVUtil' => __DIR__ . '/../..' . '/../../Services/WebDAV/classes/class.ilWebDAVUtil.php', 'ilWebLinkXmlParser' => __DIR__ . '/../..' . '/../../Modules/WebResource/classes/class.ilWebLinkXmlParser.php', 'ilWebLinkXmlParserException' => __DIR__ . '/../..' . '/../../Modules/WebResource/classes/class.ilWebLinkXmlParserException.php', 'ilWebLinkXmlWriter' => __DIR__ . '/../..' . '/../../Modules/WebResource/classes/class.ilWebLinkXmlWriter.php', diff --git a/libs/composer/vendor/composer/installed.json b/libs/composer/vendor/composer/installed.json index 8d76f6cda277..e03c2c6b1731 100644 --- a/libs/composer/vendor/composer/installed.json +++ b/libs/composer/vendor/composer/installed.json @@ -1110,6 +1110,425 @@ "xmldsig" ] }, + { + "name": "sabre/dav", + "version": "3.2.2", + "version_normalized": "3.2.2.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/dav.git", + "reference": "e987775e619728f12205606c9cc3ee565ffb1516" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/dav/zipball/e987775e619728f12205606c9cc3ee565ffb1516", + "reference": "e987775e619728f12205606c9cc3ee565ffb1516", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-mbstring": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "lib-libxml": ">=2.7.0", + "php": ">=5.5.0", + "psr/log": "^1.0", + "sabre/event": ">=2.0.0, <4.0.0", + "sabre/http": "^4.2.1", + "sabre/uri": "^1.0.1", + "sabre/vobject": "^4.1.0", + "sabre/xml": "^1.4.0" + }, + "require-dev": { + "evert/phpdoc-md": "~0.1.0", + "monolog/monolog": "^1.18", + "phpunit/phpunit": "> 4.8, <6.0.0", + "sabre/cs": "^1.0.0" + }, + "suggest": { + "ext-curl": "*", + "ext-pdo": "*" + }, + "time": "2017-02-15T03:06:08+00:00", + "bin": [ + "bin/sabredav", + "bin/naturalselection" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\DAV\\": "lib/DAV/", + "Sabre\\DAVACL\\": "lib/DAVACL/", + "Sabre\\CalDAV\\": "lib/CalDAV/", + "Sabre\\CardDAV\\": "lib/CardDAV/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "WebDAV Framework for PHP", + "homepage": "http://sabre.io/", + "keywords": [ + "CalDAV", + "CardDAV", + "WebDAV", + "framework", + "iCalendar" + ] + }, + { + "name": "sabre/event", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/event.git", + "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534", + "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.4" + }, + "time": "2015-11-05T20:14:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + }, + "files": [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "async", + "events", + "hooks", + "plugin", + "promise", + "signal" + ] + }, + { + "name": "sabre/http", + "version": "v4.2.4", + "version_normalized": "4.2.4.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/http.git", + "reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/http/zipball/acccec4ba863959b2d10c1fa0fb902736c5c8956", + "reference": "acccec4ba863959b2d10c1fa0fb902736c5c8956", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": ">=5.4", + "sabre/event": ">=1.0.0,<4.0.0", + "sabre/uri": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.3", + "sabre/cs": "~0.0.1" + }, + "suggest": { + "ext-curl": " to make http requests with the Client class" + }, + "time": "2018-02-23T11:10:29+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\HTTP\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "The sabre/http library provides utilities for dealing with http requests and responses. ", + "homepage": "https://github.com/fruux/sabre-http", + "keywords": [ + "http" + ] + }, + { + "name": "sabre/uri", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/uri.git", + "reference": "ada354d83579565949d80b2e15593c2371225e61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/uri/zipball/ada354d83579565949d80b2e15593c2371225e61", + "reference": "ada354d83579565949d80b2e15593c2371225e61", + "shasum": "" + }, + "require": { + "php": ">=5.4.7" + }, + "require-dev": { + "phpunit/phpunit": ">=4.0,<6.0", + "sabre/cs": "~1.0.0" + }, + "time": "2017-02-20T19:59:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\Uri\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "Functions for making sense out of URIs.", + "homepage": "http://sabre.io/uri/", + "keywords": [ + "rfc3986", + "uri", + "url" + ] + }, + { + "name": "sabre/vobject", + "version": "4.1.6", + "version_normalized": "4.1.6.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/vobject.git", + "reference": "122cacbdea2c6133ac04db86ec05854beef75adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/vobject/zipball/122cacbdea2c6133ac04db86ec05854beef75adf", + "reference": "122cacbdea2c6133ac04db86ec05854beef75adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.5", + "sabre/xml": ">=1.5 <3.0" + }, + "require-dev": { + "phpunit/phpunit": "> 4.8.35, <6.0.0", + "sabre/cs": "^1.0.0" + }, + "suggest": { + "hoa/bench": "If you would like to run the benchmark scripts" + }, + "time": "2018-04-20T07:22:50+00:00", + "bin": [ + "bin/vobject", + "bin/generate_vcards" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\VObject\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Dominik Tobschall", + "email": "dominik@fruux.com", + "homepage": "http://tobschall.de/", + "role": "Developer" + }, + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net", + "homepage": "http://mnt.io/", + "role": "Developer" + } + ], + "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "homepage": "http://sabre.io/vobject/", + "keywords": [ + "availability", + "freebusy", + "iCalendar", + "ical", + "ics", + "jCal", + "jCard", + "recurrence", + "rfc2425", + "rfc2426", + "rfc2739", + "rfc4770", + "rfc5545", + "rfc5546", + "rfc6321", + "rfc6350", + "rfc6351", + "rfc6474", + "rfc6638", + "rfc6715", + "rfc6868", + "vCalendar", + "vCard", + "vcf", + "xCal", + "xCard" + ] + }, + { + "name": "sabre/xml", + "version": "1.5.0", + "version_normalized": "1.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sabre-io/xml.git", + "reference": "59b20e5bbace9912607481634f97d05a776ffca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabre-io/xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7", + "reference": "59b20e5bbace9912607481634f97d05a776ffca7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "lib-libxml": ">=2.6.20", + "php": ">=5.5.5", + "sabre/uri": ">=1.0,<3.0.0" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~1.0.0" + }, + "time": "2016-10-09T22:57:52+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\Xml\\": "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role": "Developer" + } + ], + "description": "sabre/xml is an XML library that you may not hate.", + "homepage": "https://sabre.io/xml/", + "keywords": [ + "XMLReader", + "XMLWriter", + "dom", + "xml" + ] + }, { "name": "simplesamlphp/saml2", "version": "v3.1.5", diff --git a/libs/composer/vendor/sabre/dav/.gitignore b/libs/composer/vendor/sabre/dav/.gitignore new file mode 100644 index 000000000000..6cf245883206 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/.gitignore @@ -0,0 +1,43 @@ +# Unit tests +tests/temp +tests/.sabredav +tests/cov + +# Custom settings for tests +tests/config.user.php + +# ViM +*.swp + +# Composer +composer.lock +vendor + +# Composer binaries +bin/phing +bin/phpunit +bin/vobject +bin/generate_vcards +bin/phpdocmd +bin/phpunit +bin/php-cs-fixer +bin/sabre-cs-fixer + +# Assuming every .php file in the root is for testing +/*.php + +# Other testing stuff +/tmpdata +/data +/public + +# Build +build +build.properties + +# Docs +docs/api +docs/wikidocs + +# Mac +.DS_Store diff --git a/libs/composer/vendor/sabre/dav/.travis.yml b/libs/composer/vendor/sabre/dav/.travis.yml new file mode 100644 index 000000000000..85637048ad49 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/.travis.yml @@ -0,0 +1,36 @@ +language: php +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + + +env: + matrix: + - LOWEST_DEPS="" TEST_DEPS="" + - LOWEST_DEPS="--prefer-lowest" TEST_DEPS="tests/Sabre/" + +services: + - mysql + - postgresql + +sudo: false + +before_script: + - mysql -e 'create database sabredav_test' + - psql -c "create database sabredav_test" -U postgres + - psql -c "create user sabredav with PASSWORD 'sabredav';GRANT ALL PRIVILEGES ON DATABASE sabredav_test TO sabredav" -U postgres + # - composer self-update + - composer update --prefer-dist $LOWEST_DEPS + +# addons: +# postgresql: "9.5" + +script: + - ./bin/phpunit --configuration tests/phpunit.xml.dist $TEST_DEPS + - ./bin/sabre-cs-fixer fix . --dry-run --diff + +cache: + directories: + - $HOME/.composer/cache diff --git a/libs/composer/vendor/sabre/dav/CHANGELOG.md b/libs/composer/vendor/sabre/dav/CHANGELOG.md new file mode 100644 index 000000000000..0bccc995cfd9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/CHANGELOG.md @@ -0,0 +1,2378 @@ +ChangeLog +========= + +3.2.2 (2017-02-14) +------------------ + +* #943: Fix CardDAV XML reporting bug, which was affecting several CardDAV + clients. Bug was introduced in 3.2.1. +* The zip release ships with [sabre/vobject 4.1.2][vobj], + [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml]. + + +3.2.1 (2017-01-28) +------------------ + +* #877: Fix for syncing large calendars when using the Sqlite PDO backend. + (@theseer). +* #889 Added support for filtering vCard properties in the addressbook-query + REPORT (@DeepDiver1975). +* The zip release ships with [sabre/vobject 4.1.2][vobj], + [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml]. + + +3.2.0 (2016-06-27) +------------------ + +* The default ACL rules allow an unauthenticated user to read information + about nodes that don't have their own ACL defined. This was a security + problem. +* The zip release ships with [sabre/vobject 4.1.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml]. + + +3.2.0-beta1 (2016-05-20) +------------------------ + +* #833: Calendars throw exceptions when the sharing plugin is not enabled. +* #834: Return vCards exactly as they were stored if we don't need to convert + in between versions. +* The zip release ships with [sabre/vobject 4.1.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.2.0-alpha1 (2016-05-09) +------------------------- + +* Database changes for CalDAV. If you are using the CalDAV PDO backends, you + must migrate. Run `./bin/migrateto32.php` for more info. +* Support for WebDAV Resource Sharing, an upcoming standard. +* Added support for sharing in the CalDAV PDO backend! Users can now invite + others to their calendar and give them read/read-write access! +* #397: Support for PSR-3. You can now log exceptions with your favourite + psr3-compatible logging tool. +* #825: Actual proper, tested support for PostgreSQL. We require version 9.5. +* Removed database migration script for sabre/dav 1.7. To update from that + version you now first need to update to sabre/dav 3.1. +* Removed deprecated function: `Sabre\DAV\Auth\Plugin::getCurrentUser()`. +* #774: Fixes for getting free disk space on Windows. +* #803: Major changes in the sharing API. If you were using an old sabre/dav + sharing api, head to the website for more detailed migration notes. +* #657: Support for optional auth using `{DAV:}unauthorized` and `{DAV:}all` + privileges. This allows you to assign a privilege to a resource, allowing + non-authenticated users to access it. For instance, this could allow you + to create a public read-only collection. +* #812 #814: ICS/VCF exporter now includes a more useful filename in its + `Content-Disposition` header. (@Xenopathic). +* #801: BC break: If you were using the `Href` object before, it's behavior + now changed a bit, and `LocalHref` was added to replace the old, default + behavior of `Href`. See the migration doc for more info. +* Removed `Sabre\DAVACL\Plugin::$allowAccessToNodesWithoutACL` setting. + Instead, you can provide a set of default ACL rules with + `Sabre\DAVACL\Plugin::setDefaultAcl()`. +* Introduced `Sabre\DAVACL\ACLTrait` which contains a default implementation + of `Sabre\DAV\IACL` with some sane defaults. We're using this trait all over + the place now, reducing the amount of boilerplate. +* Plugins can now control the "Supported Privilege Set". +* Added Sharing, ICSExport and VCFExport plugins to `groupwareserver.php` + example. +* The `{DAV:}all` privilege is now no longer abstract, so it can be assigned + directly. We're using the `{DAV:}all` privilege now in a lot of cases where + we before assigned both `{DAV:}read` and `{DAV:}write`. +* Resources that are not collections no longer support the `{DAV:}bind` and + `{DAV:}unbind` privileges. +* Corrected the CalDAV-scheduling related privileges. +* Doing an `UNLOCK` no longer requires the `{DAV:}write-content` privilege. +* Added a new `getPrincipalByUri` plugin event. Allowing plugins to request + quickly where a principal lives on a server. +* Renamed `phpunit.xml` to `phpunit.xml.dist` to make local modifications easy. +* Functionality from `IShareableCalendar` is merged into `ISharedCalendar`. +* #751: Fixed XML responses from failing `MKCOL` requests. +* #600: Support for `principal-match` ACL `REPORT`. +* #599: Support for `acl-principal-prop-set` ACL `REPORT`. +* #798: Added an index on `firstoccurence` field in MySQL CalDAV backend. This + should speed up common calendar-query requests. +* #759: DAV\Client is now able to actually correctly resolve relative urls. +* #671: We are no longer checking the `read-free-busy` privilege on individual + calendars during freebusy operations in the scheduling plugin. Instead, we + check the `schedule-query-freebusy` privilege on the target users' inbox, + which validates access for the entire account, per the spec. +* The zip release ships with [sabre/vobject 4.1.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.1.5 (????-??-??) +------------------ + +* Fixed: Creating a new calendar on some MySQL configurations caused an error. +* #889 Added support for filtering vCard properties in the addressbook-query + REPORT (@DeepDiver1975). + + + +3.1.4 (2016-05-28) +------------------ + +* #834: Backport from `master`: Return vCards exactly as they were stored if + we don't need to convert in between versions. This should speed up many + large addressbook syncs sometimes up to 50%. +* The zip release ships with [sabre/vobject 4.1.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml]. + + +3.1.3 (2016-04-06) +------------------ + +* Set minimum libxml version to 2.7.0 in `composer.json`. +* #805: It wasn't possible to create calendars that hold events, journals and + todos using MySQL, because the `components` column was 1 byte too small. +* The zip release ships with [sabre/vobject 4.1.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.1.2 (2016-03-12) +------------------ + +* #784: Sync logs for address books were not correctly cleaned up after + deleting them. +* #787: Cannot use non-seekable stream-wrappers with range requests. +* Faster XML parsing and generating due to sabre/xml update. +* #793: The Sqlite schema is now more strict and more similar to the MySQL + schema. This solves a problem within Baikal. +* The zip release ships with [sabre/vobject 4.0.3][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.1.1 (2016-01-25) +------------------ + +* #755: The brower plugin and some operations would break when scheduling and + delegation would both be enabled. +* #757: A bunch of unittest improvements (@jakobsack). +* The zip release ships with [sabre/vobject 4.0.2][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.1.0 (2016-01-06) +------------------ + +* Better error message when the browser plugin is not enabled. +* Added a super minimal server example. +* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to + use emoji in some tables where you couldn't before. +* #710: Provide an Auth backend that acts as a helper for people implementing + OAuth2 Bearer token. (@fkooman). +* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached. +* #727: Added another workaround to make CalDAV work for Windows 10 clients. +* #742: Fixes to make sure that vobject 4 is correctly supported. +* #726: Better error reporting in `Client::propPatch`. We're now throwing + exceptions. +* #608: When a HTTP error is triggered during `Client:propFind`, we're now + throwing `Sabre\HTTP\ClientHttpException` instead of `Sabre\DAV\Exception`. + This new exception contains a LOT more information about the problem. +* #721: Events are now handled in the correct order for `COPY` requests. + Before this subtle bugs could appear that could cause data-loss. +* #747: Now throwing exceptions and setting the HTTP status to 500 in subtle + cases where no other plugin set a correct HTTP status. +* #686: Corrected PDO principal backend's findByURI for email addresses that + don't match the exact capitalization. +* #512: The client now has it's own `User-Agent`. +* #720: Some browser improvements. +* The zip release ships with [sabre/vobject 4.0.1][vobj], + [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.1.0-alpha2 (2015-09-05) +------------------------- + +* Massive calendars and addressbooks should see a big drop in peak memory + usage. +* Fixed a privilege bug in the availability system. +* #697: Added a "tableName" member to the PropertyStorage PDO backend. (@Frzk). +* #699: PostgreSQL fix for the Locks PDO backend. (@TCKnet) +* Removed the `simplefsserver.php` example file. It's not simple enough. +* #703: PropPatch in client is not correctly encoded. +* #709: Throw exception when running into empty + `supported-calendar-component-set`. +* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This + fixes issues when using sabre/dav as a client. +* The zip release ships with [sabre/vobject 4.0.0-alpha2][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml]. + + +3.1.0-alpha1 (2015-07-19) +------------------------- + +* Now requires PHP 5.5 +* Upgraded to vobject 4, which is a lot faster. +* Support for PHP 7. +* #690: Support for `calendar-availability`, draft 05. + [reference][calendar-availability]. +* #691: Workaround for broken Windows Phone client. +* The zip release ships with [sabre/vobject 4.0.0-alpha1][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.10 (2016-??-??) +------------------ + +* #889 Added support for filtering vCard properties in the addressbook-query + REPORT (@DeepDiver1975). + + +3.0.9 (2016-04-06) +------------------ + +* Set minimum libxml version to 2.7.0 in `composer.json`. +* #727: Added another workaround to make CalDAV work for Windows 10 clients. +* #805: It wasn't possible to create calendars that hold events, journals and + todos using MySQL, because the `components` column was 1 byte too small. +* The zip release ships with [sabre/vobject 3.5.1][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.0.8 (2016-03-12) +------------------ + +* #784: Sync logs for address books were not correctly cleaned up after + deleting them. +* #787: Cannot use non-seekable stream-wrappers with range requests. +* Faster XML parsing and generating due to sabre/xml update. +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.0.7 (2016-01-12) +------------------ + +* #752: PHP 7 support for 3.0 branch. (@DeepDiver1975) +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.0.6 (2016-01-04) +------------------ + +* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to + use emoji in some tables where you couldn't before. +* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached. +* #734: Return `418 I'm a Teapot` when generating a multistatus response that + has resources with no returned properties. +* #740: Bugs in `migrate20.php` script. +* The zip release ships with [sabre/vobject 3.4.8][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.0.5 (2015-09-15) +------------------ + +* #704: Fixed broken uri encoding in multistatus responses. This affected + at least CyberDuck, but probably also others. +* The zip release ships with [sabre/vobject 3.4.7][vobj], +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml]. + + +3.0.4 (2015-09-06) +------------------ + +* #703: PropPatch in client is not correctly encoded. +* #709: Throw exception when running into empty + `supported-calendar-component-set`. +* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This + fixes issues when using sabre/dav as a client. +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml]. + + +3.0.3 (2015-08-06) +------------------ + +* #700: Digest Auth fails on `HEAD` requests. +* Fixed example files to no longer use now-deprecated realm argument. +* The zip release ships with [sabre/vobject 3.4.6][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.2 (2015-07-21) +------------------ + +* #657: Migration script would break when coming a cross an iCalendar object + with no UID. +* #691: Workaround for broken Windows Phone client. +* Fixed a whole bunch of incorrect php docblocks. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.1 (2015-07-02) +------------------ + +* #674: Postgres sql file fixes. (@davesouthey) +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* #680: Fix 'autoprefixing' of dead `{DAV:}href` properties. +* #675: NTLM support in DAV\Client. (@k42b3) +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.0 (2015-06-02) +------------------ + +* No changes since last beta. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta3 (2015-05-29) +------------------------ + +* Fixed deserializing href properties with no value. +* Fixed deserializing `{DAV:}propstat` without a `{DAV:}prop`. +* #668: More information about vcf-export-plugin in browser plugin. +* #669: Add export button to browser plugin for address books. (@mgee) +* #670: multiget report hrefs were not decoded. +* The zip release ships with [sabre/vobject 3.4.4][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta2 (2015-05-27) +------------------------ + +* A node's properties should not overwrite properties that were already set. +* Some uris were not correctly encoded in notifications. +* The zip release ships with [sabre/vobject 3.4.4][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta1 (2015-05-25) +------------------------ + +* `migrate22.php` is now called `migrate30.php`. +* Using php-cs-fixer for automated coding standards enforcement and fixing. +* #660: principals could break html output. +* #662: Fixed several bugs in the `share` request parser. +* #665: Fix a bug in serialization of complex properties in the proppatch + request in the client. +* #666: expand-property report did not correctly prepend the base uri when + generating uris, this caused delegation to break. +* #659: Don't throw errors when when etag-related checks are done on + collections. +* Fully supporting the updated `Prefer` header syntax, as defined in + [rfc7240][rfc7240]. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-alpha1 (2015-05-19) +------------------------- + +* It's now possible to get all property information from files using the + browser plugin. +* Browser plugin will now show a 'calendar export' button when the + ics-export plugin is enabled. +* Some nodes that by default showed the current time as their last + modification time, now no longer has a last modification time. +* CardDAV namespace was missing from default namespaceMap. +* #646: Properties can now control their own HTML output in the browser plugin. +* #646: Nicer HTML output for the `{DAV:}acl` property. +* Browser plugin no longer shows a few properties that take up a lot of space, + but are likely not really interesting for most users. +* #654: Added a collection, `Sabre\DAVACL\FS\HomeCollection` for automatically + creating a private home collection per-user. +* Changed all MySQL columns from `VARCHAR` to `VARBINARY` where possible. +* Improved older migration scripts a bit to allow easier testing. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 4.0.0-alpha3][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 0.4.3][xml]. + + +2.2.0-alpha4 (2015-04-13) +------------------------- + +* Complete rewrite of the XML system. We now use our own [sabre/xml][xml], + which has a much smarter XML Reader and Writer. +* BC Break: It's no longer possible to instantiate the Locks plugin without + a locks backend. I'm not sure why this ever made sense. +* Simplified the Locking system and fixed a bug related to if tokens checking + locks unrelated to the current request. +* `FSExt` Directory and File no longer do custom property storage. This + functionality is already covered pretty well by the `PropertyStorage` plugin, + so please switch. +* Renamed `Sabre\CardDAV\UserAddressBooks` to `Sabre\CardDAV\AddressBookHome` + to be more consistent with `CalendarHome` as well as the CardDAV + specification. +* `Sabre\DAV\IExtendedCollection` now receives a `Sabre\DAV\MkCol` object as + its second argument, and no longer receives seperate properties and + resourcetype arguments. +* `MKCOL` now integrates better with propertystorage plugins. +* #623: Remove need of temporary files when working with Range requests. + (@dratini0) +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt], + [sabre/uri 1.0.0][uri] and [sabre/xml 0.4.3][xml]. + + +2.2.0-alpha3 (2015-02-25) +------------------------- + +* Contains all the changes introduced between 2.1.2 and 2.1.3. +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt] and + [sabre/uri 1.0.0][uri]. + + +2.2.0-alpha2 (2015-01-09) +------------------------- + +* Renamed `Sabre\DAV\Auth\Backend\BackendInterface::requireAuth` to + `challenge`, which is a more correct and better sounding name. +* The zip release ships with [sabre/vobject 3.3.5][vobj], + [sabre/http 3.0.4][http], [sabre/event 2.0.1][evnt]. + + +2.2.0-alpha1 (2014-12-10) +------------------------- + +* The browser plugin now has a new page with information about your sabredav + server, and shows information about every plugin that's loaded in the + system. +* #191: The Authentication system can now support multiple authentication + backends. +* Removed: all `$tableName` arguments from every PDO backend. This was already + deprecated, but has now been fully removed. All of these have been replaced + with public properties. +* Deleted several classes that were already deprecated much earlier: + * `Sabre\CalDAV\CalendarRootNode` + * `Sabre\CalDAV\UserCalendars` + * `Sabre\DAV\Exception\FileNotFound` + * `Sabre\DAV\Locks\Backend\FS` + * `Sabre\DAV\PartialUpdate\IFile` + * `Sabre\DAV\URLUtil` +* Removed: `Sabre\DAV\Client::addTrustedCertificates` and + `Sabre\DAV\Client::setVerifyPeer`. +* Removed: `Sabre\DAV\Plugin::getPlugin()` can now no longer return plugins + based on its class name. +* Removed: `Sabre\DAVACL\Plugin::getPrincipalByEmail()`. +* #560: GuessContentType plugin will now set content-type to + `application/octet-stream` if a better content-type could not be determined. +* #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to + specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`. +* #582: Authentication backend interface changed to be stateless. If you + implemented your own authentication backend, make sure you upgrade your class + to the latest API! +* #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use + `Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead. +* #193: Fix `Sabre\DAV\FSExt\Directory::getQuotaInfo()` on windows. + + +2.1.11 (2016-10-06) +------------------- + +* #805: It wasn't possible to create calendars that hold events, journals and + todos using MySQL, because the `components` column was 1 byte too small. +* The zip release ships with [sabre/vobject 3.5.3][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.10 (2016-03-10) +------------------- + +* #784: Sync logs for address books were not correctly cleaned up after + deleting them. +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.9 (2016-01-25) +------------------ + +* #674: PHP7 support (@DeepDiver1975). +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.8 (2016-01-04) +------------------ + +* #729: Fixed a caching problem in the Tree object. +* #740: Bugs in `migrate20.php` script. +* The zip release ships with [sabre/vobject 3.4.8][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.7 (2015-09-05) +------------------ + +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.6 (2015-07-21) +------------------ + +* #657: Migration script would break when coming a cross an iCalendar object + with no UID. +* #691: Workaround for broken Windows Phone client. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.5 (2015-07-11) +------------------ + +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.4 (2015-05-25) +------------------ + +* #651: Double-encoded path in the browser plugin. Should fix a few broken + links in some setups. +* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE). +* #658: Updating `schedule-calendar-default-URL` does not work well, so we're + disabling it until there's a better fix. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.3 (2015-02-25) +------------------ + +* #586: `SCHEDULE-STATUS` should not contain a reason-phrase. +* #539: Fixed a bug related to scheduling in shared calendars. +* #595: Support for calendar-timezone in iCalendar exports. +* #581: findByUri would send empty prefixes to the principal backend (@soydeedo) +* #611: Escaping a bit more HTML output in the browser plugin. (@LukasReschke) +* #610: Don't allow discovery of arbitrary files using `..` in the browser + plugin (@LukasReschke). +* Browser plugin now shows quota properties. +* #612: PropertyStorage didn't delete properties from nodes when a node's + parents get deleted. +* #581: Fixed problems related to finding attendee information during + scheduling. +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt]. + + +2.1.2 (2014-12-10) +------------------ + +* #566: Another issue related to the migration script, which would cause + scheduling to not work well for events that were already added before the + migration. +* #567: Doing freebusy requests on accounts that had 0 calendars would throw + a `E_NOTICE`. +* #572: `HEAD` requests trigger a PHP warning. +* #579: Browser plugin can throw exception for a few resourcetypes that didn't + have an icon defined. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt]. + + +2.1.1 (2014-11-22) +------------------ + +* #561: IMip Plugin didn't strip mailto: from email addresses. +* #566: Migration process had 2 problems related to adding the `uid` field + to the `calendarobjects` table. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0 (2014-11-19) +------------------ + +* #541: CalDAV PDO backend didn't respect overridden PDO table names. +* #550: Scheduling invites are no longer delivered into shared calendars. +* #554: `calendar-multiget` `REPORT` did not work on inbox items. +* #555: The `calendar-timezone` property is now respected for floating times + and all-day events in the `calendar-query`, `calendar-multiget` and + `free-busy-query` REPORTs. +* #555: The `calendar-timezone` property is also respected for scheduling + free-busy requests. +* #547: CalDAV system too aggressively 'corrects' incoming iCalendar data, and + as a result doesn't return an etag for common cases. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0-alpha2 (2014-10-23) +------------------------- + +* Added: calendar-user-address-set to default principal search properties + list. This should fix iOS attendee autocomplete support. +* Changed: Moved all 'notifications' functionality from `Sabre\CalDAV\Plugin` + to a new plugin: `Sabre\CalDAV\Notifications\Plugin`. If you want to use + notifications-related functionality, just add this plugin. +* Changed: Accessing the caldav inbox, outbox or notification collection no + longer triggers getCalendarsForUser() on backends. +* #533: New invites are no longer delivered to taks-only calendars. +* #538: Added `calendarObjectChange` event. +* Scheduling speedups. +* #539: added `afterResponse` event. (@joserobleda) +* Deprecated: All the "tableName" constructor arguments for all the PDO + backends are now deprecated. They still work, but will be removed in the + next major sabredav version. Every argument that is now deprecated can now + be accessed as a public property on the respective backends. +* #529: Added getCalendarObjectByUID to PDO backend, speeding up scheduling + operations on large calendars. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0-alpha1 (2014-09-23) +------------------------- + +* Added: Support for [rfc6638][rfc6638], also known as CalDAV Scheduling. +* Added: Automatically converting between vCard 3, 4 and jCard using the + `Accept:` header, in CardDAV reports, and automatically converting from + jCard to vCard upon `PUT`. It's important to note that your backends _may_ + now receive both vCard 3.0 and 4.0. +* Added: #444. Collections can now opt-in to support high-speed `MOVE`. +* Changed: PropertyStorage backends now have a `move` method. +* Added: `beforeMove`, and `afterMove` events. +* Changed: A few database changes for the CalDAV PDO backend. Make sure you + run `bin/migrate21.php` to upgrade your database schema. +* Changed: CalDAV backends have a new method: `getCalendarObjectByUID`. This + method MUST be implemented by all backends, but the `AbstractBackend` has a + simple default implementation for this. +* Changed: `Sabre\CalDAV\UserCalendars` has been renamed to + `Sabre\CalDAV\CalendarHome`. +* Changed: `Sabre\CalDAV\CalendarRootNode` has been renamed to + `Sabre\CalDAV\CalendarRoot`. +* Changed: The IMipHandler has been completely removed. With CalDAV scheduling + support, it is no longer needed. It's functionality has been replaced by + `Sabre\CalDAV\Schedule\IMipPlugin`, which can now send emails for clients + other than iCal. +* Removed: `Sabre\DAV\ObjectTree` and `Sabre\DAV\Tree\FileSystem`. All this + functionality has been merged into `Sabre\DAV\Tree`. +* Changed: PrincipalBackend now has a findByUri method. +* Changed: `PrincipalBackend::searchPrincipals` has a new optional `test` + argument. +* Added: Support for the `{http://calendarserver.org/ns/}email-address-set` + property. +* #460: PropertyStorage must move properties during `MOVE` requests. +* Changed: Restructured the zip distribution to be a little bit more lean + and consistent. +* #524: Full support for the `test="anyof"` attribute in principal-search + `REPORT`. +* #472: Always returning lock tokens in the lockdiscovery property. +* Directory entries in the Browser plugin are sorted by type and name. + (@aklomp) +* #486: It's now possible to return additional properties when an 'allprop' + PROPFIND request is being done. (@aklomp) +* Changed: Now return HTTP errors when an addressbook-query REPORT is done + on a uri that's not a vcard. This should help with debugging this common + mistake. +* Changed: `PUT` requests with a `Content-Range` header now emit a 400 status + instead of 501, as per RFC7231. +* Added: Browser plugin can now display the contents of the + `{DAV:}supported-privilege-set` property. +* Added: Now reporting `CALDAV:max-resource-size`, but we're not actively + restricting it yet. +* Changed: CalDAV plugin is now responsible for reporting + `CALDAV:supported-collation-set` and `CALDAV:supported-calendar-data` + properties. +* Added: Now reporting `CARDDAV:max-resource-size`, but we're not actively + restricting it yet. +* Added: Support for `CARDDAV:supported-collation-set`. +* Changed: CardDAV plugin is now responsible for reporting + `CARDDAV:supported-address-data`. This functionality has been removed from + the CardDAV PDO backend. +* When a REPORT is not supported, we now emit HTTP error 415, instead of 403. +* #348: `HEAD` requests now work wherever `GET` also works. +* Changed: Lower priority for the iMip plugins `schedule` event listener. +* Added: #523 Custom CalDAV backends can now mark any calendar as read-only. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 3.0.0][http], and [sabre/event 2.0.0][evnt]. + + +2.0.9 (2015-09-04) +------------------ + +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.6][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + + +2.0.8 (2015-07-11) +------------------ + +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* The zip release ships with [sabre/vobject 3.3.5][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.7 (2015-05-25) +------------------ + +* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE). +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.6 (2014-12-10) +------------------ + +* Added `Sabre\CalDAV\CalendarRoot` as an alias for + `Sabre\CalDAV\CalendarRootNode`. The latter is going to be deprecated in 2.1, + so this makes it slightly easier to write code that works in both branches. +* #497: Making sure we're initializing the sync-token field with a value after + migration. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.5 (2014-10-14) +------------------ + +* #514: CalDAV PDO backend didn't work when overriding the 'calendar changes' + database table name. +* #515: 304 status code was not being sent when checking preconditions. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.4 (2014-08-27) +------------------ + +* #483: typo in calendars creation for PostgreSQL. +* #487: Locks are now automatically removed after a node has been deleted. +* #496: Improve CalDAV and CardDAV sync when there is no webdav-sync support. +* Added: Automatically mapping internal sync-tokens to getctag. +* The zip release ships with [sabre/vobject 3.3.1][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.3 (2014-07-14) +------------------ + +* #474: Fixed PropertyStorage `pathFilter()`. +* #476: CSP policy incorrect, causing stylesheets to not load in the browser + plugin. +* #475: Href properties in the browser plugin sometimes included a backslash. +* #478: `TooMuchMatches` exception never worked. This was fixed, and we also + took this opportunity to rename it to `TooManyMatches`. +* The zip release ships with [sabre/vobject 3.2.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.2 (2014-06-12) +------------------ + +* #470: Fixed compatibility with PHP < 5.4.14. +* #467: Fixed a problem in `examples/calendarserver.php`. +* #466: All the postgresql sample files have been updated. +* Fixed: An error would be thrown if a client did a propfind on a node the + user didn't have access to. +* Removed: Old and broken example code from the `examples/` directory. +* The zip release ships with [sabre/vobject 3.2.3][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.1][evnt]. + + +2.0.1 (2014-05-28) +------------------ + +* #459: PROPFIND requests on Files with no Depth header would return a fatal + error. +* #464: A PROPFIND allprops request should not return properties with status + 404. +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. + + +2.0.0 (2014-05-22) +------------------ + +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. +* Fixed: #456: Issue in sqlite migration script. +* Updated: MySQL database schema optimized by using more efficient column types. +* Cleaned up browser design. + + +2.0.0-beta1 (2014-05-15) +------------------------- + +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. +* BC Break: Property updating and fetching got refactored. Read the [migration + document][mi20] for more information. This allows for creation of a generic + property storage, and other property-related functionality that was not + possible before. +* BC Break: Removed `propertyUpdate`, `beforeGetProperties` and + `afterGetProperties` events. +* Fixed: #413: Memory optimizations for the CardDAV PDO backend. +* Updated: Brand new browser plugin with more debugging features and a design + that is slightly less painful. +* Added: Support for the `{DAV:}supported-method-set` property server-wide. +* Making it easier for implementors to override how the CardDAV addressbook + home is located. +* Fixed: Issue #422 Preconditions were not being set on PUT on non-existent + files. Not really a chance for data-loss, but incorrect nevertheless. +* Fixed: Issue #428: Etag check with `If:` fails if the target is a collection. +* Fixed: Issues #430, #431, #433: Locks plugin didn't not properly release + filesystem based locks. +* Fixed: #443. Support for creating new calendar subscriptions for OS X 10.9.2 + and up. +* Removed: `Sabre\DAV\Server::NODE_*` constants. +* Moved all precondition checking into a central place, instead of having to + think about it on a per-method basis. +* jCal transformation for calendar-query REPORT now works again. +* Switched to PSR-4 +* Fixed: #175. Returning ETag header upon a failed `If-Match` or + `If-None-Match` check. +* Removed: `lib/Sabre/autoload.php`. Use `vendor/autoload.php` instead. +* Removed: all the rfc documentation from the sabre/dav source. This made the + package needlessly larger. +* Updated: Issue #439. Lots of updates in PATCH support. The + Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be + removed in a future version. +* Added: `Sabre\DAV\Exception\LengthRequired`. + +1.9.0-alpha2 (2014-01-14) +------------------------- + +* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.1, and + sabre/event 1.0.0. +* Added: Browser can now inspect any node, if ?sabreaction=browser is appended. +* Fixed: Issue #178. Support for multiple items in the Timeout header. +* Fixed: Issue #382. Stricter checking if calendar-query is allowed to run. +* Added: Depth: Infinity support for PROPFIND request. Thanks Thomas Müller and + Markus Goetz. + + +1.9.0-alpha1 (2013-11-07) +------------------------- + +* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.0alpha5, and + sabre/event 1.0.0. +* BC Break: The CardDAV and CalDAV BackendInterface each have a new method: + getMultipleCards and getMultipleCalendarObjects. The Abstract and PDO backends + have default implementations, but if you implement that interface directly, + this method is now required. +* BC Break: XML property classes now receive an extra argument in their + unserialize method ($propertyMap). This allows for recursively parsing + properties, if needed. +* BC Break: Now using sabre/event for event emitting/subscription. For plugin + authors this means Server::subscribeEvent is now Server::on, and + Server::broadcastEvent is now Server::emit. +* BC Break: Almost all core functionality moved into a CorePlugin. +* BC Break: Most events triggered by the server got an overhaul. +* Changed: Sabre\HTTP now moved into a dedicated sabre/http package. +* Added: Support for WebDAV-sync (rfc6578). +* Added: Support for caldav-subscriptions, which is an easy way for caldav + clients to manage a list of subscriptions on the server. +* Added: Support for emitting and receiving jCal instead of iCalendar for + CalDAV. +* Added: BasicCallback authenticaton backend, for creating simple authentication + systems without having to define any classes. +* Added: A $transactionType property on the server class. This can be used for + logging and performance measuring purposes. +* Fixed: If event handlers modify the request body from a PUT request, an ETag + is no longer sent back. +* Added: Sabre\DAV\IMultiGet to optimize requests that retrieve information + about lists of resources. +* Added: MultiGet support to default CalDAV and CardDAV backends, speeding up + the multiget and sync reports quite a bit! +* Added: ICSExportPlugin can now generate jCal, filter on time-ranges and expand + recurrences. +* Fixed: Read-only access to calendars still allows the sharee to modify basic + calendar properties, such as the displayname and color. +* Changed: The default supportedPrivilegeSet has changed. Most privileges are no + longer marked as abstract. +* Changed: More elegant ACL management for CalendarObject and Card nodes. +* Added: Browser plugin now marks a carddav directory as type Directory, and a + shared calendar as 'Shared'. +* Added: When debugExceptions is turned on, all previous exceptions are also + traversed. +* Removed: Got rid of the Version classes for CalDAV, CardDAV, HTTP, and DAVACL. + Now that there's no separate packages anymore, this makes a bit more sense. +* Added: Generalized the multistatus response parser a bit more, for better + re-use. +* Added: Sabre\DAV\Client now has support for complex properties for PROPPATCH. + (Issue #299). +* Added: Sabre\DAV\Client has support for gzip and deflate encoding. +* Added: Sabre\DAV\Client now has support for sending objects as streams. +* Added: Deserializer for {DAV:}current-user-privilege-set. +* Added: Addressbooks or backends can now specify custom acl rules when creating + cards. +* Added: The ability for plugins to validate custom tokens in If: headers. +* Changed: Completely refactored the Lock plugin to deal with the new If: header + system. +* Added: Checking preconditions for MOVE, COPY, DELETE and PROPPATCH methods. +* Added: has() method on DAV\Property\SupportedReportSet. +* Added: If header now gets checked (with ETag) all the time. Before the dealing + with the If-header was a responsibility of the Locking plugin. +* Fixed: Outbox access for delegates. +* Added: Issue 333: It's now possible to override the calendar-home in the + CalDAV plugin. +* Added: A negotiateContentType to HTTP\Request. A convenience method. +* Fixed: Issue 349: Denying copying or moving a resource into it's own subtree. +* Fixed: SabreDAV catches every exception again. +* Added: Issue #358, adding a component=vevent parameter to the content-types + for calendar objects, if the caldav backend provides this info. + + +1.8.12-stable (2015-01-21) +-------------------------- + +* The zip release ships with sabre/vobject 2.1.7. +* #568: Support empty usernames and passwords in basic auth. + + +1.8.11 (2014-12-10) +------------------- + +* The zip release ships with sabre/vobject 2.1.6. +* Updated: MySQL database schema optimized by using more efficient column types. +* #516: The DAV client will now only redirect to HTTP and HTTPS urls. + + +1.8.10 (2014-05-15) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* includes changes from version 1.7.12. + + +1.8.9 (2014-02-26) +------------------ + +* The zip release ships with sabre/vobject 2.1.3. +* includes changes from version 1.7.11. + + +1.8.8 (2014-02-09) +------------------ + +* includes changes from version 1.7.10. +* The zip release ships with sabre/vobject 2.1.3. + +1.8.7 (2013-10-02) +------------------ + +* the zip release ships with sabre/vobject 2.1.3. +* includes changes from version 1.7.9. + + +1.8.6 (2013-06-18) +------------------ + +* The zip release ships with sabre/vobject 2.1.0. +* Includes changes from version 1.7.8. + + +1.8.5 (2013-04-11) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Includes changes from version 1.7.7. + + +1.8.4 (2013-04-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Includes changes from version 1.7.6. + + +1.8.3 (2013-03-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.6. +* Includes changes from version 1.7.5. +* Fixed: organizer email-address for shared calendars is now prefixed with + mailto:, as it should. + + +1.8.2 (2013-01-19) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Includes changes from version 1.7.4. + + +1.8.1 (2012-12-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Includes changes from version 1.7.3. +* Fixed: Typo in 1.7 migration script caused it to fail. + + +1.8.0 (2012-11-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* BC Break: Moved the entire codebase to PHP namespaces. +* BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks, Principals) now + has consistent naming conventions. There's a BackendInterface, and an + AbstractBackend class. +* BC Break: Changed a bunch of constructor signatures in the CalDAV package, to + reduce dependencies on the ACL package. +* BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method, so + sharees can figure out who is also on a shared calendar. +* Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support for + principal-property-search on any node. +* Added: Simple console script to fire up a fileserver in the current directory + using PHP 5.4's built-in webserver. +* Added: Sharee's can now also read out the list of invites for a shared + calendar. +* Added: The Proxy principal classes now both implement an interface, for + greater flexibility. + + +1.7.13 (2014-07-28) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* Changed: Removed phing and went with a custom build script for now. + + +1.7.12 (2014-05-15) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* Updated: Issue #439. Lots of updates in PATCH support. The + Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be removed + in a future version. +* Fixed: Restoring old setting after changing libxml_disable_entity_loader. +* Fixed: Issue #422: Preconditions were not being set on PUT on non-existent + files. Not really a chance for data-loss, but incorrect nevertheless. +* Fixed: Issue #427: Now checking preconditions on DELETE requests. +* Fixed: Issue #428: Etag check with If: fails if the target is a collection. +* Fixed: Issue #393: PATCH request with missing end-range was handled + incorrectly. +* Added: Sabre_DAV_Exception_LengthRequired to omit 411 errors. + + +1.7.11 (2014-02-26) +------------------- + +* The zip release ships with sabre/vobject 2.1.3. +* Fixed: Issue #407: large downloads failed. +* Fixed: Issue #414: XXE security problem on older PHP versions. + + +1.7.10 (2014-02-09) +------------------- + +* Fixed: Issue #374: Don't urlescape colon (:) when it's not required. +* Fixed: Potential security vulnerability in the http client. + + +1.7.9 (2013-10-02) +------------------ + +* The zip release ships with sabre/vobject 2.1.3. +* Fixed: Issue #365. Incorrect output when principal urls have spaces in them. +* Added: Issue #367: Automatically adding a UID to vcards that don't have them. + + +1.7.8 (2013-06-17) +------------------ + +* The zip release ships with sabre/vobject 2.1.0. +* Changed: Sabre\DAV\Client::verifyPeer is now a protected property (instead of + private). +* Fixed: Text was incorrectly escaped in the Href and HrefList properties, + disallowing urls with ampersands (&) in them. +* Added: deserializer for Sabre\DAVACL\Property\CurrentUserPrivilegeSet. +* Fixed: Issue 335: Client only deserializes properties with status 200. +* Fixed: Issue 341: Escaping xml in 423 Locked error responses. +* Added: Issue 339: beforeGetPropertiesForPath event. + + +1.7.7 (2013-04-11) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Fixed: Assets in the browser plugins were not being served on windows + machines. + + +1.7.6 (2013-04-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Fixed: vcardurl in database schema can now hold 255 characters instead of 80 + (which is often way to small). +* Fixed: The browser plugin potentially allowed people to open any arbitrary + file on windows servers (CVE-2013-1939). + + +1.7.5 (2013-03-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.6. +* Change: No longer advertising support for 4.0 vcards. iOS and OS X address + book don't handle this well, and just advertising 3.0 support seems like the + most logical course of action. +* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it, + don't use this..). + + +1.7.4 (2013-01-19) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Changed: To be compatible with MS Office 2011 for Mac, a workaround was + removed that was added to support old versions of Windows XP (pre-SP3). + Indeed! We needed a crazy workaround to work with one MS product in the past, + and we can't keep that workaround to be compatible with another MS product. +* Fixed: expand-properties REPORT had incorrect values for the href element. +* Fixed: Range requests now work for non-seekable streams. (Thanks Alfred + Klomp). +* Fixed: Changed serialization of {DAV:}getlastmodified and {DAV:}supportedlock + to improve compatibility with MS Office 2011 for Mac. +* Changed: reverted the automatic translation of 'DAV:' xml namespaces to + 'urn:DAV' when parsing files. Issues were reported with libxml 2.6.32, on a + relatively recent debian release, so we'll wait till 2015 to take this one out + again. +* Added: Sabre_DAV_Exception_ServiceUnavailable, for emitting 503's. + + +1.7.3 (2012-12-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Fixed: Removing double slashes from getPropertiesForPath. +* Change: Marked a few more properties in the CardDAV as protected, instead of + private. +* Fixed: SharingPlugin now plays nicer with other plugins with similar + functionality. +* Fixed: Issue 174. Sending back HTTP/1.0 for requests with this version. + + +1.7.2 (2012-11-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Added: ACL plugin advertises support for 'calendarserver-principal- + property-search'. +* Fixed: [#153] Allowing for relative http principals in iMip requests. +* Added: Support for cs:first-name and cs:last-name properties in sharing + invites. +* Fixed: Made a bunch of properties protected, where they were private before. +* Added: Some non-standard properties for sharing to improve compatibility. +* Fixed: some bugfixes in postgres sql script. +* Fixed: When requesting some properties using PROPFIND, they could show up as + both '200 Ok' and '403 Forbidden'. +* Fixed: calendar-proxy principals were not checked for deeper principal + membership than 1 level. +* Fixed: setGroupMemberSet argument now correctly receives relative principal + urls, instead of the absolute ones. +* Fixed: Server class will filter out any bonus properties if any extra were + returned. This means the implementor of the IProperty class can be a bit + lazier when implementing. Note: bug numbers after this line refer to Google + Code tickets. We're using github now. + + +1.7.1 (2012-10-07) +------------------ + +* Fixed: include path problem in the migration script. + + +1.7.0 (2012-10-06) +------------------ + +* BC Break: The calendarobjects database table has a bunch of new fields, and a + migration script is required to ensure everything will keep working. Read the + wiki for more details. +* BC Break: The ICalendar interface now has a new method: calendarQuery. +* BC Break: In this version a number of classes have been deleted, that have + been previously deprecated. Namely: - Sabre_DAV_Directory (now: + Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now: + Sabre_DAV_SimpleCollection) +* BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument. + If you extended this class, you should fix this method. It's only used for + informational purposes. +* BC Break: The DAV: namespace is no longer converted to urn:DAV. This was a + workaround for a bug in older PHP versions (pre-5.3). +* Removed: Sabre.includes.php was deprecated, and is now removed. +* Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please use + Sabre_DAV_Server and check the examples in the examples/ directory. +* Changed: The Sabre_VObject library now spawned into it's own project! The + VObject library is still included in the SabreDAV zip package. +* Added: Experimental interfaces to allow implementation of caldav-sharing. Note + that no implementation is provided yet, just the api hooks. +* Added: Free-busy reporting compliant with the caldav-scheduling standard. This + allows iCal and other clients to fetch other users' free-busy data. +* Added: Experimental NotificationSupport interface to add caldav notifications. +* Added: VCF Export plugin. If enabled, it can generate an export of an entire + addressbook. +* Added: Support for PATCH using a SabreDAV format, to live-patch files. +* Added: Support for Prefer: return-minimal and Brief: t headers for PROPFIND + and PROPPATCH requests. +* Changed: Responsibility for dealing with the calendar-query is now moved from + the CalDAV plugin to the CalDAV backends. This allows for heavy optimizations. +* Changed: The CalDAV PDO backend is now a lot faster for common calendar + queries. +* Changed: We are now using the composer autoloader. +* Changed: The CalDAV backend now all implement an interface. +* Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is now the + basis of every property class. +* Update: Caching results for principal lookups. This should cut down queries + and performance for a number of heavy requests. +* Update: ObjectTree caches lookups much more aggresively, which will help + especially speeding up a bunch of REPORT queries. +* Added: Support for the schedule-calendar-transp property. +* Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded. +* Fixed: Workaround for the SOGO connector, as it doesn't understand receiving + "text/x-vcard; charset=utf-8" for a contenttype. +* Added: Sabre_DAV_Client now throws more specific exceptions in cases where we + already has an exception class. +* Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH method + to update parts of a file. +* Added: Tons of timezone name mappings for Microsoft Exchange. +* Added: Support for an 'exception' event in the server class. +* Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!) +* Fixed: Rejecting calendar objects if they are not in the + supported-calendar-component list. (thanks Armin!) +* Fixed: Issue 219: serialize() now reorders correctly. +* Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes if there is + whitespace in $dom. +* Fixed: Returning 409 Conflict instead of 500 when an attempt is made to create + a file as a child of something that's not a collection. +* Fixed: Issue 237: xml-encoding values in SabreDAV error responses. +* Fixed: Returning 403, instead of 501 when an unknown REPORT is requested. +* Fixed: Postfixing slash on {DAV:}owner properties. +* Fixed: Several embarrassing spelling mistakes in docblocks. + + +1.6.10 (2013-06-17) +------------------- + +* Fixed: Text was incorrectly escaped in the Href and HrefList properties, + disallowing urls with ampersands (&) in them. +* Fixed: Issue 341: Escaping xml in 423 Locked error responses. + + +1.6.9 (2013-04-11) +------------------ + +* Fixed: Assets in the browser plugins were not being served on windows + machines. + + +1.6.8 (2013-04-08) +------------------ + +* Fixed: vcardurl in database schema can now hold 255 characters instead of 80 + (which is often way to small). +* Fixed: The browser plugin potentially allowed people to open any arbitrary + file on windows servers. (CVE-2013-1939). + + +1.6.7 (2013-03-01) +------------------ + +* Change: No longer advertising support for 4.0 vcards. iOS and OS X address + book don't handle this well, and just advertising 3.0 support seems like the + most logical course of action. +* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it, + don't use this..). + + +1.6.6 (2013-01-19) +------------------ + +* Fixed: Backported a fix for broken XML serialization in error responses. + (Thanks @DeepDiver1975!) + + +1.6.5 (2012-10-04) +------------------ + +* Fixed: Workaround for line-ending bug OS X 10.8 addressbook has. +* Added: Ability to allow users to set SSL certificates for the Client class. + (Thanks schiesbn!). +* Fixed: Directory indexes with lots of nodes should be a lot faster. +* Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with + Sabre_DAV_Client, and no valid properties are returned. +* Fixed: Issue with filtering on alarms in tasks. + + +1.6.4 (2012-08-02) +------------------ + +* Fixed: Issue 220: Calendar-query filters may fail when filtering on alarms, if + an overridden event has it's alarm removed. +* Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler. +* Fixed: Issue 222: beforeWriteContent shouldn't be called for lock requests. +* Fixed: Problem with POST requests to the outbox if mailto: was not lower + cased. +* Fixed: Yearly recurrence rule expansion on leap-days no behaves correctly. +* Fixed: Correctly checking if recurring, all-day events with no dtstart fall in + a timerange if the start of the time-range exceeds the start of the instance + of an event, but not the end. +* Fixed: All-day recurring events wouldn't match if an occurence ended exactly + on the start of a time-range. +* Fixed: HTTP basic auth did not correctly deal with passwords containing colons + on some servers. +* Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the + calendar-query REPORT and free-busy calculations. + + +1.6.3 (2012-06-12) +------------------ + +* Added: It's now possible to specify in Sabre_DAV_Client which type of + authentication is to be used. +* Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed. +* Fixed: Issue 205: Parsing an iCalendar 0-second date interval. +* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making sure + every iCalendar object only contains 1 component, and disallowing vcards, + forcing every component to have a UID. +* Fixed: Basic validation for vcards in the CardDAV plugin. +* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it from + updating events. +* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in a + recurring event could result in an endless loop. +* Fixed: All uri fields are now a maximum of 200 characters. The Bynari outlook + plugin used much longer strings so this should improve compatibility. +* Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See + https://bugs.kde.org/show_bug.cgi?id=300047 +* Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken. + + +1.6.2 (2012-04-16) +------------------ + +* Fixed: Sabre_VObject_Node::$parent should have been public. +* Fixed: Recurrence rules of events are now taken into consideration when doing + time-range queries on alarms. +* Fixed: Added a workaround for the fact that php's DateInterval cannot parse + weeks and days at the same time. +* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's + version number from various outputs. +* Fixed: DTSTART values would be incorrect when expanding events. +* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY BYDAY + recurrences. +* Fixed: Issue 203: A problem with overridden events hitting the exact date and + time of a subsequent event in the recurrence set. +* Fixed: There was a problem with recurrence rules, for example the 5th tuesday + of the month, if this day did not exist. +* Added: New HTTP status codes from draft-nottingham-http-new-status-04. + + +1.6.1 (2012-03-05) +------------------ + +* Added: createFile and put() can now return an ETag. +* Added: Sending back an ETag on for operations on CardDAV backends. This should + help with OS X 10.6 Addressbook compatibility. +* Fixed: Fixed a bug where an infinite loop could occur in the recurrence + iterator if the recurrence was YEARLY, with a BYMONTH rule, and either BYDAY + or BYMONTHDAY match the first day of the month. +* Fixed: Events that are excluded using EXDATE are still counted in the COUNT= + parameter in the RRULE property. +* Added: Support for time-range filters on VALARM components. +* Fixed: Correctly filtering all-day events. +* Fixed: Sending back correct mimetypes from the browser plugin (thanks + Jürgen). +* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency. +* Fixed: Calendardata would be destroyed when performing a MOVE request. + + +1.6.0 (2012-02-22) +------------------ + +* BC Break: Now requires PHP 5.3 +* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also implement + the getSupportedPrivilegeSet method. See website for details. +* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to + Sabre_VObject_DateTimeParser. +* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods: + 'searchPrincipals' and 'updatePrincipal'. +* BC Break: Sabre_DAV_ILockable is removed and all related per-node locking + functionality. +* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of + Sabre_DAV_Exception_NotFound. The former will be removed in a later version. +* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead. +* BC Break: Sabre_CalDAV_Server is now deprecated, check out the documentation + on how to setup a caldav server with just Sabre_DAV_Server. +* BC Break: Default Principals PDO backend now needs a new field in the + 'principals' table. See the website for details. +* Added: Ability to create new calendars and addressbooks from within the + browser plugin. +* Added: Browser plugin: icons for various nodes. +* Added: Support for FREEBUSY reports! +* Added: Support for creating principals with admin-level privileges. +* Added: Possibility to let server send out invitation emails on behalf of + CalDAV client, using Sabre_CalDAV_Schedule_IMip. +* Changed: beforeCreateFile event now passes data argument by reference. +* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now be + specified in Sabre_VObject_Property::$classMap. +* Added: Ability for plugins to tell the ACL plugin which principal plugins are + searchable. +* Added: [DAVACL] Per-node overriding of supported privileges. This allows for + custom privileges where needed. +* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin, which + allows for easy searching for principals, based on their properties. +* Added: Sabre_VObject_Component::getComponents() to return a list of only + components and not properties. +* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV, + DAVACL, HTTP, VObject) as an alternative to the autoloader. This often works + much faster. +* Added: Support for the 'Me card', which allows Addressbook.app users specify + which vcard is their own. +* Added: Support for updating principal properties in the DAVACL principal + backends. +* Changed: Major refactoring in the calendar-query REPORT code. Should make + things more flexible and correct. +* Changed: The calendar-proxy-[read|write] principals will now only appear in + the tree, if they actually exist in the Principal backend. This should reduce + some problems people have been having with this. +* Changed: Sabre_VObject_Element_* classes are now renamed to + Sabre_VObject_Property. Old classes are retained for backwards compatibility, + but this will be removed in the future. +* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports based on + lists of events. +* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times for + recurring events. +* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT. +* Fixed: Issue 154: Encoding of VObject parameters with no value was incorrect. +* Added: Support for {DAV:}acl-restrictions property from RFC3744. +* Added: The contentlength for calendar objects can now be supplied by a CalDAV + backend, allowing for more optimizations. +* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath. +* Fixed: {DAV:}getcontentlength may now be not specified. +* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths from + clients. This means that + will now be treated as a literal rather than a + space, and this should improve compatibility with the Windows built-in client. +* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402 status + codes. +* Added: Some mysql unique constraints to example files. +* Fixed: Correctly formatting HTTP dates. +* Fixed: Issue 94: Sending back Last-Modified header for 304 responses. +* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal, + Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar. +* Changed: Properties are now also automatically mapped to their appropriate + classes, if they are created using the add() or __set() methods. +* Changed: Cloning VObject objects now clones the entire tree, rather than just + the default shallow copy. +* Added: Support for recurrence expansion in the CALDAV:calendar-multiget and + CALDAV:calendar-query REPORTS. +* Changed: CalDAV PDO backend now sorts calendars based on the internal + 'calendarorder' field. +* Added: Issue 181: Carddav backends may no optionally not supply the carddata + in getCards, if etag and size are specified. This may speed up certain + requests. +* Added: More arguments to beforeWriteContent and beforeCreateFile (see + WritingPlugins wiki document). +* Added: Hook for iCalendar validation. This allows us to validate iCalendar + objects when they're uploaded. At the moment we're just validating syntax. +* Added: VObject now support Windows Timezone names correctly (thanks mrpace2). +* Added: If a timezonename could not be detected, we fall back on the default + PHP timezone. +* Added: Now a Composer package (thanks willdurand). +* Fixed: Support for \N as a newline character in the VObject reader. +* Added: afterWriteContent, afterCreateFile and afterUnbind events. +* Added: Postgresql example files. Not part of the unittests though, so use at + your own risk. +* Fixed: Issue 182: Removed backticks from sql queries, so it will work with + Postgres. + + +1.5.9 (2012-04-16) +------------------ + +* Fixed: Issue with parsing timezone identifiers that were surrounded by quotes. + (Fixes emClient compatibility). + + +1.5.8 (2012-02-22) +------------------ + +* Fixed: Issue 95: Another timezone parsing issue, this time in calendar-query. + + +1.5.7 (2012-02-19) +------------------ + +* Fixed: VObject properties are now always encoded before components. +* Fixed: Sabre_DAVACL had issues with multiple levels of privilege aggregration. +* Changed: Added 'GuessContentType' plugin to fileserver.php example. +* Fixed: The Browser plugin will now trigger the correct events when creating + files. +* Fixed: The ICSExportPlugin now considers ACL's. +* Added: Made it optional to supply carddata from an Addressbook backend when + requesting getCards. This can make some operations much faster, and could + result in much lower memory use. +* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file. +* Fixed: Issue 191: beforeUnlock was triggered twice. + + +1.5.6 (2012-01-07) +------------------ + +* Fixed: Issue 174: VObject could break UTF-8 characters. +* Fixed: pear package installation issues. + + +1.5.5 (2011-12-16) +------------------ + +* Fixed: CalDAV time-range filter workaround for recurring events. +* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple files to + be locked at the same time. + + +1.5.4 (2011-10-28) +------------------ + +* Fixed: GuessContentType plugin now supports mixed case file extensions. +* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME'). +* Changed: Sending back HTTP 204 after a PUT request on an existing resource + instead of HTTP 200. This should fix Evolution CardDAV client compatibility. +* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available. +* Added: All VObject elements now have a reference to their parent node. + + +1.5.3 (2011-09-28) +------------------ + +* Fixed: Sabre_DAV_Collection was missing from the includes file. +* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in + uppercase. +* Fixed: Issue 153: Support for files with mixed newline styles in + Sabre_VObject. +* Fixed: Issue 159: Automatically converting any vcard and icalendardata to + UTF-8. +* Added: Sabre_DAV_SimpleFile class for easy static file creation. +* Added: Issue 158: Support for the CARDDAV:supported-address-data property. + + +1.5.2 (2011-09-21) +------------------ + +* Fixed: carddata and calendardata MySQL fields are now of type 'mediumblob'. + 'TEXT' was too small sometimes to hold all the data. +* Fixed: {DAV:}supported-report-set is now correctly reporting the reports for + IAddressBook. +* Added: Sabre_VObject_Property::add() to add duplicate parameters to + properties. +* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject + interfaces. +* Fixed: Issue 140: Not returning 201 Created if an event cancelled the creation + of a file. +* Fixed: Issue 150: Faster URLUtil::encodePath() implementation. +* Fixed: Issue 144: Browser plugin could interfere with + TemporaryFileFilterPlugin if it was loaded first. +* Added: It's not possible to specify more 'alternate uris' in principal + backends. + + +1.5.1 (2011-08-24) +------------------ + +* Fixed: Issue 137. Hiding action interface in HTML browser for non-collections. +* Fixed: addressbook-query is now correctly returned from the + {DAV:}supported-report-set property. +* Fixed: Issue 142: Bugs in groupwareserver.php example. +* Fixed: Issue 139: Rejecting PUT requests with Content-Range. + + +1.5.0 (2011-08-12) +------------------ + +* Added: CardDAV support. +* Added: An experimental WebDAV client. +* Added: MIME-Directory grouping support in the VObject library. This is very + useful for people attempting to parse vcards. +* BC Break: Adding parameters with the VObject libraries now overwrites the + previous parameter, rather than just add it. This makes more sense for 99% of + the cases. +* BC Break: lib/Sabre.autoload.php is now removed in favor of + lib/Sabre/autoload.php. +* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in a + future version. Use Sabre_DAV_Collection instead. +* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be removed in + a future version. Use Sabre_DAV_SimpleCollection instead. +* Fixed: Problem with overriding tablenames for the CalDAV backend. +* Added: Clark-notation parser to XML utility. +* Added: unset() support to VObject components. +* Fixed: Refactored CalDAV property fetching to be faster and simpler. +* Added: Central string-matcher for CalDAV and CardDAV plugins. +* Added: i;unicode-casemap support +* Fixed: VObject bug: wouldn't parse parameters if they weren't specified in + uppercase. +* Fixed: VObject bug: Parameters now behave more like Properties. +* Fixed: VObject bug: Parameters with no value are now correctly parsed. +* Changed: If calendars don't specify which components they allow, 'all' + components are assumed (e.g.: VEVENT, VTODO, VJOURNAL). +* Changed: Browser plugin now uses POST variable 'sabreAction' instead of + 'action' to reduce the chance of collisions. + + +1.4.4 (2011-07-07) +------------------ + +* Fixed: Issue 131: Custom CalDAV backends could break in certain cases. +* Added: The option to override the default tablename all PDO backends use. + (Issue 60). +* Fixed: Issue 124: 'File' authentication backend now takes realm into + consideration. +* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This allows + users to update the {DAV:}group-member-set property. +* Added: Helper functions for DateTime-values in Sabre_VObject package. +* Added: VObject library can now automatically map iCalendar properties to + custom classes. + + +1.4.3 (2011-04-25) +------------------ + +* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug. +* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please change + the DATETIME field to an INT to ensure this field will work correctly. +* Change: Sabre_DAV_Property_Principal is now renamed to + Sabre_DAVACL_Property_Principal. +* Added: API level support for ACL HTTP method. +* Fixed: Bug in serializing {DAV:}acl property. +* Added: deserializer for {DAV:}resourcetype property. +* Added: deserializer for {DAV:}acl property. +* Added: deserializer for {DAV:}principal property. + + +1.4.2-beta (2011-04-01) +----------------------- + +* Added: It's not possible to disable listing of nodes that are denied read + access by ACL. +* Fixed: Changed a few properties in CalDAV classes from private to protected. +* Fixed: Issue 119: Terrible things could happen when relying on guessBaseUri, + the server was running on the root of the domain and a user tried to access a + file ending in .php. This is a slight BC break. +* Fixed: Issue 118: Lock tokens in If headers without a uri should be treated as + the request uri, not 'all relevant uri's. +* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in cases + where there were similar named locked files in a directory. + + +1.4.1-beta (2011-02-26) +----------------------- + +* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks. +* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when running + on apache, so a few workarounds were added. +* Change: Slightly changed CalDAV Backend API's, to allow for heavy + optimizations. This is non-bc breaking. + + +1.4.0-beta (2011-02-12) +----------------------- + +* Added: Partly RFC3744 ACL support. +* Added: Calendar-delegation (caldav-proxy) support. +* BC break: In order to fix Issue 99, a new argument had to be added to + Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for details. +* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be removed + in a later version. Use PDO or the new File class instead. +* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked deprecated, and + will be removed in a future version. Please use Sabre_VObject instead. +* Removed: All principal-related functionality has been removed from the + Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin. +* Added: VObject library, for easy vcard/icalendar parsing using a natural + interface. +* Added: Ability to automatically generate full .ics feeds off calendars. To + use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your calendar + url. +* Added: Plugins can now specify a pluginname, for easy access using + Sabre_DAV_Server::getPlugin(). +* Added: beforeGetProperties event. +* Added: updateProperties event. +* Added: Principal listings and calendar-access can now be done privately, + disallowing users from accessing or modifying other users' data. +* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If it's an + array with node-objects, a Root collection will automatically be created, and + the nodes are used as top-level children. +* Added: The principal base uri is now customizable. It used to be hardcoded to + 'principals/[user]'. +* Added: getSupportedReportSet method in ServerPlugin class. This allows you to + easily specify which reports you're implementing. +* Added: A '..' link to the HTML browser. +* Fixed: Issue 99: Locks on child elements were ignored when their parent nodes + were deleted. +* Fixed: Issue 90: lockdiscovery property and LOCK response now include a + {DAV}lockroot element. +* Fixed: Issue 96: support for 'default' collation in CalDAV text-match filters. +* Fixed: Issue 102: Ensuring that copy and move with identical source and + destination uri's fails. +* Fixed: Issue 105: Supporting MKCALENDAR with no body. +* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util. +* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a string) +* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the root + node. +* Added: Global way to easily supply new resourcetypes for certain node classes. +* Fixed: Issue 59: Allowing the user to override the authentication realm in + Sabre_CalDAV_Server. +* Update: Issue 97: Looser time-range checking if there's a recurrence rule in + an event. This fixes 'missing recurring events'. + + +1.3.0 (2010-10-14) +------------------ + +* Added: childExists method to Sabre_DAV_ICollection. This is an api break, so + if you implement Sabre_DAV_ICollection directly, add the method. +* Changed: Almost all HTTP method implementations now take a uri argument, + including events. This allows for internal rerouting of certain calls. If you + have custom plugins, make sure they use this argument. If they don't, they + will likely still work, but it might get in the way of future changes. +* Changed: All getETag methods MUST now surround the etag with double-quotes. + This was a mistake made in all previous SabreDAV versions. If you don't do + this, any If-Match, If-None-Match and If: headers using Etags will work + incorrectly. (Issue 85). +* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to easily + implement basic authentication. +* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden instead. +* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection instead. +* Added: Browser plugin now uses {DAV:}displayname if this property is + available. +* Added: Cache layer in the ObjectTree. +* Added: Tree classes now have a delete and getChildren method. +* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if the + date is an exact match. +* Fixed: Support for multiple ETags in If-Match and If-None-Match headers. +* Fixed: Improved baseUrl handling. +* Fixed: Issue 67: Non-seekable stream support in ::put()/::get(). +* Fixed: Issue 65: Invalid dates are now ignored. +* Updated: Refactoring in Sabre_CalDAV to make everything a bit more ledgable. +* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on + Windows. +* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to 'file + size'-1. + + +1.2.5 (2010-08-18) +------------------ + +* Fixed: Issue 73: guessBaseUrl fails for some servers. +* Fixed: Issue 67: SabreDAV works better with non-seekable streams. +* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if + the date is an exact match. + + +1.2.4 (2010-07-13) +------------------ + +* Fixed: Issue 62: Guessing baseUrl fails when url contains a query-string. +* Added: Apache configuration sample for CGI/FastCGI setups. +* Fixed: Issue 64: Only returning calendar-data when it was actually requested. + + +1.2.3 (2010-06-26) +------------------ + +* Fixed: Issue 57: Supporting quotes around etags in If-Match and If-None-Match + + +1.2.2 (2010-06-21) +------------------ + +* Updated: SabreDAV now attempts to guess the BaseURI if it's not set. +* Updated: Better compatibility with BitKinex +* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET + requests. +* Fixed: Issue with certain encoded paths in Browser Plugin. + + +1.2.1 (2010-06-07) +------------------ + +* Fixed: Issue 50, patch by Mattijs Hoitink. +* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter. +* Fixed: Issue 38, Allowing custom filters to be added to TemporaryFileFilter. +* Fixed: Issue 53, ETags in the If: header were always failing. This behaviour + is now corrected. +* Added: Apache Authentication backend, in case authentication through .htaccess + is desired. +* Updated: Small improvements to example files. + + +1.2.0 (2010-05-24) +------------------ + +* Fixed: Browser plugin now displays international characters. +* Changed: More properties in CalDAV classes are now protected instead of + private. + + +1.2.0beta3 (2010-05-14) +----------------------- + +* Fixed: Custom properties were not properly sent back for allprops requests. +* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007. +* Changed: Removed CalDAV items from includes.php, and added a few missing ones. + + +1.2.0beta2 (2010-05-04) +----------------------- + +* Fixed: Issue 46: Fatal error for some non-existent nodes. +* Updated: some example sql to include email address. +* Added: 208 and 508 statuscodes from RFC5842. +* Added: Apache2 configuration examples + + +1.2.0beta1 (2010-04-28) +----------------------- + +* Fixed: redundant namespace declaration in resourcetypes. +* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable + interface is used. +* Changed: using http://sabredav.org/ns for all custom xml properties. +* Added: email address property to principals. +* Updated: CalendarObject validation. + + +1.2.0alpha4 (2010-04-24) +------------------------ + +* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since, + If-Unmodified-Since. +* Changed: Brand new build system. Functionality is split up between Sabre, + Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to that a new + non-pear package will be created with all this functionality combined. +* Changed: Autoloader moved to Sabre/autoload.php. +* Changed: The Allow: header is now more accurate, with appropriate HTTP methods + per uri. +* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few + places where Sabre_DAV_Exception_NotImplemented was used. + + +1.2.0alpha3 (2010-04-20) +------------------------ + +* Update: Complete rewrite of property updating. Now easier to use and atomic. +* Fixed: Issue 16, automatically adding trailing / to baseUri. +* Added: text/plain is used for .txt files in GuessContentType plugin. +* Added: support for principal-property-search and principal-search-property-set + reports. +* Added: Issue 31: Hiding exception information by default. Can be turned on + with the Sabre_DAV_Server::$debugExceptions property. + + +1.2.0alpha2 (2010-04-08) +------------------------ + +* Added: Calendars are now private and can only be read by the owner. +* Fixed: double namespace declaration in multistatus responses. +* Added: MySQL database dumps. MySQL is now also supported next to SQLite. +* Added: expand-properties REPORT from RFC 3253. +* Added: Sabre_DAV_Property_IHref interface for properties exposing urls. +* Added: Issue 25: Throwing error on broken Finder behaviour. +* Changed: Authentication backend is now aware of current user. + + +1.2.0alpha1 (2010-03-31) +------------------------ + +* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special + characters. +* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes Office + 2010 compatibility. +* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease + debugging. +* Fixed: Issue 36: Incorrect variable name, throwing error in some requests. +* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter. +* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8. +* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales. +* Added: More unittests. +* Added: SabreDAV version to all error responses. +* Added: URLUtil class for decoding urls. +* Changed: Now using pear.sabredav.org pear channel. +* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method. + + +1.1.2-alpha (2010-03-18) +------------------------ + +* Added: RFC5397 - current-user-principal support. +* Fixed: Issue 27: encoding entities in property responses. +* Added: naturalselection script now allows the user to specify a 'minimum + number of bytes' for deletion. This should reduce load due to less crawling +* Added: Full support for the calendar-query report. +* Added: More unittests. +* Added: Support for complex property deserialization through the static + ::unserialize() method. +* Added: Support for modifying calendar-component-set +* Fixed: Issue 29: Added TIMEOUT_INFINITE constant + + +1.1.1-alpha (2010-03-11) +------------------------ + +* Added: RFC5689 - Extended MKCOL support. +* Fixed: Evolution support for CalDAV. +* Fixed: PDO-locks backend was pretty much completely broken. This is 100% + unittested now. +* Added: support for ctags. +* Fixed: Comma's between HTTP methods in 'Allow' method. +* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a + datadirectory must always be specified from now on. +* Changed: Moved Sabre_DAV_Server::parseProps to + Sabre_DAV_XMLUtil::parseProperties. +* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection. +* Changed: Sabre_DAV_Exception_PermissionDenied is now + Sabre_DAV_Exception_Forbidden. +* Changed: Sabre_CalDAV_ICalendarCollection is removed. +* Added: Sabre_DAV_IExtendedCollection. +* Added: Many more unittests. +* Added: support for calendar-timezone property. + + +1.1.0-alpha (2010-03-01) +------------------------ + +* Note: This version is forked from version 1.0.5, so release dates may be out + of order. +* Added: CalDAV - RFC 4791 +* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for this. +* Added: PDO authentication backend. +* Added: Example sql for auth, caldav, locks for sqlite. +* Added: Sabre_DAV_Browser_GuessContentType plugin +* Changed: Authentication plugin refactored, making it possible to implement + non-digest authentication. +* Fixed: Better error display in browser plugin. +* Added: Support for {DAV:}supported-report-set +* Added: XML utility class with helper functions for the WebDAV protocol. +* Added: Tons of unittests +* Added: PrincipalCollection and Principal classes +* Added: Sabre_DAV_Server::getProperties for easy property retrieval +* Changed: {DAV:}resourceType defaults to 0 +* Changed: Any non-null resourceType now gets a / appended to the href value. + Before this was just for {DAV:}collection's, but this is now also the case for + for example {DAV:}principal. +* Changed: The Href property class can now optionally create non-relative uri's. +* Changed: Sabre_HTTP_Response now returns false if headers are already sent and + header-methods are called. +* Fixed: Issue 19: HEAD requests on Collections +* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response +* Fixed: Issue 18: Doesn't work with Evolution Contacts + + +1.0.15 (2010-05-28) +------------------- + +* Added: Issue 31: Hiding exception information by default. Can be turned on + with the Sabre_DAV_Server::$debugExceptions property. +* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also the + case in the upcoming 1.2.0, so it will improve future compatibility. + + +1.0.14 (2010-04-15) +------------------- + +* Fixed: double namespace declaration in multistatus responses. + + +1.0.13 (2010-03-30) +------------------- + +* Fixed: Issue 40: Last references to basename/dirname + + +1.0.12 (2010-03-30) +------------------- + +* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter. +* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special + characters. +* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8. +* Fixed: Issue 39: Basename fails on non-utf-8 locales. +* Added: More unittests. +* Added: SabreDAV version to all error responses. +* Added: URLUtil class for decoding urls. +* Updated: Now using pear.sabredav.org pear channel. + + +1.0.11 (2010-03-23) +------------------- + +* Non-public release. This release is identical to 1.0.10, but it is used to + test releasing packages to pear.sabredav.org. + + +1.0.10 (2010-03-22) +------------------- + +* Fixed: Issue 34: Invalid Lock-Token header response. +* Added: Issue 35: Adding SabreDAV version to HTTP OPTIONS responses. + + +1.0.9 (2010-03-19) +------------------ + +* Fixed: Issue 27: Entities not being encoded in PROPFIND responses. +* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant. + + +1.0.8 (2010-03-03) +------------------ + +* Fixed: Issue 21: typos causing errors +* Fixed: Issue 23: Comma's between methods in Allow header. +* Added: Sabre_DAV_ICollection interface, to aid in future compatibility. +* Added: Sabre_DAV_Exception_Forbidden exception. This will replace + Sabre_DAV_Exception_PermissionDenied in the future, and can already be used to + ensure future compatibility. + + +1.0.7 (2010-02-24) +------------------ + +* Fixed: Issue 19 regression for MS Office + + +1.0.6 (2010-02-23) +------------------ + +* Fixed: Issue 19: HEAD requests on Collections + + +1.0.5 (2010-01-22) +------------------ + +* Fixed: Fatal error when a malformed url was used for unlocking, in conjuction + with Sabre.autoload.php due to a incorrect filename. +* Fixed: Improved unittests and build system + + +1.0.4 (2010-01-11) +------------------ + +* Fixed: needed 2 different releases. One for googlecode and one for pearfarm. + This is to retain the old method to install SabreDAV until pearfarm becomes + the standard installation method. + + +1.0.3 (2010-01-11) +------------------ + +* Added: RFC4709 support (davmount) +* Added: 6 unittests +* Added: naturalselection. A tool to keep cache directories below a specified + theshold. +* Changed: Now using pearfarm.org channel server. + + +1.0.1 (2009-12-22) +------------------ + +* Fixed: Issue 15: typos in examples +* Fixed: Minor pear installation issues + + +1.0.0 (2009-11-02) +------------------ + +* Added: SimpleDirectory class. This class allows creating static directory + structures with ease. +* Changed: Custom complex properties and exceptions now get an instance of + Sabre_DAV_Server as their first argument in serialize() +* Changed: Href complex property now prepends server's baseUri +* Changed: delete before an overwriting copy/move is now handles by server class + instead of tree classes +* Changed: events must now explicitly return false to stop execution. Before, + execution would be stopped by anything loosely evaluating to false. +* Changed: the getPropertiesForPath method now takes a different set of + arguments, and returns a different response. This allows plugin developers to + return statuses for properties other than 200 and 404. The hrefs are now also + always calculated relative to the baseUri, and not the uri of the request. +* Changed: generatePropFindResponse is renamed to generateMultiStatus, and now + takes a list of properties similar to the response of getPropertiesForPath. + This was also needed to improve flexibility for plugin development. +* Changed: Auth plugins are no longer included. They were not yet stable + quality, so they will probably be reintroduced in a later version. +* Changed: PROPPATCH also used generateMultiStatus now. +* Removed: unknownProperties event. This is replaced by the afterGetProperties + event, which should provide more flexibility. +* Fixed: Only calling getSize() on IFile instances in httpHead() +* Added: beforeBind event. This is invoked upon file or directory creation +* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an + existing resource. +* Added: beforeUnbind event. This is invoked right before deletion of any + resource. +* Added: afterGetProperties event. This event can be used to make modifications + to property responses. +* Added: beforeLock and beforeUnlock events. +* Added: afterBind event. +* Fixed: Copy and Move could fail in the root directory. This is now fixed. +* Added: Plugins can now be retrieved by their classname. This is useful for + inter-plugin communication. +* Added: The Auth backend can now return usernames and user-id's. +* Added: The Auth backend got a getUsers method +* Added: Sabre_DAV_FSExt_Directory now returns quota info + + +0.12.1-beta (2009-09-11) +------------------------ + +* Fixed: UNLOCK bug. Unlock didn't work at all + + +0.12-beta (2009-09-10) +---------------------- + +* Updated: Browser plugin now shows multiple {DAV:}resourcetype values if + available. +* Added: Experimental PDO backend for Locks Manager +* Fixed: Sending Content-Length: 0 for every empty response. This improves NGinx + compatibility. +* Fixed: Last modification time is reported in UTC timezone. This improves + Finder compatibility. + + +0.11-beta (2009-08-11) +---------------------- + +* Updated: Now in Beta +* Updated: Pear package no longer includes docs/ directory. These just contained + rfc's, which are publicly available. This reduces the package from ~800k to + ~60k +* Added: generatePropfindResponse now takes a baseUri argument +* Added: ResourceType property can now contain multiple resourcetypes. +* Fixed: Issue 13. + + +0.10-alpha (2009-08-03) +----------------------- + +* Added: Plugin to automatically map GET requests to non-files to PROPFIND + (Sabre_DAV_Browser_MapGetToPropFind). This should allow easier debugging of + complicated WebDAV setups. +* Added: Sabre_DAV_Property_Href class. For future use. +* Added: Ability to choose to use auth-int, auth or both for HTTP Digest + authentication. (Issue 11) +* Changed: Made more methods in Sabre_DAV_Server public. +* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests to + non-existent files. (Issue 12) +* Added: Central list of defined xml namespace prefixes. This can reduce + Bandwidth and legibility for xml bodies with user-defined namespaces. +* Added: now a PEAR-compatible package again, thanks to Michael Gauthier +* Changed: moved default copy and move logic from ObjectTree to Tree class + +0.9a-alpha (2009-07-21) +---------------------- + +* Fixed: Broken release + +0.9-alpha (2009-07-21) +---------------------- + +* Changed: Major refactoring, removed most of the logic from the Tree objects. + The Server class now directly works with the INode, IFile and IDirectory + objects. If you created your own Tree objects, this will most likely break in + this release. +* Changed: Moved all the Locking logic from the Tree and Server classes into a + separate plugin. +* Changed: TemporaryFileFilter is now a plugin. +* Added: Comes with an autoloader script. This can be used instead of the + includer script, and is preferred by some people. +* Added: AWS Authentication class. +* Added: simpleserversetup.py script. This will quickly get a fileserver up and + running. +* Added: When subscribing to events, it is now possible to supply a priority. + This is for example needed to ensure that the Authentication Plugin is used + before any other Plugin. +* Added: 22 new tests. +* Added: Users-manager plugin for .htdigest files. Experimental and subject to + change. +* Added: RFC 2324 HTTP 418 status code +* Fixed: Exclusive locks could in some cases be picked up as shared locks +* Fixed: Digest auth for non-apache servers had a bug (still not actually tested + this well). + + +0.8-alpha (2009-05-30) +---------------------- + +* Changed: Renamed all exceptions! This is a compatibility break. Every + Exception now follows Sabre_DAV_Exception_FileNotFound convention instead of + Sabre_DAV_FileNotFoundException. +* Added: Browser plugin now allows uploading and creating directories straight + from the browser. +* Added: 12 more unittests +* Fixed: Locking bug, which became prevalent on Windows Vista. +* Fixed: Netdrive support +* Fixed: TemporaryFileFilter filtered out too many files. Fixed some of the + regexes. +* Fixed: Added README and ChangeLog to package + + +0.7-alpha (2009-03-29) +---------------------- + +* Added: System to return complex properties from PROPFIND. +* Added: support for {DAV:}supportedlock. +* Added: support for {DAV:}lockdiscovery. +* Added: 6 new tests. +* Added: New plugin system. +* Added: Simple HTML directory plugin, for browser access. +* Added: Server class now sends back standard pre-condition error xml bodies. + This was new since RFC4918. +* Added: Sabre_DAV_Tree_Aggregate, which can 'host' multiple Tree objects into + one. +* Added: simple basis for HTTP REPORT method. This method is not used yet, but + can be used by plugins to add reports. +* Changed: ->getSize is only called for files, no longer for collections. r303 +* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter +* Changed: Sabre_DAV_TemporaryFileFilter is now called + Sabre_DAV_Tree_TemporaryFileFilter. +* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server + class, and using a public property instead. +* Fixed: bug related to parsing proppatch and propfind requests. Didn't show up + in most clients, but it needed fixing regardless. (r255) +* Fixed: auth-int is now properly supported within HTTP Digest. +* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918 sec + 8.2. +* Fixed: TemporaryFileFilter now lets through GET's if they actually exist on + the backend. (r274) +* Fixed: Some methods didn't get passed through in the FilterTree (r283). +* Fixed: LockManager is now slightly more complex, Tree classes slightly less. + (r287) + + +0.6-alpha (2009-02-16) +---------------------- + +* Added: Now uses streams for files, instead of strings. This means it won't + require to hold entire files in memory, which can be an issue if you're + dealing with big files. Note that this breaks compatibility for put() and + createFile methods. +* Added: HTTP Digest Authentication helper class. +* Added: Support for HTTP Range header +* Added: Support for ETags within If: headers +* Added: The API can now return ETags and override the default Content-Type +* Added: starting with basic framework for unittesting, using PHPUnit. +* Added: 49 unittests. +* Added: Abstraction for the HTTP request. +* Updated: Using Clark Notation for tags in properties. This means tags are + serialized as {namespace}tagName instead of namespace#tagName +* Fixed: HTTP_BasicAuth class now works as expected. +* Fixed: DAV_Server uses / for a default baseUrl. +* Fixed: Last modification date is no longer ignored in PROPFIND. +* Fixed: PROPFIND now sends back information about the requestUri even when + "Depth: 1" is specified. + + +0.5-alpha (2009-01-14) +---------------------- + +* Added: Added a very simple example for implementing a mapping to PHP file + streams. This should allow easy implementation of for example a WebDAV to FTP + proxy. +* Added: HTTP Basic Authentication helper class. +* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and will be + a start towards the creating of a testing framework. +* Updated: Backwards compatibility break: all require_once() statements are + removed from all the files. It is now recommended to use autoloading of + classes, or just including lib/Sabre.includes.php. This fix was made to allow + easier integration into applications not using this standard inclusion model. +* Updated: Better in-file documentation. +* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager. +* Updated: Fixes a shared-lock bug. +* Updated: Removed ?> from the bottom of each php file. +* Updated: Split up some operations from Sabre_DAV_Server to + Sabre_HTTP_Response. +* Fixed: examples are now actually included in the pear package. + + +0.4-alpha (2008-11-05) +---------------------- + +* Passes all litmus tests! +* Added: more examples +* Added: Custom property support +* Added: Shared lock support +* Added: Depth support to locks +* Added: Locking on unmapped urls (non-existent nodes) +* Fixed: Advertising as WebDAV class 3 support + + +0.3-alpha (2008-06-29) +---------------------- + +* Fully working in MS Windows clients. +* Added: temporary file filter: support for smultron files. +* Added: Phing build scripts +* Added: PEAR package +* Fixed: MOVE bug identified using finder. +* Fixed: Using gzuncompress instead of gzdecode in the temporary file filter. + This seems more common. + + +0.2-alpha (2008-05-27) +---------------------- + +* Somewhat working in Windows clients +* Added: Working PROPPATCH method (doesn't support custom properties yet) +* Added: Temporary filename handling system +* Added: Sabre_DAV_IQuota to return quota information +* Added: PROPFIND now reads the request body and only supplies the requested + properties + + +0.1-alpha (2008-04-04) +---------------------- + +* First release! +* Passes litmus: basic, http and copymove test. +* Fully working in Finder and DavFS2. + +Project started: 2007-12-13 + +[vobj]: http://sabre.io/vobject/ +[evnt]: http://sabre.io/event/ +[http]: http://sabre.io/http/ +[uri]: http://sabre.io/uri/ +[xml]: http://sabre.io/xml/ +[mi20]: http://sabre.io/dav/upgrade/1.8-to-2.0/ +[rfc6638]: http://tools.ietf.org/html/rfc6638 "CalDAV Scheduling" +[rfc7240]: http://tools.ietf.org/html/rfc7240 +[calendar-availability]: https://tools.ietf.org/html/draft-daboo-calendar-availability-05 diff --git a/libs/composer/vendor/sabre/dav/CONTRIBUTING.md b/libs/composer/vendor/sabre/dav/CONTRIBUTING.md new file mode 100644 index 000000000000..425ee19ba85b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/CONTRIBUTING.md @@ -0,0 +1,87 @@ +Contributing to sabre projects +============================== + +Want to contribute to sabre/dav? Here are some guidelines to ensure your patch +gets accepted. + + +Building a new feature? Contact us first +---------------------------------------- + +We may not want to accept every feature that comes our way. Sometimes +features are out of scope for our projects. + +We don't want to waste your time, so by having a quick chat with us first, +you may find out quickly if the feature makes sense to us, and we can give +some tips on how to best build the feature. + +If we don't accept the feature, it could be for a number of reasons. For +instance, we've rejected features in the past because we felt uncomfortable +assuming responsibility for maintaining the feature. + +In those cases, it's often possible to keep the feature separate from the +sabre projects. sabre/dav for instance has a plugin system, and there's no +reason the feature can't live in a project you own. + +In that case, definitely let us know about your plugin as well, so we can +feature it on [sabre.io][4]. + +We are often on [IRC][5], in the #sabredav channel on freenode. If there's +no one there, post a message on the [mailing list][6]. + + +Coding standards +---------------- + +sabre projects follow: + +1. [PSR-1][1] +2. [PSR-4][2] + +sabre projects don't follow [PSR-2][3]. + +In addition to that, here's a list of basic rules: + +1. PHP 5.4 array syntax must be used every where. This means you use `[` and + `]` instead of `array(` and `)`. +2. Use PHP namespaces everywhere. +3. Use 4 spaces for indentation. +4. Try to keep your lines under 80 characters. This is not a hard rule, as + there are many places in the source where it felt more sensibile to not + do so. In particular, function declarations are never split over multiple + lines. +5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`, + `function`, etc. they belong to. +6. `public` must be omitted from method declarations. It must also be omitted + for static properties. +7. All files should use unix-line endings (`\n`). +8. Files must omit the closing php tag (`?>`). +9. `true`, `false` and `null` are always lower-case. +10. Constants are always upper-case. +11. Any of the rules stated before may be broken where this is the pragmatic + thing to do. + + +Unit test requirements +---------------------- + +Any new feature or change requires unittests. We use [PHPUnit][7] for all our +tests. + +Adding unittests will greatly increase the likelyhood of us quickly accepting +your pull request. If unittests are not included though for whatever reason, +we'd still _love_ your pull request. + +We may have to write the tests ourselves, which can increase the time it takes +to accept the patch, but we'd still really like your contribution! + +To run the testsuite jump into the directory `cd tests` and trigger `phpunit`. +Make sure you did a `composer install` beforehand. + +[1]: http://www.php-fig.org/psr/psr-1/ +[2]: http://www.php-fig.org/psr/psr-4/ +[3]: http://www.php-fig.org/psr/psr-2/ +[4]: http://sabre.io/ +[5]: irc://freenode.net/#sabredav +[6]: http://groups.google.com/group/sabredav-discuss +[7]: http://phpunit.de/ diff --git a/libs/composer/vendor/sabre/dav/LICENSE b/libs/composer/vendor/sabre/dav/LICENSE new file mode 100644 index 000000000000..fd3539e33c6f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/). + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of SabreDAV nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/dav/README.md b/libs/composer/vendor/sabre/dav/README.md new file mode 100644 index 000000000000..86a0fe9a6fc7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/README.md @@ -0,0 +1,38 @@ +![sabre's logo](http://sabre.io/img/logo.png) sabre/dav +======================================================= + +Introduction +------------ + +sabre/dav is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers. + +Full documentation can be found on the website: + +http://sabre.io/ + + +Build status +------------ + +| branch | status | minimum PHP version | +| ------------ | ------ | ------------------- | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 | +| 3.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 | +| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 | +| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 | +| 1.8 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.8)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 | +| 1.7 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.7)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 | +| 1.6 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.6)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 | + +Documentation +------------- + +* [Introduction](http://sabre.io/dav/). +* [Installation](http://sabre.io/dav/install/). + + +Made at fruux +------------- + +SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/libs/composer/vendor/sabre/dav/bin/build.php b/libs/composer/vendor/sabre/dav/bin/build.php new file mode 100755 index 000000000000..c4ba20941618 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/build.php @@ -0,0 +1,177 @@ +#!/usr/bin/env php + [ + 'init', 'test', 'clean', + ], + 'markrelease' => [ + 'init', 'test', 'clean', + ], + 'clean' => [], + 'test' => [ + 'composerupdate', + ], + 'init' => [], + 'composerupdate' => [], + ]; + +$default = 'buildzip'; + +$baseDir = __DIR__ . '/../'; +chdir($baseDir); + +$currentTask = $default; +if ($argc > 1) $currentTask = $argv[1]; +$version = null; +if ($argc > 2) $version = $argv[2]; + +if (!isset($tasks[$currentTask])) { + echo "Task not found: ", $currentTask, "\n"; + die(1); +} + +// Creating the dependency graph +$newTaskList = []; +$oldTaskList = [$currentTask => true]; + +while (count($oldTaskList) > 0) { + + foreach ($oldTaskList as $task => $foo) { + + if (!isset($tasks[$task])) { + echo "Dependency not found: " . $task, "\n"; + die(1); + } + $dependencies = $tasks[$task]; + + $fullFilled = true; + foreach ($dependencies as $dependency) { + if (isset($newTaskList[$dependency])) { + // Already in the fulfilled task list. + continue; + } else { + $oldTaskList[$dependency] = true; + $fullFilled = false; + } + + } + if ($fullFilled) { + unset($oldTaskList[$task]); + $newTaskList[$task] = 1; + } + + } + +} + +foreach (array_keys($newTaskList) as $task) { + + echo "task: " . $task, "\n"; + call_user_func($task); + echo "\n"; + +} + +function init() { + + global $version; + if (!$version) { + include __DIR__ . '/../vendor/autoload.php'; + $version = Sabre\DAV\Version::VERSION; + } + + echo " Building sabre/dav " . $version, "\n"; + +} + +function clean() { + + global $baseDir; + echo " Removing build files\n"; + $outputDir = $baseDir . '/build/SabreDAV'; + if (is_dir($outputDir)) { + system('rm -r ' . $baseDir . '/build/SabreDAV'); + } + +} + +function composerupdate() { + + global $baseDir; + echo " Updating composer packages to latest version\n\n"; + system('cd ' . $baseDir . '; composer update'); +} + +function test() { + + global $baseDir; + + echo " Running all unittests.\n"; + echo " This may take a while.\n\n"; + system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml.dist --stop-on-failure', $code); + if ($code != 0) { + echo "PHPUnit reported error code $code\n"; + die(1); + } + +} + +function buildzip() { + + global $baseDir, $version; + echo " Generating composer.json\n"; + + $input = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true); + $newComposer = [ + "require" => $input['require'], + "config" => [ + "bin-dir" => "./bin", + ], + "prefer-stable" => true, + "minimum-stability" => "alpha", + ]; + unset( + $newComposer['require']['sabre/vobject'], + $newComposer['require']['sabre/http'], + $newComposer['require']['sabre/uri'], + $newComposer['require']['sabre/event'] + ); + $newComposer['require']['sabre/dav'] = $version; + mkdir('build/SabreDAV'); + file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT)); + + echo " Downloading dependencies\n"; + system("cd build/SabreDAV; composer install -n", $code); + if ($code !== 0) { + echo "Composer reported error code $code\n"; + die(1); + } + + echo " Removing pointless files\n"; + unlink('build/SabreDAV/composer.json'); + unlink('build/SabreDAV/composer.lock'); + + echo " Moving important files to the root of the project\n"; + + $fileNames = [ + 'CHANGELOG.md', + 'LICENSE', + 'README.md', + 'examples', + ]; + foreach ($fileNames as $fileName) { + echo " $fileName\n"; + rename('build/SabreDAV/vendor/sabre/dav/' . $fileName, 'build/SabreDAV/' . $fileName); + } + + // + + echo "\n"; + echo "Zipping the sabredav distribution\n\n"; + system('cd build; zip -qr sabredav-' . $version . '.zip SabreDAV'); + + echo "Done."; + +} diff --git a/libs/composer/vendor/sabre/dav/bin/googlecode_upload.py b/libs/composer/vendor/sabre/dav/bin/googlecode_upload.py new file mode 100755 index 000000000000..caafd5dedac4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/googlecode_upload.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# +# Copyright 2006, 2007 Google Inc. All Rights Reserved. +# Author: danderson@google.com (David Anderson) +# +# Script for uploading files to a Google Code project. +# +# This is intended to be both a useful script for people who want to +# streamline project uploads and a reference implementation for +# uploading files to Google Code projects. +# +# To upload a file to Google Code, you need to provide a path to the +# file on your local machine, a small summary of what the file is, a +# project name, and a valid account that is a member or owner of that +# project. You can optionally provide a list of labels that apply to +# the file. The file will be uploaded under the same name that it has +# in your local filesystem (that is, the "basename" or last path +# component). Run the script with '--help' to get the exact syntax +# and available options. +# +# Note that the upload script requests that you enter your +# googlecode.com password. This is NOT your Gmail account password! +# This is the password you use on googlecode.com for committing to +# Subversion and uploading files. You can find your password by going +# to http://code.google.com/hosting/settings when logged in with your +# Gmail account. If you have already committed to your project's +# Subversion repository, the script will automatically retrieve your +# credentials from there (unless disabled, see the output of '--help' +# for details). +# +# If you are looking at this script as a reference for implementing +# your own Google Code file uploader, then you should take a look at +# the upload() function, which is the meat of the uploader. You +# basically need to build a multipart/form-data POST request with the +# right fields and send it to https://PROJECT.googlecode.com/files . +# Authenticate the request using HTTP Basic authentication, as is +# shown below. +# +# Licensed under the terms of the Apache Software License 2.0: +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Questions, comments, feature requests and patches are most welcome. +# Please direct all of these to the Google Code users group: +# http://groups.google.com/group/google-code-hosting + +"""Google Code file uploader script. +""" + +__author__ = 'danderson@google.com (David Anderson)' + +import httplib +import os.path +import optparse +import getpass +import base64 +import sys + + +def upload(file, project_name, user_name, password, summary, labels=None): + """Upload a file to a Google Code project's file server. + + Args: + file: The local path to the file. + project_name: The name of your project on Google Code. + user_name: Your Google account name. + password: The googlecode.com password for your account. + Note that this is NOT your global Google Account password! + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + + Returns: a tuple: + http_status: 201 if the upload succeeded, something else if an + error occurred. + http_reason: The human-readable string associated with http_status + file_url: If the upload succeeded, the URL of the file on Google + Code, None otherwise. + """ + # The login is the user part of user@gmail.com. If the login provided + # is in the full user@domain form, strip it down. + if user_name.endswith('@gmail.com'): + user_name = user_name[:user_name.index('@gmail.com')] + + form_fields = [('summary', summary)] + if labels is not None: + form_fields.extend([('label', l.strip()) for l in labels]) + + content_type, body = encode_upload_request(form_fields, file) + + upload_host = '%s.googlecode.com' % project_name + upload_uri = '/files' + auth_token = base64.b64encode('%s:%s'% (user_name, password)) + headers = { + 'Authorization': 'Basic %s' % auth_token, + 'User-Agent': 'Googlecode.com uploader v0.9.4', + 'Content-Type': content_type, + } + + server = httplib.HTTPSConnection(upload_host) + server.request('POST', upload_uri, body, headers) + resp = server.getresponse() + server.close() + + if resp.status == 201: + location = resp.getheader('Location', None) + else: + location = None + return resp.status, resp.reason, location + + +def encode_upload_request(fields, file_path): + """Encode the given fields and file into a multipart form body. + + fields is a sequence of (name, value) pairs. file is the path of + the file to upload. The file will be uploaded to Google Code with + the same file name. + + Returns: (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' + CRLF = '\r\n' + + body = [] + + # Add the metadata about the upload first + for key, value in fields: + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="%s"' % key, + '', + value, + ]) + + # Now add the file itself + file_name = os.path.basename(file_path) + f = open(file_path, 'rb') + file_content = f.read() + f.close() + + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="filename"; filename="%s"' + % file_name, + # The upload server determines the mime-type, no need to set it. + 'Content-Type: application/octet-stream', + '', + file_content, + ]) + + # Finalize the form body + body.extend(['--' + BOUNDARY + '--', '']) + + return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) + + +def upload_find_auth(file_path, project_name, summary, labels=None, + user_name=None, password=None, tries=3): + """Find credentials and upload a file to a Google Code project's file server. + + file_path, project_name, summary, and labels are passed as-is to upload. + + Args: + file_path: The local path to the file. + project_name: The name of your project on Google Code. + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + config_dir: Path to Subversion configuration directory, 'none', or None. + user_name: Your Google account name. + tries: How many attempts to make. + """ + + while tries > 0: + if user_name is None: + # Read username if not specified or loaded from svn config, or on + # subsequent tries. + sys.stdout.write('Please enter your googlecode.com username: ') + sys.stdout.flush() + user_name = sys.stdin.readline().rstrip() + if password is None: + # Read password if not loaded from svn config, or on subsequent tries. + print 'Please enter your googlecode.com password.' + print '** Note that this is NOT your Gmail account password! **' + print 'It is the password you use to access Subversion repositories,' + print 'and can be found here: http://code.google.com/hosting/settings' + password = getpass.getpass() + + status, reason, url = upload(file_path, project_name, user_name, password, + summary, labels) + # Returns 403 Forbidden instead of 401 Unauthorized for bad + # credentials as of 2007-07-17. + if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]: + # Rest for another try. + user_name = password = None + tries = tries - 1 + else: + # We're done. + break + + return status, reason, url + + +def main(): + parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY ' + '-p PROJECT [options] FILE') + parser.add_option('-s', '--summary', dest='summary', + help='Short description of the file') + parser.add_option('-p', '--project', dest='project', + help='Google Code project name') + parser.add_option('-u', '--user', dest='user', + help='Your Google Code username') + parser.add_option('-w', '--password', dest='password', + help='Your Google Code password') + parser.add_option('-l', '--labels', dest='labels', + help='An optional list of comma-separated labels to attach ' + 'to the file') + + options, args = parser.parse_args() + + if not options.summary: + parser.error('File summary is missing.') + elif not options.project: + parser.error('Project name is missing.') + elif len(args) < 1: + parser.error('File to upload not provided.') + elif len(args) > 1: + parser.error('Only one file may be specified.') + + file_path = args[0] + + if options.labels: + labels = options.labels.split(',') + else: + labels = None + + status, reason, url = upload_find_auth(file_path, options.project, + options.summary, labels, + options.user, options.password) + if url: + print 'The file was uploaded successfully.' + print 'URL: %s' % url + return 0 + else: + print 'An error occurred. Your file was not uploaded.' + print 'Google Code upload server said: %s (%s)' % (reason, status) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libs/composer/vendor/sabre/dav/bin/migrateto20.php b/libs/composer/vendor/sabre/dav/bin/migrateto20.php new file mode 100755 index 000000000000..77236804f354 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/migrateto20.php @@ -0,0 +1,453 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch ($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +foreach (['calendar', 'addressbook'] as $itemType) { + + $tableName = $itemType . 's'; + $tableNameOld = $tableName . '_old'; + $changesTable = $itemType . 'changes'; + + echo "Upgrading '$tableName'\n"; + + // The only cross-db way to do this, is to just fetch a single record. + $row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch(); + + if (!$row) { + + echo "No records were found in the '$tableName' table.\n"; + echo "\n"; + echo "We're going to rename the old table to $tableNameOld (just in case).\n"; + echo "and re-create the new table.\n"; + + switch ($driver) { + + case 'mysql' : + $pdo->exec("RENAME TABLE $tableName TO $tableNameOld"); + switch ($itemType) { + case 'calendar' : + $pdo->exec(" + CREATE TABLE calendars ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(100), + displayname VARCHAR(100), + uri VARCHAR(200), + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + description TEXT, + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARCHAR(10), + timezone TEXT, + components VARCHAR(20), + transparent TINYINT(1) NOT NULL DEFAULT '0', + UNIQUE(principaluri, uri) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + "); + break; + case 'addressbook' : + $pdo->exec(" + CREATE TABLE addressbooks ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(255), + displayname VARCHAR(255), + uri VARCHAR(200), + description TEXT, + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + UNIQUE(principaluri, uri) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + "); + break; + } + break; + + case 'sqlite' : + + $pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld"); + + switch ($itemType) { + case 'calendar' : + $pdo->exec(" + CREATE TABLE calendars ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + synctoken integer, + description text, + calendarorder integer, + calendarcolor text, + timezone text, + components text, + transparent bool + ); + "); + break; + case 'addressbook' : + $pdo->exec(" + CREATE TABLE addressbooks ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + description text, + synctoken integer + ); + "); + + break; + } + break; + + } + echo "Creation of 2.0 $tableName table is complete\n"; + + } else { + + // Checking if there's a synctoken field already. + if (array_key_exists('synctoken', $row)) { + echo "The 'synctoken' field already exists in the $tableName table.\n"; + echo "It's likely you already upgraded, so we're simply leaving\n"; + echo "the $tableName table alone\n"; + } else { + + echo "1.8 table schema detected\n"; + switch ($driver) { + + case 'mysql' : + $pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'"); + $pdo->exec("ALTER TABLE $tableName DROP ctag"); + $pdo->exec("UPDATE $tableName SET synctoken = '1'"); + break; + case 'sqlite' : + $pdo->exec("ALTER TABLE $tableName ADD synctoken integer"); + $pdo->exec("UPDATE $tableName SET synctoken = '1'"); + echo "Note: there's no easy way to remove fields in sqlite.\n"; + echo "The ctag field is no longer used, but it's kept in place\n"; + break; + + } + + echo "Upgraded '$tableName' to 2.0 schema.\n"; + + } + + } + + try { + $pdo->query("SELECT * FROM $changesTable LIMIT 1"); + + echo "'$changesTable' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + + } catch (Exception $e) { + echo "Creating '$changesTable' table.\n"; + + switch ($driver) { + + case 'mysql' : + $pdo->exec(" + CREATE TABLE $changesTable ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARCHAR(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + {$itemType}id INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX {$itemType}id_synctoken ({$itemType}id, synctoken) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + "); + break; + case 'sqlite' : + $pdo->exec(" + + CREATE TABLE $changesTable ( + id integer primary key asc, + uri text, + synctoken integer, + {$itemType}id integer, + operation bool + ); + + "); + $pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);"); + break; + + } + + } + +} + +try { + $pdo->query("SELECT * FROM calendarsubscriptions LIMIT 1"); + + echo "'calendarsubscriptions' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + +} catch (Exception $e) { + echo "Creating calendarsubscriptions table.\n"; + + switch ($driver) { + + case 'mysql' : + $pdo->exec(" +CREATE TABLE calendarsubscriptions ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARCHAR(200) NOT NULL, + principaluri VARCHAR(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARCHAR(10), + striptodos TINYINT(1) NULL, + stripalarms TINYINT(1) NULL, + stripattachments TINYINT(1) NULL, + lastmodified INT(11) UNSIGNED, + UNIQUE(principaluri, uri) +); + "); + break; + case 'sqlite' : + $pdo->exec(" + +CREATE TABLE calendarsubscriptions ( + id integer primary key asc, + uri text, + principaluri text, + source text, + displayname text, + refreshrate text, + calendarorder integer, + calendarcolor text, + striptodos bool, + stripalarms bool, + stripattachments bool, + lastmodified int +); + "); + + $pdo->exec("CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);"); + break; + + } + +} + +try { + $pdo->query("SELECT * FROM propertystorage LIMIT 1"); + + echo "'propertystorage' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + +} catch (Exception $e) { + echo "Creating propertystorage table.\n"; + + switch ($driver) { + + case 'mysql' : + $pdo->exec(" +CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + value MEDIUMBLOB +); + "); + $pdo->exec(" +CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100)); + "); + break; + case 'sqlite' : + $pdo->exec(" +CREATE TABLE propertystorage ( + id integer primary key asc, + path TEXT, + name TEXT, + value TEXT +); + "); + $pdo->exec(" +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); + "); + + break; + + } + +} + +echo "Upgrading cards table to 2.0 schema\n"; + +try { + + $create = false; + $row = $pdo->query("SELECT * FROM cards LIMIT 1")->fetch(); + if (!$row) { + $random = mt_rand(1000, 9999); + echo "There was no data in the cards table, so we're re-creating it\n"; + echo "The old table will be renamed to cards_old$random, just in case.\n"; + + $create = true; + + switch ($driver) { + case 'mysql' : + $pdo->exec("RENAME TABLE cards TO cards_old$random"); + break; + case 'sqlite' : + $pdo->exec("ALTER TABLE cards RENAME TO cards_old$random"); + break; + + } + } + +} catch (Exception $e) { + + echo "Exception while checking cards table. Assuming that the table does not yet exist.\n"; + echo "Debug: ", $e->getMessage(), "\n"; + $create = true; + +} + +if ($create) { + switch ($driver) { + case 'mysql' : + $pdo->exec(" +CREATE TABLE cards ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + addressbookid INT(11) UNSIGNED NOT NULL, + carddata MEDIUMBLOB, + uri VARCHAR(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + "); + break; + + case 'sqlite' : + + $pdo->exec(" +CREATE TABLE cards ( + id integer primary key asc, + addressbookid integer, + carddata blob, + uri text, + lastmodified integer, + etag text, + size integer +); + "); + break; + + } +} else { + switch ($driver) { + case 'mysql' : + $pdo->exec(" + ALTER TABLE cards + ADD etag VARBINARY(32), + ADD size INT(11) UNSIGNED NOT NULL; + "); + break; + + case 'sqlite' : + + $pdo->exec(" + ALTER TABLE cards ADD etag text; + ALTER TABLE cards ADD size integer; + "); + break; + + } + echo "Reading all old vcards and populating etag and size fields.\n"; + $result = $pdo->query('SELECT id, carddata FROM cards'); + $stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?'); + while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + $stmt->execute([ + md5($row['carddata']), + strlen($row['carddata']), + $row['id'] + ]); + } + + +} + +echo "Upgrade to 2.0 schema completed.\n"; diff --git a/libs/composer/vendor/sabre/dav/bin/migrateto21.php b/libs/composer/vendor/sabre/dav/bin/migrateto21.php new file mode 100755 index 000000000000..c81ee5cca1a9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/migrateto21.php @@ -0,0 +1,176 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch ($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +echo "Upgrading 'calendarobjects'\n"; +$addUid = false; +try { + $result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1'); + $row = $result->fetch(\PDO::FETCH_ASSOC); + + if (!$row) { + echo "No data in table. Going to try to add the uid field anyway.\n"; + $addUid = true; + } elseif (array_key_exists('uid', $row)) { + echo "uid field exists. Assuming that this part of the migration has\n"; + echo "Already been completed.\n"; + } else { + echo "2.0 schema detected.\n"; + $addUid = true; + } + +} catch (Exception $e) { + echo "Could not find a calendarobjects table. Skipping this part of the\n"; + echo "upgrade.\n"; +} + +if ($addUid) { + + switch ($driver) { + case 'mysql' : + $pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT'); + break; + } + + $result = $pdo->query('SELECT id, calendardata FROM calendarobjects'); + $stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?'); + $counter = 0; + + while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + try { + $vobj = \Sabre\VObject\Reader::read($row['calendardata']); + } catch (\Exception $e) { + echo "Warning! Item with id $row[id] could not be parsed!\n"; + continue; + } + $uid = null; + $item = $vobj->getBaseComponent(); + if (!isset($item->UID)) { + echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n"; + continue; + } + $uid = (string)$item->UID; + $stmt->execute([$uid, $row['id']]); + $counter++; + + } + +} + +echo "Creating 'schedulingobjects'\n"; + +switch ($driver) { + + case 'mysql' : + $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects +( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(255), + calendardata MEDIUMBLOB, + uri VARCHAR(200), + lastmodified INT(11) UNSIGNED, + etag VARCHAR(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + '); + break; + + + case 'sqlite' : + $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects ( + id integer primary key asc, + principaluri text, + calendardata blob, + uri text, + lastmodified integer, + etag text, + size integer +) +'); + break; +} + +echo "Done.\n"; + +echo "Upgrade to 2.1 schema completed.\n"; diff --git a/libs/composer/vendor/sabre/dav/bin/migrateto30.php b/libs/composer/vendor/sabre/dav/bin/migrateto30.php new file mode 100755 index 000000000000..9ca77c13c3cf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/migrateto30.php @@ -0,0 +1,171 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch ($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +echo "Upgrading 'propertystorage'\n"; +$addValueType = false; +try { + $result = $pdo->query('SELECT * FROM propertystorage LIMIT 1'); + $row = $result->fetch(\PDO::FETCH_ASSOC); + + if (!$row) { + echo "No data in table. Going to re-create the table.\n"; + $random = mt_rand(1000, 9999); + echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n"; + + switch ($driver) { + + case 'mysql' : + $pdo->exec('RENAME TABLE propertystorage TO propertystorage_old' . $random); + $pdo->exec(' + CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + valuetype INT UNSIGNED, + value MEDIUMBLOB + ); + '); + $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path(600), name(100));'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old' . $random); + $pdo->exec(' +CREATE TABLE propertystorage ( + id integer primary key asc, + path text, + name text, + valuetype integer, + value blob +);'); + + $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path, name);'); + break; + + } + } elseif (array_key_exists('valuetype', $row)) { + echo "valuetype field exists. Assuming that this part of the migration has\n"; + echo "Already been completed.\n"; + } else { + echo "2.1 schema detected. Going to perform upgrade.\n"; + $addValueType = true; + } + +} catch (Exception $e) { + echo "Could not find a propertystorage table. Skipping this part of the\n"; + echo "upgrade.\n"; + echo $e->getMessage(), "\n"; +} + +if ($addValueType) { + + switch ($driver) { + case 'mysql' : + $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT'); + + break; + } + + $pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL '); + +} + +echo "Migrating vcardurl\n"; + +$result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL'); +$stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)'); + +while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + // Inserting the new record + $stmt1->execute([ + 'addressbooks/' . basename($row['uri']), + '{http://calendarserver.org/ns/}me-card', + serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl'])) + ]); + + echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl'])); + +} + +echo "Done.\n"; +echo "Upgrade to 3.0 schema completed.\n"; diff --git a/libs/composer/vendor/sabre/dav/bin/migrateto32.php b/libs/composer/vendor/sabre/dav/bin/migrateto32.php new file mode 100755 index 000000000000..7567aeb60f74 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/migrateto32.php @@ -0,0 +1,268 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch ($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +echo "Creating 'calendarinstances'\n"; +$addValueType = false; +try { + $result = $pdo->query('SELECT * FROM calendarinstances LIMIT 1'); + $result->fetch(\PDO::FETCH_ASSOC); + echo "calendarinstances exists. Assuming this part of the migration has already been done.\n"; +} catch (Exception $e) { + echo "calendarinstances does not yet exist. Creating table and migrating data.\n"; + + switch ($driver) { + case 'mysql' : + $pdo->exec(<<exec(" +INSERT INTO calendarinstances + ( + calendarid, + principaluri, + access, + displayname, + uri, + description, + calendarorder, + calendarcolor, + transparent + ) +SELECT + id, + principaluri, + 1, + displayname, + uri, + description, + calendarorder, + calendarcolor, + transparent +FROM calendars +"); + break; + case 'sqlite' : + $pdo->exec(<<exec(" +INSERT INTO calendarinstances + ( + calendarid, + principaluri, + access, + displayname, + uri, + description, + calendarorder, + calendarcolor, + transparent + ) +SELECT + id, + principaluri, + 1, + displayname, + uri, + description, + calendarorder, + calendarcolor, + transparent +FROM calendars +"); + break; + } + +} +try { + $result = $pdo->query('SELECT * FROM calendars LIMIT 1'); + $row = $result->fetch(\PDO::FETCH_ASSOC); + + if (!$row) { + echo "Source table is empty.\n"; + $migrateCalendars = true; + } + + $columnCount = count($row); + if ($columnCount === 3) { + echo "The calendars table has 3 columns already. Assuming this part of the migration was already done.\n"; + $migrateCalendars = false; + } else { + echo "The calendars table has " . $columnCount . " columns.\n"; + $migrateCalendars = true; + } + +} catch (Exception $e) { + echo "calendars table does not exist. This is a major problem. Exiting.\n"; + exit(-1); +} + +if ($migrateCalendars) { + + $calendarBackup = 'calendars_3_1_' . $backupPostfix; + echo "Backing up 'calendars' to '", $calendarBackup, "'\n"; + + switch ($driver) { + case 'mysql' : + $pdo->exec('RENAME TABLE calendars TO ' . $calendarBackup); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE calendars RENAME TO ' . $calendarBackup); + break; + + } + + echo "Creating new calendars table.\n"; + switch ($driver) { + case 'mysql' : + $pdo->exec(<<exec(<<exec(<<0): + print "Bytes to go before we hit threshold:", bytes + else: + print "Threshold exceeded with:", -bytes, "bytes" + dir = os.listdir(cacheDir) + dir2 = [] + for file in dir: + path = cacheDir + '/' + file + dir2.append({ + "path" : path, + "atime": os.stat(path).st_atime, + "size" : os.stat(path).st_size + }) + + dir2.sort(lambda x,y: int(x["atime"]-y["atime"])) + + filesunlinked = 0 + gainedspace = 0 + + # Left is the amount of bytes that need to be freed up + # The default is the 'min_erase setting' + left = min_erase + + # If the min_erase setting is lower than the amount of bytes over + # the threshold, we use that number instead. + if left < -bytes : + left = -bytes + + print "Need to delete at least:", left; + + for file in dir2: + + # Only deleting files if we're not simulating + if not simulate: os.unlink(file["path"]) + left = int(left - file["size"]) + gainedspace = gainedspace + file["size"] + filesunlinked = filesunlinked + 1 + + if(left<0): + break + + print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace) + + + time.sleep(sleep) + + + +def main(): + parser = OptionParser( + version="naturalselection v0.3", + description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" + + "This utility is distributed alongside SabreDAV.", + usage="usage: %prog [options] cacheDirectory", + ) + parser.add_option( + '-s', + dest="simulate", + action="store_true", + help="Don't actually make changes, but just simulate the behaviour", + ) + parser.add_option( + '-r','--runs', + help="How many times to check before exiting. -1 is infinite, which is the default", + type="int", + dest="runs", + default=-1 + ) + parser.add_option( + '-n','--interval', + help="Sleep time in seconds (default = 5)", + type="int", + dest="sleep", + default=5 + ) + parser.add_option( + '-l','--threshold', + help="Threshold in bytes (default = 10737418240, which is 10GB)", + type="int", + dest="threshold", + default=10737418240 + ) + parser.add_option( + '-m', '--min-erase', + help="Minimum number of bytes to erase when the threshold is reached. " + + "Setting this option higher will reduce the number of times the cache directory will need to be scanned. " + + "(the default is 1073741824, which is 1GB.)", + type="int", + dest="min_erase", + default=1073741824 + ) + + options,args = parser.parse_args() + if len(args)<1: + parser.error("This utility requires at least 1 argument") + cacheDir = args[0] + + print "Natural Selection" + print "Cache directory:", cacheDir + free = getfreespace(cacheDir); + print "Current free disk space:", free + + runs = options.runs; + while runs!=0 : + run( + cacheDir, + sleep=options.sleep, + simulate=options.simulate, + threshold=options.threshold, + min_erase=options.min_erase + ) + if runs>0: + runs = runs - 1 + +if __name__ == '__main__' : + main() diff --git a/libs/composer/vendor/sabre/dav/bin/sabredav b/libs/composer/vendor/sabre/dav/bin/sabredav new file mode 100755 index 000000000000..032371ba8bc7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/sabredav @@ -0,0 +1,2 @@ +#!/bin/sh +php -S 0.0.0.0:8080 `dirname $0`/sabredav.php diff --git a/libs/composer/vendor/sabre/dav/bin/sabredav.php b/libs/composer/vendor/sabre/dav/bin/sabredav.php new file mode 100755 index 000000000000..950075d1af71 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/bin/sabredav.php @@ -0,0 +1,53 @@ +stream = fopen('php://stdout', 'w'); + + } + + function log($msg) { + fwrite($this->stream, $msg . "\n"); + } + +} + +$log = new CliLog(); + +if (php_sapi_name() !== 'cli-server') { + die("This script is intended to run on the built-in php webserver"); +} + +// Finding composer + + +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +use Sabre\DAV; + +// Root +$root = new DAV\FS\Directory(getcwd()); + +// Setting up server. +$server = new DAV\Server($root); + +// Browser plugin +$server->addPlugin(new DAV\Browser\Plugin()); + +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/composer.json b/libs/composer/vendor/sabre/dav/composer.json new file mode 100644 index 000000000000..fca0e07fbd45 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/composer.json @@ -0,0 +1,68 @@ +{ + "name": "sabre/dav", + "type": "library", + "description": "WebDAV Framework for PHP", + "keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"], + "homepage": "http://sabre.io/", + "license" : "BSD-3-Clause", + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + } + ], + "require": { + "php": ">=5.5.0", + "sabre/vobject": "^4.1.0", + "sabre/event" : ">=2.0.0, <4.0.0", + "sabre/xml" : "^1.4.0", + "sabre/http" : "^4.2.1", + "sabre/uri" : "^1.0.1", + "ext-dom": "*", + "ext-pcre": "*", + "ext-spl": "*", + "ext-simplexml": "*", + "ext-mbstring" : "*", + "ext-ctype" : "*", + "ext-date" : "*", + "ext-iconv" : "*", + "lib-libxml" : ">=2.7.0", + "psr/log": "^1.0" + }, + "require-dev" : { + "phpunit/phpunit" : "> 4.8, <6.0.0", + "evert/phpdoc-md" : "~0.1.0", + "sabre/cs" : "^1.0.0", + "monolog/monolog": "^1.18" + }, + "suggest" : { + "ext-curl" : "*", + "ext-pdo" : "*" + }, + "autoload": { + "psr-4" : { + "Sabre\\DAV\\" : "lib/DAV/", + "Sabre\\DAVACL\\" : "lib/DAVACL/", + "Sabre\\CalDAV\\" : "lib/CalDAV/", + "Sabre\\CardDAV\\" : "lib/CardDAV/" + } + }, + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-dav" + }, + "bin" : [ + "bin/sabredav", + "bin/naturalselection" + ], + "config" : { + "bin-dir" : "./bin" + }, + "extra" : { + "branch-alias": { + "dev-master": "3.1.0-dev" + } + } +} diff --git a/libs/composer/vendor/sabre/dav/examples/addressbookserver.php b/libs/composer/vendor/sabre/dav/examples/addressbookserver.php new file mode 100644 index 000000000000..dd3682397226 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/addressbookserver.php @@ -0,0 +1,57 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +//Mapping PHP errors to exceptions +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Autoloader +require_once 'vendor/autoload.php'; + +// Backends +$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo); +$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo); +$carddavBackend = new Sabre\CardDAV\Backend\PDO($pdo); +//$caldavBackend = new Sabre\CalDAV\Backend\PDO($pdo); + +// Setting up the directory tree // +$nodes = [ + new Sabre\DAVACL\PrincipalCollection($principalBackend), +// new Sabre\CalDAV\CalendarRoot($authBackend, $caldavBackend), + new Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend), +]; + +// The object tree needs in turn to be passed to the server class +$server = new Sabre\DAV\Server($nodes); +$server->setBaseUri($baseUri); + +// Plugins +$server->addPlugin(new Sabre\DAV\Auth\Plugin($authBackend)); +$server->addPlugin(new Sabre\DAV\Browser\Plugin()); +//$server->addPlugin(new Sabre\CalDAV\Plugin()); +$server->addPlugin(new Sabre\CardDAV\Plugin()); +$server->addPlugin(new Sabre\DAVACL\Plugin()); +$server->addPlugin(new Sabre\DAV\Sync\Plugin()); + +// And off we go! +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/examples/calendarserver.php b/libs/composer/vendor/sabre/dav/examples/calendarserver.php new file mode 100644 index 000000000000..c5dd0293b400 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/calendarserver.php @@ -0,0 +1,80 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +//Mapping PHP errors to exceptions +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Files we need +require_once 'vendor/autoload.php'; + +// Backends +$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo); +$calendarBackend = new Sabre\CalDAV\Backend\PDO($pdo); +$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo); + +// Directory structure +$tree = [ + new Sabre\CalDAV\Principal\Collection($principalBackend), + new Sabre\CalDAV\CalendarRoot($principalBackend, $calendarBackend), +]; + +$server = new Sabre\DAV\Server($tree); + +if (isset($baseUri)) + $server->setBaseUri($baseUri); + +/* Server Plugins */ +$authPlugin = new Sabre\DAV\Auth\Plugin($authBackend); +$server->addPlugin($authPlugin); + +$aclPlugin = new Sabre\DAVACL\Plugin(); +$server->addPlugin($aclPlugin); + +/* CalDAV support */ +$caldavPlugin = new Sabre\CalDAV\Plugin(); +$server->addPlugin($caldavPlugin); + +/* Calendar subscription support */ +$server->addPlugin( + new Sabre\CalDAV\Subscriptions\Plugin() +); + +/* Calendar scheduling support */ +$server->addPlugin( + new Sabre\CalDAV\Schedule\Plugin() +); + +/* WebDAV-Sync plugin */ +$server->addPlugin(new Sabre\DAV\Sync\Plugin()); + +/* CalDAV Sharing support */ +$server->addPlugin(new Sabre\DAV\Sharing\Plugin()); +$server->addPlugin(new Sabre\CalDAV\SharingPlugin()); + +// Support for html frontend +$browser = new Sabre\DAV\Browser\Plugin(); +$server->addPlugin($browser); + +// And off we go! +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/examples/fileserver.php b/libs/composer/vendor/sabre/dav/examples/fileserver.php new file mode 100644 index 000000000000..59a453f63c7d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/fileserver.php @@ -0,0 +1,56 @@ +setBaseUri($baseUri); + +// Support for LOCK and UNLOCK +$lockBackend = new \Sabre\DAV\Locks\Backend\File($tmpDir . '/locksdb'); +$lockPlugin = new \Sabre\DAV\Locks\Plugin($lockBackend); +$server->addPlugin($lockPlugin); + +// Support for html frontend +$browser = new \Sabre\DAV\Browser\Plugin(); +$server->addPlugin($browser); + +// Automatically guess (some) contenttypes, based on extesion +$server->addPlugin(new \Sabre\DAV\Browser\GuessContentType()); + +// Authentication backend +$authBackend = new \Sabre\DAV\Auth\Backend\File('.htdigest'); +$auth = new \Sabre\DAV\Auth\Plugin($authBackend); +$server->addPlugin($auth); + +// Temporary file filter +$tempFF = new \Sabre\DAV\TemporaryFileFilterPlugin($tmpDir); +$server->addPlugin($tempFF); + +// And off we go! +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/examples/groupwareserver.php b/libs/composer/vendor/sabre/dav/examples/groupwareserver.php new file mode 100644 index 000000000000..2e0af1e1f0dd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/groupwareserver.php @@ -0,0 +1,101 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +/** + * Mapping PHP errors to exceptions. + * + * While this is not strictly needed, it makes a lot of sense to do so. If an + * E_NOTICE or anything appears in your code, this allows SabreDAV to intercept + * the issue and send a proper response back to the client (HTTP/1.1 500). + */ +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Autoloader +require_once 'vendor/autoload.php'; + +/** + * The backends. Yes we do really need all of them. + * + * This allows any developer to subclass just any of them and hook into their + * own backend systems. + */ +$authBackend = new \Sabre\DAV\Auth\Backend\PDO($pdo); +$principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($pdo); +$carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); +$caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); + +/** + * The directory tree + * + * Basically this is an array which contains the 'top-level' directories in the + * WebDAV server. + */ +$nodes = [ + // /principals + new \Sabre\CalDAV\Principal\Collection($principalBackend), + // /calendars + new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend), + // /addressbook + new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend), +]; + +// The object tree needs in turn to be passed to the server class +$server = new \Sabre\DAV\Server($nodes); +if (isset($baseUri)) $server->setBaseUri($baseUri); + +// Plugins +$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend)); +$server->addPlugin(new \Sabre\DAV\Browser\Plugin()); +$server->addPlugin(new \Sabre\DAV\Sync\Plugin()); +$server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); +$server->addPlugin(new \Sabre\DAVACL\Plugin()); + +// CalDAV plugins +$server->addPlugin(new \Sabre\CalDAV\Plugin()); +$server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin()); +$server->addPlugin(new \Sabre\CalDAV\SharingPlugin()); +$server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); + +// CardDAV plugins +$server->addPlugin(new \Sabre\CardDAV\Plugin()); +$server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); + +// And off we go! +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/examples/minimal.php b/libs/composer/vendor/sabre/dav/examples/minimal.php new file mode 100644 index 000000000000..4ac2560646a1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/minimal.php @@ -0,0 +1,20 @@ +addPlugin( + new Sabre\DAV\Browser\Plugin() +); + +$server->exec(); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql new file mode 100644 index 000000000000..9ec88babecb6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.addressbooks.sql @@ -0,0 +1,28 @@ +CREATE TABLE addressbooks ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARBINARY(255), + displayname VARCHAR(255), + uri VARBINARY(200), + description TEXT, + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + UNIQUE(principaluri(100), uri(100)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE cards ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + addressbookid INT(11) UNSIGNED NOT NULL, + carddata MEDIUMBLOB, + uri VARBINARY(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE addressbookchanges ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + addressbookid INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX addressbookid_synctoken (addressbookid, synctoken) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.calendars.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.calendars.sql new file mode 100644 index 000000000000..21c5bcb44377 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.calendars.sql @@ -0,0 +1,76 @@ +CREATE TABLE calendarobjects ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + calendardata MEDIUMBLOB, + uri VARBINARY(200), + calendarid INTEGER UNSIGNED NOT NULL, + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL, + componenttype VARBINARY(8), + firstoccurence INT(11) UNSIGNED, + lastoccurence INT(11) UNSIGNED, + uid VARBINARY(200), + UNIQUE(calendarid, uri), + INDEX calendarid_time (calendarid, firstoccurence) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendars ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1', + components VARBINARY(21) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendarinstances ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + calendarid INTEGER UNSIGNED NOT NULL, + principaluri VARBINARY(100), + access TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1 = owner, 2 = read, 3 = readwrite', + displayname VARCHAR(100), + uri VARBINARY(200), + description TEXT, + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARBINARY(10), + timezone TEXT, + transparent TINYINT(1) NOT NULL DEFAULT '0', + share_href VARBINARY(100), + share_displayname VARCHAR(100), + share_invitestatus TINYINT(1) NOT NULL DEFAULT '2' COMMENT '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid', + UNIQUE(principaluri, uri), + UNIQUE(calendarid, principaluri), + UNIQUE(calendarid, share_href) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendarchanges ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + calendarid INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX calendarid_synctoken (calendarid, synctoken) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendarsubscriptions ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + principaluri VARBINARY(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARBINARY(10), + striptodos TINYINT(1) NULL, + stripalarms TINYINT(1) NULL, + stripattachments TINYINT(1) NULL, + lastmodified INT(11) UNSIGNED, + UNIQUE(principaluri, uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE schedulingobjects ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARBINARY(255), + calendardata MEDIUMBLOB, + uri VARBINARY(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.locks.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.locks.sql new file mode 100644 index 000000000000..96a3a88d95ce --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.locks.sql @@ -0,0 +1,12 @@ +CREATE TABLE locks ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + owner VARCHAR(100), + timeout INTEGER UNSIGNED, + created INTEGER, + token VARBINARY(100), + scope TINYINT, + depth TINYINT, + uri VARBINARY(1000), + INDEX(token), + INDEX(uri(100)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.principals.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.principals.sql new file mode 100644 index 000000000000..ea0d16a2739b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.principals.sql @@ -0,0 +1,20 @@ +CREATE TABLE principals ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + email VARBINARY(80), + displayname VARCHAR(80), + UNIQUE(uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE groupmembers ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principal_id INTEGER UNSIGNED NOT NULL, + member_id INTEGER UNSIGNED NOT NULL, + UNIQUE(principal_id, member_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO principals (uri,email,displayname) VALUES +('principals/admin', 'admin@example.org','Administrator'), +('principals/admin/calendar-proxy-read', null, null), +('principals/admin/calendar-proxy-write', null, null); + diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql new file mode 100644 index 000000000000..1b5ca5ac6ec1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.propertystorage.sql @@ -0,0 +1,9 @@ +CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + valuetype INT UNSIGNED, + value MEDIUMBLOB +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100)); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/mysql.users.sql b/libs/composer/vendor/sabre/dav/examples/sql/mysql.users.sql new file mode 100644 index 000000000000..22ac312d5b0d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/mysql.users.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + username VARBINARY(50), + digesta1 VARBINARY(32), + UNIQUE(username) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql new file mode 100644 index 000000000000..98f414f42e41 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.addressbooks.sql @@ -0,0 +1,44 @@ +CREATE TABLE addressbooks ( + id SERIAL NOT NULL, + principaluri VARCHAR(255), + displayname VARCHAR(255), + uri VARCHAR(200), + description TEXT, + synctoken INTEGER NOT NULL DEFAULT 1 +); + +ALTER TABLE ONLY addressbooks + ADD CONSTRAINT addressbooks_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX addressbooks_ukey + ON addressbooks USING btree (principaluri, uri); + +CREATE TABLE cards ( + id SERIAL NOT NULL, + addressbookid INTEGER NOT NULL, + carddata BYTEA, + uri VARCHAR(200), + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL +); + +ALTER TABLE ONLY cards + ADD CONSTRAINT cards_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX cards_ukey + ON cards USING btree (addressbookid, uri); + +CREATE TABLE addressbookchanges ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + synctoken INTEGER NOT NULL, + addressbookid INTEGER NOT NULL, + operation SMALLINT NOT NULL +); + +ALTER TABLE ONLY addressbookchanges + ADD CONSTRAINT addressbookchanges_pkey PRIMARY KEY (id); + +CREATE INDEX addressbookchanges_addressbookid_synctoken_ix + ON addressbookchanges USING btree (addressbookid, synctoken); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.calendars.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.calendars.sql new file mode 100644 index 000000000000..67dc41a5a7af --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.calendars.sql @@ -0,0 +1,105 @@ +CREATE TABLE calendarobjects ( + id SERIAL NOT NULL, + calendardata BYTEA, + uri VARCHAR(200), + calendarid INTEGER NOT NULL, + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL, + componenttype VARCHAR(8), + firstoccurence INTEGER, + lastoccurence INTEGER, + uid VARCHAR(200) +); + +ALTER TABLE ONLY calendarobjects + ADD CONSTRAINT calendarobjects_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendarobjects_ukey + ON calendarobjects USING btree (calendarid, uri); + + +CREATE TABLE calendars ( + id SERIAL NOT NULL, + synctoken INTEGER NOT NULL DEFAULT 1, + components VARCHAR(21) +); + +ALTER TABLE ONLY calendars + ADD CONSTRAINT calendars_pkey PRIMARY KEY (id); + + +CREATE TABLE calendarinstances ( + id SERIAL NOT NULL, + calendarid INTEGER NOT NULL, + principaluri VARCHAR(100), + access SMALLINT NOT NULL DEFAULT '1', -- '1 = owner, 2 = read, 3 = readwrite' + displayname VARCHAR(100), + uri VARCHAR(200), + description TEXT, + calendarorder INTEGER NOT NULL DEFAULT 0, + calendarcolor VARCHAR(10), + timezone TEXT, + transparent SMALLINT NOT NULL DEFAULT '0', + share_href VARCHAR(100), + share_displayname VARCHAR(100), + share_invitestatus SMALLINT NOT NULL DEFAULT '2' -- '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid' +); + +ALTER TABLE ONLY calendarinstances + ADD CONSTRAINT calendarinstances_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendarinstances_principaluri_uri + ON calendarinstances USING btree (principaluri, uri); + + +CREATE UNIQUE INDEX calendarinstances_principaluri_calendarid + ON calendarinstances USING btree (principaluri, calendarid); + +CREATE UNIQUE INDEX calendarinstances_principaluri_share_href + ON calendarinstances USING btree (principaluri, share_href); + +CREATE TABLE calendarsubscriptions ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + principaluri VARCHAR(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INTEGER NOT NULL DEFAULT 0, + calendarcolor VARCHAR(10), + striptodos SMALLINT NULL, + stripalarms SMALLINT NULL, + stripattachments SMALLINT NULL, + lastmodified INTEGER +); + +ALTER TABLE ONLY calendarsubscriptions + ADD CONSTRAINT calendarsubscriptions_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendarsubscriptions_ukey + ON calendarsubscriptions USING btree (principaluri, uri); + +CREATE TABLE calendarchanges ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + synctoken INTEGER NOT NULL, + calendarid INTEGER NOT NULL, + operation SMALLINT NOT NULL DEFAULT 0 +); + +ALTER TABLE ONLY calendarchanges + ADD CONSTRAINT calendarchanges_pkey PRIMARY KEY (id); + +CREATE INDEX calendarchanges_calendarid_synctoken_ix + ON calendarchanges USING btree (calendarid, synctoken); + +CREATE TABLE schedulingobjects ( + id SERIAL NOT NULL, + principaluri VARCHAR(255), + calendardata BYTEA, + uri VARCHAR(200), + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL +); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.locks.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.locks.sql new file mode 100644 index 000000000000..0290528cecb6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.locks.sql @@ -0,0 +1,19 @@ +CREATE TABLE locks ( + id SERIAL NOT NULL, + owner VARCHAR(100), + timeout INTEGER, + created INTEGER, + token VARCHAR(100), + scope SMALLINT, + depth SMALLINT, + uri TEXT +); + +ALTER TABLE ONLY locks + ADD CONSTRAINT locks_pkey PRIMARY KEY (id); + +CREATE INDEX locks_token_ix + ON locks USING btree (token); + +CREATE INDEX locks_uri_ix + ON locks USING btree (uri); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.principals.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.principals.sql new file mode 100644 index 000000000000..5a65260a2cc3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.principals.sql @@ -0,0 +1,30 @@ +CREATE TABLE principals ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + email VARCHAR(80), + displayname VARCHAR(80) +); + +ALTER TABLE ONLY principals + ADD CONSTRAINT principals_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX principals_ukey + ON principals USING btree (uri); + +CREATE TABLE groupmembers ( + id SERIAL NOT NULL, + principal_id INTEGER NOT NULL, + member_id INTEGER NOT NULL +); + +ALTER TABLE ONLY groupmembers + ADD CONSTRAINT groupmembers_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX groupmembers_ukey + ON groupmembers USING btree (principal_id, member_id); + +INSERT INTO principals (uri,email,displayname) VALUES +('principals/admin', 'admin@example.org','Administrator'), +('principals/admin/calendar-proxy-read', null, null), +('principals/admin/calendar-proxy-write', null, null); + diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql new file mode 100644 index 000000000000..d1463faae55c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.propertystorage.sql @@ -0,0 +1,13 @@ +CREATE TABLE propertystorage ( + id SERIAL NOT NULL, + path VARCHAR(1024) NOT NULL, + name VARCHAR(100) NOT NULL, + valuetype INT, + value BYTEA +); + +ALTER TABLE ONLY propertystorage + ADD CONSTRAINT propertystorage_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX propertystorage_ukey + ON propertystorage (path, name); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/pgsql.users.sql b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.users.sql new file mode 100644 index 000000000000..9d6047b8c6f7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/pgsql.users.sql @@ -0,0 +1,14 @@ +CREATE TABLE users ( + id SERIAL NOT NULL, + username VARCHAR(50), + digesta1 VARCHAR(32) +); + +ALTER TABLE ONLY users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX users_ukey + ON users USING btree (username); + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql new file mode 100644 index 000000000000..0baed8bfbe4d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.addressbooks.sql @@ -0,0 +1,28 @@ +CREATE TABLE addressbooks ( + id integer primary key asc NOT NULL, + principaluri text NOT NULL, + displayname text, + uri text NOT NULL, + description text, + synctoken integer DEFAULT 1 NOT NULL +); + +CREATE TABLE cards ( + id integer primary key asc NOT NULL, + addressbookid integer NOT NULL, + carddata blob, + uri text NOT NULL, + lastmodified integer, + etag text, + size integer +); + +CREATE TABLE addressbookchanges ( + id integer primary key asc NOT NULL, + uri text, + synctoken integer NOT NULL, + addressbookid integer NOT NULL, + operation integer NOT NULL +); + +CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.calendars.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.calendars.sql new file mode 100644 index 000000000000..1c80704966c9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.calendars.sql @@ -0,0 +1,76 @@ +CREATE TABLE calendarobjects ( + id integer primary key asc NOT NULL, + calendardata blob NOT NULL, + uri text NOT NULL, + calendarid integer NOT NULL, + lastmodified integer NOT NULL, + etag text NOT NULL, + size integer NOT NULL, + componenttype text, + firstoccurence integer, + lastoccurence integer, + uid text +); + +CREATE TABLE calendars ( + id integer primary key asc NOT NULL, + synctoken integer DEFAULT 1 NOT NULL, + components text NOT NULL +); + +CREATE TABLE calendarinstances ( + id integer primary key asc NOT NULL, + calendarid integer NOT NULL, + principaluri text NULL, + access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1', + displayname text, + uri text NOT NULL, + description text, + calendarorder integer, + calendarcolor text, + timezone text, + transparent bool, + share_href text, + share_displayname text, + share_invitestatus integer DEFAULT '2', + UNIQUE (principaluri, uri), + UNIQUE (calendarid, principaluri), + UNIQUE (calendarid, share_href) +); + +CREATE TABLE calendarchanges ( + id integer primary key asc NOT NULL, + uri text, + synctoken integer NOT NULL, + calendarid integer NOT NULL, + operation integer NOT NULL +); + +CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken); + +CREATE TABLE calendarsubscriptions ( + id integer primary key asc NOT NULL, + uri text NOT NULL, + principaluri text NOT NULL, + source text NOT NULL, + displayname text, + refreshrate text, + calendarorder integer, + calendarcolor text, + striptodos bool, + stripalarms bool, + stripattachments bool, + lastmodified int +); + +CREATE TABLE schedulingobjects ( + id integer primary key asc NOT NULL, + principaluri text NOT NULL, + calendardata blob, + uri text NOT NULL, + lastmodified integer, + etag text NOT NULL, + size integer NOT NULL +); + +CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.locks.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.locks.sql new file mode 100644 index 000000000000..622baea423d4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.locks.sql @@ -0,0 +1,12 @@ +BEGIN TRANSACTION; +CREATE TABLE locks ( + id integer primary key asc NOT NULL, + owner text, + timeout integer, + created integer, + token text, + scope integer, + depth integer, + uri text +); +COMMIT; diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.principals.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.principals.sql new file mode 100644 index 000000000000..4105156f879c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.principals.sql @@ -0,0 +1,20 @@ +CREATE TABLE principals ( + id INTEGER PRIMARY KEY ASC NOT NULL, + uri TEXT NOT NULL, + email TEXT, + displayname TEXT, + UNIQUE(uri) +); + +CREATE TABLE groupmembers ( + id INTEGER PRIMARY KEY ASC NOT NULL, + principal_id INTEGER NOT NULL, + member_id INTEGER NOT NULL, + UNIQUE(principal_id, member_id) +); + + +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator'); +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null); +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null); + diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql new file mode 100644 index 000000000000..72e860ce3ba1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.propertystorage.sql @@ -0,0 +1,10 @@ +CREATE TABLE propertystorage ( + id integer primary key asc NOT NULL, + path text NOT NULL, + name text NOT NULL, + valuetype integer NOT NULL, + value string +); + + +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); diff --git a/libs/composer/vendor/sabre/dav/examples/sql/sqlite.users.sql b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.users.sql new file mode 100644 index 000000000000..5597b058a34f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/sql/sqlite.users.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer primary key asc NOT NULL, + username TEXT NOT NULL, + digesta1 TEXT NOT NULL, + UNIQUE(username) +); + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/libs/composer/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf new file mode 100644 index 000000000000..c5f29ba80e88 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_htaccess.conf @@ -0,0 +1,16 @@ +RewriteEngine On +# This makes every request go to server.php +RewriteRule (.*) server.php [L] + +# Output buffering needs to be off, to prevent high memory usage +php_flag output_buffering off + +# This is also to prevent high memory usage +php_flag always_populate_raw_post_data off + +# This is almost a given, but magic quotes is *still* on on some +# linux distributions +php_flag magic_quotes_gpc off + +# SabreDAV is not compatible with mbstring function overloading +php_flag mbstring.func_overload off diff --git a/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost.conf b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost.conf new file mode 100644 index 000000000000..74289641e7b1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost.conf @@ -0,0 +1,29 @@ +# This is a sample configuration to setup a dedicated Apache vhost for WebDAV. +# +# The main thing that should be configured is the servername, and the path to +# your SabreDAV installation (DocumentRoot). +# +# This configuration assumed mod_php5 is used, as it sets a few default php +# settings as well. + + + # Don't forget to change the server name + # ServerName dav.example.org + + # The DocumentRoot is also required + # DocumentRoot /home/sabredav/ + + RewriteEngine On + # This makes every request go to server.php + RewriteRule ^/(.*)$ /server.php [L] + + # Output buffering needs to be off, to prevent high memory usage + php_flag output_buffering off + + # This is also to prevent high memory usage + php_flag always_populate_raw_post_data off + + # SabreDAV is not compatible with mbstring function overloading + php_flag mbstring.func_overload off + + diff --git a/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf new file mode 100644 index 000000000000..607254c6e935 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/examples/webserver/apache2_vhost_cgi.conf @@ -0,0 +1,21 @@ +# This is a sample configuration to setup a dedicated Apache vhost for WebDAV. +# +# The main thing that should be configured is the servername, and the path to +# your SabreDAV installation (DocumentRoot). +# +# This configuration assumes CGI or FastCGI is used. + + + # Don't forget to change the server name + # ServerName dav.example.org + + # The DocumentRoot is also required + # DocumentRoot /home/sabredav/ + + # This makes every request go to server.php. This also makes sure + # the Authentication information is available. If your server script is + # not called server.php, be sure to change it. + RewriteEngine On + RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php new file mode 100644 index 000000000000..311b1c41509e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php @@ -0,0 +1,226 @@ +getCalendarObject($calendarId, $uri); + }, $uris); + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on either VEVENT or VTODO. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interprete all these filters can also simply + * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * @param mixed $calendarId + * @param array $filters + * @return array + */ + function calendarQuery($calendarId, array $filters) { + + $result = []; + $objects = $this->getCalendarObjects($calendarId); + + foreach ($objects as $object) { + + if ($this->validateFilterForObject($object, $filters)) { + $result[] = $object['uri']; + } + + } + + return $result; + + } + + /** + * This method validates if a filter (as passed to calendarQuery) matches + * the given object. + * + * @param array $object + * @param array $filters + * @return bool + */ + protected function validateFilterForObject(array $object, array $filters) { + + // Unfortunately, setting the 'calendardata' here is optional. If + // it was excluded, we actually need another call to get this as + // well. + if (!isset($object['calendardata'])) { + $object = $this->getCalendarObject($object['calendarid'], $object['uri']); + } + + $vObject = VObject\Reader::read($object['calendardata']); + + $validator = new CalDAV\CalendarQueryValidator(); + $result = $validator->validate($vObject, $filters); + + // Destroy circular references so PHP will GC the object. + $vObject->destroy(); + + return $result; + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $principalUri + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($principalUri, $uid) { + + // Note: this is a super slow naive implementation of this method. You + // are highly recommended to optimize it, if your backend allows it. + foreach ($this->getCalendarsForUser($principalUri) as $calendar) { + + // We must ignore calendars owned by other principals. + if ($calendar['principaluri'] !== $principalUri) { + continue; + } + + // Ignore calendars that are shared. + if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) { + continue; + } + + $results = $this->calendarQuery( + $calendar['id'], + [ + 'name' => 'VCALENDAR', + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'UID', + 'is-not-defined' => false, + 'time-range' => null, + 'text-match' => [ + 'value' => $uid, + 'negate-condition' => false, + 'collation' => 'i;octet', + ], + 'param-filters' => [], + ], + ] + ] + ], + ] + ); + if ($results) { + // We have a match + return $calendar['uri'] . '/' . $results[0]; + } + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php new file mode 100644 index 000000000000..bd8ee7602289 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php @@ -0,0 +1,270 @@ + 'displayname', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', + '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', + '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', + ]; + + /** + * List of subscription properties, and how they map to database fieldnames. + * + * @var array + */ + public $subscriptionPropertyMap = [ + '{DAV:}displayname' => 'displayname', + '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate', + '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', + '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos', + '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms', + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', + ]; + + /** + * Creates the backend + * + * @param \PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri. This is just the 'base uri' or 'filename' of the calendar. + * * principaluri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * Many clients also require: + * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set + * For this property, you can just return an instance of + * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet. + * + * If you return {http://sabredav.org/ns}read-only and set the value to 1, + * ACL will automatically be put in read-only mode. + * + * @param string $principalUri + * @return array + */ + function getCalendarsForUser($principalUri) { + + $fields = array_values($this->propertyMap); + $fields[] = 'calendarid'; + $fields[] = 'uri'; + $fields[] = 'synctoken'; + $fields[] = 'components'; + $fields[] = 'principaluri'; + $fields[] = 'transparent'; + $fields[] = 'access'; + + // Making fields a comma-delimited list + $fields = implode(', ', $fields); + $stmt = $this->pdo->prepare(<<calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName} + LEFT JOIN {$this->calendarTableName} ON + {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id +WHERE principaluri = ? ORDER BY calendarorder ASC +SQL + ); + $stmt->execute([$principalUri]); + + $calendars = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $components = []; + if ($row['components']) { + $components = explode(',', $row['components']); + } + + $calendar = [ + 'id' => [(int)$row['calendarid'], (int)$row['id']], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'), + '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components), + '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'), + 'share-resource-uri' => '/ns/share/' . $row['calendarid'], + ]; + + $calendar['share-access'] = (int)$row['access']; + // 1 = owner, 2 = readonly, 3 = readwrite + if ($row['access'] > 1) { + // We need to find more information about the original owner. + //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?'); + //$stmt2->execute([$row['id']]); + + // read-only is for backwards compatbility. Might go away in + // the future. + $calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ; + } + + foreach ($this->propertyMap as $xmlName => $dbName) { + $calendar[$xmlName] = $row[$dbName]; + } + + $calendars[] = $calendar; + + } + + return $calendars; + + } + + /** + * Creates a new calendar for a principal. + * + * If the creation was a success, an id must be returned that can be used + * to reference this calendar in other methods, such as updateCalendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return string + */ + function createCalendar($principalUri, $calendarUri, array $properties) { + + $fieldNames = [ + 'principaluri', + 'uri', + 'transparent', + 'calendarid', + ]; + $values = [ + ':principaluri' => $principalUri, + ':uri' => $calendarUri, + ':transparent' => 0, + ]; + + + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + if (!isset($properties[$sccs])) { + // Default value + $components = 'VEVENT,VTODO'; + } else { + if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) { + throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet'); + } + $components = implode(',', $properties[$sccs]->getValue()); + } + $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'; + if (isset($properties[$transp])) { + $values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0; + } + $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)"); + $stmt->execute([$components]); + + $calendarId = $this->pdo->lastInsertId( + $this->calendarTableName . '_id_seq' + ); + + $values[':calendarid'] = $calendarId; + + foreach ($this->propertyMap as $xmlName => $dbName) { + if (isset($properties[$xmlName])) { + + $values[':' . $dbName] = $properties[$xmlName]; + $fieldNames[] = $dbName; + } + } + + $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")"); + + $stmt->execute($values); + + return [ + $calendarId, + $this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq') + ]; + + } + + /** + * Updates properties for a calendar. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param mixed $calendarId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $supportedProperties = array_keys($this->propertyMap); + $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'; + + $propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) { + $newValues = []; + foreach ($mutations as $propertyName => $propertyValue) { + + switch ($propertyName) { + case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' : + $fieldName = 'transparent'; + $newValues[$fieldName] = $propertyValue->getValue() === 'transparent'; + break; + default : + $fieldName = $this->propertyMap[$propertyName]; + $newValues[$fieldName] = $propertyValue; + break; + } + + } + $valuesSql = []; + foreach ($newValues as $fieldName => $value) { + $valuesSql[] = $fieldName . ' = ?'; + } + + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?"); + $newValues['id'] = $instanceId; + $stmt->execute(array_values($newValues)); + + $this->addChange($calendarId, "", 2); + + return true; + + }); + + } + + /** + * Delete a calendar and all it's objects + * + * @param mixed $calendarId + * @return void + */ + function deleteCalendar($calendarId) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?'); + $stmt->execute([$instanceId]); + $access = (int)$stmt->fetchColumn(); + + if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) { + + /** + * If the user is the owner of the calendar, we delete all data and all + * instances. + **/ + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([$calendarId]); + + } else { + + /** + * If it was an instance of a shared calendar, we only delete that + * instance. + */ + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?'); + $stmt->execute([$instanceId]); + + } + + + } + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can + * be any arbitrary string, but making sure it ends with '.ics' is a + * good idea. This is only the basename, or filename, not the full + * path. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * size - The size of the calendar objects, in bytes. + * * component - optional, a string containing the type of object, such + * as 'vevent' or 'vtodo'. If specified, this will be used to populate + * the Content-Type header. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param mixed $calendarId + * @return array + */ + function getCalendarObjects($calendarId) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $result = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => (int)$row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + 'component' => strtolower($row['componenttype']), + ]; + } + + return $result; + + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * This method must return null if the object did not exist. + * + * @param mixed $calendarId + * @param string $objectUri + * @return array|null + */ + function getCalendarObject($calendarId, $objectUri) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$row) return null; + + return [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => (int)$row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + 'calendardata' => $row['calendardata'], + 'component' => strtolower($row['componenttype']), + ]; + + } + + /** + * Returns a list of calendar objects. + * + * This method should work identical to getCalendarObject, but instead + * return all the calendar objects in the list as an array. + * + * If the backend supports this, it may allow for some speed-ups. + * + * @param mixed $calendarId + * @param array $uris + * @return array + */ + function getMultipleCalendarObjects($calendarId, array $uris) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $result = []; + foreach (array_chunk($uris, 900) as $chunk) { + $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN ('; + // Inserting a whole bunch of question marks + $query .= implode(',', array_fill(0, count($chunk), '?')); + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute(array_merge([$calendarId], $chunk)); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $result[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => (int)$row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + 'calendardata' => $row['calendardata'], + 'component' => strtolower($row['componenttype']), + ]; + + } + } + return $result; + + } + + + /** + * Creates a new calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function createCalendarObject($calendarId, $objectUri, $calendarData) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $extraData = $this->getDenormalizedData($calendarData); + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)'); + $stmt->execute([ + $calendarId, + $objectUri, + $calendarData, + time(), + $extraData['etag'], + $extraData['size'], + $extraData['componentType'], + $extraData['firstOccurence'], + $extraData['lastOccurence'], + $extraData['uid'], + ]); + $this->addChange($calendarId, $objectUri, 1); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function updateCalendarObject($calendarId, $objectUri, $calendarData) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $extraData = $this->getDenormalizedData($calendarData); + + $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]); + + $this->addChange($calendarId, $objectUri, 2); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Parses some information from calendar objects, used for optimized + * calendar-queries. + * + * Returns an array with the following keys: + * * etag - An md5 checksum of the object without the quotes. + * * size - Size of the object in bytes + * * componentType - VEVENT, VTODO or VJOURNAL + * * firstOccurence + * * lastOccurence + * * uid - value of the UID property + * + * @param string $calendarData + * @return array + */ + protected function getDenormalizedData($calendarData) { + + $vObject = VObject\Reader::read($calendarData); + $componentType = null; + $component = null; + $firstOccurence = null; + $lastOccurence = null; + $uid = null; + foreach ($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') { + $componentType = $component->name; + $uid = (string)$component->UID; + break; + } + } + if (!$componentType) { + throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); + } + if ($componentType === 'VEVENT') { + $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); + // Finding the last occurence is a bit harder + if (!isset($component->RRULE)) { + if (isset($component->DTEND)) { + $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); + } elseif (isset($component->DURATION)) { + $endDate = clone $component->DTSTART->getDateTime(); + $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); + $lastOccurence = $endDate->getTimeStamp(); + } elseif (!$component->DTSTART->hasTime()) { + $endDate = clone $component->DTSTART->getDateTime(); + $endDate = $endDate->modify('+1 day'); + $lastOccurence = $endDate->getTimeStamp(); + } else { + $lastOccurence = $firstOccurence; + } + } else { + $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID); + $maxDate = new \DateTime(self::MAX_DATE); + if ($it->isInfinite()) { + $lastOccurence = $maxDate->getTimeStamp(); + } else { + $end = $it->getDtEnd(); + while ($it->valid() && $end < $maxDate) { + $end = $it->getDtEnd(); + $it->next(); + + } + $lastOccurence = $end->getTimeStamp(); + } + + } + + // Ensure Occurence values are positive + if ($firstOccurence < 0) $firstOccurence = 0; + if ($lastOccurence < 0) $lastOccurence = 0; + } + + // Destroy circular references to PHP will GC the object. + $vObject->destroy(); + + return [ + 'etag' => md5($calendarData), + 'size' => strlen($calendarData), + 'componentType' => $componentType, + 'firstOccurence' => $firstOccurence, + 'lastOccurence' => $lastOccurence, + 'uid' => $uid, + ]; + + } + + /** + * Deletes an existing calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * @param mixed $calendarId + * @param string $objectUri + * @return void + */ + function deleteCalendarObject($calendarId, $objectUri) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + + $this->addChange($calendarId, $objectUri, 3); + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on a VEVENT. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interpret all these filters can also simply + * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * This specific implementation (for the PDO) backend optimizes filters on + * specific components, and VEVENT time-ranges. + * + * @param mixed $calendarId + * @param array $filters + * @return array + */ + function calendarQuery($calendarId, array $filters) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $componentType = null; + $requirePostFilter = true; + $timeRange = null; + + // if no filters were specified, we don't need to filter after a query + if (!$filters['prop-filters'] && !$filters['comp-filters']) { + $requirePostFilter = false; + } + + // Figuring out if there's a component filter + if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { + $componentType = $filters['comp-filters'][0]['name']; + + // Checking if we need post-filters + if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { + $requirePostFilter = false; + } + // There was a time-range filter + if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) { + $timeRange = $filters['comp-filters'][0]['time-range']; + + // If start time OR the end time is not specified, we can do a + // 100% accurate mysql query. + if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { + $requirePostFilter = false; + } + } + + } + + if ($requirePostFilter) { + $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid"; + } else { + $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid"; + } + + $values = [ + 'calendarid' => $calendarId, + ]; + + if ($componentType) { + $query .= " AND componenttype = :componenttype"; + $values['componenttype'] = $componentType; + } + + if ($timeRange && $timeRange['start']) { + $query .= " AND lastoccurence > :startdate"; + $values['startdate'] = $timeRange['start']->getTimeStamp(); + } + if ($timeRange && $timeRange['end']) { + $query .= " AND firstoccurence < :enddate"; + $values['enddate'] = $timeRange['end']->getTimeStamp(); + } + + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($requirePostFilter) { + if (!$this->validateFilterForObject($row, $filters)) { + continue; + } + } + $result[] = $row['uri']; + + } + + return $result; + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $principalUri + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($principalUri, $uid) { + + $query = <<calendarObjectTableName AS calendarobjects +LEFT JOIN + $this->calendarInstancesTableName AS calendar_instances + ON calendarobjects.calendarid = calendar_instances.calendarid +WHERE + calendar_instances.principaluri = ? + AND + calendarobjects.uid = ? +SQL; + + $stmt = $this->pdo->prepare($query); + $stmt->execute([$principalUri, $uid]); + + if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $row['calendaruri'] . '/' . $row['objecturi']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken in the specified calendar. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property this is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param mixed $calendarId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + // Current synctoken + $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([$calendarId]); + $currentToken = $stmt->fetchColumn(0); + + if (is_null($currentToken)) return null; + + $result = [ + 'syncToken' => $currentToken, + 'added' => [], + 'modified' => [], + 'deleted' => [], + ]; + + if ($syncToken) { + + $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken"; + if ($limit > 0) $query .= " LIMIT " . (int)$limit; + + // Fetching all changes + $stmt = $this->pdo->prepare($query); + $stmt->execute([$syncToken, $currentToken, $calendarId]); + + $changes = []; + + // This loop ensures that any duplicates are overwritten, only the + // last change on a node is relevant. + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $changes[$row['uri']] = $row['operation']; + + } + + foreach ($changes as $uri => $operation) { + + switch ($operation) { + case 1 : + $result['added'][] = $uri; + break; + case 2 : + $result['modified'][] = $uri; + break; + case 3 : + $result['deleted'][] = $uri; + break; + } + + } + } else { + // No synctoken supplied, this is the initial sync. + $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?"; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$calendarId]); + + $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + } + return $result; + + } + + /** + * Adds a change record to the calendarchanges table. + * + * @param mixed $calendarId + * @param string $objectUri + * @param int $operation 1 = add, 2 = modify, 3 = delete. + * @return void + */ + protected function addChange($calendarId, $objectUri, $operation) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([ + $objectUri, + $calendarId, + $operation, + $calendarId + ]); + $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?'); + $stmt->execute([ + $calendarId + ]); + + } + + /** + * Returns a list of subscriptions for a principal. + * + * Every subscription is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * subscription. This can be the same as the uri or a database key. + * * uri. This is just the 'base uri' or 'filename' of the subscription. + * * principaluri. The owner of the subscription. Almost always the same as + * principalUri passed to this method. + * * source. Url to the actual feed + * + * Furthermore, all the subscription info must be returned too: + * + * 1. {DAV:}displayname + * 2. {http://apple.com/ns/ical/}refreshrate + * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos + * should not be stripped). + * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms + * should not be stripped). + * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if + * attachments should not be stripped). + * 7. {http://apple.com/ns/ical/}calendar-color + * 8. {http://apple.com/ns/ical/}calendar-order + * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set + * (should just be an instance of + * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of + * default components). + * + * @param string $principalUri + * @return array + */ + function getSubscriptionsForUser($principalUri) { + + $fields = array_values($this->subscriptionPropertyMap); + $fields[] = 'id'; + $fields[] = 'uri'; + $fields[] = 'source'; + $fields[] = 'principaluri'; + $fields[] = 'lastmodified'; + + // Making fields a comma-delimited list + $fields = implode(', ', $fields); + $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC"); + $stmt->execute([$principalUri]); + + $subscriptions = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $subscription = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + 'source' => $row['source'], + 'lastmodified' => $row['lastmodified'], + + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']), + ]; + + foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { + if (!is_null($row[$dbName])) { + $subscription[$xmlName] = $row[$dbName]; + } + } + + $subscriptions[] = $subscription; + + } + + return $subscriptions; + + } + + /** + * Creates a new subscription for a principal. + * + * If the creation was a success, an id must be returned that can be used to reference + * this subscription in other methods, such as updateSubscription. + * + * @param string $principalUri + * @param string $uri + * @param array $properties + * @return mixed + */ + function createSubscription($principalUri, $uri, array $properties) { + + $fieldNames = [ + 'principaluri', + 'uri', + 'source', + 'lastmodified', + ]; + + if (!isset($properties['{http://calendarserver.org/ns/}source'])) { + throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions'); + } + + $values = [ + ':principaluri' => $principalUri, + ':uri' => $uri, + ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(), + ':lastmodified' => time(), + ]; + + foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { + if (isset($properties[$xmlName])) { + + $values[':' . $dbName] = $properties[$xmlName]; + $fieldNames[] = $dbName; + } + } + + $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")"); + $stmt->execute($values); + + return $this->pdo->lastInsertId( + $this->calendarSubscriptionsTableName . '_id_seq' + ); + + } + + /** + * Updates a subscription + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param mixed $subscriptionId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + + $supportedProperties = array_keys($this->subscriptionPropertyMap); + $supportedProperties[] = '{http://calendarserver.org/ns/}source'; + + $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) { + + $newValues = []; + + foreach ($mutations as $propertyName => $propertyValue) { + + if ($propertyName === '{http://calendarserver.org/ns/}source') { + $newValues['source'] = $propertyValue->getHref(); + } else { + $fieldName = $this->subscriptionPropertyMap[$propertyName]; + $newValues[$fieldName] = $propertyValue; + } + + } + + // Now we're generating the sql query. + $valuesSql = []; + foreach ($newValues as $fieldName => $value) { + $valuesSql[] = $fieldName . ' = ?'; + } + + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?"); + $newValues['lastmodified'] = time(); + $newValues['id'] = $subscriptionId; + $stmt->execute(array_values($newValues)); + + return true; + + }); + + } + + /** + * Deletes a subscription + * + * @param mixed $subscriptionId + * @return void + */ + function deleteSubscription($subscriptionId) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?'); + $stmt->execute([$subscriptionId]); + + } + + /** + * Returns a single scheduling object. + * + * The returned array should contain the following elements: + * * uri - A unique basename for the object. This will be used to + * construct a full uri. + * * calendardata - The iCalendar object + * * lastmodified - The last modification date. Can be an int for a unix + * timestamp, or a PHP DateTime object. + * * etag - A unique token that must change if the object changed. + * * size - The size of the object, in bytes. + * + * @param string $principalUri + * @param string $objectUri + * @return array + */ + function getSchedulingObject($principalUri, $objectUri) { + + $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?'); + $stmt->execute([$principalUri, $objectUri]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$row) return null; + + return [ + 'uri' => $row['uri'], + 'calendardata' => $row['calendardata'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + ]; + + } + + /** + * Returns all scheduling objects for the inbox collection. + * + * These objects should be returned as an array. Every item in the array + * should follow the same structure as returned from getSchedulingObject. + * + * The main difference is that 'calendardata' is optional. + * + * @param string $principalUri + * @return array + */ + function getSchedulingObjects($principalUri) { + + $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?'); + $stmt->execute([$principalUri]); + + $result = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = [ + 'calendardata' => $row['calendardata'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + ]; + } + + return $result; + + } + + /** + * Deletes a scheduling object + * + * @param string $principalUri + * @param string $objectUri + * @return void + */ + function deleteSchedulingObject($principalUri, $objectUri) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?'); + $stmt->execute([$principalUri, $objectUri]); + + } + + /** + * Creates a new scheduling object. This should land in a users' inbox. + * + * @param string $principalUri + * @param string $objectUri + * @param string $objectData + * @return void + */ + function createSchedulingObject($principalUri, $objectUri, $objectData) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)'); + $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]); + + } + + /** + * Updates the list of shares. + * + * @param mixed $calendarId + * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees + * @return void + */ + function updateInvites($calendarId, array $sharees) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); + } + $currentInvites = $this->getInvites($calendarId); + list($calendarId, $instanceId) = $calendarId; + + $removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)"); + $updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?"); + + $insertStmt = $this->pdo->prepare(' +INSERT INTO ' . $this->calendarInstancesTableName . ' + ( + calendarid, + principaluri, + access, + displayname, + uri, + description, + calendarorder, + calendarcolor, + timezone, + transparent, + share_href, + share_displayname, + share_invitestatus + ) + SELECT + ?, + ?, + ?, + displayname, + ?, + description, + calendarorder, + calendarcolor, + timezone, + 1, + ?, + ?, + ? + FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?'); + + foreach ($sharees as $sharee) { + + if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) { + // if access was set no NOACCESS, it means access for an + // existing sharee was removed. + $removeStmt->execute([$calendarId, $sharee->href]); + continue; + } + + if (is_null($sharee->principal)) { + // If the server could not determine the principal automatically, + // we will mark the invite status as invalid. + $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID; + } else { + // Because sabre/dav does not yet have an invitation system, + // every invite is automatically accepted for now. + $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED; + } + + foreach ($currentInvites as $oldSharee) { + + if ($oldSharee->href === $sharee->href) { + // This is an update + $sharee->properties = array_merge( + $oldSharee->properties, + $sharee->properties + ); + $updateStmt->execute([ + $sharee->access, + isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null, + $sharee->inviteStatus ?: $oldSharee->inviteStatus, + $calendarId, + $sharee->href + ]); + continue 2; + } + + } + // If we got here, it means it was a new sharee + $insertStmt->execute([ + $calendarId, + $sharee->principal, + $sharee->access, + \Sabre\DAV\UUIDUtil::getUUID(), + $sharee->href, + isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null, + $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE, + $instanceId + ]); + + } + + } + + /** + * Returns the list of people whom a calendar is shared with. + * + * Every item in the returned list must be a Sharee object with at + * least the following properties set: + * $href + * $shareAccess + * $inviteStatus + * + * and optionally: + * $properties + * + * @param mixed $calendarId + * @return \Sabre\DAV\Xml\Element\Sharee[] + */ + function getInvites($calendarId) { + + if (!is_array($calendarId)) { + throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId'); + } + list($calendarId, $instanceId) = $calendarId; + + $query = <<calendarInstancesTableName} +WHERE + calendarid = ? +SQL; + + $stmt = $this->pdo->prepare($query); + $stmt->execute([$calendarId]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $result[] = new Sharee([ + 'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']), + 'access' => (int)$row['access'], + /// Everyone is always immediately accepted, for now. + 'inviteStatus' => (int)$row['share_invitestatus'], + 'properties' => + !empty($row['share_displayname']) + ? ['{DAV:}displayname' => $row['share_displayname']] + : [], + 'principal' => $row['principaluri'], + ]); + + } + return $result; + + } + + /** + * Publishes a calendar + * + * @param mixed $calendarId + * @param bool $value + * @return void + */ + function setPublishStatus($calendarId, $value) { + + throw new DAV\Exception\NotImplemented('Not implemented'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php new file mode 100644 index 000000000000..6ec0bf06b9fb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php @@ -0,0 +1,65 @@ +pdo = $pdo; + + } + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri. This is just the 'base uri' or 'filename' of the calendar. + * * principaluri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * Many clients also require: + * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set + * For this property, you can just return an instance of + * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet. + * + * If you return {http://sabredav.org/ns}read-only and set the value to 1, + * ACL will automatically be put in read-only mode. + * + * @param string $principalUri + * @return array + */ + function getCalendarsForUser($principalUri) { + + // Making fields a comma-delimited list + $stmt = $this->pdo->prepare("SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC"); + $stmt->execute([$principalUri]); + + $calendars = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $calendars[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $principalUri, + ]; + + } + + return $calendars; + + } + + /** + * Creates a new calendar for a principal. + * + * If the creation was a success, an id must be returned that can be used + * to reference this calendar in other methods, such as updateCalendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return string + */ + function createCalendar($principalUri, $calendarUri, array $properties) { + + $stmt = $this->pdo->prepare("INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)"); + $stmt->execute([$principalUri, $calendarUri]); + + return $this->pdo->lastInsertId(); + + } + + /** + * Delete a calendar and all it's objects + * + * @param string $calendarId + * @return void + */ + function deleteCalendar($calendarId) { + + $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?'); + $stmt->execute([$calendarId]); + + } + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can + * be any arbitrary string, but making sure it ends with '.ics' is a + * good idea. This is only the basename, or filename, not the full + * path. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * size - The size of the calendar objects, in bytes. + * * component - optional, a string containing the type of object, such + * as 'vevent' or 'vtodo'. If specified, this will be used to populate + * the Content-Type header. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param string $calendarId + * @return array + */ + function getCalendarObjects($calendarId) { + + $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $result = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'etag' => '"' . md5($row['calendardata']) . '"', + 'calendarid' => $calendarId, + 'size' => strlen($row['calendardata']), + 'calendardata' => $row['calendardata'], + ]; + } + + return $result; + + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * This method must return null if the object did not exist. + * + * @param string $calendarId + * @param string $objectUri + * @return array|null + */ + function getCalendarObject($calendarId, $objectUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$row) return null; + + return [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'etag' => '"' . md5($row['calendardata']) . '"', + 'calendarid' => $calendarId, + 'size' => strlen($row['calendardata']), + 'calendardata' => $row['calendardata'], + ]; + + } + + /** + * Creates a new calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function createCalendarObject($calendarId, $objectUri, $calendarData) { + + $stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)'); + $stmt->execute([ + $calendarId, + $objectUri, + $calendarData + ]); + + return '"' . md5($calendarData) . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function updateCalendarObject($calendarId, $objectUri, $calendarData) { + + $stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarData, $calendarId, $objectUri]); + + return '"' . md5($calendarData) . '"'; + + } + + /** + * Deletes an existing calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * @param string $calendarId + * @param string $objectUri + * @return void + */ + function deleteCalendarObject($calendarId, $objectUri) { + + $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php new file mode 100644 index 000000000000..d77a2fe0fd53 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php @@ -0,0 +1,89 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ); + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property This is * needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $calendarId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Calendar.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Calendar.php new file mode 100644 index 000000000000..7467900ccdc5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Calendar.php @@ -0,0 +1,472 @@ +caldavBackend = $caldavBackend; + $this->calendarInfo = $calendarInfo; + + } + + /** + * Returns the name of the calendar + * + * @return string + */ + function getName() { + + return $this->calendarInfo['uri']; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + + return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch); + + } + + /** + * Returns the list of properties + * + * @param array $requestedProperties + * @return array + */ + function getProperties($requestedProperties) { + + $response = []; + + foreach ($this->calendarInfo as $propName => $propValue) { + + if (!is_null($propValue) && $propName[0] === '{') + $response[$propName] = $this->calendarInfo[$propName]; + + } + return $response; + + } + + /** + * Returns a calendar object + * + * The contained calendar objects are for example Events or Todo's. + * + * @param string $name + * @return \Sabre\CalDAV\ICalendarObject + */ + function getChild($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + + if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found'); + + $obj['acl'] = $this->getChildACL(); + + return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + + } + + /** + * Returns the full list of calendar objects + * + * @return array + */ + function getChildren() { + + $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param string[] $paths + * @return array + */ + function getMultipleChildren(array $paths) { + + $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + /** + * Checks if a child-node exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + if (!$obj) + return false; + else + return true; + + } + + /** + * Creates a new directory + * + * We actually block this, as subdirectories are not allowed in calendars. + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed'); + + } + + /** + * Creates a new file + * + * The contents of the new file must be a valid ICalendar string. + * + * @param string $name + * @param resource $calendarData + * @return string|null + */ + function createFile($name, $calendarData = null) { + + if (is_resource($calendarData)) { + $calendarData = stream_get_contents($calendarData); + } + return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData); + + } + + /** + * Deletes the calendar. + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteCalendar($this->calendarInfo['id']); + + } + + /** + * Renames the calendar. Note that most calendars use the + * {DAV:}displayname to display a name to display a name. + * + * @param string $newName + * @return void + */ + function setName($newName) { + + throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported'); + + } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return null + */ + function getLastModified() { + + return null; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->calendarInfo['principaluri']; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + + ]; + if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ]; + } + + return $acl; + + } + + /** + * This method returns the ACL's for calendar objects in this calendar. + * The result of this method automatically gets passed to the + * calendar-object nodes in the calendar. + * + * @return array + */ + function getChildACL() { + + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ]; + + } + return $acl; + + } + + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by Sabre\CalDAV\CalendarQueryParser. + * + * @param array $filters + * @return array + */ + function calendarQuery(array $filters) { + + return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); + + } + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken() { + + if ( + $this->caldavBackend instanceof Backend\SyncSupport && + isset($this->calendarInfo['{DAV:}sync-token']) + ) { + return $this->calendarInfo['{DAV:}sync-token']; + } + if ( + $this->caldavBackend instanceof Backend\SyncSupport && + isset($this->calendarInfo['{http://sabredav.org/ns}sync-token']) + ) { + return $this->calendarInfo['{http://sabredav.org/ns}sync-token']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null) { + + if (!$this->caldavBackend instanceof Backend\SyncSupport) { + return null; + } + + return $this->caldavBackend->getChangesForCalendar( + $this->calendarInfo['id'], + $syncToken, + $syncLevel, + $limit + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarHome.php new file mode 100644 index 000000000000..ffd7f72fb693 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarHome.php @@ -0,0 +1,378 @@ +caldavBackend = $caldavBackend; + $this->principalInfo = $principalInfo; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->principalInfo['uri']); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Deletes this object + * + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + function createFile($filename, $data = null) { + + throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); + + } + + /** + * Creates a new directory under this object. + * + * This is currently not allowed. + * + * @param string $filename + * @return void + */ + function createDirectory($filename) { + + throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); + + } + + /** + * Returns a single calendar, by name + * + * @param string $name + * @return Calendar + */ + function getChild($name) { + + // Special nodes + if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { + return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); + } + if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { + return new Schedule\Outbox($this->principalInfo['uri']); + } + if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) { + return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); + } + + // Calendars + foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { + if ($calendar['uri'] === $name) { + if ($this->caldavBackend instanceof Backend\SharingSupport) { + return new SharedCalendar($this->caldavBackend, $calendar); + } else { + return new Calendar($this->caldavBackend, $calendar); + } + } + } + + if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { + foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { + if ($subscription['uri'] === $name) { + return new Subscriptions\Subscription($this->caldavBackend, $subscription); + } + } + + } + + throw new NotFound('Node with name \'' . $name . '\' could not be found'); + + } + + /** + * Checks if a calendar exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + return !!$this->getChild($name); + } catch (NotFound $e) { + return false; + } + + } + + /** + * Returns a list of calendars + * + * @return array + */ + function getChildren() { + + $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); + $objs = []; + foreach ($calendars as $calendar) { + if ($this->caldavBackend instanceof Backend\SharingSupport) { + $objs[] = new SharedCalendar($this->caldavBackend, $calendar); + } else { + $objs[] = new Calendar($this->caldavBackend, $calendar); + } + } + + if ($this->caldavBackend instanceof Backend\SchedulingSupport) { + $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); + $objs[] = new Schedule\Outbox($this->principalInfo['uri']); + } + + // We're adding a notifications node, if it's supported by the backend. + if ($this->caldavBackend instanceof Backend\NotificationSupport) { + $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); + } + + // If the backend supports subscriptions, we'll add those as well, + if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { + foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { + $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription); + } + } + + return $objs; + + } + + /** + * Creates a new calendar or subscription. + * + * @param string $name + * @param MkCol $mkCol + * @throws DAV\Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + $isCalendar = false; + $isSubscription = false; + foreach ($mkCol->getResourceType() as $rt) { + switch ($rt) { + case '{DAV:}collection' : + case '{http://calendarserver.org/ns/}shared-owner' : + // ignore + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $isCalendar = true; + break; + case '{http://calendarserver.org/ns/}subscribed' : + $isSubscription = true; + break; + default : + throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); + } + } + + $properties = $mkCol->getRemainingValues(); + $mkCol->setRemainingResultCode(201); + + if ($isSubscription) { + if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) { + throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions'); + } + $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties); + + } elseif ($isCalendar) { + $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties); + + } else { + throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection'); + + } + + } + + /** + * Returns the owner of the calendar home. + * + * @return string + */ + function getOwner() { + + return $this->principalInfo['uri']; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + + } + + + /** + * This method is called when a user replied to a request to share. + * + * This method should return the url of the newly created calendar if the + * share was accepted. + * + * @param string $href The sharee who is replying (often a mailto: address) + * @param int $status One of the SharingPlugin::STATUS_* constants + * @param string $calendarUri The url to the calendar thats being shared + * @param string $inReplyTo The unique id this message is a response to + * @param string $summary A description of the reply + * @return null|string + */ + function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { + + if (!$this->caldavBackend instanceof Backend\SharingSupport) { + throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.'); + } + + return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary); + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($uid) { + + return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarObject.php new file mode 100644 index 000000000000..9d6532a35b15 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarObject.php @@ -0,0 +1,237 @@ +caldavBackend = $caldavBackend; + + if (!isset($objectData['uri'])) { + throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property'); + } + + $this->calendarInfo = $calendarInfo; + $this->objectData = $objectData; + + } + + /** + * Returns the uri for this object + * + * @return string + */ + function getName() { + + return $this->objectData['uri']; + + } + + /** + * Returns the ICalendar-formatted object + * + * @return string + */ + function get() { + + // Pre-populating the 'calendardata' is optional, if we don't have it + // already we fetch it from the backend. + if (!isset($this->objectData['calendardata'])) { + $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']); + } + return $this->objectData['calendardata']; + + } + + /** + * Updates the ICalendar-formatted object + * + * @param string|resource $calendarData + * @return string + */ + function put($calendarData) { + + if (is_resource($calendarData)) { + $calendarData = stream_get_contents($calendarData); + } + $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData); + $this->objectData['calendardata'] = $calendarData; + $this->objectData['etag'] = $etag; + + return $etag; + + } + + /** + * Deletes the calendar object + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + function getContentType() { + + $mime = 'text/calendar; charset=utf-8'; + if (isset($this->objectData['component']) && $this->objectData['component']) { + $mime .= '; component=' . $this->objectData['component']; + } + return $mime; + + } + + /** + * Returns an ETag for this object. + * + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * @return string + */ + function getETag() { + + if (isset($this->objectData['etag'])) { + return $this->objectData['etag']; + } else { + return '"' . md5($this->get()) . '"'; + } + + } + + /** + * Returns the last modification date as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return $this->objectData['lastmodified']; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + function getSize() { + + if (array_key_exists('size', $this->objectData)) { + return $this->objectData['size']; + } else { + return strlen($this->get()); + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->calendarInfo['principaluri']; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified in the object data. + if (isset($this->objectData['acl'])) { + return $this->objectData['acl']; + } + + // The default ACL + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php new file mode 100644 index 000000000000..df8008fe276f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php @@ -0,0 +1,375 @@ +name !== $filters['name']) { + return false; + } + + return + $this->validateCompFilters($vObject, $filters['comp-filters']) && + $this->validatePropFilters($vObject, $filters['prop-filters']); + + + } + + /** + * This method checks the validity of comp-filters. + * + * A list of comp-filters needs to be specified. Also the parent of the + * component we're checking should be specified, not the component to check + * itself. + * + * @param VObject\Component $parent + * @param array $filters + * @return bool + */ + protected function validateCompFilters(VObject\Component $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent->{$filter['name']}); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach ($parent->{$filter['name']} as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['comp-filters'] && !$filter['prop-filters']) { + continue; + } + + // If there are sub-filters, we need to find at least one component + // for which the subfilters hold true. + foreach ($parent->{$filter['name']} as $subComponent) { + + if ( + $this->validateCompFilters($subComponent, $filter['comp-filters']) && + $this->validatePropFilters($subComponent, $filter['prop-filters'])) { + // We had a match, so this comp-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-comp-filters or + // sub-prop-filters and there was no match. This means this filter + // needs to return false. + return false; + + } + + // If we got here it means we got through all comp-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of prop-filters. + * + * A list of prop-filters needs to be specified. Also the parent of the + * property we're checking should be specified, not the property to check + * itself. + * + * @param VObject\Component $parent + * @param array $filters + * @return bool + */ + protected function validatePropFilters(VObject\Component $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent->{$filter['name']}); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach ($parent->{$filter['name']} as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['param-filters'] && !$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one property + // for which the subfilters hold true. + foreach ($parent->{$filter['name']} as $subComponent) { + + if ( + $this->validateParamFilters($subComponent, $filter['param-filters']) && + (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match'])) + ) { + // We had a match, so this prop-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-param-filters or + // text-match filters and there was no match. This means the + // filter needs to return false. + return false; + + } + + // If we got here it means we got through all prop-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of param-filters. + * + * A list of param-filters needs to be specified. Also the parent of the + * parameter we're checking should be specified, not the parameter to check + * itself. + * + * @param VObject\Property $parent + * @param array $filters + * @return bool + */ + protected function validateParamFilters(VObject\Property $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent[$filter['name']]); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if (!$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one parameter + // for which the subfilters hold true. + foreach ($parent[$filter['name']]->getParts() as $paramPart) { + + if ($this->validateTextMatch($paramPart, $filter['text-match'])) { + // We had a match, so this param-filter succeeds + continue 2; + } + + } + + // If we got here it means there was a text-match filter and there + // were no matches. This means the filter needs to return false. + return false; + + } + + // If we got here it means we got through all param-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of a text-match. + * + * A single text-match should be specified as well as the specific property + * or parameter we need to validate. + * + * @param VObject\Node|string $check Value to check against. + * @param array $textMatch + * @return bool + */ + protected function validateTextMatch($check, array $textMatch) { + + if ($check instanceof VObject\Node) { + $check = $check->getValue(); + } + + $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']); + + return ($textMatch['negate-condition'] xor $isMatching); + + } + + /** + * Validates if a component matches the given time range. + * + * This is all based on the rules specified in rfc4791, which are quite + * complex. + * + * @param VObject\Node $component + * @param DateTime $start + * @param DateTime $end + * @return bool + */ + protected function validateTimeRange(VObject\Node $component, $start, $end) { + + if (is_null($start)) { + $start = new DateTime('1900-01-01'); + } + if (is_null($end)) { + $end = new DateTime('3000-01-01'); + } + + switch ($component->name) { + + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + + return $component->isInTimeRange($start, $end); + + case 'VALARM' : + + // If the valarm is wrapped in a recurring event, we need to + // expand the recursions, and validate each. + // + // Our datamodel doesn't easily allow us to do this straight + // in the VALARM component code, so this is a hack, and an + // expensive one too. + if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) { + + // Fire up the iterator! + $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID); + while ($it->valid()) { + $expandedEvent = $it->getEventObject(); + + // We need to check from these expanded alarms, which + // one is the first to trigger. Based on this, we can + // determine if we can 'give up' expanding events. + $firstAlarm = null; + if ($expandedEvent->VALARM !== null) { + foreach ($expandedEvent->VALARM as $expandedAlarm) { + + $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); + if ($expandedAlarm->isInTimeRange($start, $end)) { + return true; + } + + if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { + // This is an alarm with a non-relative trigger + // time, likely created by a buggy client. The + // implication is that every alarm in this + // recurring event trigger at the exact same + // time. It doesn't make sense to traverse + // further. + } else { + // We store the first alarm as a means to + // figure out when we can stop traversing. + if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { + $firstAlarm = $effectiveTrigger; + } + } + } + } + if (is_null($firstAlarm)) { + // No alarm was found. + // + // Or technically: No alarm that will change for + // every instance of the recurrence was found, + // which means we can assume there was no match. + return false; + } + if ($firstAlarm > $end) { + return false; + } + $it->next(); + } + return false; + } else { + return $component->isInTimeRange($start, $end); + } + + case 'VFREEBUSY' : + throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components'); + + case 'COMPLETED' : + case 'CREATED' : + case 'DTEND' : + case 'DTSTAMP' : + case 'DTSTART' : + case 'DUE' : + case 'LAST-MODIFIED' : + return ($start <= $component->getDateTime() && $end >= $component->getDateTime()); + + + + default : + throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component'); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php new file mode 100644 index 000000000000..1d6b2ac9ff8d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php @@ -0,0 +1,80 @@ +caldavBackend = $caldavBackend; + + } + + /** + * Returns the nodename + * + * We're overriding this, because the default will be the 'principalPrefix', + * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT + * + * @return string + */ + function getName() { + + return Plugin::CALENDAR_ROOT; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principal + * @return \Sabre\DAV\INode + */ + function getChildForPrincipal(array $principal) { + + return new CalendarHome($this->caldavBackend, $principal); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php new file mode 100644 index 000000000000..7aff2edab556 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component'); + $errorNode->appendChild($np); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php new file mode 100644 index 000000000000..fc8b971f3b4b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php @@ -0,0 +1,378 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('browserButtonActions', function($path, $node, &$actions) { + if ($node instanceof ICalendar) { + $actions .= ''; + } + }); + + } + + /** + * Intercepts GET requests on calendar urls ending with ?export. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('export', $queryParams)) return; + + $path = $request->getPath(); + + $node = $this->server->getProperties($path, [ + '{DAV:}resourcetype', + '{DAV:}displayname', + '{http://sabredav.org/ns}sync-token', + '{DAV:}sync-token', + '{http://apple.com/ns/ical/}calendar-color', + ]); + + if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) { + return; + } + // Marking the transactionType, for logging purposes. + $this->server->transactionType = 'get-calendar-export'; + + $properties = $node; + + $start = null; + $end = null; + $expand = false; + $componentType = false; + if (isset($queryParams['start'])) { + if (!ctype_digit($queryParams['start'])) { + throw new BadRequest('The start= parameter must contain a unix timestamp'); + } + $start = DateTime::createFromFormat('U', $queryParams['start']); + } + if (isset($queryParams['end'])) { + if (!ctype_digit($queryParams['end'])) { + throw new BadRequest('The end= parameter must contain a unix timestamp'); + } + $end = DateTime::createFromFormat('U', $queryParams['end']); + } + if (isset($queryParams['expand']) && !!$queryParams['expand']) { + if (!$start || !$end) { + throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.'); + } + $expand = true; + $componentType = 'VEVENT'; + } + if (isset($queryParams['componentType'])) { + if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) { + throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here'); + } + $componentType = $queryParams['componentType']; + } + + $format = \Sabre\HTTP\Util::Negotiate( + $request->getHeader('Accept'), + [ + 'text/calendar', + 'application/calendar+json', + ] + ); + + if (isset($queryParams['accept'])) { + if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') { + $format = 'application/calendar+json'; + } + } + if (!$format) { + $format = 'text/calendar'; + } + + $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response); + + // Returning false to break the event chain + return false; + + } + + /** + * This method is responsible for generating the actual, full response. + * + * @param string $path + * @param DateTime|null $start + * @param DateTime|null $end + * @param bool $expand + * @param string $componentType + * @param string $format + * @param array $properties + * @param ResponseInterface $response + */ + protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) { + + $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; + $calendarNode = $this->server->tree->getNodeForPath($path); + + $blobs = []; + if ($start || $end || $componentType) { + + // If there was a start or end filter, we need to enlist + // calendarQuery for speed. + $queryResult = $calendarNode->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => $componentType, + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $start, + 'end' => $end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + // queryResult is just a list of base urls. We need to prefix the + // calendar path. + $queryResult = array_map( + function($item) use ($path) { + return $path . '/' . $item; + }, + $queryResult + ); + $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]); + unset($queryResult); + + } else { + $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1); + } + + // Flattening the arrays + foreach ($nodes as $node) { + if (isset($node[200][$calDataProp])) { + $blobs[$node['href']] = $node[200][$calDataProp]; + } + } + unset($nodes); + + $mergedCalendar = $this->mergeObjects( + $properties, + $blobs + ); + + if ($expand) { + $calendarTimeZone = null; + // We're expanding, and for that we need to figure out the + // calendar's timezone. + $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($path, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + // Destroy circular references to PHP will GC the object. + $vtimezoneObj->destroy(); + unset($vtimezoneObj); + } else { + // Defaulting to UTC. + $calendarTimeZone = new DateTimeZone('UTC'); + } + + $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone); + } + + $filenameExtension = '.ics'; + + switch ($format) { + case 'text/calendar' : + $mergedCalendar = $mergedCalendar->serialize(); + $filenameExtension = '.ics'; + break; + case 'application/calendar+json' : + $mergedCalendar = json_encode($mergedCalendar->jsonSerialize()); + $filenameExtension = '.json'; + break; + } + + $filename = preg_replace( + '/[^a-zA-Z0-9-_ ]/um', + '', + $calendarNode->getName() + ); + $filename .= '-' . date('Y-m-d') . $filenameExtension; + + $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $response->setHeader('Content-Type', $format); + + $response->setStatus(200); + $response->setBody($mergedCalendar); + + } + + /** + * Merges all calendar objects, and builds one big iCalendar blob. + * + * @param array $properties Some CalDAV properties + * @param array $inputObjects + * @return VObject\Component\VCalendar + */ + function mergeObjects(array $properties, array $inputObjects) { + + $calendar = new VObject\Component\VCalendar(); + $calendar->VERSION = '2.0'; + if (DAV\Server::$exposeVersion) { + $calendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; + } else { + $calendar->PRODID = '-//SabreDAV//SabreDAV//EN'; + } + if (isset($properties['{DAV:}displayname'])) { + $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname']; + } + if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) { + $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color']; + } + + $collectedTimezones = []; + + $timezones = []; + $objects = []; + + foreach ($inputObjects as $href => $inputObject) { + + $nodeComp = VObject\Reader::read($inputObject); + + foreach ($nodeComp->children() as $child) { + + switch ($child->name) { + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + $objects[] = clone $child; + break; + + // VTIMEZONE is special, because we need to filter out the duplicates + case 'VTIMEZONE' : + // Naively just checking tzid. + if (in_array((string)$child->TZID, $collectedTimezones)) continue; + + $timezones[] = clone $child; + $collectedTimezones[] = $child->TZID; + break; + + } + + } + // Destroy circular references to PHP will GC the object. + $nodeComp->destroy(); + unset($nodeComp); + + } + + foreach ($timezones as $tz) $calendar->add($tz); + foreach ($objects as $obj) $calendar->add($obj); + + return $calendar; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'ics-export'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.', + 'link' => 'http://sabre.io/dav/ics-export-plugin/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendar.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendar.php new file mode 100644 index 000000000000..7cf4b12561a7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/ICalendar.php @@ -0,0 +1,18 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns all notifications for a principal + * + * @return array + */ + function getChildren() { + + $children = []; + $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri); + + foreach ($notifications as $notification) { + + $children[] = new Node( + $this->caldavBackend, + $this->principalUri, + $notification + ); + } + + return $children; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + return 'notifications'; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php new file mode 100644 index 000000000000..008e87435a09 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php @@ -0,0 +1,23 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + $this->notification = $notification; + + } + + /** + * Returns the path name for this notification + * + * @return string + */ + function getName() { + + return $this->notification->getId() . '.xml'; + + } + + /** + * Returns the etag for the notification. + * + * The etag must be surrounded by litteral double-quotes. + * + * @return string + */ + function getETag() { + + return $this->notification->getETag(); + + } + + /** + * This method must return an xml element, using the + * Sabre\CalDAV\Xml\Notification\NotificationInterface classes. + * + * @return NotificationInterface + */ + function getNotificationType() { + + return $this->notification; + + } + + /** + * Deletes this notification + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php new file mode 100644 index 000000000000..e742351f5be7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php @@ -0,0 +1,180 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('propFind', [$this, 'propFind']); + + $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification'; + + array_push($server->protectedProperties, + '{' . self::NS_CALENDARSERVER . '}notification-URL', + '{' . self::NS_CALENDARSERVER . '}notificationtype' + ); + + } + + /** + * PropFind + * + * @param PropFind $propFind + * @param BaseINode $node + * @return void + */ + function propFind(PropFind $propFind, BaseINode $node) { + + $caldavPlugin = $this->server->getPlugin('caldav'); + + if ($node instanceof DAVACL\IPrincipal) { + + $principalUrl = $node->getPrincipalUrl(); + + // notification-URL property + $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) { + + $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/'; + return new DAV\Xml\Property\Href($notificationPath); + + }); + + } + + if ($node instanceof INode) { + + $propFind->handle( + '{' . self::NS_CALENDARSERVER . '}notificationtype', + [$node, 'getNotificationType'] + ); + + } + + } + + /** + * This event is triggered before the usual GET request handler. + * + * We use this to intercept GET calls to notification nodes, and return the + * proper response. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (DAV\Exception\NotFound $e) { + return; + } + + if (!$node instanceof INode) + return; + + $writer = $this->server->xml->getWriter(); + $writer->contextUri = $this->server->getBaseUri(); + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}notification'); + $node->getNotificationType()->xmlSerializeFull($writer); + $writer->endElement(); + + $response->setHeader('Content-Type', 'application/xml'); + $response->setHeader('ETag', $node->getETag()); + $response->setStatus(200); + $response->setBody($writer->outputMemory()); + + // Return false to break the event chain. + return false; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.', + 'link' => 'http://sabre.io/dav/caldav-sharing/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Plugin.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Plugin.php new file mode 100644 index 000000000000..def11d52dff4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Plugin.php @@ -0,0 +1,1068 @@ +server->tree->getNodeForPath($parent); + + if ($node instanceof DAV\IExtendedCollection) { + try { + $node->getChild($name); + } catch (DAV\Exception\NotFound $e) { + return ['MKCALENDAR']; + } + } + return []; + + } + + /** + * Returns the path to a principal's calendar home. + * + * The return url must not end with a slash. + * This function should return null in case a principal did not have + * a calendar home. + * + * @param string $principalUrl + * @return string + */ + function getCalendarHomeForPrincipal($principalUrl) { + + // The default behavior for most sabre/dav servers is that there is a + // principals root node, which contains users directly under it. + // + // This function assumes that there are two components in a principal + // path. If there's more, we don't return a calendar home. This + // excludes things like the calendar-proxy-read principal (which it + // should). + $parts = explode('/', trim($principalUrl, '/')); + if (count($parts) !== 2) return; + if ($parts[0] !== 'principals') return; + + return self::CALENDAR_ROOT . '/' . $parts[1]; + + } + + /** + * Returns a list of features for the DAV: HTTP header. + * + * @return array + */ + function getFeatures() { + + return ['calendar-access', 'calendar-proxy']; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'caldav'; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + + $reports = []; + if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) { + $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget'; + $reports[] = '{' . self::NS_CALDAV . '}calendar-query'; + } + if ($node instanceof ICalendar) { + $reports[] = '{' . self::NS_CALDAV . '}free-busy-query'; + } + // iCal has a bug where it assumes that sync support is enabled, only + // if we say we support it on the calendar-home, even though this is + // not actually the case. + if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) { + $reports[] = '{DAV:}sync-collection'; + } + return $reports; + + } + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + + $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']); + $server->on('report', [$this, 'report']); + $server->on('propFind', [$this, 'propFind']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); + $server->on('afterMethod:GET', [$this, 'httpAfterGET']); + $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); + + $server->xml->namespaceMap[self::NS_CALDAV] = 'cal'; + $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs'; + + $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'; + + $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar'; + + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write'; + + array_push($server->protectedProperties, + + '{' . self::NS_CALDAV . '}supported-calendar-component-set', + '{' . self::NS_CALDAV . '}supported-calendar-data', + '{' . self::NS_CALDAV . '}max-resource-size', + '{' . self::NS_CALDAV . '}min-date-time', + '{' . self::NS_CALDAV . '}max-date-time', + '{' . self::NS_CALDAV . '}max-instances', + '{' . self::NS_CALDAV . '}max-attendees-per-instance', + '{' . self::NS_CALDAV . '}calendar-home-set', + '{' . self::NS_CALDAV . '}supported-collation-set', + '{' . self::NS_CALDAV . '}calendar-data', + + // CalendarServer extensions + '{' . self::NS_CALENDARSERVER . '}getctag', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for' + + ); + + if ($aclPlugin = $server->getPlugin('acl')) { + $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address'; + } + } + + /** + * This functions handles REPORT requests specific to CalDAV + * + * @param string $reportName + * @param mixed $report + * @param mixed $path + * @return bool + */ + function report($reportName, $report, $path) { + + switch ($reportName) { + case '{' . self::NS_CALDAV . '}calendar-multiget' : + $this->server->transactionType = 'report-calendar-multiget'; + $this->calendarMultiGetReport($report); + return false; + case '{' . self::NS_CALDAV . '}calendar-query' : + $this->server->transactionType = 'report-calendar-query'; + $this->calendarQueryReport($report); + return false; + case '{' . self::NS_CALDAV . '}free-busy-query' : + $this->server->transactionType = 'report-free-busy-query'; + $this->freeBusyQueryReport($report); + return false; + + } + + + } + + /** + * This function handles the MKCALENDAR HTTP method, which creates + * a new calendar. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMkCalendar(RequestInterface $request, ResponseInterface $response) { + + $body = $request->getBodyAsString(); + $path = $request->getPath(); + + $properties = []; + + if ($body) { + + try { + $mkcalendar = $this->server->xml->expect( + '{urn:ietf:params:xml:ns:caldav}mkcalendar', + $body + ); + } catch (\Sabre\Xml\ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + $properties = $mkcalendar->getProperties(); + + } + + // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored + // subscriptions. Before that it used MKCOL which was the correct way + // to do this. + // + // If the body had a {DAV:}resourcetype, it means we stumbled upon this + // request, and we simply use it instead of the pre-defined list. + if (isset($properties['{DAV:}resourcetype'])) { + $resourceType = $properties['{DAV:}resourcetype']->getValue(); + } else { + $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar']; + } + + $this->server->createCollection($path, new MkCol($resourceType, $properties)); + + $response->setStatus(201); + $response->setHeader('Content-Length', 0); + + // This breaks the method chain. + return false; + } + + /** + * PropFind + * + * This method handler is invoked before any after properties for a + * resource are fetched. This allows us to add in any CalDAV specific + * properties. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $ns = '{' . self::NS_CALDAV . '}'; + + if ($node instanceof ICalendarObjectContainer) { + + $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize); + $propFind->handle($ns . 'supported-calendar-data', function() { + return new Xml\Property\SupportedCalendarData(); + }); + $propFind->handle($ns . 'supported-collation-set', function() { + return new Xml\Property\SupportedCollationSet(); + }); + + } + + if ($node instanceof DAVACL\IPrincipal) { + + $principalUrl = $node->getPrincipalUrl(); + + $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) { + + $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl); + if (is_null($calendarHomePath)) return null; + return new LocalHref($calendarHomePath . '/'); + + }); + // The calendar-user-address-set property is basically mapped to + // the {DAV:}alternate-URI-set property. + $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) { + $addresses = $node->getAlternateUriSet(); + $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/'; + return new LocalHref($addresses); + }); + // For some reason somebody thought it was a good idea to add + // another one of these properties. We're supporting it too. + $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) { + $addresses = $node->getAlternateUriSet(); + $emails = []; + foreach ($addresses as $address) { + if (substr($address, 0, 7) === 'mailto:') { + $emails[] = substr($address, 7); + } + } + return new Xml\Property\EmailAddressSet($emails); + }); + + // These two properties are shortcuts for ical to easily find + // other principals this principal has access to. + $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for'; + $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'; + + if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) { + + $aclPlugin = $this->server->getPlugin('acl'); + $membership = $aclPlugin->getPrincipalMembership($propFind->getPath()); + $readList = []; + $writeList = []; + + foreach ($membership as $group) { + + $groupNode = $this->server->tree->getNodeForPath($group); + + $listItem = Uri\split($group)[0] . '/'; + + // If the node is either ap proxy-read or proxy-write + // group, we grab the parent principal and add it to the + // list. + if ($groupNode instanceof Principal\IProxyRead) { + $readList[] = $listItem; + } + if ($groupNode instanceof Principal\IProxyWrite) { + $writeList[] = $listItem; + } + + } + + $propFind->set($propRead, new LocalHref($readList)); + $propFind->set($propWrite, new LocalHref($writeList)); + + } + + } // instanceof IPrincipal + + if ($node instanceof ICalendarObject) { + + // The calendar-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. + // Therefore we simply expose it as a property. + $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) { + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + // Taking out \r to not screw up the xml output + return str_replace("\r", "", $val); + + }); + + } + + } + + /** + * This function handles the calendar-multiget REPORT. + * + * This report is used by the client to fetch the content of a series + * of urls. Effectively avoiding a lot of redundant requests. + * + * @param CalendarMultiGetReport $report + * @return void + */ + function calendarMultiGetReport($report) { + + $needsJson = $report->contentType === 'application/calendar+json'; + + $timeZones = []; + $propertyList = []; + + $paths = array_map( + [$this->server, 'calculateUri'], + $report->hrefs + ); + + foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) { + + if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { + $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); + + if ($report->expand) { + // We're expanding, and for that we need to figure out the + // calendar's timezone. + list($calendarPath) = Uri\split($uri); + if (!isset($timeZones[$calendarPath])) { + // Checking the calendar-timezone property. + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($calendarPath, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + } else { + // Defaulting to UTC. + $timeZone = new DateTimeZone('UTC'); + } + $timeZones[$calendarPath] = $timeZone; + } + + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]); + } + if ($needsJson) { + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } else { + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + } + + $propertyList[] = $objProps; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal')); + + } + + /** + * This function handles the calendar-query REPORT + * + * This report is used by clients to request calendar objects based on + * complex conditions. + * + * @param Xml\Request\CalendarQueryReport $report + * @return void + */ + function calendarQueryReport($report) { + + $path = $this->server->getRequestUri(); + + $needsJson = $report->contentType === 'application/calendar+json'; + + $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); + $depth = $this->server->getHTTPDepth(0); + + // The default result is an empty array + $result = []; + + $calendarTimeZone = null; + if ($report->expand) { + // We're expanding, and for that we need to figure out the + // calendar's timezone. + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($path, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + + // Destroy circular references so PHP will garbage collect the + // object. + $vtimezoneObj->destroy(); + } else { + // Defaulting to UTC. + $calendarTimeZone = new DateTimeZone('UTC'); + } + } + + // The calendarobject was requested directly. In this case we handle + // this locally. + if ($depth == 0 && $node instanceof ICalendarObject) { + + $requestedCalendarData = true; + $requestedProperties = $report->properties; + + if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { + + // We always retrieve calendar-data, as we need it for filtering. + $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; + + // If calendar-data wasn't explicitly requested, we need to remove + // it after processing. + $requestedCalendarData = false; + } + + $properties = $this->server->getPropertiesForPath( + $path, + $requestedProperties, + 0 + ); + + // This array should have only 1 element, the first calendar + // object. + $properties = current($properties); + + // If there wasn't any calendar-data returned somehow, we ignore + // this. + if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { + + $validator = new CalendarQueryValidator(); + + $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + if ($validator->validate($vObject, $report->filters)) { + + // If the client didn't require the calendar-data property, + // we won't give it back. + if (!$requestedCalendarData) { + unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + } else { + + + if ($report->expand) { + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); + } + if ($needsJson) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } elseif ($report->expand) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + } + + $result = [$properties]; + + } + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + + } + + } + + if ($node instanceof ICalendarObjectContainer && $depth === 0) { + + if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) { + // Microsoft clients incorrectly supplied depth as 0, when it actually + // should have set depth to 1. We're implementing a workaround here + // to deal with this. + // + // This targets at least the following clients: + // Windows 10 + // Windows Phone 8, 10 + $depth = 1; + } else { + throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1'); + } + + } + + // If we're dealing with a calendar, the calendar itself is responsible + // for the calendar-query. + if ($node instanceof ICalendarObjectContainer && $depth == 1) { + + $nodePaths = $node->calendarQuery($report->filters); + + foreach ($nodePaths as $path) { + + list($properties) = + $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties); + + if (($needsJson || $report->expand)) { + $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); + + if ($report->expand) { + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); + } + + if ($needsJson) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } else { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + } + $result[] = $properties; + + } + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /** + * This method is responsible for parsing the request and generating the + * response for the CALDAV:free-busy-query REPORT. + * + * @param Xml\Request\FreeBusyQueryReport $report + * @return void + */ + protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) { + + $uri = $this->server->getRequestUri(); + + $acl = $this->server->getPlugin('acl'); + if ($acl) { + $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); + } + + $calendar = $this->server->tree->getNodeForPath($uri); + if (!$calendar instanceof ICalendar) { + throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); + } + + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + + // Figuring out the default timezone for the calendar, for floating + // times. + $calendarProps = $this->server->getProperties($uri, [$tzProp]); + + if (isset($calendarProps[$tzProp])) { + $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + // Destroy circular references so PHP will garbage collect the object. + $vtimezoneObj->destroy(); + } else { + $calendarTimeZone = new DateTimeZone('UTC'); + } + + // Doing a calendar-query first, to make sure we get the most + // performance. + $urls = $calendar->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $report->start, + 'end' => $report->end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + $objects = array_map(function($url) use ($calendar) { + $obj = $calendar->getChild($url)->get(); + return $obj; + }, $urls); + + $generator = new VObject\FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($report->start, $report->end); + $generator->setTimeZone($calendarTimeZone); + $result = $generator->getResult(); + $result = $result->serialize(); + + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); + $this->server->httpResponse->setHeader('Content-Length', strlen($result)); + $this->server->httpResponse->setBody($result); + + } + + /** + * This method is triggered before a file gets updated with new content. + * + * This plugin uses this method to ensure that CalDAV objects receive + * valid calendar data. + * + * @param string $path + * @param DAV\IFile $node + * @param resource $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) { + + if (!$node instanceof ICalendarObject) + return; + + // We're onyl interested in ICalendarObject nodes that are inside of a + // real calendar. This is to avoid triggering validation and scheduling + // for non-calendars (such as an inbox). + list($parent) = Uri\split($path); + $parentNode = $this->server->tree->getNodeForPath($parent); + + if (!$parentNode instanceof ICalendar) + return; + + $this->validateICalendar( + $data, + $path, + $modified, + $this->server->httpRequest, + $this->server->httpResponse, + false + ); + + } + + /** + * This method is triggered before a new file is created. + * + * This plugin uses this method to ensure that newly created calendar + * objects contain valid calendar data. + * + * @param string $path + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) { + + if (!$parentNode instanceof ICalendar) + return; + + $this->validateICalendar( + $data, + $path, + $modified, + $this->server->httpRequest, + $this->server->httpResponse, + true + ); + + } + + /** + * Checks if the submitted iCalendar data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @param string $path + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @param RequestInterface $request The http request. + * @param ResponseInterface $response The http response. + * @param bool $isNew Is the item a new one, or an update. + * @return void + */ + protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + $before = $data; + + try { + + // If the data starts with a [, we can reasonably assume we're dealing + // with a jCal object. + if (substr($data, 0, 1) === '[') { + $vobj = VObject\Reader::readJson($data); + + // Converting $data back to iCalendar, as that's what we + // technically support everywhere. + $data = $vobj->serialize(); + $modified = true; + } else { + $vobj = VObject\Reader::read($data); + } + + } catch (VObject\ParseException $e) { + + throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage()); + + } + + if ($vobj->name !== 'VCALENDAR') { + throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.'); + } + + $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + + // Get the Supported Components for the target calendar + list($parentPath) = Uri\split($path); + $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]); + + if (isset($calendarProperties[$sCCS])) { + $supportedComponents = $calendarProperties[$sCCS]->getValue(); + } else { + $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT']; + } + + $foundType = null; + + foreach ($vobj->getComponents() as $component) { + switch ($component->name) { + case 'VTIMEZONE' : + continue 2; + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + $foundType = $component->name; + break; + } + + } + + if (!$foundType || !in_array($foundType, $supportedComponents)) { + throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents)); + } + + $options = VObject\Node::PROFILE_CALDAV; + $prefer = $this->server->getHTTPPrefer(); + + if ($prefer['handling'] !== 'strict') { + $options |= VObject\Node::REPAIR; + } + + $messages = $vobj->validate($options); + + $highestLevel = 0; + $warningMessage = null; + + // $messages contains a list of problems with the vcard, along with + // their severity. + foreach ($messages as $message) { + + if ($message['level'] > $highestLevel) { + // Recording the highest reported error level. + $highestLevel = $message['level']; + $warningMessage = $message['message']; + } + switch ($message['level']) { + + case 1 : + // Level 1 means that there was a problem, but it was repaired. + $modified = true; + break; + case 2 : + // Level 2 means a warning, but not critical + break; + case 3 : + // Level 3 means a critical error + throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']); + + } + + } + if ($warningMessage) { + $response->setHeader( + 'X-Sabre-Ew-Gross', + 'iCalendar validation warning: ' . $warningMessage + ); + } + + // We use an extra variable to allow event handles to tell us whether + // the object was modified or not. + // + // This helps us determine if we need to re-serialize the object. + $subModified = false; + + $this->server->emit( + 'calendarObjectChange', + [ + $request, + $response, + $vobj, + $parentPath, + &$subModified, + $isNew + ] + ); + + if ($modified || $subModified) { + // An event handler told us that it modified the object. + $data = $vobj->serialize(); + + // Using md5 to figure out if there was an *actual* change. + if (!$modified && strcmp($data, $before) !== 0) { + $modified = true; + } + + } + + // Destroy circular references so PHP will garbage collect the object. + $vobj->destroy(); + + } + + /** + * This method is triggered whenever a subsystem reqeuests the privileges + * that are supported on a particular node. + * + * @param INode $node + * @param array $supportedPrivilegeSet + */ + function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) { + + if ($node instanceof ICalendar) { + $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + } + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof CalendarHome) + return; + + $output .= '

+

Create new calendar

+ + +
+
+ +
+ '; + + return false; + + } + + /** + * This event is triggered after GET requests. + * + * This is used to transform data into jCal, if this was requested. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpAfterGet(RequestInterface $request, ResponseInterface $response) { + + if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) { + return; + } + + $result = HTTP\Util::negotiate( + $request->getHeader('Accept'), + ['text/calendar', 'application/calendar+json'] + ); + + if ($result !== 'application/calendar+json') { + // Do nothing + return; + } + + // Transforming. + $vobj = VObject\Reader::read($response->getBody()); + + $jsonBody = json_encode($vobj->jsonSerialize()); + $response->setBody($jsonBody); + + // Destroy circular references so PHP will garbage collect the object. + $vobj->destroy(); + + $response->setHeader('Content-Type', 'application/calendar+json'); + $response->setHeader('Content-Length', strlen($jsonBody)); + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for CalDAV (rfc4791)', + 'link' => 'http://sabre.io/dav/caldav/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php new file mode 100644 index 000000000000..e19719a76d86 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php @@ -0,0 +1,33 @@ +principalBackend, $principalInfo); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php new file mode 100644 index 000000000000..7dd3759329c1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php @@ -0,0 +1,19 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + return 'calendar-proxy-read'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden('Permission denied to delete node'); + + } + + /** + * Renames the node + * + * @param string $name The new name + * @throws DAV\Exception\Forbidden + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden('Permission denied to rename file'); + + } + + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + return []; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalInfo['uri'] . '/' . $this->getName(); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void + */ + function setGroupMemberSet(array $principals) { + + $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); + + } + + /** + * Returns the displayname + * + * This should be a human readable name for the principal. + * If none is available, return the nodename. + * + * @return string + */ + function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php new file mode 100644 index 000000000000..43dd9bf07bfd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php @@ -0,0 +1,181 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + return 'calendar-proxy-write'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden('Permission denied to delete node'); + + } + + /** + * Renames the node + * + * @param string $name The new name + * @throws DAV\Exception\Forbidden + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden('Permission denied to rename file'); + + } + + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + return []; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalInfo['uri'] . '/' . $this->getName(); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void + */ + function setGroupMemberSet(array $principals) { + + $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); + + } + + /** + * Returns the displayname + * + * This should be a human readable name for the principal. + * If none is available, return the nodename. + * + * @return string + */ + function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/User.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/User.php new file mode 100644 index 000000000000..6e97e7cca560 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Principal/User.php @@ -0,0 +1,135 @@ +principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name); + if (!$principal) { + throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); + } + if ($name === 'calendar-proxy-read') + return new ProxyRead($this->principalBackend, $this->principalProperties); + + if ($name === 'calendar-proxy-write') + return new ProxyWrite($this->principalBackend, $this->principalProperties); + + throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $r = []; + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) { + $r[] = new ProxyRead($this->principalBackend, $this->principalProperties); + } + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) { + $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties); + } + + return $r; + + } + + /** + * Returns whether or not the child node exists + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + $this->getChild($name); + return true; + } catch (DAV\Exception\NotFound $e) { + return false; + } + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = parent::getACL(); + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + return $acl; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php new file mode 100644 index 000000000000..c9fd77d93535 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php @@ -0,0 +1,15 @@ +senderEmail = $senderEmail; + + } + + /* + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $server->on('schedule', [$this, 'schedule'], 120); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'imip'; + + } + + /** + * Event handler for the 'schedule' event. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function schedule(ITip\Message $iTipMessage) { + + // Not sending any emails if the system considers the update + // insignificant. + if (!$iTipMessage->significantChange) { + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + } + return; + } + + $summary = $iTipMessage->message->VEVENT->SUMMARY; + + if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') + return; + + if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') + return; + + $sender = substr($iTipMessage->sender, 7); + $recipient = substr($iTipMessage->recipient, 7); + + if ($iTipMessage->senderName) { + $sender = $iTipMessage->senderName . ' <' . $sender . '>'; + } + if ($iTipMessage->recipientName) { + $recipient = $iTipMessage->recipientName . ' <' . $recipient . '>'; + } + + $subject = 'SabreDAV iTIP message'; + switch (strtoupper($iTipMessage->method)) { + case 'REPLY' : + $subject = 'Re: ' . $summary; + break; + case 'REQUEST' : + $subject = $summary; + break; + case 'CANCEL' : + $subject = 'Cancelled: ' . $summary; + break; + } + + $headers = [ + 'Reply-To: ' . $sender, + 'From: ' . $this->senderEmail, + 'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method, + ]; + if (DAV\Server::$exposeVersion) { + $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION; + } + $this->mail( + $recipient, + $subject, + $iTipMessage->message->serialize(), + $headers + ); + $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; + + } + + // @codeCoverageIgnoreStart + // This is deemed untestable in a reasonable manner + + /** + * This function is responsible for sending the actual email. + * + * @param string $to Recipient email address + * @param string $subject Subject of the email + * @param string $body iCalendar body + * @param array $headers List of headers + * @return void + */ + protected function mail($to, $subject, $body, array $headers) { + + mail($to, $subject, $body, implode("\r\n", $headers)); + + } + + // @codeCoverageIgnoreEnd + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Email delivery (rfc6047) for CalDAV scheduling', + 'link' => 'http://sabre.io/dav/scheduling/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php new file mode 100644 index 000000000000..88fbdc4114bf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php @@ -0,0 +1,15 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return 'inbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri); + $children = []; + foreach ($objs as $obj) { + //$obj['acl'] = $this->getACL(); + $obj['principaluri'] = $this->principalUri; + $children[] = new SchedulingObject($this->caldavBackend, $obj); + } + return $children; + + } + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After successful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + */ + function createFile($name, $data = null) { + + $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + ]; + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * @param array $filters + * @return array + */ + function calendarQuery(array $filters) { + + $result = []; + $validator = new CalDAV\CalendarQueryValidator(); + + $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri); + foreach ($objects as $object) { + $vObject = VObject\Reader::read($object['calendardata']); + if ($validator->validate($vObject, $filters)) { + $result[] = $object['uri']; + } + + // Destroy circular references to PHP will GC the object. + $vObject->destroy(); + } + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php new file mode 100644 index 000000000000..888ea308626d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php @@ -0,0 +1,123 @@ +principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return 'outbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + return []; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php new file mode 100644 index 000000000000..0b991e61979a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php @@ -0,0 +1,1066 @@ +server = $server; + $server->on('method:POST', [$this, 'httpPost']); + $server->on('propFind', [$this, 'propFind']); + $server->on('propPatch', [$this, 'propPatch']); + $server->on('calendarObjectChange', [$this, 'calendarObjectChange']); + $server->on('beforeUnbind', [$this, 'beforeUnbind']); + $server->on('schedule', [$this, 'scheduleLocalDelivery']); + $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); + + $ns = '{' . self::NS_CALDAV . '}'; + + /** + * This information ensures that the {DAV:}resourcetype property has + * the correct values. + */ + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox'; + + /** + * Properties we protect are made read-only by the server. + */ + array_push($server->protectedProperties, + $ns . 'schedule-inbox-URL', + $ns . 'schedule-outbox-URL', + $ns . 'calendar-user-address-set', + $ns . 'calendar-user-type', + $ns . 'schedule-default-calendar-URL' + ); + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + try { + $node = $this->server->tree->getNodeForPath($uri); + } catch (NotFound $e) { + return []; + } + + if ($node instanceof IOutbox) { + return ['POST']; + } + + return []; + + } + + /** + * This method handles POST request for the outbox. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + // Checking if this is a text/calendar content type + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'text/calendar') !== 0) { + return; + } + + $path = $request->getPath(); + + // Checking if we're talking to an outbox + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + return; + } + if (!$node instanceof IOutbox) + return; + + $this->server->transactionType = 'post-caldav-outbox'; + $this->outboxRequest($node, $request, $response); + + // Returning false breaks the event chain and tells the server we've + // handled the request. + return false; + + } + + /** + * This method handler is invoked during fetching of properties. + * + * We use this event to add calendar-auto-schedule-specific properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + if ($node instanceof DAVACL\IPrincipal) { + + $caldavPlugin = $this->server->getPlugin('caldav'); + $principalUrl = $node->getPrincipalUrl(); + + // schedule-outbox-URL property + $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) { + + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + if (!$calendarHomePath) { + return null; + } + $outboxPath = $calendarHomePath . '/outbox/'; + + return new LocalHref($outboxPath); + + }); + // schedule-inbox-URL property + $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) { + + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + if (!$calendarHomePath) { + return null; + } + $inboxPath = $calendarHomePath . '/inbox/'; + + return new LocalHref($inboxPath); + + }); + + $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) { + + // We don't support customizing this property yet, so in the + // meantime we just grab the first calendar in the home-set. + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + + if (!$calendarHomePath) { + return null; + } + + $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set'; + + $result = $this->server->getPropertiesForPath($calendarHomePath, [ + '{DAV:}resourcetype', + '{DAV:}share-access', + $sccs, + ], 1); + + foreach ($result as $child) { + if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar')) { + // Node is either not a calendar + continue; + } + if (isset($child[200]['{DAV:}share-access'])) { + $shareAccess = $child[200]['{DAV:}share-access']->getValue(); + if ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) { + // Node is a shared node, not owned by the relevant + // user. + continue; + } + + } + if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) { + // Either there is no supported-calendar-component-set + // (which is fine) or we found one that supports VEVENT. + return new LocalHref($child['href']); + } + } + + }); + + // The server currently reports every principal to be of type + // 'INDIVIDUAL' + $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() { + + return 'INDIVIDUAL'; + + }); + + } + + // Mapping the old property to the new property. + $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) { + + // In case it wasn't clear, the only difference is that we map the + // old property to a different namespace. + $availProp = '{' . self::NS_CALDAV . '}calendar-availability'; + $subPropFind = new PropFind( + $propFind->getPath(), + [$availProp] + ); + + $this->server->getPropertiesByNode( + $subPropFind, + $node + ); + + $propFind->set( + '{http://calendarserver.org/ns/}calendar-availability', + $subPropFind->get($availProp), + $subPropFind->getStatus($availProp) + ); + + }); + + } + + /** + * This method is called during property updates. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + // Mapping the old property to the new property. + $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) { + + $availProp = '{' . self::NS_CALDAV . '}calendar-availability'; + $subPropPatch = new PropPatch([$availProp => $value]); + $this->server->emit('propPatch', [$path, $subPropPatch]); + $subPropPatch->commit(); + + return $subPropPatch->getResult()[$availProp]; + + }); + + } + + /** + * This method is triggered whenever there was a calendar object gets + * created or updated. + * + * @param RequestInterface $request HTTP request + * @param ResponseInterface $response HTTP Response + * @param VCalendar $vCal Parsed iCalendar object + * @param mixed $calendarPath Path to calendar collection + * @param mixed $modified The iCalendar object has been touched. + * @param mixed $isNew Whether this was a new item or we're updating one + * @return void + */ + function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) { + + if (!$this->scheduleReply($this->server->httpRequest)) { + return; + } + + $calendarNode = $this->server->tree->getNodeForPath($calendarPath); + + $addresses = $this->getAddressesForPrincipal( + $calendarNode->getOwner() + ); + + if (!$isNew) { + $node = $this->server->tree->getNodeForPath($request->getPath()); + $oldObj = Reader::read($node->get()); + } else { + $oldObj = null; + } + + $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified); + + if ($oldObj) { + // Destroy circular references so PHP will GC the object. + $oldObj->destroy(); + } + + } + + /** + * This method is responsible for delivering the ITip message. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function deliver(ITip\Message $iTipMessage) { + + $this->server->emit('schedule', [$iTipMessage]); + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message'; + } + // In case the change was considered 'insignificant', we are going to + // remove any error statuses, if any. See ticket #525. + list($baseCode) = explode('.', $iTipMessage->scheduleStatus); + if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) { + $iTipMessage->scheduleStatus = null; + } + + } + + /** + * This method is triggered before a file gets deleted. + * + * We use this event to make sure that when this happens, attendees get + * cancellations, and organizers get 'DECLINED' statuses. + * + * @param string $path + * @return void + */ + function beforeUnbind($path) { + + // FIXME: We shouldn't trigger this functionality when we're issuing a + // MOVE. This is a hack. + if ($this->server->httpRequest->getMethod() === 'MOVE') return; + + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) { + return; + } + + if (!$this->scheduleReply($this->server->httpRequest)) { + return; + } + + $addresses = $this->getAddressesForPrincipal( + $node->getOwner() + ); + + $broker = new ITip\Broker(); + $messages = $broker->parseEvent(null, $addresses, $node->get()); + + foreach ($messages as $message) { + $this->deliver($message); + } + + } + + /** + * Event handler for the 'schedule' event. + * + * This handler attempts to look at local accounts to deliver the + * scheduling object. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function scheduleLocalDelivery(ITip\Message $iTipMessage) { + + $aclPlugin = $this->server->getPlugin('acl'); + + // Local delivery is not available if the ACL plugin is not loaded. + if (!$aclPlugin) { + return; + } + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient); + if (!$principalUri) { + $iTipMessage->scheduleStatus = '3.7;Could not find principal.'; + return; + } + + // We found a principal URL, now we need to find its inbox. + // Unfortunately we may not have sufficient privileges to find this, so + // we are temporarily turning off ACL to let this come through. + // + // Once we support PHP 5.5, this should be wrapped in a try..finally + // block so we can ensure that this privilege gets added again after. + $this->server->removeListener('propFind', [$aclPlugin, 'propFind']); + + $result = $this->server->getProperties( + $principalUri, + [ + '{DAV:}principal-URL', + $caldavNS . 'calendar-home-set', + $caldavNS . 'schedule-inbox-URL', + $caldavNS . 'schedule-default-calendar-URL', + '{http://sabredav.org/ns}email-address', + ] + ); + + // Re-registering the ACL event + $this->server->on('propFind', [$aclPlugin, 'propFind'], 20); + + if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) { + $iTipMessage->scheduleStatus = '5.2;Could not find local inbox'; + return; + } + if (!isset($result[$caldavNS . 'calendar-home-set'])) { + $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set'; + return; + } + if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) { + $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property'; + return; + } + + $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref(); + $homePath = $result[$caldavNS . 'calendar-home-set']->getHref(); + $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref(); + + if ($iTipMessage->method === 'REPLY') { + $privilege = 'schedule-deliver-reply'; + } else { + $privilege = 'schedule-deliver-invite'; + } + + if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) { + $iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.'; + return; + } + + // Next, we're going to find out if the item already exits in one of + // the users' calendars. + $uid = $iTipMessage->uid; + + $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics'; + + $home = $this->server->tree->getNodeForPath($homePath); + $inbox = $this->server->tree->getNodeForPath($inboxPath); + + $currentObject = null; + $objectNode = null; + $isNewNode = false; + + $result = $home->getCalendarObjectByUID($uid); + if ($result) { + // There was an existing object, we need to update probably. + $objectPath = $homePath . '/' . $result; + $objectNode = $this->server->tree->getNodeForPath($objectPath); + $oldICalendarData = $objectNode->get(); + $currentObject = Reader::read($oldICalendarData); + } else { + $isNewNode = true; + } + + $broker = new ITip\Broker(); + $newObject = $broker->processMessage($iTipMessage, $currentObject); + + $inbox->createFile($newFileName, $iTipMessage->message->serialize()); + + if (!$newObject) { + // We received an iTip message referring to a UID that we don't + // have in any calendars yet, and processMessage did not give us a + // calendarobject back. + // + // The implication is that processMessage did not understand the + // iTip message. + $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.'; + return; + } + + // Note that we are bypassing ACL on purpose by calling this directly. + // We may need to look a bit deeper into this later. Supporting ACL + // here would be nice. + if ($isNewNode) { + $calendar = $this->server->tree->getNodeForPath($calendarPath); + $calendar->createFile($newFileName, $newObject->serialize()); + } else { + // If the message was a reply, we may have to inform other + // attendees of this attendees status. Therefore we're shooting off + // another itipMessage. + if ($iTipMessage->method === 'REPLY') { + $this->processICalendarChange( + $oldICalendarData, + $newObject, + [$iTipMessage->recipient], + [$iTipMessage->sender] + ); + } + $objectNode->put($newObject->serialize()); + } + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + } + + /** + * This method is triggered whenever a subsystem requests the privileges + * that are supported on a particular node. + * + * We need to add a number of privileges for scheduling purposes. + * + * @param INode $node + * @param array $supportedPrivilegeSet + */ + function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) { + + $ns = '{' . self::NS_CALDAV . '}'; + if ($node instanceof IOutbox) { + $supportedPrivilegeSet[$ns . 'schedule-send'] = [ + 'abstract' => false, + 'aggregates' => [ + $ns . 'schedule-send-invite' => [ + 'abstract' => false, + 'aggregates' => [], + ], + $ns . 'schedule-send-reply' => [ + 'abstract' => false, + 'aggregates' => [], + ], + $ns . 'schedule-send-freebusy' => [ + 'abstract' => false, + 'aggregates' => [], + ], + // Privilege from an earlier scheduling draft, but still + // used by some clients. + $ns . 'schedule-post-vevent' => [ + 'abstract' => false, + 'aggregates' => [], + ], + ] + ]; + } + if ($node instanceof IInbox) { + $supportedPrivilegeSet[$ns . 'schedule-deliver'] = [ + 'abstract' => false, + 'aggregates' => [ + $ns . 'schedule-deliver-invite' => [ + 'abstract' => false, + 'aggregates' => [], + ], + $ns . 'schedule-deliver-reply' => [ + 'abstract' => false, + 'aggregates' => [], + ], + $ns . 'schedule-query-freebusy' => [ + 'abstract' => false, + 'aggregates' => [], + ], + ] + ]; + } + + } + + /** + * This method looks at an old iCalendar object, a new iCalendar object and + * starts sending scheduling messages based on the changes. + * + * A list of addresses needs to be specified, so the system knows who made + * the update, because the behavior may be different based on if it's an + * attendee or an organizer. + * + * This method may update $newObject to add any status changes. + * + * @param VCalendar|string $oldObject + * @param VCalendar $newObject + * @param array $addresses + * @param array $ignore Any addresses to not send messages to. + * @param bool $modified A marker to indicate that the original object + * modified by this process. + * @return void + */ + protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) { + + $broker = new ITip\Broker(); + $messages = $broker->parseEvent($newObject, $addresses, $oldObject); + + if ($messages) $modified = true; + + foreach ($messages as $message) { + + if (in_array($message->recipient, $ignore)) { + continue; + } + + $this->deliver($message); + + if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) { + if ($message->scheduleStatus) { + $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus(); + } + unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']); + + } else { + + if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) { + + if ($attendee->getNormalizedValue() === $message->recipient) { + if ($message->scheduleStatus) { + $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus(); + } + unset($attendee['SCHEDULE-FORCE-SEND']); + break; + } + + } + + } + + } + + } + + /** + * Returns a list of addresses that are associated with a principal. + * + * @param string $principal + * @return array + */ + protected function getAddressesForPrincipal($principal) { + + $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set'; + + $properties = $this->server->getProperties( + $principal, + [$CUAS] + ); + + // If we can't find this information, we'll stop processing + if (!isset($properties[$CUAS])) { + return; + } + + $addresses = $properties[$CUAS]->getHrefs(); + return $addresses; + + } + + /** + * This method handles POST requests to the schedule-outbox. + * + * Currently, two types of requests are supported: + * * FREEBUSY requests from RFC 6638 + * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04 + * + * The latter is from an expired early draft of the CalDAV scheduling + * extensions, but iCal depends on a feature from that spec, so we + * implement it. + * + * @param IOutbox $outboxNode + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) { + + $outboxPath = $request->getPath(); + + // Parsing the request body + try { + $vObject = VObject\Reader::read($request->getBody()); + } catch (VObject\ParseException $e) { + throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); + } + + // The incoming iCalendar object must have a METHOD property, and a + // component. The combination of both determines what type of request + // this is. + $componentType = null; + foreach ($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') { + $componentType = $component->name; + break; + } + } + if (is_null($componentType)) { + throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); + } + + // Validating the METHOD + $method = strtoupper((string)$vObject->METHOD); + if (!$method) { + throw new BadRequest('A METHOD property must be specified in iTIP messages'); + } + + // So we support one type of request: + // + // REQUEST with a VFREEBUSY component + + $acl = $this->server->getPlugin('acl'); + + if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') { + + $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-send-freebusy'); + $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response); + + // Destroy circular references so PHP can GC the object. + $vObject->destroy(); + unset($vObject); + + } else { + + throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint'); + + } + + } + + /** + * This method is responsible for parsing a free-busy query request and + * returning it's result. + * + * @param IOutbox $outbox + * @param VObject\Component $vObject + * @param RequestInterface $request + * @param ResponseInterface $response + * @return string + */ + protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) { + + $vFreeBusy = $vObject->VFREEBUSY; + $organizer = $vFreeBusy->ORGANIZER; + + $organizer = (string)$organizer; + + // Validating if the organizer matches the owner of the inbox. + $owner = $outbox->getOwner(); + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $uas = $caldavNS . 'calendar-user-address-set'; + $props = $this->server->getProperties($owner, [$uas]); + + if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) { + throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox'); + } + + if (!isset($vFreeBusy->ATTENDEE)) { + throw new BadRequest('You must at least specify 1 attendee'); + } + + $attendees = []; + foreach ($vFreeBusy->ATTENDEE as $attendee) { + $attendees[] = (string)$attendee; + } + + + if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) { + throw new BadRequest('DTSTART and DTEND must both be specified'); + } + + $startRange = $vFreeBusy->DTSTART->getDateTime(); + $endRange = $vFreeBusy->DTEND->getDateTime(); + + $results = []; + foreach ($attendees as $attendee) { + $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $scheduleResponse = $dom->createElement('cal:schedule-response'); + foreach ($this->server->xml->namespaceMap as $namespace => $prefix) { + + $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); + + } + $dom->appendChild($scheduleResponse); + + foreach ($results as $result) { + $xresponse = $dom->createElement('cal:response'); + + $recipient = $dom->createElement('cal:recipient'); + $recipientHref = $dom->createElement('d:href'); + + $recipientHref->appendChild($dom->createTextNode($result['href'])); + $recipient->appendChild($recipientHref); + $xresponse->appendChild($recipient); + + $reqStatus = $dom->createElement('cal:request-status'); + $reqStatus->appendChild($dom->createTextNode($result['request-status'])); + $xresponse->appendChild($reqStatus); + + if (isset($result['calendar-data'])) { + + $calendardata = $dom->createElement('cal:calendar-data'); + $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize()))); + $xresponse->appendChild($calendardata); + + } + $scheduleResponse->appendChild($xresponse); + } + + $response->setStatus(200); + $response->setHeader('Content-Type', 'application/xml'); + $response->setBody($dom->saveXML()); + + } + + /** + * Returns free-busy information for a specific address. The returned + * data is an array containing the following properties: + * + * calendar-data : A VFREEBUSY VObject + * request-status : an iTip status code. + * href: The principal's email address, as requested + * + * The following request status codes may be returned: + * * 2.0;description + * * 3.7;description + * + * @param string $email address + * @param \DateTimeInterface $start + * @param \DateTimeInterface $end + * @param VObject\Component $request + * @return array + */ + protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) { + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $aclPlugin = $this->server->getPlugin('acl'); + if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7); + + $result = $aclPlugin->principalSearch( + ['{http://sabredav.org/ns}email-address' => $email], + [ + '{DAV:}principal-URL', + $caldavNS . 'calendar-home-set', + $caldavNS . 'schedule-inbox-URL', + '{http://sabredav.org/ns}email-address', + + ] + ); + + if (!count($result)) { + return [ + 'request-status' => '3.7;Could not find principal', + 'href' => 'mailto:' . $email, + ]; + } + + if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) { + return [ + 'request-status' => '3.7;No calendar-home-set property found', + 'href' => 'mailto:' . $email, + ]; + } + if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) { + return [ + 'request-status' => '3.7;No schedule-inbox-URL property found', + 'href' => 'mailto:' . $email, + ]; + } + $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref(); + $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref(); + + // Do we have permission? + $aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 'schedule-query-freebusy'); + + // Grabbing the calendar list + $objects = []; + $calendarTimeZone = new DateTimeZone('UTC'); + + foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) { + if (!$node instanceof ICalendar) { + continue; + } + + $sct = $caldavNS . 'schedule-calendar-transp'; + $ctz = $caldavNS . 'calendar-timezone'; + $props = $node->getProperties([$sct, $ctz]); + + if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) { + // If a calendar is marked as 'transparent', it means we must + // ignore it for free-busy purposes. + continue; + } + + if (isset($props[$ctz])) { + $vtimezoneObj = VObject\Reader::read($props[$ctz]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + + // Destroy circular references so PHP can garbage collect the object. + $vtimezoneObj->destroy(); + + } + + // Getting the list of object uris within the time-range + $urls = $node->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $start, + 'end' => $end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + $calObjects = array_map(function($url) use ($node) { + $obj = $node->getChild($url)->get(); + return $obj; + }, $urls); + + $objects = array_merge($objects, $calObjects); + + } + + $inboxProps = $this->server->getProperties( + $inboxUrl, + $caldavNS . 'calendar-availability' + ); + + $vcalendar = new VObject\Component\VCalendar(); + $vcalendar->METHOD = 'REPLY'; + + $generator = new VObject\FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($start, $end); + $generator->setBaseObject($vcalendar); + $generator->setTimeZone($calendarTimeZone); + + if ($inboxProps) { + $generator->setVAvailability( + VObject\Reader::read( + $inboxProps[$caldavNS . 'calendar-availability'] + ) + ); + } + + $result = $generator->getResult(); + + $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email; + $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID; + $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER; + + return [ + 'calendar-data' => $result, + 'request-status' => '2.0;Success', + 'href' => 'mailto:' . $email, + ]; + } + + /** + * This method checks the 'Schedule-Reply' header + * and returns false if it's 'F', otherwise true. + * + * @param RequestInterface $request + * @return bool + */ + private function scheduleReply(RequestInterface $request) { + + $scheduleReply = $request->getHeader('Schedule-Reply'); + return $scheduleReply !== 'F'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds calendar-auto-schedule, as defined in rfc6638', + 'link' => 'http://sabre.io/dav/scheduling/', + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php new file mode 100644 index 000000000000..0cd05a965c33 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php @@ -0,0 +1,155 @@ +caldavBackend = $caldavBackend; + + if (!isset($objectData['uri'])) { + throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property'); + } + + $this->objectData = $objectData; + + } + + /** + * Returns the ICalendar-formatted object + * + * @return string + */ + function get() { + + // Pre-populating the 'calendardata' is optional, if we don't have it + // already we fetch it from the backend. + if (!isset($this->objectData['calendardata'])) { + $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']); + } + return $this->objectData['calendardata']; + + } + + /** + * Updates the ICalendar-formatted object + * + * @param string|resource $calendarData + * @return string + */ + function put($calendarData) { + + throw new MethodNotAllowed('Updating scheduling objects is not supported'); + + } + + /** + * Deletes the scheduling message + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->objectData['principaluri']; + + } + + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified in the object data. + // + + if (isset($this->objectData['acl'])) { + return $this->objectData['acl']; + } + + // The default ACL + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php new file mode 100644 index 000000000000..7a77616e3539 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php @@ -0,0 +1,229 @@ +calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED; + + } + + /** + * This function must return a URI that uniquely identifies the shared + * resource. This URI should be identical across instances, and is + * also used in several other XML bodies to connect invites to + * resources. + * + * This may simply be a relative reference to the original shared instance, + * but it could also be a urn. As long as it's a valid URI and unique. + * + * @return string + */ + function getShareResourceUri() { + + return $this->calendarInfo['share-resource-uri']; + + } + + /** + * Updates the list of sharees. + * + * Every item must be a Sharee object. + * + * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees + * @return void + */ + function updateInvites(array $sharees) { + + $this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees); + + } + + /** + * Returns the list of people whom this resource is shared with. + * + * Every item in the returned array must be a Sharee object with + * at least the following properties set: + * + * * $href + * * $shareAccess + * * $inviteStatus + * + * and optionally: + * + * * $properties + * + * @return \Sabre\DAV\Xml\Element\Sharee[] + */ + function getInvites() { + + return $this->caldavBackend->getInvites($this->calendarInfo['id']); + + } + + /** + * Marks this calendar as published. + * + * Publishing a calendar should automatically create a read-only, public, + * subscribable calendar. + * + * @param bool $value + * @return void + */ + function setPublishStatus($value) { + + $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value); + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = []; + + switch ($this->getShareAccess()) { + case SPlugin::ACCESS_NOTSHARED : + case SPlugin::ACCESS_SHAREDOWNER : + $acl[] = [ + 'privilege' => '{DAV:}share', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}share', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + // No break intentional! + case SPlugin::ACCESS_READWRITE : + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + // No break intentional! + case SPlugin::ACCESS_READ : + $acl[] = [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ]; + break; + } + return $acl; + + } + + + /** + * This method returns the ACL's for calendar objects in this calendar. + * The result of this method automatically gets passed to the + * calendar-object nodes in the calendar. + * + * @return array + */ + function getChildACL() { + + $acl = []; + + switch ($this->getShareAccess()) { + case SPlugin::ACCESS_NOTSHARED : + // No break intentional + case SPlugin::ACCESS_SHAREDOWNER : + // No break intentional + case SPlugin::ACCESS_READWRITE: + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + // No break intentional + case SPlugin::ACCESS_READ: + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ]; + break; + } + + return $acl; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php new file mode 100644 index 000000000000..5cce79678be8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php @@ -0,0 +1,401 @@ +server = $server; + + if (is_null($this->server->getPlugin('sharing'))) { + throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.'); + } + + array_push( + $this->server->protectedProperties, + '{' . Plugin::NS_CALENDARSERVER . '}invite', + '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', + '{' . Plugin::NS_CALENDARSERVER . '}shared-url' + ); + + $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share'; + $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply'; + + $this->server->on('propFind', [$this, 'propFindEarly']); + $this->server->on('propFind', [$this, 'propFindLate'], 150); + $this->server->on('propPatch', [$this, 'propPatch'], 40); + $this->server->on('method:POST', [$this, 'httpPost']); + + } + + /** + * This event is triggered when properties are requested for a certain + * node. + * + * This allows us to inject any properties early. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { + + if ($node instanceof ISharedCalendar) { + + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) { + + // Fetching owner information + $props = $this->server->getPropertiesForPath($node->getOwner(), [ + '{http://sabredav.org/ns}email-address', + '{DAV:}displayname', + ], 0); + + $ownerInfo = [ + 'href' => $node->getOwner(), + ]; + + if (isset($props[0][200])) { + + // We're mapping the internal webdav properties to the + // elements caldav-sharing expects. + if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) { + $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address']; + } + if (isset($props[0][200]['{DAV:}displayname'])) { + $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname']; + } + + } + + return new Xml\Property\Invite( + $node->getInvites(), + $ownerInfo + ); + + }); + + } + + } + + /** + * This method is triggered *after* all properties have been retrieved. + * This allows us to inject the correct resourcetype for calendars that + * have been shared. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindLate(DAV\PropFind $propFind, DAV\INode $node) { + + if ($node instanceof ISharedCalendar) { + $shareAccess = $node->getShareAccess(); + if ($rt = $propFind->get('{DAV:}resourcetype')) { + switch ($shareAccess) { + case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER : + $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner'); + break; + case \Sabre\DAV\Sharing\Plugin::ACCESS_READ : + case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE : + $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared'); + break; + + } + } + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() { + return new Xml\Property\AllowedSharingModes(true, false); + }); + + } + + } + + /** + * This method is trigged when a user attempts to update a node's + * properties. + * + * A previous draft of the sharing spec stated that it was possible to use + * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing + * the calendar. + * + * Even though this is no longer in the current spec, we keep this around + * because OS X 10.7 may still make use of this feature. + * + * @param string $path + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch($path, DAV\PropPatch $propPatch) { + + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof ISharedCalendar) + return; + + if ($node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER || $node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED) { + + $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) { + if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false; + $shares = $node->getInvites(); + foreach ($shares as $share) { + $share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS; + } + $node->updateInvites($shares); + + return true; + + }); + + } + + } + + /** + * We intercept this to handle POST requests on calendars. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return null|bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Only handling xml + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) + return; + + // Making sure the node exists + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (DAV\Exception\NotFound $e) { + return; + } + + $requestBody = $request->getBodyAsString(); + + // If this request handler could not deal with this POST request, it + // will return 'null' and other plugins get a chance to handle the + // request. + // + // However, we already requested the full body. This is a problem, + // because a body can only be read once. This is why we preemptively + // re-populated the request body with the existing data. + $request->setBody($requestBody); + + $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); + + switch ($documentType) { + + // Both the DAV:share-resource and CALENDARSERVER:share requests + // behave identically. + case '{' . Plugin::NS_CALENDARSERVER . '}share' : + + $sharingPlugin = $this->server->getPlugin('sharing'); + $sharingPlugin->shareResource($path, $message->sharees); + + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + // The invite-reply document is sent when the user replies to an + // invitation of a calendar share. + case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' : + + // This only works on the calendar-home-root node. + if (!$node instanceof CalendarHome) { + return; + } + $this->server->transactionType = 'post-invite-reply'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $url = $node->shareReply( + $message->href, + $message->status, + $message->calendarUri, + $message->inReplyTo, + $message->summary + ); + + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + if ($url) { + $writer = $this->server->xml->getWriter(); + $writer->openMemory(); + $writer->startDocument(); + $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as'); + $writer->write(new LocalHref($url)); + $writer->endElement(); + $response->setHeader('Content-Type', 'application/xml'); + $response->setBody($writer->outputMemory()); + + } + + // Breaking the event chain + return false; + + case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof ISharedCalendar) { + return; + } + $this->server->transactionType = 'post-publish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}share'); + } + + $node->setPublishStatus(true); + + // iCloud sends back the 202, so we will too. + $response->setStatus(202); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof ISharedCalendar) { + return; + } + $this->server->transactionType = 'post-unpublish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}share'); + } + + $node->setPublishStatus(false); + + $response->setStatus(200); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + } + + + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for caldav-sharing.', + 'link' => 'http://sabre.io/dav/caldav-sharing/', + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php new file mode 100644 index 000000000000..7ba259c7b689 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php @@ -0,0 +1,40 @@ +resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] = + '{http://calendarserver.org/ns/}subscribed'; + + $server->xml->elementMap['{http://calendarserver.org/ns/}source'] = + 'Sabre\\DAV\\Xml\\Property\\Href'; + + $server->on('propFind', [$this, 'propFind'], 150); + + } + + /** + * This method should return a list of server-features. + * + * This is for example 'versioning' and is added to the DAV: header + * in an OPTIONS response. + * + * @return array + */ + function getFeatures() { + + return ['calendarserver-subscribed']; + + } + + /** + * Triggered after properties have been fetched. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + // There's a bunch of properties that must appear as a self-closing + // xml-element. This event handler ensures that this will be the case. + $props = [ + '{http://calendarserver.org/ns/}subscribed-strip-alarms', + '{http://calendarserver.org/ns/}subscribed-strip-attachments', + '{http://calendarserver.org/ns/}subscribed-strip-todos', + ]; + + foreach ($props as $prop) { + + if ($propFind->getStatus($prop) === 200) { + $propFind->set($prop, '', 200); + } + + } + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'subscriptions'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.', + 'link' => null, + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php new file mode 100644 index 000000000000..6a1851ed8688 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php @@ -0,0 +1,221 @@ +caldavBackend = $caldavBackend; + $this->subscriptionInfo = $subscriptionInfo; + + $required = [ + 'id', + 'uri', + 'principaluri', + 'source', + ]; + + foreach ($required as $r) { + if (!isset($subscriptionInfo[$r])) { + throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node'); + } + } + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->subscriptionInfo['uri']; + + } + + /** + * Returns the last modification time + * + * @return int + */ + function getLastModified() { + + if (isset($this->subscriptionInfo['lastmodified'])) { + return $this->subscriptionInfo['lastmodified']; + } + + } + + /** + * Deletes the current node + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteSubscription( + $this->subscriptionInfo['id'] + ); + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + return []; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + + return $this->caldavBackend->updateSubscription( + $this->subscriptionInfo['id'], + $propPatch + ); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname. + * + * If the array is empty, it means 'all properties' were requested. + * + * Note that it's fine to liberally give properties back, instead of + * conforming to the list of requested properties. + * The Server class will filter out the extra. + * + * @param array $properties + * @return array + */ + function getProperties($properties) { + + $r = []; + + foreach ($properties as $prop) { + + switch ($prop) { + case '{http://calendarserver.org/ns/}source' : + $r[$prop] = new Href($this->subscriptionInfo['source']); + break; + default : + if (array_key_exists($prop, $this->subscriptionInfo)) { + $r[$prop] = $this->subscriptionInfo[$prop]; + } + break; + } + + } + + return $r; + + } + + /** + * Returns the owner principal. + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->subscriptionInfo['principaluri']; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php new file mode 100644 index 000000000000..9669be304e13 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php @@ -0,0 +1,84 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar', + 'version' => $reader->getAttribute('version') ?: '2.0', + ]; + + $elems = (array)$reader->parseInnerTree(); + foreach ($elems as $elem) { + + switch ($elem['name']) { + case '{' . Plugin::NS_CALDAV . '}expand' : + + $result['expand'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + + if (!$result['expand']['start'] || !$result['expand']['end']) { + throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data'); + } + if ($result['expand']['end'] <= $result['expand']['start']) { + throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data'); + } + break; + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php new file mode 100644 index 000000000000..c21ede66b5c4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php @@ -0,0 +1,97 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}comp-filter' : + $result['comp-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}prop-filter' : + $result['prop-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}time-range' : + if ($result['name'] === 'VCALENDAR') { + throw new BadRequest('You cannot add time-range filters on the VCALENDAR component'); + } + $result['time-range'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { + throw new BadRequest('The end-date must be larger than the start-date'); + } + break; + + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 000000000000..bf422cf05457 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php @@ -0,0 +1,82 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'text-match' => null, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}text-match' : + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', + 'value' => $elem['value'], + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php new file mode 100644 index 000000000000..db9207295a2f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php @@ -0,0 +1,98 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'param-filters' => [], + 'text-match' => null, + 'time-range' => false, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}param-filter' : + $result['param-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}time-range' : + $result['time-range'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { + throw new BadRequest('The end-date must be larger than the start-date'); + } + break; + case '{' . Plugin::NS_CALDAV . '}text-match' : + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', + 'value' => $elem['value'], + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php new file mode 100644 index 000000000000..92a9ac7b7158 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php @@ -0,0 +1,302 @@ + $value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-notification'); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}'; + + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z')); + + $writer->startElement($cs . 'invite-notification'); + + $writer->writeElement($cs . 'uid', $this->id); + $writer->writeElement('{DAV:}href', $this->href); + + switch ($this->type) { + + case DAV\Sharing\Plugin::INVITE_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case DAV\Sharing\Plugin::INVITE_NORESPONSE : + $writer->writeElement($cs . 'invite-noresponse'); + break; + + } + + $writer->writeElement($cs . 'hosturl', [ + '{DAV:}href' => $writer->contextUri . $this->hostUrl + ]); + + if ($this->summary) { + $writer->writeElement($cs . 'summary', $this->summary); + } + + $writer->startElement($cs . 'access'); + if ($this->readOnly) { + $writer->writeElement($cs . 'read'); + } else { + $writer->writeElement($cs . 'read-write'); + } + $writer->endElement(); // access + + $writer->startElement($cs . 'organizer'); + // If the organizer contains a 'mailto:' part, it means it should be + // treated as absolute. + if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') { + $writer->writeElement('{DAV:}href', $this->organizer); + } else { + $writer->writeElement('{DAV:}href', $writer->contextUri . $this->organizer); + } + if ($this->commonName) { + $writer->writeElement($cs . 'common-name', $this->commonName); + } + if ($this->firstName) { + $writer->writeElement($cs . 'first-name', $this->firstName); + } + if ($this->lastName) { + $writer->writeElement($cs . 'last-name', $this->lastName); + } + $writer->endElement(); // organizer + + if ($this->commonName) { + $writer->writeElement($cs . 'organizer-cn', $this->commonName); + } + if ($this->firstName) { + $writer->writeElement($cs . 'organizer-first', $this->firstName); + } + if ($this->lastName) { + $writer->writeElement($cs . 'organizer-last', $this->lastName); + } + if ($this->supportedComponents) { + $writer->writeElement('{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set', $this->supportedComponents); + } + + $writer->endElement(); // invite-notification + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php new file mode 100644 index 000000000000..f4b10a396bcb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php @@ -0,0 +1,213 @@ + $value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-reply'); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}'; + + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z')); + + $writer->startElement($cs . 'invite-reply'); + + $writer->writeElement($cs . 'uid', $this->id); + $writer->writeElement($cs . 'in-reply-to', $this->inReplyTo); + $writer->writeElement('{DAV:}href', $this->href); + + switch ($this->type) { + + case DAV\Sharing\Plugin::INVITE_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case DAV\Sharing\Plugin::INVITE_DECLINED : + $writer->writeElement($cs . 'invite-declined'); + break; + + } + + $writer->writeElement($cs . 'hosturl', [ + '{DAV:}href' => $writer->contextUri . $this->hostUrl + ]); + + if ($this->summary) { + $writer->writeElement($cs . 'summary', $this->summary); + } + $writer->endElement(); // invite-reply + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php new file mode 100644 index 000000000000..b98f9c888890 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php @@ -0,0 +1,45 @@ +id = $id; + $this->type = $type; + $this->description = $description; + $this->href = $href; + $this->etag = $etag; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}systemstatus'); + $writer->writeAttribute('type', $type); + $writer->endElement(); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . Plugin::NS_CALENDARSERVER . '}'; + switch ($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $writer->startElement($cs . 'systemstatus'); + $writer->writeAttribute('type', $type); + + + if ($this->description) { + $writer->writeElement($cs . 'description', $this->description); + } + if ($this->href) { + $writer->writeElement('{DAV:}href', $this->href); + } + + $writer->endElement(); // systemstatus + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /* + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php new file mode 100644 index 000000000000..54e5a116a6a1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php @@ -0,0 +1,87 @@ +canBeShared = $canBeShared; + $this->canBePublished = $canBePublished; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + if ($this->canBeShared) { + $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-shared'); + } + if ($this->canBePublished) { + $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-published'); + } + + } + + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php new file mode 100644 index 000000000000..fc6f1d505faa --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php @@ -0,0 +1,80 @@ +emails = $emails; + + } + + /** + * Returns the email addresses + * + * @return array + */ + function getValue() { + + return $this->emails; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->emails as $email) { + + $writer->writeElement('{http://calendarserver.org/ns/}email-address', $email); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php new file mode 100644 index 000000000000..4f33c464cc36 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php @@ -0,0 +1,128 @@ +sharees = $sharees; + + } + + /** + * Returns the list of users, as it was passed to the constructor. + * + * @return array + */ + function getValue() { + + return $this->sharees; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $cs = '{' . Plugin::NS_CALENDARSERVER . '}'; + + foreach ($this->sharees as $sharee) { + + if ($sharee->access === DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) { + $writer->startElement($cs . 'organizer'); + } else { + $writer->startElement($cs . 'user'); + + switch ($sharee->inviteStatus) { + case DAV\Sharing\Plugin::INVITE_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case DAV\Sharing\Plugin::INVITE_DECLINED : + $writer->writeElement($cs . 'invite-declined'); + break; + case DAV\Sharing\Plugin::INVITE_NORESPONSE : + $writer->writeElement($cs . 'invite-noresponse'); + break; + case DAV\Sharing\Plugin::INVITE_INVALID : + $writer->writeElement($cs . 'invite-invalid'); + break; + } + + $writer->startElement($cs . 'access'); + switch ($sharee->access) { + case DAV\Sharing\Plugin::ACCESS_READWRITE : + $writer->writeElement($cs . 'read-write'); + break; + case DAV\Sharing\Plugin::ACCESS_READ : + $writer->writeElement($cs . 'read'); + break; + + } + $writer->endElement(); // access + + } + + $href = new DAV\Xml\Property\Href($sharee->href); + $href->xmlSerialize($writer); + + if (isset($sharee->properties['{DAV:}displayname'])) { + $writer->writeElement($cs . 'common-name', $sharee->properties['{DAV:}displayname']); + } + if ($sharee->comment) { + $writer->writeElement($cs . 'summary', $sharee->comment); + } + $writer->endElement(); // organizer or user + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php new file mode 100644 index 000000000000..10c20be55c82 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php @@ -0,0 +1,130 @@ +value = $value; + + } + + /** + * Returns the current value + * + * @return string + */ + function getValue() { + + return $this->value; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->value) { + case self::TRANSPARENT : + $writer->writeElement('{' . Plugin::NS_CALDAV . '}transparent'); + break; + case self::OPAQUE : + $writer->writeElement('{' . Plugin::NS_CALDAV . '}opaque'); + break; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = Deserializer\enum($reader, Plugin::NS_CALDAV); + + if (in_array('transparent', $elems)) { + $value = self::TRANSPARENT; + } else { + $value = self::OPAQUE; + } + return new self($value); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php new file mode 100644 index 000000000000..7fc25c5f0c2a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php @@ -0,0 +1,129 @@ +components = $components; + + } + + /** + * Returns the list of supported components + * + * @return array + */ + function getValue() { + + return $this->components; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->components as $component) { + + $writer->startElement('{' . Plugin::NS_CALDAV . '}comp'); + $writer->writeAttributes(['name' => $component]); + $writer->endElement(); + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + + $components = []; + + foreach ((array)$elems as $elem) { + if ($elem['name'] === '{' . Plugin::NS_CALDAV . '}comp') { + $components[] = $elem['attributes']['name']; + } + } + + if (!$components) { + throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element'); + } + + return new self($components); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php new file mode 100644 index 000000000000..d123ba4c0d2d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php @@ -0,0 +1,60 @@ +startElement('{' . Plugin::NS_CALDAV . '}calendar-data'); + $writer->writeAttributes([ + 'content-type' => 'text/calendar', + 'version' => '2.0', + ]); + $writer->endElement(); // calendar-data + $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data'); + $writer->writeAttributes([ + 'content-type' => 'application/calendar+json', + ]); + $writer->endElement(); // calendar-data + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 000000000000..af10860d0775 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php @@ -0,0 +1,57 @@ +writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php new file mode 100644 index 000000000000..6d3c5d5089ce --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php @@ -0,0 +1,124 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'hrefs' => [], + 'properties' => [], + ]; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data']; + } + break; + case '{DAV:}href' : + $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']); + break; + + } + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + + return $obj; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php new file mode 100644 index 000000000000..e0b1c795032a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php @@ -0,0 +1,139 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter', + '{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter', + '{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter', + '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'filters' => null, + 'properties' => [], + ]; + + if (!is_array($elems)) $elems = []; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data']; + } + break; + case '{' . Plugin::NS_CALDAV . '}filter' : + foreach ($elem['value'] as $subElem) { + if ($subElem['name'] === '{' . Plugin::NS_CALDAV . '}comp-filter') { + if (!is_null($newProps['filters'])) { + throw new BadRequest('Only one top-level comp-filter may be defined'); + } + $newProps['filters'] = $subElem['value']; + } + } + break; + + } + + } + + if (is_null($newProps['filters'])) { + throw new BadRequest('The {' . Plugin::NS_CALDAV . '}filter element is required for this request'); + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + return $obj; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php new file mode 100644 index 000000000000..0f6c1e0741dc --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php @@ -0,0 +1,91 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $timeRange = '{' . Plugin::NS_CALDAV . '}time-range'; + + $start = null; + $end = null; + + foreach ((array)$reader->parseInnerTree([]) as $elem) { + + if ($elem['name'] !== $timeRange) continue; + + $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start']; + $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end']; + + } + if (!$start && !$end) { + throw new BadRequest('The freebusy report must have a time-range element'); + } + if ($start) { + $start = DateTimeParser::parseDateTime($start); + } + if ($end) { + $end = DateTimeParser::parseDateTime($end); + } + $result = new self(); + $result->start = $start; + $result->end = $end; + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php new file mode 100644 index 000000000000..db32cc6a59e5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php @@ -0,0 +1,150 @@ +href = $href; + $this->calendarUri = $calendarUri; + $this->inReplyTo = $inReplyTo; + $this->summary = $summary; + $this->status = $status; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = KeyValue::xmlDeserialize($reader); + + $href = null; + $calendarUri = null; + $inReplyTo = null; + $summary = null; + $status = null; + + foreach ($elems as $name => $value) { + + switch ($name) { + + case '{' . Plugin::NS_CALENDARSERVER . '}hosturl' : + foreach ($value as $bla) { + if ($bla['name'] === '{DAV:}href') { + $calendarUri = $bla['value']; + } + } + break; + case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' : + $status = DAV\Sharing\Plugin::INVITE_ACCEPTED; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' : + $status = DAV\Sharing\Plugin::INVITE_DECLINED; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' : + $inReplyTo = $value; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}summary' : + $summary = $value; + break; + case '{DAV:}href' : + $href = $value; + break; + } + + } + if (is_null($calendarUri)) { + throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist'); + } + + return new self($href, $calendarUri, $inReplyTo, $summary, $status); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php new file mode 100644 index 000000000000..ce7fafde910a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php @@ -0,0 +1,79 @@ +properties; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + } + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php new file mode 100644 index 000000000000..e0bd8e0af31c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php @@ -0,0 +1,111 @@ +sharees = $sharees; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseGetElements([ + '{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $sharees = []; + + foreach ($elems as $elem) { + switch ($elem['name']) { + + case '{' . Plugin::NS_CALENDARSERVER . '}set' : + $sharee = $elem['value']; + + $sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary'; + $commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name'; + + $properties = []; + if (isset($sharee[$commonName])) { + $properties['{DAV:}displayname'] = $sharee[$commonName]; + } + + $access = array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee) + ? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE + : \Sabre\DAV\Sharing\Plugin::ACCESS_READ; + + $sharees[] = new Sharee([ + 'href' => $sharee['{DAV:}href'], + 'properties' => $properties, + 'access' => $access, + 'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null + ]); + break; + + case '{' . Plugin::NS_CALENDARSERVER . '}remove' : + $sharees[] = new Sharee([ + 'href' => $elem['value']['{DAV:}href'], + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS + ]); + break; + + } + } + + return new self($sharees); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBook.php new file mode 100644 index 000000000000..c9d28a091c8e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBook.php @@ -0,0 +1,357 @@ +carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + + } + + /** + * Returns the name of the addressbook + * + * @return string + */ + function getName() { + + return $this->addressBookInfo['uri']; + + } + + /** + * Returns a card + * + * @param string $name + * @return Card + */ + function getChild($name) { + + $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); + if (!$obj) throw new DAV\Exception\NotFound('Card not found'); + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); + + } + + /** + * Returns the full list of cards + * + * @return array + */ + function getChildren() { + + $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + return $children; + + } + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param string[] $paths + * @return array + */ + function getMultipleChildren(array $paths) { + + $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + return $children; + + } + + /** + * Creates a new directory + * + * We actually block this, as subdirectories are not allowed in addressbooks. + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed'); + + } + + /** + * Creates a new file + * + * The contents of the new file must be a valid VCARD. + * + * This method may return an ETag. + * + * @param string $name + * @param resource $vcardData + * @return string|null + */ + function createFile($name, $vcardData = null) { + + if (is_resource($vcardData)) { + $vcardData = stream_get_contents($vcardData); + } + // Converting to UTF-8, if needed + $vcardData = DAV\StringUtil::ensureUTF8($vcardData); + + return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData); + + } + + /** + * Deletes the entire addressbook. + * + * @return void + */ + function delete() { + + $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']); + + } + + /** + * Renames the addressbook + * + * @param string $newName + * @return void + */ + function setName($newName) { + + throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported'); + + } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return void + */ + function getLastModified() { + + return null; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch(DAV\PropPatch $propPatch) { + + return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * @param array $properties + * @return array + */ + function getProperties($properties) { + + $response = []; + foreach ($properties as $propertyName) { + + if (isset($this->addressBookInfo[$propertyName])) { + + $response[$propertyName] = $this->addressBookInfo[$propertyName]; + + } + + } + + return $response; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->addressBookInfo['principaluri']; + + } + + + /** + * This method returns the ACL's for card nodes in this address book. + * The result of this method automatically gets passed to the + * card nodes in this address book. + * + * @return array + */ + function getChildACL() { + + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + ]; + + } + + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken() { + + if ( + $this->carddavBackend instanceof Backend\SyncSupport && + isset($this->addressBookInfo['{DAV:}sync-token']) + ) { + return $this->addressBookInfo['{DAV:}sync-token']; + } + if ( + $this->carddavBackend instanceof Backend\SyncSupport && + isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token']) + ) { + return $this->addressBookInfo['{http://sabredav.org/ns}sync-token']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null) { + + if (!$this->carddavBackend instanceof Backend\SyncSupport) { + return null; + } + + return $this->carddavBackend->getChangesForAddressBook( + $this->addressBookInfo['id'], + $syncToken, + $syncLevel, + $limit + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php new file mode 100644 index 000000000000..d770c0ffe0f0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php @@ -0,0 +1,191 @@ +carddavBackend = $carddavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + list(, $name) = Uri\split($this->principalUri); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + /** + * Deletes this object + * + * @return void + */ + function delete() { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + function createFile($filename, $data = null) { + + throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); + + } + + /** + * Creates a new directory under this object. + * + * This is currently not allowed. + * + * @param string $filename + * @return void + */ + function createDirectory($filename) { + + throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); + + } + + /** + * Returns a single addressbook, by name + * + * @param string $name + * @todo needs optimizing + * @return AddressBook + */ + function getChild($name) { + + foreach ($this->getChildren() as $child) { + if ($name == $child->getName()) + return $child; + + } + throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found'); + + } + + /** + * Returns a list of addressbooks + * + * @return array + */ + function getChildren() { + + $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); + $objs = []; + foreach ($addressbooks as $addressbook) { + $objs[] = new AddressBook($this->carddavBackend, $addressbook); + } + return $objs; + + } + + /** + * Creates a new address book. + * + * @param string $name + * @param MkCol $mkCol + * @throws DAV\Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + if (!$mkCol->hasResourceType('{' . Plugin::NS_CARDDAV . '}addressbook')) { + throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection'); + } + $properties = $mkCol->getRemainingValues(); + $mkCol->setRemainingResultCode(201); + $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php new file mode 100644 index 000000000000..a9f1183da49e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php @@ -0,0 +1,80 @@ +carddavBackend = $carddavBackend; + parent::__construct($principalBackend, $principalPrefix); + + } + + /** + * Returns the name of the node + * + * @return string + */ + function getName() { + + return Plugin::ADDRESSBOOK_ROOT; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principal + * @return \Sabre\DAV\INode + */ + function getChildForPrincipal(array $principal) { + + return new AddressBookHome($this->carddavBackend, $principal['uri']); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php new file mode 100644 index 000000000000..03d2346da086 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php @@ -0,0 +1,38 @@ +getCard($addressBookId, $uri); + }, $uris); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php new file mode 100644 index 000000000000..18c0c0a99822 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php @@ -0,0 +1,190 @@ +pdo = $pdo; + + } + + /** + * Returns the list of addressbooks for a specific user. + * + * @param string $principalUri + * @return array + */ + function getAddressBooksForUser($principalUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM ' . $this->addressBooksTableName . ' WHERE principaluri = ?'); + $stmt->execute([$principalUri]); + + $addressBooks = []; + + foreach ($stmt->fetchAll() as $row) { + + $addressBooks[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{DAV:}displayname' => $row['displayname'], + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], + '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], + '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', + ]; + + } + + return $addressBooks; + + } + + + /** + * Updates properties for an address book. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param string $addressBookId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { + + $supportedProperties = [ + '{DAV:}displayname', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description', + ]; + + $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) { + + $updates = []; + foreach ($mutations as $property => $newValue) { + + switch ($property) { + case '{DAV:}displayname' : + $updates['displayname'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $updates['description'] = $newValue; + break; + } + } + $query = 'UPDATE ' . $this->addressBooksTableName . ' SET '; + $first = true; + foreach ($updates as $key => $value) { + if ($first) { + $first = false; + } else { + $query .= ', '; + } + $query .= ' ' . $key . ' = :' . $key . ' '; + } + $query .= ' WHERE id = :addressbookid'; + + $stmt = $this->pdo->prepare($query); + $updates['addressbookid'] = $addressBookId; + + $stmt->execute($updates); + + $this->addChange($addressBookId, "", 2); + + return true; + + }); + + } + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @return int Last insert id + */ + function createAddressBook($principalUri, $url, array $properties) { + + $values = [ + 'displayname' => null, + 'description' => null, + 'principaluri' => $principalUri, + 'uri' => $url, + ]; + + foreach ($properties as $property => $newValue) { + + switch ($property) { + case '{DAV:}displayname' : + $values['displayname'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $values['description'] = $newValue; + break; + default : + throw new DAV\Exception\BadRequest('Unknown property: ' . $property); + } + + } + + $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)'; + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + return $this->pdo->lastInsertId( + $this->addressBooksTableName . '_id_seq' + ); + + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param int $addressBookId + * @return void + */ + function deleteAddressBook($addressBookId) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressBookId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([$addressBookId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBookChangesTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressBookId]); + + } + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param mixed $addressbookId + * @return array + */ + function getCards($addressbookId) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressbookId]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row['etag'] = '"' . $row['etag'] . '"'; + $row['lastmodified'] = (int)$row['lastmodified']; + $result[] = $row; + } + return $result; + + } + + /** + * Returns a specific card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * If the card does not exist, you must return false. + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array + */ + function getCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1'); + $stmt->execute([$addressBookId, $cardUri]); + + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$result) return false; + + $result['etag'] = '"' . $result['etag'] . '"'; + $result['lastmodified'] = (int)$result['lastmodified']; + return $result; + + } + + /** + * Returns a list of cards. + * + * This method should work identical to getCard, but instead return all the + * cards in the list as an array. + * + * If the backend supports this, it may allow for some speed-ups. + * + * @param mixed $addressBookId + * @param array $uris + * @return array + */ + function getMultipleCards($addressBookId, array $uris) { + + $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri IN ('; + // Inserting a whole bunch of question marks + $query .= implode(',', array_fill(0, count($uris), '?')); + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute(array_merge([$addressBookId], $uris)); + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row['etag'] = '"' . $row['etag'] . '"'; + $row['lastmodified'] = (int)$row['lastmodified']; + $result[] = $row; + } + return $result; + + } + + /** + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function createCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)'); + + $etag = md5($cardData); + + $stmt->execute([ + $cardData, + $cardUri, + time(), + $addressBookId, + strlen($cardData), + $etag, + ]); + + $this->addChange($addressBookId, $cardUri, 1); + + return '"' . $etag . '"'; + + } + + /** + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function updateCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?'); + + $etag = md5($cardData); + $stmt->execute([ + $cardData, + time(), + strlen($cardData), + $etag, + $cardUri, + $addressBookId + ]); + + $this->addChange($addressBookId, $cardUri, 2); + + return '"' . $etag . '"'; + + } + + /** + * Deletes a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool + */ + function deleteCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?'); + $stmt->execute([$addressBookId, $cardUri]); + + $this->addChange($addressBookId, $cardUri, 3); + + return $stmt->rowCount() === 1; + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken in the specified address book. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'updated.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token + * property. This is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $addressBookId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + + // Current synctoken + $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([$addressBookId]); + $currentToken = $stmt->fetchColumn(0); + + if (is_null($currentToken)) return null; + + $result = [ + 'syncToken' => $currentToken, + 'added' => [], + 'modified' => [], + 'deleted' => [], + ]; + + if ($syncToken) { + + $query = "SELECT uri, operation FROM " . $this->addressBookChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken"; + if ($limit > 0) $query .= " LIMIT " . (int)$limit; + + // Fetching all changes + $stmt = $this->pdo->prepare($query); + $stmt->execute([$syncToken, $currentToken, $addressBookId]); + + $changes = []; + + // This loop ensures that any duplicates are overwritten, only the + // last change on a node is relevant. + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $changes[$row['uri']] = $row['operation']; + + } + + foreach ($changes as $uri => $operation) { + + switch ($operation) { + case 1: + $result['added'][] = $uri; + break; + case 2: + $result['modified'][] = $uri; + break; + case 3: + $result['deleted'][] = $uri; + break; + } + + } + } else { + // No synctoken supplied, this is the initial sync. + $query = "SELECT uri FROM " . $this->cardsTableName . " WHERE addressbookid = ?"; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$addressBookId]); + + $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + } + return $result; + + } + + /** + * Adds a change record to the addressbookchanges table. + * + * @param mixed $addressBookId + * @param string $objectUri + * @param int $operation 1 = add, 2 = modify, 3 = delete + * @return void + */ + protected function addChange($addressBookId, $objectUri, $operation) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([ + $objectUri, + $addressBookId, + $operation, + $addressBookId + ]); + $stmt = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET synctoken = synctoken + 1 WHERE id = ?'); + $stmt->execute([ + $addressBookId + ]); + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php new file mode 100644 index 000000000000..f80618a8e141 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php @@ -0,0 +1,81 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property. This is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $addressBookId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Card.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Card.php new file mode 100644 index 000000000000..42a2d7b6a114 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Card.php @@ -0,0 +1,216 @@ +carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + $this->cardData = $cardData; + + } + + /** + * Returns the uri for this object + * + * @return string + */ + function getName() { + + return $this->cardData['uri']; + + } + + /** + * Returns the VCard-formatted object + * + * @return string + */ + function get() { + + // Pre-populating 'carddata' is optional. If we don't yet have it + // already, we fetch it from the backend. + if (!isset($this->cardData['carddata'])) { + $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']); + } + return $this->cardData['carddata']; + + } + + /** + * Updates the VCard-formatted object + * + * @param string $cardData + * @return string|null + */ + function put($cardData) { + + if (is_resource($cardData)) + $cardData = stream_get_contents($cardData); + + // Converting to UTF-8, if needed + $cardData = DAV\StringUtil::ensureUTF8($cardData); + + $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData); + $this->cardData['carddata'] = $cardData; + $this->cardData['etag'] = $etag; + + return $etag; + + } + + /** + * Deletes the card + * + * @return void + */ + function delete() { + + $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + function getContentType() { + + return 'text/vcard; charset=utf-8'; + + } + + /** + * Returns an ETag for this object + * + * @return string + */ + function getETag() { + + if (isset($this->cardData['etag'])) { + return $this->cardData['etag']; + } else { + $data = $this->get(); + if (is_string($data)) { + return '"' . md5($data) . '"'; + } else { + // We refuse to calculate the md5 if it's a stream. + return null; + } + } + + } + + /** + * Returns the last modification date as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + function getSize() { + + if (array_key_exists('size', $this->cardData)) { + return $this->cardData['size']; + } else { + return strlen($this->get()); + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->addressBookInfo['principaluri']; + + } + + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified through the cardData array. + if (isset($this->cardData['acl'])) { + return $this->cardData['acl']; + } + + return [ + [ + 'privilege' => '{DAV:}all', + 'principal' => $this->addressBookInfo['principaluri'], + 'protected' => true, + ], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/IAddressBook.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/IAddressBook.php new file mode 100644 index 000000000000..f80e0557554c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/IAddressBook.php @@ -0,0 +1,18 @@ +on('propFind', [$this, 'propFindEarly']); + $server->on('propFind', [$this, 'propFindLate'], 150); + $server->on('report', [$this, 'report']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + $server->on('afterMethod:GET', [$this, 'httpAfterGet']); + + $server->xml->namespaceMap[self::NS_CARDDAV] = 'card'; + + $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport'; + $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport'; + + /* Mapping Interfaces to {DAV:}resourcetype values */ + $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook'; + $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory'; + + /* Adding properties that may never be changed */ + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set'; + + $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href'; + + $this->server = $server; + + } + + /** + * Returns a list of supported features. + * + * This is used in the DAV: header in the OPTIONS and PROPFIND requests. + * + * @return array + */ + function getFeatures() { + + return ['addressbook']; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof IAddressBook || $node instanceof ICard) { + return [ + '{' . self::NS_CARDDAV . '}addressbook-multiget', + '{' . self::NS_CARDDAV . '}addressbook-query', + ]; + } + return []; + + } + + + /** + * Adds all CardDAV-specific properties + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { + + $ns = '{' . self::NS_CARDDAV . '}'; + + if ($node instanceof IAddressBook) { + + $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize); + $propFind->handle($ns . 'supported-address-data', function() { + return new Xml\Property\SupportedAddressData(); + }); + $propFind->handle($ns . 'supported-collation-set', function() { + return new Xml\Property\SupportedCollationSet(); + }); + + } + if ($node instanceof DAVACL\IPrincipal) { + + $path = $propFind->getPath(); + + $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) { + return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/'); + }); + + if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() { + return new LocalHref($this->directories); + }); + + } + + if ($node instanceof ICard) { + + // The address-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. + // Therefore we simply expose it as a property. + $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) { + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + return $val; + + }); + + } + + } + + /** + * This functions handles REPORT requests specific to CardDAV + * + * @param string $reportName + * @param \DOMNode $dom + * @param mixed $path + * @return bool + */ + function report($reportName, $dom, $path) { + + switch ($reportName) { + case '{' . self::NS_CARDDAV . '}addressbook-multiget' : + $this->server->transactionType = 'report-addressbook-multiget'; + $this->addressbookMultiGetReport($dom); + return false; + case '{' . self::NS_CARDDAV . '}addressbook-query' : + $this->server->transactionType = 'report-addressbook-query'; + $this->addressBookQueryReport($dom); + return false; + default : + return; + + } + + + } + + /** + * Returns the addressbook home for a given principal + * + * @param string $principal + * @return string + */ + protected function getAddressbookHomeForPrincipal($principal) { + + list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal); + return self::ADDRESSBOOK_ROOT . '/' . $principalId; + + } + + + /** + * This function handles the addressbook-multiget REPORT. + * + * This report is used by the client to fetch the content of a series + * of urls. Effectively avoiding a lot of redundant requests. + * + * @param Xml\Request\AddressBookMultiGetReport $report + * @return void + */ + function addressbookMultiGetReport($report) { + + $contentType = $report->contentType; + $version = $report->version; + if ($version) { + $contentType .= '; version=' . $version; + } + + $vcardType = $this->negotiateVCard( + $contentType + ); + + $propertyList = []; + $paths = array_map( + [$this->server, 'calculateUri'], + $report->hrefs + ); + foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) { + + if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) { + + $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard( + $props[200]['{' . self::NS_CARDDAV . '}address-data'], + $vcardType + ); + + } + $propertyList[] = $props; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal')); + + } + + /** + * This method is triggered before a file gets updated with new content. + * + * This plugin uses this method to ensure that Card nodes receive valid + * vcard data. + * + * @param string $path + * @param DAV\IFile $node + * @param resource $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) { + + if (!$node instanceof ICard) + return; + + $this->validateVCard($data, $modified); + + } + + /** + * This method is triggered before a new file is created. + * + * This plugin uses this method to ensure that Card nodes receive valid + * vcard data. + * + * @param string $path + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) { + + if (!$parentNode instanceof IAddressBook) + return; + + $this->validateVCard($data, $modified); + + } + + /** + * Checks if the submitted iCalendar data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + protected function validateVCard(&$data, &$modified) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + $before = $data; + + try { + + // If the data starts with a [, we can reasonably assume we're dealing + // with a jCal object. + if (substr($data, 0, 1) === '[') { + $vobj = VObject\Reader::readJson($data); + + // Converting $data back to iCalendar, as that's what we + // technically support everywhere. + $data = $vobj->serialize(); + $modified = true; + } else { + $vobj = VObject\Reader::read($data); + } + + } catch (VObject\ParseException $e) { + + throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage()); + + } + + if ($vobj->name !== 'VCARD') { + throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.'); + } + + $options = VObject\Node::PROFILE_CARDDAV; + $prefer = $this->server->getHTTPPrefer(); + + if ($prefer['handling'] !== 'strict') { + $options |= VObject\Node::REPAIR; + } + + $messages = $vobj->validate($options); + + $highestLevel = 0; + $warningMessage = null; + + // $messages contains a list of problems with the vcard, along with + // their severity. + foreach ($messages as $message) { + + if ($message['level'] > $highestLevel) { + // Recording the highest reported error level. + $highestLevel = $message['level']; + $warningMessage = $message['message']; + } + + switch ($message['level']) { + + case 1 : + // Level 1 means that there was a problem, but it was repaired. + $modified = true; + break; + case 2 : + // Level 2 means a warning, but not critical + break; + case 3 : + // Level 3 means a critical error + throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: ' . $message['message']); + + } + + } + if ($warningMessage) { + $this->server->httpResponse->setHeader( + 'X-Sabre-Ew-Gross', + 'vCard validation warning: ' . $warningMessage + ); + + // Re-serializing object. + $data = $vobj->serialize(); + if (!$modified && strcmp($data, $before) !== 0) { + // This ensures that the system does not send an ETag back. + $modified = true; + } + } + + // Destroy circular references to PHP will GC the object. + $vobj->destroy(); + } + + + /** + * This function handles the addressbook-query REPORT + * + * This report is used by the client to filter an addressbook based on a + * complex query. + * + * @param Xml\Request\AddressBookQueryReport $report + * @return void + */ + protected function addressbookQueryReport($report) { + + $depth = $this->server->getHTTPDepth(0); + + if ($depth == 0) { + $candidateNodes = [ + $this->server->tree->getNodeForPath($this->server->getRequestUri()) + ]; + if (!$candidateNodes[0] instanceof ICard) { + throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0'); + } + } else { + $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); + } + + $contentType = $report->contentType; + if ($report->version) { + $contentType .= '; version=' . $report->version; + } + + $vcardType = $this->negotiateVCard( + $contentType + ); + + $validNodes = []; + foreach ($candidateNodes as $node) { + + if (!$node instanceof ICard) + continue; + + $blob = $node->get(); + if (is_resource($blob)) { + $blob = stream_get_contents($blob); + } + + if (!$this->validateFilters($blob, $report->filters, $report->test)) { + continue; + } + + $validNodes[] = $node; + + if ($report->limit && $report->limit <= count($validNodes)) { + // We hit the maximum number of items, we can stop now. + break; + } + + } + + $result = []; + foreach ($validNodes as $validNode) { + + if ($depth == 0) { + $href = $this->server->getRequestUri(); + } else { + $href = $this->server->getRequestUri() . '/' . $validNode->getName(); + } + + list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0); + + if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) { + + $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard( + $props[200]['{' . self::NS_CARDDAV . '}address-data'], + $vcardType, + $report->addressDataProperties + ); + + } + $result[] = $props; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /** + * Validates if a vcard makes it throught a list of filters. + * + * @param string $vcardData + * @param array $filters + * @param string $test anyof or allof (which means OR or AND) + * @return bool + */ + function validateFilters($vcardData, array $filters, $test) { + + + if (!$filters) return true; + $vcard = VObject\Reader::read($vcardData); + + foreach ($filters as $filter) { + + $isDefined = isset($vcard->{$filter['name']}); + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) { + + // We only need to check for existence + $success = $isDefined; + + } else { + + $vProperties = $vcard->select($filter['name']); + + $results = []; + if ($filter['param-filters']) { + $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); + } + if ($filter['text-matches']) { + $texts = []; + foreach ($vProperties as $vProperty) + $texts[] = $vProperty->getValue(); + + $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); + } + + if (count($results) === 1) { + $success = $results[0]; + } else { + if ($filter['test'] === 'anyof') { + $success = $results[0] || $results[1]; + } else { + $success = $results[0] && $results[1]; + } + } + + } // else + + // There are two conditions where we can already determine whether + // or not this filter succeeds. + if ($test === 'anyof' && $success) { + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + return true; + } + if ($test === 'allof' && !$success) { + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + return false; + } + + } // foreach + + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * Validates if a param-filter can be applied to a specific property. + * + * @todo currently we're only validating the first parameter of the passed + * property. Any subsequence parameters with the same name are + * ignored. + * @param array $vProperties + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateParamFilters(array $vProperties, array $filters, $test) { + + foreach ($filters as $filter) { + + $isDefined = false; + foreach ($vProperties as $vProperty) { + $isDefined = isset($vProperty[$filter['name']]); + if ($isDefined) break; + } + + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + + // If there's no text-match, we can just check for existence + } elseif (!$filter['text-match'] || !$isDefined) { + + $success = $isDefined; + + } else { + + $success = false; + foreach ($vProperties as $vProperty) { + // If we got all the way here, we'll need to validate the + // text-match filter. + $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']); + if ($success) break; + } + if ($filter['text-match']['negate-condition']) { + $success = !$success; + } + + } // else + + // There are two conditions where we can already determine whether + // or not this filter succeeds. + if ($test === 'anyof' && $success) { + return true; + } + if ($test === 'allof' && !$success) { + return false; + } + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * Validates if a text-filter can be applied to a specific property. + * + * @param array $texts + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateTextMatches(array $texts, array $filters, $test) { + + foreach ($filters as $filter) { + + $success = false; + foreach ($texts as $haystack) { + $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']); + + // Breaking on the first match + if ($success) break; + } + if ($filter['negate-condition']) { + $success = !$success; + } + + if ($success && $test === 'anyof') + return true; + + if (!$success && $test == 'allof') + return false; + + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * This event is triggered when fetching properties. + * + * This event is scheduled late in the process, after most work for + * propfind has been done. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindLate(DAV\PropFind $propFind, DAV\INode $node) { + + // If the request was made using the SOGO connector, we must rewrite + // the content-type property. By default SabreDAV will send back + // text/x-vcard; charset=utf-8, but for SOGO we must strip that last + // part. + if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) { + return; + } + $contentType = $propFind->get('{DAV:}getcontenttype'); + list($part) = explode(';', $contentType); + if ($part === 'text/x-vcard' || $part === 'text/vcard') { + $propFind->set('{DAV:}getcontenttype', 'text/x-vcard'); + } + + } + + /** + * This method is used to generate HTML output for the + * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new addressbooks. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof AddressBookHome) + return; + + $output .= '
+

Create new address book

+ + +
+
+ +
+ '; + + return false; + + } + + /** + * This event is triggered after GET requests. + * + * This is used to transform data into jCal, if this was requested. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpAfterGet(RequestInterface $request, ResponseInterface $response) { + + if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) { + return; + } + + $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType); + + $newBody = $this->convertVCard( + $response->getBody(), + $target + ); + + $response->setBody($newBody); + $response->setHeader('Content-Type', $mimeType . '; charset=utf-8'); + $response->setHeader('Content-Length', strlen($newBody)); + + } + + /** + * This helper function performs the content-type negotiation for vcards. + * + * It will return one of the following strings: + * 1. vcard3 + * 2. vcard4 + * 3. jcard + * + * It defaults to vcard3. + * + * @param string $input + * @param string $mimeType + * @return string + */ + protected function negotiateVCard($input, &$mimeType = null) { + + $result = HTTP\Util::negotiate( + $input, + [ + // Most often used mime-type. Version 3 + 'text/x-vcard', + // The correct standard mime-type. Defaults to version 3 as + // well. + 'text/vcard', + // vCard 4 + 'text/vcard; version=4.0', + // vCard 3 + 'text/vcard; version=3.0', + // jCard + 'application/vcard+json', + ] + ); + + $mimeType = $result; + switch ($result) { + + default : + case 'text/x-vcard' : + case 'text/vcard' : + case 'text/vcard; version=3.0' : + $mimeType = 'text/vcard'; + return 'vcard3'; + case 'text/vcard; version=4.0' : + return 'vcard4'; + case 'application/vcard+json' : + return 'jcard'; + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + } + + /** + * Converts a vcard blob to a different version, or jcard. + * + * @param string|resource $data + * @param string $target + * @param array $propertiesFilter + * @return string + */ + protected function convertVCard($data, $target, array $propertiesFilter = null) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $input = VObject\Reader::read($data); + if (!empty($propertiesFilter)) { + $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter); + $keys = array_unique(array_map(function($child) { + return $child->name; + }, $input->children())); + $keys = array_diff($keys, $propertiesFilter); + foreach ($keys as $key) { + unset($input->$key); + } + $data = $input->serialize(); + } + $output = null; + try { + + switch ($target) { + default : + case 'vcard3' : + if ($input->getDocumentType() === VObject\Document::VCARD30) { + // Do nothing + return $data; + } + $output = $input->convert(VObject\Document::VCARD30); + return $output->serialize(); + case 'vcard4' : + if ($input->getDocumentType() === VObject\Document::VCARD40) { + // Do nothing + return $data; + } + $output = $input->convert(VObject\Document::VCARD40); + return $output->serialize(); + case 'jcard' : + $output = $input->convert(VObject\Document::VCARD40); + return json_encode($output); + + } + + } finally { + + // Destroy circular references to PHP will GC the object. + $input->destroy(); + if (!is_null($output)) { + $output->destroy(); + } + } + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'carddav'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for CardDAV (rfc6352)', + 'link' => 'http://sabre.io/dav/carddav/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php new file mode 100644 index 000000000000..2d61db6ac3d5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php @@ -0,0 +1,172 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('browserButtonActions', function($path, $node, &$actions) { + if ($node instanceof IAddressBook) { + $actions .= ''; + } + }); + } + + /** + * Intercepts GET requests on addressbook urls ending with ?export. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('export', $queryParams)) return; + + $path = $request->getPath(); + + $node = $this->server->tree->getNodeForPath($path); + + if (!($node instanceof IAddressBook)) return; + + $this->server->transactionType = 'get-addressbook-export'; + + // Checking ACL, if available. + if ($aclPlugin = $this->server->getPlugin('acl')) { + $aclPlugin->checkPrivileges($path, '{DAV:}read'); + } + + $nodes = $this->server->getPropertiesForPath($path, [ + '{' . Plugin::NS_CARDDAV . '}address-data', + ], 1); + + $format = 'text/directory'; + + $output = null; + $filenameExtension = null; + + switch ($format) { + case 'text/directory': + $output = $this->generateVCF($nodes); + $filenameExtension = '.vcf'; + break; + } + + $filename = preg_replace( + '/[^a-zA-Z0-9-_ ]/um', + '', + $node->getName() + ); + $filename .= '-' . date('Y-m-d') . $filenameExtension; + + $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $response->setHeader('Content-Type', $format); + + $response->setStatus(200); + $response->setBody($output); + + // Returning false to break the event chain + return false; + + } + + /** + * Merges all vcard objects, and builds one big vcf export + * + * @param array $nodes + * @return string + */ + function generateVCF(array $nodes) { + + $output = ""; + + foreach ($nodes as $node) { + + if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) { + continue; + } + $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data']; + + // Parsing this node so VObject can clean up the output. + $vcard = VObject\Reader::read($nodeData); + $output .= $vcard->serialize(); + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + } + + return $output; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'vcf-export'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.', + 'link' => 'http://sabre.io/dav/vcf-export-plugin/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php new file mode 100644 index 000000000000..a130cd61d2e2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php @@ -0,0 +1,63 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard', + 'version' => $reader->getAttribute('version') ?: '3.0', + ]; + + $elems = (array)$reader->parseInnerTree(); + $result['addressDataProperties'] = array_map(function($element) { + return $element['attributes']['name']; + }, $elems); + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 000000000000..936e26917ce8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php @@ -0,0 +1,89 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'text-match' => null, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CARDDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CARDDAV . '}text-match' : + $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains'; + + if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) { + throw new BadRequest('Unknown match-type: ' . $matchType); + } + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap', + 'value' => $elem['value'], + 'match-type' => $matchType, + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php new file mode 100644 index 000000000000..d7799429db48 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php @@ -0,0 +1,98 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + if (isset($att['test']) && $att['test'] === 'allof') { + $result['test'] = 'allof'; + } + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CARDDAV . '}param-filter' : + $result['param-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CARDDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CARDDAV . '}text-match' : + $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains'; + + if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) { + throw new BadRequest('Unknown match-type: ' . $matchType); + } + $result['text-matches'][] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap', + 'value' => $elem['value'], + 'match-type' => $matchType, + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php new file mode 100644 index 000000000000..aecd8a09fc87 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php @@ -0,0 +1,83 @@ + 'text/vcard', 'version' => '3.0'], + ['contentType' => 'text/vcard', 'version' => '4.0'], + ['contentType' => 'application/vcard+json', 'version' => '4.0'], + ]; + } + + $this->supportedData = $supportedData; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->supportedData as $supported) { + $writer->startElement('{' . Plugin::NS_CARDDAV . '}address-data-type'); + $writer->writeAttributes([ + 'content-type' => $supported['contentType'], + 'version' => $supported['version'] + ]); + $writer->endElement(); // address-data-type + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 000000000000..778aa2b64e50 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php @@ -0,0 +1,47 @@ +writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php new file mode 100644 index 000000000000..0115a0107222 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php @@ -0,0 +1,113 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'hrefs' => [], + 'properties' => [] + ]; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data']; + } + break; + case '{DAV:}href' : + $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']); + break; + + } + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + return $obj; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php new file mode 100644 index 000000000000..09fad008adb5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php @@ -0,0 +1,199 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = (array)$reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter', + '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter', + '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'filters' => null, + 'properties' => [], + 'test' => 'anyof', + 'limit' => null, + ]; + + if (!is_array($elems)) $elems = []; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data']; + } + break; + case '{' . Plugin::NS_CARDDAV . '}filter' : + + if (!is_null($newProps['filters'])) { + throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element'); + } + if (isset($elem['attributes']['test'])) { + $newProps['test'] = $elem['attributes']['test']; + if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') { + throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"'); + } + } + + $newProps['filters'] = []; + foreach ((array)$elem['value'] as $subElem) { + if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') { + $newProps['filters'][] = $subElem['value']; + } + } + break; + case '{' . Plugin::NS_CARDDAV . '}limit' : + foreach ($elem['value'] as $child) { + if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') { + $newProps['limit'] = (int)$child['value']; + } + } + break; + + } + + } + + if (is_null($newProps['filters'])) { + /* + * We are supposed to throw this error, but KDE sometimes does not + * include the filter element, and we need to treat it as if no + * filters are supplied + */ + //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request'); + $newProps['filters'] = []; + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + + return $obj; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php new file mode 100644 index 000000000000..40a95f8bfb71 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php @@ -0,0 +1,144 @@ +realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + + $userpass = $auth->getCredentials(); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"]; + } + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + return [true, $this->principalPrefix . $userpass[0]]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php new file mode 100644 index 000000000000..ae7a8a12f77b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php @@ -0,0 +1,138 @@ +realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + + $bearerToken = $auth->getToken($request); + if (!$bearerToken) { + return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"]; + } + $principalUrl = $this->validateBearerToken($bearerToken); + if (!$principalUrl) { + return [false, "Bearer token was incorrect"]; + } + return [true, $principalUrl]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Bearer Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php new file mode 100644 index 000000000000..4b47f56c9ff1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php @@ -0,0 +1,168 @@ +realm = $realm; + + } + + /** + * Returns a users digest hash based on the username and realm. + * + * If the user was not known, null must be returned. + * + * @param string $realm + * @param string $username + * @return string|null + */ + abstract function getDigestHash($realm, $username); + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $digest = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $digest->init(); + + $username = $digest->getUsername(); + + // No username was given + if (!$username) { + return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"]; + } + + $hash = $this->getDigestHash($this->realm, $username); + // If this was false, the user account didn't exist + if ($hash === false || is_null($hash)) { + return [false, "Username or password was incorrect"]; + } + if (!is_string($hash)) { + throw new DAV\Exception('The returned value from getDigestHash must be a string or null'); + } + + // If this was false, the password or part of the hash was incorrect. + if (!$digest->validateA1($hash)) { + return [false, "Username or password was incorrect"]; + } + + return [true, $this->principalPrefix . $username]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $auth->init(); + + $oldStatus = $response->getStatus() ?: 200; + $auth->requireLogin(); + + // Preventing the digest utility from modifying the http status code, + // this should be handled by the main plugin. + $response->setStatus($oldStatus); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php new file mode 100644 index 000000000000..e203d26855d6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php @@ -0,0 +1,96 @@ +getRawServerValue('REMOTE_USER'); + if (is_null($remoteUser)) { + $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER'); + } + if (is_null($remoteUser)) { + return [false, 'No REMOTE_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly']; + } + + return [true, $this->principalPrefix . $remoteUser]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php new file mode 100644 index 000000000000..0fb2210f4883 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php @@ -0,0 +1,70 @@ +addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php new file mode 100644 index 000000000000..7ad8f48b2e11 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php @@ -0,0 +1,58 @@ +callBack = $callBack; + + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + protected function validateUserPass($username, $password) { + + $cb = $this->callBack; + return $cb($username, $password); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php new file mode 100644 index 000000000000..3a687d747b29 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php @@ -0,0 +1,77 @@ +loadFile($filename); + + } + + /** + * Loads an htdigest-formatted file. This method can be called multiple times if + * more than 1 file is used. + * + * @param string $filename + * @return void + */ + function loadFile($filename) { + + foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) { + + if (substr_count($line, ":") !== 2) + throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons'); + + list($username, $realm, $A1) = explode(':', $line); + + if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) + throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash'); + + $this->users[$realm . ':' . $username] = $A1; + + } + + } + + /** + * Returns a users' information + * + * @param string $realm + * @param string $username + * @return string + */ + function getDigestHash($realm, $username) { + + return isset($this->users[$realm . ':' . $username]) ? $this->users[$realm . ':' . $username] : false; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php new file mode 100644 index 000000000000..c2f6de974807 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php @@ -0,0 +1,57 @@ +pdo = $pdo; + + } + + /** + * Returns the digest hash for a user. + * + * @param string $realm + * @param string $username + * @return string|null + */ + function getDigestHash($realm, $username) { + + $stmt = $this->pdo->prepare('SELECT digesta1 FROM ' . $this->tableName . ' WHERE username = ?'); + $stmt->execute([$username]); + return $stmt->fetchColumn() ?: null; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Plugin.php new file mode 100644 index 000000000000..bbb5d180d0c0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Auth/Plugin.php @@ -0,0 +1,285 @@ +addBackend($authBackend); + } + + } + + /** + * Adds an authentication backend to the plugin. + * + * @param Backend\BackendInterface $authBackend + * @return void + */ + function addBackend(Backend\BackendInterface $authBackend) { + + $this->backends[] = $authBackend; + + } + + /** + * Initializes the plugin. This function is automatically called by the server + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $server->on('beforeMethod', [$this, 'beforeMethod'], 10); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'auth'; + + } + + /** + * Returns the currently logged-in principal. + * + * This will return a string such as: + * + * principals/username + * principals/users/username + * + * This method will return null if nobody is logged in. + * + * @return string|null + */ + function getCurrentPrincipal() { + + return $this->currentPrincipal; + + } + + /** + * This method is called before any HTTP method and forces users to be authenticated + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if ($this->currentPrincipal) { + + // We already have authentication information. This means that the + // event has already fired earlier, and is now likely fired for a + // sub-request. + // + // We don't want to authenticate users twice, so we simply don't do + // anything here. See Issue #700 for additional reasoning. + // + // This is not a perfect solution, but will be fixed once the + // "currently authenticated principal" is information that's not + // not associated with the plugin, but rather per-request. + // + // See issue #580 for more information about that. + return; + + } + + $authResult = $this->check($request, $response); + + if ($authResult[0]) { + // Auth was successful + $this->currentPrincipal = $authResult[1]; + $this->loginFailedReasons = null; + return; + } + + + + // If we got here, it means that no authentication backend was + // successful in authenticating the user. + $this->currentPrincipal = null; + $this->loginFailedReasons = $authResult[1]; + + if ($this->autoRequireLogin) { + $this->challenge($request, $response); + throw new NotAuthenticated(implode(', ', $authResult[1])); + } + + } + + /** + * Checks authentication credentials, and logs the user in if possible. + * + * This method returns an array. The first item in the array is a boolean + * indicating if login was successful. + * + * If login was successful, the second item in the array will contain the + * current principal url/path of the logged in user. + * + * If login was not successful, the second item in the array will contain a + * an array with strings. The strings are a list of reasons why login was + * unsuccessful. For every auth backend there will be one reason, so usually + * there's just one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + if (!$this->backends) { + throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.'); + } + $reasons = []; + foreach ($this->backends as $backend) { + + $result = $backend->check( + $request, + $response + ); + + if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) { + throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.'); + } + + if ($result[0]) { + $this->currentPrincipal = $result[1]; + // Exit early + return [true, $result[1]]; + } + $reasons[] = $result[1]; + + } + + return [false, $reasons]; + + } + + /** + * This method sends authentication challenges to the user. + * + * This method will for example cause a HTTP Basic backend to set a + * WWW-Authorization header, indicating to the client that it should + * authenticate. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + foreach ($this->backends as $backend) { + $backend->challenge($request, $response); + } + + } + + /** + * List of reasons why login failed for the last login operation. + * + * @var string[]|null + */ + protected $loginFailedReasons; + + /** + * Returns a list of reasons why login was unsuccessful. + * + * This method will return the login failed reasons for the last login + * operation. One for each auth backend. + * + * This method returns null if the last authentication attempt was + * successful, or if there was no authentication attempt yet. + * + * @return string[]|null + */ + function getLoginFailedReasons() { + + return $this->loginFailedReasons; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Generic authentication plugin', + 'link' => 'http://sabre.io/dav/authentication/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php new file mode 100644 index 000000000000..3ba2aee25b20 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php @@ -0,0 +1,101 @@ + 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + + // groupware + 'ics' => 'text/calendar', + 'vcf' => 'text/vcard', + + // text + 'txt' => 'text/plain', + + ]; + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + // Using a relatively low priority (200) to allow other extensions + // to set the content-type first. + $server->on('propFind', [$this, 'propFind'], 200); + + } + + /** + * Our PROPFIND handler + * + * Here we set a contenttype, if the node didn't already have one. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getcontenttype', function() use ($propFind) { + + list(, $fileName) = URLUtil::splitPath($propFind->getPath()); + return $this->getContentType($fileName); + + }); + + } + + /** + * Simple method to return the contenttype + * + * @param string $fileName + * @return string + */ + protected function getContentType($fileName) { + + // Just grabbing the extension + $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1)); + if (isset($this->extensionMap[$extension])) { + return $this->extensionMap[$extension]; + } + return 'application/octet-stream'; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php new file mode 100644 index 000000000000..f4be6b348416 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php @@ -0,0 +1,34 @@ +baseUri = $baseUri; + $this->namespaceMap = $namespaceMap; + + } + + /** + * Generates a 'full' url based on a relative one. + * + * For relative urls, the base of the application is taken as the reference + * url, not the 'current url of the current request'. + * + * Absolute urls are left alone. + * + * @param string $path + * @return string + */ + function fullUrl($path) { + + return Uri\resolve($this->baseUri, $path); + + } + + /** + * Escape string for HTML output. + * + * @param string $input + * @return string + */ + function h($input) { + + return htmlspecialchars($input, ENT_COMPAT, 'UTF-8'); + + } + + /** + * Generates a full -tag. + * + * Url is automatically expanded. If label is not specified, we re-use the + * url. + * + * @param string $url + * @param string $label + * @return string + */ + function link($url, $label = null) { + + $url = $this->h($this->fullUrl($url)); + return '' . ($label ? $this->h($label) : $url) . ''; + + } + + /** + * This method takes an xml element in clark-notation, and turns it into a + * shortened version with a prefix, if it was a known namespace. + * + * @param string $element + * @return string + */ + function xmlName($element) { + + list($ns, $localName) = XmlService::parseClarkNotation($element); + if (isset($this->namespaceMap[$ns])) { + $propName = $this->namespaceMap[$ns] . ':' . $localName; + } else { + $propName = $element; + } + return "h($element) . "\">" . $this->h($propName) . ""; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php new file mode 100644 index 000000000000..61327c49a0c6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php @@ -0,0 +1,60 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + } + + /** + * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $node = $this->server->tree->getNodeForPath($request->getPath()); + if ($node instanceof DAV\IFile) return; + + $subRequest = clone $request; + $subRequest->setMethod('PROPFIND'); + + $this->server->invokeMethod($subRequest, $response); + return false; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/Plugin.php new file mode 100644 index 000000000000..545ad56337e1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/Plugin.php @@ -0,0 +1,802 @@ +enablePost = $enablePost; + + } + + /** + * Initializes the plugin and subscribes to events + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGetEarly'], 90); + $this->server->on('method:GET', [$this, 'httpGet'], 200); + $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200); + if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']); + } + + /** + * This method intercepts GET requests that have ?sabreAction=info + * appended to the URL + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGetEarly(RequestInterface $request, ResponseInterface $response) { + + $params = $request->getQueryParameters(); + if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') { + return $this->httpGet($request, $response); + } + + } + + /** + * This method intercepts GET requests to collections and returns the html + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + // We're not using straight-up $_GET, because we want everything to be + // unit testable. + $getVars = $request->getQueryParameters(); + + // CSP headers + $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"); + + $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null; + + switch ($sabreAction) { + + case 'asset' : + // Asset handling, such as images + $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null); + return false; + default : + case 'info' : + try { + $this->server->tree->getNodeForPath($request->getPath()); + } catch (DAV\Exception\NotFound $e) { + // We're simply stopping when the file isn't found to not interfere + // with other plugins. + return; + } + + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generateDirectoryIndex($request->getPath()) + ); + + return false; + + case 'plugins' : + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generatePluginListing() + ); + + return false; + + } + + } + + /** + * Handles POST requests for tree operations. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPOST(RequestInterface $request, ResponseInterface $response) { + + $contentType = $request->getHeader('Content-Type'); + list($contentType) = explode(';', $contentType); + if ($contentType !== 'application/x-www-form-urlencoded' && + $contentType !== 'multipart/form-data') { + return; + } + $postVars = $request->getPostData(); + + if (!isset($postVars['sabreAction'])) + return; + + $uri = $request->getPath(); + + if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) { + + switch ($postVars['sabreAction']) { + + case 'mkcol' : + if (isset($postVars['name']) && trim($postVars['name'])) { + // Using basename() because we won't allow slashes + list(, $folderName) = URLUtil::splitPath(trim($postVars['name'])); + + if (isset($postVars['resourceType'])) { + $resourceType = explode(',', $postVars['resourceType']); + } else { + $resourceType = ['{DAV:}collection']; + } + + $properties = []; + foreach ($postVars as $varName => $varValue) { + // Any _POST variable in clark notation is treated + // like a property. + if ($varName[0] === '{') { + // PHP will convert any dots to underscores. + // This leaves us with no way to differentiate + // the two. + // Therefore we replace the string *DOT* with a + // real dot. * is not allowed in uris so we + // should be good. + $varName = str_replace('*DOT*', '.', $varName); + $properties[$varName] = $varValue; + } + } + + $mkCol = new MkCol( + $resourceType, + $properties + ); + $this->server->createCollection($uri . '/' . $folderName, $mkCol); + } + break; + + // @codeCoverageIgnoreStart + case 'put' : + + if ($_FILES) $file = current($_FILES); + else break; + + list(, $newName) = URLUtil::splitPath(trim($file['name'])); + if (isset($postVars['name']) && trim($postVars['name'])) + $newName = trim($postVars['name']); + + // Making sure we only have a 'basename' component + list(, $newName) = URLUtil::splitPath($newName); + + if (is_uploaded_file($file['tmp_name'])) { + $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r')); + } + break; + // @codeCoverageIgnoreEnd + + } + + } + $response->setHeader('Location', $request->getUrl()); + $response->setStatus(302); + return false; + + } + + /** + * Escapes a string for html. + * + * @param string $value + * @return string + */ + function escapeHTML($value) { + + return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + + } + + /** + * Generates the html directory index for a given url + * + * @param string $path + * @return string + */ + function generateDirectoryIndex($path) { + + $html = $this->generateHeader($path ? $path : '/', $path); + + $node = $this->server->tree->getNodeForPath($path); + if ($node instanceof DAV\ICollection) { + + $html .= "

Nodes

\n"; + $html .= ""; + + $subNodes = $this->server->getPropertiesForChildren($path, [ + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ]); + + foreach ($subNodes as $subPath => $subProps) { + + $subNode = $this->server->tree->getNodeForPath($subPath); + $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath); + list(, $displayPath) = URLUtil::splitPath($subPath); + + $subNodes[$subPath]['subNode'] = $subNode; + $subNodes[$subPath]['fullPath'] = $fullPath; + $subNodes[$subPath]['displayPath'] = $displayPath; + } + uasort($subNodes, [$this, 'compareNodes']); + + foreach ($subNodes as $subProps) { + $type = [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + if (isset($subProps['{DAV:}resourcetype'])) { + $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']); + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + $buttonActions = ''; + if ($subProps['subNode'] instanceof DAV\IFile) { + $buttonActions = ''; + } + $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]); + + $html .= ''; + $html .= ''; + } + + $html .= '
' . $this->escapeHTML($subProps['displayPath']) . '' . $this->escapeHTML($type['string']) . ''; + if (isset($subProps['{DAV:}getcontentlength'])) { + $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes'); + } + $html .= ''; + if (isset($subProps['{DAV:}getlastmodified'])) { + $lastMod = $subProps['{DAV:}getlastmodified']->getTime(); + $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a')); + } + $html .= '' . $buttonActions . '
'; + + } + + $html .= "
"; + $html .= "

Properties

"; + $html .= ""; + + // Allprops request + $propFind = new PropFindAll($path); + $properties = $this->server->getPropertiesByNode($propFind, $node); + + $properties = $propFind->getResultForMultiStatus()[200]; + + foreach ($properties as $propName => $propValue) { + if (!in_array($propName, $this->uninterestingProperties)) { + $html .= $this->drawPropertyRow($propName, $propValue); + } + + } + + + $html .= "
"; + $html .= "
"; + + /* Start of generating actions */ + + $output = ''; + if ($this->enablePost) { + $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]); + } + + if ($output) { + + $html .= "

Actions

"; + $html .= "
\n"; + $html .= $output; + $html .= "
\n"; + $html .= "
\n"; + } + + $html .= $this->generateFooter(); + + $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"); + + return $html; + + } + + /** + * Generates the 'plugins' page. + * + * @return string + */ + function generatePluginListing() { + + $html = $this->generateHeader('Plugins'); + + $html .= "

Plugins

"; + $html .= ""; + foreach ($this->server->getPlugins() as $plugin) { + $info = $plugin->getPluginInfo(); + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= "
' . $info['name'] . '' . $info['description'] . ''; + if (isset($info['link']) && $info['link']) { + $html .= ''; + } + $html .= '
"; + $html .= "
"; + + /* Start of generating actions */ + + $html .= $this->generateFooter(); + + return $html; + + } + + /** + * Generates the first block of HTML, including the tag and page + * header. + * + * Returns footer. + * + * @param string $title + * @param string $path + * @return string + */ + function generateHeader($title, $path = null) { + + $version = ''; + if (DAV\Server::$exposeVersion) { + $version = DAV\Version::VERSION; + } + + $vars = [ + 'title' => $this->escapeHTML($title), + 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')), + 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')), + 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')), + 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')), + 'baseUrl' => $this->server->getBaseUri(), + ]; + + $html = << + + + $vars[title] - sabre/dav $version + + + + + + +
+ +
+ + "; + + return $html; + + } + + /** + * Generates the page footer. + * + * Returns html. + * + * @return string + */ + function generateFooter() { + + $version = ''; + if (DAV\Server::$exposeVersion) { + $version = DAV\Version::VERSION; + } + return <<Generated by SabreDAV $version (c)2007-2016 http://sabre.io/ + + +HTML; + + } + + /** + * This method is used to generate the 'actions panel' output for + * collections. + * + * This specifically generates the interfaces for creating new files, and + * creating new directories. + * + * @param DAV\INode $node + * @param mixed $output + * @param string $path + * @return void + */ + function htmlActionsPanel(DAV\INode $node, &$output, $path) { + + if (!$node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') + return; + + $output .= << +

Create new folder

+ +
+ + +
+

Upload file

+ +
+
+ +
+HTML; + + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + + return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); + + } + + /** + * This method returns a local pathname to an asset. + * + * @param string $assetName + * @throws DAV\Exception\NotFound + * @return string + */ + protected function getLocalAssetPath($assetName) { + + $assetDir = __DIR__ . '/assets/'; + $path = $assetDir . $assetName; + + // Making sure people aren't trying to escape from the base path. + $path = str_replace('\\', '/', $path); + if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) { + return $path; + } + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + + /** + * This method reads an asset from disk and generates a full http response. + * + * @param string $assetName + * @return void + */ + protected function serveAsset($assetName) { + + $assetPath = $this->getLocalAssetPath($assetName); + + // Rudimentary mime type detection + $mime = 'application/octet-stream'; + $map = [ + 'ico' => 'image/vnd.microsoft.icon', + 'png' => 'image/png', + 'css' => 'text/css', + ]; + + $ext = substr($assetName, strrpos($assetName, '.') + 1); + if (isset($map[$ext])) { + $mime = $map[$ext]; + } + + $this->server->httpResponse->setHeader('Content-Type', $mime); + $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); + $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setBody(fopen($assetPath, 'r')); + + } + + /** + * Sort helper function: compares two directory entries based on type and + * display name. Collections sort above other types. + * + * @param array $a + * @param array $b + * @return int + */ + protected function compareNodes($a, $b) { + + $typeA = (isset($a['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue())) + : false; + + $typeB = (isset($b['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue())) + : false; + + // If same type, sort alphabetically by filename: + if ($typeA === $typeB) { + return strnatcasecmp($a['displayPath'], $b['displayPath']); + } + return (($typeA < $typeB) ? 1 : -1); + + } + + /** + * Maps a resource type to a human-readable string and icon. + * + * @param array $resourceTypes + * @param DAV\INode $node + * @return array + */ + private function mapResourceType(array $resourceTypes, $node) { + + if (!$resourceTypes) { + if ($node instanceof DAV\IFile) { + return [ + 'string' => 'File', + 'icon' => 'file', + ]; + } else { + return [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + } + } + + $types = [ + '{http://calendarserver.org/ns/}calendar-proxy-write' => [ + 'string' => 'Proxy-Write', + 'icon' => 'people', + ], + '{http://calendarserver.org/ns/}calendar-proxy-read' => [ + 'string' => 'Proxy-Read', + 'icon' => 'people', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [ + 'string' => 'Outbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [ + 'string' => 'Inbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}calendar' => [ + 'string' => 'Calendar', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}shared-owner' => [ + 'string' => 'Shared', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}subscribed' => [ + 'string' => 'Subscription', + 'icon' => 'calendar', + ], + '{urn:ietf:params:xml:ns:carddav}directory' => [ + 'string' => 'Directory', + 'icon' => 'globe', + ], + '{urn:ietf:params:xml:ns:carddav}addressbook' => [ + 'string' => 'Address book', + 'icon' => 'book', + ], + '{DAV:}principal' => [ + 'string' => 'Principal', + 'icon' => 'person', + ], + '{DAV:}collection' => [ + 'string' => 'Collection', + 'icon' => 'folder', + ], + ]; + + $info = [ + 'string' => [], + 'icon' => 'cog', + ]; + foreach ($resourceTypes as $k => $resourceType) { + if (isset($types[$resourceType])) { + $info['string'][] = $types[$resourceType]['string']; + } else { + $info['string'][] = $resourceType; + } + } + foreach ($types as $key => $resourceInfo) { + if (in_array($key, $resourceTypes)) { + $info['icon'] = $resourceInfo['icon']; + break; + } + } + $info['string'] = implode(', ', $info['string']); + + return $info; + + } + + /** + * Draws a table row for a property + * + * @param string $name + * @param mixed $value + * @return string + */ + private function drawPropertyRow($name, $value) { + + $html = new HtmlOutputHelper( + $this->server->getBaseUri(), + $this->server->xml->namespaceMap + ); + + return "" . $html->xmlName($name) . "" . $this->drawPropertyValue($html, $value) . ""; + + } + + /** + * Draws a table row for a property + * + * @param HtmlOutputHelper $html + * @param mixed $value + * @return string + */ + private function drawPropertyValue($html, $value) { + + if (is_scalar($value)) { + return $html->h($value); + } elseif ($value instanceof HtmlOutput) { + return $value->toHtml($html); + } elseif ($value instanceof \Sabre\Xml\XmlSerializable) { + + // There's no default html output for this property, we're going + // to output the actual xml serialization instead. + $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri()); + // removing first and last line, as they contain our root + // element. + $xml = explode("\n", $xml); + $xml = array_slice($xml, 2, -2); + return "
" . $html->h(implode("\n", $xml)) . "
"; + + } else { + return "unknown"; + } + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins; + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'browser'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Generates HTML indexes and debug information for your sabre/dav server', + 'link' => 'http://sabre.io/dav/browser-plugin/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php new file mode 100644 index 000000000000..c14b7f2f96d3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php @@ -0,0 +1,132 @@ +handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->result[$propertyName] = [200, $value]; + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + // If there's nothing in this list, we're adding one fictional item. + if (!$result) { + $result[] = '{http://sabredav.org/ns}idk'; + } + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2b2c10a22cc7a57c4dc5d7156f184448f2bee92b GIT binary patch literal 4286 zcmc&&O-NKx6uz%14NNBzA}E}JHil3^6a|4&P_&6!RGSD}1rgCAC@`9dTC_@vqNoT8 z0%;dQR0K_{iUM;bf#3*%o1h^C2N7@IH_nmc?Va~t5U6~f`_BE&`Of`&_n~tUev3uN zziw!)bL*XR-2hy!51_yCgT8fb3s`VC=e=KcI5&)PGGQlpSAh?}1mK&Pg8c>z0Y`y$ zAT_6qJ%yV?|0!S$5WO@z3+`QD17OyXL4PyiM}RavtA7Tu7p)pn^p7Ks@m6m7)A}X$ z4Y+@;NrHYq_;V@RoZ|;69MPx!46Ftg*Tc~711C+J`JMuUfYwNBzXPB9sZm3WK9272 z&x|>@f_EO{b3cubqjOyc~J3I$d_lHIpN}q z!{kjX{c{12XF=~Z$w$kazXHB!b53>u!rx}_$e&dD`xNgv+MR&p2yN1xb0>&9t@28Z zV&5u#j_D=P9mI#){2s8@eGGj(?>gooo<%RT14>`VSZ&_l6GlGnan=^bemD56rRN{? zSAqZD$i;oS9SF6#f5I`#^C&hW@13s_lc3LUl(PWmHcop2{vr^kO`kP(*4!m=3Hn3e#Oc!a2;iDn+FbXzcOHEQ zbXZ)u93cj1WA=KS+M>jZ=oYyXq}1?ZdsjsX0A zkJXCvi~cfO@2ffd7r^;>=SsL-3U%l5HRoEZ#0r%`7%&% ziLTXJqU*JeXt3H5`AS#h(dpfl+`Ox|)*~QS%h&VO!d#)!>r3U5_YsDi2fY6Sd&vw% literal 0 HcmV?d00001 diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE new file mode 100644 index 000000000000..2199f4a69418 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Waybury + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css new file mode 100644 index 000000000000..e7486740030a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css @@ -0,0 +1,510 @@ +@font-face { + font-family: 'Icons'; + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot'); + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg'); + font-weight: normal; + font-style: normal; +} + +.oi[data-glyph].oi-text-replace { + font-size: 0; + line-height: 0; +} + +.oi[data-glyph].oi-text-replace:before { + width: 1em; + text-align: center; +} + +.oi[data-glyph]:before { + font-family: 'Icons'; + display: inline-block; + speak: none; + line-height: 1; + vertical-align: baseline; + font-weight: normal; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.oi[data-glyph]:empty:before { + width: 1em; + text-align: center; + box-sizing: content-box; +} + +.oi[data-glyph].oi-align-left:before { + text-align: left; +} + +.oi[data-glyph].oi-align-right:before { + text-align: right; +} + +.oi[data-glyph].oi-align-center:before { + text-align: center; +} + +.oi[data-glyph].oi-flip-horizontal:before { + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.oi[data-glyph].oi-flip-vertical:before { + -webkit-transform: scale(1, -1); + -ms-transform: scale(-1, 1); + transform: scale(1, -1); +} +.oi[data-glyph].oi-flip-horizontal-vertical:before { + -webkit-transform: scale(-1, -1); + -ms-transform: scale(-1, 1); + transform: scale(-1, -1); +} + + +.oi[data-glyph=account-login]:before { content:'\e000'; } + +.oi[data-glyph=account-logout]:before { content:'\e001'; } + +.oi[data-glyph=action-redo]:before { content:'\e002'; } + +.oi[data-glyph=action-undo]:before { content:'\e003'; } + +.oi[data-glyph=align-center]:before { content:'\e004'; } + +.oi[data-glyph=align-left]:before { content:'\e005'; } + +.oi[data-glyph=align-right]:before { content:'\e006'; } + +.oi[data-glyph=aperture]:before { content:'\e007'; } + +.oi[data-glyph=arrow-bottom]:before { content:'\e008'; } + +.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; } + +.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; } + +.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; } + +.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; } + +.oi[data-glyph=arrow-left]:before { content:'\e00d'; } + +.oi[data-glyph=arrow-right]:before { content:'\e00e'; } + +.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; } + +.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; } + +.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; } + +.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; } + +.oi[data-glyph=arrow-top]:before { content:'\e013'; } + +.oi[data-glyph=audio-spectrum]:before { content:'\e014'; } + +.oi[data-glyph=audio]:before { content:'\e015'; } + +.oi[data-glyph=badge]:before { content:'\e016'; } + +.oi[data-glyph=ban]:before { content:'\e017'; } + +.oi[data-glyph=bar-chart]:before { content:'\e018'; } + +.oi[data-glyph=basket]:before { content:'\e019'; } + +.oi[data-glyph=battery-empty]:before { content:'\e01a'; } + +.oi[data-glyph=battery-full]:before { content:'\e01b'; } + +.oi[data-glyph=beaker]:before { content:'\e01c'; } + +.oi[data-glyph=bell]:before { content:'\e01d'; } + +.oi[data-glyph=bluetooth]:before { content:'\e01e'; } + +.oi[data-glyph=bold]:before { content:'\e01f'; } + +.oi[data-glyph=bolt]:before { content:'\e020'; } + +.oi[data-glyph=book]:before { content:'\e021'; } + +.oi[data-glyph=bookmark]:before { content:'\e022'; } + +.oi[data-glyph=box]:before { content:'\e023'; } + +.oi[data-glyph=briefcase]:before { content:'\e024'; } + +.oi[data-glyph=british-pound]:before { content:'\e025'; } + +.oi[data-glyph=browser]:before { content:'\e026'; } + +.oi[data-glyph=brush]:before { content:'\e027'; } + +.oi[data-glyph=bug]:before { content:'\e028'; } + +.oi[data-glyph=bullhorn]:before { content:'\e029'; } + +.oi[data-glyph=calculator]:before { content:'\e02a'; } + +.oi[data-glyph=calendar]:before { content:'\e02b'; } + +.oi[data-glyph=camera-slr]:before { content:'\e02c'; } + +.oi[data-glyph=caret-bottom]:before { content:'\e02d'; } + +.oi[data-glyph=caret-left]:before { content:'\e02e'; } + +.oi[data-glyph=caret-right]:before { content:'\e02f'; } + +.oi[data-glyph=caret-top]:before { content:'\e030'; } + +.oi[data-glyph=cart]:before { content:'\e031'; } + +.oi[data-glyph=chat]:before { content:'\e032'; } + +.oi[data-glyph=check]:before { content:'\e033'; } + +.oi[data-glyph=chevron-bottom]:before { content:'\e034'; } + +.oi[data-glyph=chevron-left]:before { content:'\e035'; } + +.oi[data-glyph=chevron-right]:before { content:'\e036'; } + +.oi[data-glyph=chevron-top]:before { content:'\e037'; } + +.oi[data-glyph=circle-check]:before { content:'\e038'; } + +.oi[data-glyph=circle-x]:before { content:'\e039'; } + +.oi[data-glyph=clipboard]:before { content:'\e03a'; } + +.oi[data-glyph=clock]:before { content:'\e03b'; } + +.oi[data-glyph=cloud-download]:before { content:'\e03c'; } + +.oi[data-glyph=cloud-upload]:before { content:'\e03d'; } + +.oi[data-glyph=cloud]:before { content:'\e03e'; } + +.oi[data-glyph=cloudy]:before { content:'\e03f'; } + +.oi[data-glyph=code]:before { content:'\e040'; } + +.oi[data-glyph=cog]:before { content:'\e041'; } + +.oi[data-glyph=collapse-down]:before { content:'\e042'; } + +.oi[data-glyph=collapse-left]:before { content:'\e043'; } + +.oi[data-glyph=collapse-right]:before { content:'\e044'; } + +.oi[data-glyph=collapse-up]:before { content:'\e045'; } + +.oi[data-glyph=command]:before { content:'\e046'; } + +.oi[data-glyph=comment-square]:before { content:'\e047'; } + +.oi[data-glyph=compass]:before { content:'\e048'; } + +.oi[data-glyph=contrast]:before { content:'\e049'; } + +.oi[data-glyph=copywriting]:before { content:'\e04a'; } + +.oi[data-glyph=credit-card]:before { content:'\e04b'; } + +.oi[data-glyph=crop]:before { content:'\e04c'; } + +.oi[data-glyph=dashboard]:before { content:'\e04d'; } + +.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; } + +.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; } + +.oi[data-glyph=delete]:before { content:'\e050'; } + +.oi[data-glyph=dial]:before { content:'\e051'; } + +.oi[data-glyph=document]:before { content:'\e052'; } + +.oi[data-glyph=dollar]:before { content:'\e053'; } + +.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; } + +.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; } + +.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; } + +.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; } + +.oi[data-glyph=droplet]:before { content:'\e058'; } + +.oi[data-glyph=eject]:before { content:'\e059'; } + +.oi[data-glyph=elevator]:before { content:'\e05a'; } + +.oi[data-glyph=ellipses]:before { content:'\e05b'; } + +.oi[data-glyph=envelope-closed]:before { content:'\e05c'; } + +.oi[data-glyph=envelope-open]:before { content:'\e05d'; } + +.oi[data-glyph=euro]:before { content:'\e05e'; } + +.oi[data-glyph=excerpt]:before { content:'\e05f'; } + +.oi[data-glyph=expand-down]:before { content:'\e060'; } + +.oi[data-glyph=expand-left]:before { content:'\e061'; } + +.oi[data-glyph=expand-right]:before { content:'\e062'; } + +.oi[data-glyph=expand-up]:before { content:'\e063'; } + +.oi[data-glyph=external-link]:before { content:'\e064'; } + +.oi[data-glyph=eye]:before { content:'\e065'; } + +.oi[data-glyph=eyedropper]:before { content:'\e066'; } + +.oi[data-glyph=file]:before { content:'\e067'; } + +.oi[data-glyph=fire]:before { content:'\e068'; } + +.oi[data-glyph=flag]:before { content:'\e069'; } + +.oi[data-glyph=flash]:before { content:'\e06a'; } + +.oi[data-glyph=folder]:before { content:'\e06b'; } + +.oi[data-glyph=fork]:before { content:'\e06c'; } + +.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; } + +.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; } + +.oi[data-glyph=globe]:before { content:'\e06f'; } + +.oi[data-glyph=graph]:before { content:'\e070'; } + +.oi[data-glyph=grid-four-up]:before { content:'\e071'; } + +.oi[data-glyph=grid-three-up]:before { content:'\e072'; } + +.oi[data-glyph=grid-two-up]:before { content:'\e073'; } + +.oi[data-glyph=hard-drive]:before { content:'\e074'; } + +.oi[data-glyph=header]:before { content:'\e075'; } + +.oi[data-glyph=headphones]:before { content:'\e076'; } + +.oi[data-glyph=heart]:before { content:'\e077'; } + +.oi[data-glyph=home]:before { content:'\e078'; } + +.oi[data-glyph=image]:before { content:'\e079'; } + +.oi[data-glyph=inbox]:before { content:'\e07a'; } + +.oi[data-glyph=infinity]:before { content:'\e07b'; } + +.oi[data-glyph=info]:before { content:'\e07c'; } + +.oi[data-glyph=italic]:before { content:'\e07d'; } + +.oi[data-glyph=justify-center]:before { content:'\e07e'; } + +.oi[data-glyph=justify-left]:before { content:'\e07f'; } + +.oi[data-glyph=justify-right]:before { content:'\e080'; } + +.oi[data-glyph=key]:before { content:'\e081'; } + +.oi[data-glyph=laptop]:before { content:'\e082'; } + +.oi[data-glyph=layers]:before { content:'\e083'; } + +.oi[data-glyph=lightbulb]:before { content:'\e084'; } + +.oi[data-glyph=link-broken]:before { content:'\e085'; } + +.oi[data-glyph=link-intact]:before { content:'\e086'; } + +.oi[data-glyph=list-rich]:before { content:'\e087'; } + +.oi[data-glyph=list]:before { content:'\e088'; } + +.oi[data-glyph=location]:before { content:'\e089'; } + +.oi[data-glyph=lock-locked]:before { content:'\e08a'; } + +.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; } + +.oi[data-glyph=loop-circular]:before { content:'\e08c'; } + +.oi[data-glyph=loop-square]:before { content:'\e08d'; } + +.oi[data-glyph=loop]:before { content:'\e08e'; } + +.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; } + +.oi[data-glyph=map-marker]:before { content:'\e090'; } + +.oi[data-glyph=map]:before { content:'\e091'; } + +.oi[data-glyph=media-pause]:before { content:'\e092'; } + +.oi[data-glyph=media-play]:before { content:'\e093'; } + +.oi[data-glyph=media-record]:before { content:'\e094'; } + +.oi[data-glyph=media-skip-backward]:before { content:'\e095'; } + +.oi[data-glyph=media-skip-forward]:before { content:'\e096'; } + +.oi[data-glyph=media-step-backward]:before { content:'\e097'; } + +.oi[data-glyph=media-step-forward]:before { content:'\e098'; } + +.oi[data-glyph=media-stop]:before { content:'\e099'; } + +.oi[data-glyph=medical-cross]:before { content:'\e09a'; } + +.oi[data-glyph=menu]:before { content:'\e09b'; } + +.oi[data-glyph=microphone]:before { content:'\e09c'; } + +.oi[data-glyph=minus]:before { content:'\e09d'; } + +.oi[data-glyph=monitor]:before { content:'\e09e'; } + +.oi[data-glyph=moon]:before { content:'\e09f'; } + +.oi[data-glyph=move]:before { content:'\e0a0'; } + +.oi[data-glyph=musical-note]:before { content:'\e0a1'; } + +.oi[data-glyph=paperclip]:before { content:'\e0a2'; } + +.oi[data-glyph=pencil]:before { content:'\e0a3'; } + +.oi[data-glyph=people]:before { content:'\e0a4'; } + +.oi[data-glyph=person]:before { content:'\e0a5'; } + +.oi[data-glyph=phone]:before { content:'\e0a6'; } + +.oi[data-glyph=pie-chart]:before { content:'\e0a7'; } + +.oi[data-glyph=pin]:before { content:'\e0a8'; } + +.oi[data-glyph=play-circle]:before { content:'\e0a9'; } + +.oi[data-glyph=plus]:before { content:'\e0aa'; } + +.oi[data-glyph=power-standby]:before { content:'\e0ab'; } + +.oi[data-glyph=print]:before { content:'\e0ac'; } + +.oi[data-glyph=project]:before { content:'\e0ad'; } + +.oi[data-glyph=pulse]:before { content:'\e0ae'; } + +.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; } + +.oi[data-glyph=question-mark]:before { content:'\e0b0'; } + +.oi[data-glyph=rain]:before { content:'\e0b1'; } + +.oi[data-glyph=random]:before { content:'\e0b2'; } + +.oi[data-glyph=reload]:before { content:'\e0b3'; } + +.oi[data-glyph=resize-both]:before { content:'\e0b4'; } + +.oi[data-glyph=resize-height]:before { content:'\e0b5'; } + +.oi[data-glyph=resize-width]:before { content:'\e0b6'; } + +.oi[data-glyph=rss-alt]:before { content:'\e0b7'; } + +.oi[data-glyph=rss]:before { content:'\e0b8'; } + +.oi[data-glyph=script]:before { content:'\e0b9'; } + +.oi[data-glyph=share-boxed]:before { content:'\e0ba'; } + +.oi[data-glyph=share]:before { content:'\e0bb'; } + +.oi[data-glyph=shield]:before { content:'\e0bc'; } + +.oi[data-glyph=signal]:before { content:'\e0bd'; } + +.oi[data-glyph=signpost]:before { content:'\e0be'; } + +.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; } + +.oi[data-glyph=sort-descending]:before { content:'\e0c0'; } + +.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; } + +.oi[data-glyph=star]:before { content:'\e0c2'; } + +.oi[data-glyph=sun]:before { content:'\e0c3'; } + +.oi[data-glyph=tablet]:before { content:'\e0c4'; } + +.oi[data-glyph=tag]:before { content:'\e0c5'; } + +.oi[data-glyph=tags]:before { content:'\e0c6'; } + +.oi[data-glyph=target]:before { content:'\e0c7'; } + +.oi[data-glyph=task]:before { content:'\e0c8'; } + +.oi[data-glyph=terminal]:before { content:'\e0c9'; } + +.oi[data-glyph=text]:before { content:'\e0ca'; } + +.oi[data-glyph=thumb-down]:before { content:'\e0cb'; } + +.oi[data-glyph=thumb-up]:before { content:'\e0cc'; } + +.oi[data-glyph=timer]:before { content:'\e0cd'; } + +.oi[data-glyph=transfer]:before { content:'\e0ce'; } + +.oi[data-glyph=trash]:before { content:'\e0cf'; } + +.oi[data-glyph=underline]:before { content:'\e0d0'; } + +.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; } + +.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; } + +.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; } + +.oi[data-glyph=video]:before { content:'\e0d4'; } + +.oi[data-glyph=volume-high]:before { content:'\e0d5'; } + +.oi[data-glyph=volume-low]:before { content:'\e0d6'; } + +.oi[data-glyph=volume-off]:before { content:'\e0d7'; } + +.oi[data-glyph=warning]:before { content:'\e0d8'; } + +.oi[data-glyph=wifi]:before { content:'\e0d9'; } + +.oi[data-glyph=wrench]:before { content:'\e0da'; } + +.oi[data-glyph=x]:before { content:'\e0db'; } + +.oi[data-glyph=yen]:before { content:'\e0dc'; } + +.oi[data-glyph=zoom-in]:before { content:'\e0dd'; } + +.oi[data-glyph=zoom-out]:before { content:'\e0de'; } diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot new file mode 100644 index 0000000000000000000000000000000000000000..7ca7c170f1a7780c7ed612e469d746adad4fabeb GIT binary patch literal 23144 zcmdsfd3;>eeeeC9JBvmdX=XH=Bx|Huq#0|mEX^*FEZoYPLI_uavict*V{?@TO zMkW-`8y`~?`wHZ(8ar|*sx;95Q5460cy8M@a&Y4EWz?ix|5@Bu?7IB}JD+&;moFlT zv2NJCdwfKrExSW__7i;aoZ)!Dh8|D=_bt2cICSS{9#tA}|E!{@_usyMY~;Hy|K)1b z|54<9>yD8-Cn&2tjC2w2NB51~G5)JjaZFJL@xHiwV*kNIzw~BwMcIz$wlO)OsC{De z@{6~4mj1g^rAARs`R+$fJT`Z|{D>Nt`4#3;p?b6)z5IwWpz^pBH9osEe9M17lR2*| zyUo=T$RnAz0#nX^Hlfp`Vn@F!L^tjyj4R!vq?GTL(*wUeO9Du5*||njzJ6Xg|C;R8 z0KUhO&Ff_SMHM2dE@77Pwf7>(kQRbP~cYFu*RbH+< zUEW_YJ%7CK_Fj1zPSeHtA@`&1QgvN* zclFxp+p8z5zghj`>R;5@Y8q=+)ZAHfs%E<8hc&l5?ewnpj(Sgf zzv+F$x6^l@?~U4M?b_PowO_Bj=&$v+`v?4c{a^6E9#8_&z>2`Ffd>N9fu97g4!#&# z651PjHdG4zbJ!7X4{r(|37-xBxUQ)#UALlcux@AFvAR#xovJ%q_pQ3~_1*RT^#|%d zQU7%P>kZuv0}T%}oNah@fnz~&!Qg_u3m#qYtp&emY;3%}@##o=M2kESIUSjf{2=mH zQ?_Ym)2Eug)bx6@t+}arRrAi~C!3#-c1CZC-Wh#7`orjNV@EJ#yGx%3MW#4d0uU0)$(@zSTAHszP; zuQ=>KS^BgpkW{{+a<-kbpLROvt))+c{C}Cw_%gm!#+PXL!LyG(DuOd_Hqg&xo%m9t z;4e-E=!9avSPq|Na^{erPP(YOX;PoiN+o@QCdKs3YK<;xQ&XB|?5MYwbp~VjmntjE z6_l)^nx;(|TT_!|JwQ;=P=qqhR3?{A#vQ@z%M^bZ4QeO8cS20R{7|}7O7A{#V)sMn zD(~9aa!NE5Onyi;<8+j3vpo-w4t0aPM*6e1+Dtf%`iSr^QuPQI_U3tF0vU9o9N!`Le*GMm=NjI<4i_=) z&DJq#l+e%3uccIauR7$C(3N$})le#-XYbJm;{B2-FOL#)#f@R0yhV>gJXa)JZ8a!g zKPQHG)~>Fro>ufam8BNa8bUGAp#FJTnC|&TSoJXEK zg8y3nrabpu-sykj_v?QJ?qHx4@E2Luql6_-Gj;?RS|CixFoiN{^z}{J@aCKPGjG!5 z@v}vGV$(_e=1n8V&#F<*#KVuAJwCFDo+#d>-&{lzRbxi^i+QwNaY3V0K$_q;#=QCx zV0CYur;8wzb0y}oXOIEH0(qgB$#>ANkW#O-vT92CaHuwn1@Q$OiC8?D^CfZ_e?IJt z*_f3ELh1Z6%C?lH>A_kr{eAczEl@M_i<*FT&xM-0Ns8+K(jI}xBaghOQK}(bZ`AY? zHGx14?bE2<_o1gsk)Rfg%>P2a1U?oY_hX%7X)y6%A{aoAa=vWX7xTHq7hIa=n%Uuk z#1Ye#&zD|sxx`wR#)1gN%V-?j{K^{UN|zOt?Oy(@oXZwv5lUtAeQJysx`TqXVen96 zO0tE#KbMF*67i%x5p+77vBh*#gI~4V=^~FN{fWBxV85>P560_0ktPQPBlPK#V`VRu zjyc4~YonN#2lanj*tfWUaIk;TvV~OrAT>o!Reo^K8&E6Dd0(}H@hfRGjpk!$IG6Qj znDD6o$s_l#dh7hE`?XTvU>|61@R9wwty^>Z9~qo^uC1?+>yfk}l33qrsJc9p89M|* zBIisMQiX6KSa1dlQ)wFh+0XP->5UtwrZ#Sb^qx|qmrg!>>5`d&y$lv)(i!RsDiP2M zBqVps*+g<$av)z{HkpV8LCCnOKI2Lk2aj~F+Q`7#7v(aDr-@)qXN29ILi0-5KJ`3hmQ918kk zaYrDO!A@WrF7#7DL)%(97@2vM*GMF1UhN-J9UT#_3yh~T5}bLJG1^eSNctlk;MgW_ z<2{d^0-{Sna5AHZ0KP)Ls1{}blg|JnNlN7xsq;J3=6dU2U20U*cI}$^ipw>-XHjn$ zy-uMabQvTLGB-#JTda@3*21U^tRa>z7_^VkxAnc?LRVJ@Bd!mA_q#N&f&dU zI=#5Qv0i=Z>F^Eo=FA!Ib^c>_2~S13phou&r_;@ezQ&`C+Q=^LnESfTTJ^f6%{MP! z!?R`XqX0PdLIvyOmpdsq%T+MA5U&u3rC_X-BpW+sIc`&Jb{>;4%)lQ@4l>2})G2dc z6+>d$c*>&}p2)3Ox!L~`jAF==k0{VoB0#Tc>=ze|EJw z9QyxuxaxhI^X_UoI`f--n;+S~deyS^YD8^wxb%;?Z0`JKU%iJa^&^`L`yWXr?p$Bs zc0hxd{{mXkw6Y5$&E_-dW-4@1Kc$<<;S3j$!OX)$m}-Na1e{@GK$j~*# z!Zkxpp;gXEu)4ufOn5Mv@#sQZ1f{AHYg^-qkJ#)EyZyRgBY&X&nT#g~5>;p_ zVp|xEM}mPSPoijN>zLpV)@53$0Iy)`#=>`y&)(`w1>??esD_d$H4&F&E=ISyTsP{+ zD1IqyAE>BFrYd&b=yE+Hrd>17xy1GtPwMWw)cU9WDQ}IRysl?lK$YA8?|H`Rez}X9 zl?3KK#x0pr0`yGSKNLtUI0EtkMuADB2rp|> zo!6?=6oTfC_gn4ZYPH9z4UYwqO8%LxmE_)}j~JOoGjsB|PKjdQbj!H|)%MZcxbnDJ zPI+PO9*APOi;^ZFp@*NTunpTP231 zKWA(p30`XPM&BS$GxJxlJm4;@sxL-hmlI-0pRk_6QiFBCGx8Cua+2vm0!zMZJ@dKD zXZi$}oL8rdVl+vUtRC?*L(Vj#J#z+pkixcfsF`AE4?qMKH;HYqm<$1oO)PO(i+NoD zF>EVUH`tRj9dd3}gH`&e%Ai`4udbXTI|Z9)t1%brI6DWpcVh-mhQpU543_HV4OttZ*8s#fqlWSqU<989HKeF`+Jm%$;32QxP z+jNmL0U)|2gbxQA=6KO@BkJn5y6HLu+codxcfv$uj>UMC;iurM+SYL+@RXXA%c;qQfVrn zMq%vmZtn?3(u};)J({k&DUIQmp*vSZC`0ir|VBpH&4)9y})C1VA!2V7k8g1 z&2K{TAft~ebSmf%lp$!V&Tt~tnh84-1>W&eM1XsXA<4oGZ55g^|IM4rW&_~?5`>-2 z6!c(|raY8bjQv$uOu6OQj^Tm@hGGE3YC;9*#O{FR?acA1gXBVe!?=^X$a6|6ay5NAQaQ5 z@f7x_d9#$?d=Ww;n5Kj65zsRz;1Z}BI&LRR3;@x0C}9+L;{{^}MB2XyLx+4n%{lG7 z?<9YgG|g+21>O%ryu@>iMoLf_)Ud1atqUcF@nt%@Q(_A&IcS%p)y(4{RPxd*%Nu zi7gi!hGc^@X3mJeH@k;zC1H`#CB{a^o^+XrkI|mV=bgY`hHhRk5m>$WRaMPf=b4{_ zgV$Eo@Fo@024gSjA7g(1WzLgbo$YTev|I3WKV$}WtB;Ki38-^Wio!k$Qw)mYi&~n? zNf<}NY5f!pvu>Bx^nPT6GiaQJ{PKC=wF-l$!%~0cYFYE(%;pPyf;9(&d~=0V(t2ji zws!Zw2nk?*SiEL}QAeAbqF7cFU z7{11qzW&V+9-GDVo1X){9{*g!(2#%6RV-hw+QVa!^eQog9U6-p{R=R?@;{){R%R0N z1BIfKCTCUy6tR6)N&tqq454^>u>PP}{70SG)0aQXrYQxc7|>HD9R{%`a!IIj(vTLm zHxcXmuq`EEO-ezc0y(f?hdfWFTDyqldLNavjXlv$t-^Py(oV(O*Q__a9MwQr4uzX{AV3h=tkIf~i^V-88t+QgnyiRL}uVqp>es(r7 zuU%OeYQautYf}Ql-4$ z!6VpDeQm+AeIZz0VN#jt85)kcjod8Ej!;IOr<9x48d$ zZBaqfHXSS8aig}PMZ5R&CKizGSu%$00Q02`W~pF)SPN%&B`<-sOBtBV{(;oW=Hhqb zB~&7DUOA4r68ji<>AkVfyfVO823~q^jP<+l5_%?ikfbrm+g;3anR4L4g=}N>73dR= z&>@Z2mUTQ0x*+>ru(+)1X_t)oF!u2RSXXkqu1&UPs<9>Ftk3p4ER>N-v)_eRneAMF zT6sE|9=m!?Pm9at2yfnT|C)6lxoMrxr@Gs^2CmWWm3D|yWo=_5*i<7$l9-O=R;3Kr^Wd`q zU~KM40@gJGHlP%%2Vn?s+Ni3kGvpaw)p2)wXXnbBYa_vWPrrV`t^b;iF82mMtbh9+ zS}LZ=ck{}Qj?UwqtA=apf|0;V{iK(?bl38lkSFpZ{iXY8skMjM4#jd~PGXcx31i#w zb}oOvLE1!_1=ZNXT{bWwTb3k8O2jx_s#vfl*U_prkA(s=j6Q_ zwRwvxb{D&DUN^Y*rY_@V;g%JvR<7El<BmN6ieAmntSMgT4u)Si%qHi*74#=gevLdda{_h+0LHA8BqkbslFG&Kd8cmFD82Zofl_?m3P&fLU8eHK1L z8y6W%7}i_H2f$6tS$Y2%b_$-)Zr~(oGj#^p-UX4^1>6pRk5zTz10ia79;B=<PmRh&yoU5>5wf%gMMi1Vb|ZJ6U=a@~|Rzi9)}cO0rQq ziHllDLPHLR(%~i*)?~}JLX%t8heFjMS4XYgrdD~p4u4&&raj=G+KQ{IlSOYaz2TmA zH5h5??pZ+8+~ljSwg*-dsZ|cY+WTUn*RJ+CRF~6J=?~5tXYTLxdlD{NP1swdIvqCO zRqI5evMH}RszY})hpt)@sB;DuHhDT@3#)2t>MDa_hrO!WSu6Z^R5rUS>}sIeQ5EVc zlc5}q0a;CKi30o%#>5LF9B;zT97}UWLi>(Ji}`AXbV?nuTrMntJtG0Fu|rv`WRwCh z*>Zy)TJnLfuoTV8i}!PH$q_JH_F3a%{D1eGc@k$<^pqt41SAneu?ODX|9P^Xdu!o< z^7I@gK(NRxJ}6IPyYe3kFzdTque92g{0x#JX2*xGbJ)=EAR9Q5SsOnA3*gHv4NE*- z4Zm`X(LUR+4Gqng^+}JGVW+orR==3?gD0F}Rv?Cg5&z6yzvwfy8NdED&Zmuj_Gjs@ z`TburwU=3*0i2zY+y-W0Y5N1t&c%YbL&HGT(gp}N!b-k%#}3O5U@qw4?EXv36t4O? zC98r5QmQDJ0(RyYxE?4o@Y}O& z17IKc;nRmrH#izQdmPPxk73Be*B%BcKE-KdXvbwcUWt2Pjs&6xOO%EWr;CzOQXc8k zzo>t)j~%Z^2Jv~aPyak!*UR0T>mztM%)roVq2I!(3IM>=z_9`6RQ(Q+%_P4s_{)?R z99aMw@cRldwZLNKGmsP)w8AwY4CLO#<}5E;1$}+KXU3NaX9^jgUtIG0XB>XezpR7P zoCiu`hpfR1Aza`KV0+|3)RGSOvvrq6dol%Q07H$i^dExzbx{obP@?$KEqgM0g39^YHMj>}3UnW7!P#gQf4=BG`Usd9I_pJcpvqax-rF|FQimBG}65#Rs0j zc6^@MloJ*hsNI-hi&N0^-$Ue{x6#22C+(o^raKFveGUhxn9_~j-CeW(E0cynL+n$V z^+RVE9>SFbrQ65Y5ZIXAg!ij9n~dVX24o+*7T5yX6hqkcr=$mZiiX+UY*DB|qt+gg zwqo8TtqHGhDzlX*i7aiTU??YwMeW2v#Zi zBc7@n_|a^3@Fg%ML(=U=4vmtrd|*5~>@6giAAnNp;r;h{{EfX`<|Cd1bEH#S)QC04Z4NaAovO)MB?M}b zgOWqd{v%aX3B&d8B7th=o>{}4!ZN;x4$gRiIY!XL4n|ty2Z$Qp1ke}cH;W!4#j=M( zbl74juQ7^2|%Dk27f75{bH68K1t z3^H?=)COypv};34+kG8w&F;MB`iEYo;k}cP8z&pWP##+I`g*@aPquGQ$wKdKc@^;eeyO)y)Z(IRwksK4{BwuLaQEZNZ5&u%Fz z#sSNS^$}rFf8s)u$;WNz}?_oAzFE&7cVIZD+h8nEhi3!1f09wA5)1|a| zB4Q+l&m@zsJUwv6uz$+AHt1KrXIiFb-{S_^Mryog>UyxXN_z(R0A$(j&X#Dlrt-4M zezV_D+GJ&Y86TSi5gBXQx$)-Q7_g^#{*3|7j$M&^0U9Z+CYT?PZo-$5lVsTV*mlCu znRR%z42#V}V)IbcwDuBY7hdN+Uj8iB;On3-fKMsuB7*taGLg+YXC3}#1TJttI}hNO z0CmLJPXHSTze%B*3TY$jI%iX50065N?U7I3mc|~M=xa;g_Q`;lw)M5y5KD*%LJ?c? zR~!bAW=%t?Bk6soYxnlGwe{_-J-sh&ga+{3%-W&lcqF5{Seos-9IuQnoQw5?Z)2{1 zjFSzlH7~~R6884Y8AL9^nHtz)ME2=!KrikAU>Met0T*b5`*8VbwG!*dc52h&D766a z67q&FobFw>@OvLVec{t$dil!J7wSLonG2^s{5`p@hTm9np9G*DSP(9Jsw5FivC}X? z!)%cOvyuPtB{`3E!&jpoNdv6#L7QXs142D#bI#rP`fV^ZvHLi@K>znYFjKa`(4B8! zLn7!DIj&bK+Vz7*k{-6z#2ff0O<&SkQ|G)Ci9meo7t)l;p2!CFthsB;8;@_H zP%qAS0JdCg8Q9h0UFlu3JYEsEf2uK)G*TlT+oe!m($`O3+Yujr3K zu|2d^)3zQ$Io{JaF^TbT%pvG#UaTRQg%yzJr&kd8pY}t8{e1}^)TpJ{!f8wwWt6eu zW0!SGdSY8CNC0c@qdcZW8n08-8g{Z}lZNA7{YT@p@Sdk$e(0_d^6I;O`Qz+Tl++JW z+pfK}-?;aom$#Dl`xnl&eQ@muB^`ZM^2G%D3Fa^x#V&WF&zbTWEW?PQyGeq5He^9 zX|pn|NPOx5@&L?TpLhXL8Lk^=&fMsdw;Vd-(tqwMm0Z-|LeHTC$V&N4hGk0w<*{_) z5%5Wdu=(MT1mIn_G{Zr6WiOTd7%@d$4fDL8N#2UcS_=8W3S_``3NcS9hnh@5m1?GZ zEv;$T<5}Vv@O=KCsy8GvtJ}6U77tCkRL>4EeVzW*)=#xgHfoK}-uS0K`UCy9LtoP3 zjvhknNZFdeL?@z3H&K6#9X{&^yGk;|LIrW?*jrj`n!Cs;R=&nB2Cx zwa%$k_N*G<+3(9=m%n9kLZhGQjM5XO|;)z{achl5^&1o1nAxYK0_^!%gHg0?V`J|?P=LHqXFMSDayz({; ze#q_<%qJh_NEgB&#iTb+laGJksDAE_8;-IQ{zE&~9Dee1Z@wv}k0Nv54FH=s@5S*S zW__mIf@Piko`j7IM<5XAX?=qK8xb5v3OClCkh~scaHcKDha7VWs5B-1lyNB8U#V*i zvE`oQ&2fY;5yUY+i=**;$dcuJhR?9Ew>oVL)nZ$TmIQ($IgkZcY^zh$_QShNkL$m9 z@)b&!zI+#A8`S9CKh%G8>PZSbuIaD7a@UtxLBro1O;&<;v7Z)u#Kx6&2~-AAGFvNZ zw^U{Ad0I8C-SYT99Q)|c@7FGzmik_sX-S*bpMUmdisK)8_wF(7$Yl^ zSH^fg%?K>81PE@^#Ls~&3{$~0af*b`_2?6zN}7ZOaWkevqy&tiZ6#?@yQH6!I8@$< zWZN9pz@D$=^8>u2Atfx69akHZBC@En37@LyVR8KUq^Nc{(IntQ^C^W%u$#qcsNhm4p-oYvN}V)`rq{BHTy zd)9OMphS~DmEXcAFZ$1AB!!{wn}L%vFO|PxybgamXRedqZ2dx7njwsQBj=~(Pi0yT zi@zj2@mc^eyloB2uy0)4$;UfqK8o)xbn$=6o~Tjev~a%o3C@RJfr006q>XnZBJKq2 zV=@w#V+Ek=yR8E&@_6}0J|l>uDU6-jMlb~GWo`<5g>&t#_(~Nldkn7+NOjxexNbVC zw;K}!%YpHrwCR|P#Fg=`bF^!%p<-cLt2%2%to~gYmHA=07%=C-D0gKDrV+$~2TQii z>Lj3V(_?@b6&${U)fArYLRi3+{V;YWV?pdu5U1p2k0IB}D3>LC;veA?^YjrTuo63i zLx}kx(A0p26B(PBcCB+xn`9P(S<7f1o)@5UNDQ3TDtDB!A2p#y(|v7I>=vDXonWf1 z4`)SSL*N6I>=zZuzBXfQs%et}uQXyz(&1VoXam4e1~GtX0Yid^Rpbs*Z zMA}WEEJ;Qxt)((n5a)rwKMgynG?KzXIy+C`V;{C_n35Hf){A4(Xz~DqYT?(4VN?x&t9ATers*era_Tqb+b1rnNuMIz5-B@vh{n|L=C*_X80~k$pa9jaemf8kdl~=TA zTv@0AmvEmDi!tw7`88KbjN(gwjlX33CG-ot$n~{K;~V{X0q557(Ofq|pVTzI!|BHx zaV?(%K3^h(7>1n%E<{ZFCaW3 zGJT~CywZHcouwr_qA=1$*fk2{|2a*l#*W-@a0<3y0KbM|-TEhsAKOvhLLP zo~Uza4&QAL+~#v=s_M$d;(3>9(jeREoh%ismhJD*$Q5Gz+l!R(va17nP{ za*^$d(0~Z25W8p@!5pVT7vK-&2H|{6s(b=erKF88d0@gbJN!8Jy48=FwhVabsK)8Gk+nT(ju}IT%&Udx#Pg6FJbn{#z^`;e2g>! z+5NSd7C?vp!%VA)BlxwMwkfqVYNqWG$DhI}hOJIit!^`6HdXkyAIYapa}(emAZQ@!OM!@NYs{ z0*t>HznaEB4SGmkjd#27W1u_n_CAz5qyQ3Kbzpqt(6|;I{Xld}*M_d>=6(AQ?1>)T zb7*&T?f!j-*6u&BYdo6n>W(hledy4{lEsU6B6la}b{*W=wQu|o>L~jS()DBe_Z?Kw zS>snph1pHk`_0&KMs1crsCj4SV6h6v0&bqcxD^`FVa)=XY?idGM%Nb(9`r^>8tcLdWQZRJxhO1&(UAdH2oz#PcP8d>96QT`UZWI zUZQW&x2Z&bjb9b~4*e~?Os~*)>3j5j`T_lSI!Av;KcpYgkLmA~w@<3*=@y9C>q(2A zjOfXVo}B2(i=Kk$DTxwNbeMxZjtGMy<225B9j%FoXF%wrXVs!k?9qg5s?`cnK6;k zL}pxMc8YAb$o7bAT4XaKn-$p{oUbBV5ZR*0_KNI?$c~Ean8<1(J1(+2MXp=qdPFWQ zav9jrMJ^|Dd66rKTv6nDMQ%joMn!H+4o_-J;MV3TaWuh(cBra-xtIg@Py)MWI&|Mnqv$6vjkB z6NPb6*eQzLqSzyfX;I9GVpbG$qL>%Of+!Y6u~!sFL~&FU$3#&R#c@&GDSEp_Z;$9r zi{6ar&5GWf=*^4Xg6J)Z-d@o=B6>$f@0jS-MDMuh-6=-8#Ym4BNsEz;7|Du}oEXWA zk%Aa0ijiJ1G9pGs#mJZ#(ZtA3G1@Igd&Fp3jAq1WR*dGvXkLsK#As2B_KMLFF*+(n z$Hb^6M#shIPBGRk#(Kn9T8w4HSXPYX#8_U86~tIkjP;7K5ivF@#>T{$CdS6a*iNB! z3#~_JX`y9=mK9n~XnCO(gjN(i^Ey7PQWIG;<SQt;ScwXs*-`HK=76p0$;!^O`vT-g6sA9SOvHx!L(@xe7gk-ya|LxS+E zDBmc1qGI6lm7?G|&fqXu`)_rbqT~LC7K`IzbFWq?F6BeiDtb`hB1O6SLQ%;ae@}tZ zdPX^OTHbvKz6)`hp4t3FD&`2nlwId*2PupEWfk}Qv%jn<7Uc(jIY_x#z0+R~R<2N| z`^&l#rf%?;ElQ|*6f39(Ux8cE7|pVZm3`M=)|76_=l*h#GEDi&Uk+A!sCoXfuJpn< z&1)@6gt}81tdw9Bxk@R%3-J7T$}D_mDt*yIUzCO`Q4+R5CGg#>CPIkr_o(N;8T|o+ylr?;9K0XF_Rdalbq67?0ZVRNHsj_`XvL zOVP(rC12iX8jATvQ?Q%puc5s%5p@%>|94N`4^KZ*j*G@QSZm>eV~bG|@gI*; z97;;pN9z|ka!TpM(#eGdagqHZ6Jlc$W8!1u(CpG4mGdnXWfbmGg87&zA3hR2(=3`Z zsf;QtnKrR#N@QH$SPWi4lVUWVf`{lbwxZ&~DKUWoO4=a$06+Vw=T2I2c^r(9fCI4M z_{{0wz`19X-+qf!{Vf&M(wU&q_W#e1UjqDANwVqEkE#S?2356N=b2YEOkzLpzZCD! zs`nSodoIXp4|aqoJ;pNT-*vMty`_C``>T4d;480!#J#$geyn>h<)&WuRP|84pzBr0 zpCBxc4gq0R4CM+icsC^sa;S$Au0$x;DA!?G9azR2l_=#FFk7^8yCj!*kYuuw3Szxe zxl0+K3{vg}u@3*CfhJIMd0UC-_)uBeJH>y$U zEovV%TD@KEtH!GFYNDE~rmFqaJJq|?0qP+2ZZ$(4q7GBWk|CQO~I&EAg(ZOe0*$tTzq_dLVRL;QhaiJN_=X3T6}JNUVMIhL40BS_=MPmxP=l9G~>Qj$`W(votM@{;nC3X%$w#wW)n$0f%nCnP5(CnYB* zrzEE)rzPhm=OyPS7bF)Zk57qBiA#x3Nk~adNlHmhNl8gfNlVF1$xF#kDM%?y8J`-P z8kZWMnvj~9nv|NHnv$BDnwFZInwOfNT98_pIzBBnEiNrSEg>y2Eh#NIEhQ~AEiElK zEiWxUtst#1ZG3KQZd`7BZbEKiZc=V?Zc1)yZdz_`ZeDJFZb9z&yx6?By!gC?yu`eu zyyU!;ywtq3yxhFJy!^a^yu!Tk`LX$N`SJM)`HA^S`N{by`KkG7`MLRd`T6+;`Gxu8 z3t|i63gQbA3K9#F3X%&_3Q`Nw3UUkb3i1mI3JRqZ{eM?A=Y_Xl%_!<(kjWAd3InM; z3u37Oz8D0}dbe^9SnytTIf$ngTdNFb&uMlHmk3yd)0mFe)WKQP(7r+rnacBtAA8m)i>0`>Jjx# z^{D!m`nLLx`mXw(`o4Nh{XjjgeyE;MKT`jso>Wh%e^x(M|Dt}PeyV<^ey)C@eyN^T zzf#Yre^vjceyx6^eye_`{$2fE{XzYQ`cJhDr2V7nQGZhZOFgUpOZ{2>1*FbZuiCEu zstVPocBucY{-!CKs%ct~7Od%-MY94;U7>|&p;|ZXN-a#gO1oO?uJzDtTDWG{BD9{` zHQKe>b=vh>FYN}+p*gikt+#fgc9RyR-K^cBxwJmoty;8pn|8YvqxDsmX?JL`TAUWI zC1{CSl9sHcXsKG7)=%rN-KnK(cWHmn251AdLE2#LZtWf|L%UZSq7BuCY4>UOYY%7- zYQwcm?IG=9ZG@JkjnqbIk7$o-+1g{;<64e3S{tLBA-Q`Kq+~fHmk*NVZb*f%FsK+@ zGFRKKy{UZ}1b7+LKWI$Q{Ggq|LxN`pza7l=(R!nP(lW)e$I@Ya%DTk*k+sbTH=em7 z{ECHFd=>I@=!npR-LC0&sM|Z;KJWI=ZtYi=T)FMaFRtWa5n)MTnPJ6Yi^IOVD)Or5 zufFo?Yp))Dbyat@`%T?5x=-lt?$N);q#kuWUhmM|ye>?Y*J* z(Hmd6X;ak7sE=;G^5)(*kG*;A&40fo?v|Oibhr{+J6x~3-gk*U_C7Iv2K5=;XKJ6d zefHft@z&B?KZ*{Io*2FVwqdtTx!r#Iq}%t#*kYcHamT#U_u9Ts_1$qt=p8#^uZ_Dl zZbjTL@saTl#LtL-Hz6jWHsPDZs}g4=?o9L~MJ7Fzv@7W^$%B%gO@2A~yObMJvQxIE z{5>^2bsYpsLE6cF;r#~md$-@&{;vM(`oGuzvpaj=`OKXi>G!4AryooIr7vspW;dx# zP5kMmpr#0Z%KL&iWohy)uzR!l)4ptL81wKP`cvC8Jwf4Tcx!v6Ju<`>C35U8UzE!m z<+Ab|9Q9xTW^s?+tCw!f&u3)eG``N6<~dxvyn;yG_Lcju2V zrY>!oWg8z232n@yi`vMO=!Z`B4)Gs_vG(R5Zxo(0gAJSc@MMF3Easi$WqRhz8Jh<% zLo|wD`yU29b_)|0R`Tq$no@(m$4hjt!MocOA4 z3>M+~VWIykiHDaO`}ipPyCPfX*Vb|UllQr5gq_<&3wA z9LBtEJ#6E2EUStNOmHhbVzd3G5SPo2IY*U6w9-arv10yKk`MwdgRwN|46Smk)bK(p z<~iS8Fq+7XQMU}=yp{U9^u}gOJR5b0hV-q6@m7z@TfJ>|)GkLI_26|AA-Vl$y>2?2 z<8!;ba+Ixnx$Yl5QP_pyAHhSm$ZqBEX=Lb{2&h#YL4AwAYmUBT-#G7e(|J?~+?D3A zl^^NcdkZE_w9tGp<^x63%*qKnKmAyKyTF*t7M}ABusi_@Gbbx5M6|l>zBZ4y4YzUG zZw5iSaBt8#aiWJ9(S!92#c&4=9(^+t8vYKrP7O9roYb@4nDl4%CHwNFzZ`zU@GdxM z&ls#9&U$GOOJOOK24{^lP7Kyhojl-ST*Ke!p9Y;WeEmeE{fQ?^N3krHwK@CcC#auU z>lbWc+u8P&+t;r*_{2ZiGw#;c&ssT^6|tfPrL$&HKQF($`89Tg9VvbNiIB8Iw>fZEh%5L5_V)DG zCK$dHUy45AwdrrO4;b&qufx`l8Qd+p+jl*-aX1^wL_cvIwtgrM-}u-r>W=17%Bdi5 zb4O--v!^2ybLfqVpcV1Dt+aYrWFkE&(?C-G=?n@__4dXB&09LRvT}hT=82smoG}-c z$*1YF>dV(Vyw6+K*VnIidNq$ngjB%T0EX#0Kz=%~u=u5$W5vsP8Rmpj_;vMjHzUhd>0thS%<{1)5y zF8;7(xx1#y;rrTU_qu&iKDQ+dv!(umpZ+-XJfgOmoLy6J0`Ar z^G3s$V|nxEWBe+{jO8mCPbP+^fk@FIw758-IoVB*#H8JuJ8|0M>=C2A*~0!f?NIK{ zv=1Nof&I(iTHLonbGSM(?OFTB96r2%|6zy8)X`vpXe=2M)dM~rFn~u3xqUp~}lhokzPwv`cpd`wMs+2o}s!4}8();yJdT+na-3*2DY9WI26N z)~qpOvK%h&L2E!QIhiN(OmdT9^KRd{ZXY`sct(-qvU@Ca8C~LZS?Den+s~(u4@pfO zG9=Y0qAlj5w~{DmHN^zTv6Uo>UlxK;JV73upXt^MDko2_EHKE~qHgf93aTCXtXR(J^#<~%;*@o!uve?>qn5AOO)JHNhjPm(R01uExn9ub0 zPwhIs=4}H#hyhrG$`5B8VXw12bvxH>T(_}du#QZ<%h201t#TDQGQkXsi5+;b%YJJJ z&xzozn4PksqO!6gC)Viv(urya32z%%yUhXxtU|zT2x_0x{tASHFH~%pH)no{ezNp% z*5<*+)>ZnlXP;YMz0BYnyrFt^%`?v}t}*hObziy9VIP$F;237@&&W2W*ZrJX1%Cv+Ef~n za~W%4IxBs)tY*5Ap07_|R9?!aGdjgqvenCKmm44K(zlG>RnT0rYs2=ftkGaRiY;R` z>`VQbY80xmEn8Y$vw7pXt<0dYJ6R%bv4~a4$r0DuvE*H*1w zw30QlO`Gtwv3&FNWk!0FUbFGpO>DE#BB@JCn|m!)>lUnASZ{Fa5q)j#ss^@>(FwMY zRk^Dc8=~jXqD}gzr_*_V5z6qFF48~iXG|#*JwMQ&uBoW5WW{V|F)K9)HwJmnVNn;j zneT`1m@Y-I_cN1(ePtpW3@C;{mXoL!!%Wisj-3@-^}3Q5Dz?^bt*gV%7|b*D>NRzB zYnZ{RA#l1&NI_&HbdV&}b9AIS&SkQic4@h9~MRlq` z1CpiYeJ>7(UFxZKDFp<$QQiqh9-GG- zLnC2}7cW_4#E4fbDIOV$SzKS4I!aYMKRC)ht2y2Pv>ae7^Yq~F&_Heby$6GfHcK^GO=^-yvy~ZdJ`{;|t(R+y0tWi&oCXFh22Rvci+7czW%O)w3}hIgkXVYMcSp z$ba>XvfoCa;I_N_1Q48<6jCfH!n zkUiBVcI`4S^b)gWxSk&?Y=CaKE2MpRP|nAY}YQ@`H?0Vp!;CkKm< zu?^q@PM-9*%=3O*hzt$%pJBf}Bn-op0+(nG=1O=fY{F){6r9-utaB?W<~j%P){m`g zD(dDs20$#3-V>j??DABH!#cOFVvVESVIgjGwmbX{iJ<_hu-GQr)El~_aEA5v;ad0n6|>YsdJ<4fyyuQ<%RbK7Zt(=(&r9^8}$#vqc# zRi>rX3i|~!=55nqkJ$kNJosZkSC~N^h#ZGhN$MVzAPUe>E3d{Z%RM;+n(f&lv}nEv zZ5cecMTE{T5~11XAvAl{@|c5l!q&=nYL`sYz6bv^SV9idKY%NT*Hs1=mTdycoU^+{Iv;r%MF;lJq~<%%gHromU_X!m;IPX@P}rlpcr>6>pshCwbr;M-vIWWN3s`@& zcv4Ma+Q>JBx%XpRH~~Cl@U>p>((q^!zW(-7Wx|)W6;o^qm-c4AaOtQg;T!4~C<{%2 z;teH@n$uN9LXeXrhZRYjNwD4Ja=|0vb-QQ*UEWbvk0-muz6 z$Ay3*``Fq1=gL=98sEn1w$lTuO7wu~ba>4EEJsOI?6-R5it^{?Goun<6>!xLZEY{L z^0SBRaWwnzh>+LBG*zU-gO&LERQwv}@)eS%;O>EuY;QxSZ7?n<<-;~T!CkBNVW6WsiAG6bA3E2OEwRfN@Fz`%KXiqE2`~!U(NRYI$5GAL;#8j5RxHDknx3}?2L-MjbU56TU`a+tnmal=$L*T}Zcn?HXZ z*vT@VEv#N#ZTx`F*u;h@Tjp#oZ>)NO86Sf{KDL}Va^!@w#mYV$_vX-gL##Ze&#jrU zjje&ESiNf1Y6q{jtYRzNHEtuu66!gtEJjh{t2b^O^?VD8lj=vUc zL${(HT(acy*v{a-qSb0UK~9S^$suT*6G0VkQpG#Tn7A^UHG&5wB~6^DS5$~vQO87) zA=3Eu;(DHj?Rso!4#tZ_Ew8JnGsOMAVGXmogeOh9*6M%JAKo!+Lk6r|lc3LQ zoS<-;02dhA6mz8!Xig}#%V|!zCg;`S-H~=pE%}(H$RKP`Y?gZqildW{GaIdH=FP}dHj?pqQU>jD|*VitrURb>VGwi*M ze}#T$R+Ynb;Frqp-R}$ab!TEL-^y0ido{26?l8T!V#C7K0ULOA<+=s6M&BQfeW1T} zl>IaF=!iG_u!kQuh7ODQ9+G=@?R*Gr_!*WOZVT5%k)8JxJ;e!Z;rtp%JaWR4SO^F@ z(~tmblHYUV^d4vcDz||bps|3&pnMq!nV<~fE4T8;`0M`#4wI11!yLpucH74D4GlOo zn%-`~Xyl0?xP#uU=q(!}V=T=l$i6SuS^DcFW8|=*xb1a7X_7Gs{n8|Z z=Oj{s1d@ICuz{C@p?z!{;mw6*Y-{x(#DK@~jq2)sf~Lh|04qotM<|oJgN%~*lwinq zf(>oXdTWLurdVy?WX{OTVndApesZw4C$?m7S+Sz-IWqEcEMI>3;g`<0thRUFYG^*p zJ}?MIQ8y0TDStPaz*1!}L2@j!*{sEL78yLmYCF@Zxq~JK&z}~8XqUa8WCp?r{X#%r z)%LUtgv;Bw*OVv-yad0*b}W2h!Sor^X4POjwv3Hu#krL)Ox`u2&jU+FyHo!*_XIOu zWwkTh)3W{$|CoyvvI#3Dt=_b6)VDc}rJF}r6s=siec|?{FBsL!mMyDg8`!q-YpU1R z+`(;ow>GS5eq`?9`L7=;eSY<}x`r1ftvAXx==-<-aorJ&yIiRD^<`_Ot|~TWmg!T9 z7ayuvIj=5z-lDaY>)+h+iuBs6?ISPxaYbzWb>Av}PvDaErTUqC5kG9m+*48=s-hdYr zjzGT`)O!%CAR(|h4!}zS!QrHD0q7-?hhth&Up8F*9s=rcn@ARlzO)|9Xc~A}E|&@6 z;fp5q6sRZT5@P`G0w~ zK?va5&P`xhN@Lb`DjVPpJwo%RA$sc$W?7rEcRyk9AH+ZT5B#6{?%kVSV~3%!V!sne zlZcHKNSe4%mMlfgjg1&46HXE!8boPGVdL@Xo8%Z2vdaU%$z@bSdo$D<;wrH8q|cm2 z%iuaEDd{5r1Z*1gMiyV^W$mc;uUxgca_!ZDWd*{Z&fxHeNsE|~OQKz(Kz|=(B-usJ zj9dEjIdKc`>HLhncjV{?M~A*M;tTLx&l5lW_+!RTqNlSTdvL@fPo?{Rs{=Sg_-uAtWNZ7t1z2&fdPf--7v;{~lN(!`WaY>`lVf#svob4O-vWNG9eEed_iByMwx}{!m@f7RR^YwJ`=$+z` zy96BeVFV~dvSs-y{ZszvXZ(>*cs6hh0a=NOqzxCa-~47Ac4koi-?zDJ7~UU$zbFh( zT7u}rv|6wa@*X64cHYNPUOl&Zt|8tMZxJOC8j$qlS-xCf<6h&g$C91TCosJ^FwO0E z681*(fHBCg!-?k5wpVfM~Q^(5c-9Y6oE&t$+F{ z<2}+pDQcXw{)yUIH8a?(rG_s|ye*xgdU`q&JwCnb$cWF%|Fm`c%57^mZ8G-l(^uEl zHL$g2WMU4RQBgUM9%bUhd#duA^7pV~O-*L!wYAUHvpTcg61I5h67up@vV|3k0VVc| zy+FCN;?^*};vYZ!nC|IetMGn>0FgPou0x|ozI0gQ#*JSz^_i*9&Z%R}jDG;5mIA2G zuA8~Ga&2Y9+!nXd;y$tA-FGiDUc*0NtWAz6w`V_l@2ug&&H1{_$S*VMONZRlv};-8 z(#EA579t>QYptzX(<$&Xn*u-6m(5$7{cLB`#I$ew!$i@ydr?jv!b~S4*)ViB%e>=G7hxwV?I@kuqpOT@ewReVNGve&^Cf z%9uSy*)MHDvQTn3(oNuc?B{d^r@~=%8)+=e#(CSB))t}}j-_V4(27WC zO&}6#cwe!KqrQU>ZitpI!@?o2#3%h<$;fZjXo)Y;u zpdUh5aDN06g+L>qe?lz8XjD%S&KibL4N!bGxpqxh&|*4s$)`&i@0TAu-aZtY#HG<5 zOqK0dz60DjZo!1HY%E3U;j#(X>ToaRuo_*$9w@IRPcJb9A~920%GD#Cd0 zX-03W?bmFJL{4xL%Ij?BvVXDg-dniJq3!#?O-K641maec>+nrpG=$N+25^wULv(~h zGDI1q6UcNa62|PBUEp(@C;<%V^54GAygfW`)h6afGLwW27cvgsB}5A$f^>p(Gma9( z$+s=yMMV+>lkaOwSpzyj5R=?fOjVhdI-SjpC}TxqF%Ql9R@UpT$PTn_mlhNUD*~gYm9*ayX(a&mI4R-B^ zgey8XcGLrtGgtTo0f=scVrSFUkU~rkIuH+eugY0cM`=2qgi6X{r4HX1-?&jZ@s*FHUshS=-vqF{vfu3 zDBjNgWbzrtM>3A2VY$Cs^hB;+`WC|%Bd)P$jeKGtvLkQbn|xxV!6)+xGT9X4m-K&W z@?4IeD@i?A_PLHwoRe}{rG&t0nz1wqT(R{2gdh{SQfM5l&%1gceuyW4prvbB3JCi0 zYa-mh)IyjksfA1rp3HP4$w*=HVyU!l4iPg&l)Z4W-v7RleV7O~0xFb4m0}wS@fZBa zk7@54g*zTd-#uABI(pCDtiM4TQffrMrgX5+U;fk414h#h$*)q&K|7Lq#Laq1%gz+j zR^Qifp_utGyZrevrqut=pU}7~kl2D@62Y6^M|6pgA3Q*?cVv2yKjm$UFbfn6#Uy&H zfKV3|!gx8>bI_2n;flm#bGH@(+j^WN+-dl=2=q+CxOlZ;;@{gh# zxFsuEKV#*z>MFQ(=YzS#JhYl;VXKy|T(Z1k%ZvtOLLGhe)uTsW&3(whie}ANFwI@% zrnY<@+O1+Mmo5iWZrxJRFr$%~O>St8}Qk&{sZ{? zUoLN$)iA5Jl$Eg5$)(w2v&UxK$HWyziD#>oo!+^*Wnas_58h||3U+Ar8{_tj+jCz- zDl@VX*&}zHlmSGZB&y^myqE1YnSNmVB9Q-J*k1EvVfIKv_DfF~wlB7A+F8GwHLwkf z);*2W>)t^;_6uqo^Pb%&1P%q7VY!hkrrpqIfMgq@@h zi`L{~#VW?RI<i& zJcKVo&J<$)D{l}D;;&Bl4!jQt!G-Su2e2fa>d&tg*NXmhiZ>#yw@fser^s9a3k*6n1d3GbF8Mr?HMm37xzoAn{Ay$ z($&)QN{%*%?RLtkvf3b9t+v~x5+TYn4I>yOAA|hzCrrq9qWAn=6Pg^}QI@7%yPBN5 zyEH$6ygn@e zSU3Z2Bnz6HfYe8}X!rIP?ZP%n^vDw7j~J7!==Zp|<@3m04~X7ESL!3k{+Nm+ip4cG|{#b zaNXK=Q!IjBzyJf1|C*+Bx5tArEO|+!=2VzeLi$rGv;pK?plKx1G@T3bp3>nU=k9VB zboEA<7f>gdLGwd;Q`(i6EFrSg>)11mY|4g7xF!jtz?U@%koE^nh z`36OMH}egO_G0V$^7>g0AGtc+K5Q0VeIC}C<~WH2azEfC^+zc+I$lYFm(}D95Dd

r5p_BLAu#&L59BB`)Vtc`5zf(^6auU$X0O^jSUb@kM3 z^S0M+t6kk#xxI2*0l$`QHMVZrO7aZBS5m=^QG6eBR<575db-pjBn8cmPk1vSt7a~l zGk?L1c~hgs$XPQD+nJfON){9`1NQw{>qfSIb?thCo#p!)B|ye25k+NNw>lA<-MWWg zQ@g!(`@F`fwT5rJXu*q5qzBz?)qw?&Kn!6WtGH{Lh!9wWyDx)t_b+^P-YZ7TM7{2* zWlyommq6TrGsy7*Ypi;HfwAicy{xvRrliJLF0%#j8XiPQuy|<(KJ3Yc84WXDWDN}s z8~AJd7x*o~f*>X6Ilt$t&E;+T9gmlM8v$7;QlJxI(0dK^0h0Pc$}Zb$QgirC3cO2U zS`il87qUP^W%retk*9!J zo^;w}jR6f&#J-h~AEkeg2!r(p-AzUY6m)nhy?Qh-#G4#3FvK?K$Thlrb85vxMG= z;=?Yy5%q*8BGwA$A8Lo1wclB7jh+Z<+qo90wt)=dP7iJRjb6k}2I7r~h(Yu<5ZO=| zTMICXjAmjIq-PE~TILp!vx46H0_Raw5NJ$#TI8yOxhQ!8?75W>_Snwyzd^>`9b!92 zQJA%?eo6gequTP2l9%bJF^^E?&p23?D?W9CaO6b4MC1sq--=I4kqC1@r z%sq+M29Y+lPc-QT1?>Jk1r0N{RbXD(wiOK<4BpI}^~MD+RK3VJ8M{1~_VtL^tubO^ zOzr=}4?1Fd)mRYUs166I1>T!(fh2nU^W-yWU*Z=TQquY-<1IH4sh|1$%QL6HJe~Sw z%9;KKkK~bh|1-&7rky^0`l~NLKVyhU-dj)ZpO%ataX6j!Rr2Si{RmEwjspm2pww1T zMxdUMJ+!h}A|E}$VjN>7DLV$zM3h2@W4R>h|Dp?CYQQtZn0N;-ay;XeF&&SIrOc||#bpJM7zo%mglNAX)A)9|Yw zi|`{2Tkz8gZ{Vj9&f-T5uEviN#NY=92IJ=frfVzkI{>e1?*{b@8Vdacg^%afs&_Y*HV_&uY8 zQKDKAJ@{n66RL-j|Gjha@+CG~=y|KU^WdBS{4)4{si7Q-cYToRX2sMHkhOMsIH#0b zp;FFULgAYyAO*3ehmuyIH<1AoLq#CJgN}i)LoW>;$y$g2x)GGeMK9#|)d_A8MMI@w z;{{n+g6s?hAd(Y7Mh@^2q8*60;t9-~1>Tk<6(sE=`D`E(sH0Hw!ysM|8pwc`lYm;X z2Z4Jf(gmnTxFS@#p?uT3>JRU9Q_GlVh2KeaUc30+Q=w8Pi4hpz2!DGl64|IUA{eGOFE}CI4>~1l(-?NY%6Mnnl%1jI&Xfi&RCO)$xL4&EjV-j#Pyb zBq1-HlV#2vIdPofcI)%1<{_e^5=WD2}{@nFCs{2o`y|}Oc f&0~6oE_2;^JyG_5=y&0iW9ggm%ZFWmXYv067aL~9 literal 0 HcmV?d00001 diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg new file mode 100644 index 000000000000..0792c003a68d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg @@ -0,0 +1,543 @@ + + + + + +Created by FontForge 20120731 at Wed Apr 30 22:56:47 2014 + By P.J. Onori +Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0f94acd1ebc42d7ae7cf1ada87d4301d73d64e30 GIT binary patch literal 25568 zcmdsfd3;<~eeXTz&Z3b<(u`))Hqsr9G^1rCOS8+eWm~pv*-mU(UNXv}XrwD|iEY*K z5{v^W0RwIl6q~9Ep~TM#u7Na+Q7ib=&q0OUx4y8cdmXBXFe&64@ zcSa*;A^m*b`{zlTImNjs)5{{a- zOh^)|MtOX4UwT@qeDl*N--*w_0er$eGlK7k?ZHVulN~>&mw?~q$yNu`Y-Ka3#Yru-G524(=d*7iu zKlcTx7Uf|{l26=zU^4wu^hwM${A0}j&3)-Rr&-$aza?qp4B9LE)BC1=Il+&i`~v3q z==6a*4*iQQl9QzED<#P=Iel-BM6OYc3gOYODQVi-n)zor9|M!W+^XugeN5hwwdxRLrC|QbdXR z)2Cj4whSd|RWBJN1*rkQD8KRg)vNfHu3lBN=Xk_S3j;Ea3i=fP6wg*%|NmE>6K+Vq z;4VPUEJ+c2#2$>;{k1=M<_llouKBlW+0Qh{$OHa^7b1lPtBjl_db{M7>Hv(e$J`P(dyIitw8iamn*A+~E*3QUDwvtg z>`f2lp1V2oeD2#Oo|(43o;y1|_DAh(BRkad=mekZ``M-N^vTzo0_Sf#{Do+DGI!Q~ zbVvQxy(QXxW0!F=agQ88n=%&3s@eaX$HH>f+(icRBW{$eppC#AoS@C51Z*vKzlHhj zm)LceF6F*r3AHD37pXsu@ZMus7~x)Fj2W#*?&9K6vf1|=LmUaac8rA) zvRSO`eb&I}wnU2iqsKybZ5!yH$Hx@GiiufUG|bOkG9_-2%Ad-u)=|{UXROj5oJ1Co9Z3M>}gPtV^`9aB-OzrkoRF5&##?Nk@lY z*2b2I-D(Te+T`(}PHRJJrbUS@U>t$pd=6_b+%JI8FXKZgi@5x3Mpd(_N(t_mxMZLS z=Rsknfg<`CO~CP#R4P@0&If>f9+S)IF$2HjEpD4R7>@>xg(`va!FVhhJUW<44IW*= z_a3EO?vcUz`oTW7y{``$o*LTmk>fjt4u5gWXA?(rPahuI@!Qmzq54o;{ZQY?_N9H> zM*4>8+d}mkPP3T;PKVu2Q&S-7aEBB2p!vvSNARb{Z;sR2MJe}(4oB{7>@pDLVwR}vc~k(gfL`dP(;ckK zE!9XZQV)(i?r~Rna3FT4$?t0l#O(f9#1Z$HeFh?>PIoxIoJE_9^6b7U8~Z!Y-Kw)< z{udQa_3ld*)iX@V{cF3|+wNtRB9RqV!E(8JHFv7Q>8xP;RaRsF$m3bBOLch{e_>r5 zpFACR;GBJ75OJ5^<;03&_Nd41vs?I=Evjmn-(dmA;d7>2`I}7srhw7!GMmi4CG6%} zhio*m%e0&D{nbq){W+3x_u7Ij1j@mT+3M@|g9{l>rvHQP7R z2b>#u_&{vSme_%ZN9Lbx>+2Ik&=(Jgacne_BVq(Y@W;%-L@?n=xDsYpVm8die)hB6 z+3?1Vv$GpFf_u-(%9Ya(UAdxH;4Xs%>3D{*T#^^K0uD*bGB+>*mKf029u4??E+8^e z*{-GR^2m|Us?Dc%&%cI6wXu*&i8|Y#O7+vC*Crb8+_3wUibpeTeY6##o~h$0TDqV= zg0_IeF?)o8R)h$Xb=pESTBA`!Tfp-iK6}E0ncXhCugTTn|d z(ToBh zO(z6oNoXe}(8dTTHyLDuk@lo*)A>h&g3MBxg+%RZJjLglu*NsrYbsf3?#SlEfrkVBJJ%;@9^l~BzvQog2Jga7qwz?%ktMoV zKMOZ7li8C%1rd)*22g9{#A)_u7pG|nS_GDx=`c)Yx6|yEo#sH$OvD8+TEPOb4kogF zJe~-4F-x7J!g`Ol-qgH)$NFYtef>RUr7o|n><*_9I052g+EnTGx@?uJnpdvyx*YW~ zWBvVsfl8ap>#p2n3LB}FanR^IWVNxcS1INJw>NctGI4#X!98I1y2@)!Nq;4P*`%}@ zyy#WtU)$2;|D?faG8%7ic|CU7CXX15Gl8bSkiQI5c@3>flh@^JsPrd|q*ZYG;9TaU z63`05ZXA3Evm0CNL06O6n4lkX+CF} zf7Zgczj!)ly-Tin(h;;(IGD}yw1xB)Oh9*@WOawQMU9dlu=mkSI+p-GGu)pDeZUlr zdY)*%&Np&a65myk$O>H{i(FwLZ3Ywp^$ z$Xz&9yN`ifPV-c5I{!=(G7+9JJ0n$25It~UL6`H->=yl5Zkl>78q>mjY=+H{dPL7O zKGTT#bO_kNg$-eMBlCqTAw=MC1GolDn8tu{iG>{2tfzHg!-i~mtuesn+~zH^t1Nf6 z)FoHM%S&gOk+~Y!77Z5bMZyDBioI;eJzrW`S=x5Kt%fz^wiXD9w>?ioNWL=R+{J0z z{bs1bK_7%u!8xGv0w#077qAf=uh`$9T#i0AY}R9vKyvsJmT-IeIsg58H|KuTKGd{f zv}W+W>(>YGKg8$m8$s2evi`>Eb@vTzM3n}kuz)*wpTm>zSlH~9E3*VZo)Vb_IY;C1 z#3Masj~SrWTFMHgXeQ1=$N@B#g@OB{Iya5SbetlSKhMIP7G@5bgLE`B3LOrmI!#q@ z_sk@179?3T_7qeqtWX_#oDCFoqNG;FB)}A1wlR}LlJB3u!m&w+B|&puvAQxeJDdMo z7%5+aM>LcePtXE8NhQDsVx;);fqW7ZHAX-PhyWLOPc+VO`WwqCau+MgHs=0iBfE@3 zMcMoje9H=GD5_BtcCE{1_3AQyvW$#-MRJfCvw{lfNvJrPfc^fL!&05KIW zC&LKHXq>&c`&37-<#|X>xnI>E8L@w6gKS_X?()uNv$IZFfw9B3y~pJZljIfdiHscC zaS*aHcq%mj3tcq7Q)*l^f6(mDPwjym4gqvI^Xqfom(9K|X1wuA7FP$vB0pC)tIyMa z1%emgia7`uHQBzZXa*rzi>+ycruuXJBS&Z(d`=s!KLx_H<6sf~lt~T1=CB9$46slD z#+ZlK!f01MeEn?~+QQ+QQ>>c+v@kB@F?Mjw>JKM(pUN(tLY%R-Mj1L4^amDU&{oYJ zf3PLuG5ZsA;{}TV_2wx-!ZmFbnlb$>f@Py7;Xx$um>Zd^5|=bs$^1)jzY0+ z3(cx!HbT}DQY^$-jbh7+Cgm^gV-|gMEnT(;X|vD;gal0ei73!si|1L?2U{hr6RdPv zlbf|(1fb$Rg|!(y!r-Ry5G+lfi4+i;hh%c_ebCh)cw?`$Tv{t_#to!%hj}or$yU%n z1Wy2l3QQGb0OC|sz?evaP+v(uAs%hk80d25gD5P7P=HS*5cY@ZvJ@A-0;b^#vpd?o zz-M5<6<{@V+z@dL2%_)OL+fru1MLQQ+rI)shiD(Bns&N(fXpROT7B-E%M60Gl9G9TiIBXb_HPo0t&;L$e9u3wBL<6G?kIoi{`N(sc8LE`inXUzSz8{RYuaffu$t>f zH7J8hRj4n91D%xzG(Gyx)5^oa9E~UXIB5k@C-g#E?ya3UxvqF|j+!Z&~ zK&Kw*Gu>z-z6Nkzd$as#vR^^a4K?w~$Pmuq@&-z`jp{Ftlwl zuD<<-(77QI!`x5cTIeU|DT`6F{%N}=wb#ziKvEGjb)Vc)#Pl`eHXAEq z?}DU;UN5TA=hen3)44BcvaqB1qb;6T@ zV#L?`SXSNGqlDBF`<2ql>QcsfmaUI&TvV64D(XOI-E|Rl%v4z#E^CliSvN(((e9FhPCdMpP~~{0kXbaZ7z?A|rjxbFkL4D!q0DiNLJJoe zTDusAwj(mMrmL5P{k%$Q!kRn{2EiR-(SA7rWeUV!vse-KuuxNZ>*!zt_>&kMZ7sjG z-Fsw&B$zf>+M3Ec4jwqQoG@wmsRIW)%5xjMu8||SPkn8!qI{4FKHr0@e&D}u%EmX|Pykn@V=m}_MpOz35KH&&WsdToa7@cc_p^;S>{7t0K_M7rjMlei%7dmCKb3xRKv(w?p>jrz8Ef$k!^NtgP>ppq& zI=fxAwsj3%uihi<5ZThIdatXYLa-!0=Zmde8ME80ZM!zFg4osAXma=W4u+dl%^+c_ zXmEM!t4al{)935twHjbk+$On-=1Sb&WwbU_nw3JYe8#qb$p2 zcjeeX$4A>kp;fn3d0jP?{kc=t+^^Wt6*kw$a^Jd}E#q^{e#@$kj?nSYz*t4K%j;Z~ zJ8fe&cGrpuccu47xtBi9mgVm-*`bJUEO3lsE}>mJy3WPzYlTgeNKl2r(`5h=GDMl6 zNPZvXvn6$dv5t=At2)bSMAp~S+oP~*kzdi0yC}-CVz4Dw?oM{yvTkJU&0Sidb$I2# zs)0?pd9Ao}La)$isiezg&^KWe^E*JC%pPCN4}2!-Lo^{Tb${n2?Op@AU~d#i$0$n2C z35#{AzvueOBe0(LRbJmiYA}j@{V7}3P)IMmMh}W*8!j@q@?r}aPEBT0 zlL<)|H=AHv4m6qFFeKy8Ox!CGhZVVtC;H`JfQ;G!Byu7E4cX%kdm3a|lk>I}HZ#25 z?Jjp)I;xBYxvbJ=a#Z^&+MOm=RdQW*o z-20-x*C_XyWQ)17)Zr=^XHJA1m41t%!ec9w%_f6=^*Zh^ZHUXJa`(PQ_v)q2YO}Mo zp)%xaEvu@iE_HcK#UUJOgi;YPg##oVv6wH^!JgrV*4QB}ks?w8GT91^9_IN2rLYtg_{9e)=lKYT zE%~gGXn*frSj2G(tfwdgK|mmZf;;fe^)KT6wA#EJP@G?o2@ot&kPnLUxUTd=3@ra$ ztXJme74!_8!e_*X(goR2^B`+-B9S(F0un$;0f&V=T@JspkEDIFUuzngJ!%&oEzM4! zx0*TltOGRRJdpyPa(Nx|dmX$_*Jd2Kr>LH7bdWzQ_qxOJl&-xLXa>$ZBB%|_!ov0k zIXmWap@fMcRSO#+$OtL<`4TxSJ0NpG4=49uSmcoAE}{nJa6N|<=jL_I!Ud%w%}r}* zzRYG^uzvNmN|}TK*zA< zv8{(86`!R%D%9g*9-EMRV2*@D4U#AfA7%>+2u^vVFZY$)SNh2DdSnEj$NF+#VmBbL zuy~E2BxG3lL0c~ceJ*O|7Ur=NLaKK?tz|;bZm0gphsAE1{ z1I$3&Oi^>W*F>-t(E4`ga<|g z13`}r4X9^ZO3TPWoV%#GfZ2@ZFwRXcF7K_XX>cJ}CF}53mQ}!yW-x*-fheKQ<>gOn zuHjkD;j0z0#f7Bot%7@vd7ai;(`6Cl!eF4+@S*|y%m_Js1MLsiH5k-d5NRYtb#0Sy zX-0zq_{a-ICL})r3e+M12HtQD4k(&U5N@IgVtg+Dw{S-6<4x{F`_iSem38n@&o(qP z?x^ditao{i@y|0fwBgjuyO>?8Qed%L;v(;Xgw_>(o zyVuL>LMYT%Vvz6K;4N`tgyQN(o1?mY=@*yYO9~g^no({*yg`8M(iBUj?J$VN0?Wt- z|NnRj|;nHOXEidE;uo}7q&=K|IWOS7rcV%kP#nlDxzTTo<(elXaZ*c?kO!x=l8&CNbD9Sx6rl5 z(uqi7`2rUtic=9Q*L@VA4dkaF5e4aMh!}*d_?vA@;UhURLd0Q49jRK{uBz=iFQ9lG zyVxyy5M{}88LcmyS-Nd07|W3n5EK|@fdB3A+7lkW!~O}B*hyj9zS;Qzw*2Ai#j!-iPZ2;YjZBv6E5iiW}F!?P5>i5-zh zc;!k~4dVj|G(V1j1#C3%3dJx6T?ii&!w@ZY1iNAUZ@FbYxJTa$D z*qw<&S8zD?p}gl=6U>Feav^LOOpCUVmCW(v%t!VsinZge!>5zSuM<+5P8Y#bf#n|1 zi!LEw(MDXp7Laf*PMgFRLO9Zzj+37DMGRz<^W zS(*Pwxx_60@`|$oX6y5+m)()-58c()3e(Ec4fXxxmdeLC;23c}B>M}y0L71z7^nq} z(p|y!3FozTfClWvnh>BFh!>t=g#7IUK=22FmeOLp6gE#pjQH@GA=DLb51iNRp90nz z{)%?GWxCLgCL|lF)=t;;U~3ij3}%NQOLlj%M3Xg@j!mqabV6a172`$hWDZ1RY~Id| zmJ55pohJBe8!YTy5_bU_DXb=d4^Y={j|d=XHa@bQkmyW0yj&!UEt13*Nl|m^OW<8- zPHVjSd7Qxuz%K}&g4209(Y56~8aEdl{(1y1PCjSoYcn905D|pjxAdI7lR0$1%Q??oBPta-gT|7e(c<(2l?EJ zRp&0%eCTtR&VB4vaju$8Ku{k)L_LroB=lB^-xVaMp`VSBMFzx1{NYPbo_v8)Z5)9E zr0_wTBlQDJJ!UX36zu&5n3~9a?5WHB`zQ3A!I`@AOoubC_N=bX}4lTBVt?HzXogIhgThxmBp0|T@(W^8li7t(C>V7Ru<3zG2u)a={o6?2(xq3L zcMUb$R@w&p0_KWp^A)cb>^pZU%p%cKQRkk)yN2I9GtAt*c*g@`%jM>wUCp*tw!sxm zC2m{Ee>RMG8ye)?uc9-NXw=_x2bE<{u;TzJ923xaVYwnHv4@VGb+kVVc{YZ>aw{ey^k z;J??@mJLnKKfCq^Yn$3~);3uIV%u6nAK8&okvn|uTlN)!a|1*DvSZse%AO0r##6Lz z5pR@_7n6%7W6P=gVaw>RMu!i){l)=VIsMA%)34+nfns}Ti>hupL|dm#iCAQcIRqXp ziZukW$Oq&(*eeM94?Cd2{=N_&WF@cI!fDJdizs8w$1cVc_(Zl+pa9O?&f)-xFkS~) zi^oisO*YnK%l&AIwch>s%MaX@X13g}fBA8A8G7dKU~RkhR(<`R2VUO7Y~R0hvF*ca zKP>R*^MWq=u}%<&LKM5WjdpX;u8|BahVEt|?2{P-rVhbrlaSAFBemFdJz_oE*QRMb z2-AjOzkm$S70OFpTejZU7 zmYe3!-((R*3Z1d!es0NTEv(jpl|u&*gOV>SZ%cy(gt!y!fZk;A=pPD6fV^uJW;p1s zZ^ zcDw?FS2FvpGQXkm1xux2ymi7z`5%l}wH^gsU z;^i(wm8q)CVe?pPYT~zcH8{(B?$+wInx>ZOPV45Lf%!Gy(hD@L5!S|a(oFzgA?6G> z6>I2%(ru>22z{@g$qS78ovd81X?aH*EeLK!k9}iTows~==Ww~VF4`HRc(_`ftAnLG zpDoI>FX-`p^dKLC7a1;Zk6L?Htmv@@mJW-MKP&NiVOz`+U!{_!=A&d-RMK$o^8)vR zCn%Qa6qWmLgs_SCy(s=eA5Z67u&k5cli?!65eSL% zTyC2Fv#G^&G8DK--Xxw@)V8W zA$k_m8G46}ywzcYy9(EeH~SGBNr5a#ajnj>wjX{pdnWgb$6jH9>{stXY=f+P^oO}0 zoqdcsZAUb*Y5q@dwv0VhjAyU0(AJ7SFBlFfjFSV^Wsqy&tiZCPPayOO&or2nFJ%Ax6@mCH|WU=_*SYu5eAa0lY}ER}aOG`{im zElXmtC0ky4y|Lkr`J4IXV^5vl(A=n)1^*-Z(p^9IOr;n4j`_O&5W*lgcgkw0P;7A<+t$7=kVmUJYJB&iNKLcYR#?Jf8UCi3}c-IBo&7UFJmBf4N`Qh>>l3(AoQJEhThXHdQjB?k8U}`}uc(8O^K_>xz>mCEd zsNm&0SWV&SPIx$6*$?4nk}QZj3gi^D>=D%FD~fpuz44FmiADN|7Fda!K_SHSBGBxR ziZ?Pg5$@*SbJ`@T5X@SH^YFYtDhJ2F+ginrQu3qvWhLC#HcM{NY1j#7+xqaX2y6)S zLM8b{d7!UN+ncQ0WI!vm7?ZH4N(R>Kk_IQZOSHD8!k0VS-r(40%Z(FIWmAjuks3#%*JVAkVSOW;sdv8Ch_~OTwRvEV zA)8Sn>8{WS{4RR0Lx>_|Rt$<*Cr$N;`HhmMhC?0&a;agrzpG7{4sBy@QbQiU8==9u zA3H1C8!9a?-hy*9m6VLx$oRN>NvLV%a#Ba``DT@D%{}6%Y-q2v%og|#)*xa@gxwU% zk|3nQS}I}%@jejfr)DP=Mp9Tv3vdD*tJK~<0CRov(Pp$t4J}UmJ(Bg zTtaI?EXJa=^eIc0Pv9%}I(>=xvsf2sk>#mU?HlWP9`CK=qgrl)KB=ly!rPBGAuWah zy?>v4rnx0qbD#i!iVV(>b_DE zyu$Q~J8?^RL}8@!l4}&k|BI>+-?UH&%~m4xX*1*{oE%-nAe2R43}HxQwj>_}tXhar z@(0Y&hK0qWlCez#11E^Bwge_V`q2r0Yjy3a?b}z$)gHI3bhP_wXj>%il~$j9_0ejJ zYO>#U?`?LID$ACruPJVkbsQu+y_vW|erDslOmfYb3u6cwO85I0M$p%$uRz;lfiIF> z5gHJKRES%&oWUF?Ll>YAi;ckf7?9}=P?-e`48Q{sp5NiXd#_s@cpH@@3A(s6CTI@n zx+PdU^!+OM^GKNt8Y!TbGV#1={ZH7LFURhC=84b7*s+6?v5nhr*cczbPDt>4Zr{jF zW2qPWw=Y?;y&skw!7w$NfS5@i#;=u{uwo4AiU=|Wb&edbN7@;>hI*^F;+GjlGKp?~8n^erjvr-tKSJ?Hp;oS1aA8mH3>NzH1w7f{2AU z2hyh^4*bdx3B@m7-4MSK^`8R&q~f0(hzKR&U(8*EQAIC<4dLd2>Ja?%ex)GY`}g)N z37G&IDfy)F|8*c|gFhggCKeH%rV`^W-P5$oU=*IFZxW>+Uumn7V}eKvEFiCw0Zlcp z(=OReUZ*`^3Vqcj^6JsuU1+7_#lpHeKKQ!mMXwLA9^r}Ad|>alnm7?=f;T>2|Nft| zTPv6SyHtjohM%JU|8)jXd{}tr$b-x2DSpsLR<1rQ4apy;qTHmN9Od<&pD{)JPb!Ha zI*CSozYpZT_k3RbjPk{0((gaWxcNTC?elf-*Pq%K_bG0B?OKYT{lCn=+q(00`Sb(N zkJBq|`(Ar3Zu3F%ziXWLS|`=N;~CZ{>h$zo+W^0c`xWPl*9%&?Xut2(uDB27@xI2Q zepL5C`(8WN|IIS3VG*p0&l7q5rwaV13ij4b(q8F=bWZxF^mA5^0Ha}ckey;rvTv{- zvtO_ruj0)-4xinF{AvEC%;in;w0u&2MgEPU+Av~J4W|re4bK^VYwR)}G@dhcnZ`^H zn!asznw!ks=6>^a=Fgg+Fu!Vk)BGPLr6u(x%SzOe&zF4LQfgUdx!H2RC1d$bskJmv zy0Y}f(r=djq|8>&?~^)=arnUR~Z*zODSu^2f_xD8E?#iwZ+U zYsGlQ$%@A+o~!tA#cwLBD#t6&Rz6#K#pbkyY#VI%+a9-l*S^VqyZyUWB~@KjYSsCw zpHy9OR5`jGBaTy!Z#(|kS>kMU4ml4wA9H@g6?J{t-Q*r|pK)j0ueyKfsq(}L;qRHPtnNnj345)O@by+qKoT-L;2mAFO?`_Lp@^ z-O{>|x)XIz)LpDM)NiZ*oVUt*o%fLUe(z)6XT5JW)HiHuINmVZ@a=|QG@2UQ;itH_ z@topR)+po3rK*;-^A-qCkfSaMDEGiB=n>zt!{PG5=qBUY1($ z+Jn}{fS{Kndfq5iurKI&{Femw5;!R{kX3{*O3O27>3^f=IW+zMq3319g#Aj-8>A{W zq34ZKJ-b`anYtfRN21&(4IrLPpLi1QYF&e8SM;r_n_H3N0bUJC20p{IS6>{ z5sOzaGC@&;q_kt|;2nDo>{oiay0Li)O{UR&KNhiQZwIEQ_IKttsAHsb^&a{^sPFRg z52#}jXPn8*fdAfMf`zAqSy(A6lh#OsfY3T=NLtUVtQ_v}N@hbeNfmQ2Cv!13Y$?^O zhSjn+0Rz6m24FoV5`|0Hptepb!>>OXB+T86R%^}vrTL>sN)tk!nU$)Y&+Y* zZeS@k%5G#gv76Z~Y>eH?##x$8ut}z}DYldCV!PQMwwK+;ZfE=0es+LOv)==Ky@MTM zhuIN!l-_pUF zX*>Yxe42fh{UMuWf5aYS53$d&huP=ZAG0s8N7xzm-`H99D0_^3k)30I!p^fVNoUxX z*;m-(>`&Pf?5ixpE`YlK8T%T0iapK#oIS(-f<4RrlFhNdV$ZSX*$eEi*^BJ!p#Lwi zZ?bQ(Ec+YwZT21ZUG_42g?*2`%D&Hj!2UbC$o`i7ko^c;;_sw)0+si4b3|YFgn3Vd z_e6P5jQ7NOPlER(c~39zN%Niw-ZRO2RNgbidv@}0HxKu4c#pyn9***GjECbqoZ#Uk z5BKtLnujNNc#?-z9-iXiojlUbBR%k4@<@b7qC67gkvNYecqGXqy*!fUkqI7|M;1dmSgsLG>LJi3#|x_PXJ$HF`o zfj^YTVmub-u>_ALd90Vm(mXc7W0O3l^4JuQ?d0)p9`E7tFpo!gJj&xS9*^VSIq`Us z$9s7^&Epe1KFQ-Mk5BRVPM+xIi5{K^^F)LvqC64fi8xOrcp}LYy*!cTi3y&VTU4^M`9GQyKlo{aHioF@}JndHe{o=o%P1W!)#q{@?1Jh_wicJtmI-W%q< z5#Af+y)oV!=e-Huo8-N{yf@8zCwT89?^Sv46z|>1)7?DX!_#4&j_`Dpr(--F=jjAb zCwaP;r_($=!PApGt@89vKGDr5diX?`Pek}cluyL?M4V3~_(YOV^zw-`pP1kilYBzu z6H|O*C!g%*lRbPg%qJs!GRh}od@{}_6MQnsCwuv1nomyf$w@w`^2sSaxs$8iTL3* zyfBOs!(asDHDHzAllExB@ ztA|q=qkkY{@(GZZ$t|hz z;kxw5Xv#l|XDhd+@Su)XuTRyPF@!Sa)g4cQ?5-Xk=*$>V^bh!zjN~6kXYQWBWEn$y zXT}s#XhnQ*GQ$QZ#@C2Nn6*l&!+$|Cw2Ui*C;e#xn%JXMM{vj}b(k$bT}E#4r`Kqm zOF|cn#=#6ruj$NKLKsg`GM3dF2n5LZ2SzicR2o646s68gX-Ij-AWhKMBqqv~tsYlS zjw_imthX~$7TUNib-~Qmj5cS=r~G$zW~`x&TT&ahYL#_9)H_6ddFX;J1lkik(5oCI7kt)<=PN4I>wPJ2#Wc#C*C zifydJ;@6D>ehVPG0857-TVI`#`k!Qs32;|nEylrZDJf(14=Cdp_o?!72J7a)z{&9o z<;IT8zK*&kY`zi=D>^zew$KGesXcUoQ(6_eAXDlHT`*AU1XL+?g)W#Vb%!pPDfNUd zlu%k7x?rKSCX^}Z_<(cQV(uFBsl(iq)?;o;y_lQQ2Fy)qBj%=5!Q7PkFgK-5n43~R z=BBhcq%0Ho(-Oj#%g2?~0O&X!KpJVLx!u>934}5&9hnwjUJyvO4!Ds=J%2jsS59tE zU0aD0?##3n;K|&XV0(tSx&`>!inqJ?QF};QZLH*QIOKFUh&uL(l2rr)2u z(9T>~RS0{)3i7SSfk5eGXQngM3Wx^IQdDmSOVZl?iQs>Sx0van_hqN<|_K;z?%Uuk?UYMmjgt>ux_!CVsrooP>lU;sAS#}a*^(;JAy0kGe1|k%L9ZF`L z?$OFEsXyb2tknG({?k|O=m6d95>N=thZ+5Af!M2orMN{>heoFO>T%VdkyocxTwcC9 zU5EVmC~g9pr74j-O3g$2d^!@mkVVeSFA1n(O`VyTKI7^AAF$3uRli?+WO z^l8~>*M$Ic0<#l^Y5;$sI$5X|t@B+MwdoCIx;yeu77e;IliN(>`o&@s0V`2ci zD-*!{eF7qcFL?lFfD3-0Y8Nn9TjMg^Mo_Z?V&%8^yD?|oe+^j(I&@c;`IB|NqJ#5| z>ML1JTh4>7pLXdZ_|ii3)fP6k0vmH_H|9x+E{CHl6T$^q`5yJFKnIx9k?BO6fly`% zlGOy8K|oJg3o@RE#u^X?+?dq>-eBlSNm_=?T4Wey)`gyAqGkvgQL~=fEXSh_)P^z} zsSRbWqc)Vep4#*xvx(YJW;3;+%rLc~%ob|Xhs+4Iq0CllLz!*VhBDi!O+PX_s10Rq zpf;3AQ5(vPQk$j7+(>OGa}%|p%+1t>GPi^>;R2Y)sE~=H`c{!kAU7_s2GvOv(xFVG z(0YOjqV=T6QEOG?sOeNF6D>5|Nd?h#m&j4m-6BU#_k=RBLesre5KV6rIcj>l$WhaM zp^i++R7P$dzLUszCn)d6XQb>17%{tl^e;+aKAu!SCD&wR|8%BeP*L!>V#~&iY3q%t zOtdc3Iyw%KYdffye$6K`+`k6$k*WR3(wzP^`lO-NGB~p4f{CrU(9AxyC6!tEsni9N cyavdL5`+vYLY% literal 0 HcmV?d00001 diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff b/libs/composer/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff new file mode 100644 index 0000000000000000000000000000000000000000..793176af47c13605a36275252d9ce11fda817f33 GIT binary patch literal 12404 zcmY*1BH0K$K@cJKf3|Jwh5k&sZ62LM2rzE#|B;M>TJA}XpfvV3cD-(2Dw zhHQ|^#wK<~j^A3+Hy8P?MF2<#;U+&_iN4#b-x~P;fD7_wVdV0kZ}Xc&|A((H0GNfX zr}?*b002;C0su0Wg*lh~mS#q#006cBw};^y93V=Tn3mt-w^sYj3BN%G83hhvY3J(k zt<8RO6951#G$RLqYwKY0?W1A)9&`9V%uIEO*%^6!AD8z1+eh>tK-d83_C|JQ0015S zx5pX)0QI{v2K91uaB&3y=)=DG#rJ8OTq3i6dSsax8XB4c0$H0Qr-M%JFB6XjECGs) zP!RyYe|<*(Mz{h3k^=(5gU0*=0`dVOtQ<_GtXWVIEVV4Gy#av0G323eB{~O29(A?h;2T}qOxfbLOyUzy`5`t(7G+5{}$ouOH zOgi-I3nML`1_yf$+fWWtf`X`T&>t2XCzKxm!1ILi{JyI1`TgF5e9tMUM^>^ok}KX) zdd*c7+kTzdM;#mP#;sI}scg#S>g`Fc3Ar;Wlaui%FcDn17oEpWM5`B$%xEFFzF4-y zSKcN|z;5j+IZLRa(Q!RVmCLsh=qa0OHhm5w>+O@Wl|>KN;d{xOzm-zGLpuV6bp3Xf z9WQ|h!f|VPm7V_f)v@#Z_Nj+;eCFDw*K=fwb&dK%RFJ6pFTzZO{fa{)5IVWQL5a@k#+fX31y>Ua=cf4lEkv0vPm@W7=*U~*SM(g07(2~c2$bhvoMK2UBkQzA{ zbjzp5Omajl3^|r?O73!0X^(wGm%fjwQN28tpkH+88bBSO2@$IN~hKpxxK8HT*tlpt&7vGy8bRnU)BkZ=gqzNZu(0L7H;j? z%V=f9ey2B3f?mC*f8V+5i;Mm`U|v{runCevRCRDWu+&54RnI{TF>-8s7FpVRPkgEG~VlMdSGX~3XgpT zJof6m{Zk1o+XPE{uj5DH1O{lo6|uG`=VvV_0c+P02~Oo4f{*A5p#~F?>mSgrz9O-N zKG}nH+0;p(K1mQ{Tpv-=06w4Zpx)q})pO|#_s{ozMqU9sd7`la*=)o#$=YVCaN_(_a#Lq-{oRUH_1J3yY z+$V&mw$BjpolQ_5)k24~eljGi{M0Nmmv;LTo^8X}IeAFFztktsba7$ol7Z8z4HZ*M zhcbP1RDt*!VtLmh4rD|naBYo$p28A*+T&)5V-?hB&?HCNL?xNc1O(=m*p*4VWyz%+ zlgE6K2zNL=Up6{=H2ACq{5+g@I+80WJ0MwqBo?1`nH2Lc6pvL;8e$bfuxJ)S(4%6a zmg!LyU<6s+jwVX(Dl%^B^>#00y&8xT-#eT@9uv48xKks_$QZgy%Miy`Li+k<+WNiO z-M_YsZQi;R9)uJ8nbiHc`STrrCmQMRA=WfOj~$e!@k6lZ1Qpqj$M)0hRK2QR?)p*Q zF!q+-vu*E0I*@!o^j=gym==cCInPeOFL6DBYW)@HsM~IVka>z> zE9*IAK)|_0fhQ12z@)UEp(0vvxx3-j1A3Xex=f1H8QUuf=_tvNkIHE$+4YCc8s(`} z{S+haF?o20#Jn;x`xV&NA^V~%Z^Zo1oi7Udg?$~kV&@Q4;7AA|k#i;}_)rO%CSCB| zSa6(N5uda+`rr#wTN~@3H=ilYNS<#oKl2ZcABU%6Z<=f*$lBxvoWWD);ZAb9-HXcz zXwLKRvEB2Io?h*pKTUvE{L%$y=|?l0w~8DaBvAV>#Q02ugnl7 zpvuE2tXY$ye(FPQlBSP60cvWA#?&y+%hl;lmS*7(Swbfb$#J@S$RCD4;*6?6NeucG zzWWmk+43MVgv9ol7*RS4ej0zC-LAE;BFOJZ`;g-9X>F@spxz)E$^H*Qiy$ z=+-FPbLszU(tALm%k4aLm3|+MOLtiS7VOWJOCL|ji9&*n9t0#-9|kB1b$xQd!Crv2 zLaS#B)W~2c&L`ZsaCsVK(8H;Cbw7P=3zye)%`cH+V4dK=5s0lXk0%Bf2T1{Tc$nA? zn!oUV@T?s~CfAS9vZK$~Boy%bFBla`M~l7} zRh)~PMDzR6={^1(DRSVdM&rrDN6(Rd$Sq->9%;pplv7wzI3{^uDLS0?dQxt$8tYF~ zOpInH)PWmu>%y=|;;{JI(mNyOWX$-sUi|1pwO{_Trs1^c3!-8=Xm_4KjgH_PySL^? zkM?##d#jWsp$T6jq-;d_)xWW&&b>Wt77ML3zuP@T#t#=U{e(0~KM4Yl`fB*la&A7h z0k2NFEv@7Q8r698`kLVq&0*!6>(o&cA578VR{|}t#X1Qq4-^Roj^5z0$B^3Ufj<7Z zKfRAs!WT{JAu?39e79iK5hXvylokK5U*lI!1XjR!K&Wuiy#rvpyn?K_iD^)P(#5Z) zfeGC1Z}!sb4uquPW<#uN2eQSPh!?3v$88W+hY;Z#Z}Zd(-htO197%{TB4kATi~;CP z&pvR;$gPpa)nteF&a)|~LNnk`ikOVZ5rnkSojVD5C8>KBUi%$RaMGQMB6jBS0#H(B ztJq!J6g#u!P3PJb#2x&t7f(t}eRV>S)64MfoMTw4q61};LU@g7Imhg*{I=( z+$_JXQMye!nIW$$ae7fYnxvuz*#-TfO@Xxtp)hE@3^uF$%f1LL10b~A+t>Sw zpwDb^nOGC?^IH0|=m1?pkSVD#4uK9KEFjpGB(463jsr#5%H#OYe+;&*y0-<>SAUs| z433>Q7xy>|wm3TD63V+ebd>p?kBw1#3?4}px*P}>>8g>b{Lru719!&ADBZ`W*jJXI zDIn=rYhZk}?;Nl|*a;>Bo$rY_2dP<05bCe4SS}G8Y)l(2?$dE}y4lV{YvPHc^Ql}^ zqdCbJc!gh6SqPWV{*|nNQJ%^uGGY`5kex61F>z(R~_6ego57uTa+%|L*b;WfiZElU7m)42Oav?YQ$ghxhr4`#uU0zQN*0Z@|&*P}9ugAm!V#C*DyB zm>ET%QC=EyqNn*-SRv79iZrwb02U)zbOW+>4%G8>_r2Hal#ht=-u;JUNsz(TbVqpa znDr7Ma;@KEmbi_y+EE#YpK6D+r}Ve`Ip2 zCeL}M?L7MJM%%oHsunYB4cY%3x2P~3VFiIc_!YnwKdie+S-1fXQ<9jL30aW^{ysD<2cx}b#s+W z!3YNxNR}Bs-eJ7aNyT_rV^p#9T^}3ba$Y%qiXr~SODzkN+9Hqe$rNsk3YRc&l`~cZ zL0>LZXr+>6mEBg-(3@bH93jD|%ck%gcSWH*av%o^v{tQ&E)hZ~KS_le51<@h=n_R2 zP+n7-n-_(-Ht9g-lnygbCYKwdsZGhnnaw~hT&Mq zY8$Qo)CvPlM5*vAyf}fgo|vYKP4>;=EYOOZRZS2QJVnRq4@};(qe$>Wfmt}r6;?<6>&vjS$J3g0(Ze;AG z^6)Cc{Y-qZ-_Gz)HyvBB%VvT{Q*Oi#_ArVdvQn2kbZX&(IIgg_RM#rV{n04%9Aeg0 zO?l3V7gHGh?EIPR``!zIpz6)T3bUD@G+K07d~3uD;qEkrwQG&YgnK!1*f)=E>b;Gf zcwsThP4rr&sk7o@R*%J+b}&vD=nEEtH{TVuJv{3ko06a0jdZAN#0VtFkal>{6|D&A zKNhl2BG)l(hEU5h*#AClccEq2D}J3s95R`z$UD%C{X^+KF?%X#%Ux>Vr* zf`NC}os(phAmO+o>C7PuS^+}N^NdXC zJVSikJWA1w%q*cIT+X)#y8bC_qL#)V>vI{&$GqT@H{7veU**rpYwnU={G2|Oznx@B(E6P78Z9M_cYNLE zp0~7FJ-#4mD45JrZ(`s$X|0e>u;5r}cOY&w>}V76>$t9))E3fd+DQQ+%VSsmB!fs6 zlhKV>^{k2EK~-xd0fH~sITsdUB}i*7d=JXWAk%B6?n7?-kv-4GfIgJ>J2zD!(EhKIlr% zA3J~eT}IGvX@9PASQ;BaW>1{6(?|`3_h5zkX%D%H9tjhF_Ya0XpEW3WH*(xsQw)3gE*5kIieWy#j3rmLL(H=MSJ%<(r8_`I<}#n0Z1%wC^sK zPv(6$bJ@1}jKL>B8qH-j@C>uv#gw{9nF0d~8i8t=d|5YR?_A@&h}X2-J1x&8?y!AXEm&~A4Az|M(OC%02Augl@EW>j5!@yDTsurgsyU&4*} zftmK!2URM@%Y>lxjgs+PfmeZboSAvT0w$N9qC$PER73cPzaC-@B=Ih^0U>q)$$p=; zF~#t3(o0r2M_XdrdIPxnURhX5SR$GPCr5^e0()~xik<;e$8a9$=>w?N>MmunBrl2O+Tzl9}do4~qOg+D0<|K4LMGG647RlDJ-d(%Yv~o&|o*YI5 z3j<;=nnfiR9w!rqLJnIW6-Lz+5rTd-X`HV3XSOsu5YIp^Ahc49jeI;dQEkhr%SM-M z9ycCxozK2|2#73l>15!iMPw(x(p(||R=EE3`=6(FbTyu~s1zNpt=(gA4Mng6ipcxF z)P!?>AL(84xgGq3^XluqI6>+*UzxlREC~0T@+!T0Zk^qb<8$%)PSulm=;#b_Ft}>EOl}wiMQupG7Us3p<$J3B9E_nJjZM_P9aI zhCfG7hqUG?P-S3xb6jWN#rq^o~W6!H#$=dCeztbmq`6wGaVEp#1n-ez@{|TY5$7UD<)Wm60v()Qo878fSO6D(HE*Xcf0zOZ*hakU)rwM*r zBk&xgpCf@`3j|qte~ZwHgq6wHPR0#eK(bb_@)-XDZIpacwlPRox<@pY z1`1aw2DqXOpKefQCXxb9Yy1%Fpc0 zfoAcK#=WS8*~UVSxB*)nnH!owf5#SEgs25T9O-%nN2l~RbawblWEiqi%Jc!ozwsX7Z7_U1aKxUuCO8wvv>vSoE&W~N_Xx%J zls;aNtSFJ9g8p~2q@`V#`i?dTg25va7m!5mBPI?Dy!+qlJOwN$VJIpe^xf>l+m}Wt z+-&|K-Z^cd6_bA_SVCD@$1CidlaSdjF40pg6e zn8(-p0593>8nl)|jS>-rJJdG8-u8GW*J@_DD?4q!f)SvhwsLq0Pc;UG#FB$Pl~(={ zpJN~{;aa(2)vTLrlHQ=qmK7MojYMk&6_|{F;8|J!O*=Or*^Il-=HEPH&|9WG+YQ57 z?SAH(M`xX!`|!s$Zf1_X0~2*k+sS4LDbe{LNhX067Y_Sted{h>s`-L#Mau%-HoX8$ zKEPaENjZJJ>kv(u_eH8tx*oEtVpXMf-D$p7;x!w6QtqA1%MEM?-nB(45(;~~yz!gl z-2>B0@@@XPZfNyXKlARn1;)%f>5kXdo7&sXU_;_7ETmz9l@|K_W68v6=m|fyt=?5m zV9#9JeEiL<_J+?5DmX$cO79rz^qytEXlu^i6D()f%$9@;t{O$8J6*ttZwIY}0Iy#` zf}ck`f3$a3pKs%clfpl1wq}cQRy@q(C^|@uUf8LnplTA1CEAxlHL*Es;bmmbl%+S5 zRO;E}05<%)@XFy1=8RIF1_!6i1qi8kp1NS78c=F3*6vuccRsv{HcnLV)!H`q!|JpE+k-U)>Av6iFg0#!Rc4v zcn5vPbeDIG4nrxzKt%Iy#|!~5+xzC~%Ko}HeBh5ntbwd@#t@b;1!yvDFJtIQcVEEh zt$x%~enqrnd;>Orvnih5i@k{d^skEX92%XyazgEoxBcX@?Ih&sM>jz`kmF>({Y__AJ#EY~<0JJ6%bT9i}-^ zbr0HkM`Am08YFM1Bawj{b53&4af-RZbStp)AMXUqkGJV&S=|rw_8h}CRw-nYtW^A_ zHvApsOu8yYydp~E6!;E-mjte(0B2^7-t|>GzQ|Shb5(Da41_R_zD%wIAC0XReR|9vyt1^J47SA*}J#UkC9m%rs}>f(F$XRbS(<-BhwY zn*xsdd~r@iFo06iiDr-2A4qS@=A^l+ck47=$5a-3Fi~a%27xC|Y{mlv|=AkrnM^kq-n1dt)gyqL%X`})To1| z7jGCa*DSn1yo`{Px^xQsZBmBnI=|?4CXKzq&76vK0BW+J@v;^x4wv4SE*6Zw*}B*} zK2Itmx$lR?4Y}S^`J^gWiQuJBbX3xwB%AXmY;p-W<|A|T-Z2W?ic7b%!eFI05b4Ee z!Cu?+%dCC%nb#$s24>N(eqWO^Z3D?rzd-QrNZep#Yd9V=pX;J^&Yr>*@}3z!Lcb>Had&qZS4iHx=r>5s|~WY#YxWFLQqF#KsE-4oBQ;qiJy$5a> zqb$Ou3{nzeBbv|VPZZCRU|lCs{GRkHW=7fsQnC#1{?X8HJ+fFKV=3Vra0fGj`UeTz z)SM%Bd%2+PyIowPPc#!~v_PVmDH+ClfSrpdHrKb1G%+Q`6r|gF>A-W}y5G)xzS;Vt z&+m4oe(xj{@_fDgxb?ijl{-wL`*|S|%|X}*b|-SMq%^uHk_UV*v+Jov*CoI~jAk)! zj%>g3Z=Q$#aZg0;Vaq1vg@Gc>pDqnPxgse3kI?I=zmU?N6y(0w;gh)qL?+`oelkGR zPVm>u99)O=?w?dl*4(xu)=4bUQ~oxZi0rH3yfnmhUDTn2DfEj%9Iz*W9U*qu*4bDKTm zG4t|oFDdl7`A!$2qeyrzchzKQ;eFA=Zk)K4wx+Jb#g}EcBRnjBLO>eIO3dmaGvA#$ zPh8YxShUaMKS@SC4_7aH(K101VY{Awp)#m66)uAcxqKz!78dzvYV$)_M~ymOrxN!h^@LC7{I@ z0-=e1D;5hUN5qTZrJr;T2p;7=_#JCjZK$qQuzI&kTpPrBdP==F4xvJ z7|y0hbt#;lWJB;^TM#RT{Zk80t^*8#jIo1XvH_c+DwgfK9{~WQ`ADQ% z@{H0wZ!F@u7`=guVS7!%xc-g93(nY4(Ij|GfCB{HmJI)#Q{rPB_p;VyQ>r$ODSM4` z;XKF^*kTjCl-5?G`jb429PR!3ol$vl^{ zh6FMD`cy2+{xQw)?b**)ABGqKq2bH0``3{)sz6mH zmI42*ui7l`fh}t^q-ab{TirY{A`RGl)Y~0GVMeN8DWkQeYkt}hWSv{D; zpD;})s5$D)rQ&vLdPQ7~<1vS(XIeqSVdazh=#5Kan7w#^$f&~160*JER%^RCtWyeY zR-D27KWknKg>I%sn20tia{YfRGhqCHdw1JWU&`gO-PQMcLa`YG$u*gV|2P+vt?HUc zgDT#|I@-bYCtYX@LXJ4}=3&~NMMH4-6Tz1#W-b;nkh^(sH`+#`3#cdn;ZHOiQn0 z$%JO`j`5I7gW=ik$ZmyhMiiSxX%3ZEx-!FoCZw=hdISVOO2KQo%p*%+zA&+1DW-w) z441WFU{Qs++G0=I2PGhN$slS!5gxDS zDnyyIkteXbUG6w1Oo$4qpvVZk+9P2(D>GyJrp4&O;up}#Gqy+mmVNY6%iaw3bjkpY z0+MUdn1Zp9K-EDm&INzm?<*mhXXloT3v$E8$s%gCdcG9-G!1X{+`s%V!}SN z?O;SafjvXG37UU)%4(S8ubTYT7lsaH4DGP>%71iM$p)AJ!Os?(CQXIXgcl>>&}|vT zMC|$<+2H5NSJwVUW?K9!%T;>=!%^do`-ukDeAF(0UY@JEH&^t z#nh6_FSDZXA07!B2nv_SQzr0yxnB3EdG#0tUOvk%RUz`^*O4vSXT&y$ek|(9m_S%q z9ndu-s*s+RyD4~ric{?70pw8XFKJIaEoG$+4v zd}0-k&G;v-`4gMoAiEjaO}hv4&#W!X^xCA!QnGlz_B?r-z zX1S*5R=4L>cZzw-jjoETsTpZVci#6jhg~GQ9seA_d3=tIdCzR_{V{o`tCiqMVtY*6 zN1lGph-imGZh3AtdI?InW*U?QuCB?w(A_lNS>_|Rp=irJA|36<+@tuam=)B`e5s4t zA17peEicz-jrfbzS3O+VIpl%Gp;9Dk5HseS6~ms;x#wi4OVs_WL~4{p?Q-E)n6)~y z2~sKzZ`BsRr-l~Fw{>35>#dG-2Tc;6XCc4Nfr$Ra%#E4OC$WwqxIW$<9}A^fPyO*B zG>CBb)l+e={!#0-dyweL@&$r7`|fyFZ*VM$B$|K5(cPgIOH#gh$Z6^;QLG~DbI$8S) zTn}yMcp&6UznC?}47~fy!nqg-PBNIygH@rHtJ!zdpjenOpaosWeFDbjAwaf&LLg2< zO>%Y^pFPrqSK)DS^*9gWvUW&p(!(s~YG( zk7p<${KJrT8XO2F)q9@#z!oc}4>!ZzM| zzMWv-^R)d{J#ypejXZH{2MZP{>k98V8x9hMN9BC7+1-DJdtDegnW2l^|5v(=ZW!?? zXne47^S{iXsi7fYFOW>P+T&Md4gBVJC=ey|zhEKh$yY;qa_=5Kyd04-0977l|NpXq z-+ql3!OXs(wh$2Lv}9~-9Hl-{fZR{8WX4V)y0*;1nKYnn<%;cN%6+K}L=O(bDI2YWV%P|e?!$p~9$gbp6JUS7 zigW@`vN4tV+aZ#tdI10~BpSN!SRsJsf14D*91sY|0dxbl0iPgjAQB*!AQ2$tAmbpL zAUB}kpqQXEpnRYTppKxmpwpo5V7OrBU}0bdU`ODv;7s7w;6C8x;O*ec5XcY`5D^gF z-)YC6khPF|P;^i{P*PADP^M6oP{U9sQ18%)&?L}o&}z^=(2dZ0FeET)Fs?8qFr%=r zuw<|Tuv)Oru-mW?a8z&-aE@>}aO3d!@Lcdp@UHMV@S_OW2uuhP2(}1u2!jZ#2p5Rp zi1>&?h#rUqh@FVbh&M>ENHjY%vQ`%%o8j~EE+5Yte;r5 zSgY8C*euvW*eclO*k0H%*ag_N*u&VnI7~PKILn75 zRgcZt_h7m)GH3TmjTgQTg)mvd-)?cbQPC*>>{A zW~HFp03DTedQto?dg&&+SEx_Gy|3%+7rTd$`&D*_W}I zTbiOdI^1rfInnM@iiL8cXu3@O(zZd7X2~)C_-1iFlTKbRGj)Q{C%Fs`^Dt~61uqyerhQm%v?856_F;6knp6Rwnft{gP3_(ZOBRj%X( zu54DW#0IWR7p~NMu3Wg*IBmVbA-#TEhC>#HW7z5gn(8Cm>O-FDV@Uf03i~5W`$G=< zW5lZihN~mOt3!dSW00d8BBWcU*c+rwTE-%89)~j_XUeNhYGamNnTk-a%rR!ZMzS%Xu|~l= z8e>N3oShZl;H)q`0ntX4dQh(ye|}i5mxy;T?2PuRusy2lNqK!3VjR&u0s zjb3u*T^^rCPN^%6#85dGMh&W3fsD!+jd-zU8I8P|5>AEOqWf92SH*VO^I6cpQh~CW zv&et?zUn)RNN1Gg+2zVjEJMe~y*{b<||7 ywW~@Wt<|f_5Ufp_!b25;<_y`n5cEZ7k=e-*v_93=5WMN&tpM*Mo6tJ&y1+Yn zgo9iR9c$ps0<0=RQh=ln;y#E2crSpp8h*V3&QfrYd++-F4d}fFgHuK@z*%x_KOE%4 ztO#lvY6=E5Bt=L{kVLR8fSEpgu~R&>4!pJd1I&}){RKJz*1)J79Pj=C^Xbd^6-p?OFxt+=7P6SKrDfi%b->Xze97ZRd7WF zhuVAk* z>{bvL4X2X`x)prI-n#?5TZi$^BB9wL=!ifU&E#t;rCOrR-$MZ;D$8kUre2P)c$7l&rm2x>+1|4jf(!CO8OHCq5*2w-tpg3pJlc2-WOz-oD+n}&yS zLW4&#Xl7Sc!D$899vQDxl>@}_Of_#9@_rzm58D9~ITQFBjO~US^YB4nj8__HV8K+C zP%VPa5*!ZjPzIRCQ%QcHEJ3d8Rcwz4r)tjTh^cx4L2Ci1W*I@OlKy3OFGL ztHfYcHwb`TJq1;%qgHo`XSy?J)&pO6B);Z1BuqDH%Gi^ZJLON?pKkD;l!^H8nE;-S zoP;J5aAPLcP$?Oq@HHGId7vqnQ{dPPqiYh7m~FfJ{Wgxjwue zIti^&aV(gEnZSGnhXDM;87~LkgWyeoF)QJBMHAS2(aU;809GSY!dwBsY&iIAIHFe! zm)ZpMoaBfQt}GZetF}Z6NM4Bfr${3>?F4n2k$9@a1C>Si7>lW;Sv1quwAJ%ul z4GZBIYj6vAD~AC!2Re4am;yXlbP}dtuQ;*gb!e2V(j`^x)+YA5U}6aWD%2<7y^2c( zLyv%z=(c{o0R7bDU7W)#zD9*6^YArd9CMP?x9 zUN|y@pqvpWerhw;u)7<+ih@GH48AijrA7eDd*Ji!Q0yj|z*4wcN{FVC?0^}P?ZCWl zr-WIS%KPXL?#q&4=h)qE>|Tq87p3~F`XEgFIk-`$b81YCdjej=;DE9Bdwh5^Op{yPPED%c8B3)cco;fnXMkA` zmq?)28dc1e8kS-bsGIww{J+4l>%(Ot5UteE2K-RToFBu7cLd#@1Y`#bWZM})s3f7y z25(LSm~Qz$fy27sDJ!44IUQm<38CW60Fxb?A$%zmujRxmE#hRbZ0zMw@UChA;wp_w zskX5PP8GGP1M|K5)x5r;N^Z59RgvR212|3=sY3$)%n<%lkeJE{cS5vY& z0le4?r(4fVtM`oETYOH#c9G+uCdFT!ij~ayPJm4k{0dHu;GeFI7Rhl)w&5jwcuVi? zkvdM>5?#Q&+ykco{J{ZoTPnq4BUmOIrQNzY5L?bvLr8kzjlW9yA`(0yg1;A9%XuS^ zUQ}9QRI{W`Xi|~g;-&zO&DhIa=WK`LVI;V())J+-+J{;!K%1MytXm~$A%!*o1M<#!5nhs;7miu%Bn~To&w}t%f9x(g;sc7ge z8H0nWH4XNngsj%A`QTYv-I~V9QF&M;fjE>wFYD=AKYXwlE)mr_DS|DnfH=ospRs)n z8m0`G$?eX#p|N&~1E`jv(hEPfJHUqpNDp`+%xqOJHLPr`CH^!lmV`Jlf>&AvyW`DR z!A>8(j1QOQO?*^Ti)ynGj9o?PI==10c_PCd&L-(+;{Mpru;Q;Q3u?3h{%&0b1c7~2irt>%n;10S7cvot#ES_6>)$)+^ zz?>CuMg+SWt7*T!@~s6B_rN#ztln(+Mkqh3wBy$CT8*AYVYg56=1qs-!IB6)$ z6Ae%I!Zjg$kZ*FYt)Qj8K z>4YD&Y@95Eb9>;ww)qycNinA`i_5UH6TT&-%FM97UDOu68oTn&Sd{`K{h+5xH>R#D zzaG5R^2N(=vlNFFDaG6MOl-ZPpi46RZ}!C_O(>V-pdzKrJGKIE`cU=?`EnZ^&;=_> zXwOKsEV6VsmBwOJQJHN+9@|0?d0n;Jxl6*3RInwn=axmnlans5}@xBDN^@6R2 zi?CxUeBaLNiG6u-UUk zetRcn1x)OOha#wz(!SG>1*PiWWN68Kw+|p&*-AbI@M<1@IuFJH%mQ!g{sOTMyftF% z1sJQ~`~V(}V0%g4A#t894$Au_Ls~kQa#Za1a!oCgdu3nqTnM-HOHUZ!PVgSy-){&F zG#FeChveX_5PlUvzk+x35D%ELJ}IV;tP|mTQk3qcv^gspUJGGy7hE|Tj$Pnv%w~O* z?`+xUiCxYh+PpRNv88Z&0N>FtS;1o(N&#%t@Vp%Bw5e)mYxsF5T(}*MSqTR>rhr%W b0mT0S5)@H#d7`>U00000NkvXXu0mjfEz&h@ literal 0 HcmV?d00001 diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Client.php b/libs/composer/vendor/sabre/dav/lib/DAV/Client.php new file mode 100644 index 000000000000..175ad1bc4593 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Client.php @@ -0,0 +1,439 @@ +xml->elementMap. + * It's deprecated as of version 3.0.0, and should no longer be used. + * + * @deprecated + * @var array + */ + public $propertyMap = []; + + /** + * Base URI + * + * This URI will be used to resolve relative urls. + * + * @var string + */ + protected $baseUri; + + /** + * Basic authentication + */ + const AUTH_BASIC = 1; + + /** + * Digest authentication + */ + const AUTH_DIGEST = 2; + + /** + * NTLM authentication + */ + const AUTH_NTLM = 4; + + /** + * Identity encoding, which basically does not nothing. + */ + const ENCODING_IDENTITY = 1; + + /** + * Deflate encoding + */ + const ENCODING_DEFLATE = 2; + + /** + * Gzip encoding + */ + const ENCODING_GZIP = 4; + + /** + * Sends all encoding headers. + */ + const ENCODING_ALL = 7; + + /** + * Content-encoding + * + * @var int + */ + protected $encoding = self::ENCODING_IDENTITY; + + /** + * Constructor + * + * Settings are provided through the 'settings' argument. The following + * settings are supported: + * + * * baseUri + * * userName (optional) + * * password (optional) + * * proxy (optional) + * * authType (optional) + * * encoding (optional) + * + * authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST + * and self::AUTH_NTLM. If you know which authentication method will be + * used, it's recommended to set it, as it will save a great deal of + * requests to 'discover' this information. + * + * Encoding is a bitmap with one of the ENCODING constants. + * + * @param array $settings + */ + function __construct(array $settings) { + + if (!isset($settings['baseUri'])) { + throw new \InvalidArgumentException('A baseUri must be provided'); + } + + parent::__construct(); + + $this->baseUri = $settings['baseUri']; + + if (isset($settings['proxy'])) { + $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']); + } + + if (isset($settings['userName'])) { + $userName = $settings['userName']; + $password = isset($settings['password']) ? $settings['password'] : ''; + + if (isset($settings['authType'])) { + $curlType = 0; + if ($settings['authType'] & self::AUTH_BASIC) { + $curlType |= CURLAUTH_BASIC; + } + if ($settings['authType'] & self::AUTH_DIGEST) { + $curlType |= CURLAUTH_DIGEST; + } + if ($settings['authType'] & self::AUTH_NTLM) { + $curlType |= CURLAUTH_NTLM; + } + } else { + $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST; + } + + $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType); + $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password); + + } + + if (isset($settings['encoding'])) { + $encoding = $settings['encoding']; + + $encodings = []; + if ($encoding & self::ENCODING_IDENTITY) { + $encodings[] = 'identity'; + } + if ($encoding & self::ENCODING_DEFLATE) { + $encodings[] = 'deflate'; + } + if ($encoding & self::ENCODING_GZIP) { + $encodings[] = 'gzip'; + } + $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings)); + } + + $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)'); + + $this->xml = new Xml\Service(); + // BC + $this->propertyMap = & $this->xml->elementMap; + + } + + /** + * Does a PROPFIND request + * + * The list of requested properties must be specified as an array, in clark + * notation. + * + * The returned array will contain a list of filenames as keys, and + * properties as values. + * + * The properties array will contain the list of properties. Only properties + * that are actually returned from the server (without error) will be + * returned, anything else is discarded. + * + * Depth should be either 0 or 1. A depth of 1 will cause a request to be + * made to the server to also return all child resources. + * + * @param string $url + * @param array $properties + * @param int $depth + * @return array + */ + function propFind($url, array $properties, $depth = 0) { + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $root = $dom->createElementNS('DAV:', 'd:propfind'); + $prop = $dom->createElement('d:prop'); + + foreach ($properties as $property) { + + list( + $namespace, + $elementName + ) = \Sabre\Xml\Service::parseClarkNotation($property); + + if ($namespace === 'DAV:') { + $element = $dom->createElement('d:' . $elementName); + } else { + $element = $dom->createElementNS($namespace, 'x:' . $elementName); + } + + $prop->appendChild($element); + } + + $dom->appendChild($root)->appendChild($prop); + $body = $dom->saveXML(); + + $url = $this->getAbsoluteUrl($url); + + $request = new HTTP\Request('PROPFIND', $url, [ + 'Depth' => $depth, + 'Content-Type' => 'application/xml' + ], $body); + + $response = $this->send($request); + + if ((int)$response->getStatus() >= 400) { + throw new HTTP\ClientHttpException($response); + } + + $result = $this->parseMultiStatus($response->getBodyAsString()); + + // If depth was 0, we only return the top item + if ($depth === 0) { + reset($result); + $result = current($result); + return isset($result[200]) ? $result[200] : []; + } + + $newResult = []; + foreach ($result as $href => $statusList) { + + $newResult[$href] = isset($statusList[200]) ? $statusList[200] : []; + + } + + return $newResult; + + } + + /** + * Updates a list of properties on the server + * + * The list of properties must have clark-notation properties for the keys, + * and the actual (string) value for the value. If the value is null, an + * attempt is made to delete the property. + * + * @param string $url + * @param array $properties + * @return bool + */ + function propPatch($url, array $properties) { + + $propPatch = new Xml\Request\PropPatch(); + $propPatch->properties = $properties; + $xml = $this->xml->write( + '{DAV:}propertyupdate', + $propPatch + ); + + $url = $this->getAbsoluteUrl($url); + $request = new HTTP\Request('PROPPATCH', $url, [ + 'Content-Type' => 'application/xml', + ], $xml); + $response = $this->send($request); + + if ($response->getStatus() >= 400) { + throw new HTTP\ClientHttpException($response); + } + + if ($response->getStatus() === 207) { + // If it's a 207, the request could still have failed, but the + // information is hidden in the response body. + $result = $this->parseMultiStatus($response->getBodyAsString()); + + $errorProperties = []; + foreach ($result as $href => $statusList) { + foreach ($statusList as $status => $properties) { + + if ($status >= 400) { + foreach ($properties as $propName => $propValue) { + $errorProperties[] = $propName . ' (' . $status . ')'; + } + } + + } + } + if ($errorProperties) { + + throw new HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties)); + } + } + return true; + + } + + /** + * Performs an HTTP options request + * + * This method returns all the features from the 'DAV:' header as an array. + * If there was no DAV header, or no contents this method will return an + * empty array. + * + * @return array + */ + function options() { + + $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl('')); + $response = $this->send($request); + + $dav = $response->getHeader('Dav'); + if (!$dav) { + return []; + } + + $features = explode(',', $dav); + foreach ($features as &$v) { + $v = trim($v); + } + return $features; + + } + + /** + * Performs an actual HTTP request, and returns the result. + * + * If the specified url is relative, it will be expanded based on the base + * url. + * + * The returned array contains 3 keys: + * * body - the response body + * * httpCode - a HTTP code (200, 404, etc) + * * headers - a list of response http headers. The header names have + * been lowercased. + * + * For large uploads, it's highly recommended to specify body as a stream + * resource. You can easily do this by simply passing the result of + * fopen(..., 'r'). + * + * This method will throw an exception if an HTTP error was received. Any + * HTTP status code above 399 is considered an error. + * + * Note that it is no longer recommended to use this method, use the send() + * method instead. + * + * @param string $method + * @param string $url + * @param string|resource|null $body + * @param array $headers + * @throws ClientException, in case a curl error occurred. + * @return array + */ + function request($method, $url = '', $body = null, array $headers = []) { + + $url = $this->getAbsoluteUrl($url); + + $response = $this->send(new HTTP\Request($method, $url, $headers, $body)); + return [ + 'body' => $response->getBodyAsString(), + 'statusCode' => (int)$response->getStatus(), + 'headers' => array_change_key_case($response->getHeaders()), + ]; + + } + + /** + * Returns the full url based on the given url (which may be relative). All + * urls are expanded based on the base url as given by the server. + * + * @param string $url + * @return string + */ + function getAbsoluteUrl($url) { + + return Uri\resolve( + $this->baseUri, + $url + ); + + } + + /** + * Parses a WebDAV multistatus response body + * + * This method returns an array with the following structure + * + * [ + * 'url/to/resource' => [ + * '200' => [ + * '{DAV:}property1' => 'value1', + * '{DAV:}property2' => 'value2', + * ], + * '404' => [ + * '{DAV:}property1' => null, + * '{DAV:}property2' => null, + * ], + * ], + * 'url/to/resource2' => [ + * .. etc .. + * ] + * ] + * + * + * @param string $body xml body + * @return array + */ + function parseMultiStatus($body) { + + $multistatus = $this->xml->expect('{DAV:}multistatus', $body); + + $result = []; + + foreach ($multistatus->getResponses() as $response) { + + $result[$response->getHref()] = $response->getResponseProperties(); + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Collection.php b/libs/composer/vendor/sabre/dav/lib/DAV/Collection.php new file mode 100644 index 000000000000..35c90b5afaeb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Collection.php @@ -0,0 +1,109 @@ +getChildren() as $child) { + + if ($child->getName() === $name) return $child; + + } + throw new Exception\NotFound('File not found: ' . $name); + + } + + /** + * Checks is a child-node exists. + * + * It is generally a good idea to try and override this. Usually it can be optimized. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + + $this->getChild($name); + return true; + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After successful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + */ + function createFile($name, $data = null) { + + throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Exception\Forbidden + * @return void + */ + function createDirectory($name) { + + throw new Exception\Forbidden('Permission denied to create directory'); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/CorePlugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/CorePlugin.php new file mode 100644 index 000000000000..676cdd04a294 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/CorePlugin.php @@ -0,0 +1,959 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet']); + $server->on('method:OPTIONS', [$this, 'httpOptions']); + $server->on('method:HEAD', [$this, 'httpHead']); + $server->on('method:DELETE', [$this, 'httpDelete']); + $server->on('method:PROPFIND', [$this, 'httpPropFind']); + $server->on('method:PROPPATCH', [$this, 'httpPropPatch']); + $server->on('method:PUT', [$this, 'httpPut']); + $server->on('method:MKCOL', [$this, 'httpMkcol']); + $server->on('method:MOVE', [$this, 'httpMove']); + $server->on('method:COPY', [$this, 'httpCopy']); + $server->on('method:REPORT', [$this, 'httpReport']); + + $server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90); + $server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200); + $server->on('propFind', [$this, 'propFind']); + $server->on('propFind', [$this, 'propFindNode'], 120); + $server->on('propFind', [$this, 'propFindLate'], 200); + + $server->on('exception', [$this, 'exception']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'core'; + + } + + /** + * This is the default implementation for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof IFile) return; + + $body = $node->get(); + + // Converting string into stream, if needed. + if (is_string($body)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $body); + rewind($stream); + $body = $stream; + } + + /* + * TODO: getetag, getlastmodified, getsize should also be used using + * this method + */ + $httpHeaders = $this->server->getHTTPHeaders($path); + + /* ContentType needs to get a default, because many webservers will otherwise + * default to text/html, and we don't want this for security reasons. + */ + if (!isset($httpHeaders['Content-Type'])) { + $httpHeaders['Content-Type'] = 'application/octet-stream'; + } + + + if (isset($httpHeaders['Content-Length'])) { + + $nodeSize = $httpHeaders['Content-Length']; + + // Need to unset Content-Length, because we'll handle that during figuring out the range + unset($httpHeaders['Content-Length']); + + } else { + $nodeSize = null; + } + + $response->addHeaders($httpHeaders); + + $range = $this->server->getHTTPRange(); + $ifRange = $request->getHeader('If-Range'); + $ignoreRangeHeader = false; + + // If ifRange is set, and range is specified, we first need to check + // the precondition. + if ($nodeSize && $range && $ifRange) { + + // if IfRange is parsable as a date we'll treat it as a DateTime + // otherwise, we must treat it as an etag. + try { + $ifRangeDate = new \DateTime($ifRange); + + // It's a date. We must check if the entity is modified since + // the specified date. + if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true; + else { + $modified = new \DateTime($httpHeaders['Last-Modified']); + if ($modified > $ifRangeDate) $ignoreRangeHeader = true; + } + + } catch (\Exception $e) { + + // It's an entity. We can do a simple comparison. + if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; + elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true; + } + } + + // We're only going to support HTTP ranges if the backend provided a filesize + if (!$ignoreRangeHeader && $nodeSize && $range) { + + // Determining the exact byte offsets + if (!is_null($range[0])) { + + $start = $range[0]; + $end = $range[1] ? $range[1] : $nodeSize - 1; + if ($start >= $nodeSize) + throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); + + if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); + if ($end >= $nodeSize) $end = $nodeSize - 1; + + } else { + + $start = $nodeSize - $range[1]; + $end = $nodeSize - 1; + + if ($start < 0) $start = 0; + + } + + // Streams may advertise themselves as seekable, but still not + // actually allow fseek. We'll manually go forward in the stream + // if fseek failed. + if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) { + $consumeBlock = 8192; + for ($consumed = 0; $start - $consumed > 0;){ + if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')'); + $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock))); + } + } + + $response->setHeader('Content-Length', $end - $start + 1); + $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize); + $response->setStatus(206); + $response->setBody($body); + + } else { + + if ($nodeSize) $response->setHeader('Content-Length', $nodeSize); + $response->setStatus(200); + $response->setBody($body); + + } + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP OPTIONS + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpOptions(RequestInterface $request, ResponseInterface $response) { + + $methods = $this->server->getAllowedMethods($request->getPath()); + + $response->setHeader('Allow', strtoupper(implode(', ', $methods))); + $features = ['1', '3', 'extended-mkcol']; + + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + + $response->setHeader('DAV', implode(', ', $features)); + $response->setHeader('MS-Author-Via', 'DAV'); + $response->setHeader('Accept-Ranges', 'bytes'); + $response->setHeader('Content-Length', '0'); + $response->setStatus(200); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP HEAD + * + * This method is normally used to take a peak at a url, and only get the + * HTTP response headers, without the body. This is used by clients to + * determine if a remote file was changed, so they can use a local cached + * version, instead of downloading it again + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpHead(RequestInterface $request, ResponseInterface $response) { + + // This is implemented by changing the HEAD request to a GET request, + // and dropping the response body. + $subRequest = clone $request; + $subRequest->setMethod('GET'); + + try { + $this->server->invokeMethod($subRequest, $response, false); + $response->setBody(''); + } catch (Exception\NotImplemented $e) { + // Some clients may do HEAD requests on collections, however, GET + // requests and HEAD requests _may_ not be defined on a collection, + // which would trigger a 501. + // This breaks some clients though, so we're transforming these + // 501s into 200s. + $response->setStatus(200); + $response->setBody(''); + $response->setHeader('Content-Type', 'text/plain'); + $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode()); + } + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP Delete + * + * The HTTP delete method, deletes a given uri + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpDelete(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + if (!$this->server->emit('beforeUnbind', [$path])) return false; + $this->server->tree->delete($path); + $this->server->emit('afterUnbind', [$path]); + + $response->setStatus(204); + $response->setHeader('Content-Length', '0'); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPFIND + * + * This WebDAV method requests information about an uri resource, or a list of resources + * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value + * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) + * + * The request body contains an XML data structure that has a list of properties the client understands + * The response body is also an xml document, containing information about every uri resource and the requested properties + * + * It has to return a HTTP 207 Multi-status status code + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPropFind(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $requestBody = $request->getBodyAsString(); + if (strlen($requestBody)) { + try { + $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + } else { + $propFindXml = new Xml\Request\PropFind(); + $propFindXml->allProp = true; + $propFindXml->properties = []; + } + + $depth = $this->server->getHTTPDepth(1); + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $newProperties = $this->server->getPropertiesIteratorForPath($path, $propFindXml->properties, $depth); + + // This is a multi-status response + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Vary', 'Brief,Prefer'); + + // Normally this header is only needed for OPTIONS responses, however.. + // iCal seems to also depend on these being set for PROPFIND. Since + // this is not harmful, we'll add it. + $features = ['1', '3', 'extended-mkcol']; + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + $response->setHeader('DAV', implode(', ', $features)); + + $prefer = $this->server->getHTTPPrefer(); + $minimal = $prefer['return'] === 'minimal'; + + $data = $this->server->generateMultiStatus($newProperties, $minimal); + $response->setBody($data); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPPATCH + * + * This method is called to update properties on a Node. The request is an XML body with all the mutations. + * In this XML body it is specified which properties should be set/updated and/or deleted + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPropPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + try { + $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody()); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + $newProperties = $propPatch->properties; + + $result = $this->server->updateProperties($path, $newProperties); + + $prefer = $this->server->getHTTPPrefer(); + $response->setHeader('Vary', 'Brief,Prefer'); + + if ($prefer['return'] === 'minimal') { + + // If return-minimal is specified, we only have to check if the + // request was successful, and don't need to return the + // multi-status. + $ok = true; + foreach ($result as $prop => $code) { + if ((int)$code > 299) { + $ok = false; + } + } + + if ($ok) { + + $response->setStatus(204); + return false; + + } + + } + + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + + // Reorganizing the result for generateMultiStatus + $multiStatus = []; + foreach ($result as $propertyName => $code) { + if (isset($multiStatus[$code])) { + $multiStatus[$code][$propertyName] = null; + } else { + $multiStatus[$code] = [$propertyName => null]; + } + } + $multiStatus['href'] = $path; + + $response->setBody( + $this->server->generateMultiStatus([$multiStatus]) + ); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP PUT method + * + * This HTTP method updates a file, or creates a new one. + * + * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $response) { + + $body = $request->getBodyAsStream(); + $path = $request->getPath(); + + // Intercepting Content-Range + if ($request->getHeader('Content-Range')) { + /* + An origin server that allows PUT on a given target resource MUST send + a 400 (Bad Request) response to a PUT request that contains a + Content-Range header field. + + Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4 + */ + throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.'); + } + + // Intercepting the Finder problem + if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) { + + /* + Many webservers will not cooperate well with Finder PUT requests, + because it uses 'Chunked' transfer encoding for the request body. + + The symptom of this problem is that Finder sends files to the + server, but they arrive as 0-length files in PHP. + + If we don't do anything, the user might think they are uploading + files successfully, but they end up empty on the server. Instead, + we throw back an error if we detect this. + + The reason Finder uses Chunked, is because it thinks the files + might change as it's being uploaded, and therefore the + Content-Length can vary. + + Instead it sends the X-Expected-Entity-Length header with the size + of the file at the very start of the request. If this header is set, + but we don't get a request body we will fail the request to + protect the end-user. + */ + + // Only reading first byte + $firstByte = fread($body, 1); + if (strlen($firstByte) !== 1) { + throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.'); + } + + // The body needs to stay intact, so we copy everything to a + // temporary stream. + + $newBody = fopen('php://temp', 'r+'); + fwrite($newBody, $firstByte); + stream_copy_to_stream($body, $newBody); + rewind($newBody); + + $body = $newBody; + + } + + if ($this->server->tree->nodeExists($path)) { + + $node = $this->server->tree->getNodeForPath($path); + + // If the node is a collection, we'll deny it + if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.'); + + if (!$this->server->updateFile($path, $body, $etag)) { + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + } else { + + $etag = null; + // If we got here, the resource didn't exist yet. + if (!$this->server->createFile($path, $body, $etag)) { + // For one reason or another the file was not created. + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(201); + + } + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + + /** + * WebDAV MKCOL + * + * The MKCOL method is used to create a new collection (directory) on the server + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMkcol(RequestInterface $request, ResponseInterface $response) { + + $requestBody = $request->getBodyAsString(); + $path = $request->getPath(); + + if ($requestBody) { + + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) { + + // We must throw 415 for unsupported mkcol bodies + throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); + + } + + try { + $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody); + } catch (\Sabre\Xml\ParseException $e) { + throw new Exception\BadRequest($e->getMessage(), null, $e); + } + + $properties = $mkcol->getProperties(); + + if (!isset($properties['{DAV:}resourcetype'])) + throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property'); + + $resourceType = $properties['{DAV:}resourcetype']->getValue(); + unset($properties['{DAV:}resourcetype']); + + } else { + + $properties = []; + $resourceType = ['{DAV:}collection']; + + } + + $mkcol = new MkCol($resourceType, $properties); + + $result = $this->server->createCollection($path, $mkcol); + + if (is_array($result)) { + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $response->setBody( + $this->server->generateMultiStatus([$result]) + ); + + } else { + $response->setHeader('Content-Length', '0'); + $response->setStatus(201); + } + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP MOVE method + * + * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMove(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $moveInfo = $this->server->getCopyAndMoveInfo($request); + + if ($moveInfo['destinationExists']) { + + if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false; + + } + if (!$this->server->emit('beforeUnbind', [$path])) return false; + if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false; + if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false; + + if ($moveInfo['destinationExists']) { + + $this->server->tree->delete($moveInfo['destination']); + $this->server->emit('afterUnbind', [$moveInfo['destination']]); + + } + + $this->server->tree->move($path, $moveInfo['destination']); + + // Its important afterMove is called before afterUnbind, because it + // allows systems to transfer data from one path to another. + // PropertyStorage uses this. If afterUnbind was first, it would clean + // up all the properties before it has a chance. + $this->server->emit('afterMove', [$path, $moveInfo['destination']]); + $this->server->emit('afterUnbind', [$path]); + $this->server->emit('afterBind', [$moveInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($moveInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP COPY method + * + * This method copies one uri to a different uri, and works much like the MOVE request + * A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpCopy(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $copyInfo = $this->server->getCopyAndMoveInfo($request); + + if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false; + if ($copyInfo['destinationExists']) { + if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false; + $this->server->tree->delete($copyInfo['destination']); + } + + $this->server->tree->copy($path, $copyInfo['destination']); + $this->server->emit('afterBind', [$copyInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($copyInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + + } + + /** + * HTTP REPORT method implementation + * + * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253) + * It's used in a lot of extensions, so it made sense to implement it into the core. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpReport(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $result = $this->server->xml->parse( + $request->getBody(), + $request->getUrl(), + $rootElementName + ); + + if ($this->server->emit('report', [$rootElementName, $result, $path])) { + + // If emit returned true, it means the report was not supported + throw new Exception\ReportNotSupported(); + + } + + // Sending back false will interrupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * This method is called during property updates. + * + * Here we check if a user attempted to update a protected property and + * ensure that the process fails if this is the case. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) { + + // Comparing the mutation list to the list of protected properties. + $mutations = $propPatch->getMutations(); + + $protected = array_intersect( + $this->server->protectedProperties, + array_keys($mutations) + ); + + if ($protected) { + $propPatch->setResultCode($protected, 403); + } + + } + + /** + * This method is called during property updates. + * + * Here we check if a node implements IProperties and let the node handle + * updating of (some) properties. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchNodeUpdate($path, PropPatch $propPatch) { + + // This should trigger a 404 if the node doesn't exist. + $node = $this->server->tree->getNodeForPath($path); + + if ($node instanceof IProperties) { + $node->propPatch($propPatch); + } + + } + + /** + * This method is called when properties are retrieved. + * + * Here we add all the default properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getlastmodified', function() use ($node) { + $lm = $node->getLastModified(); + if ($lm) { + return new Xml\Property\GetLastModified($lm); + } + }); + + if ($node instanceof IFile) { + $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']); + $propFind->handle('{DAV:}getetag', [$node, 'getETag']); + $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']); + } + + if ($node instanceof IQuota) { + $quotaInfo = null; + $propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) { + $quotaInfo = $node->getQuotaInfo(); + return $quotaInfo[0]; + }); + $propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) { + if (!$quotaInfo) { + $quotaInfo = $node->getQuotaInfo(); + } + return $quotaInfo[1]; + }); + } + + $propFind->handle('{DAV:}supported-report-set', function() use ($propFind) { + $reports = []; + foreach ($this->server->getPlugins() as $plugin) { + $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath())); + } + return new Xml\Property\SupportedReportSet($reports); + }); + $propFind->handle('{DAV:}resourcetype', function() use ($node) { + return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node)); + }); + $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) { + return new Xml\Property\SupportedMethodSet( + $this->server->getAllowedMethods($propFind->getPath()) + ); + }); + + } + + /** + * Fetches properties for a node. + * + * This event is called a bit later, so plugins have a chance first to + * populate the result. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindNode(PropFind $propFind, INode $node) { + + if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) { + + $nodeProperties = $node->getProperties($propertyNames); + foreach ($nodeProperties as $propertyName => $propertyValue) { + $propFind->set($propertyName, $propertyValue, 200); + } + + } + + } + + /** + * This method is called when properties are retrieved. + * + * This specific handler is called very late in the process, because we + * want other systems to first have a chance to handle the properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindLate(PropFind $propFind, INode $node) { + + $propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) { + + // If we already have a sync-token from the current propFind + // request, we can re-use that. + $val = $propFind->get('{http://sabredav.org/ns}sync-token'); + if ($val) return $val; + + $val = $propFind->get('{DAV:}sync-token'); + if ($val && is_scalar($val)) { + return $val; + } + if ($val && $val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + + // If we got here, the earlier two properties may simply not have + // been part of the earlier request. We're going to fetch them. + $result = $this->server->getProperties($propFind->getPath(), [ + '{http://sabredav.org/ns}sync-token', + '{DAV:}sync-token', + ]); + + if (isset($result['{http://sabredav.org/ns}sync-token'])) { + return $result['{http://sabredav.org/ns}sync-token']; + } + if (isset($result['{DAV:}sync-token'])) { + $val = $result['{DAV:}sync-token']; + if (is_scalar($val)) { + return $val; + } elseif ($val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + } + + }); + + } + + /** + * Listens for exception events, and automatically logs them. + * + * @param Exception $e + */ + function exception($e) { + + $logLevel = \Psr\Log\LogLevel::CRITICAL; + if ($e instanceof \Sabre\DAV\Exception) { + // If it's a standard sabre/dav exception, it means we have a http + // status code available. + $code = $e->getHTTPCode(); + + if ($code >= 400 && $code < 500) { + // user error + $logLevel = \Psr\Log\LogLevel::INFO; + } else { + // Server-side error. We mark it's as an error, but it's not + // critical. + $logLevel = \Psr\Log\LogLevel::ERROR; + } + } + + $this->server->getLogger()->log( + $logLevel, + 'Uncaught exception', + [ + 'exception' => $e, + ] + ); + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.', + 'link' => null, + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception.php new file mode 100644 index 000000000000..14f5bab2a53d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception.php @@ -0,0 +1,57 @@ +lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock'); + $errorNode->appendChild($error); + $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri)); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php new file mode 100644 index 000000000000..77df7ca9eccc --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php @@ -0,0 +1,29 @@ +ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype'); + $errorNode->appendChild($error); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php new file mode 100644 index 000000000000..51a253b296bf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php @@ -0,0 +1,38 @@ +ownerDocument->createElementNS('DAV:', 'd:valid-sync-token'); + $errorNode->appendChild($error); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php new file mode 100644 index 000000000000..98971855834b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php @@ -0,0 +1,30 @@ +message = 'The locktoken supplied does not match any locks on this entity'; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri'); + $errorNode->appendChild($error); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Locked.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Locked.php new file mode 100644 index 000000000000..8176db46e879 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/Locked.php @@ -0,0 +1,72 @@ +lock = $lock; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 423; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted'); + $errorNode->appendChild($error); + + $href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href'); + $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri)); + $error->appendChild( + $href + ); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php new file mode 100644 index 000000000000..30c1c255345f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php @@ -0,0 +1,47 @@ +getAllowedMethods($server->getRequestUri()); + + return [ + 'Allow' => strtoupper(implode(', ', $methods)), + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php new file mode 100644 index 000000000000..e69a60c75491 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php @@ -0,0 +1,30 @@ +header = $header; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 412; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->header) { + $prop = $errorNode->ownerDocument->createElement('s:header'); + $prop->nodeValue = $this->header; + $errorNode->appendChild($prop); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php new file mode 100644 index 000000000000..a8369562702c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php @@ -0,0 +1,32 @@ +ownerDocument->createElementNS('DAV:', 'd:supported-report'); + $errorNode->appendChild($error); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php new file mode 100644 index 000000000000..c8ccfc062a9d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ServiceUnavailable extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 503; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php new file mode 100644 index 000000000000..d0f0f84e89bb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php @@ -0,0 +1,38 @@ +ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits'); + $errorNode->appendChild($error); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php new file mode 100644 index 000000000000..f3d92842da5e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php @@ -0,0 +1,30 @@ +path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + foreach ($iterator as $entry) { + + $nodes[] = $this->getChild($entry->getFilename()); + + } + return $nodes; + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + */ + function delete() { + + foreach ($this->getChildren() as $child) $child->delete(); + rmdir($this->path); + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + $absolute = realpath($this->path); + return [ + disk_total_space($absolute) - disk_free_space($absolute), + disk_free_space($absolute) + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/FS/File.php b/libs/composer/vendor/sabre/dav/lib/DAV/FS/File.php new file mode 100644 index 000000000000..4fc5af0574be --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/FS/File.php @@ -0,0 +1,95 @@ +path, $data); + clearstatcache(true, $this->path); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return void + */ + function delete() { + + unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return mixed + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + function getContentType() { + + return null; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/FS/Node.php b/libs/composer/vendor/sabre/dav/lib/DAV/FS/Node.php new file mode 100644 index 000000000000..424718f966e4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/FS/Node.php @@ -0,0 +1,80 @@ +path = $path; + + } + + + + /** + * Returns the name of the node + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->path); + return $name; + + } + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + function setName($name) { + + list($parentPath, ) = URLUtil::splitPath($this->path); + list(, $newName) = URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + rename($this->path, $newPath); + + $this->path = $newPath; + + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return filemtime($this->path); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/Directory.php b/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/Directory.php new file mode 100644 index 000000000000..dd5f992dbce5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/Directory.php @@ -0,0 +1,211 @@ +path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + return '"' . sha1( + fileinode($newPath) . + filesize($newPath) . + filemtime($newPath) + ) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + // We're not allowing dots + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File could not be located'); + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + if ($name == '.' || $name == '..') + throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + + foreach ($iterator as $entry) { + + $nodes[] = $this->getChild($entry->getFilename()); + + } + return $nodes; + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return bool + */ + function delete() { + + // Deleting all children + foreach ($this->getChildren() as $child) $child->delete(); + + // Removing the directory itself + rmdir($this->path); + + return true; + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + + $total = disk_total_space(realpath($this->path)); + $free = disk_free_space(realpath($this->path)); + + return [ + $total - $free, + $free + ]; + } + + /** + * Moves a node into this collection. + * + * It is up to the implementors to: + * 1. Create the new resource. + * 2. Remove the old resource. + * 3. Transfer any properties or other data. + * + * Generally you should make very sure that your collection can easily move + * the move. + * + * If you don't, just return false, which will trigger sabre/dav to handle + * the move itself. If you return true from this function, the assumption + * is that the move was successful. + * + * @param string $targetName New local file/collection name. + * @param string $sourcePath Full path to source node + * @param DAV\INode $sourceNode Source node itself + * @return bool + */ + function moveInto($targetName, $sourcePath, DAV\INode $sourceNode) { + + // We only support FSExt\Directory or FSExt\File objects, so + // anything else we want to quickly reject. + if (!$sourceNode instanceof self && !$sourceNode instanceof File) { + return false; + } + + // PHP allows us to access protected properties from other objects, as + // long as they are defined in a class that has a shared inheritence + // with the current class. + rename($sourceNode->path, $this->path . '/' . $targetName); + + return true; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/File.php b/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/File.php new file mode 100644 index 000000000000..eb5ae19fec56 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/FSExt/File.php @@ -0,0 +1,152 @@ +path, $data); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Updates the file based on a range specification. + * + * The first argument is the data, which is either a readable stream + * resource or a string. + * + * The second argument is the type of update we're doing. + * This is either: + * * 1. append + * * 2. update based on a start byte + * * 3. update based on an end byte + *; + * The third argument is the start or end byte. + * + * After a successful put operation, you may choose to return an ETag. The + * ETAG must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * @param resource|string $data + * @param int $rangeType + * @param int $offset + * @return string|null + */ + function patch($data, $rangeType, $offset = null) { + + switch ($rangeType) { + case 1 : + $f = fopen($this->path, 'a'); + break; + case 2 : + $f = fopen($this->path, 'c'); + fseek($f, $offset); + break; + case 3 : + $f = fopen($this->path, 'c'); + fseek($f, $offset, SEEK_END); + break; + } + if (is_string($data)) { + fwrite($f, $data); + } else { + stream_copy_to_stream($data, $f); + } + fclose($f); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return bool + */ + function delete() { + + return unlink($this->path); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string|null + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType() { + + return null; + + } + + /** + * Returns the size of the file, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/File.php b/libs/composer/vendor/sabre/dav/lib/DAV/File.php new file mode 100644 index 000000000000..5161fbd51536 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/File.php @@ -0,0 +1,96 @@ +locksFile = $locksFile; + + } + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks) { + + $newLocks = []; + + $locks = $this->getData(); + + foreach ($locks as $lock) { + + if ($lock->uri === $uri || + //deep locks on parents + ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) || + + // locks on children + ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) { + + $newLocks[] = $lock; + + } + + } + + // Checking if we can remove any of these locks + foreach ($newLocks as $k => $lock) { + if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); + } + return $newLocks; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 1800; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getData(); + + foreach ($locks as $k => $lock) { + if ( + ($lock->token == $lockInfo->token) || + (time() > $lock->timeout + $lock->created) + ) { + unset($locks[$k]); + } + } + $locks[] = $lockInfo; + $this->putData($locks); + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $locks = $this->getData(); + foreach ($locks as $k => $lock) { + + if ($lock->token == $lockInfo->token) { + + unset($locks[$k]); + $this->putData($locks); + return true; + + } + } + return false; + + } + + /** + * Loads the lockdata from the filesystem. + * + * @return array + */ + protected function getData() { + + if (!file_exists($this->locksFile)) return []; + + // opening up the file, and creating a shared lock + $handle = fopen($this->locksFile, 'r'); + flock($handle, LOCK_SH); + + // Reading data until the eof + $data = stream_get_contents($handle); + + // We're all good + flock($handle, LOCK_UN); + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!$data) return []; + return $data; + + } + + /** + * Saves the lockdata + * + * @param array $newData + * @return void + */ + protected function putData(array $newData) { + + // opening up the file, and creating an exclusive lock + $handle = fopen($this->locksFile, 'a+'); + flock($handle, LOCK_EX); + + // We can only truncate and rewind once the lock is acquired. + ftruncate($handle, 0); + rewind($handle); + + fwrite($handle, serialize($newData)); + flock($handle, LOCK_UN); + fclose($handle); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php new file mode 100644 index 000000000000..510f266f7e67 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php @@ -0,0 +1,180 @@ +pdo = $pdo; + + } + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatenation prevents us + // from doing this though. + $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM ' . $this->tableName . ' WHERE (created > (? - timeout)) AND ((uri = ?)'; + $params = [time(),$uri]; + + // We need to check locks for every part in the uri. + $uriParts = explode('/', $uri); + + // We already covered the last part of the uri + array_pop($uriParts); + + $currentPath = ''; + + foreach ($uriParts as $part) { + + if ($currentPath) $currentPath .= '/'; + $currentPath .= $part; + + $query .= ' OR (depth!=0 AND uri = ?)'; + $params[] = $currentPath; + + } + + if ($returnChildLocks) { + + $query .= ' OR (uri LIKE ?)'; + $params[] = $uri . '/%'; + + } + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute($params); + $result = $stmt->fetchAll(); + + $lockList = []; + foreach ($result as $row) { + + $lockInfo = new LockInfo(); + $lockInfo->owner = $row['owner']; + $lockInfo->token = $row['token']; + $lockInfo->timeout = $row['timeout']; + $lockInfo->created = $row['created']; + $lockInfo->scope = $row['scope']; + $lockInfo->depth = $row['depth']; + $lockInfo->uri = $row['uri']; + $lockList[] = $lockInfo; + + } + + return $lockList; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 30 * 60; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getLocks($uri, false); + $exists = false; + foreach ($locks as $lock) { + if ($lock->token == $lockInfo->token) $exists = true; + } + + if ($exists) { + $stmt = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); + $stmt->execute([ + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token + ]); + } else { + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); + $stmt->execute([ + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token + ]); + } + + return true; + + } + + + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->tableName . ' WHERE uri = ? AND token = ?'); + $stmt->execute([$uri, $lockInfo->token]); + + return $stmt->rowCount() === 1; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/libs/composer/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php new file mode 100644 index 000000000000..2c8cca0fee4b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php @@ -0,0 +1,80 @@ +addPlugin($lockPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * locksBackend + * + * @var Backend\BackendInterface + */ + protected $locksBackend; + + /** + * server + * + * @var DAV\Server + */ + protected $server; + + /** + * __construct + * + * @param Backend\BackendInterface $locksBackend + */ + function __construct(Backend\BackendInterface $locksBackend) { + + $this->locksBackend = $locksBackend; + + } + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + + $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock'; + + $server->on('method:LOCK', [$this, 'httpLock']); + $server->on('method:UNLOCK', [$this, 'httpUnlock']); + $server->on('validateTokens', [$this, 'validateTokens']); + $server->on('propFind', [$this, 'propFind']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'locks'; + + } + + /** + * This method is called after most properties have been found + * it allows us to add in any Lock-related properties + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}supportedlock', function() { + return new DAV\Xml\Property\SupportedLock(); + }); + $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { + return new DAV\Xml\Property\LockDiscovery( + $this->getLocks($propFind->getPath()) + ); + }); + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + return ['LOCK','UNLOCK']; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * In this case this is only the number 2. The 2 in the Dav: header + * indicates the server supports locks. + * + * @return array + */ + function getFeatures() { + + return [2]; + + } + + /** + * Returns all lock information on a particular uri + * + * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array. + * + * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree + * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object + * for any possible locks and return those as well. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks = false) { + + return $this->locksBackend->getLocks($uri, $returnChildLocks); + + } + + /** + * Locks an uri + * + * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock + * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type + * of lock (shared or exclusive) and the owner of the lock + * + * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock + * + * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3 + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpLock(RequestInterface $request, ResponseInterface $response) { + + $uri = $request->getPath(); + + $existingLocks = $this->getLocks($uri); + + if ($body = $request->getBodyAsString()) { + // This is a new lock request + + $existingLock = null; + // Checking if there's already non-shared locks on the uri. + foreach ($existingLocks as $existingLock) { + if ($existingLock->scope === LockInfo::EXCLUSIVE) { + throw new DAV\Exception\ConflictingLock($existingLock); + } + } + + $lockInfo = $this->parseLockRequest($body); + $lockInfo->depth = $this->server->getHTTPDepth(); + $lockInfo->uri = $uri; + if ($existingLock && $lockInfo->scope != LockInfo::SHARED) + throw new DAV\Exception\ConflictingLock($existingLock); + + } else { + + // Gonna check if this was a lock refresh. + $existingLocks = $this->getLocks($uri); + $conditions = $this->server->getIfConditions($request); + $found = null; + + foreach ($existingLocks as $existingLock) { + foreach ($conditions as $condition) { + foreach ($condition['tokens'] as $token) { + if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) { + $found = $existingLock; + break 3; + } + } + } + } + + // If none were found, this request is in error. + if (is_null($found)) { + if ($existingLocks) { + throw new DAV\Exception\Locked(reset($existingLocks)); + } else { + throw new DAV\Exception\BadRequest('An xml body is required for lock requests'); + } + + } + + // This must have been a lock refresh + $lockInfo = $found; + + // The resource could have been locked through another uri. + if ($uri != $lockInfo->uri) $uri = $lockInfo->uri; + + } + + if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout; + + $newFile = false; + + // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first + try { + $this->server->tree->getNodeForPath($uri); + + // We need to call the beforeWriteContent event for RFC3744 + // Edit: looks like this is not used, and causing problems now. + // + // See Issue 222 + // $this->server->emit('beforeWriteContent',array($uri)); + + } catch (DAV\Exception\NotFound $e) { + + // It didn't, lets create it + $this->server->createFile($uri, fopen('php://memory', 'r')); + $newFile = true; + + } + + $this->lockNode($uri, $lockInfo); + + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Lock-Token', 'token . '>'); + $response->setStatus($newFile ? 201 : 200); + $response->setBody($this->generateLockResponse($lockInfo)); + + // Returning false will interrupt the event chain and mark this method + // as 'handled'. + return false; + + } + + /** + * Unlocks a uri + * + * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header + * The server should return 204 (No content) on success + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpUnlock(RequestInterface $request, ResponseInterface $response) { + + $lockToken = $request->getHeader('Lock-Token'); + + // If the locktoken header is not supplied, we need to throw a bad request exception + if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied'); + + $path = $request->getPath(); + $locks = $this->getLocks($path); + + // Windows sometimes forgets to include < and > in the Lock-Token + // header + if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>'; + + foreach ($locks as $lock) { + + if ('token . '>' == $lockToken) { + + $this->unlockNode($path, $lock); + $response->setHeader('Content-Length', '0'); + $response->setStatus(204); + + // Returning false will break the method chain, and mark the + // method as 'handled'. + return false; + + } + + } + + // If we got here, it means the locktoken was invalid + throw new DAV\Exception\LockTokenMatchesRequestUri(); + + } + + /** + * This method is called after a node is deleted. + * + * We use this event to clean up any locks that still exist on the node. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $locks = $this->getLocks($path, $includeChildren = true); + foreach ($locks as $lock) { + $this->unlockNode($path, $lock); + } + + } + + /** + * Locks a uri + * + * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored + * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return; + return $this->locksBackend->lock($uri, $lockInfo); + + } + + /** + * Unlocks a uri + * + * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return; + return $this->locksBackend->unlock($uri, $lockInfo); + + } + + + /** + * Returns the contents of the HTTP Timeout header. + * + * The method formats the header into an integer. + * + * @return int + */ + function getTimeoutHeader() { + + $header = $this->server->httpRequest->getHeader('Timeout'); + + if ($header) { + + if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7)); + elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE; + else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header'); + + } else { + + $header = 0; + + } + + return $header; + + } + + /** + * Generates the response for successful LOCK requests + * + * @param LockInfo $lockInfo + * @return string + */ + protected function generateLockResponse(LockInfo $lockInfo) { + + return $this->server->xml->write('{DAV:}prop', [ + '{DAV:}lockdiscovery' => + new DAV\Xml\Property\LockDiscovery([$lockInfo]) + ]); + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * In addition, it will also ensure that it checks any missing lokens that + * must be present in the request, and reject requests without the proper + * tokens. + * + * @param RequestInterface $request + * @param mixed $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + // First we need to gather a list of locks that must be satisfied. + $mustLocks = []; + $method = $request->getMethod(); + + // Methods not in that list are operations that doesn't alter any + // resources, and we don't need to check the lock-states for. + switch ($method) { + + case 'DELETE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + break; + case 'MKCOL' : + case 'MKCALENDAR' : + case 'PROPPATCH' : + case 'PUT' : + case 'PATCH' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + false + )); + break; + case 'MOVE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'COPY' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'LOCK' : + //Temporary measure.. figure out later why this is needed + // Here we basically ignore all incoming tokens... + foreach ($conditions as $ii => $condition) { + foreach ($condition['tokens'] as $jj => $token) { + $conditions[$ii]['tokens'][$jj]['validToken'] = true; + } + } + return; + + } + + // It's possible that there's identical locks, because of shared + // parents. We're removing the duplicates here. + $tmp = []; + foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock; + $mustLocks = array_values($tmp); + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Lock tokens always start with opaquelocktoken: + if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') { + continue; + } + + $checkToken = substr($token['token'], 16); + // Looping through our list with locks. + foreach ($mustLocks as $jj => $mustLock) { + + if ($mustLock->token == $checkToken) { + + // We have a match! + // Removing this one from mustlocks + unset($mustLocks[$jj]); + + // Marking the condition as valid. + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + + // Advancing to the next token + continue 2; + + } + + } + + // If we got here, it means that there was a + // lock-token, but it was not in 'mustLocks'. + // + // This is an edge-case, as it could mean that token + // was specified with a url that was not 'required' to + // check. So we're doing one extra lookup to make sure + // we really don't know this token. + // + // This also gets triggered when the user specified a + // lock-token that was expired. + $oddLocks = $this->getLocks($condition['uri']); + foreach ($oddLocks as $oddLock) { + + if ($oddLock->token === $checkToken) { + + // We have a hit! + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + continue 2; + + } + } + + // If we get all the way here, the lock-token was + // really unknown. + + + } + + } + + // If there's any locks left in the 'mustLocks' array, it means that + // the resource was locked and we must block it. + if ($mustLocks) { + + throw new DAV\Exception\Locked(reset($mustLocks)); + + } + + } + + /** + * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object + * + * @param string $body + * @return LockInfo + */ + protected function parseLockRequest($body) { + + $result = $this->server->xml->expect( + '{DAV:}lockinfo', + $body + ); + + $lockInfo = new LockInfo(); + + $lockInfo->owner = $result->owner; + $lockInfo->token = DAV\UUIDUtil::getUUID(); + $lockInfo->scope = $result->scope; + + return $lockInfo; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK', + 'link' => 'http://sabre.io/dav/locks/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/MkCol.php b/libs/composer/vendor/sabre/dav/lib/DAV/MkCol.php new file mode 100644 index 000000000000..042e14bca311 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/MkCol.php @@ -0,0 +1,72 @@ +resourceType = $resourceType; + parent::__construct($mutations); + + } + + /** + * Returns the resourcetype of the new collection. + * + * @return string[] + */ + function getResourceType() { + + return $this->resourceType; + + } + + /** + * Returns true or false if the MKCOL operation has at least the specified + * resource type. + * + * If the resourcetype is specified as an array, all resourcetypes are + * checked. + * + * @param string|string[] $resourceType + * @return bool + */ + function hasResourceType($resourceType) { + + return count(array_diff((array)$resourceType, $this->resourceType)) === 0; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Mount/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/Mount/Plugin.php new file mode 100644 index 000000000000..dc923ad85224 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Mount/Plugin.php @@ -0,0 +1,86 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + + } + + /** + * 'beforeMethod' event handles. This event handles intercepts GET requests ending + * with ?mount + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('mount', $queryParams)) return; + + $currentUri = $request->getAbsoluteUrl(); + + // Stripping off everything after the ? + list($currentUri) = explode('?', $currentUri); + + $this->davMount($response, $currentUri); + + // Returning false to break the event chain + return false; + + } + + /** + * Generates the davmount response + * + * @param ResponseInterface $response + * @param string $uri absolute uri + * @return void + */ + function davMount(ResponseInterface $response, $uri) { + + $response->setStatus(200); + $response->setHeader('Content-Type', 'application/davmount+xml'); + ob_start(); + echo '', "\n"; + echo "\n"; + echo " ", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "\n"; + echo ""; + $response->setBody(ob_get_clean()); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Node.php b/libs/composer/vendor/sabre/dav/lib/DAV/Node.php new file mode 100644 index 000000000000..ef6eea18e060 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Node.php @@ -0,0 +1,54 @@ +addPlugin($patchPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + const RANGE_APPEND = 1; + const RANGE_START = 2; + const RANGE_END = 3; + + /** + * Reference to server + * + * @var DAV\Server + */ + protected $server; + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $server->on('method:PATCH', [$this, 'httpPatch']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'partialupdate'; + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * We claim to support PATCH method (partirl update) if and only if + * - the node exist + * - the node implements our partial update interface + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + $tree = $this->server->tree; + + if ($tree->nodeExists($uri)) { + $node = $tree->getNodeForPath($uri); + if ($node instanceof IPatchSupport) { + return ['PATCH']; + } + } + return []; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * @return array + */ + function getFeatures() { + + return ['sabredav-partialupdate']; + + } + + /** + * Patch an uri + * + * The WebDAV patch request can be used to modify only a part of an + * existing resource. If the resource does not exist yet and the first + * offset is not 0, the request fails + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Get the node. Will throw a 404 if not found + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof IPatchSupport) { + throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.'); + } + + $range = $this->getHTTPUpdateRange($request); + + if (!$range) { + throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers'); + } + + $contentType = strtolower( + $request->getHeader('Content-Type') + ); + + if ($contentType != 'application/x-sabredav-partialupdate') { + throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"'); + } + + $len = $this->server->httpRequest->getHeader('Content-Length'); + if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required'); + + switch ($range[0]) { + case self::RANGE_START : + // Calculate the end-range if it doesn't exist. + if (!$range[2]) { + $range[2] = $range[1] + $len - 1; + } else { + if ($range[2] < $range[1]) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')'); + } + if ($range[2] - $range[1] + 1 != $len) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets'); + } + } + break; + } + + if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) + return; + + $body = $this->server->httpRequest->getBody(); + + + $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null); + + $this->server->emit('afterWriteContent', [$path, $node]); + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + // Breaks the event chain + return false; + + } + + /** + * Returns the HTTP custom range update header + * + * This method returns null if there is no well-formed HTTP range request + * header. It returns array(1) if it was an append request, array(2, + * $start, $end) if it's a start and end range, lastly it's array(3, + * $endoffset) if the offset was negative, and should be calculated from + * the end of the file. + * + * Examples: + * + * null - invalid + * [1] - append + * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15 + * [2,10,null] - update bytes 10 until the end of the patch body + * [3,-5] - update from 5 bytes from the end of the file. + * + * @param RequestInterface $request + * @return array|null + */ + function getHTTPUpdateRange(RequestInterface $request) { + + $range = $request->getHeader('X-Update-Range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) return null; + + if ($matches[1] === 'append') { + return [self::RANGE_APPEND]; + } elseif (strlen($matches[2]) > 0) { + return [self::RANGE_START, $matches[2], $matches[3] ?: null]; + } else { + return [self::RANGE_END, $matches[4]]; + } + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/PropFind.php b/libs/composer/vendor/sabre/dav/lib/DAV/PropFind.php new file mode 100644 index 000000000000..0940a1ce29a7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/PropFind.php @@ -0,0 +1,347 @@ +path = $path; + $this->properties = $properties; + $this->depth = $depth; + $this->requestType = $requestType; + + if ($requestType === self::ALLPROPS) { + $this->properties = [ + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}resourcetype', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}getetag', + '{DAV:}getcontenttype', + ]; + } + + foreach ($this->properties as $propertyName) { + + // Seeding properties with 404's. + $this->result[$propertyName] = [404, null]; + + } + $this->itemsLeft = count($this->result); + + } + + /** + * Handles a specific property. + * + * This method checks whether the specified property was requested in this + * PROPFIND request, and if so, it will call the callback and use the + * return value for it's value. + * + * Example: + * + * $propFind->handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) { + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->itemsLeft--; + $this->result[$propertyName] = [200, $value]; + } + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + // If this is an ALLPROPS request and the property is + // unknown, add it to the result; else ignore it: + if (!isset($this->result[$propertyName])) { + if ($this->requestType === self::ALLPROPS) { + $this->result[$propertyName] = [$status, $value]; + } + return; + } + if ($status !== 404 && $this->result[$propertyName][0] === 404) { + $this->itemsLeft--; + } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) { + $this->itemsLeft++; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null; + + } + + /** + * Updates the path for this PROPFIND. + * + * @param string $path + * @return void + */ + function setPath($path) { + + $this->path = $path; + + } + + /** + * Returns the path this PROPFIND request is for. + * + * @return string + */ + function getPath() { + + return $this->path; + + } + + /** + * Returns the depth of this propfind request. + * + * @return int + */ + function getDepth() { + + return $this->depth; + + } + + /** + * Updates the depth of this propfind request. + * + * @param int $depth + * @return void + */ + function setDepth($depth) { + + $this->depth = $depth; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + if ($this->itemsLeft === 0) { + return []; + } + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + return $result; + + } + + /** + * Returns the full list of requested properties. + * + * This returns just their names, not a status or value. + * + * @return array + */ + function getRequestedProperties() { + + return $this->properties; + + } + + /** + * Returns true if this was an '{DAV:}allprops' request. + * + * @return bool + */ + function isAllProps() { + + return $this->requestType === self::ALLPROPS; + + } + + /** + * Returns a result array that's often used in multistatus responses. + * + * The array uses status codes as keys, and property names and value pairs + * as the value of the top array.. such as : + * + * [ + * 200 => [ '{DAV:}displayname' => 'foo' ], + * ] + * + * @return array + */ + function getResultForMultiStatus() { + + $r = [ + 200 => [], + 404 => [], + ]; + foreach ($this->result as $propertyName => $info) { + if (!isset($r[$info[0]])) { + $r[$info[0]] = [$propertyName => $info[1]]; + } else { + $r[$info[0]][$propertyName] = $info[1]; + } + } + // Removing the 404's for multi-status requests. + if ($this->requestType === self::ALLPROPS) unset($r[404]); + return $r; + + } + + /** + * The path that we're fetching properties for. + * + * @var string + */ + protected $path; + + /** + * The Depth of the request. + * + * 0 means only the current item. 1 means the current item + its children. + * It can also be DEPTH_INFINITY if this is enabled in the server. + * + * @var int + */ + protected $depth = 0; + + /** + * The type of request. See the TYPE constants + */ + protected $requestType; + + /** + * A list of requested properties + * + * @var array + */ + protected $properties = []; + + /** + * The result of the operation. + * + * The keys in this array are property names. + * The values are an array with two elements: the http status code and then + * optionally a value. + * + * Example: + * + * [ + * "{DAV:}owner" : [404], + * "{DAV:}displayname" : [200, "Admin"] + * ] + * + * @var array + */ + protected $result = []; + + /** + * This is used as an internal counter for the number of properties that do + * not yet have a value. + * + * @var int + */ + protected $itemsLeft; + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/PropPatch.php b/libs/composer/vendor/sabre/dav/lib/DAV/PropPatch.php new file mode 100644 index 000000000000..6d599dacc4a9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/PropPatch.php @@ -0,0 +1,373 @@ +mutations = $mutations; + + } + + /** + * Call this function if you wish to handle updating certain properties. + * For instance, your class may be responsible for handling updates for the + * {DAV:}displayname property. + * + * In that case, call this method with the first argument + * "{DAV:}displayname" and a second argument that's a method that does the + * actual updating. + * + * It's possible to specify more than one property as an array. + * + * The callback must return a boolean or an it. If the result is true, the + * operation was considered successful. If it's false, it's consided + * failed. + * + * If the result is an integer, we'll use that integer as the http status + * code associated with the operation. + * + * @param string|string[] $properties + * @param callable $callback + * @return void + */ + function handle($properties, callable $callback) { + + $usedProperties = []; + foreach ((array)$properties as $propertyName) { + + if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) { + + $usedProperties[] = $propertyName; + // HTTP Accepted + $this->result[$propertyName] = 202; + } + + } + + // Only registering if there's any unhandled properties. + if (!$usedProperties) { + return; + } + $this->propertyUpdateCallbacks[] = [ + // If the original argument to this method was a string, we need + // to also make sure that it stays that way, so the commit function + // knows how to format the arguments to the callback. + is_string($properties) ? $properties : $usedProperties, + $callback + ]; + + } + + /** + * Call this function if you wish to handle _all_ properties that haven't + * been handled by anything else yet. Note that you effectively claim with + * this that you promise to process _all_ properties that are coming in. + * + * @param callable $callback + * @return void + */ + function handleRemaining(callable $callback) { + + $properties = $this->getRemainingMutations(); + if (!$properties) { + // Nothing to do, don't register callback + return; + } + + foreach ($properties as $propertyName) { + // HTTP Accepted + $this->result[$propertyName] = 202; + + $this->propertyUpdateCallbacks[] = [ + $properties, + $callback + ]; + } + + } + + /** + * Sets the result code for one or more properties. + * + * @param string|string[] $properties + * @param int $resultCode + * @return void + */ + function setResultCode($properties, $resultCode) { + + foreach ((array)$properties as $propertyName) { + $this->result[$propertyName] = $resultCode; + } + + if ($resultCode >= 400) { + $this->failed = true; + } + + } + + /** + * Sets the result code for all properties that did not have a result yet. + * + * @param int $resultCode + * @return void + */ + function setRemainingResultCode($resultCode) { + + $this->setResultCode( + $this->getRemainingMutations(), + $resultCode + ); + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns a list of property names, but not its values. + * + * @return string[] + */ + function getRemainingMutations() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[] = $propertyName; + } + } + + return $remaining; + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns list of properties and their values. + * + * @return array + */ + function getRemainingValues() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[$propertyName] = $propValue; + } + } + + return $remaining; + + } + + /** + * Performs the actual update, and calls all callbacks. + * + * This method returns true or false depending on if the operation was + * successful. + * + * @return bool + */ + function commit() { + + // First we validate if every property has a handler + foreach ($this->mutations as $propertyName => $value) { + + if (!isset($this->result[$propertyName])) { + $this->failed = true; + $this->result[$propertyName] = 403; + } + + } + + foreach ($this->propertyUpdateCallbacks as $callbackInfo) { + + if ($this->failed) { + break; + } + if (is_string($callbackInfo[0])) { + $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]); + } else { + $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]); + } + + } + + /** + * If anywhere in this operation updating a property failed, we must + * update all other properties accordingly. + */ + if ($this->failed) { + + foreach ($this->result as $propertyName => $status) { + if ($status === 202) { + // Failed dependency + $this->result[$propertyName] = 424; + } + } + + } + + return !$this->failed; + + } + + /** + * Executes a property callback with the single-property syntax. + * + * @param string $propertyName + * @param callable $callback + * @return void + */ + private function doCallBackSingleProp($propertyName, callable $callback) { + + $result = $callback($this->mutations[$propertyName]); + if (is_bool($result)) { + if ($result) { + if (is_null($this->mutations[$propertyName])) { + // Delete + $result = 204; + } else { + // Update + $result = 200; + } + } else { + // Fail + $result = 403; + } + } + if (!is_int($result)) { + throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool'); + } + $this->result[$propertyName] = $result; + if ($result >= 400) { + $this->failed = true; + } + + } + + /** + * Executes a property callback with the multi-property syntax. + * + * @param array $propertyList + * @param callable $callback + * @return void + */ + private function doCallBackMultiProp(array $propertyList, callable $callback) { + + $argument = []; + foreach ($propertyList as $propertyName) { + $argument[$propertyName] = $this->mutations[$propertyName]; + } + + $result = $callback($argument); + + if (is_array($result)) { + foreach ($propertyList as $propertyName) { + if (!isset($result[$propertyName])) { + $resultCode = 500; + } else { + $resultCode = $result[$propertyName]; + } + if ($resultCode >= 400) { + $this->failed = true; + } + $this->result[$propertyName] = $resultCode; + + } + } elseif ($result === true) { + + // Success + foreach ($argument as $propertyName => $propertyValue) { + $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200; + } + + } elseif ($result === false) { + // Fail :( + $this->failed = true; + foreach ($propertyList as $propertyName) { + $this->result[$propertyName] = 403; + } + } else { + throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool'); + } + + } + + /** + * Returns the result of the operation. + * + * @return array + */ + function getResult() { + + return $this->result; + + } + + /** + * Returns the full list of mutations + * + * @return array + */ + function getMutations() { + + return $this->mutations; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php new file mode 100644 index 000000000000..b15d7fef9444 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php @@ -0,0 +1,80 @@ +isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind); + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch); + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path); + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php new file mode 100644 index 000000000000..6f3f1feaf577 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php @@ -0,0 +1,246 @@ +pdo = $pdo; + + } + + /** + * Fetches properties for a path. + * + * This method received a PropFind object, which contains all the + * information about the properties that need to be fetched. + * + * Usually you would just want to call 'get404Properties' on this object, + * as this will give you the _exact_ list of properties that need to be + * fetched, and haven't yet. + * + * However, you can also support the 'allprops' property here. In that + * case, you should check for $propFind->isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind) { + + if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) { + return; + } + + $query = 'SELECT name, value, valuetype FROM ' . $this->tableName . ' WHERE path = ?'; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$path]); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if (gettype($row['value']) === 'resource') { + $row['value'] = stream_get_contents($row['value']); + } + switch ($row['valuetype']) { + case null : + case self::VT_STRING : + $propFind->set($row['name'], $row['value']); + break; + case self::VT_XML : + $propFind->set($row['name'], new Complex($row['value'])); + break; + case self::VT_OBJECT : + $propFind->set($row['name'], unserialize($row['value'])); + break; + } + } + + } + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $propPatch->handleRemaining(function($properties) use ($path) { + + + if ($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') { + + $updateSql = <<tableName} (path, name, valuetype, value) +VALUES (:path, :name, :valuetype, :value) +ON CONFLICT (path, name) +DO UPDATE SET valuetype = :valuetype, value = :value +SQL; + + + } else { + $updateSql = <<tableName} (path, name, valuetype, value) +VALUES (:path, :name, :valuetype, :value) +SQL; + + } + + $updateStmt = $this->pdo->prepare($updateSql); + $deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?"); + + foreach ($properties as $name => $value) { + + if (!is_null($value)) { + if (is_scalar($value)) { + $valueType = self::VT_STRING; + } elseif ($value instanceof Complex) { + $valueType = self::VT_XML; + $value = $value->getXml(); + } else { + $valueType = self::VT_OBJECT; + $value = serialize($value); + } + + $updateStmt->bindParam('path', $path, \PDO::PARAM_STR); + $updateStmt->bindParam('name', $name, \PDO::PARAM_STR); + $updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT); + $updateStmt->bindParam('value', $value, \PDO::PARAM_LOB); + + $updateStmt->execute(); + + } else { + $deleteStmt->execute([$path, $name]); + } + + } + + return true; + + }); + + } + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path) { + + $stmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? OR path LIKE ? ESCAPE '='"); + $childPath = strtr( + $path, + [ + '=' => '==', + '%' => '=%', + '_' => '=_' + ] + ) . '/%'; + + $stmt->execute([$path, $childPath]); + + } + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination) { + + // I don't know a way to write this all in a single sql query that's + // also compatible across db engines, so we're letting PHP do all the + // updates. Much slower, but it should still be pretty fast in most + // cases. + $select = $this->pdo->prepare('SELECT id, path FROM ' . $this->tableName . ' WHERE path = ? OR path LIKE ?'); + $select->execute([$source, $source . '/%']); + + $update = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET path = ? WHERE id = ?'); + while ($row = $select->fetch(\PDO::FETCH_ASSOC)) { + + // Sanity check. SQL may select too many records, such as records + // with different cases. + if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue; + + $trailingPart = substr($row['path'], strlen($source) + 1); + $newPath = $destination; + if ($trailingPart) { + $newPath .= '/' . $trailingPart; + } + $update->execute([$newPath, $row['id']]); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php new file mode 100644 index 000000000000..a66a14113fb8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php @@ -0,0 +1,185 @@ +backend = $backend; + + } + + /** + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $server->on('propFind', [$this, 'propFind'], 130); + $server->on('propPatch', [$this, 'propPatch'], 300); + $server->on('afterMove', [$this, 'afterMove']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * Called during PROPFIND operations. + * + * If there's any requested properties that don't have a value yet, this + * plugin will look in the property storage backend to find them. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $path = $propFind->getPath(); + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propFind($propFind->getPath(), $propFind); + + } + + /** + * Called during PROPPATCH operations + * + * If there's any updated properties that haven't been stored, the + * propertystorage backend can handle it. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propPatch($path, $propPatch); + + } + + /** + * Called after a node is deleted. + * + * This allows the backend to clean up any properties still in the + * database. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->delete($path); + + } + + /** + * Called after a node is moved. + * + * This allows the backend to move all the associated properties. + * + * @param string $source + * @param string $destination + * @return void + */ + function afterMove($source, $destination) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($source)) return; + // If the destination is filtered, afterUnbind will handle cleaning up + // the properties. + if ($pathFilter && !$pathFilter($destination)) return; + + $this->backend->move($source, $destination); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'property-storage'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin allows any arbitrary WebDAV property to be set on any resource.', + 'link' => 'http://sabre.io/dav/property-storage/', + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Server.php b/libs/composer/vendor/sabre/dav/lib/DAV/Server.php new file mode 100644 index 000000000000..6805ec0b0130 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Server.php @@ -0,0 +1,1687 @@ + '{DAV:}collection', + ]; + + /** + * This property allows the usage of Depth: infinity on PROPFIND requests. + * + * By default Depth: infinity is treated as Depth: 1. Allowing Depth: + * infinity is potentially risky, as it allows a single client to do a full + * index of the webdav server, which is an easy DoS attack vector. + * + * Only turn this on if you know what you're doing. + * + * @var bool + */ + public $enablePropfindDepthInfinity = false; + + /** + * Reference to the XML utility object. + * + * @var Xml\Service + */ + public $xml; + + /** + * If this setting is turned off, SabreDAV's version number will be hidden + * from various places. + * + * Some people feel this is a good security measure. + * + * @var bool + */ + static $exposeVersion = true; + + /** + * Sets up the server + * + * If a Sabre\DAV\Tree object is passed as an argument, it will + * use it as the directory tree. If a Sabre\DAV\INode is passed, it + * will create a Sabre\DAV\Tree and use the node as the root. + * + * If nothing is passed, a Sabre\DAV\SimpleCollection is created in + * a Sabre\DAV\Tree. + * + * If an array is passed, we automatically create a root node, and use + * the nodes in the array as top-level children. + * + * @param Tree|INode|array|null $treeOrNode The tree object + */ + function __construct($treeOrNode = null) { + + if ($treeOrNode instanceof Tree) { + $this->tree = $treeOrNode; + } elseif ($treeOrNode instanceof INode) { + $this->tree = new Tree($treeOrNode); + } elseif (is_array($treeOrNode)) { + + // If it's an array, a list of nodes was passed, and we need to + // create the root node. + foreach ($treeOrNode as $node) { + if (!($node instanceof INode)) { + throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode'); + } + } + + $root = new SimpleCollection('root', $treeOrNode); + $this->tree = new Tree($root); + + } elseif (is_null($treeOrNode)) { + $root = new SimpleCollection('root'); + $this->tree = new Tree($root); + } else { + throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null'); + } + + $this->xml = new Xml\Service(); + $this->sapi = new HTTP\Sapi(); + $this->httpResponse = new HTTP\Response(); + $this->httpRequest = $this->sapi->getRequest(); + $this->addPlugin(new CorePlugin()); + + } + + /** + * Starts the DAV Server + * + * @return void + */ + function exec() { + + try { + + // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an + // origin, we must make sure we send back HTTP/1.0 if this was + // requested. + // This is mainly because nginx doesn't support Chunked Transfer + // Encoding, and this forces the webserver SabreDAV is running on, + // to buffer entire responses to calculate Content-Length. + $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); + + // Setting the base url + $this->httpRequest->setBaseUrl($this->getBaseUri()); + $this->invokeMethod($this->httpRequest, $this->httpResponse); + + } catch (\Exception $e) { + + try { + $this->emit('exception', [$e]); + } catch (\Exception $ignore) { + } + $DOM = new \DOMDocument('1.0', 'utf-8'); + $DOM->formatOutput = true; + + $error = $DOM->createElementNS('DAV:', 'd:error'); + $error->setAttribute('xmlns:s', self::NS_SABREDAV); + $DOM->appendChild($error); + + $h = function($v) { + + return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); + + }; + + if (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); + } + + $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); + $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); + if ($this->debugExceptions) { + $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); + $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); + $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); + $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); + } + + if ($this->debugExceptions) { + $previous = $e; + while ($previous = $previous->getPrevious()) { + $xPrevious = $DOM->createElement('s:previous-exception'); + $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); + $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); + $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); + $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); + $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); + $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); + $error->appendChild($xPrevious); + } + } + + + if ($e instanceof Exception) { + + $httpCode = $e->getHTTPCode(); + $e->serialize($this, $error); + $headers = $e->getHTTPHeaders($this); + + } else { + + $httpCode = 500; + $headers = []; + + } + $headers['Content-Type'] = 'application/xml; charset=utf-8'; + + $this->httpResponse->setStatus($httpCode); + $this->httpResponse->setHeaders($headers); + $this->httpResponse->setBody($DOM->saveXML()); + $this->sapi->sendResponse($this->httpResponse); + + } + + } + + /** + * Sets the base server uri + * + * @param string $uri + * @return void + */ + function setBaseUri($uri) { + + // If the baseUri does not end with a slash, we must add it + if ($uri[strlen($uri) - 1] !== '/') + $uri .= '/'; + + $this->baseUri = $uri; + + } + + /** + * Returns the base responding uri + * + * @return string + */ + function getBaseUri() { + + if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri(); + return $this->baseUri; + + } + + /** + * This method attempts to detect the base uri. + * Only the PATH_INFO variable is considered. + * + * If this variable is not set, the root (/) is assumed. + * + * @return string + */ + function guessBaseUri() { + + $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO'); + $uri = $this->httpRequest->getRawServerValue('REQUEST_URI'); + + // If PATH_INFO is found, we can assume it's accurate. + if (!empty($pathInfo)) { + + // We need to make sure we ignore the QUERY_STRING part + if ($pos = strpos($uri, '?')) + $uri = substr($uri, 0, $pos); + + // PATH_INFO is only set for urls, such as: /example.php/path + // in that case PATH_INFO contains '/path'. + // Note that REQUEST_URI is percent encoded, while PATH_INFO is + // not, Therefore they are only comparable if we first decode + // REQUEST_INFO as well. + $decodedUri = URLUtil::decodePath($uri); + + // A simple sanity check: + if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) { + $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo)); + return rtrim($baseUri, '/') . '/'; + } + + throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); + + } + + // The last fallback is that we're just going to assume the server root. + return '/'; + + } + + /** + * Adds a plugin to the server + * + * For more information, console the documentation of Sabre\DAV\ServerPlugin + * + * @param ServerPlugin $plugin + * @return void + */ + function addPlugin(ServerPlugin $plugin) { + + $this->plugins[$plugin->getPluginName()] = $plugin; + $plugin->initialize($this); + + } + + /** + * Returns an initialized plugin by it's name. + * + * This function returns null if the plugin was not found. + * + * @param string $name + * @return ServerPlugin + */ + function getPlugin($name) { + + if (isset($this->plugins[$name])) + return $this->plugins[$name]; + + return null; + + } + + /** + * Returns all plugins + * + * @return array + */ + function getPlugins() { + + return $this->plugins; + + } + + /** + * Returns the PSR-3 logger object. + * + * @return LoggerInterface + */ + function getLogger() { + + if (!$this->logger) { + $this->logger = new NullLogger(); + } + return $this->logger; + + } + + /** + * Handles a http request, and execute a method based on its name + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param bool $sendResponse Whether to send the HTTP response to the DAV client. + * @return void + */ + function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) { + + $method = $request->getMethod(); + + if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return; + if (!$this->emit('beforeMethod', [$request, $response])) return; + + if (self::$exposeVersion) { + $response->setHeader('X-Sabre-Version', Version::VERSION); + } + + $this->transactionType = strtolower($method); + + if (!$this->checkPreconditions($request, $response)) { + $this->sapi->sendResponse($response); + return; + } + + if ($this->emit('method:' . $method, [$request, $response])) { + if ($this->emit('method', [$request, $response])) { + $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method."; + if ($method === "GET") { + $exMessage .= " Enable the Browser plugin to get a better result here."; + } + + // Unsupported method + throw new Exception\NotImplemented($exMessage); + } + } + + if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; + if (!$this->emit('afterMethod', [$request, $response])) return; + + if ($response->getStatus() === null) { + throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.'); + } + if ($sendResponse) { + $this->sapi->sendResponse($response); + $this->emit('afterResponse', [$request, $response]); + } + + } + + // {{{ HTTP/WebDAV protocol helpers + + /** + * Returns an array with all the supported HTTP methods for a specific uri. + * + * @param string $path + * @return array + */ + function getAllowedMethods($path) { + + $methods = [ + 'OPTIONS', + 'GET', + 'HEAD', + 'DELETE', + 'PROPFIND', + 'PUT', + 'PROPPATCH', + 'COPY', + 'MOVE', + 'REPORT' + ]; + + // The MKCOL is only allowed on an unmapped uri + try { + $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $methods[] = 'MKCOL'; + } + + // We're also checking if any of the plugins register any new methods + foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path)); + array_unique($methods); + + return $methods; + + } + + /** + * Gets the uri for the request, keeping the base uri into consideration + * + * @return string + */ + function getRequestUri() { + + return $this->calculateUri($this->httpRequest->getUrl()); + + } + + /** + * Turns a URI such as the REQUEST_URI into a local path. + * + * This method: + * * strips off the base path + * * normalizes the path + * * uri-decodes the path + * + * @param string $uri + * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri + * @return string + */ + function calculateUri($uri) { + + if ($uri[0] != '/' && strpos($uri, '://')) { + + $uri = parse_url($uri, PHP_URL_PATH); + + } + + $uri = Uri\normalize(str_replace('//', '/', $uri)); + $baseUri = Uri\normalize($this->getBaseUri()); + + if (strpos($uri, $baseUri) === 0) { + + return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/'); + + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri . '/' === $baseUri) { + + return ''; + + } else { + + throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')'); + + } + + } + + /** + * Returns the HTTP depth header + * + * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object + * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent + * + * @param mixed $default + * @return int + */ + function getHTTPDepth($default = self::DEPTH_INFINITY) { + + // If its not set, we'll grab the default + $depth = $this->httpRequest->getHeader('Depth'); + + if (is_null($depth)) return $default; + + if ($depth == 'infinity') return self::DEPTH_INFINITY; + + + // If its an unknown value. we'll grab the default + if (!ctype_digit($depth)) return $default; + + return (int)$depth; + + } + + /** + * Returns the HTTP range header + * + * This method returns null if there is no well-formed HTTP range request + * header or array($start, $end). + * + * The first number is the offset of the first byte in the range. + * The second number is the offset of the last byte in the range. + * + * If the second offset is null, it should be treated as the offset of the last byte of the entity + * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity + * + * @return array|null + */ + function getHTTPRange() { + + $range = $this->httpRequest->getHeader('range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null; + + if ($matches[1] === '' && $matches[2] === '') return null; + + return [ + $matches[1] !== '' ? $matches[1] : null, + $matches[2] !== '' ? $matches[2] : null, + ]; + + } + + /** + * Returns the HTTP Prefer header information. + * + * The prefer header is defined in: + * http://tools.ietf.org/html/draft-snell-http-prefer-14 + * + * This method will return an array with options. + * + * Currently, the following options may be returned: + * [ + * 'return-asynch' => true, + * 'return-minimal' => true, + * 'return-representation' => true, + * 'wait' => 30, + * 'strict' => true, + * 'lenient' => true, + * ] + * + * This method also supports the Brief header, and will also return + * 'return-minimal' if the brief header was set to 't'. + * + * For the boolean options, false will be returned if the headers are not + * specified. For the integer options it will be 'null'. + * + * @return array + */ + function getHTTPPrefer() { + + $result = [ + // can be true or false + 'respond-async' => false, + // Could be set to 'representation' or 'minimal'. + 'return' => null, + // Used as a timeout, is usually a number. + 'wait' => null, + // can be 'strict' or 'lenient'. + 'handling' => false, + ]; + + if ($prefer = $this->httpRequest->getHeader('Prefer')) { + + $result = array_merge( + $result, + HTTP\parsePrefer($prefer) + ); + + } elseif ($this->httpRequest->getHeader('Brief') == 't') { + $result['return'] = 'minimal'; + } + + return $result; + + } + + + /** + * Returns information about Copy and Move requests + * + * This function is created to help getting information about the source and the destination for the + * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions + * + * The returned value is an array with the following keys: + * * destination - Destination path + * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) + * + * @param RequestInterface $request + * @throws Exception\BadRequest upon missing or broken request headers + * @throws Exception\UnsupportedMediaType when trying to copy into a + * non-collection. + * @throws Exception\PreconditionFailed If overwrite is set to false, but + * the destination exists. + * @throws Exception\Forbidden when source and destination paths are + * identical. + * @throws Exception\Conflict When trying to copy a node into its own + * subtree. + * @return array + */ + function getCopyAndMoveInfo(RequestInterface $request) { + + // Collecting the relevant HTTP headers + if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); + $destination = $this->calculateUri($request->getHeader('Destination')); + $overwrite = $request->getHeader('Overwrite'); + if (!$overwrite) $overwrite = 'T'; + if (strtoupper($overwrite) == 'T') $overwrite = true; + elseif (strtoupper($overwrite) == 'F') $overwrite = false; + // We need to throw a bad request exception, if the header was invalid + else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F'); + + list($destinationDir) = URLUtil::splitPath($destination); + + try { + $destinationParent = $this->tree->getNodeForPath($destinationDir); + if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection'); + } catch (Exception\NotFound $e) { + + // If the destination parent node is not found, we throw a 409 + throw new Exception\Conflict('The destination node is not found'); + } + + try { + + $destinationNode = $this->tree->getNodeForPath($destination); + + // If this succeeded, it means the destination already exists + // we'll need to throw precondition failed in case overwrite is false + if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite'); + + } catch (Exception\NotFound $e) { + + // Destination didn't exist, we're all good + $destinationNode = false; + + } + + $requestPath = $request->getPath(); + if ($destination === $requestPath) { + throw new Exception\Forbidden('Source and destination uri are identical.'); + } + if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') { + throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.'); + } + + // These are the three relevant properties we need to return + return [ + 'destination' => $destination, + 'destinationExists' => !!$destinationNode, + 'destinationNode' => $destinationNode, + ]; + + } + + /** + * Returns a list of properties for a path + * + * This is a simplified version getPropertiesForPath. If you aren't + * interested in status codes, but you just want to have a flat list of + * properties, use this method. + * + * Please note though that any problems related to retrieving properties, + * such as permission issues will just result in an empty array being + * returned. + * + * @param string $path + * @param array $propertyNames + * @return array + */ + function getProperties($path, $propertyNames) { + + $result = $this->getPropertiesForPath($path, $propertyNames, 0); + if (isset($result[0][200])) { + return $result[0][200]; + } else { + return []; + } + + } + + /** + * A kid-friendly way to fetch properties for a node's children. + * + * The returned array will be indexed by the path of the of child node. + * Only properties that are actually found will be returned. + * + * The parent node will not be returned. + * + * @param string $path + * @param array $propertyNames + * @return array + */ + function getPropertiesForChildren($path, $propertyNames) { + + $result = []; + foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) { + + // Skipping the parent path + if ($k === 0) continue; + + $result[$row['href']] = $row[200]; + + } + return $result; + + } + + /** + * Returns a list of HTTP headers for a particular resource + * + * The generated http headers are based on properties provided by the + * resource. The method basically provides a simple mapping between + * DAV property and HTTP header. + * + * The headers are intended to be used for HEAD and GET requests. + * + * @param string $path + * @return array + */ + function getHTTPHeaders($path) { + + $propertyMap = [ + '{DAV:}getcontenttype' => 'Content-Type', + '{DAV:}getcontentlength' => 'Content-Length', + '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getetag' => 'ETag', + ]; + + $properties = $this->getProperties($path, array_keys($propertyMap)); + + $headers = []; + foreach ($propertyMap as $property => $header) { + if (!isset($properties[$property])) continue; + + if (is_scalar($properties[$property])) { + $headers[$header] = $properties[$property]; + + // GetLastModified gets special cased + } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) { + $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); + } + + } + + return $headers; + + } + + /** + * Small helper to support PROPFIND with DEPTH_INFINITY. + * + * @param PropFind $propFind + * @param array $yieldFirst + * @return \Iterator + */ + private function generatePathNodes(PropFind $propFind, array $yieldFirst = null) { + if ($yieldFirst !== null) { + yield $yieldFirst; + } + $newDepth = $propFind->getDepth(); + $path = $propFind->getPath(); + + if ($newDepth !== self::DEPTH_INFINITY) { + $newDepth--; + } + + foreach ($this->tree->getChildren($path) as $childNode) { + $subPropFind = clone $propFind; + $subPropFind->setDepth($newDepth); + if ($path !== '') { + $subPath = $path . '/' . $childNode->getName(); + } else { + $subPath = $childNode->getName(); + } + $subPropFind->setPath($subPath); + + yield [ + $subPropFind, + $childNode + ]; + + if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) { + foreach ($this->generatePathNodes($subPropFind) as $subItem) { + yield $subItem; + } + } + + } + } + + /** + * Returns a list of properties for a given path + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * If a depth of 1 is requested child elements will also be returned. + * + * @param string $path + * @param array $propertyNames + * @param int $depth + * @return array + * + * @deprecated Use getPropertiesIteratorForPath() instead (as it's more memory efficient) + * @see getPropertiesIteratorForPath() + */ + function getPropertiesForPath($path, $propertyNames = [], $depth = 0) { + + return iterator_to_array($this->getPropertiesIteratorForPath($path, $propertyNames, $depth)); + + } + /** + * Returns a list of properties for a given path + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * If a depth of 1 is requested child elements will also be returned. + * + * @param string $path + * @param array $propertyNames + * @param int $depth + * @return \Iterator + */ + function getPropertiesIteratorForPath($path, $propertyNames = [], $depth = 0) { + + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $path = trim($path, '/'); + + $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS; + $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType); + + $parentNode = $this->tree->getNodeForPath($path); + + $propFindRequests = [[ + $propFind, + $parentNode + ]]; + + if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) { + $propFindRequests = $this->generatePathNodes(clone $propFind, current($propFindRequests)); + } + + foreach ($propFindRequests as $propFindRequest) { + + list($propFind, $node) = $propFindRequest; + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result = $propFind->getResultForMultiStatus(); + $result['href'] = $propFind->getPath(); + + // WebDAV recommends adding a slash to the path, if the path is + // a collection. + // Furthermore, iCal also demands this to be the case for + // principals. This is non-standard, but we support it. + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result['href'] .= '/'; + } + yield $result; + } + + } + + } + + /** + * Returns a list of properties for a list of paths. + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * The result is returned as an array, with paths for it's keys. + * The result may be returned out of order. + * + * @param array $paths + * @param array $propertyNames + * @return array + */ + function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) { + + $result = [ + ]; + + $nodes = $this->tree->getMultipleNodes($paths); + + foreach ($nodes as $path => $node) { + + $propFind = new PropFind($path, $propertyNames); + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result[$path] = $propFind->getResultForMultiStatus(); + $result[$path]['href'] = $path; + + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result[$path]['href'] .= '/'; + } + } + + } + + return $result; + + } + + + /** + * Determines all properties for a node. + * + * This method tries to grab all properties for a node. This method is used + * internally getPropertiesForPath and a few others. + * + * It could be useful to call this, if you already have an instance of your + * target node and simply want to run through the system to get a correct + * list of properties. + * + * @param PropFind $propFind + * @param INode $node + * @return bool + */ + function getPropertiesByNode(PropFind $propFind, INode $node) { + + return $this->emit('propFind', [$propFind, $node]); + + } + + /** + * This method is invoked by sub-systems creating a new file. + * + * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin). + * It was important to get this done through a centralized function, + * allowing plugins to intercept this using the beforeCreateFile event. + * + * This method will return true if the file was actually created + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function createFile($uri, $data, &$etag = null) { + + list($dir, $name) = URLUtil::splitPath($uri); + + if (!$this->emit('beforeBind', [$uri])) return false; + + $parent = $this->tree->getNodeForPath($dir); + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Files can only be created as children of collections'); + } + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false; + + $etag = $parent->createFile($name, $data); + + if ($modified) $etag = null; + + $this->tree->markDirty($dir . '/' . $name); + + $this->emit('afterBind', [$uri]); + $this->emit('afterCreateFile', [$uri, $parent]); + + return true; + } + + /** + * This method is invoked by sub-systems updating a file. + * + * This method will return true if the file was actually updated + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function updateFile($uri, $data, &$etag = null) { + + $node = $this->tree->getNodeForPath($uri); + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false; + + $etag = $node->put($data); + if ($modified) $etag = null; + $this->emit('afterWriteContent', [$uri, $node]); + + return true; + } + + + + /** + * This method is invoked by sub-systems creating a new directory. + * + * @param string $uri + * @return void + */ + function createDirectory($uri) { + + $this->createCollection($uri, new MkCol(['{DAV:}collection'], [])); + + } + + /** + * Use this method to create a new collection + * + * @param string $uri The new uri + * @param MkCol $mkCol + * @return array|null + */ + function createCollection($uri, MkCol $mkCol) { + + list($parentUri, $newName) = URLUtil::splitPath($uri); + + // Making sure the parent exists + try { + $parent = $this->tree->getNodeForPath($parentUri); + + } catch (Exception\NotFound $e) { + throw new Exception\Conflict('Parent node does not exist'); + + } + + // Making sure the parent is a collection + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Parent node is not a collection'); + } + + // Making sure the child does not already exist + try { + $parent->getChild($newName); + + // If we got here.. it means there's already a node on that url, and we need to throw a 405 + throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); + + } catch (Exception\NotFound $e) { + // NotFound is the expected behavior. + } + + + if (!$this->emit('beforeBind', [$uri])) return; + + if ($parent instanceof IExtendedCollection) { + + /** + * If the parent is an instance of IExtendedCollection, it means that + * we can pass the MkCol object directly as it may be able to store + * properties immediately. + */ + $parent->createExtendedCollection($newName, $mkCol); + + } else { + + /** + * If the parent is a standard ICollection, it means only + * 'standard' collections can be created, so we should fail any + * MKCOL operation that carries extra resourcetypes. + */ + if (count($mkCol->getResourceType()) > 1) { + throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); + } + + $parent->createDirectory($newName); + + } + + // If there are any properties that have not been handled/stored, + // we ask the 'propPatch' event to handle them. This will allow for + // example the propertyStorage system to store properties upon MKCOL. + if ($mkCol->getRemainingMutations()) { + $this->emit('propPatch', [$uri, $mkCol]); + } + $success = $mkCol->commit(); + + if (!$success) { + $result = $mkCol->getResult(); + + $formattedResult = [ + 'href' => $uri, + ]; + + foreach ($result as $propertyName => $status) { + + if (!isset($formattedResult[$status])) { + $formattedResult[$status] = []; + } + $formattedResult[$status][$propertyName] = null; + + } + return $formattedResult; + } + + $this->tree->markDirty($parentUri); + $this->emit('afterBind', [$uri]); + + } + + /** + * This method updates a resource's properties + * + * The properties array must be a list of properties. Array-keys are + * property names in clarknotation, array-values are it's values. + * If a property must be deleted, the value should be null. + * + * Note that this request should either completely succeed, or + * completely fail. + * + * The response is an array with properties for keys, and http status codes + * as their values. + * + * @param string $path + * @param array $properties + * @return array + */ + function updateProperties($path, array $properties) { + + $propPatch = new PropPatch($properties); + $this->emit('propPatch', [$path, $propPatch]); + $propPatch->commit(); + + return $propPatch->getResult(); + + } + + /** + * This method checks the main HTTP preconditions. + * + * Currently these are: + * * If-Match + * * If-None-Match + * * If-Modified-Since + * * If-Unmodified-Since + * + * The method will return true if all preconditions are met + * The method will return false, or throw an exception if preconditions + * failed. If false is returned the operation should be aborted, and + * the appropriate HTTP response headers are already set. + * + * Normally this method will throw 412 Precondition Failed for failures + * related to If-None-Match, If-Match and If-Unmodified Since. It will + * set the status to 304 Not Modified for If-Modified_since. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function checkPreconditions(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = null; + $lastMod = null; + $etag = null; + + if ($ifMatch = $request->getHeader('If-Match')) { + + // If-Match contains an entity tag. Only if the entity-tag + // matches we are allowed to make the request succeed. + // If the entity-tag is '*' we are only allowed to make the + // request succeed if a resource exists at that url. + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match'); + } + + // Only need to check entity tags if they are not * + if ($ifMatch !== '*') { + + // There can be multiple ETags + $ifMatch = explode(',', $ifMatch); + $haveMatch = false; + foreach ($ifMatch as $ifMatchItem) { + + // Stripping any extra spaces + $ifMatchItem = trim($ifMatchItem, ' '); + + $etag = $node instanceof IFile ? $node->getETag() : null; + if ($etag === $ifMatchItem) { + $haveMatch = true; + } else { + // Evolution has a bug where it sometimes prepends the " + // with a \. This is our workaround. + if (str_replace('\\"', '"', $ifMatchItem) === $etag) { + $haveMatch = true; + } + } + + } + if (!$haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match'); + } + } + } + + if ($ifNoneMatch = $request->getHeader('If-None-Match')) { + + // The If-None-Match header contains an ETag. + // Only if the ETag does not match the current ETag, the request will succeed + // The header can also contain *, in which case the request + // will only succeed if the entity does not exist at all. + $nodeExists = true; + if (!$node) { + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $nodeExists = false; + } + } + if ($nodeExists) { + $haveMatch = false; + if ($ifNoneMatch === '*') $haveMatch = true; + else { + + // There might be multiple ETags + $ifNoneMatch = explode(',', $ifNoneMatch); + $etag = $node instanceof IFile ? $node->getETag() : null; + + foreach ($ifNoneMatch as $ifNoneMatchItem) { + + // Stripping any extra spaces + $ifNoneMatchItem = trim($ifNoneMatchItem, ' '); + + if ($etag === $ifNoneMatchItem) $haveMatch = true; + + } + + } + + if ($haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + if ($request->getMethod() === 'GET') { + $response->setStatus(304); + return false; + } else { + throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match'); + } + } + } + + } + + if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) { + + // The If-Modified-Since header contains a date. We + // will only return the entity if it has been changed since + // that date. If it hasn't been changed, we return a 304 + // header + // Note that this header only has to be checked if there was no If-None-Match header + // as per the HTTP spec. + $date = HTTP\Util::parseHTTPDate($ifModifiedSince); + + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod <= $date) { + $response->setStatus(304); + $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); + return false; + } + } + } + } + + if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) { + + // The If-Unmodified-Since will allow allow the request if the + // entity has not changed since the specified date. + $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); + + // We must only check the date if it's valid + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod > $date) { + throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since'); + } + } + } + + } + + // Now the hardest, the If: header. The If: header can contain multiple + // urls, ETags and so-called 'state tokens'. + // + // Examples of state tokens include lock-tokens (as defined in rfc4918) + // and sync-tokens (as defined in rfc6578). + // + // The only proper way to deal with these, is to emit events, that a + // Sync and Lock plugin can pick up. + $ifConditions = $this->getIfConditions($request); + + foreach ($ifConditions as $kk => $ifCondition) { + foreach ($ifCondition['tokens'] as $ii => $token) { + $ifConditions[$kk]['tokens'][$ii]['validToken'] = false; + } + } + + // Plugins are responsible for validating all the tokens. + // If a plugin deemed a token 'valid', it will set 'validToken' to + // true. + $this->emit('validateTokens', [$request, &$ifConditions]); + + // Now we're going to analyze the result. + + // Every ifCondition needs to validate to true, so we exit as soon as + // we have an invalid condition. + foreach ($ifConditions as $ifCondition) { + + $uri = $ifCondition['uri']; + $tokens = $ifCondition['tokens']; + + // We only need 1 valid token for the condition to succeed. + foreach ($tokens as $token) { + + $tokenValid = $token['validToken'] || !$token['token']; + + $etagValid = false; + if (!$token['etag']) { + $etagValid = true; + } + // Checking the ETag, only if the token was already deemed + // valid and there is one. + if ($token['etag'] && $tokenValid) { + + // The token was valid, and there was an ETag. We must + // grab the current ETag and check it. + $node = $this->tree->getNodeForPath($uri); + $etagValid = $node instanceof IFile && $node->getETag() == $token['etag']; + + } + + + if (($tokenValid && $etagValid) ^ $token['negate']) { + // Both were valid, so we can go to the next condition. + continue 2; + } + + + } + + // If we ended here, it means there was no valid ETag + token + // combination found for the current condition. This means we fail! + throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If'); + + } + + return true; + + } + + /** + * This method is created to extract information from the WebDAV HTTP 'If:' header + * + * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information + * The function will return an array, containing structs with the following keys + * + * * uri - the uri the condition applies to. + * * tokens - The lock token. another 2 dimensional array containing 3 elements + * + * Example 1: + * + * If: () + * + * Would result in: + * + * [ + * [ + * 'uri' => '/request/uri', + * 'tokens' => [ + * [ + * [ + * 'negate' => false, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => "" + * ] + * ] + * ], + * ] + * ] + * + * Example 2: + * + * If: (Not ["Im An ETag"]) (["Another ETag"]) (Not ["Path2 ETag"]) + * + * Would result in: + * + * [ + * [ + * 'uri' => 'path', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => '"Im An ETag"' + * ], + * [ + * 'negate' => false, + * 'token' => '', + * 'etag' => '"Another ETag"' + * ] + * ] + * ], + * ], + * [ + * 'uri' => 'path2', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => '', + * 'etag' => '"Path2 ETag"' + * ] + * ] + * ], + * ], + * ] + * + * @param RequestInterface $request + * @return array + */ + function getIfConditions(RequestInterface $request) { + + $header = $request->getHeader('If'); + if (!$header) return []; + + $matches = []; + + $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im'; + preg_match_all($regex, $header, $matches, PREG_SET_ORDER); + + $conditions = []; + + foreach ($matches as $match) { + + // If there was no uri specified in this match, and there were + // already conditions parsed, we add the condition to the list of + // conditions for the previous uri. + if (!$match['uri'] && count($conditions)) { + $conditions[count($conditions) - 1]['tokens'][] = [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ]; + } else { + + if (!$match['uri']) { + $realUri = $request->getPath(); + } else { + $realUri = $this->calculateUri($match['uri']); + } + + $conditions[] = [ + 'uri' => $realUri, + 'tokens' => [ + [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ] + ], + + ]; + } + + } + + return $conditions; + + } + + /** + * Returns an array with resourcetypes for a node. + * + * @param INode $node + * @return array + */ + function getResourceTypeForNode(INode $node) { + + $result = []; + foreach ($this->resourceTypeMapping as $className => $resourceType) { + if ($node instanceof $className) $result[] = $resourceType; + } + return $result; + + } + + // }}} + // {{{ XML Readers & Writers + + + /** + * Generates a WebDAV propfind response body based on a list of nodes. + * + * If 'strip404s' is set to true, all 404 responses will be removed. + * + * @param array|\Traversable $fileProperties The list with nodes + * @param bool $strip404s + * @return string + */ + function generateMultiStatus($fileProperties, $strip404s = false) { + + $w = $this->xml->getWriter(); + $w->openMemory(); + $w->contextUri = $this->baseUri; + $w->startDocument(); + + $w->startElement('{DAV:}multistatus'); + + foreach ($fileProperties as $entry) { + + $href = $entry['href']; + unset($entry['href']); + if ($strip404s) { + unset($entry[404]); + } + $response = new Xml\Element\Response( + ltrim($href, '/'), + $entry + ); + $w->write([ + 'name' => '{DAV:}response', + 'value' => $response + ]); + } + $w->endElement(); + + return $w->outputMemory(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/ServerPlugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/ServerPlugin.php new file mode 100644 index 000000000000..b2c468ab3871 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/ServerPlugin.php @@ -0,0 +1,110 @@ + $this->getPluginName(), + 'description' => null, + 'link' => null, + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php b/libs/composer/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php new file mode 100644 index 000000000000..034aefbdce6a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php @@ -0,0 +1,69 @@ +server = $server; + + $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource'; + + array_push( + $server->protectedProperties, + '{DAV:}share-mode' + ); + + $server->on('method:POST', [$this, 'httpPost']); + $server->on('propFind', [$this, 'propFind']); + $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('onBrowserPostAction', [$this, 'browserPostAction']); + + } + + /** + * Updates the list of sharees on a shared resource. + * + * The sharees array is a list of people that are to be added modified + * or removed in the list of shares. + * + * @param string $path + * @param Sharee[] $sharees + * @return void + */ + function shareResource($path, array $sharees) { + + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof ISharedNode) { + + throw new Forbidden('Sharing is not allowed on this node'); + + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}share'); + } + + foreach ($sharees as $sharee) { + // We're going to attempt to get a local principal uri for a share + // href by emitting the getPrincipalByUri event. + $principal = null; + $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]); + $sharee->principal = $principal; + } + $node->updateInvites($sharees); + + } + + /** + * This event is triggered when properties are requested for nodes. + * + * This allows us to inject any sharings-specific properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + if ($node instanceof ISharedNode) { + + $propFind->handle('{DAV:}share-access', function() use ($node) { + + return new Property\ShareAccess($node->getShareAccess()); + + }); + $propFind->handle('{DAV:}invite', function() use ($node) { + + return new Property\Invite($node->getInvites()); + + }); + $propFind->handle('{DAV:}share-resource-uri', function() use ($node) { + + return new Property\Href($node->getShareResourceUri()); + + }); + + } + + } + + /** + * We intercept this to handle POST requests on shared resources + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return null|bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $contentType = $request->getHeader('Content-Type'); + + // We're only interested in the davsharing content type. + if (strpos($contentType, 'application/davsharing+xml') === false) { + return; + } + + $message = $this->server->xml->parse( + $request->getBody(), + $request->getUrl(), + $documentType + ); + + switch ($documentType) { + + case '{DAV:}share-resource': + + $this->shareResource($path, $message->sharees); + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + default : + throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type'); + + } + + } + + /** + * This method is triggered whenever a subsystem reqeuests the privileges + * hat are supported on a particular node. + * + * We need to add a number of privileges for scheduling purposes. + * + * @param INode $node + * @param array $supportedPrivilegeSet + */ + function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) { + + if ($node instanceof ISharedNode) { + $supportedPrivilegeSet['{DAV:}share'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin implements WebDAV resource sharing', + 'link' => 'https://github.com/evert/webdav-sharing' + ]; + + } + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. + * + * @param INode $node + * @param string $output + * @param string $path + * @return bool|null + */ + function htmlActionsPanel(INode $node, &$output, $path) { + + if (!$node instanceof ISharedNode) { + return; + } + + $aclPlugin = $this->server->getPlugin('acl'); + if ($aclPlugin) { + if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) { + // Sharing is not permitted, we will not draw this interface. + return; + } + } + + $output .= '

+

Share this resource

+ +
+ +
+ + + '; + + } + + /** + * This method is triggered for POST actions generated by the browser + * plugin. + * + * @param string $path + * @param string $action + * @param array $postVars + */ + function browserPostAction($path, $action, $postVars) { + + if ($action !== 'share') { + return; + } + + if (empty($postVars['href'])) { + throw new BadRequest('The "href" POST parameter is required'); + } + if (empty($postVars['access'])) { + throw new BadRequest('The "access" POST parameter is required'); + } + + $accessMap = [ + 'readwrite' => self::ACCESS_READWRITE, + 'read' => self::ACCESS_READ, + 'no-access' => self::ACCESS_NOACCESS, + ]; + + if (!isset($accessMap[$postVars['access']])) { + throw new BadRequest('The "access" POST must be readwrite, read or no-access'); + } + $sharee = new Sharee([ + 'href' => $postVars['href'], + 'access' => $accessMap[$postVars['access']], + ]); + + $this->shareResource( + $path, + [$sharee] + ); + return false; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/SimpleCollection.php b/libs/composer/vendor/sabre/dav/lib/DAV/SimpleCollection.php new file mode 100644 index 000000000000..998cfcbff5fd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/SimpleCollection.php @@ -0,0 +1,107 @@ +name = $name; + foreach ($children as $child) { + + if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument'); + $this->addChild($child); + + } + + } + + /** + * Adds a new childnode to this collection + * + * @param INode $child + * @return void + */ + function addChild(INode $child) { + + $this->children[$child->getName()] = $child; + + } + + /** + * Returns the name of the collection + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns a child object, by its name. + * + * This method makes use of the getChildren method to grab all the child nodes, and compares the name. + * Generally its wise to override this, as this can usually be optimized + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws Exception\NotFound + * @return INode + */ + function getChild($name) { + + if (isset($this->children[$name])) return $this->children[$name]; + throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); + + } + + /** + * Returns a list of children for this collection + * + * @return INode[] + */ + function getChildren() { + + return array_values($this->children); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/SimpleFile.php b/libs/composer/vendor/sabre/dav/lib/DAV/SimpleFile.php new file mode 100644 index 000000000000..bcad786f34a2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/SimpleFile.php @@ -0,0 +1,121 @@ +name = $name; + $this->contents = $contents; + $this->mimeType = $mimeType; + + } + + /** + * Returns the node name for this file. + * + * This name is used to construct the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $this->contents; + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + function getSize() { + + return strlen($this->contents); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * @return string + */ + function getETag() { + + return '"' . sha1($this->contents) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * @return string + */ + function getContentType() { + + return $this->mimeType; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/StringUtil.php b/libs/composer/vendor/sabre/dav/lib/DAV/StringUtil.php new file mode 100644 index 000000000000..10eecebfd47f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/StringUtil.php @@ -0,0 +1,91 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => array( + * 'foo.php.bak', + * 'old.txt' + * ) + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Sync/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/Sync/Plugin.php new file mode 100644 index 000000000000..8e4b1aa6412d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Sync/Plugin.php @@ -0,0 +1,277 @@ +server = $server; + $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport'; + + $self = $this; + + $server->on('report', function($reportName, $dom, $uri) use ($self) { + + if ($reportName === '{DAV:}sync-collection') { + $this->server->transactionType = 'report-sync-collection'; + $self->syncCollection($uri, $dom); + return false; + } + + }); + + $server->on('propFind', [$this, 'propFind']); + $server->on('validateTokens', [$this, 'validateTokens']); + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof ISyncCollection && $node->getSyncToken()) { + return [ + '{DAV:}sync-collection', + ]; + } + + return []; + + } + + + /** + * This method handles the {DAV:}sync-collection HTTP REPORT. + * + * @param string $uri + * @param SyncCollectionReport $report + * @return void + */ + function syncCollection($uri, SyncCollectionReport $report) { + + // Getting the data + $node = $this->server->tree->getNodeForPath($uri); + if (!$node instanceof ISyncCollection) { + throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); + } + $token = $node->getSyncToken(); + if (!$token) { + throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); + } + + $syncToken = $report->syncToken; + if (!is_null($syncToken)) { + // Sync-token must start with our prefix + if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + } + + $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); + + } + $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit); + + if (is_null($changeInfo)) { + + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + + } + + // Encoding the response + $this->sendSyncCollectionResponse( + $changeInfo['syncToken'], + $uri, + $changeInfo['added'], + $changeInfo['modified'], + $changeInfo['deleted'], + $report->properties + ); + + } + + /** + * Sends the response to a sync-collection request. + * + * @param string $syncToken + * @param string $collectionUrl + * @param array $added + * @param array $modified + * @param array $deleted + * @param array $properties + * @return void + */ + protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { + + + $fullPaths = []; + + // Pre-fetching children, if this is possible. + foreach (array_merge($added, $modified) as $item) { + $fullPath = $collectionUrl . '/' . $item; + $fullPaths[] = $fullPath; + } + + $responses = []; + foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { + + // The 'Property_Response' class is responsible for generating a + // single {DAV:}response xml element. + $responses[] = new DAV\Xml\Element\Response($fullPath, $props); + + } + + + + // Deleted items also show up as 'responses'. They have no properties, + // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. + foreach ($deleted as $item) { + + $fullPath = $collectionUrl . '/' . $item; + $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); + + } + $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody( + $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri()) + ); + + } + + /** + * This method is triggered whenever properties are requested for a node. + * We intercept this to see if we must return a {DAV:}sync-token. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}sync-token', function() use ($node) { + if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) { + return; + } + return self::SYNCTOKEN_PREFIX . $token; + }); + + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * @param RequestInterface $request + * @param array $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Sync-tokens must always start with our designated prefix. + if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + continue; + } + + // Checking if the token is a match. + $node = $this->server->tree->getNodeForPath($condition['uri']); + + if ( + $node instanceof ISyncCollection && + $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX)) + ) { + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + } + + } + + } + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for WebDAV Collection Sync (rfc6578)', + 'link' => 'http://sabre.io/dav/sync/', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php b/libs/composer/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php new file mode 100644 index 000000000000..7b453d10536a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php @@ -0,0 +1,297 @@ +dataDir = $dataDir; + + } + + /** + * Initialize the plugin + * + * This is called automatically be the Server class after this plugin is + * added with Sabre\DAV\Server::addPlugin() + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + $server->on('beforeMethod', [$this, 'beforeMethod']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + + } + + /** + * This method is called before any HTTP method handler + * + * This method intercepts any GET, DELETE, PUT and PROPFIND calls to + * filenames that are known to match the 'temporary file' regex. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if (!$tempLocation = $this->isTempFile($request->getPath())) + return; + + switch ($request->getMethod()) { + case 'GET' : + return $this->httpGet($request, $response, $tempLocation); + case 'PUT' : + return $this->httpPut($request, $response, $tempLocation); + case 'PROPFIND' : + return $this->httpPropfind($request, $response, $tempLocation); + case 'DELETE' : + return $this->httpDelete($request, $response, $tempLocation); + } + return; + + } + + /** + * This method is invoked if some subsystem creates a new file. + * + * This is used to deal with HTTP LOCK requests which create a new + * file. + * + * @param string $uri + * @param resource $data + * @param ICollection $parent + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return bool + */ + function beforeCreateFile($uri, $data, ICollection $parent, $modified) { + + if ($tempPath = $this->isTempFile($uri)) { + + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp', 'true'); + file_put_contents($tempPath, $data); + return false; + } + return; + + } + + /** + * This method will check if the url matches the temporary file pattern + * if it does, it will return an path based on $this->dataDir for the + * temporary file storage. + * + * @param string $path + * @return bool|string + */ + protected function isTempFile($path) { + + // We're only interested in the basename. + list(, $tempPath) = URLUtil::splitPath($path); + + foreach ($this->temporaryFilePatterns as $tempFile) { + + if (preg_match($tempFile, $tempPath)) { + return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; + } + + } + + return false; + + } + + + /** + * This method handles the GET method for temporary files. + * If the file doesn't exist, it will return false which will kick in + * the regular system for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('Content-Type', 'application/octet-stream'); + $hR->setHeader('Content-Length', filesize($tempLocation)); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(200); + $hR->setBody(fopen($tempLocation, 'r')); + return false; + + } + + /** + * This method handles the PUT method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + $hR->setHeader('X-Sabre-Temp', 'true'); + + $newFile = !file_exists($tempLocation); + + if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { + throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); + } + + file_put_contents($tempLocation, $this->server->httpRequest->getBody()); + $hR->setStatus($newFile ? 201 : 200); + return false; + + } + + /** + * This method handles the DELETE method. + * + * If the file didn't exist, it will return false, which will make the + * standard HTTP DELETE handler kick in. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + unlink($tempLocation); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(204); + return false; + + } + + /** + * This method handles the PROPFIND method. + * + * It's a very lazy method, it won't bother checking the request body + * for which properties were requested, and just sends back a default + * set of properties. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(207); + $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $properties = [ + 'href' => $request->getPath(), + 200 => [ + '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), + '{DAV:}getcontentlength' => filesize($tempLocation), + '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), + '{' . Server::NS_SABREDAV . '}tempFile' => true, + + ], + ]; + + $data = $this->server->generateMultiStatus([$properties]); + $hR->setBody($data); + return false; + + } + + + /** + * This method returns the directory where the temporary files should be stored. + * + * @return string + */ + protected function getDataDir() + { + return $this->dataDir; + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Tree.php b/libs/composer/vendor/sabre/dav/lib/DAV/Tree.php new file mode 100644 index 000000000000..5d2792503f90 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Tree.php @@ -0,0 +1,340 @@ +rootNode = $rootNode; + + } + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @return INode + */ + function getNodeForPath($path) { + + $path = trim($path, '/'); + if (isset($this->cache[$path])) return $this->cache[$path]; + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + // Attempting to fetch its parent + list($parentName, $baseName) = URLUtil::splitPath($path); + + // If there was no parent, we must simply ask it from the root node. + if ($parentName === "") { + $node = $this->rootNode->getChild($baseName); + } else { + // Otherwise, we recursively grab the parent and ask him/her. + $parent = $this->getNodeForPath($parentName); + + if (!($parent instanceof ICollection)) + throw new Exception\NotFound('Could not find node at path: ' . $path); + + $node = $parent->getChild($baseName); + + } + + $this->cache[$path] = $node; + return $node; + + } + + /** + * This function allows you to check if a node exists. + * + * Implementors of this class should override this method to make + * it cheaper. + * + * @param string $path + * @return bool + */ + function nodeExists($path) { + + try { + + // The root always exists + if ($path === '') return true; + + list($parent, $base) = URLUtil::splitPath($path); + + $parentNode = $this->getNodeForPath($parent); + if (!$parentNode instanceof ICollection) return false; + return $parentNode->childExists($base); + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * Copies a file from path to another + * + * @param string $sourcePath The source location + * @param string $destinationPath The full destination path + * @return void + */ + function copy($sourcePath, $destinationPath) { + + $sourceNode = $this->getNodeForPath($sourcePath); + + // grab the dirname and basename components + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + $destinationParent = $this->getNodeForPath($destinationDir); + $this->copyNode($sourceNode, $destinationParent, $destinationName); + + $this->markDirty($destinationDir); + + } + + /** + * Moves a file from one location to another + * + * @param string $sourcePath The path to the file which should be moved + * @param string $destinationPath The full destination path, so not just the destination parent node + * @return int + */ + function move($sourcePath, $destinationPath) { + + list($sourceDir) = URLUtil::splitPath($sourcePath); + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + if ($sourceDir === $destinationDir) { + // If this is a 'local' rename, it means we can just trigger a rename. + $sourceNode = $this->getNodeForPath($sourcePath); + $sourceNode->setName($destinationName); + } else { + $newParentNode = $this->getNodeForPath($destinationDir); + $moveSuccess = false; + if ($newParentNode instanceof IMoveTarget) { + // The target collection may be able to handle the move + $sourceNode = $this->getNodeForPath($sourcePath); + $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode); + } + if (!$moveSuccess) { + $this->copy($sourcePath, $destinationPath); + $this->getNodeForPath($sourcePath)->delete(); + } + } + $this->markDirty($sourceDir); + $this->markDirty($destinationDir); + + } + + /** + * Deletes a node from the tree + * + * @param string $path + * @return void + */ + function delete($path) { + + $node = $this->getNodeForPath($path); + $node->delete(); + + list($parent) = URLUtil::splitPath($path); + $this->markDirty($parent); + + } + + /** + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array + */ + function getChildren($path) { + + $node = $this->getNodeForPath($path); + $children = $node->getChildren(); + $basePath = trim($path, '/'); + if ($basePath !== '') $basePath .= '/'; + + foreach ($children as $child) { + + $this->cache[$basePath . $child->getName()] = $child; + + } + return $children; + + } + + /** + * This method is called with every tree update + * + * Examples of tree updates are: + * * node deletions + * * node creations + * * copy + * * move + * * renaming nodes + * + * If Tree classes implement a form of caching, this will allow + * them to make sure caches will be expired. + * + * If a path is passed, it is assumed that the entire subtree is dirty + * + * @param string $path + * @return void + */ + function markDirty($path) { + + // We don't care enough about sub-paths + // flushing the entire cache + $path = trim($path, '/'); + foreach ($this->cache as $nodePath => $node) { + if ($path === '' || $nodePath == $path || strpos($nodePath, $path . '/') === 0) + unset($this->cache[$nodePath]); + + } + + } + + /** + * This method tells the tree system to pre-fetch and cache a list of + * children of a single parent. + * + * There are a bunch of operations in the WebDAV stack that request many + * children (based on uris), and sometimes fetching many at once can + * optimize this. + * + * This method returns an array with the found nodes. It's keys are the + * original paths. The result may be out of order. + * + * @param array $paths List of nodes that must be fetched. + * @return array + */ + function getMultipleNodes($paths) { + + // Finding common parents + $parents = []; + foreach ($paths as $path) { + list($parent, $node) = URLUtil::splitPath($path); + if (!isset($parents[$parent])) { + $parents[$parent] = [$node]; + } else { + $parents[$parent][] = $node; + } + } + + $result = []; + + foreach ($parents as $parent => $children) { + + $parentNode = $this->getNodeForPath($parent); + if ($parentNode instanceof IMultiGet) { + foreach ($parentNode->getMultipleChildren($children) as $childNode) { + $fullPath = $parent . '/' . $childNode->getName(); + $result[$fullPath] = $childNode; + $this->cache[$fullPath] = $childNode; + } + } else { + foreach ($children as $child) { + $fullPath = $parent . '/' . $child; + $result[$fullPath] = $this->getNodeForPath($fullPath); + } + } + + } + + return $result; + + } + + + /** + * copyNode + * + * @param INode $source + * @param ICollection $destinationParent + * @param string $destinationName + * @return void + */ + protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null) { + + if (!$destinationName) $destinationName = $source->getName(); + + if ($source instanceof IFile) { + + $data = $source->get(); + + // If the body was a string, we need to convert it to a stream + if (is_string($data)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $data); + rewind($stream); + $data = $stream; + } + $destinationParent->createFile($destinationName, $data); + $destination = $destinationParent->getChild($destinationName); + + } elseif ($source instanceof ICollection) { + + $destinationParent->createDirectory($destinationName); + + $destination = $destinationParent->getChild($destinationName); + foreach ($source->getChildren() as $child) { + + $this->copyNode($child, $destination); + + } + + } + if ($source instanceof IProperties && $destination instanceof IProperties) { + + $props = $source->getProperties([]); + $propPatch = new PropPatch($props); + $destination->propPatch($propPatch); + $propPatch->commit(); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/UUIDUtil.php b/libs/composer/vendor/sabre/dav/lib/DAV/UUIDUtil.php new file mode 100644 index 000000000000..177adafd3b8c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/UUIDUtil.php @@ -0,0 +1,64 @@ +value array. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Prop implements XmlDeserializable { + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT) { + + $clark = $reader->getClark(); + $values[$clark] = self::parseCurrentElement($reader)['value']; + + } else { + $reader->read(); + } + + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $values; + + } + + /** + * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement, + * but instead of creating deep xml array structures, it will turn any + * top-level element it doesn't recognize into either a string, or an + * XmlFragment class. + * + * This method returns arn array with 2 properties: + * * name - A clark-notation XML element name. + * * value - The parsed value. + * + * @param Reader $reader + * @return array + */ + private static function parseCurrentElement(Reader $reader) { + + $name = $reader->getClark(); + + if (array_key_exists($name, $reader->elementMap)) { + $deserializer = $reader->elementMap[$name]; + if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) { + $value = call_user_func([$deserializer, 'xmlDeserialize'], $reader); + } elseif (is_callable($deserializer)) { + $value = call_user_func($deserializer, $reader); + } else { + $type = gettype($deserializer); + if ($type === 'string') { + $type .= ' (' . $deserializer . ')'; + } elseif ($type === 'object') { + $type .= ' (' . get_class($deserializer) . ')'; + } + throw new \LogicException('Could not use this type as a deserializer: ' . $type); + } + } else { + $value = Complex::xmlDeserialize($reader); + } + + return [ + 'name' => $name, + 'value' => $value, + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php new file mode 100644 index 000000000000..ce97ae943661 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php @@ -0,0 +1,253 @@ +href = $href; + $this->responseProperties = $responseProperties; + $this->httpStatus = $httpStatus; + + } + + /** + * Returns the url + * + * @return string + */ + function getHref() { + + return $this->href; + + } + + /** + * Returns the httpStatus value + * + * @return string + */ + function getHttpStatus() { + + return $this->httpStatus; + + } + + /** + * Returns the property list + * + * @return array + */ + function getResponseProperties() { + + return $this->responseProperties; + + } + + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + if ($status = $this->getHTTPStatus()) { + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + } + $writer->writeElement('{DAV:}href', $writer->contextUri . \Sabre\HTTP\encodePath($this->getHref())); + + $empty = true; + + foreach ($this->getResponseProperties() as $status => $properties) { + + // Skipping empty lists + if (!$properties || (!ctype_digit($status) && !is_int($status))) { + continue; + } + $empty = false; + $writer->startElement('{DAV:}propstat'); + $writer->writeElement('{DAV:}prop', $properties); + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + $writer->endElement(); // {DAV:}propstat + + } + if ($empty) { + /* + * The WebDAV spec _requires_ at least one DAV:propstat to appear for + * every DAV:response. In some circumstances however, there are no + * properties to encode. + * + * In those cases we MUST specify at least one DAV:propstat anyway, with + * no properties. + */ + $writer->writeElement('{DAV:}propstat', [ + '{DAV:}prop' => [], + '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418] + ]); + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + + $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue'; + + // We are overriding the parser for {DAV:}prop. This deserializer is + // almost identical to the one for Sabre\Xml\Element\KeyValue. + // + // The difference is that if there are any child-elements inside of + // {DAV:}prop, that have no value, normally any deserializers are + // called. But we don't want this, because a singular element without + // child-elements implies 'no value' in {DAV:}prop, so we want to skip + // deserializers and just set null for those. + $reader->elementMap['{DAV:}prop'] = function(Reader $reader) { + + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + $values = []; + $reader->read(); + do { + if ($reader->nodeType === Reader::ELEMENT) { + $clark = $reader->getClark(); + + if ($reader->isEmptyElement) { + $values[$clark] = null; + $reader->next(); + } else { + $values[$clark] = $reader->parseCurrentElement()['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + $reader->read(); + return $values; + + }; + $elems = $reader->parseInnerTree(); + $reader->popContext(); + + $href = null; + $propertyLists = []; + $statusCode = null; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}href' : + $href = $elem['value']; + break; + case '{DAV:}propstat' : + $status = $elem['value']['{DAV:}status']; + list(, $status, ) = explode(' ', $status, 3); + $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : []; + if ($properties) $propertyLists[$status] = $properties; + break; + case '{DAV:}status' : + list(, $statusCode, ) = explode(' ', $elem['value'], 3); + break; + + } + + } + + return new self($href, $propertyLists, $statusCode); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php new file mode 100644 index 000000000000..e187bf11cb0a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php @@ -0,0 +1,199 @@ + $v) { + + if (property_exists($this, $k)) { + $this->$k = $v; + } else { + throw new \InvalidArgumentException('Unknown property: ' . $k); + } + + } + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + + $writer->write([ + new Href($this->href), + '{DAV:}prop' => $this->properties, + '{DAV:}share-access' => new ShareAccess($this->access), + ]); + switch ($this->inviteStatus) { + case Plugin::INVITE_NORESPONSE : + $writer->writeElement('{DAV:}invite-noresponse'); + break; + case Plugin::INVITE_ACCEPTED : + $writer->writeElement('{DAV:}invite-accepted'); + break; + case Plugin::INVITE_DECLINED : + $writer->writeElement('{DAV:}invite-declined'); + break; + case Plugin::INVITE_INVALID : + $writer->writeElement('{DAV:}invite-invalid'); + break; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + // Temporarily override configuration + $reader->pushContext(); + $reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess'; + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue'; + + $elems = Deserializer\keyValue($reader, 'DAV:'); + + // Restore previous configuration + $reader->popContext(); + + $sharee = new self(); + if (!isset($elems['href'])) { + throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element'); + } + $sharee->href = $elems['href']; + + if (isset($elems['prop'])) { + $sharee->properties = $elems['prop']; + } + if (isset($elems['comment'])) { + $sharee->comment = $elems['comment']; + } + if (!isset($elems['share-access'])) { + throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element'); + } + $sharee->access = $elems['share-access']->getValue(); + return $sharee; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php new file mode 100644 index 000000000000..258806e4a59c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php @@ -0,0 +1,89 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $xml = $reader->readInnerXml(); + + if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) { + // Easy! + $reader->next(); + return null; + } + // Now we have a copy of the inner xml, we need to traverse it to get + // all the strings. If there's no non-string data, we just return the + // string, otherwise we return an instance of this class. + $reader->read(); + + $nonText = false; + $text = ''; + + while (true) { + + switch ($reader->nodeType) { + case Reader::ELEMENT : + $nonText = true; + $reader->next(); + continue 2; + case Reader::TEXT : + case Reader::CDATA : + $text .= $reader->value; + break; + case Reader::END_ELEMENT : + break 2; + } + $reader->read(); + + } + + // Make sure we advance the cursor one step further. + $reader->read(); + + if ($nonText) { + $new = new self($xml); + return $new; + } else { + return $text; + } + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php new file mode 100644 index 000000000000..101a0f0c91a4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php @@ -0,0 +1,110 @@ +time = clone $time; + } else { + $this->time = new DateTime('@' . $time); + } + + // Setting timezone to UTC + $this->time->setTimezone(new DateTimeZone('UTC')); + + } + + /** + * getTime + * + * @return DateTime + */ + function getTime() { + + return $this->time; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->write( + HTTP\Util::toHTTPDate($this->time) + ); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + return + new self(new DateTime($reader->parseInnerTree())); + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php new file mode 100644 index 000000000000..6c4f11b87ad4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php @@ -0,0 +1,165 @@ +hrefs = $hrefs; + + } + + /** + * Returns the first Href. + * + * @return string + */ + function getHref() { + + return $this->hrefs[0]; + + } + + /** + * Returns the hrefs as an array + * + * @return array + */ + function getHrefs() { + + return $this->hrefs; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getHrefs() as $href) { + $href = Uri\resolve($writer->contextUri, $href); + $writer->writeElement('{DAV:}href', $href); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + $links = []; + foreach ($this->getHrefs() as $href) { + $links[] = $html->link($href); + } + return implode('
', $links); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $hrefs = []; + foreach ((array)$reader->parseInnerTree() as $elem) { + if ($elem['name'] !== '{DAV:}href') + continue; + + $hrefs[] = $elem['value']; + + } + if ($hrefs) { + return new self($hrefs, false); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php new file mode 100644 index 000000000000..6adad3650407 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php @@ -0,0 +1,70 @@ +sharees = $sharees; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->sharees as $sharee) { + $writer->writeElement('{DAV:}sharee', $sharee); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php new file mode 100644 index 000000000000..00d2fa708d16 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php @@ -0,0 +1,48 @@ +locks = $locks; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->locks as $lock) { + + $writer->startElement('{DAV:}activelock'); + + $writer->startElement('{DAV:}lockscope'); + if ($lock->scope === LockInfo::SHARED) { + $writer->writeElement('{DAV:}shared'); + } else { + $writer->writeElement('{DAV:}exclusive'); + } + + $writer->endElement(); // {DAV:}lockscope + + $writer->startElement('{DAV:}locktype'); + $writer->writeElement('{DAV:}write'); + $writer->endElement(); // {DAV:}locktype + + if (!self::$hideLockRoot) { + $writer->startElement('{DAV:}lockroot'); + $writer->writeElement('{DAV:}href', $writer->contextUri . $lock->uri); + $writer->endElement(); // {DAV:}lockroot + } + $writer->writeElement('{DAV:}depth', ($lock->depth == DAV\Server::DEPTH_INFINITY ? 'infinity' : $lock->depth)); + $writer->writeElement('{DAV:}timeout', 'Second-' . $lock->timeout); + + $writer->startElement('{DAV:}locktoken'); + $writer->writeElement('{DAV:}href', 'opaquelocktoken:' . $lock->token); + $writer->endElement(); // {DAV:}locktoken + + $writer->writeElement('{DAV:}owner', new XmlFragment($lock->owner)); + $writer->endElement(); // {DAV:}activelock + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php new file mode 100644 index 000000000000..ce640ff32dbe --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php @@ -0,0 +1,128 @@ +value; + + } + + /** + * Checks if the principal contains a certain value + * + * @param string $type + * @return bool + */ + function is($type) { + + return in_array($type, $this->value); + + } + + /** + * Adds a resourcetype value to this property + * + * @param string $type + * @return void + */ + function add($type) { + + $this->value[] = $type; + $this->value = array_unique($this->value); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + return + new self(parent::xmlDeserialize($reader)); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php new file mode 100644 index 000000000000..939062f76fd7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php @@ -0,0 +1,143 @@ +value = $shareAccess; + + } + + /** + * Returns the current value. + * + * @return int + */ + function getValue() { + + return $this->value; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->value) { + + case SharingPlugin::ACCESS_NOTSHARED : + $writer->writeElement('{DAV:}not-shared'); + break; + case SharingPlugin::ACCESS_SHAREDOWNER : + $writer->writeElement('{DAV:}shared-owner'); + break; + case SharingPlugin::ACCESS_READ : + $writer->writeElement('{DAV:}read'); + break; + case SharingPlugin::ACCESS_READWRITE : + $writer->writeElement('{DAV:}read-write'); + break; + case SharingPlugin::ACCESS_NOACCESS : + $writer->writeElement('{DAV:}no-access'); + break; + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + foreach ($elems as $elem) { + switch ($elem['name']) { + case '{DAV:}not-shared' : + return new self(SharingPlugin::ACCESS_NOTSHARED); + case '{DAV:}shared-owner' : + return new self(SharingPlugin::ACCESS_SHAREDOWNER); + case '{DAV:}read' : + return new self(SharingPlugin::ACCESS_READ); + case '{DAV:}read-write' : + return new self(SharingPlugin::ACCESS_READWRITE); + case '{DAV:}no-access' : + return new self(SharingPlugin::ACCESS_NOACCESS); + } + } + throw new BadRequest('Invalid value for {DAV:}share-access element'); + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php new file mode 100644 index 000000000000..677fdde4bdff --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php @@ -0,0 +1,54 @@ +writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}exclusive' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + $writer->writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}shared' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php new file mode 100644 index 000000000000..1a3878ef71f1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php @@ -0,0 +1,121 @@ +methods = $methods; + + } + + /** + * Returns the list of supported http methods. + * + * @return string[] + */ + function getValue() { + + return $this->methods; + + } + + /** + * Returns true or false if the property contains a specific method. + * + * @param string $methodName + * @return bool + */ + function has($methodName) { + + return in_array( + $methodName, + $this->methods + ); + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getValue() as $val) { + $writer->startElement('{DAV:}supported-method'); + $writer->writeAttribute('name', $val); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'h'], $this->getValue()) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php new file mode 100644 index 000000000000..96383ec96c89 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php @@ -0,0 +1,154 @@ +addReport($reports); + + } + + /** + * Adds a report to this property + * + * The report must be a string in clark-notation. + * Multiple reports can be specified as an array. + * + * @param mixed $report + * @return void + */ + function addReport($report) { + + $report = (array)$report; + + foreach ($report as $r) { + + if (!preg_match('/^{([^}]*)}(.*)$/', $r)) + throw new DAV\Exception('Reportname must be in clark-notation'); + + $this->reports[] = $r; + + } + + } + + /** + * Returns the list of supported reports + * + * @return string[] + */ + function getValue() { + + return $this->reports; + + } + + /** + * Returns true or false if the property contains a specific report. + * + * @param string $reportName + * @return bool + */ + function has($reportName) { + + return in_array( + $reportName, + $this->reports + ); + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getValue() as $val) { + $writer->startElement('{DAV:}supported-report'); + $writer->startElement('{DAV:}report'); + $writer->writeElement($val); + $writer->endElement(); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php new file mode 100644 index 000000000000..c315a9a4502f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php @@ -0,0 +1,81 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}owner'] = 'Sabre\\Xml\\Element\\XmlFragment'; + + $values = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $new = new self(); + $new->owner = !empty($values['{DAV:}owner']) ? $values['{DAV:}owner']->getXml() : null; + $new->scope = LockInfo::SHARED; + + if (isset($values['{DAV:}lockscope'])) { + foreach ($values['{DAV:}lockscope'] as $elem) { + if ($elem['name'] === '{DAV:}exclusive') $new->scope = LockInfo::EXCLUSIVE; + } + } + return $new; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php new file mode 100644 index 000000000000..9490bf58cf2e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php @@ -0,0 +1,82 @@ +value array with properties that are supposed to get set + * during creation of the new collection. + * + * @return array + */ + function getProperties() { + + return $this->properties; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + } + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php new file mode 100644 index 000000000000..f1b5b6fdc6f0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php @@ -0,0 +1,83 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + + foreach (KeyValue::xmlDeserialize($reader) as $k => $v) { + + switch ($k) { + case '{DAV:}prop' : + $self->properties = $v; + break; + case '{DAV:}allprop' : + $self->allProp = true; + + } + + } + + $reader->popContext(); + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php new file mode 100644 index 000000000000..821b9e047d48 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php @@ -0,0 +1,118 @@ +properties as $propertyName => $propertyValue) { + + if (is_null($propertyValue)) { + $writer->startElement("{DAV:}remove"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } else { + $writer->startElement("{DAV:}set"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + if ($elem['name'] === '{DAV:}remove') { + + // Ensuring there are no values. + foreach ($elem['value']['{DAV:}prop'] as $remove => $value) { + $self->properties[$remove] = null; + } + + } + } + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php new file mode 100644 index 000000000000..526a4eb6ff63 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php @@ -0,0 +1,81 @@ +sharees = $sharees; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{DAV:}sharee' => 'Sabre\DAV\Xml\Element\Sharee', + '{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess', + '{DAV:}prop' => 'Sabre\Xml\Deserializer\keyValue', + ]); + + $sharees = []; + + foreach ($elems as $elem) { + if ($elem['name'] !== '{DAV:}sharee') continue; + $sharees[] = $elem['value']; + + } + + return new self($sharees); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php new file mode 100644 index 000000000000..830293a01784 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php @@ -0,0 +1,122 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $reader->pushContext(); + + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + $elems = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $required = [ + '{DAV:}sync-token', + '{DAV:}prop', + ]; + + foreach ($required as $elem) { + if (!array_key_exists($elem, $elems)) { + throw new BadRequest('The ' . $elem . ' element in the {DAV:}sync-collection report is required'); + } + } + + + $self->properties = $elems['{DAV:}prop']; + $self->syncToken = $elems['{DAV:}sync-token']; + + if (isset($elems['{DAV:}limit'])) { + $nresults = null; + foreach ($elems['{DAV:}limit'] as $child) { + if ($child['name'] === '{DAV:}nresults') { + $nresults = (int)$child['value']; + } + } + $self->limit = $nresults; + } + + if (isset($elems['{DAV:}sync-level'])) { + + $value = $elems['{DAV:}sync-level']; + if ($value === 'infinity') { + $value = \Sabre\DAV\Server::DEPTH_INFINITY; + } + $self->syncLevel = $value; + + } + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php new file mode 100644 index 000000000000..cf5a0453b4ac --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php @@ -0,0 +1,142 @@ +responses = $responses; + $this->syncToken = $syncToken; + + } + + /** + * Returns the response list. + * + * @return \Sabre\DAV\Xml\Element\Response[] + */ + function getResponses() { + + return $this->responses; + + } + + /** + * Returns the sync-token, if available. + * + * @return string|null + */ + function getSyncToken() { + + return $this->syncToken; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getResponses() as $response) { + $writer->writeElement('{DAV:}response', $response); + } + if ($syncToken = $this->getSyncToken()) { + $writer->writeElement('{DAV:}sync-token', $syncToken); + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\\DAV\\Xml\\Element\\Prop'; + $elements = $reader->parseInnerTree($elementMap); + + $responses = []; + $syncToken = null; + + if ($elements) foreach ($elements as $elem) { + if ($elem['name'] === '{DAV:}response') { + $responses[] = $elem['value']; + } + if ($elem['name'] === '{DAV:}sync-token') { + $syncToken = $elem['value']; + } + } + + return new self($responses, $syncToken); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Service.php b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Service.php new file mode 100644 index 000000000000..f41ed984ad9d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAV/Xml/Service.php @@ -0,0 +1,47 @@ + 'Sabre\\DAV\\Xml\\Response\\MultiStatus', + '{DAV:}response' => 'Sabre\\DAV\\Xml\\Element\\Response', + + // Requests + '{DAV:}propfind' => 'Sabre\\DAV\\Xml\\Request\\PropFind', + '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch', + '{DAV:}mkcol' => 'Sabre\\DAV\\Xml\\Request\\MkCol', + + // Properties + '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType', + + ]; + + /** + * This is a default list of namespaces. + * + * If you are defining your own custom namespace, add it here to reduce + * bandwidth and improve legibility of xml bodies. + * + * @var array + */ + public $namespaceMap = [ + 'DAV:' => 'd', + 'http://sabredav.org/ns' => 's', + ]; + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/ACLTrait.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/ACLTrait.php new file mode 100644 index 000000000000..602654a2ed3f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/ACLTrait.php @@ -0,0 +1,100 @@ + '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node'); + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php new file mode 100644 index 000000000000..9d2026380c51 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php @@ -0,0 +1,181 @@ +principalPrefix = $principalPrefix; + $this->principalBackend = $principalBackend; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return IPrincipal + */ + abstract function getChildForPrincipal(array $principalInfo); + + /** + * Returns the name of this collection. + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->principalPrefix); + return $name; + + } + + /** + * Return the list of users + * + * @return array + */ + function getChildren() { + + if ($this->disableListing) + throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled'); + + $children = []; + foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) { + + $children[] = $this->getChildForPrincipal($principalInfo); + + + } + return $children; + + } + + /** + * Returns a child object, by its name. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/' . $name); + if (!$principalInfo) throw new DAV\Exception\NotFound('Principal with name ' . $name . ' not found'); + return $this->getChildForPrincipal($principalInfo); + + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. + * + * This method should simply return a list of 'child names', which may be + * used to call $this->getChild in the future. + * + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals(array $searchProperties, $test = 'allof') { + + $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test); + $r = []; + + foreach ($result as $row) { + list(, $r[]) = URLUtil::splitPath($row); + } + + return $r; + + } + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @return string + */ + function findByUri($uri) { + + return $this->principalBackend->findByUri($uri, $this->principalPrefix); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php new file mode 100644 index 000000000000..22450b4a6812 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:no-ace-conflict'); + $errorNode->appendChild($np); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php new file mode 100644 index 000000000000..5624fd22f232 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php @@ -0,0 +1,82 @@ +uri = $uri; + $this->privileges = $privileges; + + parent::__construct('User did not have the required privileges (' . implode(',', $privileges) . ') for path "' . $uri . '"'); + + } + + /** + * Adds in extra information in the xml response. + * + * This method adds the {DAV:}need-privileges element as defined in rfc3744 + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $doc = $errorNode->ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:need-privileges'); + $errorNode->appendChild($np); + + foreach ($this->privileges as $privilege) { + + $resource = $doc->createElementNS('DAV:', 'd:resource'); + $np->appendChild($resource); + + $resource->appendChild($doc->createElementNS('DAV:', 'd:href', $server->getBaseUri() . $this->uri)); + + $priv = $doc->createElementNS('DAV:', 'd:privilege'); + $resource->appendChild($priv); + + preg_match('/^{([^}]*)}(.*)$/', $privilege, $privilegeParts); + $priv->appendChild($doc->createElementNS($privilegeParts[1], 'd:' . $privilegeParts[2])); + + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php new file mode 100644 index 000000000000..a2363b174bff --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:no-abstract'); + $errorNode->appendChild($np); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php new file mode 100644 index 000000000000..d7ae188ae7e3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:recognized-principal'); + $errorNode->appendChild($np); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php new file mode 100644 index 000000000000..73b81190dd2a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:not-supported-privilege'); + $errorNode->appendChild($np); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/Collection.php new file mode 100644 index 000000000000..b4fe7a1b0bef --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/Collection.php @@ -0,0 +1,111 @@ +acl = $acl; + $this->owner = $owner; + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws NotFound + * @return \Sabre\DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new NotFound('File could not be located'); + if ($name == '.' || $name == '..') throw new Forbidden('Permission denied to . and ..'); + + if (is_dir($path)) { + + return new self($path, $this->acl, $this->owner); + + } else { + + return new File($path, $this->acl, $this->owner); + + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->owner; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return $this->acl; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/File.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/File.php new file mode 100644 index 000000000000..aaf2ae148a7a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/File.php @@ -0,0 +1,80 @@ +acl = $acl; + $this->owner = $owner; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->owner; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return $this->acl; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php new file mode 100644 index 000000000000..201235e5a0f0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php @@ -0,0 +1,128 @@ +storagePath = $storagePath; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->collectionName; + + } + + /** + * Returns a principals' collection of files. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return \Sabre\DAV\INode + */ + function getChildForPrincipal(array $principalInfo) { + + $owner = $principalInfo['uri']; + $acl = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ]; + + list(, $principalBaseName) = Uri\split($owner); + + $path = $this->storagePath . '/' . $principalBaseName; + + if (!is_dir($path)) { + mkdir($path, 0777, true); + } + return new Collection( + $path, + $acl, + $owner + ); + + } + + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/IACL.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/IACL.php new file mode 100644 index 000000000000..f7a138665a5b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/IACL.php @@ -0,0 +1,74 @@ +getChild in the future. + * + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals(array $searchProperties, $test = 'allof'); + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @return string + */ + function findByUri($uri); + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Plugin.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Plugin.php new file mode 100644 index 000000000000..a2aa118d70bd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Plugin.php @@ -0,0 +1,1636 @@ + 'Display name', + '{http://sabredav.org/ns}email-address' => 'Email address', + ]; + + /** + * Any principal uri's added here, will automatically be added to the list + * of ACL's. They will effectively receive {DAV:}all privileges, as a + * protected privilege. + * + * @var array + */ + public $adminPrincipals = []; + + /** + * The ACL plugin allows privileges to be assigned to users that are not + * logged in. To facilitate that, it modifies the auth plugin's behavior + * to only require login when a privileged operation was denied. + * + * Unauthenticated access can be considered a security concern, so it's + * possible to turn this feature off to harden the server's security. + * + * @var bool + */ + public $allowUnauthenticatedAccess = true; + + /** + * Returns a list of features added by this plugin. + * + * This list is used in the response of a HTTP OPTIONS request. + * + * @return array + */ + function getFeatures() { + + return ['access-control', 'calendarserver-principal-property-search']; + + } + + /** + * Returns a list of available methods for a given url + * + * @param string $uri + * @return array + */ + function getMethods($uri) { + + return ['ACL']; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'acl'; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + return [ + '{DAV:}expand-property', + '{DAV:}principal-match', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set', + ]; + + } + + + /** + * Checks if the current user has the specified privilege(s). + * + * You can specify a single privilege, or a list of privileges. + * This method will throw an exception if the privilege is not available + * and return true otherwise. + * + * @param string $uri + * @param array|string $privileges + * @param int $recursion + * @param bool $throwExceptions if set to false, this method won't throw exceptions. + * @throws NeedPrivileges + * @throws NotAuthenticated + * @return bool + */ + function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { + + if (!is_array($privileges)) $privileges = [$privileges]; + + $acl = $this->getCurrentUserPrivilegeSet($uri); + + $failed = []; + foreach ($privileges as $priv) { + + if (!in_array($priv, $acl)) { + $failed[] = $priv; + } + + } + + if ($failed) { + if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) { + // We are not authenticated. Kicking in the Auth plugin. + $authPlugin = $this->server->getPlugin('auth'); + $reasons = $authPlugin->getLoginFailedReasons(); + $authPlugin->challenge( + $this->server->httpRequest, + $this->server->httpResponse + ); + throw new notAuthenticated(implode(', ', $reasons) . '. Login was needed for privilege: ' . implode(', ', $failed) . ' on ' . $uri); + } + if ($throwExceptions) { + + throw new NeedPrivileges($uri, $failed); + } else { + return false; + } + } + return true; + + } + + /** + * Returns the standard users' principal. + * + * This is one authoritative principal url for the current user. + * This method will return null if the user wasn't logged in. + * + * @return string|null + */ + function getCurrentUserPrincipal() { + + /** @var $authPlugin \Sabre\DAV\Auth\Plugin */ + $authPlugin = $this->server->getPlugin('auth'); + if (!$authPlugin) { + return null; + } + return $authPlugin->getCurrentPrincipal(); + + } + + + /** + * Returns a list of principals that's associated to the current + * user, either directly or through group membership. + * + * @return array + */ + function getCurrentUserPrincipals() { + + $currentUser = $this->getCurrentUserPrincipal(); + + if (is_null($currentUser)) return []; + + return array_merge( + [$currentUser], + $this->getPrincipalMembership($currentUser) + ); + + } + + /** + * Sets the default ACL rules. + * + * These rules are used for all nodes that don't implement the IACL interface. + * + * @param array $acl + * @return void + */ + function setDefaultAcl(array $acl) { + + $this->defaultAcl = $acl; + + } + + /** + * Returns the default ACL rules. + * + * These rules are used for all nodes that don't implement the IACL interface. + * + * @return array + */ + function getDefaultAcl() { + + return $this->defaultAcl; + + } + + /** + * The default ACL rules. + * + * These rules are used for nodes that don't implement IACL. These default + * set of rules allow anyone to do anything, as long as they are + * authenticated. + * + * @var array + */ + protected $defaultAcl = [ + [ + 'principal' => '{DAV:}authenticated', + 'protected' => true, + 'privilege' => '{DAV:}all', + ], + ]; + + /** + * This array holds a cache for all the principals that are associated with + * a single principal. + * + * @var array + */ + protected $principalMembershipCache = []; + + + /** + * Returns all the principal groups the specified principal is a member of. + * + * @param string $mainPrincipal + * @return array + */ + function getPrincipalMembership($mainPrincipal) { + + // First check our cache + if (isset($this->principalMembershipCache[$mainPrincipal])) { + return $this->principalMembershipCache[$mainPrincipal]; + } + + $check = [$mainPrincipal]; + $principals = []; + + while (count($check)) { + + $principal = array_shift($check); + + $node = $this->server->tree->getNodeForPath($principal); + if ($node instanceof IPrincipal) { + foreach ($node->getGroupMembership() as $groupMember) { + + if (!in_array($groupMember, $principals)) { + + $check[] = $groupMember; + $principals[] = $groupMember; + + } + + } + + } + + } + + // Store the result in the cache + $this->principalMembershipCache[$mainPrincipal] = $principals; + + return $principals; + + } + + /** + * Find out of a principal equals another principal. + * + * This is a quick way to find out whether a principal URI is part of a + * group, or any subgroups. + * + * The first argument is the principal URI you want to check against. For + * example the principal group, and the second argument is the principal of + * which you want to find out of it is the same as the first principal, or + * in a member of the first principal's group or subgroups. + * + * So the arguments are not interchangeable. If principal A is in group B, + * passing 'B', 'A' will yield true, but 'A', 'B' is false. + * + * If the second argument is not passed, we will use the current user + * principal. + * + * @param string $checkPrincipal + * @param string $currentPrincipal + * @return bool + */ + function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null) { + + if (is_null($currentPrincipal)) { + $currentPrincipal = $this->getCurrentUserPrincipal(); + } + if ($currentPrincipal === $checkPrincipal) { + return true; + } + return in_array( + $checkPrincipal, + $this->getPrincipalMembership($currentPrincipal) + ); + + } + + + /** + * Returns a tree of supported privileges for a resource. + * + * The returned array structure should be in this form: + * + * [ + * [ + * 'privilege' => '{DAV:}read', + * 'abstract' => false, + * 'aggregates' => [] + * ] + * ] + * + * Privileges can be nested using "aggregates". Doing so means that + * if you assign someone the aggregating privilege, all the + * sub-privileges will automatically be granted. + * + * Marking a privilege as abstract means that the privilege cannot be + * directly assigned, but must be assigned via the parent privilege. + * + * So a more complex version might look like this: + * + * [ + * [ + * 'privilege' => '{DAV:}read', + * 'abstract' => false, + * 'aggregates' => [ + * [ + * 'privilege' => '{DAV:}read-acl', + * 'abstract' => false, + * 'aggregates' => [], + * ] + * ] + * ] + * ] + * + * @param string|INode $node + * @return array + */ + function getSupportedPrivilegeSet($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + $supportedPrivileges = null; + if ($node instanceof IACL) { + $supportedPrivileges = $node->getSupportedPrivilegeSet(); + } + + if (is_null($supportedPrivileges)) { + + // Default + $supportedPrivileges = [ + '{DAV:}read' => [ + 'abstract' => false, + 'aggregates' => [ + '{DAV:}read-acl' => [ + 'abstract' => false, + 'aggregates' => [], + ], + '{DAV:}read-current-user-privilege-set' => [ + 'abstract' => false, + 'aggregates' => [], + ], + ], + ], + '{DAV:}write' => [ + 'abstract' => false, + 'aggregates' => [ + '{DAV:}write-properties' => [ + 'abstract' => false, + 'aggregates' => [], + ], + '{DAV:}write-content' => [ + 'abstract' => false, + 'aggregates' => [], + ], + '{DAV:}unlock' => [ + 'abstract' => false, + 'aggregates' => [], + ], + ], + ], + ]; + if ($node instanceof DAV\ICollection) { + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + if ($node instanceof IACL) { + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + + } + + $this->server->emit( + 'getSupportedPrivilegeSet', + [$node, &$supportedPrivileges] + ); + + return $supportedPrivileges; + + } + + /** + * Returns the supported privilege set as a flat list + * + * This is much easier to parse. + * + * The returned list will be index by privilege name. + * The value is a struct containing the following properties: + * - aggregates + * - abstract + * - concrete + * + * @param string|INode $node + * @return array + */ + final function getFlatPrivilegeSet($node) { + + $privs = [ + 'abstract' => false, + 'aggregates' => $this->getSupportedPrivilegeSet($node) + ]; + + $fpsTraverse = null; + $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) { + + $myPriv = [ + 'privilege' => $privName, + 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'], + 'aggregates' => [], + 'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName, + ]; + + if (isset($privInfo['aggregates'])) { + + foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) { + + $myPriv['aggregates'][] = $subPrivName; + + } + + } + + $flat[$privName] = $myPriv; + + if (isset($privInfo['aggregates'])) { + + foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) { + + $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat); + + } + + } + + }; + + $flat = []; + $fpsTraverse('{DAV:}all', $privs, null, $flat); + + return $flat; + + } + + /** + * Returns the full ACL list. + * + * Either a uri or a INode may be passed. + * + * null will be returned if the node doesn't support ACLs. + * + * @param string|DAV\INode $node + * @return array + */ + function getAcl($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + if (!$node instanceof IACL) { + return $this->getDefaultAcl(); + } + $acl = $node->getACL(); + foreach ($this->adminPrincipals as $adminPrincipal) { + $acl[] = [ + 'principal' => $adminPrincipal, + 'privilege' => '{DAV:}all', + 'protected' => true, + ]; + } + return $acl; + + } + + /** + * Returns a list of privileges the current user has + * on a particular node. + * + * Either a uri or a DAV\INode may be passed. + * + * null will be returned if the node doesn't support ACLs. + * + * @param string|DAV\INode $node + * @return array + */ + function getCurrentUserPrivilegeSet($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + $acl = $this->getACL($node); + + $collected = []; + + $isAuthenticated = $this->getCurrentUserPrincipal() !== null; + + foreach ($acl as $ace) { + + $principal = $ace['principal']; + + switch ($principal) { + + case '{DAV:}owner' : + $owner = $node->getOwner(); + if ($owner && $this->principalMatchesPrincipal($owner)) { + $collected[] = $ace; + } + break; + + + // 'all' matches for every user + case '{DAV:}all' : + $collected[] = $ace; + break; + + case '{DAV:}authenticated' : + // Authenticated users only + if ($isAuthenticated) { + $collected[] = $ace; + } + break; + + case '{DAV:}unauthenticated' : + // Unauthenticated users only + if (!$isAuthenticated) { + $collected[] = $ace; + } + break; + + default : + if ($this->principalMatchesPrincipal($ace['principal'])) { + $collected[] = $ace; + } + break; + + } + + + } + + // Now we deduct all aggregated privileges. + $flat = $this->getFlatPrivilegeSet($node); + + $collected2 = []; + while (count($collected)) { + + $current = array_pop($collected); + $collected2[] = $current['privilege']; + + if (!isset($flat[$current['privilege']])) { + // Ignoring privileges that are not in the supported-privileges list. + $this->server->getLogger()->debug('A node has the "' . $current['privilege'] . '" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.'); + continue; + } + foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) { + $collected2[] = $subPriv; + $collected[] = $flat[$subPriv]; + } + + } + + return array_values(array_unique($collected2)); + + } + + + /** + * Returns a principal based on its uri. + * + * Returns null if the principal could not be found. + * + * @param string $uri + * @return null|string + */ + function getPrincipalByUri($uri) { + + $result = null; + $collections = $this->principalCollectionSet; + foreach ($collections as $collection) { + + try { + $principalCollection = $this->server->tree->getNodeForPath($collection); + } catch (NotFound $e) { + // Ignore and move on + continue; + } + + if (!$principalCollection instanceof IPrincipalCollection) { + // Not a principal collection, we're simply going to ignore + // this. + continue; + } + + $result = $principalCollection->findByUri($uri); + if ($result) { + return $result; + } + + } + + } + + /** + * Principal property search + * + * This method can search for principals matching certain values in + * properties. + * + * This method will return a list of properties for the matched properties. + * + * @param array $searchProperties The properties to search on. This is a + * key-value list. The keys are property + * names, and the values the strings to + * match them on. + * @param array $requestedProperties This is the list of properties to + * return for every match. + * @param string $collectionUri The principal collection to search on. + * If this is ommitted, the standard + * principal collection-set will be used. + * @param string $test "allof" to use AND to search the + * properties. 'anyof' for OR. + * @return array This method returns an array structure similar to + * Sabre\DAV\Server::getPropertiesForPath. Returned + * properties are index by a HTTP status code. + */ + function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof') { + + if (!is_null($collectionUri)) { + $uris = [$collectionUri]; + } else { + $uris = $this->principalCollectionSet; + } + + $lookupResults = []; + foreach ($uris as $uri) { + + $principalCollection = $this->server->tree->getNodeForPath($uri); + if (!$principalCollection instanceof IPrincipalCollection) { + // Not a principal collection, we're simply going to ignore + // this. + continue; + } + + $results = $principalCollection->searchPrincipals($searchProperties, $test); + foreach ($results as $result) { + $lookupResults[] = rtrim($uri, '/') . '/' . $result; + } + + } + + $matches = []; + + foreach ($lookupResults as $lookupResult) { + + list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0); + + } + + return $matches; + + } + + /** + * Sets up the plugin + * + * This method is automatically called by the server class. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + if ($this->allowUnauthenticatedAccess) { + $authPlugin = $server->getPlugin('auth'); + if (!$authPlugin) { + throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.'); + } + $authPlugin->autoRequireLogin = false; + } + + $this->server = $server; + $server->on('propFind', [$this, 'propFind'], 20); + $server->on('beforeMethod', [$this, 'beforeMethod'], 20); + $server->on('beforeBind', [$this, 'beforeBind'], 20); + $server->on('beforeUnbind', [$this, 'beforeUnbind'], 20); + $server->on('propPatch', [$this, 'propPatch']); + $server->on('beforeUnlock', [$this, 'beforeUnlock'], 20); + $server->on('report', [$this, 'report']); + $server->on('method:ACL', [$this, 'httpAcl']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('getPrincipalByUri', function($principal, &$uri) { + + $uri = $this->getPrincipalByUri($principal); + + // Break event chain + if ($uri) return false; + + }); + + array_push($server->protectedProperties, + '{DAV:}alternate-URI-set', + '{DAV:}principal-URL', + '{DAV:}group-membership', + '{DAV:}principal-collection-set', + '{DAV:}current-user-principal', + '{DAV:}supported-privilege-set', + '{DAV:}current-user-privilege-set', + '{DAV:}acl', + '{DAV:}acl-restrictions', + '{DAV:}inherited-acl-set', + '{DAV:}owner', + '{DAV:}group' + ); + + // Automatically mapping nodes implementing IPrincipal to the + // {DAV:}principal resourcetype. + $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal'; + + // Mapping the group-member-set property to the HrefList property + // class. + $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href'; + $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl'; + $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport'; + $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport'; + $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport'; + $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport'; + $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport'; + + } + + /* {{{ Event handlers */ + + /** + * Triggered before any method is handled + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + $method = $request->getMethod(); + $path = $request->getPath(); + + $exists = $this->server->tree->nodeExists($path); + + // If the node doesn't exists, none of these checks apply + if (!$exists) return; + + switch ($method) { + + case 'GET' : + case 'HEAD' : + case 'OPTIONS' : + // For these 3 we only need to know if the node is readable. + $this->checkPrivileges($path, '{DAV:}read'); + break; + + case 'PUT' : + case 'LOCK' : + // This method requires the write-content priv if the node + // already exists, and bind on the parent if the node is being + // created. + // The bind privilege is handled in the beforeBind event. + $this->checkPrivileges($path, '{DAV:}write-content'); + break; + + case 'UNLOCK' : + // Unlock is always allowed at the moment. + break; + + case 'PROPPATCH' : + $this->checkPrivileges($path, '{DAV:}write-properties'); + break; + + case 'ACL' : + $this->checkPrivileges($path, '{DAV:}write-acl'); + break; + + case 'COPY' : + case 'MOVE' : + // Copy requires read privileges on the entire source tree. + // If the target exists write-content normally needs to be + // checked, however, we're deleting the node beforehand and + // creating a new one after, so this is handled by the + // beforeUnbind event. + // + // The creation of the new node is handled by the beforeBind + // event. + // + // If MOVE is used beforeUnbind will also be used to check if + // the sourcenode can be deleted. + $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE); + break; + + } + + } + + /** + * Triggered before a new node is created. + * + * This allows us to check permissions for any operation that creates a + * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE. + * + * @param string $uri + * @return void + */ + function beforeBind($uri) { + + list($parentUri) = Uri\split($uri); + $this->checkPrivileges($parentUri, '{DAV:}bind'); + + } + + /** + * Triggered before a node is deleted + * + * This allows us to check permissions for any operation that will delete + * an existing node. + * + * @param string $uri + * @return void + */ + function beforeUnbind($uri) { + + list($parentUri) = Uri\split($uri); + $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS); + + } + + /** + * Triggered before a node is unlocked. + * + * @param string $uri + * @param DAV\Locks\LockInfo $lock + * @TODO: not yet implemented + * @return void + */ + function beforeUnlock($uri, DAV\Locks\LockInfo $lock) { + + + } + + /** + * Triggered before properties are looked up in specific nodes. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @TODO really should be broken into multiple methods, or even a class. + * @return bool + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $path = $propFind->getPath(); + + // Checking the read permission + if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) { + // User is not allowed to read properties + + // Returning false causes the property-fetching system to pretend + // that the node does not exist, and will cause it to be hidden + // from listings such as PROPFIND or the browser plugin. + if ($this->hideNodesFromListings) { + return false; + } + + // Otherwise we simply mark every property as 403. + foreach ($propFind->getRequestedProperties() as $requestedProperty) { + $propFind->set($requestedProperty, null, 403); + } + + return; + + } + + /* Adding principal properties */ + if ($node instanceof IPrincipal) { + + $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) { + return new Href($node->getAlternateUriSet()); + }); + $propFind->handle('{DAV:}principal-URL', function() use ($node) { + return new Href($node->getPrincipalUrl() . '/'); + }); + $propFind->handle('{DAV:}group-member-set', function() use ($node) { + $members = $node->getGroupMemberSet(); + foreach ($members as $k => $member) { + $members[$k] = rtrim($member, '/') . '/'; + } + return new Href($members); + }); + $propFind->handle('{DAV:}group-membership', function() use ($node) { + $members = $node->getGroupMembership(); + foreach ($members as $k => $member) { + $members[$k] = rtrim($member, '/') . '/'; + } + return new Href($members); + }); + $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']); + + } + + $propFind->handle('{DAV:}principal-collection-set', function() { + + $val = $this->principalCollectionSet; + // Ensuring all collections end with a slash + foreach ($val as $k => $v) $val[$k] = $v . '/'; + return new Href($val); + + }); + $propFind->handle('{DAV:}current-user-principal', function() { + if ($url = $this->getCurrentUserPrincipal()) { + return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url . '/'); + } else { + return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED); + } + }); + $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) { + return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node)); + }); + $propFind->handle('{DAV:}current-user-privilege-set', function() use ($node, $propFind, $path) { + if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) { + $propFind->set('{DAV:}current-user-privilege-set', null, 403); + } else { + $val = $this->getCurrentUserPrivilegeSet($node); + return new Xml\Property\CurrentUserPrivilegeSet($val); + } + }); + $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) { + /* The ACL property contains all the permissions */ + if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) { + $propFind->set('{DAV:}acl', null, 403); + } else { + $acl = $this->getACL($node); + return new Xml\Property\Acl($this->getACL($node)); + } + }); + $propFind->handle('{DAV:}acl-restrictions', function() { + return new Xml\Property\AclRestrictions(); + }); + + /* Adding ACL properties */ + if ($node instanceof IACL) { + $propFind->handle('{DAV:}owner', function() use ($node) { + return new Href($node->getOwner() . '/'); + }); + } + + } + + /** + * This method intercepts PROPPATCH methods and make sure the + * group-member-set is updated correctly. + * + * @param string $path + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch($path, DAV\PropPatch $propPatch) { + + $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) { + if (is_null($value)) { + $memberSet = []; + } elseif ($value instanceof Href) { + $memberSet = array_map( + [$this->server, 'calculateUri'], + $value->getHrefs() + ); + } else { + throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null'); + } + $node = $this->server->tree->getNodeForPath($path); + if (!($node instanceof IPrincipal)) { + // Fail + return false; + } + + $node->setGroupMemberSet($memberSet); + // We must also clear our cache, just in case + + $this->principalMembershipCache = []; + + return true; + }); + + } + + /** + * This method handles HTTP REPORT requests + * + * @param string $reportName + * @param mixed $report + * @param mixed $path + * @return bool + */ + function report($reportName, $report, $path) { + + switch ($reportName) { + + case '{DAV:}principal-property-search' : + $this->server->transactionType = 'report-principal-property-search'; + $this->principalPropertySearchReport($path, $report); + return false; + case '{DAV:}principal-search-property-set' : + $this->server->transactionType = 'report-principal-search-property-set'; + $this->principalSearchPropertySetReport($path, $report); + return false; + case '{DAV:}expand-property' : + $this->server->transactionType = 'report-expand-property'; + $this->expandPropertyReport($path, $report); + return false; + case '{DAV:}principal-match' : + $this->server->transactionType = 'report-principal-match'; + $this->principalMatchReport($path, $report); + return false; + case '{DAV:}acl-principal-prop-set' : + $this->server->transactionType = 'acl-principal-prop-set'; + $this->aclPrincipalPropSetReport($path, $report); + return false; + + } + + } + + /** + * This method is responsible for handling the 'ACL' event. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpAcl(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $body = $request->getBodyAsString(); + + if (!$body) { + throw new DAV\Exception\BadRequest('XML body expected in ACL request'); + } + + $acl = $this->server->xml->expect('{DAV:}acl', $body); + $newAcl = $acl->getPrivileges(); + + // Normalizing urls + foreach ($newAcl as $k => $newAce) { + $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']); + } + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof IACL) { + throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method'); + } + + $oldAcl = $this->getACL($node); + + $supportedPrivileges = $this->getFlatPrivilegeSet($node); + + /* Checking if protected principals from the existing principal set are + not overwritten. */ + foreach ($oldAcl as $oldAce) { + + if (!isset($oldAce['protected']) || !$oldAce['protected']) continue; + + $found = false; + foreach ($newAcl as $newAce) { + if ( + $newAce['privilege'] === $oldAce['privilege'] && + $newAce['principal'] === $oldAce['principal'] && + $newAce['protected'] + ) + $found = true; + } + + if (!$found) + throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request'); + + } + + foreach ($newAcl as $newAce) { + + // Do we recognize the privilege + if (!isset($supportedPrivileges[$newAce['privilege']])) { + throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server'); + } + + if ($supportedPrivileges[$newAce['privilege']]['abstract']) { + throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege'); + } + + // Looking up the principal + try { + $principal = $this->server->tree->getNodeForPath($newAce['principal']); + } catch (NotFound $e) { + throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); + } + if (!($principal instanceof IPrincipal)) { + throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal'); + } + + } + $node->setACL($newAcl); + + $response->setStatus(200); + + // Breaking the event chain, because we handled this method. + return false; + + } + + /* }}} */ + + /* Reports {{{ */ + + /** + * The principal-match report is defined in RFC3744, section 9.3. + * + * This report allows a client to figure out based on the current user, + * or a principal URL, the principal URL and principal URLs of groups that + * principal belongs to. + * + * @param string $path + * @param Xml\Request\PrincipalMatchReport $report + * @return void + */ + protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report) { + + $depth = $this->server->getHTTPDepth(0); + if ($depth !== 0) { + throw new BadRequest('The principal-match report is only defined on Depth: 0'); + } + + $currentPrincipals = $this->getCurrentUserPrincipals(); + + $result = []; + + if ($report->type === Xml\Request\PrincipalMatchReport::SELF) { + + // Finding all principals under the request uri that match the + // current principal. + foreach ($currentPrincipals as $currentPrincipal) { + + if ($currentPrincipal === $path || strpos($currentPrincipal, $path . '/') === 0) { + $result[] = $currentPrincipal; + } + + } + + } else { + + // We need to find all resources that have a property that matches + // one of the current principals. + $candidates = $this->server->getPropertiesForPath( + $path, + [$report->principalProperty], + 1 + ); + + foreach ($candidates as $candidate) { + + if (!isset($candidate[200][$report->principalProperty])) { + continue; + } + + $hrefs = $candidate[200][$report->principalProperty]; + + if (!$hrefs instanceof Href) { + continue; + } + + foreach ($hrefs->getHrefs() as $href) { + if (in_array(trim($href, '/'), $currentPrincipals)) { + $result[] = $candidate['href']; + continue 2; + } + } + } + + } + + $responses = []; + + foreach ($result as $item) { + + $properties = []; + + if ($report->properties) { + + $foo = $this->server->getPropertiesForPath($item, $report->properties); + $foo = $foo[0]; + $item = $foo['href']; + unset($foo['href']); + $properties = $foo; + + } + + $responses[] = new DAV\Xml\Element\Response( + $item, + $properties, + '200' + ); + + } + + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setBody( + $this->server->xml->write( + '{DAV:}multistatus', + $responses, + $this->server->getBaseUri() + ) + ); + + + } + + /** + * The expand-property report is defined in RFC3253 section 3.8. + * + * This report is very similar to a standard PROPFIND. The difference is + * that it has the additional ability to look at properties containing a + * {DAV:}href element, follow that property and grab additional elements + * there. + * + * Other rfc's, such as ACL rely on this report, so it made sense to put + * it in this plugin. + * + * @param string $path + * @param Xml\Request\ExpandPropertyReport $report + * @return void + */ + protected function expandPropertyReport($path, $report) { + + $depth = $this->server->getHTTPDepth(0); + + $result = $this->expandProperties($path, $report->properties, $depth); + + $xml = $this->server->xml->write( + '{DAV:}multistatus', + new DAV\Xml\Response\MultiStatus($result), + $this->server->getBaseUri() + ); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setBody($xml); + + } + + /** + * This method expands all the properties and returns + * a list with property values + * + * @param array $path + * @param array $requestedProperties the list of required properties + * @param int $depth + * @return array + */ + protected function expandProperties($path, array $requestedProperties, $depth) { + + $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); + + $result = []; + + foreach ($foundProperties as $node) { + + foreach ($requestedProperties as $propertyName => $childRequestedProperties) { + + // We're only traversing if sub-properties were requested + if (count($childRequestedProperties) === 0) continue; + + // We only have to do the expansion if the property was found + // and it contains an href element. + if (!array_key_exists($propertyName, $node[200])) continue; + + if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) { + continue; + } + + $childHrefs = $node[200][$propertyName]->getHrefs(); + $childProps = []; + + foreach ($childHrefs as $href) { + // Gathering the result of the children + $childProps[] = [ + 'name' => '{DAV:}response', + 'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0] + ]; + } + + // Replacing the property with its expanded form. + $node[200][$propertyName] = $childProps; + + } + $result[] = new DAV\Xml\Element\Response($node['href'], $node); + + } + + return $result; + + } + + /** + * principalSearchPropertySetReport + * + * This method responsible for handing the + * {DAV:}principal-search-property-set report. This report returns a list + * of properties the client may search on, using the + * {DAV:}principal-property-search report. + * + * @param string $path + * @param Xml\Request\PrincipalSearchPropertySetReport $report + * @return void + */ + protected function principalSearchPropertySetReport($path, $report) { + + $httpDepth = $this->server->getHTTPDepth(0); + if ($httpDepth !== 0) { + throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0'); + } + + $writer = $this->server->xml->getWriter(); + $writer->openMemory(); + $writer->startDocument(); + + $writer->startElement('{DAV:}principal-search-property-set'); + + foreach ($this->principalSearchPropertySet as $propertyName => $description) { + + $writer->startElement('{DAV:}principal-search-property'); + $writer->startElement('{DAV:}prop'); + + $writer->writeElement($propertyName); + + $writer->endElement(); // prop + + if ($description) { + $writer->write([[ + 'name' => '{DAV:}description', + 'value' => $description, + 'attributes' => ['xml:lang' => 'en'] + ]]); + } + + $writer->endElement(); // principal-search-property + + + } + + $writer->endElement(); // principal-search-property-set + + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setBody($writer->outputMemory()); + + } + + /** + * principalPropertySearchReport + * + * This method is responsible for handing the + * {DAV:}principal-property-search report. This report can be used for + * clients to search for groups of principals, based on the value of one + * or more properties. + * + * @param string $path + * @param Xml\Request\PrincipalPropertySearchReport $report + * @return void + */ + protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report) { + + if ($report->applyToPrincipalCollectionSet) { + $path = null; + } + if ($this->server->getHttpDepth('0') !== 0) { + throw new BadRequest('Depth must be 0'); + } + $result = $this->principalSearch( + $report->searchProperties, + $report->properties, + $path, + $report->test + ); + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /** + * aclPrincipalPropSet REPORT + * + * This method is responsible for handling the {DAV:}acl-principal-prop-set + * REPORT, as defined in: + * + * https://tools.ietf.org/html/rfc3744#section-9.2 + * + * This REPORT allows a user to quickly fetch information about all + * principals specified in the access control list. Most commonly this + * is used to for example generate a UI with ACL rules, allowing you + * to show names for principals for every entry. + * + * @param string $path + * @param Xml\Request\AclPrincipalPropSetReport $report + * @return void + */ + protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report) { + + if ($this->server->getHTTPDepth(0) !== 0) { + throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0'); + } + + // Fetching ACL rules for the given path. We're using the property + // API and not the local getACL, because it will ensure that all + // business rules and restrictions are applied. + $acl = $this->server->getProperties($path, '{DAV:}acl'); + + if (!$acl || !isset($acl['{DAV:}acl'])) { + throw new Forbidden('Could not fetch ACL rules for this path'); + } + + $principals = []; + foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) { + + if ($ace['principal'][0] === '{') { + // It's not a principal, it's one of the special rules such as {DAV:}authenticated + continue; + } + + $principals[] = $ace['principal']; + + } + + $properties = $this->server->getPropertiesForMultiplePaths( + $principals, + $report->properties + ); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody( + $this->server->generateMultiStatus($properties) + ); + + } + + + /* }}} */ + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof PrincipalCollection) + return; + + $output .= '
+

Create new principal

+ + +
+
+
+ +
+ '; + + return false; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for WebDAV ACL (rfc3744)', + 'link' => 'http://sabre.io/dav/acl/', + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Principal.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Principal.php new file mode 100644 index 000000000000..d7db94999460 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Principal.php @@ -0,0 +1,221 @@ +principalBackend = $principalBackend; + $this->principalProperties = $principalProperties; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalProperties['uri']; + + } + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + $uris = []; + if (isset($this->principalProperties['{DAV:}alternate-URI-set'])) { + + $uris = $this->principalProperties['{DAV:}alternate-URI-set']; + + } + + if (isset($this->principalProperties['{http://sabredav.org/ns}email-address'])) { + $uris[] = 'mailto:' . $this->principalProperties['{http://sabredav.org/ns}email-address']; + } + + return array_unique($uris); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->principalProperties['uri']); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMemberShip($this->principalProperties['uri']); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $groupMembers + * @return void + */ + function setGroupMemberSet(array $groupMembers) { + + $this->principalBackend->setGroupMemberSet($this->principalProperties['uri'], $groupMembers); + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + $uri = $this->principalProperties['uri']; + list(, $name) = URLUtil::splitPath($uri); + return $name; + + } + + /** + * Returns the name of the user + * + * @return string + */ + function getDisplayName() { + + if (isset($this->principalProperties['{DAV:}displayname'])) { + return $this->principalProperties['{DAV:}displayname']; + } else { + return $this->getName(); + } + + } + + /** + * Returns a list of properties + * + * @param array $requestedProperties + * @return array + */ + function getProperties($requestedProperties) { + + $newProperties = []; + foreach ($requestedProperties as $propName) { + + if (isset($this->principalProperties[$propName])) { + $newProperties[$propName] = $this->principalProperties[$propName]; + } + + } + + return $newProperties; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch(DAV\PropPatch $propPatch) { + + return $this->principalBackend->updatePrincipal( + $this->principalProperties['uri'], + $propPatch + ); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalProperties['uri']; + + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php new file mode 100644 index 000000000000..9bf9ba4453ca --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php @@ -0,0 +1,53 @@ +searchPrincipals( + $principalPrefix, + ['{http://sabredav.org/ns}email-address' => substr($uri, 7)] + ); + + if ($result) { + return $result[0]; + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php new file mode 100644 index 000000000000..40b6e33ead99 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php @@ -0,0 +1,141 @@ + [ + 'dbField' => 'displayname', + ], + + /** + * This is the users' primary email-address. + */ + '{http://sabredav.org/ns}email-address' => [ + 'dbField' => 'email', + ], + ]; + + /** + * Sets up the backend. + * + * @param \PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * {http://sabredav.org/ns}email-address - This is a custom SabreDAV + * field that's actualy injected in a number of other properties. If + * you have an email address, use this property. + * + * @param string $prefixPath + * @return array + */ + function getPrincipalsByPrefix($prefixPath) { + + $fields = [ + 'uri', + ]; + + foreach ($this->fieldMap as $key => $value) { + $fields[] = $value['dbField']; + } + $result = $this->pdo->query('SELECT ' . implode(',', $fields) . ' FROM ' . $this->tableName); + + $principals = []; + + while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $prefixPath) continue; + + $principal = [ + 'uri' => $row['uri'], + ]; + foreach ($this->fieldMap as $key => $value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + $principals[] = $principal; + + } + + return $principals; + + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + function getPrincipalByPath($path) { + + $fields = [ + 'id', + 'uri', + ]; + + foreach ($this->fieldMap as $key => $value) { + $fields[] = $value['dbField']; + } + $stmt = $this->pdo->prepare('SELECT ' . implode(',', $fields) . ' FROM ' . $this->tableName . ' WHERE uri = ?'); + $stmt->execute([$path]); + + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + if (!$row) return; + + $principal = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + ]; + foreach ($this->fieldMap as $key => $value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + return $principal; + + } + + /** + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param string $path + * @param DAV\PropPatch $propPatch + */ + function updatePrincipal($path, DAV\PropPatch $propPatch) { + + $propPatch->handle(array_keys($this->fieldMap), function($properties) use ($path) { + + $query = "UPDATE " . $this->tableName . " SET "; + $first = true; + + $values = []; + + foreach ($properties as $key => $value) { + + $dbField = $this->fieldMap[$key]['dbField']; + + if (!$first) { + $query .= ', '; + } + $first = false; + $query .= $dbField . ' = :' . $dbField; + $values[$dbField] = $value; + + } + + $query .= " WHERE uri = :uri"; + $values['uri'] = $path; + + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + return true; + + }); + + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. + * + * This method should simply return an array with full principal uri's. + * + * If somebody attempted to search on a property the backend does not + * support, you should simply return 0 results. + * + * You can also just return 0 results if you choose to not support + * searching at all, but keep in mind that this may stop certain features + * from working. + * + * @param string $prefixPath + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + if (count($searchProperties) == 0) return []; //No criteria + + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE '; + $values = []; + foreach ($searchProperties as $property => $value) { + switch ($property) { + case '{DAV:}displayname' : + $column = "displayname"; + break; + case '{http://sabredav.org/ns}email-address' : + $column = "email"; + break; + default : + // Unsupported property + return []; + } + if (count($values) > 0) $query .= (strcmp($test, "anyof") == 0 ? " OR " : " AND "); + $query .= 'lower(' . $column . ') LIKE lower(?)'; + $values[] = '%' . $value . '%'; + + } + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + $principals = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $prefixPath) continue; + + $principals[] = $row['uri']; + + } + + return $principals; + + } + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @param string $principalPrefix + * @return string + */ + function findByUri($uri, $principalPrefix) { + $value = null; + $scheme = null; + list($scheme, $value) = explode(":", $uri, 2); + if (empty($value)) return null; + + $uri = null; + switch ($scheme){ + case "mailto": + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE lower(email)=lower(?)'; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$value]); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $principalPrefix) continue; + + $uri = $row['uri']; + break; //Stop on first match + } + break; + default: + //unsupported uri scheme + return null; + } + return $uri; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + function getGroupMemberSet($principal) { + + $principal = $this->getPrincipalByPath($principal); + if (!$principal) throw new DAV\Exception('Principal not found'); + + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?'); + $stmt->execute([$principal['id']]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $result[] = $row['uri']; + } + return $result; + + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + function getGroupMembership($principal) { + + $principal = $this->getPrincipalByPath($principal); + if (!$principal) throw new DAV\Exception('Principal not found'); + + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?'); + $stmt->execute([$principal['id']]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $result[] = $row['uri']; + } + return $result; + + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members + * @return void + */ + function setGroupMemberSet($principal, array $members) { + + // Grabbing the list of principal id's. + $stmt = $this->pdo->prepare('SELECT id, uri FROM ' . $this->tableName . ' WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');'); + $stmt->execute(array_merge([$principal], $members)); + + $memberIds = []; + $principalId = null; + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($row['uri'] == $principal) { + $principalId = $row['id']; + } else { + $memberIds[] = $row['id']; + } + } + if (!$principalId) throw new DAV\Exception('Principal not found'); + + // Wiping out old members + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->groupMembersTableName . ' WHERE principal_id = ?;'); + $stmt->execute([$principalId]); + + foreach ($memberIds as $memberId) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->groupMembersTableName . ' (principal_id, member_id) VALUES (?, ?);'); + $stmt->execute([$principalId, $memberId]); + + } + + } + + /** + * Creates a new principal. + * + * This method receives a full path for the new principal. The mkCol object + * contains any additional webdav properties specified during the creation + * of the principal. + * + * @param string $path + * @param MkCol $mkCol + * @return void + */ + function createPrincipal($path, MkCol $mkCol) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (uri) VALUES (?)'); + $stmt->execute([$path]); + $this->updatePrincipal($path, $mkCol); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php new file mode 100644 index 000000000000..ee5b88a904ee --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php @@ -0,0 +1,98 @@ +principalBackend, $principal); + + } + + /** + * Creates a new collection. + * + * This method will receive a MkCol object with all the information about + * the new collection that's being created. + * + * The MkCol object contains information about the resourceType of the new + * collection. If you don't support the specified resourceType, you should + * throw Exception\InvalidResourceType. + * + * The object also contains a list of WebDAV properties for the new + * collection. + * + * You should call the handle() method on this object to specify exactly + * which properties you are storing. This allows the system to figure out + * exactly which properties you didn't store, which in turn allows other + * plugins (such as the propertystorage plugin) to handle storing the + * property for you. + * + * @param string $name + * @param MkCol $mkCol + * @throws InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + if (!$mkCol->hasResourceType('{DAV:}principal')) { + throw new InvalidResourceType('Only resources of type {DAV:}principal may be created here'); + } + + $this->principalBackend->createPrincipal( + $this->principalPrefix . '/' . $name, + $mkCol + ); + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + return [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + ]; + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php new file mode 100644 index 000000000000..0e1c30ccfea2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php @@ -0,0 +1,277 @@ +privileges = $privileges; + $this->prefixBaseUrl = $prefixBaseUrl; + + } + + /** + * Returns the list of privileges for this property + * + * @return array + */ + function getPrivileges() { + + return $this->privileges; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->privileges as $ace) { + + $this->serializeAce($writer, $ace); + + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + ob_start(); + echo ""; + echo ""; + foreach ($this->privileges as $privilege) { + + echo ''; + // if it starts with a {, it's a special principal + if ($privilege['principal'][0] === '{') { + echo ''; + } else { + echo ''; + } + echo ''; + echo ''; + echo ''; + + } + echo "
PrincipalPrivilege
', $html->xmlName($privilege['principal']), '', $html->link($privilege['principal']), '', $html->xmlName($privilege['privilege']), ''; + if (!empty($privilege['protected'])) echo '(protected)'; + echo '
"; + return ob_get_clean(); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elementMap = [ + '{DAV:}ace' => 'Sabre\Xml\Element\KeyValue', + '{DAV:}privilege' => 'Sabre\Xml\Element\Elements', + '{DAV:}principal' => 'Sabre\DAVACL\Xml\Property\Principal', + ]; + + $privileges = []; + + foreach ((array)$reader->parseInnerTree($elementMap) as $element) { + + if ($element['name'] !== '{DAV:}ace') { + continue; + } + $ace = $element['value']; + + if (empty($ace['{DAV:}principal'])) { + throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); + } + $principal = $ace['{DAV:}principal']; + + switch ($principal->getType()) { + case Principal::HREF : + $principal = $principal->getHref(); + break; + case Principal::AUTHENTICATED : + $principal = '{DAV:}authenticated'; + break; + case Principal::UNAUTHENTICATED : + $principal = '{DAV:}unauthenticated'; + break; + case Principal::ALL : + $principal = '{DAV:}all'; + break; + + } + + $protected = array_key_exists('{DAV:}protected', $ace); + + if (!isset($ace['{DAV:}grant'])) { + throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported'); + } + foreach ($ace['{DAV:}grant'] as $elem) { + if ($elem['name'] !== '{DAV:}privilege') { + continue; + } + + foreach ($elem['value'] as $priv) { + $privileges[] = [ + 'principal' => $principal, + 'protected' => $protected, + 'privilege' => $priv, + ]; + } + + } + + } + + return new self($privileges); + + } + + /** + * Serializes a single access control entry. + * + * @param Writer $writer + * @param array $ace + * @return void + */ + private function serializeAce(Writer $writer, array $ace) { + + $writer->startElement('{DAV:}ace'); + + switch ($ace['principal']) { + case '{DAV:}authenticated' : + $principal = new Principal(Principal::AUTHENTICATED); + break; + case '{DAV:}unauthenticated' : + $principal = new Principal(Principal::UNAUTHENTICATED); + break; + case '{DAV:}all' : + $principal = new Principal(Principal::ALL); + break; + default: + $principal = new Principal(Principal::HREF, $ace['principal']); + break; + } + + $writer->writeElement('{DAV:}principal', $principal); + $writer->startElement('{DAV:}grant'); + $writer->startElement('{DAV:}privilege'); + + $writer->writeElement($ace['privilege']); + + $writer->endElement(); // privilege + $writer->endElement(); // grant + + if (!empty($ace['protected'])) { + $writer->writeElement('{DAV:}protected'); + } + + $writer->endElement(); // ace + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php new file mode 100644 index 000000000000..8d5854c23c0a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php @@ -0,0 +1,45 @@ +writeElement('{DAV:}grant-only'); + $writer->writeElement('{DAV:}no-invert'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php new file mode 100644 index 000000000000..74c09cee150b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php @@ -0,0 +1,159 @@ +privileges = $privileges; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->privileges as $privName) { + + $writer->startElement('{DAV:}privilege'); + $writer->writeElement($privName); + $writer->endElement(); + + } + + + } + + /** + * Returns true or false, whether the specified principal appears in the + * list. + * + * @param string $privilegeName + * @return bool + */ + function has($privilegeName) { + + return in_array($privilegeName, $this->privileges); + + } + + /** + * Returns the list of privileges. + * + * @return array + */ + function getValue() { + + return $this->privileges; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = []; + + $tree = $reader->parseInnerTree(['{DAV:}privilege' => 'Sabre\\Xml\\Element\\Elements']); + foreach ($tree as $element) { + if ($element['name'] !== '{DAV:}privilege') { + continue; + } + $result[] = $element['value'][0]; + } + return new self($result); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php new file mode 100644 index 000000000000..04d22165d273 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php @@ -0,0 +1,196 @@ +type = $type; + if ($type === self::HREF && is_null($href)) { + throw new DAV\Exception('The href argument must be specified for the HREF principal type.'); + } + if ($href) { + $href = rtrim($href, '/') . '/'; + parent::__construct($href); + } + + } + + /** + * Returns the principal type + * + * @return int + */ + function getType() { + + return $this->type; + + } + + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->type) { + + case self::UNAUTHENTICATED : + $writer->writeElement('{DAV:}unauthenticated'); + break; + case self::AUTHENTICATED : + $writer->writeElement('{DAV:}authenticated'); + break; + case self::HREF : + parent::xmlSerialize($writer); + break; + case self::ALL : + $writer->writeElement('{DAV:}all'); + break; + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + switch ($this->type) { + + case self::UNAUTHENTICATED : + return 'unauthenticated'; + case self::AUTHENTICATED : + return 'authenticated'; + case self::HREF : + return parent::toHtml($html); + case self::ALL : + return 'all'; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called staticly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $tree = $reader->parseInnerTree()[0]; + + switch ($tree['name']) { + case '{DAV:}unauthenticated' : + return new self(self::UNAUTHENTICATED); + case '{DAV:}authenticated' : + return new self(self::AUTHENTICATED); + case '{DAV:}href': + return new self(self::HREF, $tree['value']); + case '{DAV:}all': + return new self(self::ALL); + default : + throw new BadRequest('Unknown or unsupported principal type: ' . $tree['name']); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php new file mode 100644 index 000000000000..b963cc8c3ba7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php @@ -0,0 +1,160 @@ +privileges = $privileges; + + } + + /** + * Returns the privilege value. + * + * @return array + */ + function getValue() { + + return $this->privileges; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $this->serializePriv($writer, '{DAV:}all', ['aggregates' => $this->privileges]); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + $traverse = function($privName, $priv) use (&$traverse, $html) { + echo "
  • "; + echo $html->xmlName($privName); + if (isset($priv['abstract']) && $priv['abstract']) { + echo " (abstract)"; + } + if (isset($priv['description'])) { + echo " " . $html->h($priv['description']); + } + if (isset($priv['aggregates'])) { + echo "\n
      \n"; + foreach ($priv['aggregates'] as $subPrivName => $subPriv) { + $traverse($subPrivName, $subPriv); + } + echo "
    "; + } + echo "
  • \n"; + }; + + ob_start(); + echo "
      "; + $traverse('{DAV:}all', ['aggregates' => $this->getValue()]); + echo "
    \n"; + + return ob_get_clean(); + + } + + + + /** + * Serializes a property + * + * This is a recursive function. + * + * @param Writer $writer + * @param string $privName + * @param array $privilege + * @return void + */ + private function serializePriv(Writer $writer, $privName, $privilege) { + + $writer->startElement('{DAV:}supported-privilege'); + + $writer->startElement('{DAV:}privilege'); + $writer->writeElement($privName); + $writer->endElement(); // privilege + + if (!empty($privilege['abstract'])) { + $writer->writeElement('{DAV:}abstract'); + } + if (!empty($privilege['description'])) { + $writer->writeElement('{DAV:}description', $privilege['description']); + } + if (isset($privilege['aggregates'])) { + foreach ($privilege['aggregates'] as $subPrivName => $subPrivilege) { + $this->serializePriv($writer, $subPrivName, $subPrivilege); + } + } + + $writer->endElement(); // supported-privilege + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php new file mode 100644 index 000000000000..0aa2f29a559f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php @@ -0,0 +1,67 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum'; + + $elems = Deserializer\keyValue( + $reader, + 'DAV:' + ); + + $reader->popContext(); + + $report = new self(); + + if (!empty($elems['prop'])) { + $report->properties = $elems['prop']; + } + + return $report; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php new file mode 100644 index 000000000000..a9938ba5bf07 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php @@ -0,0 +1,103 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + + $obj = new self(); + $obj->properties = self::traverse($elems); + + return $obj; + + } + + /** + * This method is used by deserializeXml, to recursively parse the + * {DAV:}property elements. + * + * @param array $elems + * @return void + */ + private static function traverse($elems) { + + $result = []; + + foreach ($elems as $elem) { + + if ($elem['name'] !== '{DAV:}property') { + continue; + } + + $namespace = isset($elem['attributes']['namespace']) ? + $elem['attributes']['namespace'] : + 'DAV:'; + + $propName = '{' . $namespace . '}' . $elem['attributes']['name']; + + $value = null; + if (is_array($elem['value'])) { + $value = self::traverse($elem['value']); + } + + $result[$propName] = $value; + + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php new file mode 100644 index 000000000000..1be15ab2da62 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php @@ -0,0 +1,107 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum'; + + $elems = Deserializer\keyValue( + $reader, + 'DAV:' + ); + + $reader->popContext(); + + $principalMatch = new self(); + + if (array_key_exists('self', $elems)) { + $principalMatch->type = self::SELF; + } + + if (array_key_exists('principal-property', $elems)) { + $principalMatch->type = self::PRINCIPAL_PROPERTY; + $principalMatch->principalProperty = $elems['principal-property'][0]['name']; + } + + if (!empty($elems['prop'])) { + $principalMatch->properties = $elems['prop']; + } + + return $principalMatch; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php new file mode 100644 index 000000000000..b0cf0e408ff5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php @@ -0,0 +1,127 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $foundSearchProp = false; + $self->test = 'allof'; + if ($reader->getAttribute('test') === 'anyof') { + $self->test = 'anyof'; + } + + $elemMap = [ + '{DAV:}property-search' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + + foreach ($reader->parseInnerTree($elemMap) as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $self->properties = array_keys($elem['value']); + break; + case '{DAV:}property-search' : + $foundSearchProp = true; + // This property has two sub-elements: + // {DAV:}prop - The property to be searched on. This may + // also be more than one + // {DAV:}match - The value to match with + if (!isset($elem['value']['{DAV:}prop']) || !isset($elem['value']['{DAV:}match'])) { + throw new BadRequest('The {DAV:}property-search element must contain one {DAV:}match and one {DAV:}prop element'); + } + foreach ($elem['value']['{DAV:}prop'] as $propName => $discard) { + $self->searchProperties[$propName] = $elem['value']['{DAV:}match']; + } + break; + case '{DAV:}apply-to-principal-collection-set' : + $self->applyToPrincipalCollectionSet = true; + break; + + } + + } + if (!$foundSearchProp) { + throw new BadRequest('The {DAV:}principal-property-search report must contain at least 1 {DAV:}property-search element'); + } + + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php new file mode 100644 index 000000000000..64d1f7f861e0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php @@ -0,0 +1,58 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + if (!$reader->isEmptyElement) { + throw new BadRequest('The {DAV:}principal-search-property-set element must be empty'); + } + + // The element is actually empty, so there's not much to do. + $reader->next(); + + $self = new self(); + return $self; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php new file mode 100644 index 000000000000..406dbe0e8e27 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php @@ -0,0 +1,1431 @@ +dropTables([ + 'calendarobjects', + 'calendars', + 'calendarinstances', + 'calendarchanges', + 'calendarsubscriptions', + 'schedulingobjects', + ]); + $this->createSchema('calendars'); + + $this->pdo = $this->getDb(); + + } + + function testConstruct() { + + $backend = new PDO($this->pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testGetCalendarsForUserNoCalendars() { + + $backend = new PDO($this->pdo); + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals([], $calendars); + + } + + /** + * @depends testConstruct + */ + function testCreateCalendarAndFetch() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']), + '{DAV:}displayname' => 'Hello!', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + $calendars = $backend->getCalendarsForUser('principals/user2'); + + $elementCheck = [ + 'uri' => 'somerandomid', + '{DAV:}displayname' => 'Hello!', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => '', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + 'share-access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + ]; + + $this->assertInternalType('array', $calendars); + $this->assertEquals(1, count($calendars)); + + foreach ($elementCheck as $name => $value) { + + $this->assertArrayHasKey($name, $calendars[0]); + $this->assertEquals($value, $calendars[0][$name]); + + } + + } + + /** + * @depends testConstruct + */ + function testUpdateCalendarAndFetch() { + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + + // Updating the calendar + $backend->updateCalendar($newId, $propPatch); + $result = $propPatch->commit(); + + // Verifying the result of the update + $this->assertTrue($result); + + // Fetching all calendars from this user + $calendars = $backend->getCalendarsForUser('principals/user2'); + + // Checking if all the information is still correct + $elementCheck = [ + 'id' => $newId, + 'uri' => 'somerandomid', + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => '', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => '', + '{http://calendarserver.org/ns/}getctag' => 'http://sabre.io/ns/sync/2', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]; + + $this->assertInternalType('array', $calendars); + $this->assertEquals(1, count($calendars)); + + foreach ($elementCheck as $name => $value) { + + $this->assertArrayHasKey($name, $calendars[0]); + $this->assertEquals($value, $calendars[0][$name]); + + } + + } + + /** + * @depends testConstruct + * @expectedException \InvalidArgumentException + */ + function testUpdateCalendarBadId() { + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + + // Updating the calendar + $backend->updateCalendar('raaaa', $propPatch); + + } + + /** + * @depends testUpdateCalendarAndFetch + */ + function testUpdateCalendarUnknownProperty() { + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{DAV:}yourmom' => 'wittycomment', + ]); + + // Updating the calendar + $backend->updateCalendar($newId, $propPatch); + $propPatch->commit(); + + // Verifying the result of the update + $this->assertEquals([ + '{DAV:}yourmom' => 403, + '{DAV:}displayname' => 424, + ], $propPatch->getResult()); + + } + + /** + * @depends testCreateCalendarAndFetch + */ + function testDeleteCalendar() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']), + '{DAV:}displayname' => 'Hello!', + ]); + + $backend->deleteCalendar($returnedId); + + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals([], $calendars); + + } + + /** + * @depends testCreateCalendarAndFetch + * @expectedException \InvalidArgumentException + */ + function testDeleteCalendarBadID() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']), + '{DAV:}displayname' => 'Hello!', + ]); + + $backend->deleteCalendar('bad-id'); + + } + + /** + * @depends testCreateCalendarAndFetch + * @expectedException \Sabre\DAV\Exception + */ + function testCreateCalendarIncorrectComponentSet() {; + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => 'blabla', + ]); + + } + + function testCreateCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('20120101'), + 'lastoccurence' => strtotime('20120101') + (3600 * 24), + 'componenttype' => 'VEVENT', + ], $row); + + } + function testGetMultipleObjects() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'id-1', $object); + $backend->createCalendarObject($returnedId, 'id-2', $object); + + $check = [ + [ + 'id' => 1, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-1', + 'size' => strlen($object), + 'calendardata' => $object, + 'lastmodified' => null, + ], + [ + 'id' => 2, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-2', + 'size' => strlen($object), + 'calendardata' => $object, + 'lastmodified' => null, + ], + ]; + + $result = $backend->getMultipleCalendarObjects($returnedId, ['id-1', 'id-2']); + + foreach ($check as $index => $props) { + + foreach ($props as $key => $expected) { + + $actual = $result[$index][$key]; + + switch ($key) { + case 'lastmodified' : + $this->assertInternalType('int', $actual); + break; + case 'calendardata' : + if (is_resource($actual)) { + $actual = stream_get_contents($actual); + } + // no break intentional + default : + $this->assertEquals($expected, $actual); + + } + + } + + } + + } + + /** + * @depends testGetMultipleObjects + * @expectedException \InvalidArgumentException + */ + function testGetMultipleObjectsBadId() { + + $backend = new PDO($this->pdo); + $backend->getMultipleCalendarObjects('bad-id', ['foo-bar']); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectNoComponent() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectDuration() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nDURATION:P2D\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('20120101'), + 'lastoccurence' => strtotime('20120101') + (3600 * 48), + 'componenttype' => 'VEVENT', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + * @expectedException \InvalidArgumentException + */ + function testCreateCalendarObjectBadId() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nDURATION:P2D\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject('bad-id', 'random-id', $object); + + } + + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectNoDTEND() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 10:00:00'), + 'componenttype' => 'VEVENT', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectWithDTEND() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nDTEND:20120101T110000Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 11:00:00'), + 'componenttype' => 'VEVENT', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectInfiniteRecurrence() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nRRULE:FREQ=DAILY\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime(PDO::MAX_DATE), + 'componenttype' => 'VEVENT', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectEndingRecurrence() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nDTEND;VALUE=DATE-TIME:20120101T110000Z\r\nUID:foo\r\nRRULE:FREQ=DAILY;COUNT=1000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 11:00:00') + (3600 * 24 * 999), + 'componenttype' => 'VEVENT', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectTask() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nDUE;VALUE=DATE-TIME:20120101T100000Z\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = \'random-id\''); + $row = $result->fetch(\PDO::FETCH_ASSOC); + if (is_resource($row['calendardata'])) { + $row['calendardata'] = stream_get_contents($row['calendardata']); + } + + $this->assertEquals([ + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => null, + 'lastoccurence' => null, + 'componenttype' => 'VTODO', + ], $row); + + } + + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjects() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $data = $backend->getCalendarObjects($returnedId); + + $this->assertEquals(1, count($data)); + $data = $data[0]; + + $this->assertEquals('random-id', $data['uri']); + $this->assertEquals(strlen($object), $data['size']); + + } + + /** + * @depends testGetCalendarObjects + * @expectedException \InvalidArgumentException + */ + function testGetCalendarObjectsBadId() { + + $backend = new PDO($this->pdo); + $backend->getCalendarObjects('bad-id'); + + } + + /** + * @depends testGetCalendarObjects + * @expectedException \InvalidArgumentException + */ + function testGetCalendarObjectBadId() { + + $backend = new PDO($this->pdo); + $backend->getCalendarObject('bad-id', 'foo-bar'); + + } + + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjectByUID() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $this->assertNull( + $backend->getCalendarObjectByUID('principals/user2', 'bar') + ); + $this->assertEquals( + 'somerandomid/random-id', + $backend->getCalendarObjectByUID('principals/user2', 'foo') + ); + + } + + /** + * @depends testCreateCalendarObject + */ + function testUpdateCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $object2 = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20130101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->updateCalendarObject($returnedId, 'random-id', $object2); + + $data = $backend->getCalendarObject($returnedId, 'random-id'); + + if (is_resource($data['calendardata'])) { + $data['calendardata'] = stream_get_contents($data['calendardata']); + } + + $this->assertEquals($object2, $data['calendardata']); + $this->assertEquals('random-id', $data['uri']); + + + } + + /** + * @depends testUpdateCalendarObject + * @expectedException \InvalidArgumentException + */ + function testUpdateCalendarObjectBadId() { + + $backend = new PDO($this->pdo); + $backend->updateCalendarObject('bad-id', 'object-id', 'objectdata'); + + } + + /** + * @depends testCreateCalendarObject + */ + function testDeleteCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->deleteCalendarObject($returnedId, 'random-id'); + + $data = $backend->getCalendarObject($returnedId, 'random-id'); + $this->assertNull($data); + + } + + /** + * @depends testDeleteCalendarObject + * @expectedException \InvalidArgumentException + */ + function testDeleteCalendarObjectBadId() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->deleteCalendarObject('bad-id', 'random-id'); + + } + + function testCalendarQueryNoResult() { + + $abstract = new PDO($this->pdo); + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VJOURNAL', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + ], $abstract->calendarQuery([1, 1], $filters)); + + } + + /** + * @expectedException \InvalidArgumentException + * @depends testCalendarQueryNoResult + */ + function testCalendarQueryBadId() { + + $abstract = new PDO($this->pdo); + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VJOURNAL', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $abstract->calendarQuery('bad-id', $filters); + + } + + function testCalendarQueryTodo() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject([1, 1], "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VTODO', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "todo", + ], $backend->calendarQuery([1, 1], $filters)); + + } + function testCalendarQueryTodoNotMatch() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject([1, 1], "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VTODO', + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'summary', + 'text-match' => null, + 'time-range' => null, + 'param-filters' => [], + 'is-not-defined' => false, + ], + ], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + ], $backend->calendarQuery([1, 1], $filters)); + + } + + function testCalendarQueryNoFilter() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject([1, 1], "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $result = $backend->calendarQuery([1, 1], $filters); + $this->assertTrue(in_array('todo', $result)); + $this->assertTrue(in_array('event', $result)); + + } + + function testCalendarQueryTimeRange() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject([1, 1], "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('20120103'), + 'end' => new \DateTime('20120104'), + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "event2", + ], $backend->calendarQuery([1, 1], $filters)); + + } + function testCalendarQueryTimeRangeNoEnd() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject([1, 1], "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject([1, 1], "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('20120102'), + 'end' => null, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "event2", + ], $backend->calendarQuery([1, 1], $filters)); + + } + + function testGetChanges() { + + $backend = new PDO($this->pdo); + $id = $backend->createCalendar( + 'principals/user1', + 'bla', + [] + ); + $result = $backend->getChangesForCalendar($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 1, + 'modified' => [], + 'deleted' => [], + 'added' => [], + ], $result); + + $currentToken = $result['syncToken']; + + $dummyTodo = "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($id, "todo1.ics", $dummyTodo); + $backend->createCalendarObject($id, "todo2.ics", $dummyTodo); + $backend->createCalendarObject($id, "todo3.ics", $dummyTodo); + $backend->updateCalendarObject($id, "todo1.ics", $dummyTodo); + $backend->deleteCalendarObject($id, "todo2.ics"); + + $result = $backend->getChangesForCalendar($id, $currentToken, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => ["todo1.ics"], + 'deleted' => ["todo2.ics"], + 'added' => ["todo3.ics"], + ], $result); + + $result = $backend->getChangesForCalendar($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => [], + 'deleted' => [], + 'added' => ["todo1.ics", "todo3.ics"], + ], $result); + } + + /** + * @depends testGetChanges + * @expectedException \InvalidArgumentException + */ + function testGetChangesBadId() { + + $backend = new PDO($this->pdo); + $id = $backend->createCalendar( + 'principals/user1', + 'bla', + [] + ); + $backend->getChangesForCalendar('bad-id', null, 1); + + } + + function testCreateSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + + $expected = $props; + $expected['id'] = 1; + $expected['uri'] = 'sub1'; + $expected['principaluri'] = 'principals/user1'; + + unset($expected['{http://calendarserver.org/ns/}source']); + $expected['source'] = 'http://example.org/cal.ics'; + + $this->assertEquals(1, count($subs)); + foreach ($expected as $k => $v) { + $this->assertEquals($subs[0][$k], $expected[$k]); + } + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testCreateSubscriptionFail() { + + $props = [ + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + } + + function testUpdateSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $newProps = [ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + ]; + + $propPatch = new DAV\PropPatch($newProps); + $backend->updateSubscription(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + + $expected = array_merge($props, $newProps); + $expected['id'] = 1; + $expected['uri'] = 'sub1'; + $expected['principaluri'] = 'principals/user1'; + + unset($expected['{http://calendarserver.org/ns/}source']); + $expected['source'] = 'http://example.org/cal2.ics'; + + $this->assertEquals(1, count($subs)); + foreach ($expected as $k => $v) { + $this->assertEquals($subs[0][$k], $expected[$k]); + } + + } + + function testUpdateSubscriptionsFail() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + '{DAV:}unknown' => 'foo', + ]); + + $backend->updateSubscription(1, $propPatch); + $propPatch->commit(); + + $this->assertEquals([ + '{DAV:}unknown' => 403, + '{DAV:}displayname' => 424, + '{http://calendarserver.org/ns/}source' => 424, + ], $propPatch->getResult()); + + } + + function testDeleteSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $newProps = [ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + ]; + + $backend->deleteSubscription(1); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + $this->assertEquals(0, count($subs)); + } + + function testSchedulingMethods() { + + $backend = new PDO($this->pdo); + + $calData = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"; + + $backend->createSchedulingObject( + 'principals/user1', + 'schedule1.ics', + $calData + ); + + $expected = [ + 'calendardata' => $calData, + 'uri' => 'schedule1.ics', + 'etag' => '"' . md5($calData) . '"', + 'size' => strlen($calData) + ]; + + $result = $backend->getSchedulingObject('principals/user1', 'schedule1.ics'); + foreach ($expected as $k => $v) { + $this->assertArrayHasKey($k, $result); + if (is_resource($result[$k])) { + $result[$k] = stream_get_contents($result[$k]); + } + $this->assertEquals($v, $result[$k]); + } + + $results = $backend->getSchedulingObjects('principals/user1'); + + $this->assertEquals(1, count($results)); + $result = $results[0]; + foreach ($expected as $k => $v) { + if (is_resource($result[$k])) { + $result[$k] = stream_get_contents($result[$k]); + } + $this->assertEquals($v, $result[$k]); + } + + $backend->deleteSchedulingObject('principals/user1', 'schedule1.ics'); + $result = $backend->getSchedulingObject('principals/user1', 'schedule1.ics'); + + $this->assertNull($result); + + } + + function testGetInvites() { + + $backend = new PDO($this->pdo); + + // creating a new calendar + $backend->createCalendar('principals/user1', 'somerandomid', []); + $calendar = $backend->getCalendarsForUser('principals/user1')[0]; + + $result = $backend->getInvites($calendar['id']); + $expected = [ + new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]) + ]; + + $this->assertEquals($expected, $result); + + } + + /** + * @depends testGetInvites + * @expectedException \InvalidArgumentException + */ + function testGetInvitesBadId() { + + $backend = new PDO($this->pdo); + + // creating a new calendar + $backend->createCalendar('principals/user1', 'somerandomid', []); + $calendar = $backend->getCalendarsForUser('principals/user1')[0]; + + $backend->getInvites('bad-id'); + + } + + /** + * @depends testCreateCalendarAndFetch + */ + function testUpdateInvites() { + + $backend = new PDO($this->pdo); + + // creating a new calendar + $backend->createCalendar('principals/user1', 'somerandomid', []); + $calendar = $backend->getCalendarsForUser('principals/user1')[0]; + + $ownerSharee = new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]); + + // Add a new invite + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'properties' => ['{DAV:}displayname' => 'User 2'], + ]) + ] + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + $ownerSharee, + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'properties' => [ + '{DAV:}displayname' => 'User 2', + ], + ]) + ]; + $this->assertEquals($expected, $result); + + // Checking calendar_instances too + $expectedCalendar = [ + 'id' => [1,2], + 'principaluri' => 'principals/user2', + '{http://calendarserver.org/ns/}getctag' => 'http://sabre.io/ns/sync/1', + '{http://sabredav.org/ns}sync-token' => '1', + 'share-access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'read-only' => true, + 'share-resource-uri' => '/ns/share/1', + ]; + $calendars = $backend->getCalendarsForUser('principals/user2'); + + foreach ($expectedCalendar as $k => $v) { + $this->assertEquals( + $v, + $calendars[0][$k], + "Key " . $k . " in calendars array did not have the expected value." + ); + } + + + // Updating an invite + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]) + ] + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + $ownerSharee, + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'properties' => [ + '{DAV:}displayname' => 'User 2', + ], + ]) + ]; + $this->assertEquals($expected, $result); + + // Removing an invite + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'mailto:user@example.org', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS, + ]) + ] + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + $ownerSharee + ]; + $this->assertEquals($expected, $result); + + // Preventing the owner share from being removed + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS, + ]) + ] + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]), + ]; + $this->assertEquals($expected, $result); + + } + + /** + * @depends testUpdateInvites + * @expectedException \InvalidArgumentException + */ + function testUpdateInvitesBadId() { + + $backend = new PDO($this->pdo); + // Add a new invite + $backend->updateInvites( + 'bad-id', + [] + ); + + } + + /** + * @depends testUpdateInvites + */ + function testUpdateInvitesNoPrincipal() { + + $backend = new PDO($this->pdo); + + // creating a new calendar + $backend->createCalendar('principals/user1', 'somerandomid', []); + $calendar = $backend->getCalendarsForUser('principals/user1')[0]; + + $ownerSharee = new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]); + + // Add a new invite + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => null, + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'properties' => ['{DAV:}displayname' => 'User 2'], + ]) + ] + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + $ownerSharee, + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => null, + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_INVALID, + 'properties' => [ + '{DAV:}displayname' => 'User 2', + ], + ]) + ]; + $this->assertEquals($expected, $result, null, 0.0, 10, true); // Last argument is $canonicalize = true, which allows us to compare, ignoring the order, because it's different between MySQL and Sqlite. + + } + + /** + * @depends testUpdateInvites + */ + function testDeleteSharedCalendar() { + + $backend = new PDO($this->pdo); + + // creating a new calendar + $backend->createCalendar('principals/user1', 'somerandomid', []); + $calendar = $backend->getCalendarsForUser('principals/user1')[0]; + + $ownerSharee = new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]); + + // Add a new invite + $backend->updateInvites( + $calendar['id'], + [ + new Sharee([ + 'href' => 'mailto:user@example.org', + 'principal' => 'principals/user2', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'properties' => ['{DAV:}displayname' => 'User 2'], + ]) + ] + ); + + $expectedCalendar = [ + 'id' => [1,2], + 'principaluri' => 'principals/user2', + '{http://calendarserver.org/ns/}getctag' => 'http://sabre.io/ns/sync/1', + '{http://sabredav.org/ns}sync-token' => '1', + 'share-access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'read-only' => true, + 'share-resource-uri' => '/ns/share/1', + ]; + $calendars = $backend->getCalendarsForUser('principals/user2'); + + foreach ($expectedCalendar as $k => $v) { + $this->assertEquals( + $v, + $calendars[0][$k], + "Key " . $k . " in calendars array did not have the expected value." + ); + } + + // Removing the shared calendar. + $backend->deleteCalendar($calendars[0]['id']); + + $this->assertEquals( + [], + $backend->getCalendarsForUser('principals/user2') + ); + + $result = $backend->getInvites($calendar['id']); + $expected = [ + new Sharee([ + 'href' => 'principals/user1', + 'principal' => 'principals/user1', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED, + ]), + ]; + $this->assertEquals($expected, $result); + + } + + /** + * @expectedException \Sabre\DAV\Exception\NotImplemented + */ + function testSetPublishStatus() { + + $backend = new PDO($this->pdo); + $backend->setPublishStatus([1, 1], true); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php new file mode 100644 index 000000000000..7f642efc9ae3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php @@ -0,0 +1,178 @@ + 'anything']); + + $abstract->updateCalendar('randomid', $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + } + + function testCalendarQuery() { + + $abstract = new AbstractMock(); + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + 'event1.ics', + ], $abstract->calendarQuery(1, $filters)); + + } + + function testGetCalendarObjectByUID() { + + $abstract = new AbstractMock(); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal1', 'zim') + ); + $this->assertEquals( + 'cal1/event1.ics', + $abstract->getCalendarObjectByUID('principal1', 'foo') + ); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal3', 'foo') + ); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal1', 'shared') + ); + + } + + function testGetMultipleCalendarObjects() { + + $abstract = new AbstractMock(); + $result = $abstract->getMultipleCalendarObjects(1, [ + 'event1.ics', + 'task1.ics', + ]); + + $expected = [ + [ + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ], + [ + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", + ], + ]; + + $this->assertEquals($expected, $result); + + + } + +} + +class AbstractMock extends AbstractBackend { + + function getCalendarsForUser($principalUri) { + + return [ + [ + 'id' => 1, + 'principaluri' => 'principal1', + 'uri' => 'cal1', + ], + [ + 'id' => 2, + 'principaluri' => 'principal1', + '{http://sabredav.org/ns}owner-principal' => 'principal2', + 'uri' => 'cal1', + ], + ]; + + } + function createCalendar($principalUri, $calendarUri, array $properties) { } + function deleteCalendar($calendarId) { } + function getCalendarObjects($calendarId) { + + switch ($calendarId) { + case 1: + return [ + [ + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + ], + [ + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + ], + ]; + case 2: + return [ + [ + 'id' => 3, + 'calendarid' => 2, + 'uri' => 'shared-event.ics', + ] + ]; + } + + } + + function getCalendarObject($calendarId, $objectUri) { + + switch ($objectUri) { + + case 'event1.ics' : + return [ + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ]; + case 'task1.ics' : + return [ + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", + ]; + case 'shared-event.ics' : + return [ + 'id' => 3, + 'calendarid' => 2, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:shared\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ]; + + } + + } + function createCalendarObject($calendarId, $objectUri, $calendarData) { } + function updateCalendarObject($calendarId, $objectUri, $calendarData) { } + function deleteCalendarObject($calendarId, $objectUri) { } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php new file mode 100644 index 000000000000..cc665cd8fa38 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php @@ -0,0 +1,258 @@ +calendars = $calendars; + $this->calendarData = $calendarData; + + } + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri, which the basename of the uri with which the calendar is + * accessed. + * * principalUri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * @param string $principalUri + * @return array + */ + function getCalendarsForUser($principalUri) { + + $r = []; + foreach ($this->calendars as $row) { + if ($row['principaluri'] == $principalUri) { + $r[] = $row; + } + } + + return $r; + + } + + /** + * Creates a new calendar for a principal. + * + * If the creation was a success, an id must be returned that can be used to reference + * this calendar in other methods, such as updateCalendar. + * + * This function must return a server-wide unique id that can be used + * later to reference the calendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return string|int + */ + function createCalendar($principalUri, $calendarUri, array $properties) { + + $id = DAV\UUIDUtil::getUUID(); + $this->calendars[] = array_merge([ + 'id' => $id, + 'principaluri' => $principalUri, + 'uri' => $calendarUri, + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT', 'VTODO']), + ], $properties); + + return $id; + + } + + /** + * Updates properties for a calendar. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param mixed $calendarId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { + + $propPatch->handleRemaining(function($props) use ($calendarId) { + + foreach ($this->calendars as $k => $calendar) { + + if ($calendar['id'] === $calendarId) { + foreach ($props as $propName => $propValue) { + if (is_null($propValue)) { + unset($this->calendars[$k][$propName]); + } else { + $this->calendars[$k][$propName] = $propValue; + } + } + return true; + + } + + } + + }); + + } + + /** + * Delete a calendar and all it's objects + * + * @param string $calendarId + * @return void + */ + function deleteCalendar($calendarId) { + + foreach ($this->calendars as $k => $calendar) { + if ($calendar['id'] === $calendarId) { + unset($this->calendars[$k]); + } + } + + } + + /** + * Returns all calendar objects within a calendar object. + * + * Every item contains an array with the following keys: + * * id - unique identifier which will be used for subsequent updates + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * calendarid - The calendarid as it was passed to this function. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * @param string $calendarId + * @return array + */ + function getCalendarObjects($calendarId) { + + if (!isset($this->calendarData[$calendarId])) + return []; + + $objects = $this->calendarData[$calendarId]; + + foreach ($objects as $uri => &$object) { + $object['calendarid'] = $calendarId; + $object['uri'] = $uri; + $object['lastmodified'] = null; + } + return $objects; + + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * This method must return null if the object did not exist. + * + * @param mixed $calendarId + * @param string $objectUri + * @return array|null + */ + function getCalendarObject($calendarId, $objectUri) { + + if (!isset($this->calendarData[$calendarId][$objectUri])) { + return null; + } + $object = $this->calendarData[$calendarId][$objectUri]; + $object['calendarid'] = $calendarId; + $object['uri'] = $objectUri; + $object['lastmodified'] = null; + return $object; + + } + + /** + * Creates a new calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData + * @return void + */ + function createCalendarObject($calendarId, $objectUri, $calendarData) { + + $this->calendarData[$calendarId][$objectUri] = [ + 'calendardata' => $calendarData, + 'calendarid' => $calendarId, + 'uri' => $objectUri, + ]; + return '"' . md5($calendarData) . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData + * @return void + */ + function updateCalendarObject($calendarId, $objectUri, $calendarData) { + + $this->calendarData[$calendarId][$objectUri] = [ + 'calendardata' => $calendarData, + 'calendarid' => $calendarId, + 'uri' => $objectUri, + ]; + return '"' . md5($calendarData) . '"'; + + } + + /** + * Deletes an existing calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @return void + */ + function deleteCalendarObject($calendarId, $objectUri) { + + unset($this->calendarData[$calendarId][$objectUri]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php new file mode 100644 index 000000000000..3ac22f474f63 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php @@ -0,0 +1,91 @@ +schedulingObjects[$principalUri][$objectUri])) { + return $this->schedulingObjects[$principalUri][$objectUri]; + } + + } + + /** + * Returns all scheduling objects for the inbox collection. + * + * These objects should be returned as an array. Every item in the array + * should follow the same structure as returned from getSchedulingObject. + * + * The main difference is that 'calendardata' is optional. + * + * @param string $principalUri + * @return array + */ + function getSchedulingObjects($principalUri) { + + if (isset($this->schedulingObjects[$principalUri])) { + return array_values($this->schedulingObjects[$principalUri]); + } + return []; + + } + + /** + * Deletes a scheduling object + * + * @param string $principalUri + * @param string $objectUri + * @return void + */ + function deleteSchedulingObject($principalUri, $objectUri) { + + if (isset($this->schedulingObjects[$principalUri][$objectUri])) { + unset($this->schedulingObjects[$principalUri][$objectUri]); + } + + } + + /** + * Creates a new scheduling object. This should land in a users' inbox. + * + * @param string $principalUri + * @param string $objectUri + * @param string $objectData; + * @return void + */ + function createSchedulingObject($principalUri, $objectUri, $objectData) { + + if (!isset($this->schedulingObjects[$principalUri])) { + $this->schedulingObjects[$principalUri] = []; + } + $this->schedulingObjects[$principalUri][$objectUri] = [ + 'uri' => $objectUri, + 'calendardata' => $objectData, + 'lastmodified' => null, + 'etag' => '"' . md5($objectData) . '"', + 'size' => strlen($objectData) + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php new file mode 100644 index 000000000000..eaf52e32f4a9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php @@ -0,0 +1,204 @@ +notifications = $notifications; + + } + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri, which the basename of the uri with which the calendar is + * accessed. + * * principalUri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * @param string $principalUri + * @return array + */ + function getCalendarsForUser($principalUri) { + + $calendars = parent::getCalendarsForUser($principalUri); + foreach ($calendars as $k => $calendar) { + + if (isset($calendar['share-access'])) { + continue; + } + if (!empty($this->shares[$calendar['id']])) { + $calendar['share-access'] = DAV\Sharing\Plugin::ACCESS_SHAREDOWNER; + } else { + $calendar['share-access'] = DAV\Sharing\Plugin::ACCESS_NOTSHARED; + } + $calendars[$k] = $calendar; + + } + return $calendars; + + } + + /** + * Returns a list of notifications for a given principal url. + * + * The returned array should only consist of implementations of + * Sabre\CalDAV\Notifications\INotificationType. + * + * @param string $principalUri + * @return array + */ + function getNotificationsForPrincipal($principalUri) { + + if (isset($this->notifications[$principalUri])) { + return $this->notifications[$principalUri]; + } + return []; + + } + + /** + * This deletes a specific notifcation. + * + * This may be called by a client once it deems a notification handled. + * + * @param string $principalUri + * @param NotificationInterface $notification + * @return void + */ + function deleteNotification($principalUri, NotificationInterface $notification) { + + foreach ($this->notifications[$principalUri] as $key => $value) { + if ($notification === $value) { + unset($this->notifications[$principalUri][$key]); + } + } + + } + + /** + * Updates the list of shares. + * + * @param mixed $calendarId + * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees + * @return void + */ + function updateInvites($calendarId, array $sharees) { + + if (!isset($this->shares[$calendarId])) { + $this->shares[$calendarId] = []; + } + + foreach ($sharees as $sharee) { + + $existingKey = null; + foreach ($this->shares[$calendarId] as $k => $existingSharee) { + if ($sharee->href === $existingSharee->href) { + $existingKey = $k; + } + } + // Just making sure we're not affecting an existing copy. + $sharee = clone $sharee; + $sharee->inviteStatus = DAV\Sharing\Plugin::INVITE_NORESPONSE; + + if ($sharee->access === DAV\Sharing\Plugin::ACCESS_NOACCESS) { + // It's a removal + unset($this->shares[$calendarId][$existingKey]); + } elseif ($existingKey) { + // It's an update + $this->shares[$calendarId][$existingKey] = $sharee; + } else { + // It's an addition + $this->shares[$calendarId][] = $sharee; + } + } + + // Re-numbering keys + $this->shares[$calendarId] = array_values($this->shares[$calendarId]); + + } + + /** + * Returns the list of people whom this calendar is shared with. + * + * Every item in the returned list must be a Sharee object with at + * least the following properties set: + * $href + * $shareAccess + * $inviteStatus + * + * and optionally: + * $properties + * + * @param mixed $calendarId + * @return \Sabre\DAV\Xml\Element\Sharee[] + */ + function getInvites($calendarId) { + + if (!isset($this->shares[$calendarId])) { + return []; + } + + return $this->shares[$calendarId]; + + } + + /** + * This method is called when a user replied to a request to share. + * + * @param string href The sharee who is replying (often a mailto: address) + * @param int status One of the \Sabre\DAV\Sharing\Plugin::INVITE_* constants + * @param string $calendarUri The url to the calendar thats being shared + * @param string $inReplyTo The unique id this message is a response to + * @param string $summary A description of the reply + * @return void + */ + function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { + + // This operation basically doesn't do anything yet + if ($status === DAV\Sharing\Plugin::INVITE_ACCEPTED) { + return 'calendars/blabla/calendar'; + } + + } + + /** + * Publishes a calendar + * + * @param mixed $calendarId + * @param bool $value + * @return void + */ + function setPublishStatus($calendarId, $value) { + + foreach ($this->calendars as $k => $cal) { + if ($cal['id'] === $calendarId) { + if (!$value) { + unset($cal['{http://calendarserver.org/ns/}publish-url']); + } else { + $cal['{http://calendarserver.org/ns/}publish-url'] = 'http://example.org/public/ ' . $calendarId . '.ics'; + } + return; + } + } + + throw new DAV\Exception('Calendar with id "' . $calendarId . '" not found'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php new file mode 100644 index 000000000000..adf9c8a17d66 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php @@ -0,0 +1,156 @@ +subs[$principalUri])) { + return $this->subs[$principalUri]; + } + return []; + + } + + /** + * Creates a new subscription for a principal. + * + * If the creation was a success, an id must be returned that can be used to reference + * this subscription in other methods, such as updateSubscription. + * + * @param string $principalUri + * @param string $uri + * @param array $properties + * @return mixed + */ + function createSubscription($principalUri, $uri, array $properties) { + + $properties['uri'] = $uri; + $properties['principaluri'] = $principalUri; + $properties['source'] = $properties['{http://calendarserver.org/ns/}source']->getHref(); + + if (!isset($this->subs[$principalUri])) { + $this->subs[$principalUri] = []; + } + + $id = [$principalUri, count($this->subs[$principalUri]) + 1]; + + $properties['id'] = $id; + + $this->subs[$principalUri][] = array_merge($properties, [ + 'id' => $id, + ]); + + return $id; + + } + + /** + * Updates a subscription + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param mixed $subscriptionId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + + $found = null; + foreach ($this->subs[$subscriptionId[0]] as &$sub) { + + if ($sub['id'][1] === $subscriptionId[1]) { + $found = & $sub; + break; + } + + } + + if (!$found) return; + + $propPatch->handleRemaining(function($mutations) use (&$found) { + foreach ($mutations as $k => $v) { + $found[$k] = $v; + } + return true; + }); + + } + + /** + * Deletes a subscription + * + * @param mixed $subscriptionId + * @return void + */ + function deleteSubscription($subscriptionId) { + + foreach ($this->subs[$subscriptionId[0]] as $index => $sub) { + + if ($sub['id'][1] === $subscriptionId[1]) { + unset($this->subs[$subscriptionId[0]][$index]); + return true; + } + + } + + return false; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php new file mode 100644 index 000000000000..e068ff1e78f4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php @@ -0,0 +1,9 @@ +markTestSkipped('SQLite driver is not available'); + + if (file_exists(SABRE_TEMPDIR . '/testdb.sqlite')) + unlink(SABRE_TEMPDIR . '/testdb.sqlite'); + + $pdo = new \PDO('sqlite:' . SABRE_TEMPDIR . '/testdb.sqlite'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $pdo->exec(<<exec(<<pdo = $pdo; + + } + + function testConstruct() { + + $backend = new SimplePDO($this->pdo); + $this->assertTrue($backend instanceof SimplePDO); + + } + + /** + * @depends testConstruct + */ + function testGetCalendarsForUserNoCalendars() { + + $backend = new SimplePDO($this->pdo); + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals([], $calendars); + + } + + /** + * @depends testConstruct + */ + function testCreateCalendarAndFetch() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']), + '{DAV:}displayname' => 'Hello!', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + $calendars = $backend->getCalendarsForUser('principals/user2'); + + $elementCheck = [ + 'uri' => 'somerandomid', + ]; + + $this->assertInternalType('array', $calendars); + $this->assertEquals(1, count($calendars)); + + foreach ($elementCheck as $name => $value) { + + $this->assertArrayHasKey($name, $calendars[0]); + $this->assertEquals($value, $calendars[0][$name]); + + } + + } + + /** + * @depends testConstruct + */ + function testUpdateCalendarAndFetch() { + + $backend = new SimplePDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + + // Updating the calendar + $backend->updateCalendar($newId, $propPatch); + $result = $propPatch->commit(); + + // Verifying the result of the update + $this->assertFalse($result); + + } + + /** + * @depends testCreateCalendarAndFetch + */ + function testDeleteCalendar() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT']), + '{DAV:}displayname' => 'Hello!', + ]); + + $backend->deleteCalendar($returnedId); + + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals([], $calendars); + + } + + function testCreateCalendarObject() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT calendardata FROM simple_calendarobjects WHERE uri = "random-id"'); + $this->assertEquals([ + 'calendardata' => $object, + ], $result->fetch(\PDO::FETCH_ASSOC)); + + } + function testGetMultipleObjects() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'id-1', $object); + $backend->createCalendarObject($returnedId, 'id-2', $object); + + $check = [ + [ + 'id' => 1, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-1', + 'size' => strlen($object), + 'calendardata' => $object, + ], + [ + 'id' => 2, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-2', + 'size' => strlen($object), + 'calendardata' => $object, + ], + ]; + + $result = $backend->getMultipleCalendarObjects($returnedId, ['id-1', 'id-2']); + + foreach ($check as $index => $props) { + + foreach ($props as $key => $value) { + + if ($key !== 'lastmodified') { + $this->assertEquals($value, $result[$index][$key]); + } else { + $this->assertTrue(isset($result[$index][$key])); + } + + } + + } + + } + + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjects() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $data = $backend->getCalendarObjects($returnedId); + + $this->assertEquals(1, count($data)); + $data = $data[0]; + + $this->assertEquals('random-id', $data['uri']); + $this->assertEquals(strlen($object), $data['size']); + + } + + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjectByUID() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $this->assertNull( + $backend->getCalendarObjectByUID('principals/user2', 'bar') + ); + $this->assertEquals( + 'somerandomid/random-id', + $backend->getCalendarObjectByUID('principals/user2', 'foo') + ); + + } + + /** + * @depends testCreateCalendarObject + */ + function testUpdateCalendarObject() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $object2 = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20130101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->updateCalendarObject($returnedId, 'random-id', $object2); + + $data = $backend->getCalendarObject($returnedId, 'random-id'); + + $this->assertEquals($object2, $data['calendardata']); + $this->assertEquals('random-id', $data['uri']); + + + } + + + /** + * @depends testCreateCalendarObject + */ + function testDeleteCalendarObject() { + + $backend = new SimplePDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->deleteCalendarObject($returnedId, 'random-id'); + + $data = $backend->getCalendarObject($returnedId, 'random-id'); + $this->assertNull($data); + + } + + + function testCalendarQueryNoResult() { + + $abstract = new SimplePDO($this->pdo); + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VJOURNAL', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + ], $abstract->calendarQuery(1, $filters)); + + } + + function testCalendarQueryTodo() { + + $backend = new SimplePDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VTODO', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "todo", + ], $backend->calendarQuery(1, $filters)); + + } + function testCalendarQueryTodoNotMatch() { + + $backend = new SimplePDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VTODO', + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'summary', + 'text-match' => null, + 'time-range' => null, + 'param-filters' => [], + 'is-not-defined' => false, + ], + ], + 'is-not-defined' => false, + 'time-range' => null, + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + ], $backend->calendarQuery(1, $filters)); + + } + + function testCalendarQueryNoFilter() { + + $backend = new SimplePDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $result = $backend->calendarQuery(1, $filters); + $this->assertTrue(in_array('todo', $result)); + $this->assertTrue(in_array('event', $result)); + + } + + function testCalendarQueryTimeRange() { + + $backend = new SimplePDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('20120103'), + 'end' => new \DateTime('20120104'), + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "event2", + ], $backend->calendarQuery(1, $filters)); + + } + function testCalendarQueryTimeRangeNoEnd() { + + $backend = new SimplePDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('20120102'), + 'end' => null, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $this->assertEquals([ + "event2", + ], $backend->calendarQuery(1, $filters)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php new file mode 100644 index 000000000000..36302cc35563 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php @@ -0,0 +1,49 @@ + 'principals/user']); + + $this->assertEquals( + [], + $calendarHome->getChildren() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + */ + function testGetChildNoSupport() { + + $backend = new Backend\Mock(); + $calendarHome = new CalendarHome($backend, ['uri' => 'principals/user']); + $calendarHome->getChild('notifications'); + + } + + function testGetChildren() { + + $backend = new Backend\MockSharing(); + $calendarHome = new CalendarHome($backend, ['uri' => 'principals/user']); + + $result = $calendarHome->getChildren(); + $this->assertEquals('notifications', $result[0]->getName()); + + } + + function testGetChild() { + + $backend = new Backend\MockSharing(); + $calendarHome = new CalendarHome($backend, ['uri' => 'principals/user']); + $result = $calendarHome->getChild('notifications'); + $this->assertEquals('notifications', $result->getName()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php new file mode 100644 index 000000000000..9079fdecd802 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php @@ -0,0 +1,80 @@ + 1, + 'principaluri' => 'principals/user1', + ], + [ + 'id' => 2, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/cal1', + '{http://sabredav.org/ns}owner-principal' => 'principal/owner', + '{http://sabredav.org/ns}read-only' => false, + 'principaluri' => 'principals/user1', + ], + ]; + + $this->backend = new Backend\MockSharing( + $calendars, + [], + [] + ); + + return new CalendarHome($this->backend, [ + 'uri' => 'principals/user1' + ]); + + } + + function testSimple() { + + $instance = $this->getInstance(); + $this->assertEquals('user1', $instance->getName()); + + } + + function testGetChildren() { + + $instance = $this->getInstance(); + $children = $instance->getChildren(); + $this->assertEquals(3, count($children)); + + // Testing if we got all the objects back. + $sharedCalendars = 0; + $hasOutbox = false; + $hasNotifications = false; + + foreach ($children as $child) { + + if ($child instanceof ISharedCalendar) { + $sharedCalendars++; + } + if ($child instanceof Notifications\ICollection) { + $hasNotifications = true; + } + + } + $this->assertEquals(2, $sharedCalendars); + $this->assertTrue($hasNotifications); + + } + + function testShareReply() { + + $instance = $this->getInstance(); + $result = $instance->shareReply('uri', DAV\Sharing\Plugin::INVITE_DECLINED, 'curi', '1'); + $this->assertNull($result); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php new file mode 100644 index 000000000000..4a479c81602a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php @@ -0,0 +1,85 @@ + 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test.ics'), + ]; + $principal = [ + 'uri' => 'principals/user1' + ]; + $this->backend = new Backend\MockSubscriptionSupport([], []); + $this->backend->createSubscription('principals/user1', 'uri', $props); + + return new CalendarHome($this->backend, $principal); + + } + + function testSimple() { + + $instance = $this->getInstance(); + $this->assertEquals('user1', $instance->getName()); + + } + + function testGetChildren() { + + $instance = $this->getInstance(); + $children = $instance->getChildren(); + $this->assertEquals(1, count($children)); + foreach ($children as $child) { + if ($child instanceof Subscriptions\Subscription) { + return; + } + } + $this->fail('There were no subscription nodes in the calendar home'); + + } + + function testCreateSubscription() { + + $instance = $this->getInstance(); + $rt = ['{DAV:}collection', '{http://calendarserver.org/ns/}subscribed']; + + $props = [ + '{DAV:}displayname' => 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test2.ics'), + ]; + $instance->createExtendedCollection('sub2', new MkCol($rt, $props)); + + $children = $instance->getChildren(); + $this->assertEquals(2, count($children)); + + } + + /** + * @expectedException \Sabre\DAV\Exception\InvalidResourceType + */ + function testNoSubscriptionSupport() { + + $principal = [ + 'uri' => 'principals/user1' + ]; + $backend = new Backend\Mock([], []); + $uC = new CalendarHome($backend, $principal); + + $rt = ['{DAV:}collection', '{http://calendarserver.org/ns/}subscribed']; + + $props = [ + '{DAV:}displayname' => 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test2.ics'), + ]; + $uC->createExtendedCollection('sub2', new MkCol($rt, $props)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php new file mode 100644 index 000000000000..ff52ea6ad200 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php @@ -0,0 +1,215 @@ +backend = TestUtil::getBackend(); + $this->usercalendars = new CalendarHome($this->backend, [ + 'uri' => 'principals/user1' + ]); + + } + + function testSimple() { + + $this->assertEquals('user1', $this->usercalendars->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + * @depends testSimple + */ + function testGetChildNotFound() { + + $this->usercalendars->getChild('randomname'); + + } + + function testChildExists() { + + $this->assertFalse($this->usercalendars->childExists('foo')); + $this->assertTrue($this->usercalendars->childExists('UUID-123467')); + + } + + function testGetOwner() { + + $this->assertEquals('principals/user1', $this->usercalendars->getOwner()); + + } + + function testGetGroup() { + + $this->assertNull($this->usercalendars->getGroup()); + + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + ]; + $this->assertEquals($expected, $this->usercalendars->getACL()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $this->usercalendars->setACL([]); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + * @depends testSimple + */ + function testSetName() { + + $this->usercalendars->setName('bla'); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + * @depends testSimple + */ + function testDelete() { + + $this->usercalendars->delete(); + + } + + /** + * @depends testSimple + */ + function testGetLastModified() { + + $this->assertNull($this->usercalendars->getLastModified()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + * @depends testSimple + */ + function testCreateFile() { + + $this->usercalendars->createFile('bla'); + + } + + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + * @depends testSimple + */ + function testCreateDirectory() { + + $this->usercalendars->createDirectory('bla'); + + } + + /** + * @depends testSimple + */ + function testCreateExtendedCollection() { + + $mkCol = new MkCol( + ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'], + [] + ); + $result = $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + $this->assertNull($result); + $cals = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3, count($cals)); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + * @depends testSimple + */ + function testCreateExtendedCollectionBadResourceType() { + + $mkCol = new MkCol( + ['{DAV:}collection', '{DAV:}blabla'], + [] + ); + $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + * @depends testSimple + */ + function testCreateExtendedCollectionNotACalendar() { + + $mkCol = new MkCol( + ['{DAV:}collection'], + [] + ); + $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + + } + + function testGetSupportedPrivilegesSet() { + + $this->assertNull($this->usercalendars->getSupportedPrivilegeSet()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testShareReplyFail() { + + $this->usercalendars->shareReply('uri', DAV\Sharing\Plugin::INVITE_DECLINED, 'curi', '1'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php new file mode 100644 index 000000000000..c92cde661334 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php @@ -0,0 +1,383 @@ +backend = TestUtil::getBackend(); + + $calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(2, count($calendars)); + $this->calendar = new Calendar($this->backend, $calendars[0]); + + } + + function teardown() { + + unset($this->calendar); + unset($this->backend); + + } + + function testSetup() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $this->assertInternalType('string', $children[0]->getName()); + $this->assertInternalType('string', $children[0]->get()); + $this->assertInternalType('string', $children[0]->getETag()); + $this->assertEquals('text/calendar; charset=utf-8', $children[0]->getContentType()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg1() { + + $obj = new CalendarObject( + new Backend\Mock([], []), + [], + [] + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg2() { + + $obj = new CalendarObject( + new Backend\Mock([], []), + [], + ['calendarid' => '1'] + ); + + } + + /** + * @depends testSetup + */ + function testPut() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + $newData = TestUtil::getTestCalendarData(); + + $children[0]->put($newData); + $this->assertEquals($newData, $children[0]->get()); + + } + + /** + * @depends testSetup + */ + function testPutStream() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + $newData = TestUtil::getTestCalendarData(); + + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $newData); + rewind($stream); + $children[0]->put($stream); + $this->assertEquals($newData, $children[0]->get()); + + } + + + /** + * @depends testSetup + */ + function testDelete() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $obj->delete(); + + $children2 = $this->calendar->getChildren(); + $this->assertEquals(count($children) - 1, count($children2)); + + } + + /** + * @depends testSetup + */ + function testGetLastModified() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $lastMod = $obj->getLastModified(); + $this->assertTrue(is_int($lastMod) || ctype_digit($lastMod) || is_null($lastMod)); + + } + + /** + * @depends testSetup + */ + function testGetSize() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $size = $obj->getSize(); + $this->assertInternalType('int', $size); + + } + + function testGetOwner() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertEquals('principals/user1', $obj->getOwner()); + + } + + function testGetGroup() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertNull($obj->getGroup()); + + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + ]; + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertEquals($expected, $obj->getACL()); + + } + + function testDefaultACL() { + + $backend = new Backend\Mock([], []); + $calendarObject = new CalendarObject($backend, ['principaluri' => 'principals/user1'], ['calendarid' => 1, 'uri' => 'foo']); + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + ]; + $this->assertEquals($expected, $calendarObject->getACL()); + + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $obj->setACL([]); + + } + + function testGet() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $expected = "BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Asia/Seoul +BEGIN:DAYLIGHT +TZOFFSETFROM:+0900 +RRULE:FREQ=YEARLY;UNTIL=19880507T150000Z;BYMONTH=5;BYDAY=2SU +DTSTART:19870510T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+1000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+1000 +DTSTART:19881009T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+0900 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20100225T154229Z +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +TRANSP:TRANSPARENT +SUMMARY:Something here +DTSTAMP:20100228T130202Z +DTSTART;TZID=Asia/Seoul:20100223T060000 +DTEND;TZID=Asia/Seoul:20100223T070000 +ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com +SEQUENCE:2 +END:VEVENT +END:VCALENDAR"; + + + + $this->assertEquals($expected, $obj->get()); + + } + + function testGetRefetch() { + + $backend = new Backend\Mock([], [ + 1 => [ + 'foo' => [ + 'calendardata' => 'foo', + 'uri' => 'foo' + ], + ] + ]); + $obj = new CalendarObject($backend, ['id' => 1], ['uri' => 'foo']); + + $this->assertEquals('foo', $obj->get()); + + } + + function testGetEtag1() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'etag' => 'bar', + 'calendarid' => 1 + ]; + + $backend = new Backend\Mock([], []); + $obj = new CalendarObject($backend, [], $objectInfo); + + $this->assertEquals('bar', $obj->getETag()); + + } + + function testGetEtag2() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\Mock([], []); + $obj = new CalendarObject($backend, [], $objectInfo); + + $this->assertEquals('"' . md5('foo') . '"', $obj->getETag()); + + } + + function testGetSupportedPrivilegesSet() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\Mock([], []); + $obj = new CalendarObject($backend, [], $objectInfo); + $this->assertNull($obj->getSupportedPrivilegeSet()); + + } + + function testGetSize1() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\Mock([], []); + $obj = new CalendarObject($backend, [], $objectInfo); + $this->assertEquals(3, $obj->getSize()); + + } + + function testGetSize2() { + + $objectInfo = [ + 'uri' => 'foo', + 'calendarid' => 1, + 'size' => 4, + ]; + + $backend = new Backend\Mock([], []); + $obj = new CalendarObject($backend, [], $objectInfo); + $this->assertEquals(4, $obj->getSize()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php new file mode 100644 index 000000000000..ca06d8ffa7bd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php @@ -0,0 +1,122 @@ +createComponent('VEVENT'); + $vevent->RRULE = 'FREQ=MONTHLY'; + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P15D'; + $vevent->add($valarm); + + + $vcalendar->add($vevent); + + $filter = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'is-not-defined' => false, + 'prop-filters' => [], + 'comp-filters' => [], + 'time-range' => [ + 'start' => new \DateTime('2012-05-10'), + 'end' => new \DateTime('2012-05-20'), + ], + ], + ], + ], + ], + ]; + + $validator = new CalendarQueryValidator(); + $this->assertTrue($validator->validate($vcalendar, $filter)); + + $vcalendar = new VObject\Component\VCalendar(); + + // A limited recurrence rule, should return false + $vevent = $vcalendar->createComponent('VEVENT'); + $vevent->RRULE = 'FREQ=MONTHLY;COUNT=1'; + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P15D'; + $vevent->add($valarm); + + $vcalendar->add($vevent); + + $this->assertFalse($validator->validate($vcalendar, $filter)); + } + + function testAlarmWayBefore() { + + $vcalendar = new VObject\Component\VCalendar(); + + $vevent = $vcalendar->createComponent('VEVENT'); + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P2W1D'; + $vevent->add($valarm); + + $vcalendar->add($vevent); + + $filter = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'is-not-defined' => false, + 'prop-filters' => [], + 'comp-filters' => [], + 'time-range' => [ + 'start' => new \DateTime('2011-12-10'), + 'end' => new \DateTime('2011-12-20'), + ], + ], + ], + ], + ], + ]; + + $validator = new CalendarQueryValidator(); + $this->assertTrue($validator->validate($vcalendar, $filter)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php new file mode 100644 index 000000000000..f3305163bdf4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php @@ -0,0 +1,829 @@ +assertFalse($validator->validate($vcal, ['name' => 'VFOO'])); + + } + + /** + * @param string $icalObject + * @param array $filters + * @param int $outcome + * @dataProvider provider + */ + function testValid($icalObject, $filters, $outcome) { + + $validator = new CalendarQueryValidator(); + + // Wrapping filter in a VCALENDAR component filter, as this is always + // there anyway. + $filters = [ + 'name' => 'VCALENDAR', + 'comp-filters' => [$filters], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $vObject = VObject\Reader::read($icalObject); + + switch ($outcome) { + case 0 : + $this->assertFalse($validator->validate($vObject, $filters)); + break; + case 1 : + $this->assertTrue($validator->validate($vObject, $filters)); + break; + case -1 : + try { + $validator->validate($vObject, $filters); + $this->fail('This test was supposed to fail'); + } catch (\Exception $e) { + // We need to test something to be valid for phpunit strict + // mode. + $this->assertTrue(true); + } catch (\Throwable $e) { + // PHP7 + $this->assertTrue(true); + } + break; + + } + + } + + function provider() { + + $blob1 = << 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + $filter2 = $filter1; + $filter2['name'] = 'VTODO'; + + $filter3 = $filter1; + $filter3['is-not-defined'] = true; + + $filter4 = $filter1; + $filter4['name'] = 'VTODO'; + $filter4['is-not-defined'] = true; + + $filter5 = $filter1; + $filter5['comp-filters'] = [ + [ + 'name' => 'VALARM', + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => null, + ], + ]; + $filter6 = $filter1; + $filter6['prop-filters'] = [ + [ + 'name' => 'SUMMARY', + 'is-not-defined' => false, + 'param-filters' => [], + 'time-range' => null, + 'text-match' => null, + ], + ]; + $filter7 = $filter6; + $filter7['prop-filters'][0]['name'] = 'DESCRIPTION'; + + $filter8 = $filter6; + $filter8['prop-filters'][0]['is-not-defined'] = true; + + $filter9 = $filter7; + $filter9['prop-filters'][0]['is-not-defined'] = true; + + $filter10 = $filter5; + $filter10['prop-filters'] = $filter6['prop-filters']; + + // Param filters + $filter11 = $filter1; + $filter11['prop-filters'] = [ + [ + 'name' => 'DTSTART', + 'is-not-defined' => false, + 'param-filters' => [ + [ + 'name' => 'VALUE', + 'is-not-defined' => false, + 'text-match' => null, + ], + ], + 'time-range' => null, + 'text-match' => null, + ], + ]; + + $filter12 = $filter11; + $filter12['prop-filters'][0]['param-filters'][0]['name'] = 'TZID'; + + $filter13 = $filter11; + $filter13['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true; + + $filter14 = $filter12; + $filter14['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true; + + // Param text filter + $filter15 = $filter11; + $filter15['prop-filters'][0]['param-filters'][0]['text-match'] = [ + 'collation' => 'i;ascii-casemap', + 'value' => 'dAtE', + 'negate-condition' => false, + ]; + $filter16 = $filter15; + $filter16['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet'; + + $filter17 = $filter15; + $filter17['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true; + + $filter18 = $filter15; + $filter18['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true; + $filter18['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet'; + + // prop + text + $filter19 = $filter5; + $filter19['comp-filters'][0]['prop-filters'] = [ + [ + 'name' => 'action', + 'is-not-defined' => false, + 'time-range' => null, + 'param-filters' => [], + 'text-match' => [ + 'collation' => 'i;ascii-casemap', + 'value' => 'display', + 'negate-condition' => false, + ], + ], + ]; + + // Time range + $filter20 = [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ], + ]; + // Time range, no end date + $filter21 = $filter20; + $filter21['time-range']['end'] = null; + + // Time range, no start date + $filter22 = $filter20; + $filter22['time-range']['start'] = null; + + // Time range, other dates + $filter23 = $filter20; + $filter23['time-range'] = [ + 'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')), + ]; + // Time range + $filter24 = [ + 'name' => 'VTODO', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ], + ]; + // Time range, other dates (1 month in the future) + $filter25 = $filter24; + $filter25['time-range'] = [ + 'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')), + ]; + $filter26 = $filter24; + $filter26['time-range'] = [ + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ]; + + // Time range for VJOURNAL + $filter27 = [ + 'name' => 'VJOURNAL', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ], + ]; + $filter28 = $filter27; + $filter28['time-range'] = [ + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ]; + // Time range for VFREEBUSY + $filter29 = [ + 'name' => 'VFREEBUSY', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ], + ]; + // Time range filter on property + $filter30 = [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'DTSTART', + 'is-not-defined' => false, + 'param-filters' => [], + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ], + 'text-match' => null, + ], + ], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + // Time range for alarm + $filter31 = [ + 'name' => 'VEVENT', + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 10:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 11:15:00', new \DateTimeZone('GMT')), + ], + 'text-match' => null, + ], + ], + 'is-not-defined' => false, + 'time-range' => null, + ]; + $filter32 = $filter31; + $filter32['comp-filters'][0]['time-range'] = [ + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ]; + + $filter33 = $filter31; + $filter33['name'] = 'VTODO'; + $filter34 = $filter32; + $filter34['name'] = 'VTODO'; + $filter35 = $filter31; + $filter35['name'] = 'VJOURNAL'; + $filter36 = $filter32; + $filter36['name'] = 'VJOURNAL'; + + // Time range filter on non-datetime property + $filter37 = [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'SUMMARY', + 'is-not-defined' => false, + 'param-filters' => [], + 'time-range' => [ + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ], + 'text-match' => null, + ], + ], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + $filter38 = [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2012-07-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2012-08-01 00:00:00', new \DateTimeZone('UTC')), + ] + ]; + $filter39 = [ + 'name' => 'VEVENT', + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2012-09-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2012-10-01 00:00:00', new \DateTimeZone('UTC')), + ] + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + + return [ + + // Component check + + [$blob1, $filter1, 1], + [$blob1, $filter2, 0], + [$blob1, $filter3, 0], + [$blob1, $filter4, 1], + + // Subcomponent check (4) + [$blob1, $filter5, 0], + [$blob2, $filter5, 1], + + // Property checki (6) + [$blob1, $filter6, 1], + [$blob1, $filter7, 0], + [$blob1, $filter8, 0], + [$blob1, $filter9, 1], + + // Subcomponent + property (10) + [$blob2, $filter10, 1], + + // Param filter (11) + [$blob3, $filter11, 1], + [$blob3, $filter12, 0], + [$blob3, $filter13, 0], + [$blob3, $filter14, 1], + + // Param + text (15) + [$blob3, $filter15, 1], + [$blob3, $filter16, 0], + [$blob3, $filter17, 0], + [$blob3, $filter18, 1], + + // Prop + text (19) + [$blob2, $filter19, 1], + + // Incorrect object (vcard) (20) + [$blob4, $filter1, -1], + + // Time-range for event (21) + [$blob5, $filter20, 1], + [$blob6, $filter20, 1], + [$blob7, $filter20, 1], + [$blob8, $filter20, 1], + + [$blob5, $filter21, 1], + [$blob5, $filter22, 1], + + [$blob5, $filter23, 0], + [$blob6, $filter23, 0], + [$blob7, $filter23, 0], + [$blob8, $filter23, 0], + + // Time-range for todo (31) + [$blob9, $filter24, 1], + [$blob9, $filter25, 0], + [$blob9, $filter26, 1], + [$blob10, $filter24, 1], + [$blob10, $filter25, 0], + [$blob10, $filter26, 1], + + [$blob11, $filter24, 0], + [$blob11, $filter25, 0], + [$blob11, $filter26, 1], + + [$blob12, $filter24, 1], + [$blob12, $filter25, 0], + [$blob12, $filter26, 0], + + [$blob13, $filter24, 1], + [$blob13, $filter25, 0], + [$blob13, $filter26, 1], + + [$blob14, $filter24, 1], + [$blob14, $filter25, 0], + [$blob14, $filter26, 0], + + [$blob15, $filter24, 1], + [$blob15, $filter25, 1], + [$blob15, $filter26, 1], + + [$blob16, $filter24, 1], + [$blob16, $filter25, 1], + [$blob16, $filter26, 1], + + // Time-range for journals (55) + [$blob17, $filter27, 0], + [$blob17, $filter28, 0], + [$blob18, $filter27, 0], + [$blob18, $filter28, 1], + [$blob19, $filter27, 1], + [$blob19, $filter28, 1], + + // Time-range for free-busy (61) + [$blob20, $filter29, -1], + + // Time-range on property (62) + [$blob5, $filter30, 1], + [$blob3, $filter37, -1], + [$blob3, $filter30, 0], + + // Time-range on alarm in vevent (65) + [$blob21, $filter31, 1], + [$blob21, $filter32, 0], + [$blob22, $filter31, 1], + [$blob22, $filter32, 0], + [$blob23, $filter31, 1], + [$blob23, $filter32, 0], + [$blob24, $filter31, 1], + [$blob24, $filter32, 0], + [$blob25, $filter31, 1], + [$blob25, $filter32, 0], + [$blob26, $filter31, 1], + [$blob26, $filter32, 0], + + // Time-range on alarm for vtodo (77) + [$blob27, $filter33, 1], + [$blob27, $filter34, 0], + + // Time-range on alarm for vjournal (79) + [$blob28, $filter35, -1], + [$blob28, $filter36, -1], + + // Time-range on alarm with duration (81) + [$blob29, $filter31, 1], + [$blob29, $filter32, 0], + [$blob30, $filter31, 0], + [$blob30, $filter32, 0], + + // Time-range with RRULE (85) + [$blob31, $filter20, 1], + [$blob32, $filter20, 0], + + // Bug reported on mailing list, related to all-day events (87) + //array($blob33, $filter38, 1), + + // Event in timerange, but filtered alarm is in the far future (88). + [$blob34, $filter39, 0], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php new file mode 100644 index 000000000000..df85b6ded0d0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php @@ -0,0 +1,256 @@ +backend = TestUtil::getBackend(); + + $this->calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(2, count($this->calendars)); + $this->calendar = new Calendar($this->backend, $this->calendars[0]); + + + } + + function teardown() { + + unset($this->backend); + + } + + function testSimple() { + + $this->assertEquals($this->calendars[0]['uri'], $this->calendar->getName()); + + } + + /** + * @depends testSimple + */ + function testUpdateProperties() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'NewName', + ]); + + $result = $this->calendar->propPatch($propPatch); + $result = $propPatch->commit(); + + $this->assertEquals(true, $result); + + $calendars2 = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals('NewName', $calendars2[0]['{DAV:}displayname']); + + } + + /** + * @depends testSimple + */ + function testGetProperties() { + + $question = [ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set', + ]; + + $result = $this->calendar->getProperties($question); + + foreach ($question as $q) $this->assertArrayHasKey($q, $result); + + $this->assertEquals(['VEVENT', 'VTODO'], $result['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + * @depends testSimple + */ + function testGetChildNotFound() { + + $this->calendar->getChild('randomname'); + + } + + /** + * @depends testSimple + */ + function testGetChildren() { + + $children = $this->calendar->getChildren(); + $this->assertEquals(1, count($children)); + + $this->assertTrue($children[0] instanceof CalendarObject); + + } + + /** + * @depends testGetChildren + */ + function testChildExists() { + + $this->assertFalse($this->calendar->childExists('foo')); + + $children = $this->calendar->getChildren(); + $this->assertTrue($this->calendar->childExists($children[0]->getName())); + } + + + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->calendar->createDirectory('hello'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->calendar->setName('hello'); + + } + + function testGetLastModified() { + + $this->assertNull($this->calendar->getLastModified()); + + } + + function testCreateFile() { + + $file = fopen('php://memory', 'r+'); + fwrite($file, TestUtil::getTestCalendarData()); + rewind($file); + + $this->calendar->createFile('hello', $file); + + $file = $this->calendar->getChild('hello'); + $this->assertTrue($file instanceof CalendarObject); + + } + + function testCreateFileNoSupportedComponents() { + + $file = fopen('php://memory', 'r+'); + fwrite($file, TestUtil::getTestCalendarData()); + rewind($file); + + $calendar = new Calendar($this->backend, $this->calendars[1]); + $calendar->createFile('hello', $file); + + $file = $calendar->getChild('hello'); + $this->assertTrue($file instanceof CalendarObject); + + } + + function testDelete() { + + $this->calendar->delete(); + + $calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(1, count($calendars)); + } + + function testGetOwner() { + + $this->assertEquals('principals/user1', $this->calendar->getOwner()); + + } + + function testGetGroup() { + + $this->assertNull($this->calendar->getGroup()); + + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + ]; + $this->assertEquals($expected, $this->calendar->getACL()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $this->calendar->setACL([]); + + } + + function testGetSyncToken() { + + $this->assertNull($this->calendar->getSyncToken()); + + } + + function testGetSyncTokenNoSyncSupport() { + + $calendar = new Calendar(new Backend\Mock([], []), []); + $this->assertNull($calendar->getSyncToken()); + + } + + function testGetChanges() { + + $this->assertNull($this->calendar->getChanges(1, 1)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php new file mode 100644 index 000000000000..9a3d47828fb0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php @@ -0,0 +1,113 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3 +SUMMARY:RecurringEvents 3 times +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +BEGIN:VEVENT +CREATED:20120207T111900Z +UID:foobar +DTEND;TZID=Europe/Berlin:20120208T191500 +SUMMARY:RecurringEvents 3 times OVERWRITTEN +DTSTART;TZID=Europe/Berlin:20120208T181500 +RECURRENCE-ID;TZID=Europe/Berlin:20120208T181500 +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testExpand() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + try { + $vObject = VObject\Reader::read($body); + } catch (VObject\ParseException $e) { + $this->fail('Could not parse object. Error:' . $e->getMessage() . ' full object: ' . $response->getBodyAsString()); + } + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if ($child->name == 'DTSTART') { + // DTSTART has to be one of three valid values + $this->assertContains($child->getValue(), ['20120207T171500Z', '20120208T171500Z', '20120209T171500Z'], 'DTSTART is not a valid value: ' . $child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND has to be one of three valid values + $this->assertContains($child->getValue(), ['20120207T181500Z', '20120208T181500Z', '20120209T181500Z'], 'DTEND is not a valid value: ' . $child->getValue()); + } + } + } + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php new file mode 100644 index 000000000000..efc49673f3fd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php @@ -0,0 +1,102 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,TH +SUMMARY:RecurringEvents on tuesday and thursday +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testExpandRecurringByDayEvent() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(2, count($vObject->VEVENT)); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if ($child->name == 'DTSTART') { + // DTSTART has to be one of two valid values + $this->assertContains($child->getValue(), ['20120214T171500Z', '20120216T171500Z'], 'DTSTART is not a valid value: ' . $child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND has to be one of two valid values + $this->assertContains($child->getValue(), ['20120214T181500Z', '20120216T181500Z'], 'DTEND is not a valid value: ' . $child->getValue()); + } + } + } + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php new file mode 100644 index 000000000000..3a22e03d4a57 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php @@ -0,0 +1,103 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3 +SUMMARY:RecurringEvents 3 times +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +BEGIN:VEVENT +CREATED:20120207T111900Z +UID:foobar +DTEND;TZID=Europe/Berlin:20120208T191500 +SUMMARY:RecurringEvents 3 times OVERWRITTEN +DTSTART;TZID=Europe/Berlin:20120208T181500 +RECURRENCE-ID;TZID=Europe/Berlin:20120208T181500 +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testExpand() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + // We only expect 3 events + $this->assertEquals(3, count($vObject->VEVENT), 'We got 6 events instead of 3. Output: ' . $body); + + // TZID should be gone + $this->assertFalse(isset($vObject->VEVENT->DTSTART['TZID'])); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php new file mode 100644 index 000000000000..fba47d79ba14 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php @@ -0,0 +1,207 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +DTSTART:19810329T020000 +TZNAME:GMT+2 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +DTSTART:19961027T030000 +TZNAME:GMT+1 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20140701T143658Z +UID:dba46fe8-1631-4d98-a575-97963c364dfe +DTEND:20141108T073000 +TRANSP:OPAQUE +SUMMARY:Floating Time event, starting 05:30am Europe/Berlin +DTSTART:20141108T053000 +DTSTAMP:20140701T143706Z +SEQUENCE:1 +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testExpandCalendarQuery() { + + $request = new HTTP\Request('REPORT', '/calendars/user1/calendar1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals('20141108T043000Z', $child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals('20141108T063000Z', $child->getValue()); + } + } + } + } + + function testExpandMultiGet() { + + $request = new HTTP\Request('REPORT', '/calendars/user1/calendar1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $request->setBody(' + + + + + + + + /calendars/user1/calendar1/event.ics +'); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus()); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T043000Z'); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T063000Z'); + } + } + } + } + + function testExpandExport() { + + $request = new HTTP\Request('GET', '/calendars/user1/calendar1?export&start=1&end=2000000000&expand=1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals('20141108T043000Z', $child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals('20141108T063000Z', $child->getValue()); + } + } + } + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php new file mode 100644 index 000000000000..7604c7f4c1b9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php @@ -0,0 +1,174 @@ + [ + 'obj1' => [ + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => $obj1, + ], + 'obj2' => [ + 'calendarid' => 1, + 'uri' => 'event2.ics', + 'calendardata' => $obj2 + ], + 'obj3' => [ + 'calendarid' => 1, + 'uri' => 'event3.ics', + 'calendardata' => $obj3 + ] + ], + ]; + + + $caldavBackend = new Backend\Mock([], $calendarData); + + $calendar = new Calendar($caldavBackend, [ + 'id' => 1, + 'uri' => 'calendar', + 'principaluri' => 'principals/user1', + '{' . Plugin::NS_CALDAV . '}calendar-timezone' => "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nEND:VTIMEZONE\r\nEND:VCALENDAR", + ]); + + $this->server = new DAV\Server([$calendar]); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/calendar', + ]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + } + + function testFreeBusyReport() { + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report, null); + + $this->assertEquals(200, $this->server->httpResponse->status); + $this->assertEquals('text/calendar', $this->server->httpResponse->getHeader('Content-Type')); + $this->assertTrue(strpos($this->server->httpResponse->body, 'BEGIN:VFREEBUSY') !== false); + $this->assertTrue(strpos($this->server->httpResponse->body, '20111005T120000Z/20111005T130000Z') !== false); + $this->assertTrue(strpos($this->server->httpResponse->body, '20111006T100000Z/20111006T110000Z') !== false); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testFreeBusyReportNoTimeRange() { + + $reportXML = << + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testFreeBusyReportWrongNode() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/', + ]); + $this->server->httpRequest = $request; + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report, null); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testFreeBusyReportNoACLPlugin() { + + $this->server = new DAV\Server(); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report, null); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php new file mode 100644 index 000000000000..5fd8d29a11b6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php @@ -0,0 +1,82 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +CREATED:20120313T142342Z +UID:171EBEFC-C951-499D-B234-7BA7D677B45D +DTEND;TZID=Europe/Berlin:20120227T010000 +TRANSP:OPAQUE +SUMMARY:Monday 0h +DTSTART;TZID=Europe/Berlin:20120227T000000 +DTSTAMP:20120313T142416Z +SEQUENCE:4 +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testQueryTimerange() { + + $request = new HTTP\Request( + 'REPORT', + '/calendars/user1/calendar1', + [ + 'Content-Type' => 'application/xml', + 'Depth' => '1', + ] + ); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertTrue(strpos($response->body, 'BEGIN:VCALENDAR') !== false); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php new file mode 100644 index 000000000000..75412577e9b3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php @@ -0,0 +1,386 @@ +icsExportPlugin = new ICSExportPlugin(); + $this->server->addPlugin( + $this->icsExportPlugin + ); + + $id = $this->caldavBackend->createCalendar( + 'principals/admin', + 'UUID-123467', + [ + '{DAV:}displayname' => 'Hello!', + '{http://apple.com/ns/ical/}calendar-color' => '#AA0000FF', + ] + ); + + $this->caldavBackend->createCalendarObject( + $id, + 'event-1', + <<caldavBackend->createCalendarObject( + $id, + 'todo-1', + <<assertEquals( + $this->icsExportPlugin, + $this->server->getPlugin('ics-export') + ); + $this->assertEquals($this->icsExportPlugin, $this->server->getPlugin('ics-export')); + $this->assertEquals('ics-export', $this->icsExportPlugin->getPluginInfo()['name']); + + } + + function testBeforeMethod() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export' + ); + + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('text/calendar', $response->getHeader('Content-Type')); + + $obj = VObject\Reader::read($response->body); + + $this->assertEquals(8, count($obj->children())); + $this->assertEquals(1, count($obj->VERSION)); + $this->assertEquals(1, count($obj->CALSCALE)); + $this->assertEquals(1, count($obj->PRODID)); + $this->assertTrue(strpos((string)$obj->PRODID, DAV\Version::VERSION) !== false); + $this->assertEquals(1, count($obj->VTIMEZONE)); + $this->assertEquals(1, count($obj->VEVENT)); + $this->assertEquals("Hello!", $obj->{"X-WR-CALNAME"}); + $this->assertEquals("#AA0000FF", $obj->{"X-APPLE-CALENDAR-COLOR"}); + + } + function testBeforeMethodNoVersion() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export' + ); + DAV\Server::$exposeVersion = false; + $response = $this->request($request); + DAV\Server::$exposeVersion = true; + + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('text/calendar', $response->getHeader('Content-Type')); + + $obj = VObject\Reader::read($response->body); + + $this->assertEquals(8, count($obj->children())); + $this->assertEquals(1, count($obj->VERSION)); + $this->assertEquals(1, count($obj->CALSCALE)); + $this->assertEquals(1, count($obj->PRODID)); + $this->assertFalse(strpos((string)$obj->PRODID, DAV\Version::VERSION) !== false); + $this->assertEquals(1, count($obj->VTIMEZONE)); + $this->assertEquals(1, count($obj->VEVENT)); + + } + + function testBeforeMethodNoExport() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467' + ); + $response = new HTTP\Response(); + $this->assertNull($this->icsExportPlugin->httpGet($request, $response)); + + } + + function testACLIntegrationBlocked() { + + $aclPlugin = new DAVACL\Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $this->server->addPlugin( + $aclPlugin + ); + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export' + ); + + $this->request($request, 403); + + } + + function testACLIntegrationNotBlocked() { + + $aclPlugin = new DAVACL\Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $this->server->addPlugin( + $aclPlugin + ); + $this->server->addPlugin( + new Plugin() + ); + + $this->autoLogin('admin'); + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export' + ); + + $response = $this->request($request, 200); + $this->assertEquals('text/calendar', $response->getHeader('Content-Type')); + + $obj = VObject\Reader::read($response->body); + + $this->assertEquals(8, count($obj->children())); + $this->assertEquals(1, count($obj->VERSION)); + $this->assertEquals(1, count($obj->CALSCALE)); + $this->assertEquals(1, count($obj->PRODID)); + $this->assertTrue(strpos((string)$obj->PRODID, DAV\Version::VERSION) !== false); + $this->assertEquals(1, count($obj->VTIMEZONE)); + $this->assertEquals(1, count($obj->VEVENT)); + + } + + function testBadStartParam() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&start=foo' + ); + $this->request($request, 400); + + } + + function testBadEndParam() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&end=foo' + ); + $this->request($request, 400); + + } + + function testFilterStartEnd() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&start=1&end=2' + ); + $response = $this->request($request, 200); + + $obj = VObject\Reader::read($response->getBody()); + + $this->assertEquals(0, count($obj->VTIMEZONE)); + $this->assertEquals(0, count($obj->VEVENT)); + + } + + function testExpandNoStart() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&expand=1&end=2' + ); + $this->request($request, 400); + + } + + function testExpand() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&start=1&end=2000000000&expand=1' + ); + $response = $this->request($request, 200); + + $obj = VObject\Reader::read($response->getBody()); + + $this->assertEquals(0, count($obj->VTIMEZONE)); + $this->assertEquals(1, count($obj->VEVENT)); + + } + + function testJCal() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export', + ['Accept' => 'application/calendar+json'] + ); + + $response = $this->request($request, 200); + $this->assertEquals('application/calendar+json', $response->getHeader('Content-Type')); + + } + + function testJCalInUrl() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&accept=jcal' + ); + + $response = $this->request($request, 200); + $this->assertEquals('application/calendar+json', $response->getHeader('Content-Type')); + + } + + function testNegotiateDefault() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export', + ['Accept' => 'text/plain'] + ); + + $response = $this->request($request, 200); + $this->assertEquals('text/calendar', $response->getHeader('Content-Type')); + + } + + function testFilterComponentVEVENT() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&componentType=VEVENT' + ); + + $response = $this->request($request, 200); + + $obj = VObject\Reader::read($response->body); + $this->assertEquals(1, count($obj->VTIMEZONE)); + $this->assertEquals(1, count($obj->VEVENT)); + $this->assertEquals(0, count($obj->VTODO)); + + } + + function testFilterComponentVTODO() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&componentType=VTODO' + ); + + $response = $this->request($request, 200); + + $obj = VObject\Reader::read($response->body); + + $this->assertEquals(0, count($obj->VTIMEZONE)); + $this->assertEquals(0, count($obj->VEVENT)); + $this->assertEquals(1, count($obj->VTODO)); + + } + + function testFilterComponentBadComponent() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export&componentType=VVOODOO' + ); + + $response = $this->request($request, 400); + + } + + function testContentDisposition() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export' + ); + + $response = $this->request($request, 200); + $this->assertEquals('text/calendar', $response->getHeader('Content-Type')); + $this->assertEquals( + 'attachment; filename="UUID-123467-' . date('Y-m-d') . '.ics"', + $response->getHeader('Content-Disposition') + ); + + } + + function testContentDispositionJson() { + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-123467?export', + ['Accept' => 'application/calendar+json'] + ); + + $response = $this->request($request, 200); + $this->assertEquals('application/calendar+json', $response->getHeader('Content-Type')); + $this->assertEquals( + 'attachment; filename="UUID-123467-' . date('Y-m-d') . '.json"', + $response->getHeader('Content-Disposition') + ); + + } + + function testContentDispositionBadChars() { + + $this->caldavBackend->createCalendar( + 'principals/admin', + 'UUID-b_ad"(ch)ars', + [ + '{DAV:}displayname' => 'Test bad characters', + '{http://apple.com/ns/ical/}calendar-color' => '#AA0000FF', + ] + ); + + $request = new HTTP\Request( + 'GET', + '/calendars/admin/UUID-b_ad"(ch)ars?export', + ['Accept' => 'application/calendar+json'] + ); + + $response = $this->request($request, 200); + $this->assertEquals('application/calendar+json', $response->getHeader('Content-Type')); + $this->assertEquals( + 'attachment; filename="UUID-b_adchars-' . date('Y-m-d') . '.json"', + $response->getHeader('Content-Disposition') + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php new file mode 100644 index 000000000000..a1a9b7c0443e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php @@ -0,0 +1,63 @@ + 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2011-12-01'), + 'end' => new \DateTime('2012-02-01'), + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]; + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input, $filters)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php new file mode 100644 index 000000000000..e2b85c2bcdd8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php @@ -0,0 +1,135 @@ + 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2012-01-18 21:00:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-18 21:00:00 GMT-08:00'), + ], + ], + ], + 'prop-filters' => [], + ]; + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input, $filters)); + } + + // Pacific Standard Time, translates to America/Los_Angeles (GMT-8 in January) + function testOutlookTimezoneName() { + $input = << 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + ], + ], + ], + 'prop-filters' => [], + ]; + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input, $filters)); + } + + // X-LIC-LOCATION, translates to America/Los_Angeles (GMT-8 in January) + function testLibICalLocationName() { + $input = << 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + ], + ], + ], + 'prop-filters' => [], + ]; + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input, $filters)); + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php new file mode 100644 index 000000000000..369e9a70c122 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php @@ -0,0 +1,137 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135305Z +DTSTART;TZID=Europe/Berlin:20120326T155200 +DTEND;TZID=Europe/Berlin:20120326T165200 +RRULE:FREQ=DAILY;COUNT=2;INTERVAL=1 +SUMMARY:original summary +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135352Z +DESCRIPTION: +DTSTART;TZID=Europe/Berlin:20120328T155200 +DTEND;TZID=Europe/Berlin:20120328T165200 +RECURRENCE-ID;TZID=Europe/Berlin:20120327T155200 +SEQUENCE:1 +SUMMARY:overwritten summary +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testIssue203() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(2, count($vObject->VEVENT)); + + + $expectedEvents = [ + [ + 'DTSTART' => '20120326T135200Z', + 'DTEND' => '20120326T145200Z', + 'SUMMARY' => 'original summary', + ], + [ + 'DTSTART' => '20120328T135200Z', + 'DTEND' => '20120328T145200Z', + 'SUMMARY' => 'overwritten summary', + 'RECURRENCE-ID' => '20120327T135200Z', + ] + ]; + + // try to match agains $expectedEvents array + foreach ($expectedEvents as $expectedEvent) { + + $matching = false; + + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children() as $child) { + /** @var $child Sabre\VObject\Property */ + if (isset($expectedEvent[$child->name])) { + if ($expectedEvent[$child->name] != $child->getValue()) { + continue 2; + } + } + } + + $matching = true; + break; + } + + $this->assertTrue($matching, 'Did not find the following event in the response: ' . var_export($expectedEvent, true)); + } + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php new file mode 100644 index 000000000000..ce40a90b035a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php @@ -0,0 +1,98 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135305Z +DTSTART;TZID=Europe/Berlin:20120326T155200 +DTEND;TZID=Europe/Berlin:20120326T165200 +SUMMARY:original summary +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:AUDIO +ATTACH;VALUE=URI:Basso +TRIGGER:PT0S +END:VALARM +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testIssue205() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'Exception'), 'Exception occurred: ' . $response->body); + $this->assertFalse(strpos($response->body, 'Unknown or bad format'), 'DateTime unknown format Exception: ' . $response->body); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ', '', $body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(1, count($vObject->VEVENT)); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php new file mode 100644 index 000000000000..950629fd8133 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php @@ -0,0 +1,89 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120418T172519CEST-3510gh1hVw +DTSTAMP:20120418T152519Z +DTSTART;VALUE=DATE:20120330 +DTEND;VALUE=DATE:20120531 +EXDATE;TZID=Europe/Berlin:20120330T000000 +RRULE:FREQ=YEARLY;INTERVAL=1 +SEQUENCE:1 +SUMMARY:Birthday +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +ATTENDEE:MAILTO:xxx@domain.de +DESCRIPTION:Dies ist eine Kalender Erinnerung +SUMMARY:Kalender Alarm Erinnerung +TRIGGER;VALUE=DATE-TIME:20120329T060000Z +END:VALARM +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testIssue211() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // if this assert is reached, the endless loop is gone + // There should be no matching events + $this->assertFalse(strpos('BEGIN:VEVENT', $response->body)); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php new file mode 100644 index 000000000000..c3c0b5b48a8e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php @@ -0,0 +1,99 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120601T180000 +SUMMARY:Brot backen +RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO +TRANSP:OPAQUE +DURATION:PT20M +LAST-MODIFIED:20120601T064634Z +CREATED:20120601T064634Z +DTSTAMP:20120601T064634Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT5M +ACTION:DISPLAY +DESCRIPTION:Default Event Notification +X-WR-ALARMUID:cd952c1b-b3d6-41fb-b0a6-ec3a1a5bdd58 +END:VALARM +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120606T180000 +SUMMARY:Brot backen +TRANSP:OPAQUE +STATUS:CANCELLED +DTEND;TZID=Europe/Berlin:20120606T182000 +LAST-MODIFIED:20120605T094310Z +SEQUENCE:1 +RECURRENCE-ID:20120606T160000Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testIssue220() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'PHPUnit_Framework_Error_Warning'), 'Error Warning occurred: ' . $response->body); + $this->assertFalse(strpos($response->body, 'Invalid argument supplied for foreach()'), 'Invalid argument supplied for foreach(): ' . $response->body); + + $this->assertEquals(207, $response->status); + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php new file mode 100644 index 000000000000..d0783701de9a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php @@ -0,0 +1,79 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ] + ]; + + protected $caldavCalendarObjects = [ + 1 => [ + 'event.ics' => [ + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120730T113415CEST-6804EGphkd@xxxxxx.de +DTSTAMP:20120730T093415Z +DTSTART;VALUE=DATE:20120729 +DTEND;VALUE=DATE:20120730 +SUMMARY:sunday event +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR +', + ], + ], + ]; + + function testIssue228() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // We must check if absolutely nothing was returned from this query. + $this->assertFalse(strpos($response->body, 'BEGIN:VCALENDAR')); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php new file mode 100644 index 000000000000..f1eed1775973 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php @@ -0,0 +1,262 @@ + 1, + 'principaluri' => 'principals/user1', + 'uri' => 'foo', + ] + ]; + protected $caldavCalendarObjects = [ + 1 => [ + 'bar.ics' => [ + 'uri' => 'bar.ics', + 'calendarid' => 1, + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + 'lastmodified' => null + ] + ], + ]; + + function testGet() { + + $headers = [ + 'Accept' => 'application/calendar+json', + ]; + $request = new Request('GET', '/calendars/user1/foo/bar.ics', $headers); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + $this->assertEquals(200, $response->getStatus(), "Incorrect status code: " . $body); + + $response = json_decode($body, true); + if (json_last_error() !== JSON_ERROR_NONE) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testMultiGet() { + + $xml = << + + + + + /calendars/user1/foo/bar.ics + +XML; + + $headers = []; + $request = new Request('REPORT', '/calendars/user1/foo', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), 'Full rsponse: ' . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + + $jresponse = json_decode($response, true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg() . '. Full response: ' . $response); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $jresponse + ); + + } + + function testCalendarQueryDepth1() { + + $xml = << + + + + + + + + +XML; + + $headers = [ + 'Depth' => '1', + ]; + $request = new Request('REPORT', '/calendars/user1/foo', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), "Invalid response code. Full body: " . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + $response = json_decode($response, true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testCalendarQueryDepth0() { + + $xml = << + + + + + + + + +XML; + + $headers = [ + 'Depth' => '0', + ]; + $request = new Request('REPORT', '/calendars/user1/foo/bar.ics', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), "Invalid response code. Full body: " . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + $response = json_decode($response, true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testValidateICalendar() { + + $input = [ + 'vcalendar', + [], + [ + [ + 'vevent', + [ + ['uid', (object)[], 'text', 'foo'], + ['dtstart', (object)[], 'date', '2016-04-06'], + ], + [], + ], + ], + ]; + $input = json_encode($input); + $this->caldavPlugin->beforeWriteContent( + 'calendars/user1/foo/bar.ics', + $this->server->tree->getNodeForPath('calendars/user1/foo/bar.ics'), + $input, + $modified + ); + + + $expected = <<assertVObjectEqualsVObject( + $expected, + $input + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php new file mode 100644 index 000000000000..6585f85c3790 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php @@ -0,0 +1,85 @@ +principalUri = 'principals/user1'; + + $this->notification = new CalDAV\Xml\Notification\SystemStatus(1, '"1"'); + + $this->caldavBackend = new CalDAV\Backend\MockSharing([], [], [ + 'principals/user1' => [ + $this->notification + ] + ]); + + return new Collection($this->caldavBackend, $this->principalUri); + + } + + function testGetChildren() { + + $col = $this->getInstance(); + $this->assertEquals('notifications', $col->getName()); + + $this->assertEquals([ + new Node($this->caldavBackend, $this->principalUri, $this->notification) + ], $col->getChildren()); + + } + + function testGetOwner() { + + $col = $this->getInstance(); + $this->assertEquals('principals/user1', $col->getOwner()); + + } + + function testGetGroup() { + + $col = $this->getInstance(); + $this->assertNull($col->getGroup()); + + } + + function testGetACL() { + + $col = $this->getInstance(); + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ]; + + $this->assertEquals($expected, $col->getACL()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $col = $this->getInstance(); + $col->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $col = $this->getInstance(); + $this->assertNull($col->getSupportedPrivilegeSet()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php new file mode 100644 index 000000000000..6c6e02da81ab --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php @@ -0,0 +1,96 @@ +systemStatus = new CalDAV\Xml\Notification\SystemStatus(1, '"1"'); + + $this->caldavBackend = new CalDAV\Backend\MockSharing([], [], [ + 'principals/user1' => [ + $this->systemStatus + ] + ]); + + $node = new Node($this->caldavBackend, 'principals/user1', $this->systemStatus); + return $node; + + } + + function testGetId() { + + $node = $this->getInstance(); + $this->assertEquals($this->systemStatus->getId() . '.xml', $node->getName()); + + } + + function testGetEtag() { + + $node = $this->getInstance(); + $this->assertEquals('"1"', $node->getETag()); + + } + + function testGetNotificationType() { + + $node = $this->getInstance(); + $this->assertEquals($this->systemStatus, $node->getNotificationType()); + + } + + function testDelete() { + + $node = $this->getInstance(); + $node->delete(); + $this->assertEquals([], $this->caldavBackend->getNotificationsForPrincipal('principals/user1')); + + } + + function testGetGroup() { + + $node = $this->getInstance(); + $this->assertNull($node->getGroup()); + + } + + function testGetACL() { + + $node = $this->getInstance(); + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ]; + + $this->assertEquals($expected, $node->getACL()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $node = $this->getInstance(); + $node->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $node = $this->getInstance(); + $this->assertNull($node->getSupportedPrivilegeSet()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php new file mode 100644 index 000000000000..73f256c98e77 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php @@ -0,0 +1,168 @@ +caldavBackend = new CalDAV\Backend\MockSharing(); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $calendars = new CalDAV\CalendarRoot($principalBackend, $this->caldavBackend); + $principals = new CalDAV\Principal\Collection($principalBackend); + + $root = new DAV\SimpleCollection('root'); + $root->addChild($calendars); + $root->addChild($principals); + + $this->server = new DAV\Server($root); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + $this->server->setBaseUri('/'); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + + // Adding ACL plugin + $aclPlugin = new DAVACL\Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $this->server->addPlugin($aclPlugin); + + // CalDAV is also required. + $this->server->addPlugin(new CalDAV\Plugin()); + // Adding Auth plugin, and ensuring that we are logged in. + $authBackend = new DAV\Auth\Backend\Mock(); + $authPlugin = new DAV\Auth\Plugin($authBackend); + $this->server->addPlugin($authPlugin); + + // This forces a login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + } + + function testSimple() { + + $this->assertEquals([], $this->plugin->getFeatures()); + $this->assertEquals('notifications', $this->plugin->getPluginName()); + $this->assertEquals( + 'notifications', + $this->plugin->getPluginInfo()['name'] + ); + + } + + function testPrincipalProperties() { + + $httpRequest = new Request('GET', '/', ['Host' => 'sabredav.org']); + $this->server->httpRequest = $httpRequest; + + $props = $this->server->getPropertiesForPath('principals/admin', [ + '{' . Plugin::NS_CALENDARSERVER . '}notification-URL', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + + $this->assertArrayHasKey('{' . Plugin::NS_CALENDARSERVER . '}notification-URL', $props[0][200]); + $prop = $props[0][200]['{' . Plugin::NS_CALENDARSERVER . '}notification-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/admin/notifications/', $prop->getHref()); + + } + + function testNotificationProperties() { + + $notification = new Node( + $this->caldavBackend, + 'principals/user1', + new SystemStatus('foo', '"1"') + ); + $propFind = new DAV\PropFind('calendars/user1/notifications', [ + '{' . Plugin::NS_CALENDARSERVER . '}notificationtype', + ]); + + $this->plugin->propFind($propFind, $notification); + + $this->assertEquals( + $notification->getNotificationType(), + $propFind->get('{' . Plugin::NS_CALENDARSERVER . '}notificationtype') + ); + + } + + function testNotificationGet() { + + $notification = new Node( + $this->caldavBackend, + 'principals/user1', + new SystemStatus('foo', '"1"') + ); + + $server = new DAV\Server([$notification]); + $caldav = new Plugin(); + + $server->httpRequest = new Request('GET', '/foo.xml'); + $httpResponse = new HTTP\ResponseMock(); + $server->httpResponse = $httpResponse; + + $server->addPlugin($caldav); + + $caldav->httpGet($server->httpRequest, $server->httpResponse); + + $this->assertEquals(200, $httpResponse->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + 'ETag' => ['"1"'], + ], $httpResponse->getHeaders()); + + $expected = +' + + + +'; + + $this->assertXmlStringEqualsXmlString($expected, $httpResponse->getBodyAsString()); + + } + + function testGETPassthrough() { + + $server = new DAV\Server(); + $caldav = new Plugin(); + + $httpResponse = new HTTP\ResponseMock(); + $server->httpResponse = $httpResponse; + + $server->addPlugin($caldav); + + $this->assertNull($caldav->httpGet(new HTTP\Request('GET', '/foozz'), $server->httpResponse)); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php new file mode 100644 index 000000000000..859f6aa0c4e1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php @@ -0,0 +1,1086 @@ +caldavBackend = new Backend\Mock([ + [ + 'id' => 1, + 'uri' => 'UUID-123467', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar', + $caldavNS . 'calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + $caldavNS . 'supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT', 'VTODO']), + ], + [ + 'id' => 2, + 'uri' => 'UUID-123468', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar2', + $caldavNS . 'calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + $caldavNS . 'supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT', 'VTODO']), + ] + ], [ + 1 => [ + 'UUID-2345' => [ + 'calendardata' => TestUtil::getTestCalendarData(), + ] + ] + ]); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-read', ['principals/user1']); + $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-write', ['principals/user1']); + $principalBackend->addPrincipal([ + 'uri' => 'principals/admin/calendar-proxy-read', + ]); + $principalBackend->addPrincipal([ + 'uri' => 'principals/admin/calendar-proxy-write', + ]); + + $calendars = new CalendarRoot($principalBackend, $this->caldavBackend); + $principals = new Principal\Collection($principalBackend); + + $root = new DAV\SimpleCollection('root'); + $root->addChild($calendars); + $root->addChild($principals); + + $this->server = new DAV\Server($root); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + $this->server->setBaseUri('/'); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + // Adding ACL plugin + $aclPlugin = new DAVACL\Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $this->server->addPlugin($aclPlugin); + + // Adding Auth plugin, and ensuring that we are logged in. + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/user1'); + $authPlugin = new DAV\Auth\Plugin($authBackend); + $authPlugin->beforeMethod(new \Sabre\HTTP\Request(), new \Sabre\HTTP\Response()); + $this->server->addPlugin($authPlugin); + + // This forces a login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + } + + function testSimple() { + + $this->assertEquals(['MKCALENDAR'], $this->plugin->getHTTPMethods('calendars/user1/randomnewcalendar')); + $this->assertEquals(['calendar-access', 'calendar-proxy'], $this->plugin->getFeatures()); + $this->assertEquals( + 'caldav', + $this->plugin->getPluginInfo()['name'] + ); + + } + + function testUnknownMethodPassThrough() { + + $request = new HTTP\Request('MKBREAKFAST', '/'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status, 'Incorrect status returned. Full response body:' . $this->response->body); + + } + + function testReportPassThrough() { + + $request = new HTTP\Request('REPORT', '/', ['Content-Type' => 'application/xml']); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(415, $this->response->status); + + } + + function testMkCalendarBadLocation() { + + $request = new HTTP\Request('MKCALENDAR', '/blabla'); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(403, $this->response->status); + + } + + function testMkCalendarNoParentNode() { + + $request = new HTTP\Request('MKCALENDAR', '/doesntexist/calendar'); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(409, $this->response->status); + + } + + function testMkCalendarExistingCalendar() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/calendars/user1/UUID-123467', + ]); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(405, $this->response->status); + + } + + function testMkCalendarSucceed() { + + $request = new HTTP\Request('MKCALENDAR', '/calendars/user1/NEWCALENDAR'); + + $timezone = 'BEGIN:VCALENDAR +PRODID:-//Example Corp.//CalDAV Client//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:US-Eastern +LAST-MODIFIED:19870101T000000Z +BEGIN:STANDARD +DTSTART:19671029T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:Eastern Standard Time (US & Canada) +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19870405T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:Eastern Daylight Time (US & Canada) +END:DAYLIGHT +END:VTIMEZONE +END:VCALENDAR'; + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'Invalid response code received. Full response body: ' . $this->response->body); + + $calendars = $this->caldavBackend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3, count($calendars)); + + $newCalendar = null; + foreach ($calendars as $calendar) { + if ($calendar['uri'] === 'NEWCALENDAR') { + $newCalendar = $calendar; + break; + } + } + + $this->assertInternalType('array', $newCalendar); + + $keys = [ + 'uri' => 'NEWCALENDAR', + 'id' => null, + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar restricted to events.', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => $timezone, + '{DAV:}displayname' => 'Lisa\'s Events', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => null, + ]; + + foreach ($keys as $key => $value) { + + $this->assertArrayHasKey($key, $newCalendar); + + if (is_null($value)) continue; + $this->assertEquals($value, $newCalendar[$key]); + + } + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + $this->assertTrue($newCalendar[$sccs] instanceof Xml\Property\SupportedCalendarComponentSet); + $this->assertEquals(['VEVENT'], $newCalendar[$sccs]->getValue()); + + } + + function testMkCalendarEmptyBodySucceed() { + + $request = new HTTP\Request('MKCALENDAR', '/calendars/user1/NEWCALENDAR'); + + $request->setBody(''); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'Invalid response code received. Full response body: ' . $this->response->body); + + $calendars = $this->caldavBackend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3, count($calendars)); + + $newCalendar = null; + foreach ($calendars as $calendar) { + if ($calendar['uri'] === 'NEWCALENDAR') { + $newCalendar = $calendar; + break; + } + } + + $this->assertInternalType('array', $newCalendar); + + $keys = [ + 'uri' => 'NEWCALENDAR', + 'id' => null, + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => null, + ]; + + foreach ($keys as $key => $value) { + + $this->assertArrayHasKey($key, $newCalendar); + + if (is_null($value)) continue; + $this->assertEquals($value, $newCalendar[$key]); + + } + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + $this->assertTrue($newCalendar[$sccs] instanceof Xml\Property\SupportedCalendarComponentSet); + $this->assertEquals(['VEVENT', 'VTODO'], $newCalendar[$sccs]->getValue()); + + } + + function testMkCalendarBadXml() { + + $request = new HTTP\Request('MKCALENDAR', '/blabla'); + $body = 'This is not xml'; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status); + + } + + function testPrincipalProperties() { + + $httpRequest = new HTTP\Request('FOO', '/blabla', ['Host' => 'sabredav.org']); + $this->server->httpRequest = $httpRequest; + + $props = $this->server->getPropertiesForPath('/principals/user1', [ + '{' . Plugin::NS_CALDAV . '}calendar-home-set', + '{' . Plugin::NS_CALENDARSERVER . '}calendar-proxy-read-for', + '{' . Plugin::NS_CALENDARSERVER . '}calendar-proxy-write-for', + '{' . Plugin::NS_CALENDARSERVER . '}notification-URL', + '{' . Plugin::NS_CALENDARSERVER . '}email-address-set', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-home-set', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-home-set']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals('calendars/user1/', $prop->getHref()); + + $this->assertArrayHasKey('{http://calendarserver.org/ns/}calendar-proxy-read-for', $props[0][200]); + $prop = $props[0][200]['{http://calendarserver.org/ns/}calendar-proxy-read-for']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals(['principals/admin/'], $prop->getHrefs()); + + $this->assertArrayHasKey('{http://calendarserver.org/ns/}calendar-proxy-write-for', $props[0][200]); + $prop = $props[0][200]['{http://calendarserver.org/ns/}calendar-proxy-write-for']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals(['principals/admin/'], $prop->getHrefs()); + + $this->assertArrayHasKey('{' . Plugin::NS_CALENDARSERVER . '}email-address-set', $props[0][200]); + $prop = $props[0][200]['{' . Plugin::NS_CALENDARSERVER . '}email-address-set']; + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\EmailAddressSet', $prop); + $this->assertEquals(['user1.sabredav@sabredav.org'], $prop->getValue()); + + } + + function testSupportedReportSetPropertyNonCalendar() { + + $props = $this->server->getPropertiesForPath('/calendars/user1', [ + '{DAV:}supported-report-set', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = [ + '{DAV:}expand-property', + '{DAV:}principal-match', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set', + ]; + $this->assertEquals($value, $prop->getValue()); + + } + + /** + * @depends testSupportedReportSetPropertyNonCalendar + */ + function testSupportedReportSetProperty() { + + $props = $this->server->getPropertiesForPath('/calendars/user1/UUID-123467', [ + '{DAV:}supported-report-set', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = [ + '{urn:ietf:params:xml:ns:caldav}calendar-multiget', + '{urn:ietf:params:xml:ns:caldav}calendar-query', + '{urn:ietf:params:xml:ns:caldav}free-busy-query', + '{DAV:}expand-property', + '{DAV:}principal-match', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set' + ]; + $this->assertEquals($value, $prop->getValue()); + + } + + function testSupportedReportSetUserCalendars() { + + $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + + $props = $this->server->getPropertiesForPath('/calendars/user1', [ + '{DAV:}supported-report-set', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = [ + '{DAV:}sync-collection', + '{DAV:}expand-property', + '{DAV:}principal-match', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set', + ]; + $this->assertEquals($value, $prop->getValue()); + + } + + /** + * @depends testSupportedReportSetProperty + */ + function testCalendarMultiGetReport() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Invalid HTTP status received. Full response body'); + + $expectedIcal = TestUtil::getTestCalendarData(); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportExpand() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Invalid HTTP status received. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal = $expectedIcal->expand( + new DateTime('2011-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2011-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal = $expectedIcal->expand( + new DateTime('2000-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2010-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReportWindowsPhone() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467', [ + 'Depth' => '0', + 'User-Agent' => 'MSFT-WP/8.10.14219 (gzip)', + ]); + + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal = $expectedIcal->expand( + new DateTime('2000-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2010-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReportBadDepth() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467', [ + 'Depth' => '0', + ]); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarQueryReport + */ + function testCalendarQueryReportNoCalData() { + + $body = + '' . + '' . + '' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467', [ + 'Depth' => '1', + ]); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testCalendarQueryReport + */ + function testCalendarQueryReportNoFilters() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467'); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport1Object() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467/UUID-2345', ['Depth' => '0']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal = $expectedIcal->expand( + new DateTime('2000-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2010-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport1ObjectNoCalData() { + + $body = + '' . + '' . + '' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1/UUID-123467/UUID-2345', ['Depth' => '0']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + function testHTMLActionsPanel() { + + $output = ''; + $r = $this->server->emit('onHTMLActionsPanel', [$this->server->tree->getNodeForPath('calendars/user1'), &$output]); + $this->assertFalse($r); + + $this->assertTrue(!!strpos($output, 'Display name')); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportNoEnd() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status, 'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportNoStart() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status, 'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportEndBeforeStart() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = new HTTP\Request('REPORT', '/calendars/user1', ['Depth' => '1']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status, 'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testSupportedReportSetPropertyNonCalendar + */ + function testCalendarProperties() { + + $ns = '{urn:ietf:params:xml:ns:caldav}'; + $props = $this->server->getProperties('calendars/user1/UUID-123467', [ + $ns . 'max-resource-size', + $ns . 'supported-calendar-data', + $ns . 'supported-collation-set', + ]); + + $this->assertEquals([ + $ns . 'max-resource-size' => 10000000, + $ns . 'supported-calendar-data' => new Xml\Property\SupportedCalendarData(), + $ns . 'supported-collation-set' => new Xml\Property\SupportedCollationSet(), + ], $props); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php new file mode 100644 index 000000000000..23c2488257ff --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php @@ -0,0 +1,20 @@ +getChildForPrincipal([ + 'uri' => 'principals/admin', + ]); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\User', $r); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php new file mode 100644 index 000000000000..fe07f013108d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php @@ -0,0 +1,102 @@ + 'principal/user', + ]); + $this->backend = $backend; + return $principal; + + } + + function testGetName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-read', $i->getName()); + + } + function testGetDisplayName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-read', $i->getDisplayName()); + + } + + function testGetLastModified() { + + $i = $this->getInstance(); + $this->assertNull($i->getLastModified()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testDelete() { + + $i = $this->getInstance(); + $i->delete(); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetName() { + + $i = $this->getInstance(); + $i->setName('foo'); + + } + + function testGetAlternateUriSet() { + + $i = $this->getInstance(); + $this->assertEquals([], $i->getAlternateUriSet()); + + } + + function testGetPrincipalUri() { + + $i = $this->getInstance(); + $this->assertEquals('principal/user/calendar-proxy-read', $i->getPrincipalUrl()); + + } + + function testGetGroupMemberSet() { + + $i = $this->getInstance(); + $this->assertEquals([], $i->getGroupMemberSet()); + + } + + function testGetGroupMembership() { + + $i = $this->getInstance(); + $this->assertEquals([], $i->getGroupMembership()); + + } + + function testSetGroupMemberSet() { + + $i = $this->getInstance(); + $i->setGroupMemberSet(['principals/foo']); + + $expected = [ + $i->getPrincipalUrl() => ['principals/foo'] + ]; + + $this->assertEquals($expected, $this->backend->groupMembers); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php new file mode 100644 index 000000000000..6cdb9b30e2b5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php @@ -0,0 +1,40 @@ + 'principal/user', + ]); + $this->backend = $backend; + return $principal; + + } + + function testGetName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-write', $i->getName()); + + } + function testGetDisplayName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-write', $i->getDisplayName()); + + } + + function testGetPrincipalUri() { + + $i = $this->getInstance(); + $this->assertEquals('principal/user/calendar-proxy-write', $i->getPrincipalUrl()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php new file mode 100644 index 000000000000..420bb3b1afc9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php @@ -0,0 +1,127 @@ +addPrincipal([ + 'uri' => 'principals/user/calendar-proxy-read', + ]); + $backend->addPrincipal([ + 'uri' => 'principals/user/calendar-proxy-write', + ]); + $backend->addPrincipal([ + 'uri' => 'principals/user/random', + ]); + return new User($backend, [ + 'uri' => 'principals/user', + ]); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateFile() { + + $u = $this->getInstance(); + $u->createFile('test'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateDirectory() { + + $u = $this->getInstance(); + $u->createDirectory('test'); + + } + + function testGetChildProxyRead() { + + $u = $this->getInstance(); + $child = $u->getChild('calendar-proxy-read'); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyRead', $child); + + } + + function testGetChildProxyWrite() { + + $u = $this->getInstance(); + $child = $u->getChild('calendar-proxy-write'); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyWrite', $child); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound() { + + $u = $this->getInstance(); + $child = $u->getChild('foo'); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound2() { + + $u = $this->getInstance(); + $child = $u->getChild('random'); + + } + + function testGetChildren() { + + $u = $this->getInstance(); + $children = $u->getChildren(); + $this->assertEquals(2, count($children)); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyRead', $children[0]); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyWrite', $children[1]); + + } + + function testChildExist() { + + $u = $this->getInstance(); + $this->assertTrue($u->childExists('calendar-proxy-read')); + $this->assertTrue($u->childExists('calendar-proxy-write')); + $this->assertFalse($u->childExists('foo')); + + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user/calendar-proxy-write', + 'protected' => true, + ], + ]; + + $u = $this->getInstance(); + $this->assertEquals($expected, $u->getACL()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php new file mode 100644 index 000000000000..79e323f5c32d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php @@ -0,0 +1,92 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + $this->caldavBackend->createCalendar( + 'principals/user2', + 'default', + [ + + ] + ); + + } + + function testDelivery() { + + $request = new Request('PUT', '/calendars/user1/default/foo.ics'); + $request->setBody(<<server->on('schedule', function($message) use (&$messages) { + $messages[] = $message; + }); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus(), 'Incorrect status code received. Response body:' . $response->getBodyAsString()); + + $result = $this->request(new Request('GET', '/calendars/user1/default/foo.ics'))->getBody(); + $resultVObj = VObject\Reader::read($result); + + $this->assertEquals( + '1.2', + $resultVObj->VEVENT->ATTENDEE[1]['SCHEDULE-STATUS']->getValue() + ); + + $this->assertEquals(1, count($messages)); + $message = $messages[0]; + + $this->assertInstanceOf('\Sabre\VObject\ITip\Message', $message); + $this->assertEquals('mailto:user2.sabredav@sabredav.org', $message->recipient); + $this->assertEquals('Roxy Kesh', $message->recipientName); + $this->assertEquals('mailto:user1.sabredav@sabredav.org', $message->sender); + $this->assertEquals('Administrator', $message->senderName); + $this->assertEquals('REQUEST', $message->method); + + $this->assertEquals('REQUEST', $message->message->METHOD->getValue()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php new file mode 100644 index 000000000000..0e0b609a11c9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php @@ -0,0 +1,611 @@ + 'principals/user2', + 'id' => 1, + 'uri' => 'calendar1', + $caldavNS . 'calendar-timezone' => "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nEND:VTIMEZONE\r\nEND:VCALENDAR", + ], + [ + 'principaluri' => 'principals/user2', + 'id' => 2, + 'uri' => 'calendar2', + $caldavNS . 'schedule-calendar-transp' => new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT), + ], + ]; + $calendarobjects = [ + 1 => ['1.ics' => [ + 'uri' => '1.ics', + 'calendardata' => 'BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART:20110101T130000 +DURATION:PT1H +END:VEVENT +END:VCALENDAR', + 'calendarid' => 1, + ]], + 2 => ['2.ics' => [ + 'uri' => '2.ics', + 'calendardata' => 'BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART:20110101T080000 +DURATION:PT1H +END:VEVENT +END:VCALENDAR', + 'calendarid' => 2, + ]] + + ]; + + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $this->caldavBackend = new CalDAV\Backend\MockScheduling($calendars, $calendarobjects); + + $tree = [ + new DAVACL\PrincipalCollection($principalBackend), + new CalDAV\CalendarRoot($principalBackend, $this->caldavBackend), + ]; + + $this->request = HTTP\Sapi::createFromServerArray([ + 'CONTENT_TYPE' => 'text/calendar', + ]); + $this->response = new HTTP\ResponseMock(); + + $this->server = new DAV\Server($tree); + $this->server->httpRequest = $this->request; + $this->server->httpResponse = $this->response; + + $this->aclPlugin = new DAVACL\Plugin(); + $this->aclPlugin->allowUnauthenticatedAccess = false; + $this->server->addPlugin($this->aclPlugin); + + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/user1'); + $this->authPlugin = new DAV\Auth\Plugin($authBackend); + // Forcing authentication to work. + $this->authPlugin->beforeMethod($this->request, $this->response); + $this->server->addPlugin($this->authPlugin); + + // CalDAV plugin + $this->plugin = new CalDAV\Plugin(); + $this->server->addPlugin($this->plugin); + + // Scheduling plugin + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + } + + function testWrongContentType() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/plain'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + function testNotFound() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/blabla', + ['Content-Type' => 'text/calendar'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + function testNotOutbox() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/inbox', + ['Content-Type' => 'text/calendar'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoItipMethod() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException \Sabre\DAV\Exception\NotImplemented + */ + function testNoVFreeBusy() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testIncorrectOrganizer() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoAttendees() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoDTStart() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + function testSucceed() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + 'mailto:user3.sabredav@sabredav.org', + '2.0;Success', + '3.7;Could not find principal', + 'FREEBUSY:20110101T120000Z/20110101T130000Z', + ]; + + foreach ($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string) !== false, + 'The response body did not contain: ' . $string . 'Full response: ' . $this->response->body + ); + } + + $this->assertTrue( + strpos($this->response->body, 'FREEBUSY;FBTYPE=BUSY:20110101T080000Z/20110101T090000Z') == false, + 'The response body did contain free busy info from a transparent calendar.' + ); + + } + + /** + * Testing if the freebusy request still works, even if there are no + * calendars in the target users' account. + */ + function testSucceedNoCalendars() { + + // Deleting calendars + $this->caldavBackend->deleteCalendar(1); + $this->caldavBackend->deleteCalendar(2); + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '2.0;Success', + ]; + + foreach ($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string) !== false, + 'The response body did not contain: ' . $string . 'Full response: ' . $this->response->body + ); + } + + } + + function testNoCalendarHomeFound() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + // Removing the calendar home + $this->server->on('propFind', function(DAV\PropFind $propFind) { + + $propFind->set('{' . Plugin::NS_CALDAV . '}calendar-home-set', null, 403); + + }); + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '3.7;No calendar-home-set property found', + ]; + + foreach ($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string) !== false, + 'The response body did not contain: ' . $string . 'Full response: ' . $this->response->body + ); + } + + } + + function testNoInboxFound() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + // Removing the inbox + $this->server->on('propFind', function(DAV\PropFind $propFind) { + + $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-inbox-URL', null, 403); + + }); + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '3.7;No schedule-inbox-URL property found', + ]; + + foreach ($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string) !== false, + 'The response body did not contain: ' . $string . 'Full response: ' . $this->response->body + ); + } + + } + + function testSucceedUseVAVAILABILITY() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + // Adding VAVAILABILITY manually + $this->server->on('propFind', function(DAV\PropFind $propFind) { + + $propFind->handle('{' . Plugin::NS_CALDAV . '}calendar-availability', function() { + + $avail = <<assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '2.0;Success', + 'FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20110101T080000Z/20110101T090000Z', + 'FREEBUSY:20110101T120000Z/20110101T130000Z', + 'FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20110101T170000Z/20110101T180000Z', + ]; + + foreach ($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string) !== false, + 'The response body did not contain: ' . $string . 'Full response: ' . $this->response->body + ); + } + + } + + /* + function testNoPrivilege() { + + $this->markTestIncomplete('Currently there\'s no "no privilege" situation'); + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray(array( + 'CONTENT_TYPE' => 'text/calendar', + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + )); + + $body = <<server->httpRequest->setBody($body); + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => 'application/xml', + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '3.7;No calendar-home-set property found', + ]; + + foreach($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string)!==false, + 'The response body did not contain: ' . $string .'Full response: ' . $this->response->body + ); + } + + + }*/ + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php new file mode 100644 index 000000000000..028466093548 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php @@ -0,0 +1,50 @@ +emails[] = [ + 'to' => $to, + 'subject' => $subject, + 'body' => $body, + 'headers' => $headers, + ]; + + } + + function getSentEmails() { + + return $this->emails; + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php new file mode 100644 index 000000000000..7311999f5b41 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php @@ -0,0 +1,221 @@ +assertEquals( + 'imip', + $plugin->getPluginInfo()['name'] + ); + + } + + function testDeliverReply() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REPLY'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Re: Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=REPLY', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testDeliverReplyNoMailto() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'http://example.org/recipient'; + $message->recipientName = 'Recipient'; + $message->method = 'REPLY'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = []; + + $this->assertEquals($expected, $result); + + } + + function testDeliverRequest() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REQUEST'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=REQUEST', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testDeliverCancel() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'CANCEL'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Cancelled: Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=CANCEL', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + $this->assertEquals('1.1', substr($message->scheduleStatus, 0, 3)); + + } + + function schedule(Message $message) { + + $plugin = new IMip\MockPlugin('system@example.org'); + + $server = new Server(); + $server->addPlugin($plugin); + $server->emit('schedule', [$message]); + + return $plugin->getSentEmails(); + + } + + function testDeliverInsignificantRequest() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REQUEST'; + $message->significantChange = false; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = []; + $this->assertEquals($expected, $result); + $this->assertEquals('1.0', $message->getScheduleStatus()[0]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php new file mode 100644 index 000000000000..01c3488afd66 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php @@ -0,0 +1,136 @@ +assertEquals('inbox', $inbox->getName()); + $this->assertEquals([], $inbox->getChildren()); + $this->assertEquals('principals/user1', $inbox->getOwner()); + $this->assertEquals(null, $inbox->getGroup()); + + $this->assertEquals([ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{urn:ietf:params:xml:ns:caldav}schedule-deliver', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + ], $inbox->getACL()); + + $ok = false; + + } + + /** + * @depends testSetup + */ + function testGetChildren() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $backend->createSchedulingObject('principals/user1', 'schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + 1, + count($inbox->getChildren()) + ); + $this->assertInstanceOf('Sabre\CalDAV\Schedule\SchedulingObject', $inbox->getChildren()[0]); + $this->assertEquals( + 'schedule1.ics', + $inbox->getChildren()[0]->getName() + ); + + } + + /** + * @depends testGetChildren + */ + function testCreateFile() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $inbox->createFile('schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + 1, + count($inbox->getChildren()) + ); + $this->assertInstanceOf('Sabre\CalDAV\Schedule\SchedulingObject', $inbox->getChildren()[0]); + $this->assertEquals( + 'schedule1.ics', + $inbox->getChildren()[0]->getName() + ); + + } + + /** + * @depends testSetup + */ + function testCalendarQuery() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $backend->createSchedulingObject('principals/user1', 'schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + ['schedule1.ics'], + $inbox->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false + ]) + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php new file mode 100644 index 000000000000..3ab2c2288cf0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php @@ -0,0 +1,134 @@ + 'POST', + 'REQUEST_URI' => '/notfound', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + + $this->assertHTTPStatus(501, $req); + + } + + function testPostPassThruNotTextCalendar() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + ]); + + $this->assertHTTPStatus(501, $req); + + } + + function testPostPassThruNoOutBox() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + + $this->assertHTTPStatus(501, $req); + + } + + function testInvalidIcalBody() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + $req->setBody('foo'); + + $this->assertHTTPStatus(400, $req); + + } + + function testNoVEVENT() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + + $body = [ + 'BEGIN:VCALENDAR', + 'BEGIN:VTIMEZONE', + 'END:VTIMEZONE', + 'END:VCALENDAR', + ]; + + $req->setBody(implode("\r\n", $body)); + + $this->assertHTTPStatus(400, $req); + + } + + function testNoMETHOD() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + + $body = [ + 'BEGIN:VCALENDAR', + 'BEGIN:VEVENT', + 'END:VEVENT', + 'END:VCALENDAR', + ]; + + $req->setBody(implode("\r\n", $body)); + + $this->assertHTTPStatus(400, $req); + + } + + function testUnsupportedMethod() { + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + ]); + + $body = [ + 'BEGIN:VCALENDAR', + 'METHOD:PUBLISH', + 'BEGIN:VEVENT', + 'END:VEVENT', + 'END:VCALENDAR', + ]; + + $req->setBody(implode("\r\n", $body)); + + $this->assertHTTPStatus(501, $req); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php new file mode 100644 index 000000000000..04d4b12379e7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php @@ -0,0 +1,48 @@ +assertEquals('outbox', $outbox->getName()); + $this->assertEquals([], $outbox->getChildren()); + $this->assertEquals('principals/user1', $outbox->getOwner()); + $this->assertEquals(null, $outbox->getGroup()); + + $this->assertEquals([ + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + ], $outbox->getACL()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php new file mode 100644 index 000000000000..cee911b6e0b5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php @@ -0,0 +1,39 @@ +assertEquals( + 'caldav-schedule', + $plugin->getPluginInfo()['name'] + ); + + } + + function testOptions() { + + $plugin = new Plugin(); + $expected = [ + 'calendar-auto-schedule', + 'calendar-availability', + ]; + $this->assertEquals($expected, $plugin->getFeatures()); + + } + + function testGetHTTPMethods() { + + $this->assertEquals([], $this->caldavSchedulePlugin->getHTTPMethods('notfound')); + $this->assertEquals([], $this->caldavSchedulePlugin->getHTTPMethods('calendars/user1')); + $this->assertEquals(['POST'], $this->caldavSchedulePlugin->getHTTPMethods('calendars/user1/outbox')); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php new file mode 100644 index 000000000000..2d0391893ef7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php @@ -0,0 +1,146 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + $this->principalBackend->addPrincipal([ + 'uri' => 'principals/user1/calendar-proxy-read' + ]); + + } + + function testPrincipalProperties() { + + $props = $this->server->getPropertiesForPath('/principals/user1', [ + '{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', + '{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', + '{urn:ietf:params:xml:ns:caldav}calendar-user-type', + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/outbox/', $prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/inbox/', $prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals(['mailto:user1.sabredav@sabredav.org', '/principals/user1/'], $prop->getHrefs()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-type', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']; + $this->assertEquals('INDIVIDUAL', $prop); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL']; + $this->assertEquals('calendars/user1/default/', $prop->getHref()); + + } + function testPrincipalPropertiesBadPrincipal() { + + $props = $this->server->getPropertiesForPath('principals/user1/calendar-proxy-read', [ + '{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', + '{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', + '{urn:ietf:params:xml:ns:caldav}calendar-user-type', + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + $this->assertArrayHasKey(404, $props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', $props[0][404]); + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', $props[0][404]); + + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals(['/principals/user1/calendar-proxy-read/'], $prop->getHrefs()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-type', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']; + $this->assertEquals('INDIVIDUAL', $prop); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', $props[0][404]); + + } + function testNoDefaultCalendar() { + + foreach ($this->caldavBackend->getCalendarsForUser('principals/user1') as $calendar) { + $this->caldavBackend->deleteCalendar($calendar['id']); + } + $props = $this->server->getPropertiesForPath('/principals/user1', [ + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(404, $props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', $props[0][404]); + + } + + /** + * There are two properties for availability. The server should + * automatically map the old property to the standard property. + */ + function testAvailabilityMapping() { + + $path = 'calendars/user1/inbox'; + $oldProp = '{http://calendarserver.org/ns/}calendar-availability'; + $newProp = '{urn:ietf:params:xml:ns:caldav}calendar-availability'; + $value1 = 'first value'; + $value2 = 'second value'; + + // Storing with the old name + $this->server->updateProperties($path, [ + $oldProp => $value1 + ]); + + // Retrieving with the new name + $this->assertEquals( + [$newProp => $value1], + $this->server->getProperties($path, [$newProp]) + ); + + // Storing with the new name + $this->server->updateProperties($path, [ + $newProp => $value2 + ]); + + // Retrieving with the old name + $this->assertEquals( + [$oldProp => $value2], + $this->server->getProperties($path, [$oldProp]) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php new file mode 100644 index 000000000000..870f14c14476 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php @@ -0,0 +1,71 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'shared', + [ + 'share-access' => DAV\Sharing\Plugin::ACCESS_READWRITE + ] + ); + $this->caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + + } + + function testPrincipalProperties() { + + $props = $this->server->getPropertiesForPath('/principals/user1', [ + '{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', + '{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', + '{urn:ietf:params:xml:ns:caldav}calendar-user-type', + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + ]); + + $this->assertArrayHasKey(0, $props); + $this->assertArrayHasKey(200, $props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/outbox/', $prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/inbox/', $prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals(['mailto:user1.sabredav@sabredav.org', '/principals/user1/'], $prop->getHrefs()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-type', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']; + $this->assertEquals('INDIVIDUAL', $prop); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', $props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL']; + $this->assertEquals('calendars/user1/default/', $prop->getHref()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php new file mode 100644 index 000000000000..8123c685c762 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php @@ -0,0 +1,666 @@ + 'principals/user1', + 'uri' => 'cal', + ], + [ + 'principaluri' => 'principals/user2', + 'uri' => 'cal', + ], + ]; + + function setUp() { + + $this->calendarObjectUri = '/calendars/user1/cal/object.ics'; + + parent::setUp(); + + } + + function testNewInvite() { + + $newObject = <<deliver(null, $newObject); + $this->assertItemsInInbox('user2', 1); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + + function testNewOnWrongCollection() { + + $newObject = <<calendarObjectUri = '/calendars/user1/object.ics'; + $this->deliver(null, $newObject); + $this->assertItemsInInbox('user2', 0); + + + } + function testNewInviteSchedulingDisabled() { + + $newObject = <<deliver(null, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + function testUpdatedInvite() { + + $newObject = <<deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + + } + function testUpdatedInviteSchedulingDisabled() { + + $newObject = <<deliver($oldObject, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + + function testUpdatedInviteWrongPath() { + + $newObject = <<calendarObjectUri = '/calendars/user1/inbox/foo.ics'; + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testDeletedInvite() { + + $newObject = null; + + $oldObject = <<deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + + } + + function testDeletedInviteSchedulingDisabled() { + + $newObject = null; + + $oldObject = <<deliver($oldObject, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + + /** + * A MOVE request will trigger an unbind on a scheduling resource. + * + * However, we must not treat it as a cancellation, it just got moved to a + * different calendar. + */ + function testUnbindIgnoredOnMove() { + + $newObject = null; + + $oldObject = <<server->httpRequest->setMethod('MOVE'); + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testDeletedInviteWrongUrl() { + + $newObject = null; + + $oldObject = <<calendarObjectUri = '/calendars/user1/inbox/foo.ics'; + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testReply() { + + $oldObject = <<putPath('calendars/user2/cal/foo.ics', $oldObject); + + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + $this->assertItemsInInbox('user1', 0); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + + + + function testInviteUnknownUser() { + + $newObject = <<deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + + function testInviteNoInboxUrl() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-inbox-URL', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + + function testInviteNoCalendarHomeSet() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}calendar-home-set', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + function testInviteNoDefaultCalendar() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-default-calendar-URL', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + function testInviteNoScheduler() { + + $newObject = <<server->removeAllListeners('schedule'); + $this->deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + function testInviteNoACLPlugin() { + + $this->setupACL = false; + parent::setUp(); + + $newObject = <<deliver(null, $newObject); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $newObject + ); + + } + + protected $calendarObjectUri; + + function deliver($oldObject, &$newObject, $disableScheduling = false) { + + $this->server->httpRequest->setUrl($this->calendarObjectUri); + if ($disableScheduling) { + $this->server->httpRequest->setHeader('Schedule-Reply', 'F'); + } + + if ($oldObject && $newObject) { + // update + $this->putPath($this->calendarObjectUri, $oldObject); + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $newObject); + rewind($stream); + $modified = false; + + $this->server->emit('beforeWriteContent', [ + $this->calendarObjectUri, + $this->server->tree->getNodeForPath($this->calendarObjectUri), + &$stream, + &$modified + ]); + if ($modified) { + $newObject = $stream; + } + + } elseif ($oldObject && !$newObject) { + // delete + $this->putPath($this->calendarObjectUri, $oldObject); + + $this->caldavSchedulePlugin->beforeUnbind( + $this->calendarObjectUri + ); + } else { + + // create + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $newObject); + rewind($stream); + $modified = false; + $this->server->emit('beforeCreateFile', [ + $this->calendarObjectUri, + &$stream, + $this->server->tree->getNodeForPath(dirname($this->calendarObjectUri)), + &$modified + ]); + + if ($modified) { + $newObject = $stream; + } + } + + } + + + /** + * Creates or updates a node at the specified path. + * + * This circumvents sabredav's internal server apis, so all events and + * access control is skipped. + * + * @param string $path + * @param string $data + * @return void + */ + function putPath($path, $data) { + + list($parent, $base) = \Sabre\HTTP\UrlUtil::splitPath($path); + $parentNode = $this->server->tree->getNodeForPath($parent); + + /* + if ($parentNode->childExists($base)) { + $childNode = $parentNode->getChild($base); + $childNode->put($data); + } else {*/ + $parentNode->createFile($base, $data); + //} + + } + + function assertItemsInInbox($user, $count) { + + $inboxNode = $this->server->tree->getNodeForPath('calendars/' . $user . '/inbox'); + $this->assertEquals($count, count($inboxNode->getChildren())); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php new file mode 100644 index 000000000000..be83cd08110b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php @@ -0,0 +1,378 @@ +markTestSkipped('SQLite driver is not available'); + $this->backend = new Backend\MockScheduling(); + + $this->data = <<data = <<inbox = new Inbox($this->backend, 'principals/user1'); + $this->inbox->createFile('item1.ics', $this->data); + + } + + function teardown() { + + unset($this->inbox); + unset($this->backend); + + } + + function testSetup() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $this->assertInternalType('string', $children[0]->getName()); + $this->assertInternalType('string', $children[0]->get()); + $this->assertInternalType('string', $children[0]->getETag()); + $this->assertEquals('text/calendar; charset=utf-8', $children[0]->getContentType()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg1() { + + $obj = new SchedulingObject( + new Backend\MockScheduling([], []), + [], + [] + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg2() { + + $obj = new SchedulingObject( + new Backend\MockScheduling([], []), + [], + ['calendarid' => '1'] + ); + + } + + /** + * @depends testSetup + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + function testPut() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $children[0]->put(''); + + } + + /** + * @depends testSetup + */ + function testDelete() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $obj->delete(); + + $children2 = $this->inbox->getChildren(); + $this->assertEquals(count($children) - 1, count($children2)); + + } + + /** + * @depends testSetup + */ + function testGetLastModified() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $lastMod = $obj->getLastModified(); + $this->assertTrue(is_int($lastMod) || ctype_digit($lastMod) || is_null($lastMod)); + + } + + /** + * @depends testSetup + */ + function testGetSize() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $size = $obj->getSize(); + $this->assertInternalType('int', $size); + + } + + function testGetOwner() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertEquals('principals/user1', $obj->getOwner()); + + } + + function testGetGroup() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertNull($obj->getGroup()); + + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + ]; + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertEquals($expected, $obj->getACL()); + + } + + function testDefaultACL() { + + $backend = new Backend\MockScheduling([], []); + $calendarObject = new SchedulingObject($backend, ['calendarid' => 1, 'uri' => 'foo', 'principaluri' => 'principals/user1']); + $expected = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ], + ]; + $this->assertEquals($expected, $calendarObject->getACL()); + + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $obj->setACL([]); + + } + + function testGet() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $this->assertEquals($this->data, $obj->get()); + + } + + function testGetRefetch() { + + $backend = new Backend\MockScheduling(); + $backend->createSchedulingObject('principals/user1', 'foo', 'foo'); + + $obj = new SchedulingObject($backend, [ + 'calendarid' => 1, + 'uri' => 'foo', + 'principaluri' => 'principals/user1', + ]); + + $this->assertEquals('foo', $obj->get()); + + } + + function testGetEtag1() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'etag' => 'bar', + 'calendarid' => 1 + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + + $this->assertEquals('bar', $obj->getETag()); + + } + + function testGetEtag2() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + + $this->assertEquals('"' . md5('foo') . '"', $obj->getETag()); + + } + + function testGetSupportedPrivilegesSet() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertNull($obj->getSupportedPrivilegeSet()); + + } + + function testGetSize1() { + + $objectInfo = [ + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals(3, $obj->getSize()); + + } + + function testGetSize2() { + + $objectInfo = [ + 'uri' => 'foo', + 'calendarid' => 1, + 'size' => 4, + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals(4, $obj->getSize()); + + } + + function testGetContentType() { + + $objectInfo = [ + 'uri' => 'foo', + 'calendarid' => 1, + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals('text/calendar; charset=utf-8', $obj->getContentType()); + + } + + function testGetContentType2() { + + $objectInfo = [ + 'uri' => 'foo', + 'calendarid' => 1, + 'component' => 'VEVENT', + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals('text/calendar; charset=utf-8; component=VEVENT', $obj->getContentType()); + + } + function testGetACL2() { + + $objectInfo = [ + 'uri' => 'foo', + 'calendarid' => 1, + 'acl' => [], + ]; + + $backend = new Backend\MockScheduling([], []); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals([], $obj->getACL()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php new file mode 100644 index 000000000000..f71c195238c3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php @@ -0,0 +1,176 @@ + 1, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/original', + '{http://sabredav.org/ns}owner-principal' => 'principals/owner', + '{http://sabredav.org/ns}read-only' => false, + 'share-access' => Sharing\Plugin::ACCESS_READWRITE, + 'principaluri' => 'principals/sharee', + ]; + } + + $this->backend = new Backend\MockSharing( + [$props], + [], + [] + ); + + $sharee = new Sharee(); + $sharee->href = 'mailto:removeme@example.org'; + $sharee->properties['{DAV:}displayname'] = 'To be removed'; + $sharee->access = Sharing\Plugin::ACCESS_READ; + $this->backend->updateInvites(1, [$sharee]); + + return new SharedCalendar($this->backend, $props); + + } + + function testGetInvites() { + + $sharee = new Sharee(); + $sharee->href = 'mailto:removeme@example.org'; + $sharee->properties['{DAV:}displayname'] = 'To be removed'; + $sharee->access = Sharing\Plugin::ACCESS_READ; + $sharee->inviteStatus = Sharing\Plugin::INVITE_NORESPONSE; + + $this->assertEquals( + [$sharee], + $this->getInstance()->getInvites() + ); + + } + + function testGetOwner() { + $this->assertEquals('principals/sharee', $this->getInstance()->getOwner()); + } + + function testGetACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => 'principals/sharee', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => 'principals/sharee/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + ]; + + $this->assertEquals($expected, $this->getInstance()->getACL()); + + } + + function testGetChildACL() { + + $expected = [ + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee/calendar-proxy-read', + 'protected' => true, + ], + + ]; + + $this->assertEquals($expected, $this->getInstance()->getChildACL()); + + } + + function testUpdateInvites() { + + $instance = $this->getInstance(); + $newSharees = [ + new Sharee(), + new Sharee() + ]; + $newSharees[0]->href = 'mailto:test@example.org'; + $newSharees[0]->properties['{DAV:}displayname'] = 'Foo Bar'; + $newSharees[0]->comment = 'Booh'; + $newSharees[0]->access = Sharing\Plugin::ACCESS_READWRITE; + + $newSharees[1]->href = 'mailto:removeme@example.org'; + $newSharees[1]->access = Sharing\Plugin::ACCESS_NOACCESS; + + $instance->updateInvites($newSharees); + + $expected = [ + clone $newSharees[0] + ]; + $expected[0]->inviteStatus = Sharing\Plugin::INVITE_NORESPONSE; + $this->assertEquals($expected, $instance->getInvites()); + + } + + function testPublish() { + + $instance = $this->getInstance(); + $this->assertNull($instance->setPublishStatus(true)); + $this->assertNull($instance->setPublishStatus(false)); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php new file mode 100644 index 000000000000..9589176a3683 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php @@ -0,0 +1,396 @@ +caldavCalendars = [ + [ + 'principaluri' => 'principals/user1', + 'id' => 1, + 'uri' => 'cal1', + ], + [ + 'principaluri' => 'principals/user1', + 'id' => 2, + 'uri' => 'cal2', + 'share-access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE, + ], + [ + 'principaluri' => 'principals/user1', + 'id' => 3, + 'uri' => 'cal3', + ], + ]; + + parent::setUp(); + + // Making the logged in user an admin, for full access: + $this->aclPlugin->adminPrincipals[] = 'principals/user2'; + + } + + function testSimple() { + + $this->assertInstanceOf('Sabre\\CalDAV\\SharingPlugin', $this->server->getPlugin('caldav-sharing')); + $this->assertEquals( + 'caldav-sharing', + $this->caldavSharingPlugin->getPluginInfo()['name'] + ); + + } + + /** + * @expectedException \LogicException + */ + function testSetupWithoutCoreSharingPlugin() { + + $server = new DAV\Server(); + $server->addPlugin( + new SharingPlugin() + ); + + } + + function testGetFeatures() { + + $this->assertEquals(['calendarserver-sharing'], $this->caldavSharingPlugin->getFeatures()); + + } + + function testBeforeGetShareableCalendar() { + + // Forcing the server to authenticate: + $this->authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + $props = $this->server->getProperties('calendars/user1/cal1', [ + '{' . Plugin::NS_CALENDARSERVER . '}invite', + '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', + ]); + + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\Invite', $props['{' . Plugin::NS_CALENDARSERVER . '}invite']); + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\AllowedSharingModes', $props['{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes']); + + } + + function testBeforeGetSharedCalendar() { + + $props = $this->server->getProperties('calendars/user1/cal2', [ + '{' . Plugin::NS_CALENDARSERVER . '}shared-url', + '{' . Plugin::NS_CALENDARSERVER . '}invite', + ]); + + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\Invite', $props['{' . Plugin::NS_CALENDARSERVER . '}invite']); + //$this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $props['{' . Plugin::NS_CALENDARSERVER . '}shared-url']); + + } + + function testUpdateResourceType() { + + $this->caldavBackend->updateInvites(1, + [ + new Sharee([ + 'href' => 'mailto:joe@example.org', + ]) + ] + ); + $result = $this->server->updateProperties('calendars/user1/cal1', [ + '{DAV:}resourcetype' => new DAV\Xml\Property\ResourceType(['{DAV:}collection']) + ]); + + $this->assertEquals([ + '{DAV:}resourcetype' => 200 + ], $result); + + $this->assertEquals(0, count($this->caldavBackend->getInvites(1))); + + } + + function testUpdatePropertiesPassThru() { + + $result = $this->server->updateProperties('calendars/user1/cal3', [ + '{DAV:}foo' => 'bar', + ]); + + $this->assertEquals([ + '{DAV:}foo' => 200, + ], $result); + + } + + function testUnknownMethodNoPOST() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PATCH', + 'REQUEST_URI' => '/', + ]); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnknownMethodNoXML() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/', + 'CONTENT_TYPE' => 'text/plain', + ]); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnknownMethodNoNode() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/foo', + 'CONTENT_TYPE' => 'text/xml', + ]); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testShareRequest() { + + $request = new HTTP\Request('POST', '/calendars/user1/cal1', ['Content-Type' => 'text/xml']); + + $xml = << + + + mailto:joe@example.org + Joe Shmoe + + + + mailto:nancy@example.org + + +RRR; + + $request->setBody($xml); + + $response = $this->request($request, 200); + + $this->assertEquals( + [ + new Sharee([ + 'href' => 'mailto:joe@example.org', + 'properties' => [ + '{DAV:}displayname' => 'Joe Shmoe', + ], + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE, + 'comment' => '', + ]), + ], + $this->caldavBackend->getInvites(1) + ); + + // Wiping out tree cache + $this->server->tree->markDirty(''); + + // Verifying that the calendar is now marked shared. + $props = $this->server->getProperties('calendars/user1/cal1', ['{DAV:}resourcetype']); + $this->assertTrue( + $props['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared-owner') + ); + + } + + function testShareRequestNoShareableCalendar() { + + $request = new HTTP\Request( + 'POST', + '/calendars/user1/cal2', + ['Content-Type' => 'text/xml'] + ); + + $xml = ' + + + mailto:joe@example.org + Joe Shmoe + + + + mailto:nancy@example.org + + +'; + + $request->setBody($xml); + + $response = $this->request($request, 403); + + } + + function testInviteReply() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1', + 'CONTENT_TYPE' => 'text/xml', + ]); + + $xml = ' + + /principals/owner + + +'; + + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + } + + function testInviteBadXML() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1', + 'CONTENT_TYPE' => 'text/xml', + ]); + + $xml = ' + + +'; + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(400, $response->status, $response->body); + + } + + function testInviteWrongUrl() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal1', + 'CONTENT_TYPE' => 'text/xml', + ]); + + $xml = ' + + /principals/owner + +'; + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + // If the plugin did not handle this request, it must ensure that the + // body is still accessible by other plugins. + $this->assertEquals($xml, $request->getBody(true)); + + } + + function testPublish() { + + $request = new HTTP\Request('POST', '/calendars/user1/cal1', ['Content-Type' => 'text/xml']); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(202, $response->status, $response->body); + + } + + + function testUnpublish() { + + $request = new HTTP\Request( + 'POST', + '/calendars/user1/cal1', + ['Content-Type' => 'text/xml'] + ); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + } + + function testPublishWrongUrl() { + + $request = new HTTP\Request( + 'POST', + '/calendars/user1', + ['Content-Type' => 'text/xml'] + ); + + $xml = ' + +'; + + $request->setBody($xml); + $this->request($request, 501); + + } + + function testUnpublishWrongUrl() { + + $request = new HTTP\Request( + 'POST', + '/calendars/user1', + ['Content-Type' => 'text/xml'] + ); + $xml = ' + +'; + + $request->setBody($xml); + + $this->request($request, 501); + + } + + function testUnknownXmlDoc() { + + + $request = new HTTP\Request( + 'POST', + '/calendars/user1/cal2', + ['Content-Type' => 'text/xml'] + ); + + $xml = ' +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php new file mode 100644 index 000000000000..8ad0f8ac574b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php @@ -0,0 +1,123 @@ + + + + + + + + + + #1C4587FF + Jewish holidays + Foo + 19 + + webcal://www.example.org/ + + P1W + + + + +XML; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + $request = new Request('MKCOL', '/calendars/user1/subscription1', $headers, $body); + + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $subscriptions = $this->caldavBackend->getSubscriptionsForUser('principals/user1'); + $this->assertSubscription($subscriptions[0]); + + + } + /** + * OS X 10.9.2 and up + */ + function testMKCALENDAR() { + + $body = << + + + + + + + + + + + + P1W + + webcal://www.example.org/ + + #1C4587FF + 19 + Foo + + Jewish holidays + + + +XML; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + $request = new Request('MKCALENDAR', '/calendars/user1/subscription1', $headers, $body); + + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $subscriptions = $this->caldavBackend->getSubscriptionsForUser('principals/user1'); + $this->assertSubscription($subscriptions[0]); + + // Also seeing if it works when calling this as a PROPFIND. + $this->assertEquals([ + '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '', + ], + $this->server->getProperties('calendars/user1/subscription1', ['{http://calendarserver.org/ns/}subscribed-strip-alarms']) + ); + + + } + + function assertSubscription($subscription) { + + $this->assertEquals('', $subscription['{http://calendarserver.org/ns/}subscribed-strip-attachments']); + $this->assertEquals('', $subscription['{http://calendarserver.org/ns/}subscribed-strip-todos']); + $this->assertEquals('#1C4587FF', $subscription['{http://apple.com/ns/ical/}calendar-color']); + $this->assertEquals('Jewish holidays', $subscription['{DAV:}displayname']); + $this->assertEquals('Foo', $subscription['{urn:ietf:params:xml:ns:caldav}calendar-description']); + $this->assertEquals('19', $subscription['{http://apple.com/ns/ical/}calendar-order']); + $this->assertEquals('webcal://www.example.org/', $subscription['{http://calendarserver.org/ns/}source']->getHref()); + $this->assertEquals('P1W', $subscription['{http://apple.com/ns/ical/}refreshrate']); + $this->assertEquals('subscription1', $subscription['uri']); + $this->assertEquals('principals/user1', $subscription['principaluri']); + $this->assertEquals('webcal://www.example.org/', $subscription['source']); + $this->assertEquals(['principals/user1', 1], $subscription['id']); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php new file mode 100644 index 000000000000..dc6d2d5f04c5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php @@ -0,0 +1,50 @@ +addPlugin($plugin); + + $this->assertEquals( + '{http://calendarserver.org/ns/}subscribed', + $server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] + ); + $this->assertEquals( + 'Sabre\\DAV\\Xml\\Property\\Href', + $server->xml->elementMap['{http://calendarserver.org/ns/}source'] + ); + + $this->assertEquals( + ['calendarserver-subscribed'], + $plugin->getFeatures() + ); + + $this->assertEquals( + 'subscriptions', + $plugin->getPluginInfo()['name'] + ); + + } + + function testPropFind() { + + $propName = '{http://calendarserver.org/ns/}subscribed-strip-alarms'; + $propFind = new PropFind('foo', [$propName]); + $propFind->set($propName, null, 200); + + $plugin = new Plugin(); + $plugin->propFind($propFind, new \Sabre\DAV\SimpleCollection('hi')); + + $this->assertFalse(is_null($propFind->get($propName))); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php new file mode 100644 index 000000000000..559d526cd1d0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php @@ -0,0 +1,131 @@ + new Href('http://example.org/src', false), + 'lastmodified' => date('2013-04-06 11:40:00'), // tomorrow is my birthday! + '{DAV:}displayname' => 'displayname', + ]; + + + $id = $caldavBackend->createSubscription('principals/user1', 'uri', array_merge($info, $override)); + $subInfo = $caldavBackend->getSubscriptionsForUser('principals/user1'); + + $this->assertEquals(1, count($subInfo)); + $subscription = new Subscription($caldavBackend, $subInfo[0]); + + $this->backend = $caldavBackend; + return $subscription; + + } + + function testValues() { + + $sub = $this->getSub(); + + $this->assertEquals('uri', $sub->getName()); + $this->assertEquals(date('2013-04-06 11:40:00'), $sub->getLastModified()); + $this->assertEquals([], $sub->getChildren()); + + $this->assertEquals( + [ + '{DAV:}displayname' => 'displayname', + '{http://calendarserver.org/ns/}source' => new Href('http://example.org/src', false), + ], + $sub->getProperties(['{DAV:}displayname', '{http://calendarserver.org/ns/}source']) + ); + + $this->assertEquals('principals/user1', $sub->getOwner()); + $this->assertNull($sub->getGroup()); + + $acl = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ] + ]; + $this->assertEquals($acl, $sub->getACL()); + + $this->assertNull($sub->getSupportedPrivilegeSet()); + + } + + function testValues2() { + + $sub = $this->getSub([ + 'lastmodified' => null, + ]); + + $this->assertEquals(null, $sub->getLastModified()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $sub = $this->getSub(); + $sub->setACL([]); + + } + + function testDelete() { + + $sub = $this->getSub(); + $sub->delete(); + + $this->assertEquals([], $this->backend->getSubscriptionsForUser('principals1/user1')); + + } + + function testUpdateProperties() { + + $sub = $this->getSub(); + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'foo', + ]); + $sub->propPatch($propPatch); + $this->assertTrue($propPatch->commit()); + + $this->assertEquals( + 'foo', + $this->backend->getSubscriptionsForUser('principals/user1')[0]['{DAV:}displayname'] + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testBadConstruct() { + + $caldavBackend = new \Sabre\CalDAV\Backend\MockSubscriptionSupport([], []); + new Subscription($caldavBackend, []); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php new file mode 100644 index 000000000000..673d39c0aa56 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php @@ -0,0 +1,189 @@ +createCalendar( + 'principals/user1', + 'UUID-123467', + [ + '{DAV:}displayname' => 'user1 calendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + ] + ); + $backend->createCalendar( + 'principals/user1', + 'UUID-123468', + [ + '{DAV:}displayname' => 'user1 calendar2', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + ] + ); + $backend->createCalendarObject($calendarId, 'UUID-2345', self::getTestCalendarData()); + return $backend; + + } + + static function getTestCalendarData($type = 1) { + + $calendarData = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Asia/Seoul +BEGIN:DAYLIGHT +TZOFFSETFROM:+0900 +RRULE:FREQ=YEARLY;UNTIL=19880507T150000Z;BYMONTH=5;BYDAY=2SU +DTSTART:19870510T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+1000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+1000 +DTSTART:19881009T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+0900 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20100225T154229Z +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +TRANSP:TRANSPARENT +SUMMARY:Something here +DTSTAMP:20100228T130202Z'; + + switch ($type) { + case 1 : + $calendarData .= "\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDTEND;TZID=Asia/Seoul:20100223T070000\n"; + break; + case 2 : + $calendarData .= "\nDTSTART:20100223T060000\nDTEND:20100223T070000\n"; + break; + case 3 : + $calendarData .= "\nDTSTART;VALUE=DATE:20100223\nDTEND;VALUE=DATE:20100223\n"; + break; + case 4 : + $calendarData .= "\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDURATION:PT1H\n"; + break; + case 5 : + $calendarData .= "\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDURATION:-P5D\n"; + break; + case 6 : + $calendarData .= "\nDTSTART;VALUE=DATE:20100223\n"; + break; + case 7 : + $calendarData .= "\nDTSTART;VALUE=DATETIME:20100223T060000\n"; + break; + + // No DTSTART, so intentionally broken + case 'X' : + $calendarData .= "\n"; + break; + } + + + $calendarData .= 'ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com +SEQUENCE:2 +END:VEVENT +END:VCALENDAR'; + + return $calendarData; + + } + + static function getTestTODO($type = 'due') { + + switch ($type) { + + case 'due' : + $extra = "DUE:20100104T000000Z"; + break; + case 'due2' : + $extra = "DUE:20060104T000000Z"; + break; + case 'due_date' : + $extra = "DUE;VALUE=DATE:20060104"; + break; + case 'due_tz' : + $extra = "DUE;TZID=Asia/Seoul:20060104T000000Z"; + break; + case 'due_dtstart' : + $extra = "DTSTART:20050223T060000Z\nDUE:20060104T000000Z"; + break; + case 'due_dtstart2' : + $extra = "DTSTART:20090223T060000Z\nDUE:20100104T000000Z"; + break; + case 'dtstart' : + $extra = 'DTSTART:20100223T060000Z'; + break; + case 'dtstart2' : + $extra = 'DTSTART:20060223T060000Z'; + break; + case 'dtstart_date' : + $extra = 'DTSTART;VALUE=DATE:20100223'; + break; + case 'dtstart_tz' : + $extra = 'DTSTART;TZID=Asia/Seoul:20100223T060000Z'; + break; + case 'dtstart_duration' : + $extra = "DTSTART:20061023T060000Z\nDURATION:PT1H"; + break; + case 'dtstart_duration2' : + $extra = "DTSTART:20101023T060000Z\nDURATION:PT1H"; + break; + case 'completed' : + $extra = 'COMPLETED:20060601T000000Z'; + break; + case 'completed2' : + $extra = 'COMPLETED:20090601T000000Z'; + break; + case 'created' : + $extra = 'CREATED:20060601T000000Z'; + break; + case 'created2' : + $extra = 'CREATED:20090601T000000Z'; + break; + case 'completedcreated' : + $extra = "CREATED:20060601T000000Z\nCOMPLETED:20070101T000000Z"; + break; + case 'completedcreated2' : + $extra = "CREATED:20090601T000000Z\nCOMPLETED:20100101T000000Z"; + break; + case 'notime' : + $extra = 'X-FILLER:oh hello'; + break; + default : + throw new Exception('Unknown type: ' . $type); + + } + + $todo = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Example Corp.//CalDAV Client//EN +BEGIN:VTODO +DTSTAMP:20060205T235335Z +' . $extra . ' +STATUS:NEEDS-ACTION +SUMMARY:Task #1 +UID:DDDEEB7915FA61233B861457@example.com +BEGIN:VALARM +ACTION:AUDIO +TRIGGER;RELATED=START:-PT10M +END:VALARM +END:VTODO +END:VCALENDAR'; + + return $todo; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php new file mode 100644 index 000000000000..629df90c119d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php @@ -0,0 +1,406 @@ + 'calendar1', + 'principaluri' => 'principals/admin', + 'uri' => 'calendar1', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT', 'VTODO', 'VJOURNAL']), + ], + [ + 'id' => 'calendar2', + 'principaluri' => 'principals/admin', + 'uri' => 'calendar2', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VJOURNAL']), + ] + ]; + + $this->calBackend = new Backend\Mock($calendars, []); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = [ + new CalendarRoot($principalBackend, $this->calBackend), + ]; + + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + $plugin = new Plugin(); + $this->server->addPlugin($plugin); + + $response = new HTTP\ResponseMock(); + $this->server->httpResponse = $response; + + } + + function request(HTTP\Request $request) { + + $this->server->httpRequest = $request; + $this->server->exec(); + + return $this->server->httpResponse; + + } + + function testCreateFile() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testCreateFileValid() { + + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics', + ['Prefer' => 'handling=strict'] + ); + + $ics = <<setBody($ics); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5($ics) . '"'], + ], $response->getHeaders()); + + $expected = [ + 'uri' => 'blabla.ics', + 'calendardata' => $ics, + 'calendarid' => 'calendar1', + 'lastmodified' => null, + ]; + + $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics')); + + } + + function testCreateFileNoVersion() { + + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics', + ['Prefer' => 'handling=strict'] + ); + + $ics = <<setBody($ics); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileNoVersionFixed() { + + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics', + ['Prefer' => 'handling=lenient'] + ); + + $ics = <<setBody($ics); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + 'X-Sabre-Ew-Gross' => ['iCalendar validation warning: VERSION MUST appear exactly once in a VCALENDAR component'], + ], $response->getHeaders()); + + $ics = << 'blabla.ics', + 'calendardata' => $ics, + 'calendarid' => 'calendar1', + 'lastmodified' => null, + ]; + + $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics')); + + } + + function testCreateFileNoComponents() { + + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics', + ['Prefer' => 'handling=strict'] + ); + $ics = <<setBody($ics); + + $response = $this->request($request); + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileNoUID() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileVCard() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFile2Components() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VJOURNAL\r\nUID:foo\r\nEND:VJOURNAL\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFile2UIDS() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:bar\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileWrongComponent() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VFREEBUSY\r\nUID:foo\r\nEND:VFREEBUSY\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFile() { + + $this->calBackend->createCalendarObject('calendar1', 'blabla.ics', 'foo'); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + ]); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testUpdateFileParsableBody() { + + $this->calBackend->createCalendarObject('calendar1', 'blabla.ics', 'foo'); + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics' + ); + $ics = <<setBody($ics); + $response = $this->request($request); + + $this->assertEquals(204, $response->status); + + $expected = [ + 'uri' => 'blabla.ics', + 'calendardata' => $ics, + 'calendarid' => 'calendar1', + 'lastmodified' => null, + ]; + + $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1', 'blabla.ics')); + + } + + function testCreateFileInvalidComponent() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFileInvalidComponent() { + + $this->calBackend->createCalendarObject('calendar2', 'blabla.ics', 'foo'); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + /** + * What we are testing here, is if we send in a latin1 character, the + * server should automatically transform this into UTF-8. + * + * More importantly. If any transformation happens, the etag must no longer + * be returned by the server. + */ + function testCreateFileModified() { + + $request = new HTTP\Request( + 'PUT', + '/calendars/admin/calendar1/blabla.ics' + ); + $ics = <<setBody($ics); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $this->assertNull($response->getHeader('ETag')); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php new file mode 100644 index 000000000000..cd700893d670 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php @@ -0,0 +1,146 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $simpleExpected = '' . "\n" . ''; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $writer->write($notification); + $writer->endElement(); + + $this->assertEquals($simpleExpected, $writer->outputMemory()); + + $writer = new Writer(); + $writer->contextUri = '/'; + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + 'DAV:' => 'd', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $notification->xmlSerializeFull($writer); + $writer->endElement(); + + $this->assertXmlStringEqualsXmlString($expected, $writer->outputMemory()); + + + } + + function dataProvider() { + + $dtStamp = new \DateTime('2012-01-01 00:00:00 GMT'); + return [ + [ + [ + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'inReplyTo' => 'bar', + 'href' => 'mailto:foo@example.org', + 'type' => DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'hostUrl' => 'calendar' + ], +<< + + 20120101T000000Z + + foo + bar + mailto:foo@example.org + + + /calendar + + + + +FOO + ], + [ + [ + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'inReplyTo' => 'bar', + 'href' => 'mailto:foo@example.org', + 'type' => DAV\Sharing\Plugin::INVITE_DECLINED, + 'hostUrl' => 'calendar', + 'summary' => 'Summary!' + ], +<< + + 20120101T000000Z + + foo + bar + mailto:foo@example.org + + + /calendar + + Summary! + + + +FOO + ], + + ]; + + } + + /** + * @expectedException InvalidArgumentException + */ + function testMissingArg() { + + new InviteReply([]); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownArg() { + + new InviteReply([ + 'foo-i-will-break' => true, + + 'id' => 1, + 'etag' => '"bla"', + 'href' => 'abc', + 'dtStamp' => 'def', + 'inReplyTo' => 'qrs', + 'type' => 'ghi', + 'hostUrl' => 'jkl', + ]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php new file mode 100644 index 000000000000..f03093916e95 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php @@ -0,0 +1,165 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $simpleExpected = '' . "\n"; + $this->namespaceMap['http://calendarserver.org/ns/'] = 'cs'; + + $xml = $this->write($notification); + + $this->assertXmlStringEqualsXmlString($simpleExpected, $xml); + + $this->namespaceMap['urn:ietf:params:xml:ns:caldav'] = 'cal'; + $xml = $this->writeFull($notification); + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + function dataProvider() { + + $dtStamp = new \DateTime('2012-01-01 00:00:00', new \DateTimeZone('GMT')); + return [ + [ + [ + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => DAV\Sharing\Plugin::INVITE_ACCEPTED, + 'readOnly' => true, + 'hostUrl' => 'calendar', + 'organizer' => 'principal/user1', + 'commonName' => 'John Doe', + 'summary' => 'Awesome stuff!' + ], +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + Awesome stuff! + + + + + /principal/user1 + John Doe + + John Doe + + + +FOO + ], + [ + [ + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => DAV\Sharing\Plugin::INVITE_NORESPONSE, + 'readOnly' => true, + 'hostUrl' => 'calendar', + 'organizer' => 'principal/user1', + 'firstName' => 'Foo', + 'lastName' => 'Bar', + ], +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + + + + + /principal/user1 + Foo + Bar + + Foo + Bar + + + +FOO + ], + + ]; + + } + + /** + * @expectedException InvalidArgumentException + */ + function testMissingArg() { + + new Invite([]); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownArg() { + + new Invite([ + 'foo-i-will-break' => true, + + 'id' => 1, + 'etag' => '"bla"', + 'href' => 'abc', + 'dtStamp' => 'def', + 'type' => 'ghi', + 'readOnly' => true, + 'hostUrl' => 'jkl', + 'organizer' => 'mno', + ]); + + } + + function writeFull($input) { + + $writer = new Writer(); + $writer->contextUri = '/'; + $writer->namespaceMap = $this->namespaceMap; + $writer->openMemory(); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $input->xmlSerializeFull($writer); + $writer->endElement(); + return $writer->outputMemory(); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php new file mode 100644 index 000000000000..1f9034340f5e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php @@ -0,0 +1,69 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $writer->write($notification); + $writer->endElement(); + $this->assertXmlStringEqualsXmlString($expected1, $writer->outputMemory()); + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + 'DAV:' => 'd', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $notification->xmlSerializeFull($writer); + $writer->endElement(); + $this->assertXmlStringEqualsXmlString($expected2, $writer->outputMemory()); + + } + + function dataProvider() { + + return [ + + [ + new SystemStatus('foo', '"1"'), + '' . "\n" . '' . "\n", + '' . "\n" . '' . "\n", + ], + [ + new SystemStatus('foo', '"1"', SystemStatus::TYPE_MEDIUM, 'bar'), + '' . "\n" . '' . "\n", + '' . "\n" . 'bar' . "\n", + ], + [ + new SystemStatus('foo', '"1"', SystemStatus::TYPE_LOW, null, 'http://example.org/'), + '' . "\n" . '' . "\n", + '' . "\n" . 'http://example.org/' . "\n", + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php new file mode 100644 index 000000000000..0602d4f24f44 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\AllowedSharingModes', $sccs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new AllowedSharingModes(true, true); + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + + +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php new file mode 100644 index 000000000000..30651a080e4d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php @@ -0,0 +1,40 @@ + 'cs', + 'DAV:' => 'd', + ]; + + function testSimple() { + + $eas = new EmailAddressSet(['foo@example.org']); + $this->assertEquals(['foo@example.org'], $eas->getValue()); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new EmailAddressSet(['foo@example.org']); + + $xml = $this->write([ + '{DAV:}root' => $property + ]); + + $this->assertXmlStringEqualsXmlString( +' + +foo@example.org +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php new file mode 100644 index 000000000000..1397dcca2b7d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php @@ -0,0 +1,112 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + + } + + function testSimple() { + + $invite = new Invite([]); + $this->assertInstanceOf('Sabre\CalDAV\Xml\Property\Invite', $invite); + $this->assertEquals([], $invite->getValue()); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new Invite([ + new Sharee([ + 'href' => 'mailto:thedoctor@example.org', + 'properties' => ['{DAV:}displayname' => 'The Doctor'], + 'inviteStatus' => SP::INVITE_ACCEPTED, + 'access' => SP::ACCESS_SHAREDOWNER, + ]), + new Sharee([ + 'href' => 'mailto:user1@example.org', + 'inviteStatus' => SP::INVITE_ACCEPTED, + 'access' => SP::ACCESS_READWRITE, + ]), + new Sharee([ + 'href' => 'mailto:user2@example.org', + 'properties' => ['{DAV:}displayname' => 'John Doe'], + 'inviteStatus' => SP::INVITE_DECLINED, + 'access' => SP::ACCESS_READ, + ]), + new Sharee([ + 'href' => 'mailto:user3@example.org', + 'properties' => ['{DAV:}displayname' => 'Joe Shmoe'], + 'inviteStatus' => SP::INVITE_NORESPONSE, + 'access' => SP::ACCESS_READ, + 'comment' => 'Something, something', + ]), + new Sharee([ + 'href' => 'mailto:user4@example.org', + 'properties' => ['{DAV:}displayname' => 'Hoe Boe'], + 'inviteStatus' => SP::INVITE_INVALID, + 'access' => SP::ACCESS_READ, + ]), + ]); + + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + mailto:thedoctor@example.org + The Doctor + + + + + + + mailto:user1@example.org + + + + + + + mailto:user2@example.org + John Doe + + + + + + + mailto:user3@example.org + Joe Shmoe + Something, something + + + + + + + mailto:user4@example.org + Hoe Boe + + +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php new file mode 100644 index 000000000000..729db4569e5f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php @@ -0,0 +1,118 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + + } + + function testSimple() { + + $prop = new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE); + $this->assertEquals( + ScheduleCalendarTransp::OPAQUE, + $prop->getValue() + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testBadValue() { + + new ScheduleCalendarTransp('ahhh'); + + } + + /** + * @depends testSimple + */ + function testSerializeOpaque() { + + $property = new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + + /** + * @depends testSimple + */ + function testSerializeTransparent() { + + $property = new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + + function testUnserializeTransparent() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'] + ); + + $this->assertEquals( + new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT), + $result['value'] + ); + + } + + function testUnserializeOpaque() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'] + ); + + $this->assertEquals( + new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE), + $result['value'] + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php new file mode 100644 index 000000000000..1acc402d36bf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php @@ -0,0 +1,102 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + } + + function testSimple() { + + $prop = new SupportedCalendarComponentSet(['VEVENT']); + $this->assertEquals( + ['VEVENT'], + $prop->getValue() + ); + + } + + function testMultiple() { + + $prop = new SupportedCalendarComponentSet(['VEVENT', 'VTODO']); + $this->assertEquals( + ['VEVENT', 'VTODO'], + $prop->getValue() + ); + + } + + /** + * @depends testSimple + * @depends testMultiple + */ + function testSerialize() { + + $property = new SupportedCalendarComponentSet(['VEVENT', 'VTODO']); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + + +', $xml); + + } + + function testUnserialize() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'] + ); + + $this->assertEquals( + new SupportedCalendarComponentSet(['VEVENT', 'VTODO']), + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\Xml\ParseException + */ + function testUnserializeEmpty() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'] + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php new file mode 100644 index 000000000000..442b6a059f40 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php @@ -0,0 +1,36 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\SupportedCalendarData', $sccs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $property = new SupportedCalendarData(); + + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php new file mode 100644 index 000000000000..e009fb6cd85c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php @@ -0,0 +1,37 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\SupportedCollationSet', $scs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedCollationSet(); + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + +i;ascii-casemap +i;octet +i;unicode-casemap +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php new file mode 100644 index 000000000000..d5e87db854b6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php @@ -0,0 +1,369 @@ + 'Sabre\\CalDAV\\Xml\\Request\CalendarQueryReport', + ]; + + function testDeserialize() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $calendarQueryReport = new CalendarQueryReport(); + $calendarQueryReport->properties = [ + '{DAV:}getetag', + ]; + $calendarQueryReport->filters = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $this->assertEquals( + $calendarQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoFilter() { + + $xml = << + + + + + +XML; + + $this->parse($xml); + + } + + function testDeserializeComplex() { + + $xml = << + + + + + + + + + + + + + + + + + + + + + + hi + + + + + + + + + + Hello + + + + + +XML; + + $result = $this->parse($xml); + $calendarQueryReport = new CalendarQueryReport(); + $calendarQueryReport->version = '2.0'; + $calendarQueryReport->contentType = 'application/json+calendar'; + $calendarQueryReport->properties = [ + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:caldav}calendar-data', + ]; + $calendarQueryReport->expand = [ + 'start' => new DateTimeImmutable('2015-01-01 00:00:00', new DateTimeZone('UTC')), + 'end' => new DateTimeImmutable('2016-01-01 00:00:00', new DateTimeZone('UTC')), + ]; + $calendarQueryReport->filters = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'is-not-defined' => true, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ], + ], + 'prop-filters' => [ + [ + 'name' => 'UID', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [ + [ + 'name' => 'X-PARAM', + 'is-not-defined' => false, + 'text-match' => null, + ], + [ + 'name' => 'X-PARAM2', + 'is-not-defined' => true, + 'text-match' => null, + ], + [ + 'name' => 'X-PARAM3', + 'is-not-defined' => false, + 'text-match' => [ + 'negate-condition' => true, + 'collation' => 'i;ascii-casemap', + 'value' => 'hi', + ], + ], + ], + ], + [ + 'name' => 'X-PROP2', + 'is-not-defined' => true, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP3', + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new DateTimeImmutable('2015-01-01 00:00:00', new DateTimeZone('UTC')), + 'end' => new DateTimeImmutable('2016-01-01 00:00:00', new DateTimeZone('UTC')), + ], + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP4', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => [ + 'negate-condition' => false, + 'collation' => 'i;ascii-casemap', + 'value' => 'Hello', + ], + 'param-filters' => [], + ], + ], + 'time-range' => [ + 'start' => new DateTimeImmutable('2015-01-01 00:00:00', new DateTimeZone('UTC')), + 'end' => new DateTimeImmutable('2016-01-01 00:00:00', new DateTimeZone('UTC')), + ] + ], + ], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $this->assertEquals( + $calendarQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeDoubleTopCompFilter() { + + $xml = << + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeMissingExpandEnd() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeExpandEndBeforeStart() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangeOnVCALENDAR() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangeEndBeforeStart() { + + $xml = << + + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangePropEndBeforeStart() { + + $xml = << + + + + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php new file mode 100644 index 000000000000..b0770899914f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php @@ -0,0 +1,78 @@ + 'Sabre\\CalDAV\\Xml\\Request\\InviteReply', + ]; + + function testDeserialize() { + + $xml = << + + /principal/1 + /calendar/1 + + blabla + Summary + +XML; + + $result = $this->parse($xml); + $inviteReply = new InviteReply('/principal/1', '/calendar/1', 'blabla', 'Summary', DAV\Sharing\Plugin::INVITE_ACCEPTED); + + $this->assertEquals( + $inviteReply, + $result['value'] + ); + + } + + function testDeserializeDeclined() { + + $xml = << + + /principal/1 + /calendar/1 + + blabla + Summary + +XML; + + $result = $this->parse($xml); + $inviteReply = new InviteReply('/principal/1', '/calendar/1', 'blabla', 'Summary', DAV\Sharing\Plugin::INVITE_DECLINED); + + $this->assertEquals( + $inviteReply, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoHostUrl() { + + $xml = << + + /principal/1 + + blabla + Summary + +XML; + + $this->parse($xml); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php new file mode 100644 index 000000000000..73a2c3a13d2d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php @@ -0,0 +1,83 @@ + 'Sabre\\CalDAV\\Xml\\Request\\Share', + ]; + + function testDeserialize() { + + $xml = << + + + mailto:eric@example.com + Eric York + Shared workspace + + + + mailto:foo@bar + + +XML; + + $result = $this->parse($xml); + $share = new Share([ + new Sharee([ + 'href' => 'mailto:eric@example.com', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE, + 'properties' => [ + '{DAV:}displayname' => 'Eric York', + ], + 'comment' => 'Shared workspace', + ]), + new Sharee([ + 'href' => 'mailto:foo@bar', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS, + ]), + ]); + + $this->assertEquals( + $share, + $result['value'] + ); + + } + + function testDeserializeMinimal() { + + $xml = << + + + mailto:eric@example.com + + + +XML; + + $result = $this->parse($xml); + $share = new Share([ + new Sharee([ + 'href' => 'mailto:eric@example.com', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + ]), + ]); + + $this->assertEquals( + $share, + $result['value'] + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php new file mode 100644 index 000000000000..552e2ba77e5a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php @@ -0,0 +1,43 @@ +backend = new Backend\Mock(); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = [ + new AddressBookRoot($principalBackend, $this->backend), + new DAVACL\PrincipalCollection($principalBackend) + ]; + + $this->plugin = new Plugin(); + $this->plugin->directories = ['directory']; + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->addPlugin($this->plugin); + $this->server->debugExceptions = true; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php new file mode 100644 index 000000000000..871f4a457c13 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php @@ -0,0 +1,159 @@ +backend = new Backend\Mock(); + $this->s = new AddressBookHome( + $this->backend, + 'principals/user1' + ); + + } + + function testGetName() { + + $this->assertEquals('user1', $this->s->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->s->setName('user2'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testDelete() { + + $this->s->delete(); + + } + + function testGetLastModified() { + + $this->assertNull($this->s->getLastModified()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateFile() { + + $this->s->createFile('bla'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->s->createDirectory('bla'); + + } + + function testGetChild() { + + $child = $this->s->getChild('book1'); + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBook', $child); + $this->assertEquals('book1', $child->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChild404() { + + $this->s->getChild('book2'); + + } + + function testGetChildren() { + + $children = $this->s->getChildren(); + $this->assertEquals(2, count($children)); + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBook', $children[0]); + $this->assertEquals('book1', $children[0]->getName()); + + } + + function testCreateExtendedCollection() { + + $resourceType = [ + '{' . Plugin::NS_CARDDAV . '}addressbook', + '{DAV:}collection', + ]; + $this->s->createExtendedCollection('book2', new MkCol($resourceType, ['{DAV:}displayname' => 'a-book 2'])); + + $this->assertEquals([ + 'id' => 'book2', + 'uri' => 'book2', + '{DAV:}displayname' => 'a-book 2', + 'principaluri' => 'principals/user1', + ], $this->backend->addressBooks[2]); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + */ + function testCreateExtendedCollectionInvalid() { + + $resourceType = [ + '{DAV:}collection', + ]; + $this->s->createExtendedCollection('book2', new MkCol($resourceType, ['{DAV:}displayname' => 'a-book 2'])); + + } + + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->s->getOwner()); + $this->assertNull($this->s->getGroup()); + $this->assertEquals([ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ], $this->s->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $this->s->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->s->getSupportedPrivilegeSet() + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php new file mode 100644 index 000000000000..f8da38a16dc1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php @@ -0,0 +1,355 @@ + '1'] + ); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + ], + ], + '/addressbooks/user1/book1/card2' => [ + 404 => [ + '{DAV:}getetag' => null, + ], + ] + ], $result); + + + } + + function testQueryDepth0() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1/card1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + ], + ], + ], $result); + + + } + + function testQueryNoMatch() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1', + ['Depth' => '1'] + ); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([], $result); + + } + + function testQueryLimit() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody( +' + + + + + + + + 1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + ], + ], + ], $result); + + + } + + function testJson() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1/card1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $vobjVersion = \Sabre\VObject\Version::VERSION; + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject ' . $vobjVersion . '\/\/EN"],["uid",{},"text","12345"]]]', + ], + ], + ], $result); + + } + + function testVCard4() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1/card1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $vobjVersion = \Sabre\VObject\Version::VERSION; + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject $vobjVersion//EN\r\nUID:12345\r\nEND:VCARD\r\n", + ], + ], + ], $result); + + } + + function testAddressBookDepth0() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1', + ['Depth' => '0'] + ); + + $request->setBody( + ' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(415, $response->status, 'Incorrect status code. Full response body:' . $response->body); + } + + function testAddressBookProperties() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book3', + ['Depth' => '1'] + ); + + $request->setBody( + ' + + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([ + '/addressbooks/user1/book3/card3' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nFN:Test-Card\nEMAIL;TYPE=home:bar@example.org\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:3.0\r\nUID:12345\r\nFN:Test-Card\r\nEND:VCARD\r\n", + ], + ], + ], $result); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php new file mode 100644 index 000000000000..fc20480f2d4a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php @@ -0,0 +1,31 @@ +assertEquals('addressbooks', $root->getName()); + + } + + function testGetChildForPrincipal() { + + $pBackend = new DAVACL\PrincipalBackend\Mock(); + $cBackend = new Backend\Mock(); + $root = new AddressBookRoot($pBackend, $cBackend); + + $children = $root->getChildren(); + $this->assertEquals(3, count($children)); + + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBookHome', $children[0]); + $this->assertEquals('user1', $children[0]->getName()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php new file mode 100644 index 000000000000..1f0064dd38a6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php @@ -0,0 +1,194 @@ +backend = new Backend\Mock(); + $this->ab = new AddressBook( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + '{DAV:}displayname' => 'd-name', + 'principaluri' => 'principals/user1', + ] + ); + + } + + function testGetName() { + + $this->assertEquals('book1', $this->ab->getName()); + + } + + function testGetChild() { + + $card = $this->ab->getChild('card1'); + $this->assertInstanceOf('Sabre\\CardDAV\\Card', $card); + $this->assertEquals('card1', $card->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound() { + + $card = $this->ab->getChild('card3'); + + } + + function testGetChildren() { + + $cards = $this->ab->getChildren(); + $this->assertEquals(2, count($cards)); + + $this->assertEquals('card1', $cards[0]->getName()); + $this->assertEquals('card2', $cards[1]->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->ab->createDirectory('name'); + + } + + function testCreateFile() { + + $file = fopen('php://memory', 'r+'); + fwrite($file, 'foo'); + rewind($file); + $this->ab->createFile('card2', $file); + + $this->assertEquals('foo', $this->backend->cards['foo']['card2']); + + } + + function testDelete() { + + $this->ab->delete(); + $this->assertEquals(1, count($this->backend->addressBooks)); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->ab->setName('foo'); + + } + + function testGetLastModified() { + + $this->assertNull($this->ab->getLastModified()); + + } + + function testUpdateProperties() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'barrr', + ]); + $this->ab->propPatch($propPatch); + $this->assertTrue($propPatch->commit()); + + $this->assertEquals('barrr', $this->backend->addressBooks[0]['{DAV:}displayname']); + + } + + function testGetProperties() { + + $props = $this->ab->getProperties(['{DAV:}displayname']); + $this->assertEquals([ + '{DAV:}displayname' => 'd-name', + ], $props); + + } + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->ab->getOwner()); + $this->assertNull($this->ab->getGroup()); + $this->assertEquals([ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ], $this->ab->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $this->ab->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->ab->getSupportedPrivilegeSet() + ); + + } + + function testGetSyncTokenNoSyncSupport() { + + $this->assertNull($this->ab->getSyncToken()); + + } + function testGetChangesNoSyncSupport() { + + $this->assertNull($this->ab->getChanges(1, null)); + + } + + function testGetSyncToken() { + + $this->driver = 'sqlite'; + $this->dropTables(['addressbooks', 'cards', 'addressbookchanges']); + $this->createSchema('addressbooks'); + $backend = new Backend\PDO( + $this->getPDO() + ); + $ab = new AddressBook($backend, ['id' => 1, '{DAV:}sync-token' => 2]); + $this->assertEquals(2, $ab->getSyncToken()); + } + + function testGetSyncToken2() { + + $this->driver = 'sqlite'; + $this->dropTables(['addressbooks', 'cards', 'addressbookchanges']); + $this->createSchema('addressbooks'); + $backend = new Backend\PDO( + $this->getPDO() + ); + $ab = new AddressBook($backend, ['id' => 1, '{http://sabredav.org/ns}sync-token' => 2]); + $this->assertEquals(2, $ab->getSyncToken()); + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php new file mode 100644 index 000000000000..f62bfb1ae689 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php @@ -0,0 +1,373 @@ +dropTables([ + 'addressbooks', + 'cards', + 'addressbookchanges', + ]); + $this->createSchema('addressbooks'); + $pdo = $this->getPDO(); + + $this->backend = new PDO($pdo); + $pdo->exec("INSERT INTO addressbooks (principaluri, displayname, uri, description, synctoken) VALUES ('principals/user1', 'book1', 'book1', 'addressbook 1', 1)"); + $pdo->exec("INSERT INTO cards (addressbookid, carddata, uri, lastmodified, etag, size) VALUES (1, 'card1', 'card1', 0, '" . md5('card1') . "', 5)"); + + } + + function testGetAddressBooksForUser() { + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1 + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testUpdateAddressBookInvalidProp() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + '{DAV:}foo' => 'bar', + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1 + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testUpdateAddressBookNoProps() { + + $propPatch = new PropPatch([ + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + $this->assertTrue($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1 + ] + ]; + + $this->assertEquals($expected, $result); + + + } + + function testUpdateAddressBookSuccess() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + '{http://calendarserver.org/ns/}getctag' => 2, + '{http://sabredav.org/ns}sync-token' => 2 + ] + ]; + + $this->assertEquals($expected, $result); + + + } + + function testDeleteAddressBook() { + + $this->backend->deleteAddressBook(1); + + $this->assertEquals([], $this->backend->getAddressBooksForUser('principals/user1')); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testCreateAddressBookUnsupportedProp() { + + $this->backend->createAddressBook('principals/user1', 'book2', [ + '{DAV:}foo' => 'bar', + ]); + + } + + function testCreateAddressBookSuccess() { + + $this->backend->createAddressBook('principals/user1', 'book2', [ + '{DAV:}displayname' => 'book2', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 2', + ]); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1, + ], + [ + 'id' => 2, + 'uri' => 'book2', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book2', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 2', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1, + ] + ]; + $result = $this->backend->getAddressBooksForUser('principals/user1'); + $this->assertEquals($expected, $result); + + } + + function testGetCards() { + + $result = $this->backend->getCards(1); + + $expected = [ + [ + 'id' => 1, + 'uri' => 'card1', + 'lastmodified' => 0, + 'etag' => '"' . md5('card1') . '"', + 'size' => 5 + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testGetCard() { + + $result = $this->backend->getCard(1, 'card1'); + + $expected = [ + 'id' => 1, + 'uri' => 'card1', + 'carddata' => 'card1', + 'lastmodified' => 0, + 'etag' => '"' . md5('card1') . '"', + 'size' => 5 + ]; + + if (is_resource($result['carddata'])) { + $result['carddata'] = stream_get_contents($result['carddata']); + } + + $this->assertEquals($expected, $result); + + } + + /** + * @depends testGetCard + */ + function testCreateCard() { + + $result = $this->backend->createCard(1, 'card2', 'data2'); + $this->assertEquals('"' . md5('data2') . '"', $result); + $result = $this->backend->getCard(1, 'card2'); + $this->assertEquals(2, $result['id']); + $this->assertEquals('card2', $result['uri']); + if (is_resource($result['carddata'])) { + $result['carddata'] = stream_get_contents($result['carddata']); + } + $this->assertEquals('data2', $result['carddata']); + + } + + /** + * @depends testCreateCard + */ + function testGetMultiple() { + + $result = $this->backend->createCard(1, 'card2', 'data2'); + $result = $this->backend->createCard(1, 'card3', 'data3'); + $check = [ + [ + 'id' => 1, + 'uri' => 'card1', + 'carddata' => 'card1', + 'lastmodified' => 0, + ], + [ + 'id' => 2, + 'uri' => 'card2', + 'carddata' => 'data2', + 'lastmodified' => time(), + ], + [ + 'id' => 3, + 'uri' => 'card3', + 'carddata' => 'data3', + 'lastmodified' => time(), + ], + ]; + + $result = $this->backend->getMultipleCards(1, ['card1', 'card2', 'card3']); + + foreach ($check as $index => $node) { + + foreach ($node as $k => $v) { + + $expected = $v; + $actual = $result[$index][$k]; + + switch ($k) { + case 'lastmodified' : + $this->assertInternalType('int', $actual); + break; + case 'carddata' : + if (is_resource($actual)) { + $actual = stream_get_contents($actual); + } + // No break intended. + default : + $this->assertEquals($expected, $actual); + break; + } + + } + + } + + + } + + /** + * @depends testGetCard + */ + function testUpdateCard() { + + $result = $this->backend->updateCard(1, 'card1', 'newdata'); + $this->assertEquals('"' . md5('newdata') . '"', $result); + + $result = $this->backend->getCard(1, 'card1'); + $this->assertEquals(1, $result['id']); + if (is_resource($result['carddata'])) { + $result['carddata'] = stream_get_contents($result['carddata']); + } + $this->assertEquals('newdata', $result['carddata']); + + } + + /** + * @depends testGetCard + */ + function testDeleteCard() { + + $this->backend->deleteCard(1, 'card1'); + $result = $this->backend->getCard(1, 'card1'); + $this->assertFalse($result); + + } + + function testGetChanges() { + + $backend = $this->backend; + $id = $backend->createAddressBook( + 'principals/user1', + 'bla', + [] + ); + $result = $backend->getChangesForAddressBook($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 1, + "added" => [], + 'modified' => [], + 'deleted' => [], + ], $result); + + $currentToken = $result['syncToken']; + + $dummyCard = "BEGIN:VCARD\r\nEND:VCARD\r\n"; + + $backend->createCard($id, "card1.ics", $dummyCard); + $backend->createCard($id, "card2.ics", $dummyCard); + $backend->createCard($id, "card3.ics", $dummyCard); + $backend->updateCard($id, "card1.ics", $dummyCard); + $backend->deleteCard($id, "card2.ics"); + + $result = $backend->getChangesForAddressBook($id, $currentToken, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => ["card1.ics"], + 'deleted' => ["card2.ics"], + "added" => ["card3.ics"], + ], $result); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php new file mode 100644 index 000000000000..8638dc74a658 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php @@ -0,0 +1,258 @@ +addressBooks = $addressBooks; + $this->cards = $cards; + + if (is_null($this->addressBooks)) { + $this->addressBooks = [ + [ + 'id' => 'foo', + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'd-name', + ], + [ + 'id' => 'bar', + 'uri' => 'book3', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'd-name', + ], + ]; + + $card2 = fopen('php://memory', 'r+'); + fwrite($card2, "BEGIN:VCARD\nVERSION:3.0\nUID:45678\nEND:VCARD"); + rewind($card2); + $this->cards = [ + 'foo' => [ + 'card1' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", + 'card2' => $card2, + ], + 'bar' => [ + 'card3' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nFN:Test-Card\nEMAIL;TYPE=home:bar@example.org\nEND:VCARD", + ], + ]; + } + + } + + + function getAddressBooksForUser($principalUri) { + + $books = []; + foreach ($this->addressBooks as $book) { + if ($book['principaluri'] === $principalUri) { + $books[] = $book; + } + } + return $books; + + } + + /** + * Updates properties for an address book. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param string $addressBookId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { + + foreach ($this->addressBooks as &$book) { + if ($book['id'] !== $addressBookId) + continue; + + $propPatch->handleRemaining(function($mutations) use (&$book) { + foreach ($mutations as $key => $value) { + $book[$key] = $value; + } + return true; + }); + + } + + } + + function createAddressBook($principalUri, $url, array $properties) { + + $this->addressBooks[] = array_merge($properties, [ + 'id' => $url, + 'uri' => $url, + 'principaluri' => $principalUri, + ]); + + } + + function deleteAddressBook($addressBookId) { + + foreach ($this->addressBooks as $key => $value) { + if ($value['id'] === $addressBookId) + unset($this->addressBooks[$key]); + } + unset($this->cards[$addressBookId]); + + } + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param mixed $addressBookId + * @return array + */ + function getCards($addressBookId) { + + $cards = []; + foreach ($this->cards[$addressBookId] as $uri => $data) { + if (is_resource($data)) { + $cards[] = [ + 'uri' => $uri, + 'carddata' => $data, + ]; + } else { + $cards[] = [ + 'uri' => $uri, + 'carddata' => $data, + 'etag' => '"' . md5($data) . '"', + 'size' => strlen($data) + ]; + } + } + return $cards; + + } + + /** + * Returns a specfic card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * If the card does not exist, you must return false. + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array + */ + function getCard($addressBookId, $cardUri) { + + if (!isset($this->cards[$addressBookId][$cardUri])) { + return false; + } + + $data = $this->cards[$addressBookId][$cardUri]; + return [ + 'uri' => $cardUri, + 'carddata' => $data, + 'etag' => '"' . md5($data) . '"', + 'size' => strlen($data) + ]; + + } + + /** + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function createCard($addressBookId, $cardUri, $cardData) { + + if (is_resource($cardData)) { + $cardData = stream_get_contents($cardData); + } + $this->cards[$addressBookId][$cardUri] = $cardData; + return '"' . md5($cardData) . '"'; + + } + + /** + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function updateCard($addressBookId, $cardUri, $cardData) { + + if (is_resource($cardData)) { + $cardData = stream_get_contents($cardData); + } + $this->cards[$addressBookId][$cardUri] = $cardData; + return '"' . md5($cardData) . '"'; + + } + + function deleteCard($addressBookId, $cardUri) { + + unset($this->cards[$addressBookId][$cardUri]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php new file mode 100644 index 000000000000..c1b0e274ebd8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php @@ -0,0 +1,9 @@ +backend = new Backend\Mock(); + $this->card = new Card( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ], + [ + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + ] + ); + + } + + function testGet() { + + $result = $this->card->get(); + $this->assertEquals('card', $result); + + } + function testGet2() { + + $this->card = new Card( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ], + [ + 'uri' => 'card1', + 'addressbookid' => 'foo', + ] + ); + $result = $this->card->get(); + $this->assertEquals("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", $result); + + } + + + /** + * @depends testGet + */ + function testPut() { + + $file = fopen('php://memory', 'r+'); + fwrite($file, 'newdata'); + rewind($file); + $this->card->put($file); + $result = $this->card->get(); + $this->assertEquals('newdata', $result); + + } + + + function testDelete() { + + $this->card->delete(); + $this->assertEquals(1, count($this->backend->cards['foo'])); + + } + + function testGetContentType() { + + $this->assertEquals('text/vcard; charset=utf-8', $this->card->getContentType()); + + } + + function testGetETag() { + + $this->assertEquals('"' . md5('card') . '"', $this->card->getETag()); + + } + + function testGetETag2() { + + $card = new Card( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ], + [ + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + 'etag' => '"blabla"', + ] + ); + $this->assertEquals('"blabla"', $card->getETag()); + + } + + function testGetLastModified() { + + $this->assertEquals(null, $this->card->getLastModified()); + + } + + function testGetSize() { + + $this->assertEquals(4, $this->card->getSize()); + $this->assertEquals(4, $this->card->getSize()); + + } + + function testGetSize2() { + + $card = new Card( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ], + [ + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'etag' => '"blabla"', + 'size' => 4, + ] + ); + $this->assertEquals(4, $card->getSize()); + + } + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->card->getOwner()); + $this->assertNull($this->card->getGroup()); + $this->assertEquals([ + [ + 'privilege' => '{DAV:}all', + 'principal' => 'principals/user1', + 'protected' => true, + ], + ], $this->card->getACL()); + + } + function testOverrideACL() { + + $card = new Card( + $this->backend, + [ + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ], + [ + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + 'acl' => [ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + ], + ] + ); + $this->assertEquals([ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + ], $card->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetACL() { + + $this->card->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->card->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php new file mode 100644 index 000000000000..4796a131f879 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php @@ -0,0 +1,30 @@ +addPlugin($plugin); + + $props = $server->getProperties('directory', ['{DAV:}resourcetype']); + $this->assertTrue($props['{DAV:}resourcetype']->is('{' . Plugin::NS_CARDDAV . '}directory')); + + } + +} + +class DirectoryMock extends DAV\SimpleCollection implements IDirectory { + + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php new file mode 100644 index 000000000000..2d57c6ae7591 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php @@ -0,0 +1,99 @@ + 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + ]); + + $request->setBody( +' + + + + + + /addressbooks/user1/book1/card1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", + ] + ] + ], $result); + + } + + function testMultiGetVCard4() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + ]); + + $request->setBody( +' + + + + + + /addressbooks/user1/book1/card1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(['baseUri' => '/']); + + $result = $client->parseMultiStatus($response->body); + + $prodId = "PRODID:-//Sabre//Sabre VObject " . \Sabre\VObject\Version::VERSION . "//EN"; + + $this->assertEquals([ + '/addressbooks/user1/book1/card1' => [ + 200 => [ + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:4.0\r\n$prodId\r\nUID:12345\r\nEND:VCARD\r\n", + ] + ] + ], $result); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php new file mode 100644 index 000000000000..6962e7830c56 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php @@ -0,0 +1,102 @@ +assertEquals('{' . Plugin::NS_CARDDAV . '}addressbook', $this->server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook']); + + $this->assertTrue(in_array('addressbook', $this->plugin->getFeatures())); + $this->assertEquals('carddav', $this->plugin->getPluginInfo()['name']); + + } + + function testSupportedReportSet() { + + $this->assertEquals([ + '{' . Plugin::NS_CARDDAV . '}addressbook-multiget', + '{' . Plugin::NS_CARDDAV . '}addressbook-query', + ], $this->plugin->getSupportedReportSet('addressbooks/user1/book1')); + + } + + function testSupportedReportSetEmpty() { + + $this->assertEquals([ + ], $this->plugin->getSupportedReportSet('')); + + } + + function testAddressBookHomeSet() { + + $result = $this->server->getProperties('principals/user1', ['{' . Plugin::NS_CARDDAV . '}addressbook-home-set']); + + $this->assertEquals(1, count($result)); + $this->assertTrue(isset($result['{' . Plugin::NS_CARDDAV . '}addressbook-home-set'])); + $this->assertEquals('addressbooks/user1/', $result['{' . Plugin::NS_CARDDAV . '}addressbook-home-set']->getHref()); + + } + + function testDirectoryGateway() { + + $result = $this->server->getProperties('principals/user1', ['{' . Plugin::NS_CARDDAV . '}directory-gateway']); + + $this->assertEquals(1, count($result)); + $this->assertTrue(isset($result['{' . Plugin::NS_CARDDAV . '}directory-gateway'])); + $this->assertEquals(['directory'], $result['{' . Plugin::NS_CARDDAV . '}directory-gateway']->getHrefs()); + + } + + function testReportPassThrough() { + + $this->assertNull($this->plugin->report('{DAV:}foo', new \DomDocument(), '')); + + } + + function testHTMLActionsPanel() { + + $output = ''; + $r = $this->server->emit('onHTMLActionsPanel', [$this->server->tree->getNodeForPath('addressbooks/user1'), &$output]); + $this->assertFalse($r); + + $this->assertTrue(!!strpos($output, 'Display name')); + + } + + function testAddressbookPluginProperties() { + + $ns = '{' . Plugin::NS_CARDDAV . '}'; + $propFind = new DAV\PropFind('addressbooks/user1/book1', [ + $ns . 'supported-address-data', + $ns . 'supported-collation-set', + ]); + $node = $this->server->tree->getNodeForPath('addressbooks/user1/book1'); + $this->plugin->propFindEarly($propFind, $node); + + $this->assertInstanceOf( + 'Sabre\\CardDAV\\Xml\\Property\\SupportedAddressData', + $propFind->get($ns . 'supported-address-data') + ); + $this->assertInstanceOf( + 'Sabre\\CardDAV\\Xml\\Property\\SupportedCollationSet', + $propFind->get($ns . 'supported-collation-set') + ); + + + } + + function testGetTransform() { + + $request = new \Sabre\HTTP\Request('GET', '/addressbooks/user1/book1/card1', ['Accept: application/vcard+json']); + $response = new \Sabre\HTTP\ResponseMock(); + $this->server->invokeMethod($request, $response); + + $this->assertEquals(200, $response->getStatus()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php new file mode 100644 index 000000000000..d4bc48098dc4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php @@ -0,0 +1,56 @@ + 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + ], + ]; + protected $carddavCards = [ + 1 => [ + 'card1.vcf' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", + ], + ]; + + function testDontStrip() { + + $result = $this->server->getProperties('addressbooks/user1/book1/card1.vcf', ['{DAV:}getcontenttype']); + $this->assertEquals([ + '{DAV:}getcontenttype' => 'text/vcard; charset=utf-8' + ], $result); + + } + function testStrip() { + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0.2) Gecko/20120216 Thunderbird/10.0.2 Lightning/1.2.1', + ]); + $result = $this->server->getProperties('addressbooks/user1/book1/card1.vcf', ['{DAV:}getcontenttype']); + $this->assertEquals([ + '{DAV:}getcontenttype' => 'text/x-vcard' + ], $result); + + } + function testDontTouchOtherMimeTypes() { + + $this->server->httpRequest = new HTTP\Request('GET', '/addressbooks/user1/book1/card1.vcf', [ + 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0.2) Gecko/20120216 Thunderbird/10.0.2 Lightning/1.2.1', + ]); + + $propFind = new PropFind('hello', ['{DAV:}getcontenttype']); + $propFind->set('{DAV:}getcontenttype', 'text/plain'); + $this->carddavPlugin->propFindLate($propFind, new \Sabre\DAV\SimpleCollection('foo')); + $this->assertEquals('text/plain', $propFind->get('{DAV:}getcontenttype')); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php new file mode 100644 index 000000000000..ec8a3501e817 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php @@ -0,0 +1,62 @@ +createAddressBook( + 'principals/user1', + 'UUID-123467', + [ + '{DAV:}displayname' => 'user1 addressbook', + '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'AddressBook description', + ] + ); + $backend->createAddressBook( + 'principals/user1', + 'UUID-123468', + [ + '{DAV:}displayname' => 'user1 addressbook2', + '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'AddressBook description', + ] + ); + $backend->createCard($addressbookId, 'UUID-2345', self::getTestCardData()); + return $pdo; + + } + + static function deleteSQLiteDB() { + $sqliteTest = new Backend\PDOSqliteTest(); + $pdo = $sqliteTest->tearDown(); + } + + static function getTestCardData() { + + $addressbookData = 'BEGIN:VCARD +VERSION:3.0 +PRODID:-//Acme Inc.//RoadRunner 1.0//EN +FN:Wile E. Coyote +N:Coyote;Wile;Erroll;; +ORG:Acme Inc. +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +REV:2012-06-20T07:00:39+00:00 +END:VCARD'; + + return $addressbookData; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php new file mode 100644 index 000000000000..82d82faddc1c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php @@ -0,0 +1,135 @@ + 'book1', + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + ] + ]; + protected $carddavCards = [ + 'book1' => [ + "card1" => "BEGIN:VCARD\r\nFN:Person1\r\nEND:VCARD\r\n", + "card2" => "BEGIN:VCARD\r\nFN:Person2\r\nEND:VCARD", + "card3" => "BEGIN:VCARD\r\nFN:Person3\r\nEND:VCARD\r\n", + "card4" => "BEGIN:VCARD\nFN:Person4\nEND:VCARD\n", + ] + ]; + + function setUp() { + + parent::setUp(); + $plugin = new VCFExportPlugin(); + $this->server->addPlugin( + $plugin + ); + + } + + function testSimple() { + + $plugin = $this->server->getPlugin('vcf-export'); + $this->assertInstanceOf('Sabre\\CardDAV\\VCFExportPlugin', $plugin); + + $this->assertEquals( + 'vcf-export', + $plugin->getPluginInfo()['name'] + ); + + } + + function testExport() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/addressbooks/user1/book1?export', + 'QUERY_STRING' => 'export', + 'REQUEST_METHOD' => 'GET', + ]); + + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + $expected = "BEGIN:VCARD +FN:Person1 +END:VCARD +BEGIN:VCARD +FN:Person2 +END:VCARD +BEGIN:VCARD +FN:Person3 +END:VCARD +BEGIN:VCARD +FN:Person4 +END:VCARD +"; + // We actually expected windows line endings + $expected = str_replace("\n", "\r\n", $expected); + + $this->assertEquals($expected, $response->body); + + } + + function testBrowserIntegration() { + + $plugin = $this->server->getPlugin('vcf-export'); + $actions = ''; + $addressbook = new AddressBook($this->carddavBackend, []); + $this->server->emit('browserButtonActions', ['/foo', $addressbook, &$actions]); + $this->assertContains('/foo?export', $actions); + + } + + function testContentDisposition() { + + $request = new HTTP\Request( + 'GET', + '/addressbooks/user1/book1?export' + ); + + $response = $this->request($request, 200); + $this->assertEquals('text/directory', $response->getHeader('Content-Type')); + $this->assertEquals( + 'attachment; filename="book1-' . date('Y-m-d') . '.vcf"', + $response->getHeader('Content-Disposition') + ); + + } + + function testContentDispositionBadChars() { + + $this->carddavBackend->createAddressBook( + 'principals/user1', + 'book-b_ad"(ch)ars', + [] + ); + $this->carddavBackend->createCard( + 'book-b_ad"(ch)ars', + 'card1', + "BEGIN:VCARD\r\nFN:Person1\r\nEND:VCARD\r\n" + ); + + $request = new HTTP\Request( + 'GET', + '/addressbooks/user1/book-b_ad"(ch)ars?export' + ); + + $response = $this->request($request, 200); + $this->assertEquals('text/directory', $response->getHeader('Content-Type')); + $this->assertEquals( + 'attachment; filename="book-b_adchars-' . date('Y-m-d') . '.vcf"', + $response->getHeader('Content-Disposition') + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php new file mode 100644 index 000000000000..03c468f868f9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php @@ -0,0 +1,209 @@ +assertTrue($this->plugin->validateFilters($input, $filters, $test), $message); + } else { + $this->assertFalse($this->plugin->validateFilters($input, $filters, $test), $message); + } + + } + + function data() { + + $body1 = << 'title', 'is-not-defined' => false, 'param-filters' => [], 'text-matches' => []]; + + // Check if FOO is defined + $filter2 = + ['name' => 'foo', 'is-not-defined' => false, 'param-filters' => [], 'text-matches' => []]; + + // Check if TITLE is not defined + $filter3 = + ['name' => 'title', 'is-not-defined' => true, 'param-filters' => [], 'text-matches' => []]; + + // Check if FOO is not defined + $filter4 = + ['name' => 'foo', 'is-not-defined' => true, 'param-filters' => [], 'text-matches' => []]; + + // Check if TEL[TYPE] is defined + $filter5 = + [ + 'name' => 'tel', + 'is-not-defined' => false, + 'test' => 'anyof', + 'param-filters' => [ + [ + 'name' => 'type', + 'is-not-defined' => false, + 'text-match' => null + ], + ], + 'text-matches' => [], + ]; + + // Check if TEL[FOO] is defined + $filter6 = $filter5; + $filter6['param-filters'][0]['name'] = 'FOO'; + + // Check if TEL[TYPE] is not defined + $filter7 = $filter5; + $filter7['param-filters'][0]['is-not-defined'] = true; + + // Check if TEL[FOO] is not defined + $filter8 = $filter5; + $filter8['param-filters'][0]['name'] = 'FOO'; + $filter8['param-filters'][0]['is-not-defined'] = true; + + // Combining property filters + $filter9 = $filter5; + $filter9['param-filters'][] = $filter6['param-filters'][0]; + + $filter10 = $filter5; + $filter10['param-filters'][] = $filter6['param-filters'][0]; + $filter10['test'] = 'allof'; + + // Check if URL contains 'google' + $filter11 = + [ + 'name' => 'url', + 'is-not-defined' => false, + 'test' => 'anyof', + 'param-filters' => [], + 'text-matches' => [ + [ + 'match-type' => 'contains', + 'value' => 'google', + 'negate-condition' => false, + 'collation' => 'i;octet', + ], + ], + ]; + + // Check if URL contains 'bing' + $filter12 = $filter11; + $filter12['text-matches'][0]['value'] = 'bing'; + + // Check if URL does not contain 'google' + $filter13 = $filter11; + $filter13['text-matches'][0]['negate-condition'] = true; + + // Check if URL does not contain 'bing' + $filter14 = $filter11; + $filter14['text-matches'][0]['value'] = 'bing'; + $filter14['text-matches'][0]['negate-condition'] = true; + + // Param filter with text + $filter15 = $filter5; + $filter15['param-filters'][0]['text-match'] = [ + 'match-type' => 'contains', + 'value' => 'WORK', + 'collation' => 'i;octet', + 'negate-condition' => false, + ]; + $filter16 = $filter15; + $filter16['param-filters'][0]['text-match']['negate-condition'] = true; + + + // Param filter + text filter + $filter17 = $filter5; + $filter17['test'] = 'anyof'; + $filter17['text-matches'][] = [ + 'match-type' => 'contains', + 'value' => '444', + 'collation' => 'i;octet', + 'negate-condition' => false, + ]; + + $filter18 = $filter17; + $filter18['text-matches'][0]['negate-condition'] = true; + + $filter18['test'] = 'allof'; + + return [ + + // Basic filters + [$body1, [$filter1], 'anyof',true], + [$body1, [$filter2], 'anyof',false], + [$body1, [$filter3], 'anyof',false], + [$body1, [$filter4], 'anyof',true], + + // Combinations + [$body1, [$filter1, $filter2], 'anyof',true], + [$body1, [$filter1, $filter2], 'allof',false], + [$body1, [$filter1, $filter4], 'anyof',true], + [$body1, [$filter1, $filter4], 'allof',true], + [$body1, [$filter2, $filter3], 'anyof',false], + [$body1, [$filter2, $filter3], 'allof',false], + + // Basic parameters + [$body1, [$filter5], 'anyof', true, 'TEL;TYPE is defined, so this should return true'], + [$body1, [$filter6], 'anyof', false, 'TEL;FOO is not defined, so this should return false'], + + [$body1, [$filter7], 'anyof', false, 'TEL;TYPE is defined, so this should return false'], + [$body1, [$filter8], 'anyof', true, 'TEL;TYPE is not defined, so this should return true'], + + // Combined parameters + [$body1, [$filter9], 'anyof', true], + [$body1, [$filter10], 'anyof', false], + + // Text-filters + [$body1, [$filter11], 'anyof', true], + [$body1, [$filter12], 'anyof', false], + [$body1, [$filter13], 'anyof', false], + [$body1, [$filter14], 'anyof', true], + + // Param filter with text-match + [$body1, [$filter15], 'anyof', true], + [$body1, [$filter16], 'anyof', false], + + // Param filter + text filter + [$body1, [$filter17], 'anyof', true], + [$body1, [$filter18], 'anyof', false], + [$body1, [$filter18], 'anyof', false], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php new file mode 100644 index 000000000000..acba2cfc8f32 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php @@ -0,0 +1,305 @@ + 'addressbook1', + 'principaluri' => 'principals/admin', + 'uri' => 'addressbook1', + ] + ]; + + $this->cardBackend = new Backend\Mock($addressbooks, []); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = [ + new AddressBookRoot($principalBackend, $this->cardBackend), + ]; + + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + $plugin = new Plugin(); + $this->server->addPlugin($plugin); + + $response = new HTTP\ResponseMock(); + $this->server->httpResponse = $response; + + } + + function request(HTTP\Request $request, $expectedStatus = null) { + + $this->server->httpRequest = $request; + $this->server->exec(); + + if ($expectedStatus) { + + $realStatus = $this->server->httpResponse->getStatus(); + + $msg = ''; + if ($realStatus !== $expectedStatus) { + $msg = 'Response body: ' . $this->server->httpResponse->getBodyAsString(); + } + $this->assertEquals( + $expectedStatus, + $realStatus, + $msg + ); + } + + return $this->server->httpResponse; + + } + + function testCreateFile() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + ]); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testCreateFileValid() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + + $vcard = <<setBody($vcard); + + $response = $this->request($request, 201); + + // The custom Ew header should not be set + $this->assertNull( + $response->getHeader('X-Sabre-Ew-Gross') + ); + // Valid, non-auto-fixed responses should contain an ETag. + $this->assertTrue( + $response->getHeader('ETag') !== null, + 'We did not receive an etag' + ); + + + $expected = [ + 'uri' => 'blabla.vcf', + 'carddata' => $vcard, + 'size' => strlen($vcard), + 'etag' => '"' . md5($vcard) . '"', + ]; + + $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1', 'blabla.vcf')); + + } + + /** + * This test creates an intentionally broken vCard that vobject is able + * to automatically repair. + * + * @depends testCreateFileValid + */ + function testCreateVCardAutoFix() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + + // The error in this vcard is that there's not enough semi-colons in N + $vcard = <<setBody($vcard); + + $response = $this->request($request, 201); + + // Auto-fixed vcards should NOT return an etag + $this->assertNull( + $response->getHeader('ETag') + ); + + // We should have gotten an Ew header + $this->assertNotNull( + $response->getHeader('X-Sabre-Ew-Gross') + ); + + $expectedVCard = << 'blabla.vcf', + 'carddata' => $expectedVCard, + 'size' => strlen($expectedVCard), + 'etag' => '"' . md5($expectedVCard) . '"', + ]; + + $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1', 'blabla.vcf')); + + } + + /** + * This test creates an intentionally broken vCard that vobject is able + * to automatically repair. + * + * However, we're supplying a heading asking the server to treat the + * request as strict, so the server should still let the request fail. + * + * @depends testCreateFileValid + */ + function testCreateVCardStrictFail() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf', + [ + 'Prefer' => 'handling=strict', + ] + ); + + // The error in this vcard is that there's not enough semi-colons in N + $vcard = <<setBody($vcard); + $this->request($request, 415); + + } + + function testCreateFileNoUID() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + $vcard = <<setBody($vcard); + + $response = $this->request($request, 201); + + $foo = $this->cardBackend->getCard('addressbook1', 'blabla.vcf'); + $this->assertTrue( + strpos($foo['carddata'], 'UID') !== false, + print_r($foo, true) + ); + } + + function testCreateFileJson() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + $request->setBody('[ "vcard" , [ [ "VERSION", {}, "text", "4.0"], [ "UID" , {}, "text", "foo" ], [ "FN", {}, "text", "FirstName LastName"] ] ]'); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + $foo = $this->cardBackend->getCard('addressbook1', 'blabla.vcf'); + $this->assertEquals("BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nFN:FirstName LastName\r\nEND:VCARD\r\n", $foo['carddata']); + + } + + function testCreateFileVCalendar() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + ]); + $request->setBody("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFile() { + + $this->cardBackend->createCard('addressbook1', 'blabla.vcf', 'foo'); + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + + $response = $this->request($request, 415); + + } + + function testUpdateFileParsableBody() { + + $this->cardBackend->createCard('addressbook1', 'blabla.vcf', 'foo'); + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + + $body = "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nFN:FirstName LastName\r\nEND:VCARD\r\n"; + $request->setBody($body); + + $response = $this->request($request, 204); + + $expected = [ + 'uri' => 'blabla.vcf', + 'carddata' => $body, + 'size' => strlen($body), + 'etag' => '"' . md5($body) . '"', + ]; + + $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1', 'blabla.vcf')); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php new file mode 100644 index 000000000000..43abebdcec13 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CardDAV\Xml\Property\SupportedAddressData', $property); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedAddressData(); + + $this->namespaceMap[CardDAV\Plugin::NS_CARDDAV] = 'card'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' +' . +'' . +'' . +'' . +' +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php new file mode 100644 index 000000000000..e06aff101d96 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CardDAV\Xml\Property\SupportedCollationSet', $property); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedCollationSet(); + + $this->namespaceMap[CardDAV\Plugin::NS_CARDDAV] = 'card'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' +' . +'i;ascii-casemap' . +'i;octet' . +'i;unicode-casemap' . +' +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookMultiGetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookMultiGetTest.php new file mode 100644 index 000000000000..ea2ab75ceec8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookMultiGetTest.php @@ -0,0 +1,47 @@ + 'Sabre\\CardDAV\\Xml\\Request\AddressBookMultiGetReport', + ]; + + function testDeserialize() { + + /* lines look a bit odd but this triggers an XML parsing bug */ + $xml = << + + + + + /foo.vcf + +XML; + + $result = $this->parse($xml); + $addressBookMultiGetReport = new AddressBookMultiGetReport(); + $addressBookMultiGetReport->properties = [ + '{DAV:}getcontenttype', + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:carddav}address-data', + ]; + $addressBookMultiGetReport->hrefs = ['/foo.vcf']; + $addressBookMultiGetReport->contentType = 'text/vcard'; + $addressBookMultiGetReport->version = '4.0'; + $addressBookMultiGetReport->addressDataProperties = []; + + + $this->assertEquals( + $addressBookMultiGetReport, + $result['value'] + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php new file mode 100644 index 000000000000..3a2e4b46a0e0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php @@ -0,0 +1,350 @@ + 'Sabre\\CardDAV\\Xml\\Request\AddressBookQueryReport', + ]; + + function testDeserialize() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ] + ]; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + function testDeserializeAllOf() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'allof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ] + ]; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadTest() { + + $xml = << + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * We should error on this, but KDE does this, so we chose to support it. + */ + function testDeserializeNoFilter() { + + $xml = << + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = []; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + function testDeserializeComplex() { + + $xml = << + + + + + + + + + + + + + + + + Hello! + + + + No + + + 10 + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:carddav}address-data', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => true, + 'param-filters' => [], + 'text-matches' => [], + ], + [ + 'name' => 'x-foo', + 'test' => 'allof', + 'is-not-defined' => false, + 'param-filters' => [ + [ + 'name' => 'x-param1', + 'is-not-defined' => false, + 'text-match' => null, + ], + [ + 'name' => 'x-param2', + 'is-not-defined' => true, + 'text-match' => null, + ], + [ + 'name' => 'x-param3', + 'is-not-defined' => false, + 'text-match' => [ + 'negate-condition' => false, + 'value' => 'Hello!', + 'match-type' => 'contains', + 'collation' => 'i;unicode-casemap', + ], + ], + ], + 'text-matches' => [], + ], + [ + 'name' => 'x-prop2', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [ + [ + 'negate-condition' => true, + 'value' => 'No', + 'match-type' => 'starts-with', + 'collation' => 'i;unicode-casemap', + ], + ], + ] + ]; + + $addressBookQueryReport->version = '4.0'; + $addressBookQueryReport->contentType = 'application/vcard+json'; + $addressBookQueryReport->limit = 10; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadMatchType() { + + $xml = << + + + + + + + + Hello! + + + + +XML; + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadMatchType2() { + + $xml = << + + + + + + + No + + + +XML; + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeDoubleFilter() { + + $xml = << + + + + + + + + + +XML; + $this->parse($xml); + + } + + function testDeserializeAddressbookElements() { + + $xml = << + + + + + + + + + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:carddav}address-data' + ]; + $addressBookQueryReport->filters = []; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->contentType = 'text/vcard'; + $addressBookQueryReport->version = '3.0'; + $addressBookQueryReport->addressDataProperties = [ + 'VERSION', + 'UID', + 'NICKNAME', + 'EMAIL', + 'FN', + 'TEL', + ]; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php new file mode 100644 index 000000000000..6a8d389a0085 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php @@ -0,0 +1,64 @@ +response = new HTTP\ResponseMock(); + $this->server = new Server($this->getRootNode()); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->httpResponse = $this->response; + $this->server->debugExceptions = true; + $this->deleteTree(SABRE_TEMPDIR, false); + file_put_contents(SABRE_TEMPDIR . '/test.txt', 'Test contents'); + mkdir(SABRE_TEMPDIR . '/dir'); + file_put_contents(SABRE_TEMPDIR . '/dir/child.txt', 'Child contents'); + + + } + + function tearDown() { + + $this->deleteTree(SABRE_TEMPDIR, false); + + } + + protected function getRootNode() { + + return new FS\Directory(SABRE_TEMPDIR); + + } + + private function deleteTree($path, $deleteRoot = true) { + + foreach (scandir($path) as $node) { + + if ($node == '.' || $node == '.svn' || $node == '..') continue; + $myPath = $path . '/' . $node; + if (is_file($myPath)) { + unlink($myPath); + } else { + $this->deleteTree($myPath); + } + + } + if ($deleteRoot) rmdir($path); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php new file mode 100644 index 000000000000..455403affe70 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php @@ -0,0 +1,91 @@ +assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckUnknownUser() { + + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'wrongpassword', + ]); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckSuccess() { + + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + ]); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertEquals( + 'Basic realm="writing unittests on a saturday night"', + $response->getHeader('WWW-Authenticate') + ); + + } + +} + + +class AbstractBasicMock extends AbstractBasic { + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + function validateUserPass($username, $password) { + + return ($username == 'username' && $password == 'password'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBearerTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBearerTest.php new file mode 100644 index 000000000000..c38578830078 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBearerTest.php @@ -0,0 +1,90 @@ +assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckInvalidToken() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_AUTHORIZATION' => 'Bearer foo', + ]); + $response = new HTTP\Response(); + + $backend = new AbstractBearerMock(); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckSuccess() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_AUTHORIZATION' => 'Bearer valid', + ]); + $response = new HTTP\Response(); + + $backend = new AbstractBearerMock(); + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new AbstractBearerMock(); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertEquals( + 'Bearer realm="writing unittests on a saturday night"', + $response->getHeader('WWW-Authenticate') + ); + + } + +} + + +class AbstractBearerMock extends AbstractBearer { + + /** + * Validates a bearer token + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $bearerToken + * @return bool + */ + function validateBearerToken($bearerToken) { + + return 'valid' === $bearerToken ? 'principals/username' : false; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php new file mode 100644 index 000000000000..14c72aaa0f6f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php @@ -0,0 +1,138 @@ +assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckBadGetUserInfoResponse() { + + $header = 'username=null, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testCheckBadGetUserInfoResponse2() { + + $header = 'username=array, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $backend->check($request, $response); + + } + + function testCheckUnknownUser() { + + $header = 'username=false, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckBadPassword() { + + $header = 'username=user, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + 'REQUEST_METHOD' => 'PUT', + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheck() { + + $digestHash = md5('HELLO:12345:1:1:auth:' . md5('GET:/')); + $header = 'username=user, realm=myRealm, nonce=12345, uri=/, response=' . $digestHash . ', opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_DIGEST' => $header, + 'REQUEST_URI' => '/', + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertEquals( + [true, 'principals/user'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertStringStartsWith( + 'Digest realm="writing unittests on a saturday night"', + $response->getHeader('WWW-Authenticate') + ); + + } + +} + + +class AbstractDigestMock extends AbstractDigest { + + function getDigestHash($realm, $userName) { + + switch ($userName) { + case 'null' : return null; + case 'false' : return false; + case 'array' : return []; + case 'user' : return 'HELLO'; + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php new file mode 100644 index 000000000000..b14e9fa2ea39 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php @@ -0,0 +1,45 @@ +dropTables('users'); + $this->createSchema('users'); + + $this->getPDO()->query( + "INSERT INTO users (username,digesta1) VALUES ('user','hash')" + + ); + + } + + function testConstruct() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testUserInfo() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $this->assertNull($backend->getDigestHash('realm', 'blabla')); + + $expected = 'hash'; + + $this->assertEquals($expected, $backend->getDigestHash('realm', 'user')); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php new file mode 100644 index 000000000000..29cbc2162826 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php @@ -0,0 +1,71 @@ +assertInstanceOf('Sabre\DAV\Auth\Backend\Apache', $backend); + + } + + function testNoHeader() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testRemoteUser() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REMOTE_USER' => 'username', + ]); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRedirectRemoteUser() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REDIRECT_REMOTE_USER' => 'username', + ]); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new Apache(); + $backend->challenge($request, $response); + + $this->assertNull( + $response->getHeader('WWW-Authenticate') + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php new file mode 100644 index 000000000000..167f31eab37a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php @@ -0,0 +1,36 @@ + 'Basic ' . base64_encode('foo:bar'), + ]); + $response = new Response(); + + $this->assertEquals( + [true, 'principals/foo'], + $backend->check($request, $response) + ); + + $this->assertEquals(['foo', 'bar'], $args); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php new file mode 100644 index 000000000000..f694f4806ee6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php @@ -0,0 +1,41 @@ +assertTrue($file instanceof File); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testLoadFileBroken() { + + file_put_contents(SABRE_TEMPDIR . '/backend', 'user:realm:hash'); + $file = new File(SABRE_TEMPDIR . '/backend'); + + } + + function testLoadFile() { + + file_put_contents(SABRE_TEMPDIR . '/backend', 'user:realm:' . md5('user:realm:password')); + $file = new File(); + $file->loadFile(SABRE_TEMPDIR . '/backend'); + + $this->assertFalse($file->getDigestHash('realm', 'blabla')); + $this->assertEquals(md5('user:realm:password'), $file->getDigestHash('realm', 'user')); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php new file mode 100644 index 000000000000..369bc249e470 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php @@ -0,0 +1,87 @@ +principal = $principal; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + if ($this->invalidCheckResponse) { + return 'incorrect!'; + } + if ($this->fail) { + return [false, "fail!"]; + } + return [true, $this->principal]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the oppurtunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php new file mode 100644 index 000000000000..18f59793ad7f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php @@ -0,0 +1,9 @@ +assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('auth')); + $this->assertInternalType('array', $plugin->getPluginInfo()); + + } + + /** + * @depends testInit + */ + function testAuthenticate() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $plugin = new Plugin(new Backend\Mock()); + $fakeServer->addPlugin($plugin); + $this->assertTrue( + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]) + ); + + } + + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception\NotAuthenticated + */ + function testAuthenticateFail() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $backend = new Backend\Mock(); + $backend->fail = true; + + $plugin = new Plugin($backend); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + + /** + * @depends testAuthenticateFail + */ + function testAuthenticateFailDontAutoRequire() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $backend = new Backend\Mock(); + $backend->fail = true; + + $plugin = new Plugin($backend); + $plugin->autoRequireLogin = false; + $fakeServer->addPlugin($plugin); + $this->assertTrue( + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]) + ); + $this->assertEquals(1, count($plugin->getLoginFailedReasons())); + + } + + /** + * @depends testAuthenticate + */ + function testMultipleBackend() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $backend1 = new Backend\Mock(); + $backend2 = new Backend\Mock(); + $backend2->fail = true; + + $plugin = new Plugin(); + $plugin->addBackend($backend1); + $plugin->addBackend($backend2); + + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + $this->assertEquals('principals/admin', $plugin->getCurrentPrincipal()); + + } + + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception + */ + function testNoAuthBackend() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception + */ + function testInvalidCheckResponse() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $backend = new Backend\Mock(); + $backend->invalidCheckResponse = true; + + $plugin = new Plugin($backend); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + + /** + * @depends testAuthenticate + */ + function testGetCurrentPrincipal() { + + $fakeServer = new DAV\Server(new DAV\SimpleCollection('bla')); + $plugin = new Plugin(new Backend\Mock()); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + $this->assertEquals('principals/admin', $plugin->getCurrentPrincipal()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php new file mode 100644 index 000000000000..ec104ec805e6 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php @@ -0,0 +1,235 @@ +put('hi'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testGet() { + + $file = new FileMock(); + $file->get(); + + } + + function testGetSize() { + + $file = new FileMock(); + $this->assertEquals(0, $file->getSize()); + + } + + + function testGetETag() { + + $file = new FileMock(); + $this->assertNull($file->getETag()); + + } + + function testGetContentType() { + + $file = new FileMock(); + $this->assertNull($file->getContentType()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testDelete() { + + $file = new FileMock(); + $file->delete(); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetName() { + + $file = new FileMock(); + $file->setName('hi'); + + } + + function testGetLastModified() { + + $file = new FileMock(); + // checking if lastmod is within the range of a few seconds + $lastMod = $file->getLastModified(); + $compareTime = ($lastMod + 1) - time(); + $this->assertTrue($compareTime < 3); + + } + + function testGetChild() { + + $dir = new DirectoryMock(); + $file = $dir->getChild('mockfile'); + $this->assertTrue($file instanceof FileMock); + + } + + function testChildExists() { + + $dir = new DirectoryMock(); + $this->assertTrue($dir->childExists('mockfile')); + + } + + function testChildExistsFalse() { + + $dir = new DirectoryMock(); + $this->assertFalse($dir->childExists('mockfile2')); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChild404() { + + $dir = new DirectoryMock(); + $file = $dir->getChild('blabla'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateFile() { + + $dir = new DirectoryMock(); + $dir->createFile('hello', 'data'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateDirectory() { + + $dir = new DirectoryMock(); + $dir->createDirectory('hello'); + + } + + function testSimpleDirectoryConstruct() { + + $dir = new SimpleCollection('simpledir', []); + $this->assertInstanceOf('Sabre\DAV\SimpleCollection', $dir); + + } + + /** + * @depends testSimpleDirectoryConstruct + */ + function testSimpleDirectoryConstructChild() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir', [$file]); + $file2 = $dir->getChild('mockfile'); + + $this->assertEquals($file, $file2); + + } + + /** + * @expectedException Sabre\DAV\Exception + * @depends testSimpleDirectoryConstruct + */ + function testSimpleDirectoryBadParam() { + + $dir = new SimpleCollection('simpledir', ['string shouldn\'t be here']); + + } + + /** + * @depends testSimpleDirectoryConstruct + */ + function testSimpleDirectoryAddChild() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir'); + $dir->addChild($file); + $file2 = $dir->getChild('mockfile'); + + $this->assertEquals($file, $file2); + + } + + /** + * @depends testSimpleDirectoryConstruct + * @depends testSimpleDirectoryAddChild + */ + function testSimpleDirectoryGetChildren() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir'); + $dir->addChild($file); + + $this->assertEquals([$file], $dir->getChildren()); + + } + + /* + * @depends testSimpleDirectoryConstruct + */ + function testSimpleDirectoryGetName() { + + $dir = new SimpleCollection('simpledir'); + $this->assertEquals('simpledir', $dir->getName()); + + } + + /** + * @depends testSimpleDirectoryConstruct + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testSimpleDirectoryGetChild404() { + + $dir = new SimpleCollection('simpledir'); + $dir->getChild('blabla'); + + } +} + +class DirectoryMock extends Collection { + + function getName() { + + return 'mockdir'; + + } + + function getChildren() { + + return [new FileMock()]; + + } + +} + +class FileMock extends File { + + function getName() { + + return 'mockfile'; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php new file mode 100644 index 000000000000..54a3053ec9ff --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php @@ -0,0 +1,70 @@ +server->getPropertiesForPath('/somefile.jpg', $properties); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(404, $result[0]); + $this->assertArrayHasKey('{DAV:}getcontenttype', $result[0][404]); + + } + + /** + * @depends testGetProperties + */ + function testGetPropertiesPluginEnabled() { + + $this->server->addPlugin(new GuessContentType()); + $properties = [ + '{DAV:}getcontenttype', + ]; + $result = $this->server->getPropertiesForPath('/somefile.jpg', $properties); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(200, $result[0], 'We received: ' . print_r($result, true)); + $this->assertArrayHasKey('{DAV:}getcontenttype', $result[0][200]); + $this->assertEquals('image/jpeg', $result[0][200]['{DAV:}getcontenttype']); + + } + + /** + * @depends testGetPropertiesPluginEnabled + */ + function testGetPropertiesUnknown() { + + $this->server->addPlugin(new GuessContentType()); + $properties = [ + '{DAV:}getcontenttype', + ]; + $result = $this->server->getPropertiesForPath('/somefile.hoi', $properties); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(200, $result[0]); + $this->assertArrayHasKey('{DAV:}getcontenttype', $result[0][200]); + $this->assertEquals('application/octet-stream', $result[0][200]['{DAV:}getcontenttype']); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php new file mode 100644 index 000000000000..33c4ede96b46 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php @@ -0,0 +1,44 @@ +server->addPlugin(new MapGetToPropFind()); + + } + + function testCollectionGet() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'GET', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Incorrect status response received. Full response body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol'], + 'Vary' => ['Brief,Prefer'], + ], + $this->response->getHeaders() + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php new file mode 100644 index 000000000000..f20c50f86373 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php @@ -0,0 +1,186 @@ +server->addPlugin($this->plugin = new Plugin()); + $this->server->tree->getNodeForPath('')->createDirectory('dir2'); + + } + + function testCollectionGet() { + + $request = new HTTP\Request('GET', '/dir'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, 'dir') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/child.txt">') !== false); + + } + + /** + * Adding the If-None-Match should have 0 effect, but it threw an error. + */ + function testCollectionGetIfNoneMatch() { + + $request = new HTTP\Request('GET', '/dir'); + $request->setHeader('If-None-Match', '"foo-bar"'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, '<title>dir') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/child.txt">') !== false); + + } + function testCollectionGetRoot() { + + $request = new HTTP\Request('GET', '/'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(200, $this->response->status, "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, '<title>/') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/">') !== false); + $this->assertTrue(strpos($body, '<span class="btn disabled">') !== false); + + } + + function testGETPassthru() { + + $request = new HTTP\Request('GET', '/random'); + $response = new HTTP\Response(); + $this->assertNull( + $this->plugin->httpGet($request, $response) + ); + + } + + function testPostOtherContentType() { + + $request = new HTTP\Request('POST', '/', ['Content-Type' => 'text/xml']); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status); + + } + + function testPostNoSabreAction() { + + $request = new HTTP\Request('POST', '/', ['Content-Type' => 'application/x-www-form-urlencoded']); + $request->setPostData([]); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status); + + } + + function testPostMkCol() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'POST', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + ]; + $postVars = [ + 'sabreAction' => 'mkcol', + 'name' => 'new_collection', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setPostData($postVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(302, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Location' => ['/'], + ], $this->response->getHeaders()); + + $this->assertTrue(is_dir(SABRE_TEMPDIR . '/new_collection')); + + } + + function testGetAsset() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=favicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), 'Error: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['image/vnd.microsoft.icon'], + 'Content-Length' => ['4286'], + 'Cache-Control' => ['public, max-age=1209600'], + 'Content-Security-Policy' => ["default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"] + ], $this->response->getHeaders()); + + } + + function testGetAsset404() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=flavicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(404, $this->response->getStatus(), 'Error: ' . $this->response->body); + + } + + function testGetAssetEscapeBasePath() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=./../assets/favicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(404, $this->response->getStatus(), 'Error: ' . $this->response->body); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php new file mode 100644 index 000000000000..08c2080bbab0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\DAV\Browser; + +class PropFindAllTest extends \PHPUnit_Framework_TestCase { + + function testHandleSimple() { + + $pf = new PropFindAll('foo'); + $pf->handle('{DAV:}displayname', 'foo'); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + + } + + function testHandleCallBack() { + + $pf = new PropFindAll('foo'); + $pf->handle('{DAV:}displayname', function() { return 'foo'; }); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + } + + function testSet() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', 'foo'); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + } + + function testSetNull() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', null); + + $this->assertEquals(404, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals(null, $pf->get('{DAV:}displayname')); + + } + + function testGet404Properties() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', null); + $this->assertEquals( + ['{DAV:}displayname'], + $pf->get404Properties() + ); + + } + + function testGet404PropertiesNothing() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', 'foo'); + $this->assertEquals( + ['{http://sabredav.org/ns}idk'], + $pf->get404Properties() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php new file mode 100644 index 000000000000..5a48b063ce8a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php @@ -0,0 +1,34 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\RequestInterface; + +class ClientMock extends Client { + + public $request; + public $response; + + public $url; + public $curlSettings; + + /** + * Just making this method public + * + * @param string $url + * @return string + */ + function getAbsoluteUrl($url) { + + return parent::getAbsoluteUrl($url); + + } + + function doRequest(RequestInterface $request) { + + $this->request = $request; + return $this->response; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php new file mode 100644 index 000000000000..687f61e2fd21 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php @@ -0,0 +1,306 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +require_once 'Sabre/DAV/ClientMock.php'; + +class ClientTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + if (!function_exists('curl_init')) { + $this->markTestSkipped('CURL must be installed to test the client'); + } + + } + + function testConstruct() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + $this->assertInstanceOf('Sabre\DAV\ClientMock', $client); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testConstructNoBaseUri() { + + $client = new ClientMock([]); + + } + + function testAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_BASIC | CURLAUTH_DIGEST, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testBasicAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_BASIC + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_BASIC, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testDigestAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_DIGEST + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_DIGEST, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testNTLMAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_NTLM + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_NTLM, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testProxy() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'proxy' => 'localhost:8888', + ]); + + $this->assertEquals("localhost:8888", $client->curlSettings[CURLOPT_PROXY]); + + } + + function testEncoding() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'encoding' => Client::ENCODING_IDENTITY | Client::ENCODING_GZIP | Client::ENCODING_DEFLATE, + ]); + + $this->assertEquals("identity,deflate,gzip", $client->curlSettings[CURLOPT_ENCODING]); + + } + + function testPropFind() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> + <response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> + </response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propFind('foo', ['{DAV:}displayname', '{urn:zim}gir']); + + $this->assertEquals(['{DAV:}displayname' => 'bar'], $result); + + $request = $client->request; + $this->assertEquals('PROPFIND', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Depth' => ['0'], + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + /** + * @expectedException \Sabre\HTTP\ClientHttpException + */ + function testPropFindError() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $client->response = new Response(405, []); + $client->propFind('foo', ['{DAV:}displayname', '{urn:zim}gir']); + + } + + function testPropFindDepth1() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> + <response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> + </response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propFind('foo', ['{DAV:}displayname', '{urn:zim}gir'], 1); + + $this->assertEquals([ + '/foo' => [ + '{DAV:}displayname' => 'bar' + ], + ], $result); + + $request = $client->request; + $this->assertEquals('PROPFIND', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Depth' => ['1'], + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + function testPropPatch() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> + <response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> + </response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propPatch('foo', ['{DAV:}displayname' => 'hi', '{urn:zim}gir' => null]); + $this->assertTrue($result); + $request = $client->request; + $this->assertEquals('PROPPATCH', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + /** + * @depends testPropPatch + * @expectedException \Sabre\HTTP\ClientHttpException + */ + function testPropPatchHTTPError() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $client->response = new Response(403, [], ''); + $client->propPatch('foo', ['{DAV:}displayname' => 'hi', '{urn:zim}gir' => null]); + + } + + /** + * @depends testPropPatch + * @expectedException Sabre\HTTP\ClientException + */ + function testPropPatchMultiStatusError() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> +<response> + <href>/foo</href> + <propstat> + <prop> + <displayname /> + </prop> + <status>HTTP/1.1 403 Forbidden</status> + </propstat> +</response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $client->propPatch('foo', ['{DAV:}displayname' => 'hi', '{urn:zim}gir' => null]); + + } + + function testOPTIONS() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $client->response = new Response(207, [ + 'DAV' => 'calendar-access, extended-mkcol', + ]); + $result = $client->options(); + + $this->assertEquals( + ['calendar-access', 'extended-mkcol'], + $result + ); + + $request = $client->request; + $this->assertEquals('OPTIONS', $request->getMethod()); + $this->assertEquals('/', $request->getUrl()); + $this->assertEquals([ + ], $request->getHeaders()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/CorePluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/CorePluginTest.php new file mode 100644 index 000000000000..5c6f07ae80af --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/CorePluginTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\DAV; + +class CorePluginTest extends \PHPUnit_Framework_TestCase { + + function testGetInfo() { + + $corePlugin = new CorePlugin(); + $this->assertEquals('core', $corePlugin->getPluginInfo()['name']); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/DbTestHelperTrait.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/DbTestHelperTrait.php new file mode 100644 index 000000000000..63e35fb88ee4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/DbTestHelperTrait.php @@ -0,0 +1,143 @@ +<?php + +namespace Sabre\DAV; + +use PDO; +use PDOException; + +class DbCache { + + static $cache = []; + +} + +trait DbTestHelperTrait { + + /** + * Should be "mysql", "pgsql", "sqlite". + */ + public $driver = null; + + /** + * Returns a fully configured PDO object. + * + * @return PDO + */ + function getDb() { + + if (!$this->driver) { + throw new \Exception('You must set the $driver public property'); + } + + if (array_key_exists($this->driver, DbCache::$cache)) { + $pdo = DbCache::$cache[$this->driver]; + if ($pdo === null) { + $this->markTestSkipped($this->driver . ' was not enabled, not correctly configured or of the wrong version'); + } + return $pdo; + } + + try { + + switch ($this->driver) { + + case 'mysql' : + $pdo = new PDO(SABRE_MYSQLDSN, SABRE_MYSQLUSER, SABRE_MYSQLPASS); + break; + case 'sqlite' : + $pdo = new \PDO('sqlite:' . SABRE_TEMPDIR . '/testdb'); + break; + case 'pgsql' : + $pdo = new \PDO(SABRE_PGSQLDSN); + $version = $pdo->query('SELECT VERSION()')->fetchColumn(); + preg_match('|([0-9\.]){5,}|', $version, $matches); + $version = $matches[0]; + if (version_compare($version, '9.5.0', '<')) { + DbCache::$cache[$this->driver] = null; + $this->markTestSkipped('We require at least Postgres 9.5. This server is running ' . $version); + } + break; + + + + } + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + } catch (PDOException $e) { + + $this->markTestSkipped($this->driver . ' was not enabled or not correctly configured. Error message: ' . $e->getMessage()); + + } + + DbCache::$cache[$this->driver] = $pdo; + return $pdo; + + } + + /** + * Alias for getDb + * + * @return PDO + */ + function getPDO() { + + return $this->getDb(); + + } + + /** + * Uses .sql files from the examples directory to initialize the database. + * + * @param string $schemaName + * @return void + */ + function createSchema($schemaName) { + + $db = $this->getDb(); + + $queries = file_get_contents( + __DIR__ . '/../../../examples/sql/' . $this->driver . '.' . $schemaName . '.sql' + ); + + foreach (explode(';', $queries) as $query) { + + if (trim($query) === '') { + continue; + } + + $db->exec($query); + + } + + } + + /** + * Drops tables, if they exist + * + * @param string|string[] $tableNames + * @return void + */ + function dropTables($tableNames) { + + $tableNames = (array)$tableNames; + $db = $this->getDb(); + foreach ($tableNames as $tableName) { + $db->exec('DROP TABLE IF EXISTS ' . $tableName); + } + + + } + + function tearDown() { + + switch ($this->driver) { + + case 'sqlite' : + // Recreating sqlite, just in case + unset(DbCache::$cache[$this->driver]); + unlink(SABRE_TEMPDIR . '/testdb'); + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php new file mode 100644 index 000000000000..174a561b537c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Sabre\DAV\Exception; + +use DOMDocument; +use Sabre\DAV; + +class LockedTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d', 'DAV:'); + + $lockInfo = new DAV\Locks\LockInfo(); + $lockInfo->uri = '/foo'; + $locked = new Locked($lockInfo); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:lock-token-submitted xmlns:d="DAV:"> + <d:href>/foo</d:href> + </d:lock-token-submitted> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } + + function testSerializeAmpersand() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d', 'DAV:'); + + $lockInfo = new DAV\Locks\LockInfo(); + $lockInfo->uri = '/foo&bar'; + $locked = new Locked($lockInfo); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:lock-token-submitted xmlns:d="DAV:"> + <d:href>/foo&bar</d:href> + </d:lock-token-submitted> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php new file mode 100644 index 000000000000..7142937b4c0e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\DAV\Exception; + +class PaymentRequiredTest extends \PHPUnit_Framework_TestCase { + + function testGetHTTPCode() { + + $ex = new PaymentRequired(); + $this->assertEquals(402, $ex->getHTTPCode()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php new file mode 100644 index 000000000000..1cc691e6d381 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\DAV\Exception; + +class ServiceUnavailableTest extends \PHPUnit_Framework_TestCase { + + function testGetHTTPCode() { + + $ex = new ServiceUnavailable(); + $this->assertEquals(503, $ex->getHTTPCode()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php new file mode 100644 index 000000000000..0f58e8726be8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php @@ -0,0 +1,35 @@ +<?php + +namespace Sabre\DAV\Exception; + +use DOMDocument; +use Sabre\DAV; + +class TooManyMatchesTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d', 'DAV:'); + + $locked = new TooManyMatches(); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:number-of-matches-within-limits xmlns:d="DAV:"/> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php new file mode 100644 index 000000000000..0eb4f3dd878b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV; + +class ExceptionTest extends \PHPUnit_Framework_TestCase { + + function testStatus() { + + $e = new Exception(); + $this->assertEquals(500, $e->getHTTPCode()); + + } + + function testExceptionStatuses() { + + $c = [ + 'Sabre\\DAV\\Exception\\NotAuthenticated' => 401, + 'Sabre\\DAV\\Exception\\InsufficientStorage' => 507, + ]; + + foreach ($c as $class => $status) { + + $obj = new $class(); + $this->assertEquals($status, $obj->getHTTPCode()); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/DirectoryTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/DirectoryTest.php new file mode 100644 index 000000000000..097ebd26b92d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/DirectoryTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\FSExt; + +class DirectoryTest extends \PHPUnit_Framework_TestCase { + + function create() { + + return new Directory(SABRE_TEMPDIR); + + } + + function testCreate() { + + $dir = $this->create(); + $this->assertEquals(basename(SABRE_TEMPDIR), $dir->getName()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testChildExistDot() { + + $dir = $this->create(); + $dir->childExists('..'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php new file mode 100644 index 000000000000..f5d65a44f967 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php @@ -0,0 +1,110 @@ +<?php + +namespace Sabre\DAV\FSExt; + +require_once 'Sabre/TestUtil.php'; + +class FileTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + file_put_contents(SABRE_TEMPDIR . '/file.txt', 'Contents'); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testPut() { + + $filename = SABRE_TEMPDIR . '/file.txt'; + $file = new File($filename); + $result = $file->put('New contents'); + + $this->assertEquals('New contents', file_get_contents(SABRE_TEMPDIR . '/file.txt')); + $this->assertEquals( + '"' . + sha1( + fileinode($filename) . + filesize($filename) . + filemtime($filename) + ) . '"', + $result + ); + + } + + function testRange() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->put('0000000'); + $file->patch('111', 2, 3); + + $this->assertEquals('0001110', file_get_contents(SABRE_TEMPDIR . '/file.txt')); + + } + + function testRangeStream() { + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, "222"); + rewind($stream); + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->put('0000000'); + $file->patch($stream, 2, 3); + + $this->assertEquals('0002220', file_get_contents(SABRE_TEMPDIR . '/file.txt')); + + } + + + function testGet() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertEquals('Contents', stream_get_contents($file->get())); + + } + + function testDelete() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->delete(); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/file.txt')); + + } + + function testGetETag() { + + $filename = SABRE_TEMPDIR . '/file.txt'; + $file = new File($filename); + $this->assertEquals( + '"' . + sha1( + fileinode($filename) . + filesize($filename) . + filemtime($filename) + ) . '"', + $file->getETag() + ); + } + + function testGetContentType() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertNull($file->getContentType()); + + } + + function testGetSize() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertEquals(8, $file->getSize()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php new file mode 100644 index 000000000000..20fca490a35e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php @@ -0,0 +1,246 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerTest extends DAV\AbstractServer{ + + protected function getRootNode() { + + return new Directory($this->tempDir); + + } + + function testGet() { + + $request = new HTTP\Request('GET', '/test.txt'); + $filename = $this->tempDir . '/test.txt'; + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), 'Invalid status code received.'); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($filename)))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } + + function testHEAD() { + + $request = new HTTP\Request('HEAD', '/test.txt'); + $filename = $this->tempDir . '/test.txt'; + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testPut() { + + $request = new HTTP\Request('PUT', '/testput.txt'); + $filename = $this->tempDir . '/testput.txt'; + $request->setBody('Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('Testing new file', file_get_contents($filename)); + + } + + function testPutAlreadyExists() { + + $request = new HTTP\Request('PUT', '/test.txt', ['If-None-Match' => '*']); + $request->setBody('Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(412, $this->response->status); + $this->assertNotEquals('Testing new file', file_get_contents($this->tempDir . '/test.txt')); + + } + + function testMkcol() { + + $request = new HTTP\Request('MKCOL', '/testcol'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertTrue(is_dir($this->tempDir . '/testcol')); + + } + + function testPutUpdate() { + + $request = new HTTP\Request('PUT', '/test.txt'); + $request->setBody('Testing updated file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('0', $this->response->getHeader('Content-Length')); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('Testing updated file', file_get_contents($this->tempDir . '/test.txt')); + + } + + function testDelete() { + + $request = new HTTP\Request('DELETE', '/test.txt'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertFalse(file_exists($this->tempDir . '/test.txt')); + + } + + function testDeleteDirectory() { + + mkdir($this->tempDir . '/testcol'); + file_put_contents($this->tempDir . '/testcol/test.txt', 'Hi! I\'m a file with a short lifespan'); + + $request = new HTTP\Request('DELETE', '/testcol'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertFalse(file_exists($this->tempDir . '/testcol')); + + } + + function testOptions() { + + $request = new HTTP\Request('OPTIONS', '/'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [DAV\Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testMove() { + + mkdir($this->tempDir . '/testcol'); + + $request = new HTTP\Request('MOVE', '/test.txt', ['Destination' => '/testcol/test2.txt']); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + + $this->assertEquals([ + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [DAV\Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertTrue( + is_file($this->tempDir . '/testcol/test2.txt') + ); + + + } + + /** + * This test checks if it's possible to move a non-FSExt collection into a + * FSExt collection. + * + * The moveInto function *should* ignore the object and let sabredav itself + * execute the slow move. + */ + function testMoveOtherObject() { + + mkdir($this->tempDir . '/tree1'); + mkdir($this->tempDir . '/tree2'); + + $tree = new DAV\Tree(new DAV\SimpleCollection('root', [ + new DAV\FS\Directory($this->tempDir . '/tree1'), + new DAV\FSExt\Directory($this->tempDir . '/tree2'), + ])); + $this->server->tree = $tree; + + $request = new HTTP\Request('MOVE', '/tree1', ['Destination' => '/tree2/tree1']); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + + $this->assertEquals([ + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [DAV\Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertTrue( + is_dir($this->tempDir . '/tree2/tree1') + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php new file mode 100644 index 000000000000..d41abc00a2b0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php @@ -0,0 +1,337 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class GetIfConditionsTest extends AbstractServer { + + function testNoConditions() { + + $request = new HTTP\Request(); + + $conditions = $this->server->getIfConditions($request); + $this->assertEquals([], $conditions); + + } + + function testLockToken() { + + $request = new HTTP\Request('GET', '/path/', ['If' => '(<opaquelocktoken:token1>)']); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'path', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + ], + + ], + + ]; + + $this->assertEquals($compare, $conditions); + + } + + function testNotLockToken() { + + $serverVars = [ + 'HTTP_IF' => '(Not <opaquelocktoken:token1>)', + 'REQUEST_URI' => '/bla' + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'bla', + 'tokens' => [ + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + ], + + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function testLockTokenUrl() { + + $serverVars = [ + 'HTTP_IF' => '<http://www.example.com/> (<opaquelocktoken:token1>)', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => '', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + ], + + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function test2LockTokens() { + + $serverVars = [ + 'HTTP_IF' => '(<opaquelocktoken:token1>) (Not <opaquelocktoken:token2>)', + 'REQUEST_URI' => '/bla', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'bla', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ], + ], + + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function test2UriLockTokens() { + + $serverVars = [ + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1>) <http://www.example.org/node2> (Not <opaquelocktoken:token2>)', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'node1', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + ], + ], + [ + 'uri' => 'node2', + 'tokens' => [ + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ], + ], + + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function test2UriMultiLockTokens() { + + $serverVars = [ + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1>) (<opaquelocktoken:token2>) <http://www.example.org/node2> (Not <opaquelocktoken:token3>)', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'node1', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ], + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ], + ], + ], + [ + 'uri' => 'node2', + 'tokens' => [ + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token3', + 'etag' => '', + ], + ], + + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function testEtag() { + + $serverVars = [ + 'HTTP_IF' => '(["etag1"])', + 'REQUEST_URI' => '/foo', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'foo', + 'tokens' => [ + [ + 'negate' => false, + 'token' => '', + 'etag' => '"etag1"', + ], + ], + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function test2Etags() { + + $serverVars = [ + 'HTTP_IF' => '<http://www.example.org/> (["etag1"]) (["etag2"])', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => '', + 'tokens' => [ + [ + 'negate' => false, + 'token' => '', + 'etag' => '"etag1"', + ], + [ + 'negate' => false, + 'token' => '', + 'etag' => '"etag2"', + ], + ], + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + + function testComplexIf() { + + $serverVars = [ + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1> ["etag1"]) ' . + '(Not <opaquelocktoken:token2>) (["etag2"]) <http://www.example.org/node2> ' . + '(<opaquelocktoken:token3>) (Not <opaquelocktoken:token4>) (["etag3"])', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = [ + + [ + 'uri' => 'node1', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '"etag1"', + ], + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ], + [ + 'negate' => false, + 'token' => '', + 'etag' => '"etag2"', + ], + ], + ], + [ + 'uri' => 'node2', + 'tokens' => [ + [ + 'negate' => false, + 'token' => 'opaquelocktoken:token3', + 'etag' => '', + ], + [ + 'negate' => true, + 'token' => 'opaquelocktoken:token4', + 'etag' => '', + ], + [ + 'negate' => false, + 'token' => '', + 'etag' => '"etag3"', + ], + ], + ], + + ]; + $this->assertEquals($compare, $conditions); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php new file mode 100644 index 000000000000..cd8bee9686d3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php @@ -0,0 +1,188 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class HTTPPreferParsingTest extends \Sabre\DAVServerTest { + + function testParseSimple() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'return-asynch', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => true, + 'return' => null, + 'handling' => null, + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + function testParseValue() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'wait=10', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => null, + 'handling' => null, + 'wait' => '10', + ], $server->getHTTPPrefer()); + + } + + function testParseMultiple() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'return-minimal, strict,lenient', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => 'minimal', + 'handling' => 'lenient', + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + function testParseWeirdValue() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'BOOOH', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => null, + 'handling' => null, + 'wait' => null, + 'boooh' => true, + ], $server->getHTTPPrefer()); + + } + + function testBrief() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_BRIEF' => 't', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => 'minimal', + 'handling' => null, + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + /** + * propfindMinimal + * + * @return void + */ + function testpropfindMinimal() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PROPFIND', + 'REQUEST_URI' => '/', + 'HTTP_PREFER' => 'return-minimal', + ]); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:something /> + <d:resourcetype /> + </d:prop> +</d:propfind> +BLA + ); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + + $this->assertEquals(207, $response->getStatus(), $body); + + $this->assertTrue(strpos($body, 'resourcetype') !== false, $body); + $this->assertTrue(strpos($body, 'something') === false, $body); + + } + + function testproppatchMinimal() { + + $request = new HTTP\Request('PROPPATCH', '/', ['Prefer' => 'return-minimal']); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:something>nope!</d:something> + </d:prop> + </d:set> +</d:propertyupdate> +BLA + ); + + $this->server->on('propPatch', function($path, PropPatch $propPatch) { + + $propPatch->handle('{DAV:}something', function($props) { + return true; + }); + + }); + + $response = $this->request($request); + + $this->assertEquals(0, strlen($response->body), 'Expected empty body: ' . $response->body); + $this->assertEquals(204, $response->status); + + } + + function testproppatchMinimalError() { + + $request = new HTTP\Request('PROPPATCH', '/', ['Prefer' => 'return-minimal']); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:something>nope!</d:something> + </d:prop> + </d:set> +</d:propertyupdate> +BLA + ); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + + $this->assertEquals(207, $response->status); + $this->assertTrue(strpos($body, 'something') !== false); + $this->assertTrue(strpos($body, '403 Forbidden') !== false, $body); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpCopyTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpCopyTest.php new file mode 100644 index 000000000000..b5e64369ef93 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpCopyTest.php @@ -0,0 +1,199 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the COPY request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpCopyTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'content1', + 'file2' => 'content2', + 'coll1' => [ + 'file3' => 'content3', + 'file4' => 'content4', + ] + ]); + + } + + function testCopyFile() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file5' + ]); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $this->assertEquals('content1', $this->tree->getChild('file5')->get()); + + } + + function testCopyFileToSelf() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file1' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus()); + + } + + function testCopyFileToExisting() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus()); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + + } + + function testCopyFileToExistingOverwriteT() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'T', + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus()); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + + } + + function testCopyFileToExistingOverwriteBadValue() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'B', + ]); + $response = $this->request($request); + $this->assertEquals(400, $response->getStatus()); + + } + + function testCopyFileNonExistantParent() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/notfound/file2', + ]); + $response = $this->request($request); + $this->assertEquals(409, $response->getStatus()); + + } + + function testCopyFileToExistingOverwriteF() { + + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'F', + ]); + $response = $this->request($request); + $this->assertEquals(412, $response->getStatus()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + + } + + function testCopyFileToExistinBlockedCreateDestination() { + + $this->server->on('beforeBind', function($path) { + + if ($path === 'file2') { + return false; + } + + }); + $request = new HTTP\Request('COPY', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'T', + ]); + $response = $this->request($request); + + // This checks if the destination file is intact. + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + + } + + function testCopyColl() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/coll2' + ]); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $this->assertEquals('content3', $this->tree->getChild('coll2')->getChild('file3')->get()); + + } + + function testCopyCollToSelf() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/coll1' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus()); + + } + + function testCopyCollToExisting() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus()); + $this->assertEquals('content3', $this->tree->getChild('file2')->getChild('file3')->get()); + + } + + function testCopyCollToExistingOverwriteT() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/file2', + 'Overwrite' => 'T', + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus()); + $this->assertEquals('content3', $this->tree->getChild('file2')->getChild('file3')->get()); + + } + + function testCopyCollToExistingOverwriteF() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/file2', + 'Overwrite' => 'F', + ]); + $response = $this->request($request); + $this->assertEquals(412, $response->getStatus()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + + } + + function testCopyCollIntoSubtree() { + + $request = new HTTP\Request('COPY', '/coll1', [ + 'Destination' => '/coll1/subcol', + ]); + $response = $this->request($request); + $this->assertEquals(409, $response->getStatus()); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php new file mode 100644 index 000000000000..bd1b33150576 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php @@ -0,0 +1,137 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the PUT request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpDeleteTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + 'dir' => [ + 'subfile' => 'bar', + 'subfile2' => 'baz', + ], + ]); + + } + + /** + * A successful DELETE + */ + function testDelete() { + + $request = new HTTP\Request('DELETE', '/file1'); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], + $response->getHeaders() + ); + + } + + /** + * Deleting a Directory + */ + function testDeleteDirectory() { + + $request = new HTTP\Request('DELETE', '/dir'); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], + $response->getHeaders() + ); + + } + + /** + * DELETE on a node that does not exist + */ + function testDeleteNotFound() { + + $request = new HTTP\Request('DELETE', '/file2'); + $response = $this->request($request); + + $this->assertEquals( + 404, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } + + /** + * DELETE with preconditions + */ + function testDeletePreconditions() { + + $request = new HTTP\Request('DELETE', '/file1', [ + 'If-Match' => '"' . md5('foo') . '"', + ]); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } + + /** + * DELETE with incorrect preconditions + */ + function testDeletePreconditionsFailed() { + + $request = new HTTP\Request('DELETE', '/file1', [ + 'If-Match' => '"' . md5('bar') . '"', + ]); + + $response = $this->request($request); + + $this->assertEquals( + 412, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php new file mode 100644 index 000000000000..1eefba706574 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php @@ -0,0 +1,158 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the GET request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpGetTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + new Mock\Collection('dir', []), + new Mock\StreamingFile('streaming', 'stream') + ]); + + } + + function testGet() { + + $request = new HTTP\Request('GET', '/file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGetHttp10() { + + $request = new HTTP\Request('GET', '/file1'); + $request->setHttpVersion('1.0'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('1.0', $response->getHttpVersion()); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGet404() { + + $request = new HTTP\Request('GET', '/notfound'); + $response = $this->request($request); + + $this->assertEquals(404, $response->getStatus()); + + } + + function testGet404_aswell() { + + $request = new HTTP\Request('GET', '/file1/subfile'); + $response = $this->request($request); + + $this->assertEquals(404, $response->getStatus()); + + } + + /** + * We automatically normalize double slashes. + */ + function testGetDoubleSlash() { + + $request = new HTTP\Request('GET', '//file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGetCollection() { + + $request = new HTTP\Request('GET', '/dir'); + $response = $this->request($request); + + $this->assertEquals(501, $response->getStatus()); + + } + + function testGetStreaming() { + + $request = new HTTP\Request('GET', '/streaming'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + ], + $response->getHeaders() + ); + + $this->assertEquals('stream', $response->getBodyAsString()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php new file mode 100644 index 000000000000..2cd1c5ea3c13 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php @@ -0,0 +1,97 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the HEAD request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpHeadTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + new Mock\Collection('dir', []), + new Mock\StreamingFile('streaming', 'stream') + ]); + + } + + function testHEAD() { + + $request = new HTTP\Request('HEAD', '//file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('', $response->getBodyAsString()); + + } + + /** + * According to the specs, HEAD should behave identical to GET. But, broken + * clients needs HEAD requests on collections to respond with a 200, so + * that's what we do. + */ + function testHEADCollection() { + + $request = new HTTP\Request('HEAD', '/dir'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + } + + /** + * HEAD automatically internally maps to GET via a sub-request. + * The Auth plugin must not be triggered twice for these, so we'll + * test for that. + */ + function testDoubleAuth() { + + $count = 0; + + $authBackend = new Auth\Backend\BasicCallBack(function($userName, $password) use (&$count) { + $count++; + return true; + }); + $this->server->addPlugin( + new Auth\Plugin( + $authBackend + ) + ); + $request = new HTTP\Request('HEAD', '/file1', ['Authorization' => 'Basic ' . base64_encode('user:pass')]); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + $this->assertEquals(1, $count, 'Auth was triggered twice :('); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php new file mode 100644 index 000000000000..52f7c674ee9a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php @@ -0,0 +1,119 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the MOVE request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpMoveTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'content1', + 'file2' => 'content2', + ]); + + } + + function testMoveToSelf() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file1' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus()); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + + } + + function testMove() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file3' + ]); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus(), print_r($response, true)); + $this->assertEquals('content1', $this->tree->getChild('file3')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExisting() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), print_r($response, true)); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExistingOverwriteT() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'T', + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), print_r($response, true)); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExistingOverwriteF() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'F', + ]); + $response = $this->request($request); + $this->assertEquals(412, $response->getStatus(), print_r($response, true)); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + $this->assertTrue($this->tree->childExists('file1')); + $this->assertTrue($this->tree->childExists('file2')); + + } + + /** + * If we MOVE to an existing file, but a plugin prevents the original from + * being deleted, we need to make sure that the server does not delete + * the destination. + */ + function testMoveToExistingBlockedDeleteSource() { + + $this->server->on('beforeUnbind', function($path) { + + if ($path === 'file1') { + throw new \Sabre\DAV\Exception\Forbidden('uh oh'); + } + + }); + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus(), print_r($response, true)); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + $this->assertTrue($this->tree->childExists('file1')); + $this->assertTrue($this->tree->childExists('file2')); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php new file mode 100644 index 000000000000..86480b1c22a2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php @@ -0,0 +1,349 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the PUT request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpPutTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + ]); + + } + + /** + * A successful PUT of a new file. + */ + function testPut() { + + $request = new HTTP\Request('PUT', '/file2', [], 'hello'); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus(), 'Incorrect status code received. Full response body:' . $response->getBodyAsString()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * A successful PUT on an existing file. + * + * @depends testPut + */ + function testPutExisting() { + + $request = new HTTP\Request('PUT', '/file1', [], 'bar'); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus()); + + $this->assertEquals( + 'bar', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('bar') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on existing file with If-Match: * + * + * @depends testPutExisting + */ + function testPutExistingIfMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on existing file with If-Match: with a correct etag + * + * @depends testPutExisting + */ + function testPutExistingIfMatchCorrect() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-Match' => '"' . md5('foo') . '"'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->status); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'], + ], + $response->getHeaders() + ); + + } + + /** + * PUT with Content-Range should be rejected. + * + * @depends testPut + */ + function testPutContentRange() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['Content-Range' => 'bytes/100-200'], + 'hello' + ); + + $response = $this->request($request); + $this->assertEquals(400, $response->getStatus()); + + } + + /** + * PUT on non-existing file with If-None-Match: * should work. + * + * @depends testPut + */ + function testPutIfNoneMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['If-None-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on non-existing file with If-Match: * should fail. + * + * @depends testPut + */ + function testPutIfMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['If-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(412, $response->getStatus()); + + } + + /** + * PUT on existing file with If-None-Match: * should fail. + * + * @depends testPut + */ + function testPutExistingIfNoneMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-None-Match' => '*'], + 'hello' + ); + $request->setBody('hello'); + + $response = $this->request($request); + + $this->assertEquals(412, $response->getStatus()); + + } + + /** + * PUT thats created in a non-collection should be rejected. + * + * @depends testPut + */ + function testPutNoParent() { + + $request = new HTTP\Request( + 'PUT', + '/file1/file2', + [], + 'hello' + ); + + $response = $this->request($request); + $this->assertEquals(409, $response->getStatus()); + + } + + /** + * Finder may sometimes make a request, which gets its content-body + * stripped. We can't always prevent this from happening, but in some cases + * we can detected this and return an error instead. + * + * @depends testPut + */ + function testFinderPutSuccess() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['X-Expected-Entity-Length' => '5'], + 'hello' + ); + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'], + ], + $response->getHeaders() + ); + + } + + /** + * Same as the last one, but in this case we're mimicing a failed request. + * + * @depends testFinderPutSuccess + */ + function testFinderPutFail() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['X-Expected-Entity-Length' => '5'], + '' + ); + + $response = $this->request($request); + + $this->assertEquals(403, $response->getStatus()); + + } + + /** + * Plugins can intercept PUT. We need to make sure that works. + * + * @depends testPut + */ + function testPutIntercept() { + + $this->server->on('beforeBind', function($uri) { + $this->server->httpResponse->setStatus(418); + return false; + }); + + $request = new HTTP\Request('PUT', '/file2', [], 'hello'); + $response = $this->request($request); + + $this->assertEquals(418, $response->getStatus(), 'Incorrect status code received. Full response body: ' . $response->getBodyAsString()); + + $this->assertFalse( + $this->server->tree->nodeExists('file2') + ); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + ], $response->getHeaders()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php new file mode 100644 index 000000000000..ba2cf3dc152c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php @@ -0,0 +1,106 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/TestUtil.php'; + +class Issue33Test extends \PHPUnit_Framework_TestCase { + + function setUp() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testCopyMoveInfo() { + + $bar = new SimpleCollection('bar'); + $root = new SimpleCollection('webdav', [$bar]); + + $server = new Server($root); + $server->setBaseUri('/webdav/'); + + $serverVars = [ + 'REQUEST_URI' => '/webdav/bar', + 'HTTP_DESTINATION' => 'http://dev2.tribalos.com/webdav/%C3%A0fo%C3%B3', + 'HTTP_OVERWRITE' => 'F', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + + $server->httpRequest = $request; + + $info = $server->getCopyAndMoveInfo($request); + + $this->assertEquals('%C3%A0fo%C3%B3', urlencode($info['destination'])); + $this->assertFalse($info['destinationExists']); + $this->assertFalse($info['destinationNode']); + + } + + function testTreeMove() { + + mkdir(SABRE_TEMPDIR . '/issue33'); + $dir = new FS\Directory(SABRE_TEMPDIR . '/issue33'); + + $dir->createDirectory('bar'); + + $tree = new Tree($dir); + $tree->move('bar', urldecode('%C3%A0fo%C3%B3')); + + $node = $tree->getNodeForPath(urldecode('%C3%A0fo%C3%B3')); + $this->assertEquals(urldecode('%C3%A0fo%C3%B3'), $node->getName()); + + } + + function testDirName() { + + $dirname1 = 'bar'; + $dirname2 = urlencode('%C3%A0fo%C3%B3'); + + $this->assertTrue(dirname($dirname1) == dirname($dirname2)); + + } + + /** + * @depends testTreeMove + * @depends testCopyMoveInfo + */ + function testEverything() { + + // Request object + $serverVars = [ + 'REQUEST_METHOD' => 'MOVE', + 'REQUEST_URI' => '/webdav/bar', + 'HTTP_DESTINATION' => 'http://dev2.tribalos.com/webdav/%C3%A0fo%C3%B3', + 'HTTP_OVERWRITE' => 'F', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $response = new HTTP\ResponseMock(); + + // Server setup + mkdir(SABRE_TEMPDIR . '/issue33'); + $dir = new FS\Directory(SABRE_TEMPDIR . '/issue33'); + + $dir->createDirectory('bar'); + + $tree = new Tree($dir); + + $server = new Server($tree); + $server->setBaseUri('/webdav/'); + + $server->httpRequest = $request; + $server->httpResponse = $response; + $server->sapi = new HTTP\SapiMock(); + $server->exec(); + + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/issue33/' . urldecode('%C3%A0fo%C3%B3'))); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php new file mode 100644 index 000000000000..bbde69097a52 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php @@ -0,0 +1,196 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV; + +abstract class AbstractTest extends \PHPUnit_Framework_TestCase { + + /** + * @abstract + * @return AbstractBackend + */ + abstract function getBackend(); + + function testSetup() { + + $backend = $this->getBackend(); + $this->assertInstanceOf('Sabre\\DAV\\Locks\\Backend\\AbstractBackend', $backend); + + } + + /** + * @depends testSetup + */ + function testGetLocks() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + $lock->uri = 'someuri'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + + $this->assertEquals(1, count($locks)); + $this->assertEquals('Sinterklaas', $locks[0]->owner); + $this->assertEquals('someuri', $locks[0]->uri); + + } + + /** + * @depends testGetLocks + */ + function testGetLocksParent() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = DAV\Server::DEPTH_INFINITY; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + + $this->assertEquals(1, count($locks)); + $this->assertEquals('Sinterklaas', $locks[0]->owner); + $this->assertEquals('someuri', $locks[0]->uri); + + } + + + /** + * @depends testGetLocks + */ + function testGetLocksParentDepth0() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = 0; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + + $this->assertEquals(0, count($locks)); + + } + + function testGetLocksChildren() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = 0; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri/child', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + $this->assertEquals(1, count($locks)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(0, count($locks)); + + $locks = $backend->getLocks('someuri', true); + $this->assertEquals(1, count($locks)); + + } + + /** + * @depends testGetLocks + */ + function testLockRefresh() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + /* Second time */ + + $lock->owner = 'Santa Clause'; + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + + $this->assertEquals(1, count($locks)); + + $this->assertEquals('Santa Clause', $locks[0]->owner); + $this->assertEquals('someuri', $locks[0]->uri); + + } + + /** + * @depends testGetLocks + */ + function testUnlock() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1, count($locks)); + + $this->assertTrue($backend->unlock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(0, count($locks)); + + } + + /** + * @depends testUnlock + */ + function testUnlockUnknownToken() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1, count($locks)); + + $lock->token = 'SOME-OTHER-TOKEN'; + $this->assertFalse($backend->unlock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1, count($locks)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php new file mode 100644 index 000000000000..537996f3bbf1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php @@ -0,0 +1,24 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +require_once 'Sabre/TestUtil.php'; + +class FileTest extends AbstractTest { + + function getBackend() { + + \Sabre\TestUtil::clearTempDir(); + $backend = new File(SABRE_TEMPDIR . '/lockdb'); + return $backend; + + } + + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php new file mode 100644 index 000000000000..dd4758071785 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php @@ -0,0 +1,139 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks\LockInfo; + +/** + * Locks Mock backend. + * + * This backend stores lock information in memory. Mainly useful for testing. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Mock extends AbstractBackend { + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks) { + + $newLocks = []; + + $locks = $this->getData(); + + foreach ($locks as $lock) { + + if ($lock->uri === $uri || + //deep locks on parents + ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) || + + // locks on children + ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) { + + $newLocks[] = $lock; + + } + + } + + // Checking if we can remove any of these locks + foreach ($newLocks as $k => $lock) { + if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); + } + return $newLocks; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 1800; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getData(); + + foreach ($locks as $k => $lock) { + if ( + ($lock->token == $lockInfo->token) || + (time() > $lock->timeout + $lock->created) + ) { + unset($locks[$k]); + } + } + $locks[] = $lockInfo; + $this->putData($locks); + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $locks = $this->getData(); + foreach ($locks as $k => $lock) { + + if ($lock->token == $lockInfo->token) { + + unset($locks[$k]); + $this->putData($locks); + return true; + + } + } + return false; + + } + + protected $data = []; + + /** + * Loads the lockdata from the filesystem. + * + * @return array + */ + protected function getData() { + + return $this->data; + + } + + /** + * Saves the lockdata + * + * @param array $newData + * @return void + */ + protected function putData(array $newData) { + + $this->data = $newData; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php new file mode 100644 index 000000000000..0ba02fc8b539 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +class PDOMySQLTest extends PDOTest { + + public $driver = 'mysql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOPgSqlTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOPgSqlTest.php new file mode 100644 index 000000000000..39ee56419a28 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOPgSqlTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +class PDOPgSqlTest extends PDOTest { + + public $driver = 'pgsql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOSqliteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOSqliteTest.php new file mode 100644 index 000000000000..4b126dcf3591 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOSqliteTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +class PDOSqliteTest extends PDOTest { + + public $driver = 'sqlite'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php new file mode 100644 index 000000000000..a27eae93cef3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php @@ -0,0 +1,20 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +abstract class PDOTest extends AbstractTest { + + use \Sabre\DAV\DbTestHelperTrait; + + function getBackend() { + + $this->dropTables('locks'); + $this->createSchema('locks'); + + $pdo = $this->getPDO(); + + return new PDO($pdo); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php new file mode 100644 index 000000000000..1111db5b5c25 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php @@ -0,0 +1,124 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/TestUtil.php'; + +class MSWordTest extends \PHPUnit_Framework_TestCase { + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testLockEtc() { + + mkdir(SABRE_TEMPDIR . '/mstest'); + $tree = new DAV\FS\Directory(SABRE_TEMPDIR . '/mstest'); + + $server = new DAV\Server($tree); + $server->debugExceptions = true; + $locksBackend = new Backend\File(SABRE_TEMPDIR . '/locksdb'); + $locksPlugin = new Plugin($locksBackend); + $server->addPlugin($locksPlugin); + + $response1 = new HTTP\ResponseMock(); + + $server->httpRequest = $this->getLockRequest(); + $server->httpResponse = $response1; + $server->sapi = new HTTP\SapiMock(); + $server->exec(); + + $this->assertEquals(201, $server->httpResponse->getStatus(), 'Full response body:' . $response1->getBodyAsString()); + $this->assertTrue(!!$server->httpResponse->getHeaders('Lock-Token')); + $lockToken = $server->httpResponse->getHeader('Lock-Token'); + + //sleep(10); + + $response2 = new HTTP\ResponseMock(); + + $server->httpRequest = $this->getLockRequest2(); + $server->httpResponse = $response2; + $server->exec(); + + $this->assertEquals(201, $server->httpResponse->status); + $this->assertTrue(!!$server->httpResponse->getHeaders('Lock-Token')); + + //sleep(10); + + $response3 = new HTTP\ResponseMock(); + $server->httpRequest = $this->getPutRequest($lockToken); + $server->httpResponse = $response3; + $server->exec(); + + $this->assertEquals(204, $server->httpResponse->status); + + } + + function getLockRequest() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'HTTP_TIMEOUT' => 'Second-3600', + 'REQUEST_URI' => '/Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + ]); + + $request->setBody('<D:lockinfo xmlns:D="DAV:"> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + <D:owner> + <D:href>PC-Vista\User</D:href> + </D:owner> +</D:lockinfo>'); + + return $request; + + } + function getLockRequest2() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'HTTP_TIMEOUT' => 'Second-3600', + 'REQUEST_URI' => '/~$Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + ]); + + $request->setBody('<D:lockinfo xmlns:D="DAV:"> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + <D:owner> + <D:href>PC-Vista\User</D:href> + </D:owner> +</D:lockinfo>'); + + return $request; + + } + + function getPutRequest($lockToken) { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + 'HTTP_IF' => 'If: (' . $lockToken . ')', + ]); + $request->setBody('FAKE BODY'); + return $request; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php new file mode 100644 index 000000000000..7af490795744 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\HTTP\Request; + +class Plugin2Test extends \Sabre\DAVServerTest { + + public $setupLocks = true; + + function setUpTree() { + + $this->tree = new \Sabre\DAV\FS\Directory(SABRE_TEMPDIR); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + /** + * This test first creates a file with LOCK and then deletes it. + * + * After deleting the file, the lock should no longer be in the lock + * backend. + * + * Reported in ticket #487 + */ + function testUnlockAfterDelete() { + + $body = '<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> +</D:lockinfo>'; + + $request = new Request( + 'LOCK', + '/file.txt', + [], + $body + ); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus(), $response->getBodyAsString()); + + $this->assertEquals( + 1, + count($this->locksBackend->getLocks('file.txt', true)) + ); + + $request = new Request( + 'DELETE', + '/file.txt', + [ + 'If' => '(' . $response->getHeader('Lock-Token') . ')', + ] + ); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), $response->getBodyAsString()); + + $this->assertEquals( + 0, + count($this->locksBackend->getLocks('file.txt', true)) + ); + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php new file mode 100644 index 000000000000..dbbf6757aadb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php @@ -0,0 +1,1003 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class PluginTest extends DAV\AbstractServer { + + /** + * @var Plugin + */ + protected $locksPlugin; + + function setUp() { + + parent::setUp(); + $locksBackend = new Backend\File(SABRE_TEMPDIR . '/locksdb'); + $locksPlugin = new Plugin($locksBackend); + $this->server->addPlugin($locksPlugin); + $this->locksPlugin = $locksPlugin; + + } + + function testGetInfo() { + + $this->assertArrayHasKey( + 'name', + $this->locksPlugin->getPluginInfo() + ); + + } + + function testGetFeatures() { + + $this->assertEquals([2], $this->locksPlugin->getFeatures()); + + } + + function testGetHTTPMethods() { + + $this->assertEquals(['LOCK', 'UNLOCK'], $this->locksPlugin->getHTTPMethods('')); + + } + + function testLockNoBody() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(400, $this->response->status); + + } + + function testLock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status, 'Got an incorrect status back. Response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $elements = [ + '/d:prop', + '/d:prop/d:lockdiscovery', + '/d:prop/d:lockdiscovery/d:activelock', + '/d:prop/d:lockdiscovery/d:activelock/d:locktype', + '/d:prop/d:lockdiscovery/d:activelock/d:lockroot', + '/d:prop/d:lockdiscovery/d:activelock/d:lockroot/d:href', + '/d:prop/d:lockdiscovery/d:activelock/d:locktype/d:write', + '/d:prop/d:lockdiscovery/d:activelock/d:lockscope', + '/d:prop/d:lockdiscovery/d:activelock/d:lockscope/d:exclusive', + '/d:prop/d:lockdiscovery/d:activelock/d:depth', + '/d:prop/d:lockdiscovery/d:activelock/d:owner', + '/d:prop/d:lockdiscovery/d:activelock/d:timeout', + '/d:prop/d:lockdiscovery/d:activelock/d:locktoken', + '/d:prop/d:lockdiscovery/d:activelock/d:locktoken/d:href', + ]; + + foreach ($elements as $elem) { + $data = $xml->xpath($elem); + $this->assertEquals(1, count($data), 'We expected 1 match for the xpath expression "' . $elem . '". ' . count($data) . ' were found. Full response body: ' . $this->response->body); + } + + $depth = $xml->xpath('/d:prop/d:lockdiscovery/d:activelock/d:depth'); + $this->assertEquals('infinity', (string)$depth[0]); + + $token = $xml->xpath('/d:prop/d:lockdiscovery/d:activelock/d:locktoken/d:href'); + $this->assertEquals($this->response->getHeader('Lock-Token'), '<' . (string)$token[0] . '>', 'Token in response body didn\'t match token in response header.'); + + } + + /** + * @depends testLock + */ + function testDoubleLock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + $this->assertEquals(423, $this->response->status, 'Full response: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockRefresh() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $lockToken = $this->response->getHeader('Lock-Token'); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $request = new HTTP\Request('LOCK', '/test.txt', ['If' => '(' . $lockToken . ')']); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + $this->assertEquals(200, $this->response->status, 'We received an incorrect status code. Full response body: ' . $this->response->getBody()); + + } + + /** + * @depends testLock + */ + function testLockRefreshBadToken() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $lockToken = $this->response->getHeader('Lock-Token'); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $request = new HTTP\Request('LOCK', '/test.txt', ['If' => '(' . $lockToken . 'foobar) (<opaquelocktoken:anotherbadtoken>)']); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + $this->assertEquals(423, $this->response->getStatus(), 'We received an incorrect status code. Full response body: ' . $this->response->getBody()); + + } + + /** + * @depends testLock + */ + function testLockNoFile() { + + $request = new HTTP\Request('LOCK', '/notfound.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlockNoToken() { + + $request = new HTTP\Request('UNLOCK', '/test.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(400, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlockBadToken() { + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => '<opaquelocktoken:blablabla>']); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(409, $this->response->status, 'Got an incorrect status code. Full response body: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockPutNoToken() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $request = new HTTP\Request('PUT', '/test.txt'); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(423, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => $lockToken]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->invokeMethod($request, $this->server->httpResponse); + + $this->assertEquals(204, $this->server->httpResponse->status, 'Got an incorrect status code. Full response body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], + $this->server->httpResponse->getHeaders() + ); + + + } + + /** + * @depends testLock + */ + function testUnlockWindowsBug() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + // See Issue 123 + $lockToken = trim($lockToken, '<>'); + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => $lockToken]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->invokeMethod($request, $this->server->httpResponse); + + $this->assertEquals(204, $this->server->httpResponse->status, 'Got an incorrect status code. Full response body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], + $this->server->httpResponse->getHeaders() + ); + + + } + + /** + * @depends testLock + */ + function testLockRetainOwner() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ]); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner>Evert</D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + $locks = $this->locksPlugin->getLocks('test.txt'); + $this->assertEquals(1, count($locks)); + $this->assertEquals('Evert', $locks[0]->owner); + + + } + + /** + * @depends testLock + */ + function testLockPutBadToken() { + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(<opaquelocktoken:token1>)', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + // $this->assertEquals('412 Precondition failed',$this->response->status); + $this->assertEquals(423, $this->response->status); + + } + + /** + * @depends testLock + */ + function testLockDeleteParent() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'DELETE', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockDeleteSucceed() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'DELETE', + 'HTTP_IF' => '(' . $this->response->getHeader('Lock-Token') . ')', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockCopyLockSource() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockCopyLockDestination() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child2.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status, 'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockMoveLockSourceLocked() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status, 'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockMoveLockSourceSucceed() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + 'HTTP_IF' => '(' . $this->response->getHeader('Lock-Token') . ')', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'A valid lock-token was provided for the source, so this MOVE operation must succeed. Full response body: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockMoveLockDestination() { + + $serverVars = [ + 'REQUEST_URI' => '/dir/child2.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status, 'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockMoveLockParent() { + + $serverVars = [ + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_DEPTH' => 'infinite', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + 'HTTP_IF' => '</dir> (' . $this->response->getHeader('Lock-Token') . ')', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'We locked the parent of both the source and destination, but the move didn\'t succeed.'); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockPutGoodToken() { + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(' . $this->response->getHeader('Lock-Token') . ')', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(204, $this->response->status); + + } + + /** + * @depends testLock + */ + function testLockPutUnrelatedToken() { + + $request = new HTTP\Request('LOCK', '/unrelated.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->getStatus()); + + $request = new HTTP\Request( + 'PUT', + '/test.txt', + ['If' => '</unrelated.txt> (' . $this->response->getHeader('Lock-Token') . ')'] + ); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(204, $this->response->status); + + } + + function testPutWithIncorrectETag() { + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(["etag1"])', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(412, $this->response->status); + + } + + /** + * @depends testPutWithIncorrectETag + */ + function testPutWithCorrectETag() { + + // We need an ETag-enabled file node. + $tree = new DAV\Tree(new DAV\FSExt\Directory(SABRE_TEMPDIR)); + $this->server->tree = $tree; + + $filename = SABRE_TEMPDIR . '/test.txt'; + $etag = sha1( + fileinode($filename) . + filesize($filename) . + filemtime($filename) + ); + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(["' . $etag . '"])', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(204, $this->response->status, 'Incorrect status received. Full response body:' . $this->response->body); + + } + + function testDeleteWithETagOnCollection() { + + $serverVars = [ + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'DELETE', + 'HTTP_IF' => '(["etag1"])', + ]; + $request = HTTP\Sapi::createFromServerArray($serverVars); + + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(412, $this->response->status); + + } + + function testGetTimeoutHeader() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_TIMEOUT' => 'second-100', + ]); + + $this->server->httpRequest = $request; + $this->assertEquals(100, $this->locksPlugin->getTimeoutHeader()); + + } + + function testGetTimeoutHeaderTwoItems() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_TIMEOUT' => 'second-5, infinite', + ]); + + $this->server->httpRequest = $request; + $this->assertEquals(5, $this->locksPlugin->getTimeoutHeader()); + + } + + function testGetTimeoutHeaderInfinite() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_TIMEOUT' => 'infinite, second-5', + ]); + + $this->server->httpRequest = $request; + $this->assertEquals(LockInfo::TIMEOUT_INFINITE, $this->locksPlugin->getTimeoutHeader()); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testGetTimeoutHeaderInvalid() { + + $request = HTTP\Sapi::createFromServerArray([ + 'HTTP_TIMEOUT' => 'yourmom', + ]); + + $this->server->httpRequest = $request; + $this->locksPlugin->getTimeoutHeader(); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php new file mode 100644 index 000000000000..fded5e474a65 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php @@ -0,0 +1,168 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV; + +/** + * Mock Collection. + * + * This collection quickly allows you to create trees of nodes. + * Children are specified as an array. + * + * Every key a filename, every array value is either: + * * an array, for a sub-collection + * * a string, for a file + * * An instance of \Sabre\DAV\INode. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Collection extends DAV\Collection { + + protected $name; + protected $children; + protected $parent; + + /** + * Creates the object + * + * @param string $name + * @param array $children + * @param Collection $parent + * @return void + */ + function __construct($name, array $children = [], Collection $parent = null) { + + $this->name = $name; + foreach ($children as $key => $value) { + if (is_string($value)) { + $this->children[] = new File($key, $value, $this); + } elseif (is_array($value)) { + $this->children[] = new self($key, $value, $this); + } elseif ($value instanceof \Sabre\DAV\INode) { + $this->children[] = $value; + } else { + throw new \InvalidArgumentException('Unknown value passed in $children'); + } + } + $this->parent = $parent; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After successful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + */ + function createFile($name, $data = null) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $this->children[] = new File($name, $data, $this); + return '"' . md5($data) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + $this->children[] = new self($name); + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + return $this->children; + + } + + /** + * Adds an already existing node to this collection. + * + * @param \Sabre\DAV\INode $node + */ + function addNode(\Sabre\DAV\INode $node) { + + $this->children[] = $node; + + } + + /** + * Removes a childnode from this node. + * + * @param string $name + * @return void + */ + function deleteChild($name) { + + foreach ($this->children as $key => $value) { + + if ($value->getName() == $name) { + unset($this->children[$key]); + return; + } + + } + + } + + /** + * Deletes this collection and all its children,. + * + * @return void + */ + function delete() { + + foreach ($this->getChildren() as $child) { + $this->deleteChild($child->getName()); + } + $this->parent->deleteChild($this->getName()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php new file mode 100644 index 000000000000..a624b6b6bf18 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php @@ -0,0 +1,163 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV; + +/** + * Mock File + * + * See the Collection in this directory for more details. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends DAV\File { + + protected $name; + protected $contents; + protected $parent; + protected $lastModified; + + /** + * Creates the object + * + * @param string $name + * @param resource $contents + * @param Collection $parent + * @param int $lastModified + * @return void + */ + function __construct($name, $contents, Collection $parent = null, $lastModified = -1) { + + $this->name = $name; + $this->put($contents); + $this->parent = $parent; + + if ($lastModified === -1) { + $lastModified = time(); + } + + $this->lastModified = $lastModified; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Changes the name of the node. + * + * @param string $name + * @return void + */ + function setName($name) { + + $this->name = $name; + + } + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a successful put operation, you may choose to return an ETag. The + * etag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @return string|null + */ + function put($data) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $this->contents = $data; + return '"' . md5($data) . '"'; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $this->contents; + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * + * Return null if the ETag can not effectively be determined + * + * @return void + */ + function getETag() { + + return '"' . md5($this->contents) . '"'; + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return strlen($this->contents); + + } + + /** + * Delete the node + * + * @return void + */ + function delete() { + + $this->parent->deleteChild($this->name); + + } + + /** + * Returns the last modification time as a unix timestamp. + * If the information is not available, return null. + * + * @return int + */ + function getLastModified() { + + return $this->lastModified; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php new file mode 100644 index 000000000000..af3fd2d3f2fc --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php @@ -0,0 +1,94 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV\IProperties; +use Sabre\DAV\PropPatch; + +/** + * A node specifically for testing property-related operations + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropertiesCollection extends Collection implements IProperties { + + public $failMode = false; + + public $properties; + + /** + * Creates the object + * + * @param string $name + * @param array $children + * @param array $properties + * @return void + */ + function __construct($name, array $children, array $properties = []) { + + parent::__construct($name, $children, null); + $this->properties = $properties; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $proppatch + * @return bool|array + */ + function propPatch(PropPatch $proppatch) { + + $proppatch->handleRemaining(function($updateProperties) { + + switch ($this->failMode) { + case 'updatepropsfalse' : return false; + case 'updatepropsarray' : + $r = []; + foreach ($updateProperties as $k => $v) $r[$k] = 402; + return $r; + case 'updatepropsobj' : + return new \STDClass(); + } + + }); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * Note that it's fine to liberally give properties back, instead of + * conforming to the list of requested properties. + * The Server class will filter out the extra. + * + * @param array $requestedProperties + * @return array + */ + function getProperties($requestedProperties) { + + $returnedProperties = []; + foreach ($requestedProperties as $requestedProperty) { + if (isset($this->properties[$requestedProperty])) { + $returnedProperties[$requestedProperty] = + $this->properties[$requestedProperty]; + } + } + return $returnedProperties; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/SharedNode.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/SharedNode.php new file mode 100644 index 000000000000..503d64070959 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/SharedNode.php @@ -0,0 +1,125 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV\Sharing\ISharedNode; +use Sabre\DAV\Sharing\Sharee; + +class SharedNode extends \Sabre\DAV\Node implements ISharedNode { + + protected $name; + protected $access; + protected $invites = []; + + function __construct($name, $access) { + + $this->name = $name; + $this->access = $access; + + } + + function getName() { + + return $this->name; + + } + + /** + * Returns the 'access level' for the instance of this shared resource. + * + * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_ + * constants. + * + * @return int + */ + function getShareAccess() { + + return $this->access; + + } + + /** + * This function must return a URI that uniquely identifies the shared + * resource. This URI should be identical across instances, and is + * also used in several other XML bodies to connect invites to + * resources. + * + * This may simply be a relative reference to the original shared instance, + * but it could also be a urn. As long as it's a valid URI and unique. + * + * @return string + */ + function getShareResourceUri() { + + return 'urn:example:bar'; + + } + + /** + * Updates the list of sharees. + * + * Every item must be a Sharee object. + * + * @param Sharee[] $sharees + * @return void + */ + function updateInvites(array $sharees) { + + foreach ($sharees as $sharee) { + + if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) { + // Removal + foreach ($this->invites as $k => $invitee) { + + if ($invitee->href = $sharee->href) { + unset($this->invites[$k]); + } + + } + + } else { + foreach ($this->invites as $k => $invitee) { + + if ($invitee->href = $sharee->href) { + if (!$sharee->inviteStatus) { + $sharee->inviteStatus = $invitee->inviteStatus; + } + // Overwriting an existing invitee + $this->invites[$k] = $sharee; + continue 2; + } + + } + if (!$sharee->inviteStatus) { + $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE; + } + // Adding a new invitee + $this->invites[] = $sharee; + } + + } + + } + + /** + * Returns the list of people whom this resource is shared with. + * + * Every item in the returned array must be a Sharee object with + * at least the following properties set: + * + * * $href + * * $shareAccess + * * $inviteStatus + * + * and optionally: + * + * * $properties + * + * @return \Sabre\DAV\Xml\Element\Sharee[] + */ + function getInvites() { + + return $this->invites; + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php new file mode 100644 index 000000000000..d60a49d092e8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php @@ -0,0 +1,102 @@ +<?php + +namespace Sabre\DAV\Mock; + +/** + * Mock Streaming File File + * + * Works similar to the mock file, but this one works with streams and has no + * content-length or etags. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class StreamingFile extends File { + + protected $size; + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a successful put operation, you may choose to return an ETag. The + * etag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @return string|null + */ + function put($data) { + + if (is_string($data)) { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + $data = $stream; + } + $this->contents = $data; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $this->contents; + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * + * Return null if the ETag can not effectively be determined + * + * @return void + */ + function getETag() { + + return null; + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return $this->size; + + } + + /** + * Allows testing scripts to set the resource's file size. + * + * @param int $size + * @return void + */ + function setSize($size) { + + $this->size = $size; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/MockLogger.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/MockLogger.php new file mode 100644 index 000000000000..03332569300f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/MockLogger.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\DAV; + +use Psr\Log\AbstractLogger; + +/** + * The MockLogger is a simple PSR-3 implementation that we can use to test + * whether things get logged correctly. + * + * @copyright Copyright (C) fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MockLogger extends AbstractLogger { + + public $logs = []; + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + function log($level, $message, array $context = []) { + + $this->logs[] = [ + $level, + $message, + $context + ]; + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php new file mode 100644 index 000000000000..3213fcb1b456 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\DAV\Mount; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class PluginTest extends DAV\AbstractServer { + + function setUp() { + + parent::setUp(); + $this->server->addPlugin(new Plugin()); + + } + + function testPassThrough() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'GET', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(501, $this->response->status, 'We expected GET to not be implemented for Directories. Response body: ' . $this->response->body); + + } + + function testMountResponse() { + + $serverVars = [ + 'REQUEST_URI' => '/?mount', + 'REQUEST_METHOD' => 'GET', + 'QUERY_STRING' => 'mount', + 'HTTP_HOST' => 'example.org', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(200, $this->response->status); + + $xml = simplexml_load_string($this->response->body); + $this->assertInstanceOf('SimpleXMLElement', $xml, 'Response was not a valid xml document. The list of errors:' . print_r(libxml_get_errors(), true) . '. xml body: ' . $this->response->body . '. What type we got: ' . gettype($xml) . ' class, if object: ' . get_class($xml)); + + $xml->registerXPathNamespace('dm', 'http://purl.org/NET/webdav/mount'); + $url = $xml->xpath('//dm:url'); + $this->assertEquals('http://example.org/', (string)$url[0]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php new file mode 100644 index 000000000000..15289ce528c4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php @@ -0,0 +1,100 @@ +<?php + +namespace Sabre\DAV; + +require_once 'Sabre/TestUtil.php'; + +class ObjectTreeTest extends \PHPUnit_Framework_TestCase { + + protected $tree; + + function setup() { + + \Sabre\TestUtil::clearTempDir(); + mkdir(SABRE_TEMPDIR . '/root'); + mkdir(SABRE_TEMPDIR . '/root/subdir'); + file_put_contents(SABRE_TEMPDIR . '/root/file.txt', 'contents'); + file_put_contents(SABRE_TEMPDIR . '/root/subdir/subfile.txt', 'subcontents'); + $rootNode = new FSExt\Directory(SABRE_TEMPDIR . '/root'); + $this->tree = new Tree($rootNode); + + } + + function teardown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetRootNode() { + + $root = $this->tree->getNodeForPath(''); + $this->assertInstanceOf('Sabre\\DAV\\FSExt\\Directory', $root); + + } + + function testGetSubDir() { + + $root = $this->tree->getNodeForPath('subdir'); + $this->assertInstanceOf('Sabre\\DAV\\FSExt\\Directory', $root); + + } + + function testCopyFile() { + + $this->tree->copy('file.txt', 'file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/file2.txt')); + $this->assertEquals('contents', file_get_contents(SABRE_TEMPDIR . '/root/file2.txt')); + + } + + /** + * @depends testCopyFile + */ + function testCopyDirectory() { + + $this->tree->copy('subdir', 'subdir2'); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/subdir2')); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/subdir2/subfile.txt')); + $this->assertEquals('subcontents', file_get_contents(SABRE_TEMPDIR . '/root/subdir2/subfile.txt')); + + } + + /** + * @depends testCopyFile + */ + function testMoveFile() { + + $this->tree->move('file.txt', 'file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/file2.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/root/file.txt')); + $this->assertEquals('contents', file_get_contents(SABRE_TEMPDIR . '/root/file2.txt')); + + } + + /** + * @depends testMoveFile + */ + function testMoveFileNewParent() { + + $this->tree->move('file.txt', 'subdir/file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/subdir/file2.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/root/file.txt')); + $this->assertEquals('contents', file_get_contents(SABRE_TEMPDIR . '/root/subdir/file2.txt')); + + } + + /** + * @depends testCopyDirectory + */ + function testMoveDirectory() { + + $this->tree->move('subdir', 'subdir2'); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/subdir2')); + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/root/subdir2/subfile.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/root/subdir')); + $this->assertEquals('subcontents', file_get_contents(SABRE_TEMPDIR . '/root/subdir2/subfile.txt')); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PSR3Test.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PSR3Test.php new file mode 100644 index 000000000000..d30fde128a78 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PSR3Test.php @@ -0,0 +1,87 @@ +<?php + +namespace Sabre\DAV; + +class PSR3Test extends \PHPUnit_Framework_TestCase { + + function testIsLoggerAware() { + + $server = new Server(); + $this->assertInstanceOf( + 'Psr\Log\LoggerAwareInterface', + $server + ); + + } + + function testGetNullLoggerByDefault() { + + $server = new Server(); + $this->assertInstanceOf( + 'Psr\Log\NullLogger', + $server->getLogger() + ); + + } + + function testSetLogger() { + + $server = new Server(); + $logger = new MockLogger(); + + $server->setLogger($logger); + + $this->assertEquals( + $logger, + $server->getLogger() + ); + + } + + /** + * Start the server, trigger an exception and see if the logger captured + * it. + */ + function testLogException() { + + $server = new Server(); + $logger = new MockLogger(); + + $server->setLogger($logger); + + // Creating a fake environment to execute http requests in. + $request = new \Sabre\HTTP\Request( + 'GET', + '/not-found', + [] + ); + $response = new \Sabre\HTTP\Response(); + + $server->httpRequest = $request; + $server->httpResponse = $response; + $server->sapi = new \Sabre\HTTP\SapiMock(); + + // Executing the request. + $server->exec(); + + // The request should have triggered a 404 status. + $this->assertEquals(404, $response->getStatus()); + + // We should also see this in the PSR-3 log. + $this->assertEquals(1, count($logger->logs)); + + $logItem = $logger->logs[0]; + + $this->assertEquals( + \Psr\Log\LogLevel::INFO, + $logItem[0] + ); + + $this->assertInstanceOf( + 'Exception', + $logItem[2]['exception'] + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php new file mode 100644 index 000000000000..eff1e7d67380 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; + +class FileMock implements IPatchSupport { + + protected $data = ''; + + function put($str) { + + if (is_resource($str)) { + $str = stream_get_contents($str); + } + $this->data = $str; + + } + + /** + * Updates the file based on a range specification. + * + * The first argument is the data, which is either a readable stream + * resource or a string. + * + * The second argument is the type of update we're doing. + * This is either: + * * 1. append + * * 2. update based on a start byte + * * 3. update based on an end byte + *; + * The third argument is the start or end byte. + * + * After a successful put operation, you may choose to return an ETag. The + * etag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * @param resource|string $data + * @param int $rangeType + * @param int $offset + * @return string|null + */ + function patch($data, $rangeType, $offset = null) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + switch ($rangeType) { + + case 1 : + $this->data .= $data; + break; + case 3 : + // Turn the offset into an offset-offset. + $offset = strlen($this->data) - $offset; + // No break is intentional + case 2 : + $this->data = + substr($this->data, 0, $offset) . + $data . + substr($this->data, $offset + strlen($data)); + break; + + } + + } + + function get() { + + return $this->data; + + } + + function getContentType() { + + return 'text/plain'; + + } + + function getSize() { + + return strlen($this->data); + + } + + function getETag() { + + return '"' . $this->data . '"'; + + } + + function delete() { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + function setName($name) { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + function getName() { + + return 'partial'; + + } + + function getLastModified() { + + return null; + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php new file mode 100644 index 000000000000..5bd696416bba --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php @@ -0,0 +1,135 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/PartialUpdate/FileMock.php'; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $node; + protected $plugin; + + function setUp() { + + $this->node = new FileMock(); + $this->tree[] = $this->node; + + parent::setUp(); + + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + + + } + + function testInit() { + + $this->assertEquals('partialupdate', $this->plugin->getPluginName()); + $this->assertEquals(['sabredav-partialupdate'], $this->plugin->getFeatures()); + $this->assertEquals([ + 'PATCH' + ], $this->plugin->getHTTPMethods('partial')); + $this->assertEquals([ + ], $this->plugin->getHTTPMethods('')); + + } + + function testPatchNoRange() { + + $this->node->put('aaaaaaaa'); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PATCH', + 'REQUEST_URI' => '/partial', + ]); + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNotSupported() { + + $this->node->put('aaaaaaaa'); + $request = new HTTP\Request('PATCH', '/', ['X-Update-Range' => '3-4']); + $request->setBody( + 'bbb' + ); + $response = $this->request($request); + + $this->assertEquals(405, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNoContentType() { + + $this->node->put('aaaaaaaa'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-4']); + $request->setBody( + 'bbb' + ); + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchBadRange() { + + $this->node->put('aaaaaaaa'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-4', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => '3']); + $request->setBody( + 'bbb' + ); + $response = $this->request($request); + + $this->assertEquals(416, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNoLength() { + + $this->node->put('aaaaaaaa'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-5', 'Content-Type' => 'application/x-sabredav-partialupdate']); + $request->setBody( + 'bbb' + ); + $response = $this->request($request); + + $this->assertEquals(411, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchSuccess() { + + $this->node->put('aaaaaaaa'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-5', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => 3]); + $request->setBody( + 'bbb' + ); + $response = $this->request($request); + + $this->assertEquals(204, $response->status, 'Full response body:' . $response->body); + $this->assertEquals('aaabbbaa', $this->node->get()); + + } + + function testPatchNoEndRange() { + + $this->node->put('aaaaa'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => '3']); + $request->setBody( + 'bbb' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus(), 'Full response body:' . $response->getBodyAsString()); + $this->assertEquals('aaabbb', $this->node->get()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php new file mode 100644 index 000000000000..2c6274173300 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php @@ -0,0 +1,94 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV\FSExt\File; +use Sabre\DAV\Server; +use Sabre\HTTP; + +/** + * This test is an end-to-end sabredav test that goes through all + * the cases in the specification. + * + * See: http://sabre.io/dav/http-patch/ + */ +class SpecificationTest extends \PHPUnit_Framework_TestCase { + + protected $server; + + function setUp() { + + $tree = [ + new File(SABRE_TEMPDIR . '/foobar.txt') + ]; + $server = new Server($tree); + $server->debugExceptions = true; + $server->addPlugin(new Plugin()); + + $tree[0]->put('1234567890'); + + $this->server = $server; + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + /** + * @param string $headerValue + * @param string $httpStatus + * @param string $endResult + * @param int $contentLength + * + * @dataProvider data + */ + function testUpdateRange($headerValue, $httpStatus, $endResult, $contentLength = 4) { + + $headers = [ + 'Content-Type' => 'application/x-sabredav-partialupdate', + 'X-Update-Range' => $headerValue, + ]; + + if ($contentLength) { + $headers['Content-Length'] = (string)$contentLength; + } + + $request = new HTTP\Request('PATCH', '/foobar.txt', $headers, '----'); + + $request->setBody('----'); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->exec(); + + $this->assertEquals($httpStatus, $this->server->httpResponse->status, 'Incorrect http status received: ' . $this->server->httpResponse->body); + if (!is_null($endResult)) { + $this->assertEquals($endResult, file_get_contents(SABRE_TEMPDIR . '/foobar.txt')); + } + + } + + function data() { + + return [ + // Problems + ['foo', 400, null], + ['bytes=0-3', 411, null, 0], + ['bytes=4-1', 416, null], + + ['bytes=0-3', 204, '----567890'], + ['bytes=1-4', 204, '1----67890'], + ['bytes=0-', 204, '----567890'], + ['bytes=-4', 204, '123456----'], + ['bytes=-2', 204, '12345678----'], + ['bytes=2-', 204, '12----7890'], + ['append', 204, '1234567890----'], + + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php new file mode 100644 index 000000000000..ec1d616cbb97 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\DAV; + +class PropFindTest extends \PHPUnit_Framework_TestCase { + + function testHandle() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->handle('{DAV:}displayname', 'foobar'); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'foobar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testHandleCallBack() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->handle('{DAV:}displayname', function() { return 'foobar'; }); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'foobar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testAllPropDefaults() { + + $propFind = new PropFind('foo', ['{DAV:}displayname'], 0, PropFind::ALLPROPS); + + $this->assertEquals([ + 200 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testSet() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'bar'); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'bar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testSetAllpropCustom() { + + $propFind = new PropFind('foo', ['{DAV:}displayname'], 0, PropFind::ALLPROPS); + $propFind->set('{DAV:}customproperty', 'bar'); + + $this->assertEquals([ + 200 => ['{DAV:}customproperty' => 'bar'], + ], $propFind->getResultForMultiStatus()); + + } + + function testSetUnset() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'bar'); + $propFind->set('{DAV:}displayname', null); + + $this->assertEquals([ + 200 => [], + 404 => ['{DAV:}displayname' => null], + ], $propFind->getResultForMultiStatus()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php new file mode 100644 index 000000000000..72dbf5345b5d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php @@ -0,0 +1,351 @@ +<?php + +namespace Sabre\DAV; + +class PropPatchTest extends \PHPUnit_Framework_TestCase { + + protected $propPatch; + + function setUp() { + + $this->propPatch = new PropPatch([ + '{DAV:}displayname' => 'foo', + ]); + $this->assertEquals(['{DAV:}displayname' => 'foo'], $this->propPatch->getMutations()); + + } + + function testHandleSingleSuccess() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 200], $result); + + $this->assertTrue($hasRan); + + } + + function testHandleSingleFail() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return false; + }); + + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 403], $result); + + $this->assertTrue($hasRan); + + } + + function testHandleSingleCustomResult() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return 201; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 201], $result); + + $this->assertTrue($hasRan); + + } + + function testHandleSingleDeleteSuccess() { + + $hasRan = false; + + $this->propPatch = new PropPatch(['{DAV:}displayname' => null]); + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertNull($value); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 204], $result); + + $this->assertTrue($hasRan); + + } + + + function testHandleNothing() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}foobar', function($value) use (&$hasRan) { + $hasRan = true; + }); + + $this->assertFalse($hasRan); + + } + + /** + * @depends testHandleSingleSuccess + */ + function testHandleRemaining() { + + $hasRan = false; + + $this->propPatch->handleRemaining(function($mutations) use (&$hasRan) { + $hasRan = true; + $this->assertEquals(['{DAV:}displayname' => 'foo'], $mutations); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 200], $result); + + $this->assertTrue($hasRan); + + } + function testHandleRemainingNothingToDo() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function() {}); + $this->propPatch->handleRemaining(function($mutations) use (&$hasRan) { + $hasRan = true; + }); + + $this->assertFalse($hasRan); + + } + + function testSetResultCode() { + + $this->propPatch->setResultCode('{DAV:}displayname', 201); + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 201], $result); + + } + + function testSetResultCodeFail() { + + $this->propPatch->setResultCode('{DAV:}displayname', 402); + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 402], $result); + + } + + function testSetRemainingResultCode() { + + $this->propPatch->setRemainingResultCode(204); + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 204], $result); + + } + + function testCommitNoHandler() { + + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 403], $result); + + } + + function testHandlerNotCalled() { + + $hasRan = false; + + $this->propPatch->setResultCode('{DAV:}displayname', 402); + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + }); + + $this->propPatch->commit(); + + // The handler is not supposed to have ran + $this->assertFalse($hasRan); + + } + + function testDependencyFail() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + ]); + + $calledA = false; + $calledB = false; + + $propPatch->handle('{DAV:}a', function() use (&$calledA) { + $calledA = true; + return false; + }); + $propPatch->handle('{DAV:}b', function() use (&$calledB) { + $calledB = true; + return false; + }); + + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($calledB); + + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 403, + '{DAV:}b' => 424, + ], $propPatch->getResult()); + + } + + /** + * @expectedException \UnexpectedValueException + */ + function testHandleSingleBrokenResult() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + ]); + + $propPatch->handle('{DAV:}a', function() { + return []; + }); + $propPatch->commit(); + + } + + function testHandleMultiValueSuccess() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + return true; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertTrue($result); + + $this->assertEquals([ + '{DAV:}a' => 200, + '{DAV:}b' => 200, + '{DAV:}c' => 204, + ], $propPatch->getResult()); + + } + + + function testHandleMultiValueFail() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + return false; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 403, + '{DAV:}b' => 403, + '{DAV:}c' => 403, + ], $propPatch->getResult()); + + } + + function testHandleMultiValueCustomResult() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + + return [ + '{DAV:}a' => 201, + '{DAV:}b' => 204, + ]; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 201, + '{DAV:}b' => 204, + '{DAV:}c' => 500, + ], $propPatch->getResult()); + + } + + /** + * @expectedException \UnexpectedValueException + */ + function testHandleMultiValueBroken() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) { + return 'hi'; + }); + $propPatch->commit(); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php new file mode 100644 index 000000000000..a2b9987b7e5a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Xml\Property\Complex; +use Sabre\DAV\Xml\Property\Href; + +abstract class AbstractPDOTest extends \PHPUnit_Framework_TestCase { + + use \Sabre\DAV\DbTestHelperTrait; + + function getBackend() { + + $this->dropTables('propertystorage'); + $this->createSchema('propertystorage'); + + $pdo = $this->getPDO(); + + $pdo->exec("INSERT INTO propertystorage (path, name, valuetype, value) VALUES ('dir', '{DAV:}displayname', 1, 'Directory')"); + + return new PDO($this->getPDO()); + + } + + function testPropFind() { + + $backend = $this->getBackend(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals('Directory', $propFind->get('{DAV:}displayname')); + + } + + function testPropFindNothingToDo() { + + $backend = $this->getBackend(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'foo'); + $backend->propFind('dir', $propFind); + + $this->assertEquals('foo', $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testPropPatchUpdate() { + + $backend = $this->getBackend(); + + $propPatch = new PropPatch(['{DAV:}displayname' => 'bar']); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals('bar', $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropPatchUpdate + */ + function testPropPatchComplex() { + + $backend = $this->getBackend(); + + $complex = new Complex('<foo xmlns="DAV:">somevalue</foo>'); + + $propPatch = new PropPatch(['{DAV:}complex' => $complex]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}complex']); + $backend->propFind('dir', $propFind); + + $this->assertEquals($complex, $propFind->get('{DAV:}complex')); + + } + + + /** + * @depends testPropPatchComplex + */ + function testPropPatchCustom() { + + $backend = $this->getBackend(); + + $custom = new Href('/foo/bar/'); + + $propPatch = new PropPatch(['{DAV:}custom' => $custom]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}custom']); + $backend->propFind('dir', $propFind); + + $this->assertEquals($custom, $propFind->get('{DAV:}custom')); + + } + + /** + * @depends testPropFind + */ + function testPropPatchRemove() { + + $backend = $this->getBackend(); + + $propPatch = new PropPatch(['{DAV:}displayname' => null]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testDelete() { + + $backend = $this->getBackend(); + $backend->delete('dir'); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testMove() { + + $backend = $this->getBackend(); + // Creating a new child property. + $propPatch = new PropPatch(['{DAV:}displayname' => 'child']); + $backend->propPatch('dir/child', $propPatch); + $propPatch->commit(); + + $backend->move('dir', 'dir2'); + + // Old 'dir' + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + // Old 'dir/child' + $propFind = new PropFind('dir/child', ['{DAV:}displayname']); + $backend->propFind('dir/child', $propFind); + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + // New 'dir2' + $propFind = new PropFind('dir2', ['{DAV:}displayname']); + $backend->propFind('dir2', $propFind); + $this->assertEquals('Directory', $propFind->get('{DAV:}displayname')); + + // New 'dir2/child' + $propFind = new PropFind('dir2/child', ['{DAV:}displayname']); + $backend->propFind('dir2/child', $propFind); + $this->assertEquals('child', $propFind->get('{DAV:}displayname')); + } + + /** + * @depends testPropFind + */ + function testDeepDelete() { + + $backend = $this->getBackend(); + $propPatch = new PropPatch(['{DAV:}displayname' => 'child']); + $backend->propPatch('dir/child', $propPatch); + $propPatch->commit(); + $backend->delete('dir'); + + $propFind = new PropFind('dir/child', ['{DAV:}displayname']); + $backend->propFind('dir/child', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php new file mode 100644 index 000000000000..cf4c88fb85f0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php @@ -0,0 +1,117 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; + +class Mock implements BackendInterface { + + public $data = []; + + /** + * Fetches properties for a path. + * + * This method received a PropFind object, which contains all the + * information about the properties that need to be fetched. + * + * Usually you would just want to call 'get404Properties' on this object, + * as this will give you the _exact_ list of properties that need to be + * fetched, and haven't yet. + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind) { + + if (!isset($this->data[$path])) { + return; + } + + foreach ($this->data[$path] as $name => $value) { + $propFind->set($name, $value); + } + + } + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + if (!isset($this->data[$path])) { + $this->data[$path] = []; + } + $propPatch->handleRemaining(function($properties) use ($path) { + + foreach ($properties as $propName => $propValue) { + + if (is_null($propValue)) { + unset($this->data[$path][$propName]); + } else { + $this->data[$path][$propName] = $propValue; + } + return true; + + } + + }); + + } + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * @param string $path + * @return void + */ + function delete($path) { + + unset($this->data[$path]); + + } + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination) { + + foreach ($this->data as $path => $props) { + + if ($path === $source) { + $this->data[$destination] = $props; + unset($this->data[$path]); + continue; + } + + if (strpos($path, $source . '/') === 0) { + $this->data[$destination . substr($path, strlen($source) + 1)] = $props; + unset($this->data[$path]); + } + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php new file mode 100644 index 000000000000..b92b034df840 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +class PDOMysqlTest extends AbstractPDOTest { + + public $driver = 'mysql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOPgSqlTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOPgSqlTest.php new file mode 100644 index 000000000000..616c2e67a6c3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOPgSqlTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +class PDOPgSqlTest extends AbstractPDOTest { + + public $driver = 'pgsql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php new file mode 100644 index 000000000000..20a6a09e5ba1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +class PDOSqliteTest extends AbstractPDOTest { + + public $driver = 'sqlite'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php new file mode 100644 index 000000000000..130f1490f175 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php @@ -0,0 +1,117 @@ +<?php + +namespace Sabre\DAV\PropertyStorage; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $backend; + protected $plugin; + + protected $setupFiles = true; + + function setUp() { + + parent::setUp(); + $this->backend = new Backend\Mock(); + $this->plugin = new Plugin( + $this->backend + ); + + $this->server->addPlugin($this->plugin); + + } + + function testGetInfo() { + + $this->assertArrayHasKey( + 'name', + $this->plugin->getPluginInfo() + ); + + } + + function testSetProperty() { + + $this->server->updateProperties('', ['{DAV:}displayname' => 'hi']); + $this->assertEquals([ + '' => [ + '{DAV:}displayname' => 'hi', + ] + ], $this->backend->data); + + } + + /** + * @depends testSetProperty + */ + function testGetProperty() { + + $this->testSetProperty(); + $result = $this->server->getProperties('', ['{DAV:}displayname']); + + $this->assertEquals([ + '{DAV:}displayname' => 'hi', + ], $result); + + } + + /** + * @depends testSetProperty + */ + function testDeleteProperty() { + + $this->testSetProperty(); + $this->server->emit('afterUnbind', ['']); + $this->assertEquals([], $this->backend->data); + + } + + function testMove() { + + $this->server->tree->getNodeForPath('files')->createFile('source'); + $this->server->updateProperties('files/source', ['{DAV:}displayname' => 'hi']); + + $request = new \Sabre\HTTP\Request('MOVE', '/files/source', ['Destination' => '/files/dest']); + $this->assertHTTPStatus(201, $request); + + $result = $this->server->getProperties('/files/dest', ['{DAV:}displayname']); + + $this->assertEquals([ + '{DAV:}displayname' => 'hi', + ], $result); + + $this->server->tree->getNodeForPath('files')->createFile('source'); + $result = $this->server->getProperties('/files/source', ['{DAV:}displayname']); + + $this->assertEquals([], $result); + + } + + /** + * @depends testDeleteProperty + */ + function testSetPropertyInFilteredPath() { + + $this->plugin->pathFilter = function($path) { + + return false; + + }; + + $this->server->updateProperties('', ['{DAV:}displayname' => 'hi']); + $this->assertEquals([], $this->backend->data); + + } + + /** + * @depends testSetPropertyInFilteredPath + */ + function testGetPropertyInFilteredPath() { + + $this->testSetPropertyInFilteredPath(); + $result = $this->server->getProperties('', ['{DAV:}displayname']); + + $this->assertEquals([], $result); + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php new file mode 100644 index 000000000000..42759647ab0b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php @@ -0,0 +1,126 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerEventsTest extends AbstractServer { + + private $tempPath; + + private $exception; + + function testAfterBind() { + + $this->server->on('afterBind', [$this, 'afterBindHandler']); + $newPath = 'afterBind'; + + $this->tempPath = ''; + $this->server->createFile($newPath, 'body'); + $this->assertEquals($newPath, $this->tempPath); + + } + + function afterBindHandler($path) { + + $this->tempPath = $path; + + } + + function testAfterResponse() { + + $mock = $this->getMockBuilder('stdClass') + ->setMethods(['afterResponseCallback']) + ->getMock(); + $mock->expects($this->once())->method('afterResponseCallback'); + + $this->server->on('afterResponse', [$mock, 'afterResponseCallback']); + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/test.txt', + ]); + + $this->server->exec(); + + } + + function testBeforeBindCancel() { + + $this->server->on('beforeBind', [$this, 'beforeBindCancelHandler']); + $this->assertFalse($this->server->createFile('bla', 'body')); + + // Also testing put() + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/barbar', + ]); + + $this->server->httpRequest = $req; + $this->server->exec(); + + $this->assertEquals(500, $this->server->httpResponse->getStatus()); + + } + + function beforeBindCancelHandler($path) { + + return false; + + } + + function testException() { + + $this->server->on('exception', [$this, 'exceptionHandler']); + + $req = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/not/exisitng', + ]); + $this->server->httpRequest = $req; + $this->server->exec(); + + $this->assertInstanceOf('Sabre\\DAV\\Exception\\NotFound', $this->exception); + + } + + function exceptionHandler(Exception $exception) { + + $this->exception = $exception; + + } + + function testMethod() { + + $k = 1; + $this->server->on('method', function($request, $response) use (&$k) { + + $k += 1; + + return false; + + }); + $this->server->on('method', function($request, $response) use (&$k) { + + $k += 2; + + return false; + + }); + + try { + $this->server->invokeMethod( + new HTTP\Request('BLABLA', '/'), + new HTTP\Response(), + false + ); + } catch (Exception $e) {} + + // Fun fact, PHP 7.1 changes the order when sorting-by-callback. + $this->assertTrue($k >= 2 && $k <= 3); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php new file mode 100644 index 000000000000..557eddbbcf63 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php @@ -0,0 +1,366 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class ServerMKCOLTest extends AbstractServer { + + function testMkcol() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(""); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertTrue(is_dir($this->tempDir . '/testcol')); + + } + + /** + * @depends testMkcol + */ + function testMKCOLUnknownBody() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody("Hello"); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(415, $this->response->status); + + } + + /** + * @depends testMkcol + */ + function testMKCOLBrokenXML() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody("Hello"); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(400, $this->response->getStatus(), $this->response->getBodyAsString()); + + } + + /** + * @depends testMkcol + */ + function testMKCOLUnknownXML() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?><html></html>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(400, $this->response->getStatus()); + + } + + /** + * @depends testMkcol + */ + function testMKCOLNoResourceType() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <displayname>Evert</displayname> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(400, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMkcol + */ + function testMKCOLIncorrectResourceType() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /><blabla /></resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(403, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLSuccess() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /></resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLWhiteSpaceResourceType() { + + $serverVars = [ + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype> + <collection /> + </resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLNoParent() { + + $serverVars = [ + 'REQUEST_URI' => '/testnoparent/409me', + 'REQUEST_METHOD' => 'MKCOL', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(409, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLParentIsNoCollection() { + + $serverVars = [ + 'REQUEST_URI' => '/test.txt/409me', + 'REQUEST_METHOD' => 'MKCOL', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(409, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLAlreadyExists() { + + $serverVars = [ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'MKCOL', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + ], $this->response->getHeaders()); + + $this->assertEquals(405, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testMKCOLSuccess + * @depends testMKCOLAlreadyExists + */ + function testMKCOLAndProps() { + + $request = new HTTP\Request( + 'MKCOL', + '/testcol', + ['Content-Type' => 'application/xml'] + ); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /></resourcetype> + <displayname>my new collection</displayname> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Wrong statuscode received. Full response body: ' . $this->response->body); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $responseBody = $this->response->getBodyAsString(); + + $expected = <<<XML +<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:response> + <d:href>/testcol</d:href> + <d:propstat> + <d:prop> + <d:displayname /> + </d:prop> + <d:status>HTTP/1.1 403 Forbidden</d:status> + </d:propstat> + </d:response> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $responseBody + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php new file mode 100644 index 000000000000..fa67102cc436 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php @@ -0,0 +1,108 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; +require_once 'Sabre/DAV/TestPlugin.php'; + +class ServerPluginTest extends AbstractServer { + + /** + * @var Sabre\DAV\TestPlugin + */ + protected $testPlugin; + + function setUp() { + + parent::setUp(); + + $testPlugin = new TestPlugin(); + $this->server->addPlugin($testPlugin); + $this->testPlugin = $testPlugin; + + } + + /** + */ + function testBaseClass() { + + $p = new ServerPluginMock(); + $this->assertEquals([], $p->getFeatures()); + $this->assertEquals([], $p->getHTTPMethods('')); + $this->assertEquals( + [ + 'name' => 'Sabre\DAV\ServerPluginMock', + 'description' => null, + 'link' => null + ], $p->getPluginInfo() + ); + + } + + function testOptions() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'OPTIONS', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol, drinking'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, BEER, WINE'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('OPTIONS', $this->testPlugin->beforeMethod); + + + } + + function testGetPlugin() { + + $this->assertEquals($this->testPlugin, $this->server->getPlugin(get_class($this->testPlugin))); + + } + + function testUnknownPlugin() { + + $this->assertNull($this->server->getPlugin('SomeRandomClassName')); + + } + + function testGetSupportedReportSet() { + + $this->assertEquals([], $this->testPlugin->getSupportedReportSet('/')); + + } + + function testGetPlugins() { + + $this->assertEquals( + [ + get_class($this->testPlugin) => $this->testPlugin, + 'core' => $this->server->getPlugin('core'), + ], + $this->server->getPlugins() + ); + + } + + +} + +class ServerPluginMock extends ServerPlugin { + + function initialize(Server $s) { } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php new file mode 100644 index 000000000000..203cf26d9d42 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php @@ -0,0 +1,344 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class ServerPreconditionsTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfMatchNoNode() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/bar', ['If-Match' => '*']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfMatchHasNode() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '*']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfMatchWrongEtag() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '1234']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfMatchCorrectEtag() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '"abc123"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * Evolution sometimes uses \" instead of " for If-Match headers. + * + * @depends testIfMatchCorrectEtag + */ + function testIfMatchEvolutionEtag() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '\\"abc123\\"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfMatchMultiple() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '"hellothere", "abc123"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfNoneMatchNoNode() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/bar', ['If-None-Match' => '*']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfNoneMatchHasNode() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '*']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfNoneMatchWrongEtag() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfNoneMatchWrongEtagMultiple() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234", "5678"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfNoneMatchCorrectEtag() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"abc123"']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfNoneMatchCorrectEtagMultiple() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234, "abc123"']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfNoneMatchCorrectEtagAsGet() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-None-Match' => '"abc123"']); + $server->httpResponse = new HTTP\ResponseMock(); + + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + $this->assertEquals(304, $server->httpResponse->getStatus()); + $this->assertEquals(['ETag' => ['"abc123"']], $server->httpResponse->getHeaders()); + + } + + /** + * This was a test written for issue #515. + */ + function testNoneMatchCorrectEtagEnsureSapiSent() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $server->sapi = new HTTP\SapiMock(); + HTTP\SapiMock::$sent = 0; + $httpRequest = new HTTP\Request('GET', '/foo', ['If-None-Match' => '"abc123"']); + $server->httpRequest = $httpRequest; + $server->httpResponse = new HTTP\ResponseMock(); + + $server->exec(); + + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + $this->assertEquals(304, $server->httpResponse->getStatus()); + $this->assertEquals([ + 'ETag' => ['"abc123"'], + 'X-Sabre-Version' => [Version::VERSION], + ], $server->httpResponse->getHeaders()); + $this->assertEquals(1, HTTP\SapiMock::$sent); + + } + + /** + */ + function testIfModifiedSinceUnModified() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + ]); + $server->httpResponse = new HTTP\ResponseMock(); + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + + $this->assertEquals(304, $server->httpResponse->status); + $this->assertEquals([ + 'Last-Modified' => ['Sat, 06 Apr 1985 23:30:00 GMT'], + ], $server->httpResponse->getHeaders()); + + } + + + /** + */ + function testIfModifiedSinceModified() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_MODIFIED_SINCE' => 'Tue, 06 Nov 1984 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + ]); + + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfModifiedSinceInvalidDate() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_MODIFIED_SINCE' => 'Your mother', + 'REQUEST_URI' => '/foo' + ]); + $httpResponse = new HTTP\ResponseMock(); + + // Invalid dates must be ignored, so this should return true + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfModifiedSinceInvalidDate2() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 EST', + 'REQUEST_URI' => '/foo' + ]); + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + + /** + */ + function testIfUnmodifiedSinceUnModified() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + ]); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfUnmodifiedSinceModified() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_UNMODIFIED_SINCE' => 'Tue, 06 Nov 1984 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + ]); + $httpResponse = new HTTP\ResponseMock(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfUnmodifiedSinceInvalidDate() { + + $root = new SimpleCollection('root', [new ServerPreconditionsNode()]); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 06 Nov 1984 08:49:37 CET', + 'REQUEST_URI' => '/foo' + ]); + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + +} + +class ServerPreconditionsNode extends File { + + function getETag() { + + return '"abc123"'; + + } + + function getLastModified() { + + /* my birthday & time, I believe */ + return strtotime('1985-04-07 01:30 +02:00'); + + } + + function getName() { + + return 'foo'; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php new file mode 100644 index 000000000000..c968e72008a1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php @@ -0,0 +1,163 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerPropsInfiniteDepthTest extends AbstractServer { + + protected function getRootNode() { + + return new FSExt\Directory(SABRE_TEMPDIR); + + } + + function setUp() { + + if (file_exists(SABRE_TEMPDIR . '../.sabredav')) unlink(SABRE_TEMPDIR . '../.sabredav'); + parent::setUp(); + file_put_contents(SABRE_TEMPDIR . '/test2.txt', 'Test contents2'); + mkdir(SABRE_TEMPDIR . '/col'); + mkdir(SABRE_TEMPDIR . '/col/col'); + file_put_contents(SABRE_TEMPDIR . 'col/col/test.txt', 'Test contents'); + $this->server->addPlugin(new Locks\Plugin(new Locks\Backend\File(SABRE_TEMPDIR . '/.locksdb'))); + $this->server->enablePropfindDepthInfinity = true; + + } + + function tearDown() { + + parent::tearDown(); + if (file_exists(SABRE_TEMPDIR . '../.locksdb')) unlink(SABRE_TEMPDIR . '../.locksdb'); + + } + + private function sendRequest($body) { + + $request = new HTTP\Request('PROPFIND', '/', ['Depth' => 'infinity']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + } + + function testPropFindEmptyBody() { + + $this->sendRequest(""); + + $this->assertEquals(207, $this->response->status, 'Incorrect status received. Full response body: ' . $this->response->getBodyAsString()); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ], + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/', (string)$data, 'href element should have been /'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + // 8 resources are to be returned: /, col, col/col, col/col/test.txt, dir, dir/child.txt, test.txt and test2.txt + $this->assertEquals(8, count($data)); + + } + + function testSupportedLocks() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supportedlock /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = $this->response->getBodyAsString(); + $this->assertEquals(207, $this->response->getStatus(), $body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry'); + $this->assertEquals(16, count($data), 'We expected sixteen \'d:lockentry\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope'); + $this->assertEquals(16, count($data), 'We expected sixteen \'d:lockscope\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype'); + $this->assertEquals(16, count($data), 'We expected sixteen \'d:locktype\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:shared'); + $this->assertEquals(8, count($data), 'We expected eight \'d:shared\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:exclusive'); + $this->assertEquals(8, count($data), 'We expected eight \'d:exclusive\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype/d:write'); + $this->assertEquals(16, count($data), 'We expected sixteen \'d:write\' tags'); + } + + function testLockDiscovery() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:lockdiscovery /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery'); + $this->assertEquals(8, count($data), 'We expected eight \'d:lockdiscovery\' tags'); + + } + + function testUnknownProperty() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:macaroni /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + $pathTests = [ + '/d:multistatus', + '/d:multistatus/d:response', + '/d:multistatus/d:response/d:propstat', + '/d:multistatus/d:response/d:propstat/d:status', + '/d:multistatus/d:response/d:propstat/d:prop', + '/d:multistatus/d:response/d:propstat/d:prop/d:macaroni', + ]; + foreach ($pathTests as $test) { + $this->assertTrue(count($xml->xpath($test)) == true, 'We expected the ' . $test . ' element to appear in the response, we got: ' . $body); + } + + $val = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(8, count($val), $body); + $this->assertEquals('HTTP/1.1 404 Not Found', (string)$val[0]); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php new file mode 100644 index 000000000000..253200be7cfd --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php @@ -0,0 +1,201 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerPropsTest extends AbstractServer { + + protected function getRootNode() { + + return new FSExt\Directory(SABRE_TEMPDIR); + + } + + function setUp() { + + if (file_exists(SABRE_TEMPDIR . '../.sabredav')) unlink(SABRE_TEMPDIR . '../.sabredav'); + parent::setUp(); + file_put_contents(SABRE_TEMPDIR . '/test2.txt', 'Test contents2'); + mkdir(SABRE_TEMPDIR . '/col'); + file_put_contents(SABRE_TEMPDIR . 'col/test.txt', 'Test contents'); + $this->server->addPlugin(new Locks\Plugin(new Locks\Backend\File(SABRE_TEMPDIR . '/.locksdb'))); + + } + + function tearDown() { + + parent::tearDown(); + if (file_exists(SABRE_TEMPDIR . '../.locksdb')) unlink(SABRE_TEMPDIR . '../.locksdb'); + + } + + private function sendRequest($body, $path = '/', $headers = ['Depth' => '0']) { + + $request = new HTTP\Request('PROPFIND', $path, $headers, $body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + } + + function testPropFindEmptyBody() { + + $this->sendRequest(""); + $this->assertEquals(207, $this->response->status); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ], + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/', (string)$data, 'href element should have been /'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + $this->assertEquals(1, count($data)); + + } + + function testPropFindEmptyBodyFile() { + + $this->sendRequest("", '/test2.txt', []); + $this->assertEquals(207, $this->response->status); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ], + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/test2.txt', (string)$data, 'href element should have been /test2.txt'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength'); + $this->assertEquals(1, count($data)); + + } + + function testSupportedLocks() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supportedlock /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry'); + $this->assertEquals(2, count($data), 'We expected two \'d:lockentry\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope'); + $this->assertEquals(2, count($data), 'We expected two \'d:lockscope\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype'); + $this->assertEquals(2, count($data), 'We expected two \'d:locktype\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:shared'); + $this->assertEquals(1, count($data), 'We expected a \'d:shared\' tag'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:exclusive'); + $this->assertEquals(1, count($data), 'We expected a \'d:exclusive\' tag'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype/d:write'); + $this->assertEquals(2, count($data), 'We expected two \'d:write\' tags'); + } + + function testLockDiscovery() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:lockdiscovery /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery'); + $this->assertEquals(1, count($data), 'We expected a \'d:lockdiscovery\' tag'); + + } + + function testUnknownProperty() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:macaroni /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + $pathTests = [ + '/d:multistatus', + '/d:multistatus/d:response', + '/d:multistatus/d:response/d:propstat', + '/d:multistatus/d:response/d:propstat/d:status', + '/d:multistatus/d:response/d:propstat/d:prop', + '/d:multistatus/d:response/d:propstat/d:prop/d:macaroni', + ]; + foreach ($pathTests as $test) { + $this->assertTrue(count($xml->xpath($test)) == true, 'We expected the ' . $test . ' element to appear in the response, we got: ' . $body); + } + + $val = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($val), $body); + $this->assertEquals('HTTP/1.1 404 Not Found', (string)$val[0]); + + } + + function testParsePropPatchRequest() { + + $body = '<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:" xmlns:s="http://sabredav.org/NS/test"> + <d:set><d:prop><s:someprop>somevalue</s:someprop></d:prop></d:set> + <d:remove><d:prop><s:someprop2 /></d:prop></d:remove> + <d:set><d:prop><s:someprop3>removeme</s:someprop3></d:prop></d:set> + <d:remove><d:prop><s:someprop3 /></d:prop></d:remove> +</d:propertyupdate>'; + + $result = $this->server->xml->parse($body); + $this->assertEquals([ + '{http://sabredav.org/NS/test}someprop' => 'somevalue', + '{http://sabredav.org/NS/test}someprop2' => null, + '{http://sabredav.org/NS/test}someprop3' => null, + ], $result->properties); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php new file mode 100644 index 000000000000..81224d687c37 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php @@ -0,0 +1,262 @@ +<?php + +namespace Sabre\DAV; + +use DateTime; +use Sabre\HTTP; + +/** + * This file tests HTTP requests that use the Range: header. + * + * @copyright Copyright (C) fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ServerRangeTest extends \Sabre\DAVServerTest { + + protected $setupFiles = true; + + /** + * We need this string a lot + */ + protected $lastModified; + + function setUp() { + + parent::setUp(); + $this->server->createFile('files/test.txt', 'Test contents'); + + $this->lastModified = HTTP\Util::toHTTPDate( + new DateTime('@' . $this->server->tree->getNodeForPath('files/test.txt')->getLastModified()) + ); + + $stream = popen('echo "Test contents"', 'r'); + $streamingFile = new Mock\StreamingFile( + 'no-seeking.txt', + $stream + ); + $streamingFile->setSize(12); + $this->server->tree->getNodeForPath('files')->addNode($streamingFile); + + } + + function testRange() { + + $request = new HTTP\Request('GET', '/files/test.txt', ['Range' => 'bytes=2-5']); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + $this->assertEquals(206, $response->getStatus()); + $this->assertEquals('st c', $response->getBodyAsString()); + + } + + /** + * @depends testRange + */ + function testStartRange() { + + $request = new HTTP\Request('GET', '/files/test.txt', ['Range' => 'bytes=2-']); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [11], + 'Content-Range' => ['bytes 2-12/13'], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(206, $response->getStatus()); + $this->assertEquals('st contents', $response->getBodyAsString()); + + } + + /** + * @depends testRange + */ + function testEndRange() { + + $request = new HTTP\Request('GET', '/files/test.txt', ['Range' => 'bytes=-8']); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [8], + 'Content-Range' => ['bytes 5-12/13'], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(206, $response->getStatus()); + $this->assertEquals('contents', $response->getBodyAsString()); + + } + + /** + * @depends testRange + */ + function testTooHighRange() { + + $request = new HTTP\Request('GET', '/files/test.txt', ['Range' => 'bytes=100-200']); + $response = $this->request($request); + + $this->assertEquals(416, $response->getStatus()); + + } + + /** + * @depends testRange + */ + function testCrazyRange() { + + $request = new HTTP\Request('GET', '/files/test.txt', ['Range' => 'bytes=8-4']); + $response = $this->request($request); + + $this->assertEquals(416, $response->getStatus()); + + } + + function testNonSeekableStream() { + + $request = new HTTP\Request('GET', '/files/no-seeking.txt', ['Range' => 'bytes=2-5']); + $response = $this->request($request); + + $this->assertEquals(206, $response->getStatus(), $response); + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/12'], + // 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals('st c', $response->getBodyAsString()); + + } + + /** + * @depends testRange + */ + function testIfRangeEtag() { + + $request = new HTTP\Request('GET', '/files/test.txt', [ + 'Range' => 'bytes=2-5', + 'If-Range' => '"' . md5('Test contents') . '"', + ]); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(206, $response->getStatus()); + $this->assertEquals('st c', $response->getBodyAsString()); + + } + + /** + * @depends testIfRangeEtag + */ + function testIfRangeEtagIncorrect() { + + $request = new HTTP\Request('GET', '/files/test.txt', [ + 'Range' => 'bytes=2-5', + 'If-Range' => '"foobar"', + ]); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('Test contents', $response->getBodyAsString()); + + } + + /** + * @depends testIfRangeEtag + */ + function testIfRangeModificationDate() { + + $request = new HTTP\Request('GET', '/files/test.txt', [ + 'Range' => 'bytes=2-5', + 'If-Range' => 'tomorrow', + ]); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(206, $response->getStatus()); + $this->assertEquals('st c', $response->getBodyAsString()); + + } + + /** + * @depends testIfRangeModificationDate + */ + function testIfRangeModificationDateModified() { + + $request = new HTTP\Request('GET', '/files/test.txt', [ + 'Range' => 'bytes=2-5', + 'If-Range' => '-2 years', + ]); + $response = $this->request($request); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'ETag' => ['"' . md5('Test contents') . '"'], + 'Last-Modified' => [$this->lastModified], + ], + $response->getHeaders() + ); + + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('Test contents', $response->getBodyAsString()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php new file mode 100644 index 000000000000..043179a00515 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php @@ -0,0 +1,475 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class ServerSimpleTest extends AbstractServer{ + + function testConstructArray() { + + $nodes = [ + new SimpleCollection('hello') + ]; + + $server = new Server($nodes); + $this->assertEquals($nodes[0], $server->tree->getNodeForPath('hello')); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testConstructIncorrectObj() { + + $nodes = [ + new SimpleCollection('hello'), + new \STDClass(), + ]; + + $server = new Server($nodes); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testConstructInvalidArg() { + + $server = new Server(1); + + } + + function testOptions() { + + $request = new HTTP\Request('OPTIONS', '/'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testOptionsUnmapped() { + + $request = new HTTP\Request('OPTIONS', '/unmapped'); + $this->server->httpRequest = $request; + + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, MKCOL'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testNonExistantMethod() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'BLABLA', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(501, $this->response->status); + + + } + + function testBaseUri() { + + $serverVars = [ + 'REQUEST_URI' => '/blabla/test.txt', + 'REQUEST_METHOD' => 'GET', + ]; + $filename = $this->tempDir . '/test.txt'; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->setBaseUri('/blabla/'); + $this->assertEquals('/blabla/', $this->server->getBaseUri()); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($filename)))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } + + function testBaseUriAddSlash() { + + $tests = [ + '/' => '/', + '/foo' => '/foo/', + '/foo/' => '/foo/', + '/foo/bar' => '/foo/bar/', + '/foo/bar/' => '/foo/bar/', + ]; + + foreach ($tests as $test => $result) { + $this->server->setBaseUri($test); + + $this->assertEquals($result, $this->server->getBaseUri()); + + } + + } + + function testCalculateUri() { + + $uris = [ + 'http://www.example.org/root/somepath', + '/root/somepath', + '/root/somepath/', + ]; + + $this->server->setBaseUri('/root/'); + + foreach ($uris as $uri) { + + $this->assertEquals('somepath', $this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/root'); + + foreach ($uris as $uri) { + + $this->assertEquals('somepath', $this->server->calculateUri($uri)); + + } + + $this->assertEquals('', $this->server->calculateUri('/root')); + + } + + function testCalculateUriSpecialChars() { + + $uris = [ + 'http://www.example.org/root/%C3%A0fo%C3%B3', + '/root/%C3%A0fo%C3%B3', + '/root/%C3%A0fo%C3%B3/' + ]; + + $this->server->setBaseUri('/root/'); + + foreach ($uris as $uri) { + + $this->assertEquals("\xc3\xa0fo\xc3\xb3", $this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/root'); + + foreach ($uris as $uri) { + + $this->assertEquals("\xc3\xa0fo\xc3\xb3", $this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/'); + + foreach ($uris as $uri) { + + $this->assertEquals("root/\xc3\xa0fo\xc3\xb3", $this->server->calculateUri($uri)); + + } + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testCalculateUriBreakout() { + + $uri = '/path1/'; + + $this->server->setBaseUri('/path2/'); + $this->server->calculateUri($uri); + + } + + /** + */ + function testGuessBaseUri() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + */ + function testGuessBaseUriPercentEncoding() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/dir/path2/path%20with%20spaces', + 'PATH_INFO' => '/dir/path2/path with spaces', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + */ + /* + function testGuessBaseUriPercentEncoding2() { + + $this->markTestIncomplete('This behaviour is not yet implemented'); + $serverVars = [ + 'REQUEST_URI' => '/some%20directory+mixed/index.php/dir/path2/path%20with%20spaces', + 'PATH_INFO' => '/dir/path2/path with spaces', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/some%20directory+mixed/index.php/', $server->guessBaseUri()); + + }*/ + + function testGuessBaseUri2() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root/', + 'PATH_INFO' => '/root/', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + function testGuessBaseUriNoPathInfo() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/', $server->guessBaseUri()); + + } + + function testGuessBaseUriNoPathInfo2() { + + $serverVars = [ + 'REQUEST_URI' => '/a/b/c/test.php', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/', $server->guessBaseUri()); + + } + + + /** + * @depends testGuessBaseUri + */ + function testGuessBaseUriQueryString() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root?query_string=blabla', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + * @expectedException \Sabre\DAV\Exception + */ + function testGuessBaseUriBadConfig() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root/heyyy', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $server->guessBaseUri(); + + } + + function testTriggerException() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'FOO', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $httpRequest; + $this->server->on('beforeMethod', [$this, 'exceptionTrigger']); + $this->server->exec(); + + $this->assertEquals([ + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $this->assertEquals(500, $this->response->status); + + } + + function exceptionTrigger($request, $response) { + + throw new Exception('Hola'); + + } + + function testReportNotFound() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'REPORT', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->httpRequest->setBody('<?xml version="1.0"?><bla:myreport xmlns:bla="http://www.rooftopsolutions.nl/NS"></bla:myreport>'); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(415, $this->response->status, 'We got an incorrect status back. Full response body follows: ' . $this->response->body); + + } + + function testReportIntercepted() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'REPORT', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->httpRequest->setBody('<?xml version="1.0"?><bla:myreport xmlns:bla="http://www.rooftopsolutions.nl/NS"></bla:myreport>'); + $this->server->on('report', [$this, 'reportHandler']); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'testheader' => ['testvalue'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(418, $this->response->status, 'We got an incorrect status back. Full response body follows: ' . $this->response->body); + + } + + function reportHandler($reportName, $result, $path) { + + if ($reportName == '{http://www.rooftopsolutions.nl/NS}myreport') { + $this->server->httpResponse->setStatus(418); + $this->server->httpResponse->setHeader('testheader', 'testvalue'); + return false; + } + else return; + + } + + function testGetPropertiesForChildren() { + + $result = $this->server->getPropertiesForChildren('', [ + '{DAV:}getcontentlength', + ]); + + $expected = [ + 'test.txt' => ['{DAV:}getcontentlength' => 13], + 'dir/' => [], + ]; + + $this->assertEquals($expected, $result); + + } + + /** + * There are certain cases where no HTTP status may be set. We need to + * intercept these and set it to a default error message. + */ + function testNoHTTPStatusSet() { + + $this->server->on('method:GET', function() { return false; }, 1); + $this->server->httpRequest = new HTTP\Request('GET', '/'); + $this->server->exec(); + $this->assertEquals(500, $this->response->getStatus()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php new file mode 100644 index 000000000000..383f8e657efb --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php @@ -0,0 +1,102 @@ +<?php + +namespace Sabre\DAV; + +class ServerUpdatePropertiesTest extends \PHPUnit_Framework_TestCase { + + function testUpdatePropertiesFail() { + + $tree = [ + new SimpleCollection('foo'), + ]; + $server = new Server($tree); + + $result = $server->updateProperties('foo', [ + '{DAV:}foo' => 'bar' + ]); + + $expected = [ + '{DAV:}foo' => 403, + ]; + $this->assertEquals($expected, $result); + + } + + function testUpdatePropertiesProtected() { + + $tree = [ + new SimpleCollection('foo'), + ]; + $server = new Server($tree); + + $server->on('propPatch', function($path, PropPatch $propPatch) { + $propPatch->handleRemaining(function() { return true; }); + }); + $result = $server->updateProperties('foo', [ + '{DAV:}getetag' => 'bla', + '{DAV:}foo' => 'bar' + ]); + + $expected = [ + '{DAV:}getetag' => 403, + '{DAV:}foo' => 424, + ]; + $this->assertEquals($expected, $result); + + } + + function testUpdatePropertiesEventFail() { + + $tree = [ + new SimpleCollection('foo'), + ]; + $server = new Server($tree); + $server->on('propPatch', function($path, PropPatch $propPatch) { + $propPatch->setResultCode('{DAV:}foo', 404); + $propPatch->handleRemaining(function() { return true; }); + }); + + $result = $server->updateProperties('foo', [ + '{DAV:}foo' => 'bar', + '{DAV:}foo2' => 'bla', + ]); + + $expected = [ + '{DAV:}foo' => 404, + '{DAV:}foo2' => 424, + ]; + $this->assertEquals($expected, $result); + + } + + function testUpdatePropertiesEventSuccess() { + + $tree = [ + new SimpleCollection('foo'), + ]; + $server = new Server($tree); + $server->on('propPatch', function($path, PropPatch $propPatch) { + + $propPatch->handle(['{DAV:}foo', '{DAV:}foo2'], function() { + return [ + '{DAV:}foo' => 200, + '{DAV:}foo2' => 201, + ]; + }); + + }); + + $result = $server->updateProperties('foo', [ + '{DAV:}foo' => 'bar', + '{DAV:}foo2' => 'bla', + ]); + + $expected = [ + '{DAV:}foo' => 200, + '{DAV:}foo2' => 201, + ]; + $this->assertEquals($expected, $result); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/PluginTest.php new file mode 100644 index 000000000000..6aa09cac0724 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/PluginTest.php @@ -0,0 +1,190 @@ +<?php + +namespace Sabre\DAV\Sharing; + +use Sabre\DAV\Mock; +use Sabre\DAV\Xml\Property; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $setupSharing = true; + protected $setupACL = true; + protected $autoLogin = 'admin'; + + function setUpTree() { + + $this->tree[] = new Mock\SharedNode( + 'shareable', + Plugin::ACCESS_READWRITE + ); + + } + + function testFeatures() { + + $this->assertEquals( + ['resource-sharing'], + $this->sharingPlugin->getFeatures() + ); + + } + + function testProperties() { + + $result = $this->server->getPropertiesForPath( + 'shareable', + ['{DAV:}share-access'] + ); + + $expected = [ + [ + 200 => [ + '{DAV:}share-access' => new Property\ShareAccess(Plugin::ACCESS_READWRITE) + ], + 404 => [], + 'href' => 'shareable', + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + + } + + function testGetPluginInfo() { + + $result = $this->sharingPlugin->getPluginInfo(); + $this->assertInternalType('array', $result); + $this->assertEquals('sharing', $result['name']); + + } + + function testHtmlActionsPanel() { + + $node = new \Sabre\DAV\Mock\Collection('foo'); + $html = ''; + + $this->assertNull( + $this->sharingPlugin->htmlActionsPanel($node, $html, 'foo/bar') + ); + + $this->assertEquals( + '', + $html + ); + + $node = new \Sabre\DAV\Mock\SharedNode('foo', \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER); + $html = ''; + + $this->assertNull( + $this->sharingPlugin->htmlActionsPanel($node, $html, 'shareable') + ); + $this->assertContains( + 'Share this resource', + $html + ); + + } + + function testBrowserPostActionUnknownAction() { + + $this->assertNull($this->sharingPlugin->browserPostAction( + 'shareable', + 'foo', + [] + )); + + } + + function testBrowserPostActionSuccess() { + + $this->assertFalse($this->sharingPlugin->browserPostAction( + 'shareable', + 'share', + [ + 'access' => 'read', + 'href' => 'mailto:foo@example.org', + ] + )); + + $expected = [ + new \Sabre\DAV\Xml\Element\Sharee([ + 'href' => 'mailto:foo@example.org', + 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_READ, + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE, + ]) + ]; + $this->assertEquals( + $expected, + $this->tree[0]->getInvites() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testBrowserPostActionNoHref() { + + $this->sharingPlugin->browserPostAction( + 'shareable', + 'share', + [ + 'access' => 'read', + ] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testBrowserPostActionNoAccess() { + + $this->sharingPlugin->browserPostAction( + 'shareable', + 'share', + [ + 'href' => 'mailto:foo@example.org', + ] + ); + + } + + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testBrowserPostActionBadAccess() { + + $this->sharingPlugin->browserPostAction( + 'shareable', + 'share', + [ + 'href' => 'mailto:foo@example.org', + 'access' => 'bleed', + ] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testBrowserPostActionAccessDenied() { + + $this->aclPlugin->setDefaultAcl([]); + $this->sharingPlugin->browserPostAction( + 'shareable', + 'share', + [ + 'access' => 'read', + 'href' => 'mailto:foo@example.org', + ] + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/ShareResourceTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/ShareResourceTest.php new file mode 100644 index 000000000000..959811166eb4 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sharing/ShareResourceTest.php @@ -0,0 +1,210 @@ +<?php + +namespace Sabre\DAV\Sharing; + +use Sabre\DAV\Mock; +use Sabre\DAV\Xml\Element\Sharee; +use Sabre\HTTP\Request; + +class ShareResourceTest extends \Sabre\DAVServerTest { + + protected $setupSharing = true; + protected $sharingNodeMock; + + function setUpTree() { + + $this->tree[] = $this->sharingNodeMock = new Mock\SharedNode( + 'shareable', + Plugin::ACCESS_SHAREDOWNER + ); + + } + + function testShareResource() { + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:share-resource xmlns:D="DAV:"> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> + </D:sharee> +</D:share-resource> +XML; + $request = new Request('POST', '/shareable', ['Content-Type' => 'application/davsharing+xml; charset="utf-8"'], $body); + + $response = $this->request($request); + $this->assertEquals(200, $response->getStatus(), (string)$response->getBodyAsString()); + + $expected = [ + new Sharee([ + 'href' => 'mailto:eric@example.com', + 'properties' => [ + '{DAV:}displayname' => 'Eric York', + ], + 'access' => Plugin::ACCESS_READWRITE, + 'comment' => 'Shared workspace', + 'inviteStatus' => \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE, + ]) + ]; + + $this->assertEquals( + $expected, + $this->sharingNodeMock->getInvites() + ); + + } + + /** + * @depends testShareResource + */ + function testShareResourceRemoveAccess() { + + // First we just want to execute all the actions from the first + // test. + $this->testShareResource(); + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:share-resource xmlns:D="DAV:"> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:share-access> + <D:no-access /> + </D:share-access> + </D:sharee> +</D:share-resource> +XML; + $request = new Request('POST', '/shareable', ['Content-Type' => 'application/davsharing+xml; charset="utf-8"'], $body); + + $response = $this->request($request); + $this->assertEquals(200, $response->getStatus(), (string)$response->getBodyAsString()); + + $expected = []; + + $this->assertEquals( + $expected, + $this->sharingNodeMock->getInvites() + ); + + + } + + /** + * @depends testShareResource + */ + function testShareResourceInviteProperty() { + + // First we just want to execute all the actions from the first + // test. + $this->testShareResource(); + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:propfind xmlns:D="DAV:"> + <D:prop> + <D:invite /> + <D:share-access /> + <D:share-resource-uri /> + </D:prop> +</D:propfind> +XML; + $request = new Request('PROPFIND', '/shareable', ['Content-Type' => 'application/xml'], $body); + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus()); + + $expected = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:response> + <d:href>/shareable</d:href> + <d:propstat> + <d:prop> + <d:invite> + <d:sharee> + <d:href>mailto:eric@example.com</d:href> + <d:prop> + <d:displayname>Eric York</d:displayname> + </d:prop> + <d:share-access><d:read-write /></d:share-access> + <d:invite-noresponse /> + </d:sharee> + </d:invite> + <d:share-access><d:shared-owner /></d:share-access> + <d:share-resource-uri><d:href>urn:example:bar</d:href></d:share-resource-uri> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $response->getBodyAsString()); + + } + + function testShareResourceNotFound() { + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:share-resource xmlns:D="DAV:"> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> + </D:sharee> +</D:share-resource> +XML; + $request = new Request('POST', '/not-found', ['Content-Type' => 'application/davsharing+xml; charset="utf-8"'], $body); + + $response = $this->request($request, 404); + + } + + function testShareResourceNotISharedNode() { + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:share-resource xmlns:D="DAV:"> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> + </D:sharee> +</D:share-resource> +XML; + $request = new Request('POST', '/', ['Content-Type' => 'application/davsharing+xml; charset="utf-8"'], $body); + + $response = $this->request($request, 403); + + } + + function testShareResourceUnknownDoc() { + + $body = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:blablabla xmlns:D="DAV:" /> +XML; + $request = new Request('POST', '/shareable', ['Content-Type' => 'application/davsharing+xml; charset="utf-8"'], $body); + $response = $this->request($request, 400); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php new file mode 100644 index 000000000000..15ccfaf9e364 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\DAV; + +class SimpleFileTest extends \PHPUnit_Framework_TestCase { + + function testAll() { + + $file = new SimpleFile('filename.txt', 'contents', 'text/plain'); + + $this->assertEquals('filename.txt', $file->getName()); + $this->assertEquals('contents', $file->get()); + $this->assertEquals(8, $file->getSize()); + $this->assertEquals('"' . sha1('contents') . '"', $file->getETag()); + $this->assertEquals('text/plain', $file->getContentType()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php new file mode 100644 index 000000000000..e98fe9048846 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php @@ -0,0 +1,129 @@ +<?php + +namespace Sabre\DAV; + +class StringUtilTest extends \PHPUnit_Framework_TestCase { + + /** + * @param string $haystack + * @param string $needle + * @param string $collation + * @param string $matchType + * @param string $result + * @throws Exception\BadRequest + * + * @dataProvider dataset + */ + function testTextMatch($haystack, $needle, $collation, $matchType, $result) { + + $this->assertEquals($result, StringUtil::textMatch($haystack, $needle, $collation, $matchType)); + + } + + function dataset() { + + return [ + ['FOOBAR', 'FOO', 'i;octet', 'contains', true], + ['FOOBAR', 'foo', 'i;octet', 'contains', false], + ['FÖÖBAR', 'FÖÖ', 'i;octet', 'contains', true], + ['FÖÖBAR', 'föö', 'i;octet', 'contains', false], + ['FOOBAR', 'FOOBAR', 'i;octet', 'equals', true], + ['FOOBAR', 'fooBAR', 'i;octet', 'equals', false], + ['FOOBAR', 'FOO', 'i;octet', 'starts-with', true], + ['FOOBAR', 'foo', 'i;octet', 'starts-with', false], + ['FOOBAR', 'BAR', 'i;octet', 'starts-with', false], + ['FOOBAR', 'bar', 'i;octet', 'starts-with', false], + ['FOOBAR', 'FOO', 'i;octet', 'ends-with', false], + ['FOOBAR', 'foo', 'i;octet', 'ends-with', false], + ['FOOBAR', 'BAR', 'i;octet', 'ends-with', true], + ['FOOBAR', 'bar', 'i;octet', 'ends-with', false], + + ['FOOBAR', 'FOO', 'i;ascii-casemap', 'contains', true], + ['FOOBAR', 'foo', 'i;ascii-casemap', 'contains', true], + ['FÖÖBAR', 'FÖÖ', 'i;ascii-casemap', 'contains', true], + ['FÖÖBAR', 'föö', 'i;ascii-casemap', 'contains', false], + ['FOOBAR', 'FOOBAR', 'i;ascii-casemap', 'equals', true], + ['FOOBAR', 'fooBAR', 'i;ascii-casemap', 'equals', true], + ['FOOBAR', 'FOO', 'i;ascii-casemap', 'starts-with', true], + ['FOOBAR', 'foo', 'i;ascii-casemap', 'starts-with', true], + ['FOOBAR', 'BAR', 'i;ascii-casemap', 'starts-with', false], + ['FOOBAR', 'bar', 'i;ascii-casemap', 'starts-with', false], + ['FOOBAR', 'FOO', 'i;ascii-casemap', 'ends-with', false], + ['FOOBAR', 'foo', 'i;ascii-casemap', 'ends-with', false], + ['FOOBAR', 'BAR', 'i;ascii-casemap', 'ends-with', true], + ['FOOBAR', 'bar', 'i;ascii-casemap', 'ends-with', true], + + ['FOOBAR', 'FOO', 'i;unicode-casemap', 'contains', true], + ['FOOBAR', 'foo', 'i;unicode-casemap', 'contains', true], + ['FÖÖBAR', 'FÖÖ', 'i;unicode-casemap', 'contains', true], + ['FÖÖBAR', 'föö', 'i;unicode-casemap', 'contains', true], + ['FOOBAR', 'FOOBAR', 'i;unicode-casemap', 'equals', true], + ['FOOBAR', 'fooBAR', 'i;unicode-casemap', 'equals', true], + ['FOOBAR', 'FOO', 'i;unicode-casemap', 'starts-with', true], + ['FOOBAR', 'foo', 'i;unicode-casemap', 'starts-with', true], + ['FOOBAR', 'BAR', 'i;unicode-casemap', 'starts-with', false], + ['FOOBAR', 'bar', 'i;unicode-casemap', 'starts-with', false], + ['FOOBAR', 'FOO', 'i;unicode-casemap', 'ends-with', false], + ['FOOBAR', 'foo', 'i;unicode-casemap', 'ends-with', false], + ['FOOBAR', 'BAR', 'i;unicode-casemap', 'ends-with', true], + ['FOOBAR', 'bar', 'i;unicode-casemap', 'ends-with', true], + ]; + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testBadCollation() { + + StringUtil::textMatch('foobar', 'foo', 'blabla', 'contains'); + + } + + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testBadMatchType() { + + StringUtil::textMatch('foobar', 'foo', 'i;octet', 'booh'); + + } + + function testEnsureUTF8_ascii() { + + $inputString = "harkema"; + $outputString = "harkema"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + + function testEnsureUTF8_latin1() { + + $inputString = "m\xfcnster"; + $outputString = "münster"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + + function testEnsureUTF8_utf8() { + + $inputString = "m\xc3\xbcnster"; + $outputString = "münster"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php new file mode 100644 index 000000000000..aac1dee489be --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php @@ -0,0 +1,169 @@ +<?php + +namespace Sabre\DAV\Sync; + +use Sabre\DAV; + +/** + * This mocks a ISyncCollection, for unittesting. + * + * This object behaves the same as SimpleCollection. Call addChange to update + * the 'changelog' that this class uses for the collection. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MockSyncCollection extends DAV\SimpleCollection implements ISyncCollection { + + public $changeLog = []; + + public $token = null; + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken() { + + // Will be 'null' in the first round, and will increment ever after. + return $this->token; + + } + + function addChange(array $added, array $modified, array $deleted) { + + $this->token++; + $this->changeLog[$this->token] = [ + 'added' => $added, + 'modified' => $modified, + 'deleted' => $deleted, + ]; + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * array( + * 'syncToken' => 'The current synctoken', + * 'modified' => array( + * 'new.txt', + * ), + * 'deleted' => array( + * 'foo.php.bak', + * 'old.txt' + * ) + * ); + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null) { + + // This is an initial sync + if (is_null($syncToken)) { + return [ + 'added' => array_map( + function($item) { + return $item->getName(); + }, $this->getChildren() + ), + 'modified' => [], + 'deleted' => [], + 'syncToken' => $this->getSyncToken(), + ]; + } + + if (!is_int($syncToken) && !ctype_digit($syncToken)) { + + return null; + + } + if (is_null($this->token)) return null; + + $added = []; + $modified = []; + $deleted = []; + + foreach ($this->changeLog as $token => $change) { + + if ($token > $syncToken) { + + $added = array_merge($added, $change['added']); + $modified = array_merge($modified, $change['modified']); + $deleted = array_merge($deleted, $change['deleted']); + + if ($limit) { + // If there's a limit, we may need to cut things off. + // This alghorithm is weird and stupid, but it works. + $left = $limit - (count($modified) + count($deleted)); + if ($left > 0) continue; + if ($left === 0) break; + if ($left < 0) { + $modified = array_slice($modified, 0, $left); + } + $left = $limit - (count($modified) + count($deleted)); + if ($left === 0) break; + if ($left < 0) { + $deleted = array_slice($deleted, 0, $left); + } + break; + + } + + } + + } + + return [ + 'syncToken' => $this->token, + 'added' => $added, + 'modified' => $modified, + 'deleted' => $deleted, + ]; + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php new file mode 100644 index 000000000000..6bcec8b75356 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php @@ -0,0 +1,523 @@ +<?php + +namespace Sabre\DAV\Sync; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once __DIR__ . '/MockSyncCollection.php'; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $collection; + + function setUp() { + + parent::setUp(); + $this->server->addPlugin(new Plugin()); + + } + + function testGetInfo() { + + $this->assertArrayHasKey( + 'name', + (new Plugin())->getPluginInfo() + ); + + } + + function setUpTree() { + + $this->collection = + new MockSyncCollection('coll', [ + new DAV\SimpleFile('file1.txt', 'foo'), + new DAV\SimpleFile('file2.txt', 'bar'), + ]); + $this->tree = [ + $this->collection, + new DAV\SimpleCollection('normalcoll', []) + ]; + + } + + function testSupportedReportSet() { + + $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']); + $this->assertFalse($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection')); + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']); + $this->assertTrue($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection')); + + } + + function testGetSyncToken() { + + $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']); + $this->assertFalse(isset($result['{DAV:}sync-token'])); + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']); + $this->assertTrue(isset($result['{DAV:}sync-token'])); + + // non-sync-enabled collection + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/normalcoll', ['{DAV:}sync-token']); + $this->assertFalse(isset($result['{DAV:}sync-token'])); + } + + function testSyncInitialSyncCollection() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $request = new HTTP\Request('REPORT', '/coll/', ['Content-Type' => 'application/xml']); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse($response->getBodyAsString()); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/1', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file1.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollection() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:sync-level>infinite</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse($response->getBodyAsString()); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollectionLimit() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:sync-level>infinite</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> + <D:limit><D:nresults>1</D:nresults></D:limit> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(1, count($responses), 'We expected exactly 1 {DAV:}response'); + + $response = $responses[0]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollectionDepthFallBack() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + 'HTTP_DEPTH' => "1", + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSyncNoSyncInfo() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoSyncCollection() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/normalcoll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncInvalidToken() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/invalid</D:sync-token> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(403, $response->status, 'Full response body:' . $response->body); + + } + function testSyncInvalidTokenNoPrefix() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>invalid</D:sync-token> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(403, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoSyncToken() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoProp() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token /> + <D:sync-level>1</D:sync-level> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testIfConditions() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (<http://sabre.io/ns/sync/1>)', + ]); + $response = $this->request($request); + + // If a 403 is thrown this works correctly. The file in questions + // doesn't allow itself to be deleted. + // If the If conditions failed, it would have been a 412 instead. + $this->assertEquals(403, $response->status); + + } + + function testIfConditionsNot() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (Not <http://sabre.io/ns/sync/2>)', + ]); + $response = $this->request($request); + + // If a 403 is thrown this works correctly. The file in questions + // doesn't allow itself to be deleted. + // If the If conditions failed, it would have been a 412 instead. + $this->assertEquals(403, $response->status); + + } + + function testIfConditionsNoSyncToken() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (<opaquelocktoken:foo>)', + ]); + $response = $this->request($request); + + $this->assertEquals(412, $response->status); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php new file mode 100644 index 000000000000..ff139f78c41e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php @@ -0,0 +1,106 @@ +<?php + +namespace Sabre\DAV; + +class SyncTokenPropertyTest extends \Sabre\DAVServerTest { + + /** + * The assumption in these tests is that a PROPFIND is going on, and to + * fetch the sync-token, the event handler is just able to use the existing + * result. + * + * @param string $name + * @param mixed $value + * + * @dataProvider data + */ + function testAlreadyThere1($name, $value) { + + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + $name, + ]); + + $propFind->set($name, $value); + $corePlugin = new CorePlugin(); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertEquals("hello", $propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + /** + * In these test-cases, the plugin is forced to do a local propfind to + * fetch the items. + * + * @param string $name + * @param mixed $value + * + * @dataProvider data + */ + function testRefetch($name, $value) { + + $this->server->tree = new Tree( + new SimpleCollection('root', [ + new Mock\PropertiesCollection( + 'foo', + [], + [$name => $value] + ) + ]) + ); + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + $name, + ]); + + $corePlugin = $this->server->getPlugin('core'); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertEquals("hello", $propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + function testNoData() { + + $this->server->tree = new Tree( + new SimpleCollection('root', [ + new Mock\PropertiesCollection( + 'foo', + [], + [] + ) + ]) + ); + + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + ]); + + $corePlugin = $this->server->getPlugin('core'); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertNull($propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + function data() { + + return [ + [ + '{http://sabredav.org/ns}sync-token', + "hello" + ], + [ + '{DAV:}sync-token', + "hello" + ], + [ + '{DAV:}sync-token', + new Xml\Property\Href(Sync\Plugin::SYNCTOKEN_PREFIX . "hello", false) + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php new file mode 100644 index 000000000000..6acd6b077b88 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php @@ -0,0 +1,199 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class TemporaryFileFilterTest extends AbstractServer { + + function setUp() { + + parent::setUp(); + $plugin = new TemporaryFileFilterPlugin(SABRE_TEMPDIR . '/tff'); + $this->server->addPlugin($plugin); + + } + + function testPutNormal() { + + $request = new HTTP\Request('PUT', '/testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals('0', $this->response->getHeader('Content-Length')); + + $this->assertEquals('Testing new file', file_get_contents(SABRE_TEMPDIR . '/testput.txt')); + + } + + function testPutTemp() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testput.txt'), '._testput.txt should not exist in the regular file structure.'); + + } + + function testPutTempIfNoneMatch() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', ['If-None-Match' => '*'], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testput.txt'), '._testput.txt should not exist in the regular file structure.'); + + + $this->server->exec(); + + $this->assertEquals(412, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + } + + function testPutGet() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $request = new HTTP\Request('GET', '/._testput.txt'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + 'Content-Length' => [16], + 'Content-Type' => ['application/octet-stream'], + ], $this->response->getHeaders()); + + $this->assertEquals('Testing new file', stream_get_contents($this->response->body)); + + } + + function testLockNonExistant() { + + mkdir(SABRE_TEMPDIR . '/locksdir'); + $locksBackend = new Locks\Backend\File(SABRE_TEMPDIR . '/locks'); + $locksPlugin = new Locks\Plugin($locksBackend); + $this->server->addPlugin($locksPlugin); + + // mimicking an OS/X resource fork + $request = new HTTP\Request('LOCK', '/._testput.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8', $this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/', $this->response->getHeader('Lock-Token')) === 1, 'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + $this->assertEquals('true', $this->response->getHeader('X-Sabre-Temp')); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testlock.txt'), '._testlock.txt should not exist in the regular file structure.'); + + } + + function testPutDelete() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $request = new HTTP\Request('DELETE', '/._testput.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(204, $this->response->status, "Incorrect status code received. Full body:\n" . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $this->assertEquals('', $this->response->body); + + } + + function testPutPropfind() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + ], $this->response->getHeaders()); + + $request = new HTTP\Request('PROPFIND', '/._testput.txt'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Incorrect status code returned. Body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Temp' => ['true'], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $this->response->getHeaders()); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/._testput.txt', (string)$data, 'href element should have been /._testput.txt'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + $this->assertEquals(1, count($data)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php new file mode 100644 index 000000000000..619ac03fd734 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +class TestPlugin extends ServerPlugin { + + public $beforeMethod; + + function getFeatures() { + + return ['drinking']; + + } + + function getHTTPMethods($uri) { + + return ['BEER','WINE']; + + } + + function initialize(Server $server) { + + $server->on('beforeMethod', [$this, 'beforeMethod']); + + } + + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + $this->beforeMethod = $request->getMethod(); + return true; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php new file mode 100644 index 000000000000..e719e38d59d0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php @@ -0,0 +1,242 @@ +<?php + +namespace Sabre\DAV; + +class TreeTest extends \PHPUnit_Framework_TestCase { + + function testNodeExists() { + + $tree = new TreeMock(); + + $this->assertTrue($tree->nodeExists('hi')); + $this->assertFalse($tree->nodeExists('hello')); + + } + + function testCopy() { + + $tree = new TreeMock(); + $tree->copy('hi', 'hi2'); + + $this->assertArrayHasKey('hi2', $tree->getNodeForPath('')->newDirectories); + $this->assertEquals('foobar', $tree->getNodeForPath('hi/file')->get()); + $this->assertEquals(['test1' => 'value'], $tree->getNodeForPath('hi/file')->getProperties([])); + + } + + function testMove() { + + $tree = new TreeMock(); + $tree->move('hi', 'hi2'); + + $this->assertEquals('hi2', $tree->getNodeForPath('hi')->getName()); + $this->assertTrue($tree->getNodeForPath('hi')->isRenamed); + + } + + function testDeepMove() { + + $tree = new TreeMock(); + $tree->move('hi/sub', 'hi2'); + + $this->assertArrayHasKey('hi2', $tree->getNodeForPath('')->newDirectories); + $this->assertTrue($tree->getNodeForPath('hi/sub')->isDeleted); + + } + + function testDelete() { + + $tree = new TreeMock(); + $tree->delete('hi'); + $this->assertTrue($tree->getNodeForPath('hi')->isDeleted); + + } + + function testGetChildren() { + + $tree = new TreeMock(); + $children = $tree->getChildren(''); + $this->assertEquals(2, count($children)); + $this->assertEquals('hi', $children[0]->getName()); + + } + + function testGetMultipleNodes() { + + $tree = new TreeMock(); + $result = $tree->getMultipleNodes(['hi/sub', 'hi/file']); + $this->assertArrayHasKey('hi/sub', $result); + $this->assertArrayHasKey('hi/file', $result); + + $this->assertEquals('sub', $result['hi/sub']->getName()); + $this->assertEquals('file', $result['hi/file']->getName()); + + } + function testGetMultipleNodes2() { + + $tree = new TreeMock(); + $result = $tree->getMultipleNodes(['multi/1', 'multi/2']); + $this->assertArrayHasKey('multi/1', $result); + $this->assertArrayHasKey('multi/2', $result); + + } + +} + +class TreeMock extends Tree { + + private $nodes = []; + + function __construct() { + + $file = new TreeFileTester('file'); + $file->properties = ['test1' => 'value']; + $file->data = 'foobar'; + + parent::__construct( + new TreeDirectoryTester('root', [ + new TreeDirectoryTester('hi', [ + new TreeDirectoryTester('sub'), + $file, + ]), + new TreeMultiGetTester('multi', [ + new TreeFileTester('1'), + new TreeFileTester('2'), + new TreeFileTester('3'), + ]) + ]) + ); + + } + +} + +class TreeDirectoryTester extends SimpleCollection { + + public $newDirectories = []; + public $newFiles = []; + public $isDeleted = false; + public $isRenamed = false; + + function createDirectory($name) { + + $this->newDirectories[$name] = true; + + } + + function createFile($name, $data = null) { + + $this->newFiles[$name] = $data; + + } + + function getChild($name) { + + if (isset($this->newDirectories[$name])) return new self($name); + if (isset($this->newFiles[$name])) return new TreeFileTester($name, $this->newFiles[$name]); + return parent::getChild($name); + + } + + function childExists($name) { + + return !!$this->getChild($name); + + } + + function delete() { + + $this->isDeleted = true; + + } + + function setName($name) { + + $this->isRenamed = true; + $this->name = $name; + + } + +} + +class TreeFileTester extends File implements IProperties { + + public $name; + public $data; + public $properties; + + function __construct($name, $data = null) { + + $this->name = $name; + if (is_null($data)) $data = 'bla'; + $this->data = $data; + + } + + function getName() { + + return $this->name; + + } + + function get() { + + return $this->data; + + } + + function getProperties($properties) { + + return $this->properties; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + + $this->properties = $propPatch->getMutations(); + $propPatch->setRemainingResultCode(200); + + } + +} + +class TreeMultiGetTester extends TreeDirectoryTester implements IMultiGet { + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param array $paths + * @return array + */ + function getMultipleChildren(array $paths) { + + $result = []; + foreach ($paths as $path) { + try { + $child = $this->getChild($path); + $result[] = $child; + } catch (Exception\NotFound $e) { + // Do nothing + } + } + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php new file mode 100644 index 000000000000..f005ecc75f0b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php @@ -0,0 +1,25 @@ +<?php + +namespace Sabre\DAV; + +class UUIDUtilTest extends \PHPUnit_Framework_TestCase { + + function testValidateUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555') + ); + $this->assertTrue( + UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555') + ); + + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php new file mode 100644 index 000000000000..7cc10650cba2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php @@ -0,0 +1,154 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV\Xml\Property\Complex; +use Sabre\DAV\Xml\Property\Href; +use Sabre\DAV\Xml\XmlTest; + +class PropTest extends XmlTest { + + function testDeserializeSimple() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>bar</foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => 'bar', + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeEmpty() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:" /> +XML; + + $expected = [ + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeComplex() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo><no>yes</no></foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => new Complex('<no xmlns="DAV:">yes</no>') + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeCustom() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo><href>/hello</href></foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => new Href('/hello', false) + ]; + + $elementMap = [ + '{DAV:}foo' => 'Sabre\DAV\Xml\Property\Href' + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + function testDeserializeCustomCallback() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => 'zim', + ]; + + $elementMap = [ + '{DAV:}foo' => function($reader) { + $reader->next(); + return 'zim'; + } + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + /** + * @expectedException \LogicException + */ + function testDeserializeCustomBad() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = []; + + $elementMap = [ + '{DAV:}foo' => 'idk?', + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + /** + * @expectedException \LogicException + */ + function testDeserializeCustomBadObj() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = []; + + $elementMap = [ + '{DAV:}foo' => new \StdClass(), + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + function assertDecodeProp($input, array $expected, array $elementMap = []) { + + $elementMap['{DAV:}root'] = 'Sabre\DAV\Xml\Element\Prop'; + + $result = $this->parse($input, $elementMap); + $this->assertInternalType('array', $result); + $this->assertEquals($expected, $result['value']); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php new file mode 100644 index 000000000000..f19e7df7c0c9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php @@ -0,0 +1,313 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV; + +class ResponseTest extends DAV\Xml\XmlTest { + + function testSimple() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + 404 => [ + '{DAV:}owner' => null, + ] + ]; + + $property = new Response('uri', $innerProps); + + $this->assertEquals('uri', $property->getHref()); + $this->assertEquals($innerProps, $property->getResponseProperties()); + + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + 404 => [ + '{DAV:}owner' => null, + ] + ]; + + $property = new Response('uri', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:displayname>my file</d:displayname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + <d:propstat> + <d:prop> + <d:owner/> + </d:prop> + <d:status>HTTP/1.1 404 Not Found</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * This one is specifically for testing properties with no namespaces, which is legal xml + * + * @depends testSerialize + */ + function testSerializeEmptyNamespace() { + + $innerProps = [ + 200 => [ + '{}propertyname' => 'value', + ], + ]; + + $property = new Response('uri', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertEquals( +'<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <propertyname xmlns="">value</propertyname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * This one is specifically for testing properties with no namespaces, which is legal xml + * + * @depends testSerialize + */ + function testSerializeCustomNamespace() { + + $innerProps = [ + 200 => [ + '{http://sabredav.org/NS/example}propertyname' => 'value', + ], + ]; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <x1:propertyname xmlns:x1="http://sabredav.org/NS/example">value</x1:propertyname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root>', $xml); + + } + + /** + * @depends testSerialize + */ + function testSerializeComplexProperty() { + + $innerProps = [ + 200 => [ + '{DAV:}link' => new DAV\Xml\Property\Href('http://sabredav.org/', false) + ], + ]; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:link><d:href>http://sabredav.org/</d:href></d:link> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * @depends testSerialize + * @expectedException \InvalidArgumentException + */ + function testSerializeBreak() { + + $innerProps = [ + 200 => [ + '{DAV:}link' => new \STDClass() + ], + ]; + + $property = new Response('uri', $innerProps); + $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + } + + function testDeserializeComplexProperty() { + + $xml = '<?xml version="1.0"?> +<d:response xmlns:d="DAV:"> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:foo>hello</d:foo> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> +</d:response> +'; + + $result = $this->parse($xml, [ + '{DAV:}response' => 'Sabre\DAV\Xml\Element\Response', + '{DAV:}foo' => function($reader) { + + $reader->next(); + return 'world'; + }, + ]); + $this->assertEquals( + new Response('/uri', [ + '200' => [ + '{DAV:}foo' => 'world', + ] + ]), + $result['value'] + ); + + } + + /** + * @depends testSimple + */ + function testSerializeUrlencoding() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + ]; + + $property = new Response('space here', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/space%20here</d:href> + <d:propstat> + <d:prop> + <d:displayname>my file</d:displayname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * @depends testSerialize + * + * The WebDAV spec _requires_ at least one DAV:propstat to appear for + * every DAV:response. In some circumstances however, there are no + * properties to encode. + * + * In those cases we MUST specify at least one DAV:propstat anyway, with + * no properties. + */ + function testSerializeNoProperties() { + + $innerProps = []; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop /> + <d:status>HTTP/1.1 418 I\'m a teapot</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * In the case of {DAV:}prop, a deserializer should never get called, if + * the property element is empty. + */ + function testDeserializeComplexPropertyEmpty() { + + $xml = '<?xml version="1.0"?> +<d:response xmlns:d="DAV:"> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:foo /> + </d:prop> + <d:status>HTTP/1.1 404 Not Found</d:status> + </d:propstat> +</d:response> +'; + + $result = $this->parse($xml, [ + '{DAV:}response' => 'Sabre\DAV\Xml\Element\Response', + '{DAV:}foo' => function($reader) { + throw new \LogicException('This should never happen'); + }, + ]); + $this->assertEquals( + new Response('/uri', [ + '404' => [ + '{DAV:}foo' => null + ] + ]), + $result['value'] + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ShareeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ShareeTest.php new file mode 100644 index 000000000000..3704d8782446 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ShareeTest.php @@ -0,0 +1,98 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV\Sharing\Plugin; +use Sabre\DAV\Xml\XmlTest; + +class ShareeTest extends XmlTest { + + /** + * @expectedException \InvalidArgumentException + */ + function testShareeUnknownPropertyInConstructor() { + + new Sharee(['foo' => 'bar']); + + } + + function testDeserialize() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:sharee xmlns:D="DAV:"> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> +</D:sharee> +XML; + + $result = $this->parse($xml, [ + '{DAV:}sharee' => 'Sabre\\DAV\\Xml\\Element\\Sharee' + ]); + + $expected = new Sharee([ + 'href' => 'mailto:eric@example.com', + 'properties' => ['{DAV:}displayname' => 'Eric York'], + 'comment' => 'Shared workspace', + 'access' => Plugin::ACCESS_READWRITE, + ]); + $this->assertEquals( + $expected, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoHref() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:sharee xmlns:D="DAV:"> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> +</D:sharee> +XML; + + $this->parse($xml, [ + '{DAV:}sharee' => 'Sabre\\DAV\\Xml\\Element\\Sharee' + ]); + + } + + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoShareeAccess() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:sharee xmlns:D="DAV:"> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> +</D:sharee> +XML; + + $this->parse($xml, [ + '{DAV:}sharee' => 'Sabre\\DAV\\Xml\\Element\\Sharee' + ]); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php new file mode 100644 index 000000000000..bf58853371e3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php @@ -0,0 +1,109 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\DAV\Xml\XmlTest; + +class HrefTest extends XmlTest { + + function testConstruct() { + + $href = new Href('path'); + $this->assertEquals('path', $href->getHref()); + + } + + function testSerialize() { + + $href = new Href('path'); + $this->assertEquals('path', $href->getHref()); + + $this->contextUri = '/bla/'; + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything> +', $xml); + + } + + function testUnserialize() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything> +'; + + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + + $href = $result['value']; + + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $href); + + $this->assertEquals('/bla/path', $href->getHref()); + + } + + function testUnserializeIncompatible() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href2>/bla/path</d:href2></d:anything> +'; + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + $href = $result['value']; + $this->assertNull($href); + + } + function testUnserializeEmpty() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"></d:anything> +'; + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + $href = $result['value']; + $this->assertNull($href); + + } + + /** + * This method tests if hrefs containing & are correctly encoded. + */ + function testSerializeEntity() { + + $href = new Href('http://example.org/?a&b', false); + $this->assertEquals('http://example.org/?a&b', $href->getHref()); + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>http://example.org/?a&b</d:href></d:anything> +', $xml); + + } + + function testToHtml() { + + $href = new Href([ + '/foo/bar', + 'foo/bar', + 'http://example.org/bar' + ]); + + $html = new HtmlOutputHelper( + '/base/', + [] + ); + + $expected = + '<a href="/foo/bar">/foo/bar</a><br />' . + '<a href="/base/foo/bar">/base/foo/bar</a><br />' . + '<a href="http://example.org/bar">http://example.org/bar</a>'; + $this->assertEquals($expected, $href->toHtml($html)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/InviteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/InviteTest.php new file mode 100644 index 000000000000..6f8d6cc6c304 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/InviteTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Sharing\Plugin; +use Sabre\DAV\Xml\Element\Sharee; +use Sabre\DAV\Xml\XmlTest; + +class InviteTest extends XmlTest { + + function testSerialize() { + + $sharees = [ + new Sharee(), + new Sharee(), + new Sharee(), + new Sharee() + ]; + $sharees[0]->href = 'mailto:foo@example.org'; + $sharees[0]->properties['{DAV:}displayname'] = 'Foo Bar'; + $sharees[0]->access = Plugin::ACCESS_SHAREDOWNER; + $sharees[0]->inviteStatus = Plugin::INVITE_ACCEPTED; + + $sharees[1]->href = 'mailto:bar@example.org'; + $sharees[1]->access = Plugin::ACCESS_READ; + $sharees[1]->inviteStatus = Plugin::INVITE_DECLINED; + + $sharees[2]->href = 'mailto:baz@example.org'; + $sharees[2]->access = Plugin::ACCESS_READWRITE; + $sharees[2]->inviteStatus = Plugin::INVITE_NORESPONSE; + + $sharees[3]->href = 'mailto:zim@example.org'; + $sharees[3]->access = Plugin::ACCESS_READWRITE; + $sharees[3]->inviteStatus = Plugin::INVITE_INVALID; + + $invite = new Invite($sharees); + + $xml = $this->write(['{DAV:}root' => $invite]); + + $expected = <<<XML +<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> +<d:sharee> + <d:href>mailto:foo@example.org</d:href> + <d:prop> + <d:displayname>Foo Bar</d:displayname> + </d:prop> + <d:share-access><d:shared-owner /></d:share-access> + <d:invite-accepted/> +</d:sharee> +<d:sharee> + <d:href>mailto:bar@example.org</d:href> + <d:prop /> + <d:share-access><d:read /></d:share-access> + <d:invite-declined/> +</d:sharee> +<d:sharee> + <d:href>mailto:baz@example.org</d:href> + <d:prop /> + <d:share-access><d:read-write /></d:share-access> + <d:invite-noresponse/> +</d:sharee> +<d:sharee> + <d:href>mailto:zim@example.org</d:href> + <d:prop /> + <d:share-access><d:read-write /></d:share-access> + <d:invite-invalid/> +</d:sharee> +</d:root> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php new file mode 100644 index 000000000000..669efbd4534e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use DateTime; +use DateTimeZone; +use Sabre\DAV\Xml\XmlTest; + +class LastModifiedTest extends XmlTest { + + function testSerializeDateTime() { + + $dt = new DateTime('2015-03-24 11:47:00', new DateTimeZone('America/Vancouver')); + $val = ['{DAV:}getlastmodified' => new GetLastModified($dt)]; + + $result = $this->write($val); + $expected = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $result); + + } + + function testSerializeTimeStamp() { + + $dt = new DateTime('2015-03-24 11:47:00', new DateTimeZone('America/Vancouver')); + $dt = $dt->getTimeStamp(); + $val = ['{DAV:}getlastmodified' => new GetLastModified($dt)]; + + $result = $this->write($val); + $expected = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $result); + + } + + function testDeserialize() { + + $input = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $elementMap = ['{DAV:}getlastmodified' => 'Sabre\DAV\Xml\Property\GetLastModified']; + $result = $this->parse($input, $elementMap); + + $this->assertEquals( + new DateTime('2015-03-24 18:47:00', new DateTimeZone('UTC')), + $result['value']->getTime() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LocalHrefTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LocalHrefTest.php new file mode 100644 index 000000000000..c3f69c929f6a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LocalHrefTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\DAV\Xml\XmlTest; + +class LocalHrefTest extends XmlTest { + + function testConstruct() { + + $href = new LocalHref('path'); + $this->assertEquals('path', $href->getHref()); + + } + + function testSerialize() { + + $href = new LocalHref('path'); + $this->assertEquals('path', $href->getHref()); + + $this->contextUri = '/bla/'; + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything> +', $xml); + + } + function testSerializeSpace() { + + $href = new LocalHref('path alsopath'); + $this->assertEquals('path%20alsopath', $href->getHref()); + + $this->contextUri = '/bla/'; + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path%20alsopath</d:href></d:anything> +', $xml); + + } + function testToHtml() { + + $href = new LocalHref([ + '/foo/bar', + 'foo/bar', + 'http://example.org/bar' + ]); + + $html = new HtmlOutputHelper( + '/base/', + [] + ); + + $expected = + '<a href="/foo/bar">/foo/bar</a><br />' . + '<a href="/base/foo/bar">/base/foo/bar</a><br />' . + '<a href="http://example.org/bar">http://example.org/bar</a>'; + $this->assertEquals($expected, $href->toHtml($html)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php new file mode 100644 index 000000000000..0ad069c47236 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Locks\LockInfo; +use Sabre\DAV\Xml\XmlTest; + +class LockDiscoveryTest extends XmlTest { + + function testSerialize() { + + $lock = new LockInfo(); + $lock->owner = 'hello'; + $lock->token = 'blabla'; + $lock->timeout = 600; + $lock->created = strtotime('2015-03-25 19:21:00'); + $lock->scope = LockInfo::EXCLUSIVE; + $lock->depth = 0; + $lock->uri = 'hi'; + + $prop = new LockDiscovery([$lock]); + + $xml = $this->write(['{DAV:}root' => $prop]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:activelock> + <d:lockscope><d:exclusive /></d:lockscope> + <d:locktype><d:write /></d:locktype> + <d:lockroot> + <d:href>/hi</d:href> + </d:lockroot> + <d:depth>0</d:depth> + <d:timeout>Second-600</d:timeout> + <d:locktoken> + <d:href>opaquelocktoken:blabla</d:href> + </d:locktoken> + <d:owner>hello</d:owner> + + +</d:activelock> +</d:root> +', $xml); + + } + + function testSerializeShared() { + + $lock = new LockInfo(); + $lock->owner = 'hello'; + $lock->token = 'blabla'; + $lock->timeout = 600; + $lock->created = strtotime('2015-03-25 19:21:00'); + $lock->scope = LockInfo::SHARED; + $lock->depth = 0; + $lock->uri = 'hi'; + + $prop = new LockDiscovery([$lock]); + + $xml = $this->write(['{DAV:}root' => $prop]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:activelock> + <d:lockscope><d:shared /></d:lockscope> + <d:locktype><d:write /></d:locktype> + <d:lockroot> + <d:href>/hi</d:href> + </d:lockroot> + <d:depth>0</d:depth> + <d:timeout>Second-600</d:timeout> + <d:locktoken> + <d:href>opaquelocktoken:blabla</d:href> + </d:locktoken> + <d:owner>hello</d:owner> + + +</d:activelock> +</d:root> +', $xml); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/ShareAccessTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/ShareAccessTest.php new file mode 100644 index 000000000000..6e733dded21e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/ShareAccessTest.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Sharing\Plugin; +use Sabre\DAV\Xml\XmlTest; + +class ShareAccessTest extends XmlTest { + + function testSerialize() { + + $data = ['{DAV:}root' => [ + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_READ), + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_READWRITE), + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_NOTSHARED), + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_NOACCESS), + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_SHAREDOWNER), + ], + + ]]; + + $xml = $this->write($data); + + $expected = <<<XML +<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:share-access><d:read /></d:share-access> + <d:share-access><d:read-write /></d:share-access> + <d:share-access><d:not-shared /></d:share-access> + <d:share-access><d:no-access /></d:share-access> + <d:share-access><d:shared-owner /></d:share-access> +</d:root> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testDeserialize() { + + $input = <<<XML +<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:share-access><d:read /></d:share-access> + <d:share-access><d:read-write /></d:share-access> + <d:share-access><d:not-shared /></d:share-access> + <d:share-access><d:no-access /></d:share-access> + <d:share-access><d:shared-owner /></d:share-access> +</d:root> +XML; + + $data = [ + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_READ), + 'attributes' => [], + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_READWRITE), + 'attributes' => [], + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_NOTSHARED), + 'attributes' => [], + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_NOACCESS), + 'attributes' => [], + ], + [ + 'name' => '{DAV:}share-access', + 'value' => new ShareAccess(Plugin::ACCESS_SHAREDOWNER), + 'attributes' => [], + ], + + ]; + + $this->assertParsedValue( + $data, + $input, + ['{DAV:}share-access' => ShareAccess::class] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeInvalid() { + + $input = <<<XML +<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:share-access><d:foo /></d:share-access> +</d:root> +XML; + + $this->parse( + $input, + ['{DAV:}share-access' => ShareAccess::class] + ); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php new file mode 100644 index 000000000000..3d54acd2d9d7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Xml\XmlTest; + +class SupportedMethodSetTest extends XmlTest { + + function testSimple() { + + $cus = new SupportedMethodSet(['GET', 'PUT']); + $this->assertEquals(['GET', 'PUT'], $cus->getValue()); + + $this->assertTrue($cus->has('GET')); + $this->assertFalse($cus->has('HEAD')); + + } + + function testSerialize() { + + $cus = new SupportedMethodSet(['GET', 'PUT']); + $xml = $this->write(['{DAV:}foo' => $cus]); + + $expected = '<?xml version="1.0"?> +<d:foo xmlns:d="DAV:"> + <d:supported-method name="GET"/> + <d:supported-method name="PUT"/> +</d:foo>'; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testSerializeHtml() { + + $cus = new SupportedMethodSet(['GET', 'PUT']); + $result = $cus->toHtml( + new \Sabre\DAV\Browser\HtmlOutputHelper('/', []) + ); + + $this->assertEquals('GET, PUT', $result); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php new file mode 100644 index 000000000000..cc25697f68da --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php @@ -0,0 +1,115 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class SupportedReportSetTest extends DAV\AbstractServer { + + function sendPROPFIND($body) { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'PROPFIND', + 'HTTP_DEPTH' => '0', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($body); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + } + + /** + */ + function testNoReports() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supported-report-set /> + </d:prop> +</d:propfind>'; + + $this->sendPROPFIND($xml); + + $this->assertEquals(207, $this->response->status, 'We expected a multi-status response. Full response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:prop\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:supported-report-set\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:status\' element'); + + $this->assertEquals('HTTP/1.1 200 OK', (string)$data[0], 'The status for this property should have been 200'); + + } + + /** + * @depends testNoReports + */ + function testCustomReport() { + + // Intercepting the report property + $this->server->on('propFind', function(DAV\PropFind $propFind, DAV\INode $node) { + if ($prop = $propFind->get('{DAV:}supported-report-set')) { + $prop->addReport('{http://www.rooftopsolutions.nl/testnamespace}myreport'); + $prop->addReport('{DAV:}anotherreport'); + } + }, 200); + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supported-report-set /> + </d:prop> +</d:propfind>'; + + $this->sendPROPFIND($xml); + + $this->assertEquals(207, $this->response->status, 'We expected a multi-status response. Full response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + $xml->registerXPathNamespace('x', 'http://www.rooftopsolutions.nl/testnamespace'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:prop\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:supported-report-set\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report'); + $this->assertEquals(2, count($data), 'We expected 2 \'d:supported-report\' elements'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report'); + $this->assertEquals(2, count($data), 'We expected 2 \'d:report\' elements'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report/x:myreport'); + $this->assertEquals(1, count($data), 'We expected 1 \'x:myreport\' element. Full body: ' . $this->response->body); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report/d:anotherreport'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:anotherreport\' element. Full body: ' . $this->response->body); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:status\' element'); + + $this->assertEquals('HTTP/1.1 200 OK', (string)$data[0], 'The status for this property should have been 200'); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php new file mode 100644 index 000000000000..c11668b61882 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\XmlTest; + +class PropFindTest extends XmlTest { + + function testDeserializeProp() { + + $xml = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:prop> + <d:hello /> + </d:prop> +</d:root> +'; + + $result = $this->parse($xml, ['{DAV:}root' => 'Sabre\\DAV\\Xml\\Request\PropFind']); + + $propFind = new PropFind(); + $propFind->properties = ['{DAV:}hello']; + + $this->assertEquals($propFind, $result['value']); + + + } + + function testDeserializeAllProp() { + + $xml = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:allprop /> +</d:root> +'; + + $result = $this->parse($xml, ['{DAV:}root' => 'Sabre\\DAV\\Xml\\Request\PropFind']); + + $propFind = new PropFind(); + $propFind->allProp = true; + + $this->assertEquals($propFind, $result['value']); + + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php new file mode 100644 index 000000000000..03514da5cb64 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\Property\Href; +use Sabre\DAV\Xml\XmlTest; + +class PropPatchTest extends XmlTest { + + function testSerialize() { + + $propPatch = new PropPatch(); + $propPatch->properties = [ + '{DAV:}displayname' => 'Hello!', + '{DAV:}delete-me' => null, + '{DAV:}some-url' => new Href('foo/bar') + ]; + + $result = $this->write( + ['{DAV:}propertyupdate' => $propPatch] + ); + + $expected = <<<XML +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:displayname>Hello!</d:displayname> + </d:prop> + </d:set> + <d:remove> + <d:prop> + <d:delete-me /> + </d:prop> + </d:remove> + <d:set> + <d:prop> + <d:some-url> + <d:href>/foo/bar</d:href> + </d:some-url> + </d:prop> + </d:set> +</d:propertyupdate> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $result + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/ShareResourceTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/ShareResourceTest.php new file mode 100644 index 000000000000..1e6b5602de04 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/ShareResourceTest.php @@ -0,0 +1,75 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Sharing\Plugin; +use Sabre\DAV\Xml\Element\Sharee; +use Sabre\DAV\Xml\XmlTest; + +class ShareResourceTest extends XmlTest { + + function testDeserialize() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:share-resource xmlns:D="DAV:"> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:prop> + <D:displayname>Eric York</D:displayname> + </D:prop> + <D:comment>Shared workspace</D:comment> + <D:share-access> + <D:read-write /> + </D:share-access> + </D:sharee> + <D:sharee> + <D:href>mailto:eric@example.com</D:href> + <D:share-access> + <D:read /> + </D:share-access> + </D:sharee> + <D:sharee> + <D:href>mailto:wilfredo@example.com</D:href> + <D:share-access> + <D:no-access /> + </D:share-access> + </D:sharee> +</D:share-resource> +XML; + + $result = $this->parse($xml, [ + '{DAV:}share-resource' => 'Sabre\\DAV\\Xml\\Request\\ShareResource' + ]); + + $this->assertInstanceOf( + 'Sabre\\DAV\\Xml\\Request\\ShareResource', + $result['value'] + ); + + $expected = [ + new Sharee(), + new Sharee(), + new Sharee(), + ]; + + $expected[0]->href = 'mailto:eric@example.com'; + $expected[0]->properties['{DAV:}displayname'] = 'Eric York'; + $expected[0]->comment = 'Shared workspace'; + $expected[0]->access = Plugin::ACCESS_READWRITE; + + $expected[1]->href = 'mailto:eric@example.com'; + $expected[1]->access = Plugin::ACCESS_READ; + + $expected[2]->href = 'mailto:wilfredo@example.com'; + $expected[2]->access = Plugin::ACCESS_NOACCESS; + + $this->assertEquals( + $expected, + $result['value']->sharees + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php new file mode 100644 index 000000000000..bde1a103d195 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php @@ -0,0 +1,94 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\XmlTest; + +class SyncCollectionTest extends XmlTest { + + function testDeserializeProp() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>1</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = 1; + $elem->properties = ['{DAV:}foo']; + + $this->assertEquals($elem, $result['value']); + + } + + + function testDeserializeLimit() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>1</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> + <d:limit><d:nresults>5</d:nresults></d:limit> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = 1; + $elem->properties = ['{DAV:}foo']; + $elem->limit = 5; + + $this->assertEquals($elem, $result['value']); + + } + + + function testDeserializeInfinity() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>infinity</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = \Sabre\DAV\Server::DEPTH_INFINITY; + $elem->properties = ['{DAV:}foo']; + + $this->assertEquals($elem, $result['value']); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeMissingElem() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport']); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php new file mode 100644 index 000000000000..906a36085bc3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\DAV\Xml; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +abstract class XmlTest extends \PHPUnit_Framework_TestCase { + + protected $elementMap = []; + protected $namespaceMap = ['DAV:' => 'd']; + protected $contextUri = '/'; + + function write($input) { + + $writer = new Writer(); + $writer->contextUri = $this->contextUri; + $writer->namespaceMap = $this->namespaceMap; + $writer->openMemory(); + $writer->setIndent(true); + $writer->write($input); + return $writer->outputMemory(); + + } + + function parse($xml, array $elementMap = []) { + + $reader = new Reader(); + $reader->elementMap = array_merge($this->elementMap, $elementMap); + $reader->xml($xml); + return $reader->parse(); + + } + + function assertParsedValue($expected, $xml, array $elementMap = []) { + + $result = $this->parse($xml, $elementMap); + $this->assertEquals($expected, $result['value']); + + } + + function cleanUp() { + + libxml_clear_errors(); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php new file mode 100644 index 000000000000..7d7a54d064ca --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php @@ -0,0 +1,337 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class ACLMethodTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testCallback() { + + $acl = new Plugin(); + $server = new DAV\Server(); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpAcl($server->httpRequest, $server->httpResponse); + + } + + /** + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testNotSupportedByNode() { + + $tree = [ + new DAV\SimpleCollection('test'), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request(); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + function testSuccessSimple() { + + $tree = [ + new MockACLNode('test', []), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request(); + $server->httpRequest->setUrl('/test'); + + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $this->assertFalse($acl->httpACL($server->httpRequest, $server->httpResponse)); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotRecognizedPrincipal + */ + function testUnrecognizedPrincipal() { + + $tree = [ + new MockACLNode('test', []), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotRecognizedPrincipal + */ + function testUnrecognizedPrincipal2() { + + $tree = [ + new MockACLNode('test', []), + new DAV\SimpleCollection('principals', [ + new DAV\SimpleCollection('notaprincipal'), + ]), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notaprincipal</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotSupportedPrivilege + */ + function testUnknownPrivilege() { + + $tree = [ + new MockACLNode('test', []), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:bananas /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NoAbstract + */ + function testAbstractPrivilege() { + + $tree = [ + new MockACLNode('test', []), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->on('getSupportedPrivilegeSet', function($node, &$supportedPrivilegeSet) { + $supportedPrivilegeSet['{DAV:}foo'] = ['abstract' => true]; + }); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:foo /></d:privilege></d:grant> + <d:principal><d:href>/principals/foo/</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege() { + + $oldACL = [ + [ + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ], + ]; + + $tree = [ + new MockACLNode('test', $oldACL), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege2() { + + $oldACL = [ + [ + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ], + ]; + + $tree = [ + new MockACLNode('test', $oldACL), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/foo</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege3() { + + $oldACL = [ + [ + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ], + ]; + + $tree = [ + new MockACLNode('test', $oldACL), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + function testSuccessComplex() { + + $oldACL = [ + [ + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}write', + 'protected' => true, + ], + [ + 'principal' => 'principals/bar', + 'privilege' => '{DAV:}read', + ], + ]; + + $tree = [ + $node = new MockACLNode('test', $oldACL), + new DAV\SimpleCollection('principals', [ + new MockPrincipal('foo', 'principals/foo'), + new MockPrincipal('baz', 'principals/baz'), + ]), + ]; + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL', '/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/foo</d:href></d:principal> + <d:protected /> + </d:ace> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/baz</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin($acl); + + + $this->assertFalse($acl->httpAcl($server->httpRequest, $server->httpResponse)); + + $this->assertEquals([ + [ + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}write', + 'protected' => true, + ], + [ + 'principal' => 'principals/baz', + 'privilege' => '{DAV:}write', + 'protected' => false, + ], + ], $node->getACL()); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AclPrincipalPropSetReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AclPrincipalPropSetReportTest.php new file mode 100644 index 000000000000..338fe36ab0c1 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AclPrincipalPropSetReportTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\HTTP\Request; + +class AclPrincipalPropSetReportTest extends \Sabre\DAVServerTest { + + public $setupACL = true; + public $autoLogin = 'admin'; + + function testReport() { + + $xml = <<<XML +<?xml version="1.0"?> +<acl-principal-prop-set xmlns="DAV:"> + <prop> + <principal-URL /> + <displayname /> + </prop> +</acl-principal-prop-set> +XML; + + $request = new Request('REPORT', '/principals/user1', ['Content-Type' => 'application/xml', 'Depth' => 0]); + $request->setBody($xml); + + $response = $this->request($request, 207); + + $expected = <<<XML +<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:response> + <d:href>/principals/admin/</d:href> + <d:propstat> + <d:prop> + <d:principal-URL><d:href>/principals/admin/</d:href></d:principal-URL> + <d:displayname>Admin</d:displayname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $response->getBodyAsString() + ); + + } + + function testReportDepth1() { + + $xml = <<<XML +<?xml version="1.0"?> +<acl-principal-prop-set xmlns="DAV:"> + <principal-URL /> + <displayname /> +</acl-principal-prop-set> +XML; + + $request = new Request('REPORT', '/principals/user1', ['Content-Type' => 'application/xml', 'Depth' => 1]); + $request->setBody($xml); + + $this->request($request, 400); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php new file mode 100644 index 000000000000..f16693625b2c --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php @@ -0,0 +1,132 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; + +class AllowAccessTest extends \PHPUnit_Framework_TestCase { + + /** + * @var DAV\Server + */ + protected $server; + + function setUp() { + + $nodes = [ + new DAV\Mock\Collection('testdir', [ + 'file1.txt' => 'contents', + ]), + ]; + + $this->server = new DAV\Server($nodes); + $this->server->addPlugin( + new DAV\Auth\Plugin( + new DAV\Auth\Backend\Mock() + ) + ); + // Login + $this->server->getPlugin('auth')->beforeMethod( + new \Sabre\HTTP\Request(), + new \Sabre\HTTP\Response() + ); + $aclPlugin = new Plugin(); + $this->server->addPlugin($aclPlugin); + + } + + function testGet() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testGetDoesntExist() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/foo'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testHEAD() { + + $this->server->httpRequest->setMethod('HEAD'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testOPTIONS() { + + $this->server->httpRequest->setMethod('OPTIONS'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testPUT() { + + $this->server->httpRequest->setMethod('PUT'); + $this->server->httpRequest->setUrl('/testdir/file1.txt'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testPROPPATCH() { + + $this->server->httpRequest->setMethod('PROPPATCH'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testCOPY() { + + $this->server->httpRequest->setMethod('COPY'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testMOVE() { + + $this->server->httpRequest->setMethod('MOVE'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testLOCK() { + + $this->server->httpRequest->setMethod('LOCK'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testBeforeBind() { + + $this->assertTrue($this->server->emit('beforeBind', ['testdir/file'])); + + } + + + function testBeforeUnbind() { + + $this->assertTrue($this->server->emit('beforeUnbind', ['testdir'])); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php new file mode 100644 index 000000000000..ceae9aed059e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php @@ -0,0 +1,215 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; + +class BlockAccessTest extends \PHPUnit_Framework_TestCase { + + /** + * @var DAV\Server + */ + protected $server; + protected $plugin; + + function setUp() { + + $nodes = [ + new DAV\SimpleCollection('testdir'), + ]; + + $this->server = new DAV\Server($nodes); + $this->plugin = new Plugin(); + $this->plugin->setDefaultAcl([]); + $this->server->addPlugin( + new DAV\Auth\Plugin( + new DAV\Auth\Backend\Mock() + ) + ); + // Login + $this->server->getPlugin('auth')->beforeMethod( + new \Sabre\HTTP\Request(), + new \Sabre\HTTP\Response() + ); + $this->server->addPlugin($this->plugin); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testGet() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + function testGetDoesntExist() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/foo'); + + $r = $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + $this->assertTrue($r); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testHEAD() { + + $this->server->httpRequest->setMethod('HEAD'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testOPTIONS() { + + $this->server->httpRequest->setMethod('OPTIONS'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testPUT() { + + $this->server->httpRequest->setMethod('PUT'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testPROPPATCH() { + + $this->server->httpRequest->setMethod('PROPPATCH'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testCOPY() { + + $this->server->httpRequest->setMethod('COPY'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testMOVE() { + + $this->server->httpRequest->setMethod('MOVE'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testACL() { + + $this->server->httpRequest->setMethod('ACL'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testLOCK() { + + $this->server->httpRequest->setMethod('LOCK'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testBeforeBind() { + + $this->server->emit('beforeBind', ['testdir/file']); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testBeforeUnbind() { + + $this->server->emit('beforeUnbind', ['testdir']); + + } + + function testPropFind() { + + $propFind = new DAV\PropFind('testdir', [ + '{DAV:}displayname', + '{DAV:}getcontentlength', + '{DAV:}bar', + '{DAV:}owner', + ]); + + $r = $this->server->emit('propFind', [$propFind, new DAV\SimpleCollection('testdir')]); + $this->assertTrue($r); + + $expected = [ + 200 => [], + 404 => [], + 403 => [ + '{DAV:}displayname' => null, + '{DAV:}getcontentlength' => null, + '{DAV:}bar' => null, + '{DAV:}owner' => null, + ], + ]; + + $this->assertEquals($expected, $propFind->getResultForMultiStatus()); + + } + + function testBeforeGetPropertiesNoListing() { + + $this->plugin->hideNodesFromListings = true; + $propFind = new DAV\PropFind('testdir', [ + '{DAV:}displayname', + '{DAV:}getcontentlength', + '{DAV:}bar', + '{DAV:}owner', + ]); + + $r = $this->server->emit('propFind', [$propFind, new DAV\SimpleCollection('testdir')]); + $this->assertFalse($r); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php new file mode 100644 index 000000000000..1cdf2949f2e5 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class AceConflictTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new AceConflict('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0', 'utf-8'); + $root = $dom->createElementNS('DAV:', 'd:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:no-ace-conflict' => 1, + ]; + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php new file mode 100644 index 000000000000..b13e7722d89d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php @@ -0,0 +1,49 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NeedPrivilegesExceptionTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $uri = 'foo'; + $privileges = [ + '{DAV:}read', + '{DAV:}write', + ]; + $ex = new NeedPrivileges($uri, $privileges); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0', 'utf-8'); + $root = $dom->createElementNS('DAV:', 'd:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:need-privileges' => 1, + '/d:root/d:need-privileges/d:resource' => 2, + '/d:root/d:need-privileges/d:resource/d:href' => 2, + '/d:root/d:need-privileges/d:resource/d:privilege' => 2, + '/d:root/d:need-privileges/d:resource/d:privilege/d:read' => 1, + '/d:root/d:need-privileges/d:resource/d:privilege/d:write' => 1, + ]; + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php new file mode 100644 index 000000000000..f52b17371408 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NoAbstractTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NoAbstract('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0', 'utf-8'); + $root = $dom->createElementNS('DAV:', 'd:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:no-abstract' => 1, + ]; + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php new file mode 100644 index 000000000000..df89aaf84d1f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NotRecognizedPrincipalTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NotRecognizedPrincipal('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0', 'utf-8'); + $root = $dom->createElementNS('DAV:', 'd:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:recognized-principal' => 1, + ]; + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php new file mode 100644 index 000000000000..50623952be72 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NotSupportedPrivilegeTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NotSupportedPrivilege('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0', 'utf-8'); + $root = $dom->createElementNS('DAV:', 'd:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:not-supported-privilege' => 1, + ]; + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php new file mode 100644 index 000000000000..91de64372ab0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php @@ -0,0 +1,317 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class ExpandPropertiesTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $tree = [ + new DAV\Mock\PropertiesCollection('node1', [], [ + '{http://sabredav.org/ns}simple' => 'foo', + '{http://sabredav.org/ns}href' => new DAV\Xml\Property\Href('node2'), + '{DAV:}displayname' => 'Node 1', + ]), + new DAV\Mock\PropertiesCollection('node2', [], [ + '{http://sabredav.org/ns}simple' => 'simple', + '{http://sabredav.org/ns}hreflist' => new DAV\Xml\Property\Href(['node1', 'node3']), + '{DAV:}displayname' => 'Node 2', + ]), + new DAV\Mock\PropertiesCollection('node3', [], [ + '{http://sabredav.org/ns}simple' => 'simple', + '{DAV:}displayname' => 'Node 3', + ]), + ]; + + $fakeServer = new DAV\Server($tree); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->debugExceptions = true; + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + // Anyone can do anything + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ] + ]); + $this->assertTrue($plugin instanceof Plugin); + + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testSimple() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="displayname" /> + <d:property name="foo" namespace="http://www.sabredav.org/NS/2010/nonexistant" /> + <d:property name="simple" namespace="http://sabredav.org/ns" /> + <d:property name="href" namespace="http://sabredav.org/ns" /> +</d:expand-property>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node1', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, 'Incorrect status code received. Full body: ' . $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 2, + '/d:multistatus/d:response/d:propstat/d:prop' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:simple' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:href' => 1, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + $xml->registerXPathNamespace('s', 'http://sabredav.org/ns'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response: ' . $server->httpResponse->body); + + } + + } + + /** + * @depends testSimple + */ + function testExpand() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="href" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node1', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, 'Incorrect response status received. Full response body: ' . $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop/d:displayname' => 1, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + $xml->registerXPathNamespace('s', 'http://sabredav.org/ns'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . ' Full response body: ' . $server->httpResponse->getBodyAsString()); + + } + + } + + /** + * @depends testSimple + */ + function testExpandHrefList() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="hreflist" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node2', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/d:displayname' => 2, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + $xml->registerXPathNamespace('s', 'http://sabredav.org/ns'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result)); + + } + + } + + /** + * @depends testExpand + */ + function testExpandDeep() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="hreflist" namespace="http://sabredav.org/ns"> + <d:property name="href" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node2', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat' => 3, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop' => 3, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop/d:displayname' => 1, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + $xml->registerXPathNamespace('s', 'http://sabredav.org/ns'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result)); + + } + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php new file mode 100644 index 000000000000..af18e7cc057d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php @@ -0,0 +1,44 @@ +<?php + +namespace Sabre\DAVACL\FS; + +class CollectionTest extends FileTest { + + function setUp() { + + $this->path = SABRE_TEMPDIR; + $this->sut = new Collection($this->path, $this->acl, $this->owner); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetChildFile() { + + file_put_contents(SABRE_TEMPDIR . '/file.txt', 'hello'); + $child = $this->sut->getChild('file.txt'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\File', $child); + + $this->assertEquals('file.txt', $child->getName()); + $this->assertEquals($this->acl, $child->getACL()); + $this->assertEquals($this->owner, $child->getOwner()); + + } + + function testGetChildDirectory() { + + mkdir(SABRE_TEMPDIR . '/dir'); + $child = $this->sut->getChild('dir'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\Collection', $child); + + $this->assertEquals('dir', $child->getName()); + $this->assertEquals($this->acl, $child->getACL()); + $this->assertEquals($this->owner, $child->getOwner()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php new file mode 100644 index 000000000000..f57b2fa1d16a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php @@ -0,0 +1,73 @@ +<?php + +namespace Sabre\DAVACL\FS; + +class FileTest extends \PHPUnit_Framework_TestCase { + + /** + * System under test + * + * @var File + */ + protected $sut; + + protected $path = 'foo'; + protected $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + ] + ]; + + protected $owner = 'principals/evert'; + + function setUp() { + + $this->sut = new File($this->path, $this->acl, $this->owner); + + } + + function testGetOwner() { + + $this->assertEquals( + $this->owner, + $this->sut->getOwner() + ); + + } + + function testGetGroup() { + + $this->assertNull( + $this->sut->getGroup() + ); + + } + + function testGetACL() { + + $this->assertEquals( + $this->acl, + $this->sut->getACL() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetAcl() { + + $this->sut->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->sut->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php new file mode 100644 index 000000000000..87cfc83e92c8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php @@ -0,0 +1,116 @@ +<?php + +namespace Sabre\DAVACL\FS; + +use Sabre\DAVACL\PrincipalBackend\Mock as PrincipalBackend; + +class HomeCollectionTest extends \PHPUnit_Framework_TestCase { + + /** + * System under test + * + * @var HomeCollection + */ + protected $sut; + + protected $path; + protected $name = 'thuis'; + + function setUp() { + + $principalBackend = new PrincipalBackend(); + + $this->path = SABRE_TEMPDIR . '/home'; + + $this->sut = new HomeCollection($principalBackend, $this->path); + $this->sut->collectionName = $this->name; + + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetName() { + + $this->assertEquals( + $this->name, + $this->sut->getName() + ); + + } + + function testGetChild() { + + $child = $this->sut->getChild('user1'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\Collection', $child); + $this->assertEquals('user1', $child->getName()); + + $owner = 'principals/user1'; + $acl = [ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ], + ]; + + $this->assertEquals($acl, $child->getACL()); + $this->assertEquals($owner, $child->getOwner()); + + } + + function testGetOwner() { + + $this->assertNull( + $this->sut->getOwner() + ); + + } + + function testGetGroup() { + + $this->assertNull( + $this->sut->getGroup() + ); + + } + + function testGetACL() { + + $acl = [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ] + ]; + + $this->assertEquals( + $acl, + $this->sut->getACL() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetAcl() { + + $this->sut->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->sut->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php new file mode 100644 index 000000000000..2d9744e293fa --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php @@ -0,0 +1,55 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; + +class MockACLNode extends DAV\Node implements IACL { + + public $name; + public $acl; + + function __construct($name, array $acl = []) { + + $this->name = $name; + $this->acl = $acl; + + } + + function getName() { + + return $this->name; + + } + + function getOwner() { + + return null; + + } + + function getGroup() { + + return null; + + } + + function getACL() { + + return $this->acl; + + } + + function setACL(array $acl) { + + $this->acl = $acl; + + } + + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php new file mode 100644 index 000000000000..934906802ed9 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php @@ -0,0 +1,64 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; + +class MockPrincipal extends DAV\Node implements IPrincipal { + + public $name; + public $principalUrl; + public $groupMembership = []; + public $groupMemberSet = []; + + function __construct($name, $principalUrl, array $groupMembership = [], array $groupMemberSet = []) { + + $this->name = $name; + $this->principalUrl = $principalUrl; + $this->groupMembership = $groupMembership; + $this->groupMemberSet = $groupMemberSet; + + } + + function getName() { + + return $this->name; + + } + + function getDisplayName() { + + return $this->getName(); + + } + + function getAlternateUriSet() { + + return []; + + } + + function getPrincipalUrl() { + + return $this->principalUrl; + + } + + function getGroupMemberSet() { + + return $this->groupMemberSet; + + } + + function getGroupMemberShip() { + + return $this->groupMembership; + + } + + function setGroupMemberSet(array $groupMemberSet) { + + $this->groupMemberSet = $groupMemberSet; + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php new file mode 100644 index 000000000000..8552448f54b0 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php @@ -0,0 +1,79 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAVACL/MockACLNode.php'; +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PluginAdminTest extends \PHPUnit_Framework_TestCase { + + public $server; + + function setUp() { + + $principalBackend = new PrincipalBackend\Mock(); + + $tree = [ + new MockACLNode('adminonly', []), + new PrincipalCollection($principalBackend), + ]; + + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $plugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $this->server->addPlugin($plugin); + } + + function testNoAdminAccess() { + + $plugin = new Plugin(); + $this->server->addPlugin($plugin); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'OPTIONS', + 'HTTP_DEPTH' => 1, + 'REQUEST_URI' => '/adminonly', + ]); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(403, $response->status); + + } + + /** + * @depends testNoAdminAccess + */ + function testAdminAccess() { + + $plugin = new Plugin(); + $plugin->adminPrincipals = [ + 'principals/admin', + ]; + $this->server->addPlugin($plugin); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'OPTIONS', + 'HTTP_DEPTH' => 1, + 'REQUEST_URI' => '/adminonly', + ]); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(200, $response->status); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php new file mode 100644 index 000000000000..fb42efba7191 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php @@ -0,0 +1,415 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class PluginPropertiesTest extends \PHPUnit_Framework_TestCase { + + function testPrincipalCollectionSet() { + + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + //Anyone can do anything + $plugin->principalCollectionSet = [ + 'principals1', + 'principals2', + ]; + + $requestedProperties = [ + '{DAV:}principal-collection-set', + ]; + + $server = new DAV\Server(new DAV\SimpleCollection('root')); + $server->addPlugin($plugin); + + $result = $server->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200])); + $this->assertArrayHasKey('{DAV:}principal-collection-set', $result[200]); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}principal-collection-set']); + + $expected = [ + 'principals1/', + 'principals2/', + ]; + + + $this->assertEquals($expected, $result[200]['{DAV:}principal-collection-set']->getHrefs()); + + + } + + function testCurrentUserPrincipal() { + + $fakeServer = new DAV\Server(); + $plugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + $fakeServer->addPlugin($plugin); + + + $requestedProperties = [ + '{DAV:}current-user-principal', + ]; + + $result = $fakeServer->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200])); + $this->assertArrayHasKey('{DAV:}current-user-principal', $result[200]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\Principal', $result[200]['{DAV:}current-user-principal']); + $this->assertEquals(Xml\Property\Principal::UNAUTHENTICATED, $result[200]['{DAV:}current-user-principal']->getType()); + + // This will force the login + $fakeServer->emit('beforeMethod', [$fakeServer->httpRequest, $fakeServer->httpResponse]); + + $result = $fakeServer->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200])); + $this->assertArrayHasKey('{DAV:}current-user-principal', $result[200]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\Principal', $result[200]['{DAV:}current-user-principal']); + $this->assertEquals(Xml\Property\Principal::HREF, $result[200]['{DAV:}current-user-principal']->getType()); + $this->assertEquals('principals/admin/', $result[200]['{DAV:}current-user-principal']->getHref()); + + } + + function testSupportedPrivilegeSet() { + + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + $server = new DAV\Server(); + $server->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}supported-privilege-set', + ]; + + $result = $server->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200])); + $this->assertArrayHasKey('{DAV:}supported-privilege-set', $result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\SupportedPrivilegeSet', $result[200]['{DAV:}supported-privilege-set']); + + $server = new DAV\Server(); + + $prop = $result[200]['{DAV:}supported-privilege-set']; + $result = $server->xml->write('{DAV:}root', $prop); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:supported-privilege' => 1, + '/d:root/d:supported-privilege/d:privilege' => 1, + '/d:root/d:supported-privilege/d:privilege/d:all' => 1, + '/d:root/d:supported-privilege/d:abstract' => 0, + '/d:root/d:supported-privilege/d:supported-privilege' => 2, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege' => 2, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege/d:read' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege/d:write' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege' => 7, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege' => 7, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:read-acl' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:read-current-user-privilege-set' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:write-content' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:write-properties' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:bind' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:unbind' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:unlock' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:abstract' => 0, + ]; + + + // reloading because php dom sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($result); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d', 'DAV:'); + foreach ($xpaths as $xpath => $count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count . ' Full XML: ' . $result); + + } + + } + + function testACL() { + + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + + $nodes = [ + new MockACLNode('foo', [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ] + ]), + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('admin', 'principals/admin'), + ]), + + ]; + + $server = new DAV\Server($nodes); + $server->addPlugin($plugin); + $authPlugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($authPlugin); + + // Force login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $requestedProperties = [ + '{DAV:}acl', + ]; + + $result = $server->getPropertiesForPath('foo', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200]), 'The {DAV:}acl property did not return from the list. Full list: ' . print_r($result, true)); + $this->assertArrayHasKey('{DAV:}acl', $result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\Property\\Acl', $result[200]['{DAV:}acl']); + + } + + function testACLRestrictions() { + + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + + $nodes = [ + new MockACLNode('foo', [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ] + ]), + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('admin', 'principals/admin'), + ]), + + ]; + + $server = new DAV\Server($nodes); + $server->addPlugin($plugin); + $authPlugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($authPlugin); + + // Force login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $requestedProperties = [ + '{DAV:}acl-restrictions', + ]; + + $result = $server->getPropertiesForPath('foo', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1, count($result[200]), 'The {DAV:}acl-restrictions property did not return from the list. Full list: ' . print_r($result, true)); + $this->assertArrayHasKey('{DAV:}acl-restrictions', $result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\AclRestrictions', $result[200]['{DAV:}acl-restrictions']); + + } + + function testAlternateUriSet() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user', 'principals/user'), + ]) + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend()) + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}alternate-URI-set', + ]; + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}alternate-URI-set'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}alternate-URI-set']); + + $this->assertEquals([], $result[200]['{DAV:}alternate-URI-set']->getHrefs()); + + } + + function testPrincipalURL() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user', 'principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend()); + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}principal-URL', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}principal-URL'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}principal-URL']); + + $this->assertEquals('principals/user/', $result[200]['{DAV:}principal-URL']->getHref()); + + } + + function testGroupMemberSet() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user', 'principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend()); + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}group-member-set', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}group-member-set'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}group-member-set']); + + $this->assertEquals([], $result[200]['{DAV:}group-member-set']->getHrefs()); + + } + + function testGroupMemberShip() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user', 'principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $fakeServer->addPlugin($plugin); + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + + $requestedProperties = [ + '{DAV:}group-membership', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}group-membership'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}group-membership']); + + $this->assertEquals([], $result[200]['{DAV:}group-membership']->getHrefs()); + + } + + function testGetDisplayName() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user', 'principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $fakeServer->addPlugin($plugin); + $plugin->setDefaultACL([ + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}all', + ], + ]); + + $requestedProperties = [ + '{DAV:}displayname', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}displayname'])); + + $this->assertEquals('user', $result[200]['{DAV:}displayname']); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php new file mode 100644 index 000000000000..0147e6a61775 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php @@ -0,0 +1,116 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; + +class PluginUpdatePropertiesTest extends \PHPUnit_Framework_TestCase { + + function testUpdatePropertiesPassthrough() { + + $tree = [ + new DAV\SimpleCollection('foo'), + ]; + $server = new DAV\Server($tree); + $server->addPlugin(new DAV\Auth\Plugin()); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', [ + '{DAV:}foo' => 'bar', + ]); + + $expected = [ + '{DAV:}foo' => 403, + ]; + + $this->assertEquals($expected, $result); + + } + + function testRemoveGroupMembers() { + + $tree = [ + new MockPrincipal('foo', 'foo'), + ]; + $server = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $server->addPlugin($plugin); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => null, + ]); + + $expected = [ + '{DAV:}group-member-set' => 204 + ]; + + $this->assertEquals($expected, $result); + $this->assertEquals([], $tree[0]->getGroupMemberSet()); + + } + + function testSetGroupMembers() { + + $tree = [ + new MockPrincipal('foo', 'foo'), + ]; + $server = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $server->addPlugin($plugin); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => new DAV\Xml\Property\Href(['/bar', '/baz'], true), + ]); + + $expected = [ + '{DAV:}group-member-set' => 200 + ]; + + $this->assertEquals($expected, $result); + $this->assertEquals(['bar', 'baz'], $tree[0]->getGroupMemberSet()); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testSetBadValue() { + + $tree = [ + new MockPrincipal('foo', 'foo'), + ]; + $server = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $server->addPlugin($plugin); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => new \StdClass(), + ]); + + } + + function testSetBadNode() { + + $tree = [ + new DAV\SimpleCollection('foo'), + ]; + $server = new DAV\Server($tree); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $server->addPlugin($plugin); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => new DAV\Xml\Property\Href(['/bar', '/baz'], false), + ]); + + $expected = [ + '{DAV:}group-member-set' => 403, + ]; + + $this->assertEquals($expected, $result); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php new file mode 100644 index 000000000000..9fef3018df93 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php @@ -0,0 +1,217 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +use Sabre\DAV; +use Sabre\HTTP; + +abstract class AbstractPDOTest extends \PHPUnit_Framework_TestCase { + + use DAV\DbTestHelperTrait; + + function setUp() { + + $this->dropTables(['principals', 'groupmembers']); + $this->createSchema('principals'); + + $pdo = $this->getPDO(); + + $pdo->query("INSERT INTO principals (uri,email,displayname) VALUES ('principals/user','user@example.org','User')"); + $pdo->query("INSERT INTO principals (uri,email,displayname) VALUES ('principals/group','group@example.org','Group')"); + + $pdo->query("INSERT INTO groupmembers (principal_id,member_id) VALUES (5,4)"); + + } + + + function testConstruct() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testGetPrincipalsByPrefix() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $expected = [ + [ + 'uri' => 'principals/admin', + '{http://sabredav.org/ns}email-address' => 'admin@example.org', + '{DAV:}displayname' => 'Administrator', + ], + [ + 'uri' => 'principals/user', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + '{DAV:}displayname' => 'User', + ], + [ + 'uri' => 'principals/group', + '{http://sabredav.org/ns}email-address' => 'group@example.org', + '{DAV:}displayname' => 'Group', + ], + ]; + + $this->assertEquals($expected, $backend->getPrincipalsByPrefix('principals')); + $this->assertEquals([], $backend->getPrincipalsByPrefix('foo')); + + } + + /** + * @depends testConstruct + */ + function testGetPrincipalByPath() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $expected = [ + 'id' => 4, + 'uri' => 'principals/user', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + '{DAV:}displayname' => 'User', + ]; + + $this->assertEquals($expected, $backend->getPrincipalByPath('principals/user')); + $this->assertEquals(null, $backend->getPrincipalByPath('foo')); + + } + + function testGetGroupMemberSet() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $expected = ['principals/user']; + + $this->assertEquals($expected, $backend->getGroupMemberSet('principals/group')); + + } + + function testGetGroupMembership() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $expected = ['principals/group']; + + $this->assertEquals($expected, $backend->getGroupMembership('principals/user')); + + } + + function testSetGroupMemberSet() { + + $pdo = $this->getPDO(); + + // Start situation + $backend = new PDO($pdo); + $this->assertEquals(['principals/user'], $backend->getGroupMemberSet('principals/group')); + + // Removing all principals + $backend->setGroupMemberSet('principals/group', []); + $this->assertEquals([], $backend->getGroupMemberSet('principals/group')); + + // Adding principals again + $backend->setGroupMemberSet('principals/group', ['principals/user']); + $this->assertEquals(['principals/user'], $backend->getGroupMemberSet('principals/group')); + + + } + + function testSearchPrincipals() { + + $pdo = $this->getPDO(); + + $backend = new PDO($pdo); + + $result = $backend->searchPrincipals('principals', ['{DAV:}blabla' => 'foo']); + $this->assertEquals([], $result); + + $result = $backend->searchPrincipals('principals', ['{DAV:}displayname' => 'ou']); + $this->assertEquals(['principals/group'], $result); + + $result = $backend->searchPrincipals('principals', ['{DAV:}displayname' => 'UsEr', '{http://sabredav.org/ns}email-address' => 'USER@EXAMPLE']); + $this->assertEquals(['principals/user'], $result); + + $result = $backend->searchPrincipals('mom', ['{DAV:}displayname' => 'UsEr', '{http://sabredav.org/ns}email-address' => 'USER@EXAMPLE']); + $this->assertEquals([], $result); + + } + + function testUpdatePrincipal() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'pietje', + ]); + + $backend->updatePrincipal('principals/user', $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $this->assertEquals([ + 'id' => 4, + 'uri' => 'principals/user', + '{DAV:}displayname' => 'pietje', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + ], $backend->getPrincipalByPath('principals/user')); + + } + + function testUpdatePrincipalUnknownField() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'pietje', + '{DAV:}unknown' => 'foo', + ]); + + $backend->updatePrincipal('principals/user', $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}displayname' => 424, + '{DAV:}unknown' => 403 + ], $propPatch->getResult()); + + $this->assertEquals([ + 'id' => '4', + 'uri' => 'principals/user', + '{DAV:}displayname' => 'User', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + ], $backend->getPrincipalByPath('principals/user')); + + } + + function testFindByUriUnknownScheme() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $this->assertNull($backend->findByUri('http://foo', 'principals')); + + } + + + function testFindByUri() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $this->assertEquals( + 'principals/user', + $backend->findByUri('mailto:user@example.org', 'principals') + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php new file mode 100644 index 000000000000..1464f4c26abf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php @@ -0,0 +1,168 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +class Mock extends AbstractBackend { + + public $groupMembers = []; + public $principals; + + function __construct(array $principals = null) { + + $this->principals = $principals; + + if (is_null($principals)) { + + $this->principals = [ + [ + 'uri' => 'principals/user1', + '{DAV:}displayname' => 'User 1', + '{http://sabredav.org/ns}email-address' => 'user1.sabredav@sabredav.org', + '{http://sabredav.org/ns}vcard-url' => 'addressbooks/user1/book1/vcard1.vcf', + ], + [ + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Admin', + ], + [ + 'uri' => 'principals/user2', + '{DAV:}displayname' => 'User 2', + '{http://sabredav.org/ns}email-address' => 'user2.sabredav@sabredav.org', + ], + ]; + + } + + } + + function getPrincipalsByPrefix($prefix) { + + $prefix = trim($prefix, '/'); + if ($prefix) $prefix .= '/'; + $return = []; + + foreach ($this->principals as $principal) { + + if ($prefix && strpos($principal['uri'], $prefix) !== 0) continue; + + $return[] = $principal; + + } + + return $return; + + } + + function addPrincipal(array $principal) { + + $this->principals[] = $principal; + + } + + function getPrincipalByPath($path) { + + foreach ($this->getPrincipalsByPrefix('principals') as $principal) { + if ($principal['uri'] === $path) return $principal; + } + + } + + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + + $matches = []; + foreach ($this->getPrincipalsByPrefix($prefixPath) as $principal) { + + foreach ($searchProperties as $key => $value) { + + if (!isset($principal[$key])) { + continue 2; + } + if (mb_stripos($principal[$key], $value, 0, 'UTF-8') === false) { + continue 2; + } + + // We have a match for this searchProperty! + if ($test === 'allof') { + continue; + } else { + break; + } + + } + $matches[] = $principal['uri']; + + } + return $matches; + + } + + function getGroupMemberSet($path) { + + return isset($this->groupMembers[$path]) ? $this->groupMembers[$path] : []; + + } + + function getGroupMembership($path) { + + $membership = []; + foreach ($this->groupMembers as $group => $members) { + if (in_array($path, $members)) $membership[] = $group; + } + return $membership; + + } + + function setGroupMemberSet($path, array $members) { + + $this->groupMembers[$path] = $members; + + } + + /** + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param string $path + * @param \Sabre\DAV\PropPatch $propPatch + */ + function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { + + $value = null; + foreach ($this->principals as $principalIndex => $value) { + if ($value['uri'] === $path) { + $principal = $value; + break; + } + } + if (!$principal) return; + + $propPatch->handleRemaining(function($mutations) use ($principal, $principalIndex) { + + foreach ($mutations as $prop => $value) { + + if (is_null($value) && isset($principal[$prop])) { + unset($principal[$prop]); + } else { + $principal[$prop] = $value; + } + + } + + $this->principals[$principalIndex] = $principal; + + return true; + + }); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php new file mode 100644 index 000000000000..8779eb69f539 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +class PDOMySQLTest extends AbstractPDOTest { + + public $driver = 'mysql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOPgSqlTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOPgSqlTest.php new file mode 100644 index 000000000000..302616e785cf --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOPgSqlTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +class PDOPgSqlTest extends AbstractPDOTest { + + public $driver = 'pgsql'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php new file mode 100644 index 000000000000..48454981d54e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php @@ -0,0 +1,9 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +class PDOSqliteTest extends AbstractPDOTest { + + public $driver = 'sqlite'; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php new file mode 100644 index 000000000000..bcf78821bc9d --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\DAVACL; + +class PrincipalCollectionTest extends \PHPUnit_Framework_TestCase { + + function testBasic() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $this->assertTrue($pc instanceof PrincipalCollection); + + $this->assertEquals('principals', $pc->getName()); + + } + + /** + * @depends testBasic + */ + function testGetChildren() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + + $children = $pc->getChildren(); + $this->assertTrue(is_array($children)); + + foreach ($children as $child) { + $this->assertTrue($child instanceof IPrincipal); + } + + } + + /** + * @depends testBasic + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testGetChildrenDisable() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $pc->disableListing = true; + + $children = $pc->getChildren(); + + } + + function testFindByUri() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $this->assertEquals('principals/user1', $pc->findByUri('mailto:user1.sabredav@sabredav.org')); + $this->assertNull($pc->findByUri('mailto:fake.user.sabredav@sabredav.org')); + $this->assertNull($pc->findByUri('')); + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalMatchTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalMatchTest.php new file mode 100644 index 000000000000..427e37972db8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalMatchTest.php @@ -0,0 +1,123 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\HTTP\Request; + +class PrincipalMatchTest extends \Sabre\DAVServerTest { + + public $setupACL = true; + public $autoLogin = 'user1'; + + function testPrincipalMatch() { + + $xml = <<<XML +<?xml version="1.0"?> +<principal-match xmlns="DAV:"> + <self /> +</principal-match> +XML; + + $request = new Request('REPORT', '/principals', ['Content-Type' => 'application/xml']); + $request->setBody($xml); + + $response = $this->request($request, 207); + + $expected = <<<XML +<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:status>HTTP/1.1 200 OK</d:status> + <d:href>/principals/user1</d:href> + <d:propstat> + <d:prop/> + <d:status>HTTP/1.1 418 I'm a teapot</d:status> + </d:propstat> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $response->getBodyAsString() + ); + + } + + function testPrincipalMatchProp() { + + $xml = <<<XML +<?xml version="1.0"?> +<principal-match xmlns="DAV:"> + <self /> + <prop> + <resourcetype /> + </prop> +</principal-match> +XML; + + $request = new Request('REPORT', '/principals', ['Content-Type' => 'application/xml']); + $request->setBody($xml); + + $response = $this->request($request, 207); + + $expected = <<<XML +<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:status>HTTP/1.1 200 OK</d:status> + <d:href>/principals/user1/</d:href> + <d:propstat> + <d:prop> + <d:resourcetype><d:principal/></d:resourcetype> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $response->getBodyAsString() + ); + + } + + function testPrincipalMatchPrincipalProperty() { + + $xml = <<<XML +<?xml version="1.0"?> +<principal-match xmlns="DAV:"> + <principal-property> + <principal-URL /> + </principal-property> + <prop> + <resourcetype /> + </prop> +</principal-match> +XML; + + $request = new Request('REPORT', '/principals', ['Content-Type' => 'application/xml']); + $request->setBody($xml); + + $response = $this->request($request, 207); + + $expected = <<<XML +<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:status>HTTP/1.1 200 OK</d:status> + <d:href>/principals/user1/</d:href> + <d:propstat> + <d:prop> + <d:resourcetype><d:principal/></d:resourcetype> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> +</d:multistatus> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $response->getBodyAsString() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php new file mode 100644 index 000000000000..60e156d9a0a2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php @@ -0,0 +1,397 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PrincipalPropertySearchTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $backend = new PrincipalBackend\Mock(); + + $dir = new DAV\SimpleCollection('root'); + $principals = new PrincipalCollection($backend); + $dir->addChild($principals); + + $fakeServer = new DAV\Server($dir); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $fakeServer->debugExceptions = true; + $plugin = new MockPlugin(); + $plugin->allowAccessToNodesWithoutACL = true; + $plugin->allowUnauthenticatedAccess = false; + + $this->assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testDepth1() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '1', + 'REQUEST_URI' => '/principals', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->getStatus(), $server->httpResponse->getBodyAsString()); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + } + + + function testUnknownSearchField() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:yourmom /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->getStatus(), "Full body: " . $server->httpResponse->getBodyAsString()); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ], $server->httpResponse->getHeaders()); + + } + + function testCorrect() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 2, + '/d:multistatus/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat' => 4, + '/d:multistatus/d:response/d:propstat/d:prop' => 4, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 2, + '/d:multistatus/d:response/d:propstat/d:status' => 4, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + + function testAND() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:property-search> + <d:prop> + <d:foo /> + </d:prop> + <d:match>bar</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 0, + '/d:multistatus/d:response/d:href' => 0, + '/d:multistatus/d:response/d:propstat' => 0, + '/d:multistatus/d:response/d:propstat/d:prop' => 0, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 0, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 0, + '/d:multistatus/d:response/d:propstat/d:status' => 0, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + function testOR() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:" test="anyof"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:property-search> + <d:prop> + <d:foo /> + </d:prop> + <d:match>bar</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 2, + '/d:multistatus/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat' => 4, + '/d:multistatus/d:response/d:propstat/d:prop' => 4, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 2, + '/d:multistatus/d:response/d:propstat/d:status' => 4, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + function testWrongUri() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:multistatus', + '/d:multistatus/d:response' => 0, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } +} + +class MockPlugin extends Plugin { + + function getCurrentUserPrivilegeSet($node) { + + return [ + '{DAV:}read', + '{DAV:}write', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php new file mode 100644 index 000000000000..fa1314d108c2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php @@ -0,0 +1,140 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PrincipalSearchPropertySetTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $backend = new PrincipalBackend\Mock(); + + $dir = new DAV\SimpleCollection('root'); + $principals = new PrincipalCollection($backend); + $dir->addChild($principals); + + $fakeServer = new DAV\Server($dir); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $this->assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testDepth1() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:" />'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '1', + 'REQUEST_URI' => '/principals', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->status); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + } + + function testDepthIncorrectXML() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:"><d:ohell /></d:principal-search-property-set>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + } + + function testCorrect() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:"/>'; + + $serverVars = [ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(200, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], $server->httpResponse->getHeaders()); + + + $check = [ + '/d:principal-search-property-set', + '/d:principal-search-property-set/d:principal-search-property' => 2, + '/d:principal-search-property-set/d:principal-search-property/d:prop' => 2, + '/d:principal-search-property-set/d:principal-search-property/d:prop/d:displayname' => 1, + '/d:principal-search-property-set/d:principal-search-property/d:prop/s:email-address' => 1, + '/d:principal-search-property-set/d:principal-search-property/d:description' => 2, + ]; + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d', 'DAV:'); + $xml->registerXPathNamespace('s', 'http://sabredav.org/ns'); + foreach ($check as $v1 => $v2) { + + $xpath = is_int($v1) ? $v2 : $v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count, count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php new file mode 100644 index 000000000000..20622ad1757e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php @@ -0,0 +1,208 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class PrincipalTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertTrue($principal instanceof Principal); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testConstructNoUri() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, []); + + } + + function testGetName() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals('admin', $principal->getName()); + + } + + function testGetDisplayName() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals('admin', $principal->getDisplayname()); + + $principal = new Principal($principalBackend, [ + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin' + ]); + $this->assertEquals('Mr. Admin', $principal->getDisplayname()); + + } + + function testGetProperties() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, [ + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin', + '{http://www.example.org/custom}custom' => 'Custom', + '{http://sabredav.org/ns}email-address' => 'admin@example.org', + ]); + + $keys = [ + '{DAV:}displayname', + '{http://www.example.org/custom}custom', + '{http://sabredav.org/ns}email-address', + ]; + $props = $principal->getProperties($keys); + + foreach ($keys as $key) $this->assertArrayHasKey($key, $props); + + $this->assertEquals('Mr. Admin', $props['{DAV:}displayname']); + + $this->assertEquals('admin@example.org', $props['{http://sabredav.org/ns}email-address']); + } + + function testUpdateProperties() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + + $propPatch = new DAV\PropPatch(['{DAV:}yourmom' => 'test']); + + $result = $principal->propPatch($propPatch); + $result = $propPatch->commit(); + $this->assertTrue($result); + + } + + function testGetPrincipalUrl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals('principals/admin', $principal->getPrincipalUrl()); + + } + + function testGetAlternateUriSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, [ + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin', + '{http://www.example.org/custom}custom' => 'Custom', + '{http://sabredav.org/ns}email-address' => 'admin@example.org', + '{DAV:}alternate-URI-set' => [ + 'mailto:admin+1@example.org', + 'mailto:admin+2@example.org', + 'mailto:admin@example.org', + ], + ]); + + $expected = [ + 'mailto:admin+1@example.org', + 'mailto:admin+2@example.org', + 'mailto:admin@example.org', + ]; + + $this->assertEquals($expected, $principal->getAlternateUriSet()); + + } + function testGetAlternateUriSetEmpty() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, [ + 'uri' => 'principals/admin', + ]); + + $expected = []; + + $this->assertEquals($expected, $principal->getAlternateUriSet()); + + } + + function testGetGroupMemberSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals([], $principal->getGroupMemberSet()); + + } + function testGetGroupMembership() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals([], $principal->getGroupMembership()); + + } + + function testSetGroupMemberSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $principal->setGroupMemberSet(['principals/foo']); + + $this->assertEquals([ + 'principals/admin' => ['principals/foo'], + ], $principalBackend->groupMembers); + + } + + function testGetOwner() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals('principals/admin', $principal->getOwner()); + + } + + function testGetGroup() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertNull($principal->getGroup()); + + } + + function testGetACl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertEquals([ + [ + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ] + ], $principal->getACL()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetACl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $principal->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, ['uri' => 'principals/admin']); + $this->assertNull($principal->getSupportedPrivilegeSet()); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php new file mode 100644 index 000000000000..2de0ba6a853f --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php @@ -0,0 +1,321 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAVACL/MockPrincipal.php'; +require_once 'Sabre/DAVACL/MockACLNode.php'; + +class SimplePluginTest extends \PHPUnit_Framework_TestCase { + + function testValues() { + + $aclPlugin = new Plugin(); + $this->assertEquals('acl', $aclPlugin->getPluginName()); + $this->assertEquals( + ['access-control', 'calendarserver-principal-property-search'], + $aclPlugin->getFeatures() + ); + + $this->assertEquals( + [ + '{DAV:}expand-property', + '{DAV:}principal-match', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set' + ], + $aclPlugin->getSupportedReportSet('')); + + $this->assertEquals(['ACL'], $aclPlugin->getMethods('')); + + + $this->assertEquals( + 'acl', + $aclPlugin->getPluginInfo()['name'] + ); + } + + function testGetFlatPrivilegeSet() { + + $expected = [ + '{DAV:}all' => [ + 'privilege' => '{DAV:}all', + 'abstract' => false, + 'aggregates' => [ + '{DAV:}read', + '{DAV:}write', + ], + 'concrete' => '{DAV:}all', + ], + '{DAV:}read' => [ + 'privilege' => '{DAV:}read', + 'abstract' => false, + 'aggregates' => [ + '{DAV:}read-acl', + '{DAV:}read-current-user-privilege-set', + ], + 'concrete' => '{DAV:}read', + ], + '{DAV:}read-acl' => [ + 'privilege' => '{DAV:}read-acl', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}read-acl', + ], + '{DAV:}read-current-user-privilege-set' => [ + 'privilege' => '{DAV:}read-current-user-privilege-set', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}read-current-user-privilege-set', + ], + '{DAV:}write' => [ + 'privilege' => '{DAV:}write', + 'abstract' => false, + 'aggregates' => [ + '{DAV:}write-properties', + '{DAV:}write-content', + '{DAV:}unlock', + '{DAV:}bind', + '{DAV:}unbind', + ], + 'concrete' => '{DAV:}write', + ], + '{DAV:}write-properties' => [ + 'privilege' => '{DAV:}write-properties', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}write-properties', + ], + '{DAV:}write-content' => [ + 'privilege' => '{DAV:}write-content', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}write-content', + ], + '{DAV:}unlock' => [ + 'privilege' => '{DAV:}unlock', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}unlock', + ], + '{DAV:}bind' => [ + 'privilege' => '{DAV:}bind', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}bind', + ], + '{DAV:}unbind' => [ + 'privilege' => '{DAV:}unbind', + 'abstract' => false, + 'aggregates' => [], + 'concrete' => '{DAV:}unbind', + ], + + ]; + + $plugin = new Plugin(); + $plugin->allowUnauthenticatedAccess = false; + $server = new DAV\Server(); + $server->addPlugin($plugin); + $this->assertEquals($expected, $plugin->getFlatPrivilegeSet('')); + + } + + function testCurrentUserPrincipalsNotLoggedIn() { + + $acl = new Plugin(); + $acl->allowUnauthenticatedAccess = false; + $server = new DAV\Server(); + $server->addPlugin($acl); + + $this->assertEquals([], $acl->getCurrentUserPrincipals()); + + } + + function testCurrentUserPrincipalsSimple() { + + $tree = [ + + new DAV\SimpleCollection('principals', [ + new MockPrincipal('admin', 'principals/admin'), + ]) + + ]; + + $acl = new Plugin(); + $acl->allowUnauthenticatedAccess = false; + $server = new DAV\Server($tree); + $server->addPlugin($acl); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->assertEquals(['principals/admin'], $acl->getCurrentUserPrincipals()); + + } + + function testCurrentUserPrincipalsGroups() { + + $tree = [ + + new DAV\SimpleCollection('principals', [ + new MockPrincipal('admin', 'principals/admin', ['principals/administrators', 'principals/everyone']), + new MockPrincipal('administrators', 'principals/administrators', ['principals/groups'], ['principals/admin']), + new MockPrincipal('everyone', 'principals/everyone', [], ['principals/admin']), + new MockPrincipal('groups', 'principals/groups', [], ['principals/administrators']), + ]) + + ]; + + $acl = new Plugin(); + $acl->allowUnauthenticatedAccess = false; + $server = new DAV\Server($tree); + $server->addPlugin($acl); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $expected = [ + 'principals/admin', + 'principals/administrators', + 'principals/everyone', + 'principals/groups', + ]; + + $this->assertEquals($expected, $acl->getCurrentUserPrincipals()); + + // The second one should trigger the cache and be identical + $this->assertEquals($expected, $acl->getCurrentUserPrincipals()); + + } + + function testGetACL() { + + $acl = [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ], + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ], + ]; + + + $tree = [ + new MockACLNode('foo', $acl), + ]; + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $server->addPlugin($aclPlugin); + + $this->assertEquals($acl, $aclPlugin->getACL('foo')); + + } + + function testGetCurrentUserPrivilegeSet() { + + $acl = [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ], + [ + 'principal' => 'principals/user1', + 'privilege' => '{DAV:}read', + ], + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ], + ]; + + + $tree = [ + new MockACLNode('foo', $acl), + + new DAV\SimpleCollection('principals', [ + new MockPrincipal('admin', 'principals/admin'), + ]), + + ]; + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $server->addPlugin($aclPlugin); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $expected = [ + '{DAV:}write', + '{DAV:}write-properties', + '{DAV:}write-content', + '{DAV:}unlock', + '{DAV:}write-acl', + '{DAV:}read', + '{DAV:}read-acl', + '{DAV:}read-current-user-privilege-set', + ]; + + $this->assertEquals($expected, $aclPlugin->getCurrentUserPrivilegeSet('foo')); + + } + + function testCheckPrivileges() { + + $acl = [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ], + [ + 'principal' => 'principals/user1', + 'privilege' => '{DAV:}read', + ], + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ], + ]; + + + $tree = [ + new MockACLNode('foo', $acl), + + new DAV\SimpleCollection('principals', [ + new MockPrincipal('admin', 'principals/admin'), + ]), + + ]; + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $aclPlugin->allowUnauthenticatedAccess = false; + $server->addPlugin($aclPlugin); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock()); + $server->addPlugin($auth); + + //forcing login + //$auth->beforeMethod('GET','/'); + + $this->assertFalse($aclPlugin->checkPrivileges('foo', ['{DAV:}read'], Plugin::R_PARENT, false)); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php new file mode 100644 index 000000000000..7b9853fe5fe7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php @@ -0,0 +1,342 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; + +class ACLTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $acl = new Acl([]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\ACL', $acl); + + } + + function testSerializeEmpty() { + + $acl = new Acl([]); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" />'; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testSerialize() { + + $privileges = [ + [ + 'principal' => 'principals/evert', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + ]; + + $acl = new Acl($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl, '/'); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:ace> + <d:principal> + <d:href>/principals/evert/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:href>/principals/foo/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:read/> + </d:privilege> + </d:grant> + <d:protected/> + </d:ace> +</d:root> +'; + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testSerializeSpecialPrincipals() { + + $privileges = [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}unauthenticated', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}write', + ], + + ]; + + $acl = new Acl($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl, '/'); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:ace> + <d:principal> + <d:authenticated/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:unauthenticated/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:all/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> +</d:root> +'; + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testUnserialize() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:principal> + <d:href>/principals/evert/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:href>/principals/foo/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:read/> + </d:privilege> + </d:grant> + <d:protected/> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + $result = $result['value']; + + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\Acl', $result); + + $expected = [ + [ + 'principal' => '/principals/evert/', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '/principals/foo/', + 'protected' => true, + 'privilege' => '{DAV:}read', + ], + ]; + + $this->assertEquals($expected, $result->getPrivileges()); + + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testUnserializeNoPrincipal() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> +</d:root> +'; + + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + + } + + function testUnserializeOtherPrincipal() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:authenticated /></d:principal> + </d:ace> + <d:ace> + <d:grant> + <d:ignoreme /> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:unauthenticated /></d:principal> + </d:ace> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:all /></d:principal> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + $result = $result['value']; + + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\Acl', $result); + + $expected = [ + [ + 'principal' => '{DAV:}authenticated', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}unauthenticated', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}all', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + ]; + + $this->assertEquals($expected, $result->getPrivileges()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testUnserializeDeny() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ignore-me /> + <d:ace> + <d:deny> + <d:privilege> + <d:write/> + </d:privilege> + </d:deny> + <d:principal><d:href>/principals/evert</d:href></d:principal> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + + } + + function testToHtml() { + + $privileges = [ + [ + 'principal' => 'principals/evert', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => 'principals/foo', + 'privilege' => '{http://example.org/ns}read', + 'protected' => true, + ], + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}write', + ], + ]; + + $acl = new Acl($privileges); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = + '<table>' . + '<tr><th>Principal</th><th>Privilege</th><th></th></tr>' . + '<tr><td><a href="/base/principals/evert">/base/principals/evert</a></td><td><span title="{DAV:}write">d:write</span></td><td></td></tr>' . + '<tr><td><a href="/base/principals/foo">/base/principals/foo</a></td><td><span title="{http://example.org/ns}read">{http://example.org/ns}read</span></td><td>(protected)</td></tr>' . + '<tr><td><span title="{DAV:}authenticated">d:authenticated</span></td><td><span title="{DAV:}write">d:write</span></td><td></td></tr>' . + '</table>'; + + $this->assertEquals($expected, $acl->toHtml($html)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php new file mode 100644 index 000000000000..6d8b83a1285b --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +class AclRestrictionsTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $prop = new AclRestrictions(); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\AclRestrictions', $prop); + + } + + function testSerialize() { + + $prop = new AclRestrictions(); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $prop); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"><d:grant-only/><d:no-invert/></d:root>'; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php new file mode 100644 index 000000000000..d6e6b2d193f2 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; +use Sabre\Xml\Reader; + +class CurrentUserPrivilegeSetTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $privileges = [ + '{DAV:}read', + '{DAV:}write', + ]; + $prop = new CurrentUserPrivilegeSet($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $prop); + + $expected = <<<XML +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:privilege> + <d:read /> + </d:privilege> + <d:privilege> + <d:write /> + </d:privilege> +</d:root> +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testUnserialize() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:privilege> + <d:write-properties /> + </d:privilege> + <d:ignoreme /> + <d:privilege> + <d:read /> + </d:privilege> +</d:root> +'; + + $result = $this->parse($source); + $this->assertTrue($result->has('{DAV:}read')); + $this->assertTrue($result->has('{DAV:}write-properties')); + $this->assertFalse($result->has('{DAV:}bind')); + + } + + function parse($xml) { + + $reader = new Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\\DAVACL\\Xml\\Property\\CurrentUserPrivilegeSet'; + $reader->xml($xml); + $result = $reader->parse(); + return $result['value']; + + } + + function testToHtml() { + + $privileges = ['{DAV:}read', '{DAV:}write']; + + $prop = new CurrentUserPrivilegeSet($privileges); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = + '<span title="{DAV:}read">d:read</span>, ' . + '<span title="{DAV:}write">d:write</span>'; + + $this->assertEquals($expected, $prop->toHtml($html)); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php new file mode 100644 index 000000000000..876d1073aaa7 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php @@ -0,0 +1,191 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; +use Sabre\Xml\Reader; + +class PrincipalTest extends \PHPUnit_Framework_TestCase { + + function testSimple() { + + $principal = new Principal(Principal::UNAUTHENTICATED); + $this->assertEquals(Principal::UNAUTHENTICATED, $principal->getType()); + $this->assertNull($principal->getHref()); + + $principal = new Principal(Principal::AUTHENTICATED); + $this->assertEquals(Principal::AUTHENTICATED, $principal->getType()); + $this->assertNull($principal->getHref()); + + $principal = new Principal(Principal::HREF, 'admin'); + $this->assertEquals(Principal::HREF, $principal->getType()); + $this->assertEquals('admin/', $principal->getHref()); + + } + + /** + * @depends testSimple + * @expectedException Sabre\DAV\Exception + */ + function testNoHref() { + + $principal = new Principal(Principal::HREF); + + } + + /** + * @depends testSimple + */ + function testSerializeUnAuthenticated() { + + $prin = new Principal(Principal::UNAUTHENTICATED); + + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:unauthenticated/> +</d:principal>', $xml); + + } + + + /** + * @depends testSerializeUnAuthenticated + */ + function testSerializeAuthenticated() { + + $prin = new Principal(Principal::AUTHENTICATED); + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:authenticated/> +</d:principal>', $xml); + + } + + + /** + * @depends testSerializeUnAuthenticated + */ + function testSerializeHref() { + + $prin = new Principal(Principal::HREF, 'principals/admin'); + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin, '/'); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:href>/principals/admin/</d:href> +</d:principal>', $xml); + + } + + function testUnserializeHref() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +'<d:href>/principals/admin</d:href>' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::HREF, $principal->getType()); + $this->assertEquals('/principals/admin/', $principal->getHref()); + + } + + function testUnserializeAuthenticated() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:authenticated />' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::AUTHENTICATED, $principal->getType()); + + } + + function testUnserializeUnauthenticated() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:unauthenticated />' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::UNAUTHENTICATED, $principal->getType()); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testUnserializeUnknown() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:foo />' . +'</d:principal>'; + + $this->parse($xml); + + } + + function parse($xml) { + + $reader = new Reader(); + $reader->elementMap['{DAV:}principal'] = 'Sabre\\DAVACL\\Xml\\Property\\Principal'; + $reader->xml($xml); + $result = $reader->parse(); + return $result['value']; + + } + + /** + * @depends testSimple + * @dataProvider htmlProvider + */ + function testToHtml($principal, $output) { + + $html = $principal->toHtml(new HtmlOutputHelper('/', [])); + + $this->assertXmlStringEqualsXmlString( + $output, + $html + ); + + } + + /** + * Provides data for the html tests + * + * @return array + */ + function htmlProvider() { + + return [ + [ + new Principal(Principal::UNAUTHENTICATED), + '<em>unauthenticated</em>', + ], + [ + new Principal(Principal::AUTHENTICATED), + '<em>authenticated</em>', + ], + [ + new Principal(Principal::ALL), + '<em>all</em>', + ], + [ + new Principal(Principal::HREF, 'principals/admin'), + '<a href="/principals/admin/">/principals/admin/</a>', + ], + + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php new file mode 100644 index 000000000000..749d349fc7d8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php @@ -0,0 +1,103 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; + +class SupportedPrivilegeSetTest extends \PHPUnit_Framework_TestCase { + + function testSimple() { + + $prop = new SupportedPrivilegeSet([ + 'privilege' => '{DAV:}all', + ]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\SupportedPrivilegeSet', $prop); + + } + + + /** + * @depends testSimple + */ + function testSerializeSimple() { + + $prop = new SupportedPrivilegeSet([]); + + $xml = (new DAV\Server())->xml->write('{DAV:}supported-privilege-set', $prop); + + $this->assertXmlStringEqualsXmlString(' +<d:supported-privilege-set xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:supported-privilege> + <d:privilege> + <d:all/> + </d:privilege> + </d:supported-privilege> +</d:supported-privilege-set>', $xml); + + } + + /** + * @depends testSimple + */ + function testSerializeAggregate() { + + $prop = new SupportedPrivilegeSet([ + '{DAV:}read' => [], + '{DAV:}write' => [ + 'description' => 'booh', + ] + ]); + + $xml = (new DAV\Server())->xml->write('{DAV:}supported-privilege-set', $prop); + + $this->assertXmlStringEqualsXmlString(' +<d:supported-privilege-set xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:supported-privilege> + <d:privilege> + <d:all/> + </d:privilege> + <d:supported-privilege> + <d:privilege> + <d:read/> + </d:privilege> + </d:supported-privilege> + <d:supported-privilege> + <d:privilege> + <d:write/> + </d:privilege> + <d:description>booh</d:description> + </d:supported-privilege> + </d:supported-privilege> +</d:supported-privilege-set>', $xml); + + } + + function testToHtml() { + + $prop = new SupportedPrivilegeSet([ + '{DAV:}read' => [], + '{DAV:}write' => [ + 'description' => 'booh', + ], + ]); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = <<<HTML +<ul class="tree"><li><span title="{DAV:}all">d:all</span> +<ul> +<li><span title="{DAV:}read">d:read</span></li> +<li><span title="{DAV:}write">d:write</span> booh</li> +</ul></li> +</ul> + +HTML; + + $this->assertEquals($expected, $prop->toHtml($html)); + + } +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/AclPrincipalPropSetReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/AclPrincipalPropSetReportTest.php new file mode 100644 index 000000000000..bae682f21553 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/AclPrincipalPropSetReportTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAVACL\Xml\Request; + +class AclPrincipalPropSetReportTest extends \Sabre\DAV\Xml\XmlTest { + + protected $elementMap = [ + + '{DAV:}acl-principal-prop-set' => 'Sabre\DAVACL\Xml\Request\AclPrincipalPropSetReport', + + ]; + + function testDeserialize() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> +<D:acl-principal-prop-set xmlns:D="DAV:"> + <D:prop> + <D:displayname/> + </D:prop> +</D:acl-principal-prop-set> +XML; + + $result = $this->parse($xml); + + $this->assertEquals(['{DAV:}displayname'], $result['value']->properties); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/PrincipalMatchReportTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/PrincipalMatchReportTest.php new file mode 100644 index 000000000000..1431ab34939a --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Request/PrincipalMatchReportTest.php @@ -0,0 +1,51 @@ +<?php + +namespace Sabre\DAVACL\Xml\Request; + +class PrincipalMatchReportTest extends \Sabre\DAV\Xml\XmlTest { + + protected $elementMap = [ + + '{DAV:}principal-match' => 'Sabre\DAVACL\Xml\Request\PrincipalMatchReport', + + ]; + + function testDeserialize() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> + <D:principal-match xmlns:D="DAV:"> + <D:principal-property> + <D:owner/> + </D:principal-property> + </D:principal-match> +XML; + + $result = $this->parse($xml); + + $this->assertEquals(PrincipalMatchReport::PRINCIPAL_PROPERTY, $result['value']->type); + $this->assertEquals('{DAV:}owner', $result['value']->principalProperty); + + } + + function testDeserializeSelf() { + + $xml = <<<XML +<?xml version="1.0" encoding="utf-8" ?> + <D:principal-match xmlns:D="DAV:"> + <D:self /> + <D:prop> + <D:foo /> + </D:prop> + </D:principal-match> +XML; + + $result = $this->parse($xml); + + $this->assertEquals(PrincipalMatchReport::SELF, $result['value']->type); + $this->assertNull($result['value']->principalProperty); + $this->assertEquals(['{DAV:}foo'], $result['value']->properties); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/DAVServerTest.php b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVServerTest.php new file mode 100644 index 000000000000..35f240d23fa8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/DAVServerTest.php @@ -0,0 +1,306 @@ +<?php + +namespace Sabre; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; +use Sabre\HTTP\Sapi; + +/** + * This class may be used as a basis for other webdav-related unittests. + * + * This class is supposed to provide a reasonably big framework to quickly get + * a testing environment running. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class DAVServerTest extends \PHPUnit_Framework_TestCase { + + protected $setupCalDAV = false; + protected $setupCardDAV = false; + protected $setupACL = false; + protected $setupCalDAVSharing = false; + protected $setupCalDAVScheduling = false; + protected $setupCalDAVSubscriptions = false; + protected $setupCalDAVICSExport = false; + protected $setupLocks = false; + protected $setupFiles = false; + protected $setupSharing = false; + protected $setupPropertyStorage = false; + + /** + * An array with calendars. Every calendar should have + * - principaluri + * - uri + */ + protected $caldavCalendars = []; + protected $caldavCalendarObjects = []; + + protected $carddavAddressBooks = []; + protected $carddavCards = []; + + /** + * @var Sabre\DAV\Server + */ + protected $server; + protected $tree = []; + + protected $caldavBackend; + protected $carddavBackend; + protected $principalBackend; + protected $locksBackend; + protected $propertyStorageBackend; + + /** + * @var Sabre\CalDAV\Plugin + */ + protected $caldavPlugin; + + /** + * @var Sabre\CardDAV\Plugin + */ + protected $carddavPlugin; + + /** + * @var Sabre\DAVACL\Plugin + */ + protected $aclPlugin; + + /** + * @var Sabre\CalDAV\SharingPlugin + */ + protected $caldavSharingPlugin; + + /** + * CalDAV scheduling plugin + * + * @var CalDAV\Schedule\Plugin + */ + protected $caldavSchedulePlugin; + + /** + * @var Sabre\DAV\Auth\Plugin + */ + protected $authPlugin; + + /** + * @var Sabre\DAV\Locks\Plugin + */ + protected $locksPlugin; + + /** + * Sharing plugin. + * + * @var \Sabre\DAV\Sharing\Plugin + */ + protected $sharingPlugin; + + /* + * @var Sabre\DAV\PropertyStorage\Plugin + */ + protected $propertyStoragePlugin; + + /** + * If this string is set, we will automatically log in the user with this + * name. + */ + protected $autoLogin = null; + + function setUp() { + + $this->initializeEverything(); + + } + + function initializeEverything() { + + $this->setUpBackends(); + $this->setUpTree(); + + $this->server = new DAV\Server($this->tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + if ($this->setupCalDAV) { + $this->caldavPlugin = new CalDAV\Plugin(); + $this->server->addPlugin($this->caldavPlugin); + } + if ($this->setupCalDAVSharing || $this->setupSharing) { + $this->sharingPlugin = new DAV\Sharing\Plugin(); + $this->server->addPlugin($this->sharingPlugin); + } + if ($this->setupCalDAVSharing) { + $this->caldavSharingPlugin = new CalDAV\SharingPlugin(); + $this->server->addPlugin($this->caldavSharingPlugin); + } + if ($this->setupCalDAVScheduling) { + $this->caldavSchedulePlugin = new CalDAV\Schedule\Plugin(); + $this->server->addPlugin($this->caldavSchedulePlugin); + } + if ($this->setupCalDAVSubscriptions) { + $this->server->addPlugin(new CalDAV\Subscriptions\Plugin()); + } + if ($this->setupCalDAVICSExport) { + $this->caldavICSExportPlugin = new CalDAV\ICSExportPlugin(); + $this->server->addPlugin($this->caldavICSExportPlugin); + } + if ($this->setupCardDAV) { + $this->carddavPlugin = new CardDAV\Plugin(); + $this->server->addPlugin($this->carddavPlugin); + } + if ($this->setupLocks) { + $this->locksPlugin = new DAV\Locks\Plugin( + $this->locksBackend + ); + $this->server->addPlugin($this->locksPlugin); + } + if ($this->setupPropertyStorage) { + $this->propertyStoragePlugin = new DAV\PropertyStorage\Plugin( + $this->propertyStorageBackend + ); + $this->server->addPlugin($this->propertyStoragePlugin); + } + if ($this->autoLogin) { + $this->autoLogin($this->autoLogin); + } + if ($this->setupACL) { + $this->aclPlugin = new DAVACL\Plugin(); + if (!$this->autoLogin) { + $this->aclPlugin->allowUnauthenticatedAccess = false; + } + $this->aclPlugin->adminPrincipals = ['principals/admin']; + $this->server->addPlugin($this->aclPlugin); + } + + } + + /** + * Makes a request, and returns a response object. + * + * You can either pass an instance of Sabre\HTTP\Request, or an array, + * which will then be used as the _SERVER array. + * + * If $expectedStatus is set, we'll compare it with the HTTP status of + * the returned response. If it doesn't match, we'll immediately fail + * the test. + * + * @param array|\Sabre\HTTP\Request $request + * @param int $expectedStatus + * @return \Sabre\HTTP\Response + */ + function request($request, $expectedStatus = null) { + + if (is_array($request)) { + $request = HTTP\Request::createFromServerArray($request); + } + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + $this->server->exec(); + + if ($expectedStatus) { + $responseBody = $expectedStatus !== $response->getStatus() ? $response->getBodyAsString() : ''; + $this->assertEquals($expectedStatus, $response->getStatus(), 'Incorrect HTTP status received for request. Response body: ' . $responseBody); + } + return $this->server->httpResponse; + + } + + /** + * This function takes a username and sets the server in a state where + * this user is logged in, and no longer requires an authentication check. + * + * @param string $userName + */ + function autoLogin($userName) { + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/' . $userName); + $this->authPlugin = new DAV\Auth\Plugin($authBackend); + + // If the auth plugin already exists, we're removing its hooks: + if ($oldAuth = $this->server->getPlugin('auth')) { + $this->server->removeListener('beforeMethod', [$oldAuth, 'beforeMethod']); + } + $this->server->addPlugin($this->authPlugin); + + // This will trigger the actual login procedure + $this->authPlugin->beforeMethod(new Request(), new Response()); + } + + /** + * Override this to provide your own Tree for your test-case. + */ + function setUpTree() { + + if ($this->setupCalDAV) { + $this->tree[] = new CalDAV\CalendarRoot( + $this->principalBackend, + $this->caldavBackend + ); + } + if ($this->setupCardDAV) { + $this->tree[] = new CardDAV\AddressBookRoot( + $this->principalBackend, + $this->carddavBackend + ); + } + + if ($this->setupCalDAV) { + $this->tree[] = new CalDAV\Principal\Collection( + $this->principalBackend + ); + } elseif ($this->setupCardDAV || $this->setupACL) { + $this->tree[] = new DAVACL\PrincipalCollection( + $this->principalBackend + ); + } + if ($this->setupFiles) { + + $this->tree[] = new DAV\Mock\Collection('files'); + + } + + } + + function setUpBackends() { + + if ($this->setupCalDAVSharing && is_null($this->caldavBackend)) { + $this->caldavBackend = new CalDAV\Backend\MockSharing($this->caldavCalendars, $this->caldavCalendarObjects); + } + if ($this->setupCalDAVSubscriptions && is_null($this->caldavBackend)) { + $this->caldavBackend = new CalDAV\Backend\MockSubscriptionSupport($this->caldavCalendars, $this->caldavCalendarObjects); + } + if ($this->setupCalDAV && is_null($this->caldavBackend)) { + if ($this->setupCalDAVScheduling) { + $this->caldavBackend = new CalDAV\Backend\MockScheduling($this->caldavCalendars, $this->caldavCalendarObjects); + } else { + $this->caldavBackend = new CalDAV\Backend\Mock($this->caldavCalendars, $this->caldavCalendarObjects); + } + } + if ($this->setupCardDAV && is_null($this->carddavBackend)) { + $this->carddavBackend = new CardDAV\Backend\Mock($this->carddavAddressBooks, $this->carddavCards); + } + if ($this->setupCardDAV || $this->setupCalDAV || $this->setupACL) { + $this->principalBackend = new DAVACL\PrincipalBackend\Mock(); + } + if ($this->setupLocks) { + $this->locksBackend = new DAV\Locks\Backend\Mock(); + } + if ($this->setupPropertyStorage) { + $this->propertyStorageBackend = new DAV\PropertyStorage\Backend\Mock(); + } + + } + + + function assertHttpStatus($expectedStatus, HTTP\Request $req) { + + $resp = $this->request($req); + $this->assertEquals((int)$expectedStatus, (int)$resp->status, 'Incorrect HTTP status received: ' . $resp->body); + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php new file mode 100644 index 000000000000..eb486bf5b890 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php @@ -0,0 +1,22 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP Response Mock object + * + * This class exists to make the transition to sabre/http easier. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ResponseMock extends Response { + + /** + * Making these public. + */ + public $body; + public $status; + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php b/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php new file mode 100644 index 000000000000..e2888a9da797 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP Response Mock object + * + * This class exists to make the transition to sabre/http easier. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SapiMock extends Sapi { + + static $sent = 0; + + /** + * Overriding this so nothing is ever echo'd. + * + * @param ResponseInterface $response + * @return void + */ + static function sendResponse(ResponseInterface $response) { + + self::$sent++; + + } + +} diff --git a/libs/composer/vendor/sabre/dav/tests/Sabre/TestUtil.php b/libs/composer/vendor/sabre/dav/tests/Sabre/TestUtil.php new file mode 100644 index 000000000000..9df94915fb2e --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/Sabre/TestUtil.php @@ -0,0 +1,71 @@ +<?php + +namespace Sabre; + +class TestUtil { + + /** + * This function deletes all the contents of the temporary directory. + * + * @return void + */ + static function clearTempDir() { + + self::deleteTree(SABRE_TEMPDIR, false); + + } + + + private static function deleteTree($path, $deleteRoot = true) { + + foreach (scandir($path) as $node) { + + if ($node == '.' || $node == '..') continue; + $myPath = $path . '/' . $node; + if (is_file($myPath)) { + unlink($myPath); + } else { + self::deleteTree($myPath); + } + + } + if ($deleteRoot) { + rmdir($path); + } + + } + + static function getMySQLDB() { + + try { + $pdo = new \PDO(SABRE_MYSQLDSN, SABRE_MYSQLUSER, SABRE_MYSQLPASS); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + return $pdo; + } catch (\PDOException $e) { + return null; + } + + } + + static function getSQLiteDB() { + + $pdo = new \PDO('sqlite:' . SABRE_TEMPDIR . '/pdobackend'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + return $pdo; + + } + + static function getPgSqlDB() { + + //try { + $pdo = new \PDO(SABRE_PGSQLDSN); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + return $pdo; + //} catch (\PDOException $e) { + // return null; + //} + + } + + +} diff --git a/libs/composer/vendor/sabre/dav/tests/bootstrap.php b/libs/composer/vendor/sabre/dav/tests/bootstrap.php new file mode 100644 index 000000000000..26eb32aa2846 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/bootstrap.php @@ -0,0 +1,38 @@ +<?php + +set_include_path(__DIR__ . '/../lib/' . PATH_SEPARATOR . __DIR__ . PATH_SEPARATOR . get_include_path()); + +$autoLoader = include __DIR__ . '/../vendor/autoload.php'; + +// SabreDAV tests auto loading +$autoLoader->add('Sabre\\', __DIR__); +// VObject tests auto loading +$autoLoader->addPsr4('Sabre\\VObject\\', __DIR__ . '/../vendor/sabre/vobject/tests/VObject'); +$autoLoader->addPsr4('Sabre\\Xml\\', __DIR__ . '/../vendor/sabre/xml/tests/Sabre/Xml'); + +date_default_timezone_set('UTC'); + +$config = [ + 'SABRE_TEMPDIR' => dirname(__FILE__) . '/temp/', + 'SABRE_HASSQLITE' => in_array('sqlite', PDO::getAvailableDrivers()), + 'SABRE_HASMYSQL' => in_array('mysql', PDO::getAvailableDrivers()), + 'SABRE_HASPGSQL' => in_array('pgsql', PDO::getAvailableDrivers()), + 'SABRE_MYSQLDSN' => 'mysql:host=127.0.0.1;dbname=sabredav_test', + 'SABRE_MYSQLUSER' => 'sabredav', + 'SABRE_MYSQLPASS' => '', + 'SABRE_PGSQLDSN' => 'pgsql:host=localhost;dbname=sabredav_test;user=sabredav;password=sabredav', +]; + +if (file_exists(__DIR__ . '/config.user.php')) { + include __DIR__ . '/config.user.php'; + foreach ($userConfig as $key => $value) { + $config[$key] = $value; + } +} + +foreach ($config as $key => $value) { + if (!defined($key)) define($key, $value); +} + +if (!file_exists(SABRE_TEMPDIR)) mkdir(SABRE_TEMPDIR); +if (file_exists('.sabredav')) unlink('.sabredav'); diff --git a/libs/composer/vendor/sabre/dav/tests/phpcs/ruleset.xml b/libs/composer/vendor/sabre/dav/tests/phpcs/ruleset.xml new file mode 100644 index 000000000000..ec2c4c84b1d8 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/libs/composer/vendor/sabre/dav/tests/phpunit.xml.dist b/libs/composer/vendor/sabre/dav/tests/phpunit.xml.dist new file mode 100644 index 000000000000..453fabb82ad3 --- /dev/null +++ b/libs/composer/vendor/sabre/dav/tests/phpunit.xml.dist @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + beStrictAboutTestsThatDoNotTestAnything="true" + beStrictAboutOutputDuringTests="true" + beStrictAboutTestSize="true"> + + <testsuite name="sabre-event"> + <directory>../vendor/sabre/event/tests/</directory> + </testsuite> + <testsuite name="sabre-uri"> + <directory>../vendor/sabre/uri/tests/</directory> + </testsuite> + <testsuite name="sabre-xml"> + <directory>../vendor/sabre/xml/tests/Sabre/Xml/</directory> + </testsuite> + <testsuite name="sabre-http"> + <directory>../vendor/sabre/http/tests/HTTP</directory> + </testsuite> + <testsuite name="sabre-vobject"> + <directory>../vendor/sabre/vobject/tests/VObject</directory> + </testsuite> + + <testsuite name="sabre-dav"> + <directory>Sabre/DAV</directory> + </testsuite> + <testsuite name="sabre-davacl"> + <directory>Sabre/DAVACL</directory> + </testsuite> + <testsuite name="sabre-caldav"> + <directory>Sabre/CalDAV</directory> + </testsuite> + <testsuite name="sabre-carddav"> + <directory>Sabre/CardDAV</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/libs/composer/vendor/sabre/event/.gitignore b/libs/composer/vendor/sabre/event/.gitignore new file mode 100644 index 000000000000..d06a78164db1 --- /dev/null +++ b/libs/composer/vendor/sabre/event/.gitignore @@ -0,0 +1,14 @@ +#composer +vendor +composer.lock + +#binaries +bin/sabre-cs-fixer +bin/php-cs-fixer +bin/phpunit + +#vim lock files +.*.swp + +#development stuff +tests/cov diff --git a/libs/composer/vendor/sabre/event/.travis.yml b/libs/composer/vendor/sabre/event/.travis.yml new file mode 100644 index 000000000000..b6719f591f78 --- /dev/null +++ b/libs/composer/vendor/sabre/event/.travis.yml @@ -0,0 +1,26 @@ +language: php +php: + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + allow_failures: + - php: hhvm + +env: + matrix: + - LOWEST_DEPS="" + - LOWEST_DEPS="--prefer-lowest" + +before_script: + - composer update --prefer-source $LOWEST_DEPS + +script: + - ./bin/phpunit + - ./bin/sabre-cs-fixer fix . --dry-run --diff + +sudo: false + +cache: vendor diff --git a/libs/composer/vendor/sabre/event/CHANGELOG.md b/libs/composer/vendor/sabre/event/CHANGELOG.md new file mode 100644 index 000000000000..9d6d7cfaaf87 --- /dev/null +++ b/libs/composer/vendor/sabre/event/CHANGELOG.md @@ -0,0 +1,78 @@ +ChangeLog +========= + +3.0.0 (2015-11-05) +------------------ + +* Now requires PHP 5.5! +* `Promise::all()` is moved to `Promise\all()`. +* Aside from the `Promise\all()` function, there's now also `Promise\race()`. +* `Promise\reject()` and `Promise\resolve()` have also been added. +* Now 100% compatible with the Ecmascript 6 Promise. + + +3.0.0-alpha1 (2015-10-23) +------------------------- + +* This package now requires PHP 5.5. +* #26: Added an event loop implementation. Also knows as the Reactor Pattern. +* Renamed `Promise::error` to `Promise::otherwise` to be consistent with + ReactPHP and Guzzle. The `error` method is kept for BC but will be removed + in a future version. +* #27: Support for Promise-based coroutines via the `Sabre\Event\coroutine` + function. +* BC Break: Promises now use the EventLoop to run "then"-events in a separate + execution context. In practise that means you need to run the event loop to + wait for any `then`/`otherwise` callbacks to trigger. +* Promises now have a `wait()` method. Allowing you to make a promise + synchronous and simply wait for a result (or exception) to happen. + + +2.0.2 (2015-05-19) +------------------ + +* This release has no functional changes. It's just been brought up to date + with the latest coding standards. + + +2.0.1 (2014-10-06) +------------------ + +* Fixed: `$priority` was ignored in `EventEmitter::once` method. +* Fixed: Breaking the event chain was not possible in `EventEmitter::once`. + + +2.0.0 (2014-06-21) +------------------ + +* Added: When calling emit, it's now possible to specify a callback that will be + triggered after each method handled. This is dubbed the 'continueCallback' and + can be used to implement strategy patterns. +* Added: Promise object! +* Changed: EventEmitter::listeners now returns just the callbacks for an event, + and no longer returns the list by reference. The list is now automatically + sorted by priority. +* Update: Speed improvements. +* Updated: It's now possible to remove all listeners for every event. +* Changed: Now uses psr-4 autoloading. + + +1.0.1 (2014-06-12) +------------------ + +* hhvm compatible! +* Fixed: Issue #4. Compatiblitiy for PHP < 5.4.14. + + +1.0.0 (2013-07-19) +------------------ + +* Added: removeListener, removeAllListeners +* Added: once, to only listen to an event emitting once. +* Added README.md. + + +0.0.1-alpha (2013-06-29) +------------------------ + +* First version! diff --git a/libs/composer/vendor/sabre/event/LICENSE b/libs/composer/vendor/sabre/event/LICENSE new file mode 100644 index 000000000000..9a495cef0a1d --- /dev/null +++ b/libs/composer/vendor/sabre/event/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/event/README.md b/libs/composer/vendor/sabre/event/README.md new file mode 100644 index 000000000000..364906fd4968 --- /dev/null +++ b/libs/composer/vendor/sabre/event/README.md @@ -0,0 +1,50 @@ +sabre/event +=========== + +A lightweight library for event-based development in PHP. + +This library provides the following event-based concepts: + +1. EventEmitter. +2. Promises. +3. An event loop. +4. Co-routines. + +Full documentation can be found on [the website][1]. + +Installation +------------ + +Make sure you have [composer][3] installed, and then run: + + composer require sabre/event "~3.0.0" + +This package requires PHP 5.5. The 2.0 branch is still maintained as well, and +supports PHP 5.4. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=master)](https://travis-ci.org/fruux/sabre-event) | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-event) | +| 1.0 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=1.0)](https://travis-ci.org/fruux/sabre-event) | +| php53 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=php53)](https://travis-ci.org/fruux/sabre-event) | + + +Questions? +---------- + +Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket +on [GitHub][5]. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://sabre.io/event/ +[3]: http://getcomposer.org/ +[4]: http://groups.google.com/group/sabredav-discuss +[5]: https://github.com/fruux/sabre-event/issues/ diff --git a/libs/composer/vendor/sabre/event/bin/.empty b/libs/composer/vendor/sabre/event/bin/.empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/composer/vendor/sabre/event/composer.json b/libs/composer/vendor/sabre/event/composer.json new file mode 100644 index 000000000000..9a11b01aaa4d --- /dev/null +++ b/libs/composer/vendor/sabre/event/composer.json @@ -0,0 +1,47 @@ +{ + "name": "sabre/event", + "description": "sabre/event is a library for lightweight event-based programming", + "keywords": [ + "Events", + "EventEmitter", + "Promise", + "Hooks", + "Plugin", + "Signal", + "Async" + ], + "homepage": "http://sabre.io/event/", + "license": "BSD-3-Clause", + "require": { + "php": ">=5.5" + }, + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "source": "https://github.com/fruux/sabre-event" + }, + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + }, + "files" : [ + "lib/coroutine.php", + "lib/Loop/functions.php", + "lib/Promise/functions.php" + ] + }, + "require-dev": { + "sabre/cs": "~0.0.4", + "phpunit/phpunit" : "*" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/libs/composer/vendor/sabre/event/examples/promise.php b/libs/composer/vendor/sabre/event/examples/promise.php new file mode 100755 index 000000000000..b40227e15ddf --- /dev/null +++ b/libs/composer/vendor/sabre/event/examples/promise.php @@ -0,0 +1,100 @@ +#!/usr/bin/env php +<?php + +use Sabre\Event\Promise; +use Sabre\Event\Loop; +use function Sabre\Event\coroutine; + +require __DIR__ . '/../vendor/autoload.php'; + +/** + * This example shows demonstrates the Promise api. + */ + + +/* Creating a new promise */ +$promise = new Promise(); + +/* After 2 seconds we fulfill it */ +Loop\setTimeout(function() use ($promise) { + + echo "Step 1\n"; + $promise->fulfill("hello"); + +}, 2); + + +/* Callback chain */ + +$result = $promise + ->then(function($value) { + + echo "Step 2\n"; + // Immediately returning a new value. + return $value . " world"; + + }) + ->then(function($value) { + + echo "Step 3\n"; + // This 'then' returns a new promise which we resolve later. + $promise = new Promise(); + + // Resolving after 2 seconds + Loop\setTimeout(function() use ($promise, $value) { + + $promise->fulfill($value . ", how are ya?"); + + }, 2); + + return $promise; + }) + ->then(function($value) { + + echo "Step 4\n"; + // This is the final event handler. + return $value . " you rock!"; + + }) + // Making all async calls synchronous by waiting for the final result. + ->wait(); + +echo $result, "\n"; + +/* Now an identical example, this time with coroutines. */ + +$result = coroutine(function() { + + $promise = new Promise(); + + /* After 2 seconds we fulfill it */ + Loop\setTimeout(function() use ($promise) { + + echo "Step 1\n"; + $promise->fulfill("hello"); + + }, 2); + + $value = (yield $promise); + + echo "Step 2\n"; + $value .= ' world'; + + echo "Step 3\n"; + $promise = new Promise(); + Loop\setTimeout(function() use ($promise, $value) { + + $promise->fulfill($value . ", how are ya?"); + + }, 2); + + $value = (yield $promise); + + echo "Step 4\n"; + + // This is the final event handler. + yield $value . " you rock!"; + +})->wait(); + +echo $result, "\n"; diff --git a/libs/composer/vendor/sabre/event/examples/tail.php b/libs/composer/vendor/sabre/event/examples/tail.php new file mode 100755 index 000000000000..d4e82206b529 --- /dev/null +++ b/libs/composer/vendor/sabre/event/examples/tail.php @@ -0,0 +1,28 @@ +#!/usr/bin/env php +<?php + +/** + * This example can be used to logfile processing and basically wraps the tail + * command. + * + * The benefit of using this, is that it allows you to tail multiple logs at + * the same time + * + * To stop this application, hit CTRL-C + */ +if ($argc < 2) { + echo "Usage: " . $argv[0] . " filename\n"; + exit(1); +} + +require __DIR__ . '/../vendor/autoload.php'; + +$tail = popen('tail -fn0 ' . escapeshellarg($argv[1]), 'r'); + +\Sabre\Event\Loop\addReadStream($tail, function() use ($tail) { + + echo fread($tail, 4096); + +}); + +$loop->run(); diff --git a/libs/composer/vendor/sabre/event/lib/EventEmitter.php b/libs/composer/vendor/sabre/event/lib/EventEmitter.php new file mode 100644 index 000000000000..1bb1c3cf92b2 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/EventEmitter.php @@ -0,0 +1,18 @@ +<?php + +namespace Sabre\Event; + +/** + * EventEmitter object. + * + * Instantiate this class, or subclass it for easily creating event emitters. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class EventEmitter implements EventEmitterInterface { + + use EventEmitterTrait; + +} diff --git a/libs/composer/vendor/sabre/event/lib/EventEmitterInterface.php b/libs/composer/vendor/sabre/event/lib/EventEmitterInterface.php new file mode 100644 index 000000000000..0e2be2cefb50 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/EventEmitterInterface.php @@ -0,0 +1,100 @@ +<?php + +namespace Sabre\Event; + +/** + * Event Emitter Interface + * + * Anything that accepts listeners and emits events should implement this + * interface. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface EventEmitterInterface { + + /** + * Subscribe to an event. + * + * @param string $eventName + * @param callable $callBack + * @param int $priority + * @return void + */ + function on($eventName, callable $callBack, $priority = 100); + + /** + * Subscribe to an event exactly once. + * + * @param string $eventName + * @param callable $callBack + * @param int $priority + * @return void + */ + function once($eventName, callable $callBack, $priority = 100); + + /** + * Emits an event. + * + * This method will return true if 0 or more listeners were succesfully + * handled. false is returned if one of the events broke the event chain. + * + * If the continueCallBack is specified, this callback will be called every + * time before the next event handler is called. + * + * If the continueCallback returns false, event propagation stops. This + * allows you to use the eventEmitter as a means for listeners to implement + * functionality in your application, and break the event loop as soon as + * some condition is fulfilled. + * + * Note that returning false from an event subscriber breaks propagation + * and returns false, but if the continue-callback stops propagation, this + * is still considered a 'successful' operation and returns true. + * + * Lastly, if there are 5 event handlers for an event. The continueCallback + * will be called at most 4 times. + * + * @param string $eventName + * @param array $arguments + * @param callback $continueCallBack + * @return bool + */ + function emit($eventName, array $arguments = [], callable $continueCallBack = null); + + /** + * Returns the list of listeners for an event. + * + * The list is returned as an array, and the list of events are sorted by + * their priority. + * + * @param string $eventName + * @return callable[] + */ + function listeners($eventName); + + /** + * Removes a specific listener from an event. + * + * If the listener could not be found, this method will return false. If it + * was removed it will return true. + * + * @param string $eventName + * @param callable $listener + * @return bool + */ + function removeListener($eventName, callable $listener); + + /** + * Removes all listeners. + * + * If the eventName argument is specified, all listeners for that event are + * removed. If it is not specified, every listener for every event is + * removed. + * + * @param string $eventName + * @return void + */ + function removeAllListeners($eventName = null); + +} diff --git a/libs/composer/vendor/sabre/event/lib/EventEmitterTrait.php b/libs/composer/vendor/sabre/event/lib/EventEmitterTrait.php new file mode 100644 index 000000000000..257629faee2a --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/EventEmitterTrait.php @@ -0,0 +1,211 @@ +<?php + +namespace Sabre\Event; + +/** + * Event Emitter Trait + * + * This trait contains all the basic functions to implement an + * EventEmitterInterface. + * + * Using the trait + interface allows you to add EventEmitter capabilities + * without having to change your base-class. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +trait EventEmitterTrait { + + /** + * The list of listeners + * + * @var array + */ + protected $listeners = []; + + /** + * Subscribe to an event. + * + * @param string $eventName + * @param callable $callBack + * @param int $priority + * @return void + */ + function on($eventName, callable $callBack, $priority = 100) { + + if (!isset($this->listeners[$eventName])) { + $this->listeners[$eventName] = [ + true, // If there's only one item, it's sorted + [$priority], + [$callBack] + ]; + } else { + $this->listeners[$eventName][0] = false; // marked as unsorted + $this->listeners[$eventName][1][] = $priority; + $this->listeners[$eventName][2][] = $callBack; + } + + } + + /** + * Subscribe to an event exactly once. + * + * @param string $eventName + * @param callable $callBack + * @param int $priority + * @return void + */ + function once($eventName, callable $callBack, $priority = 100) { + + $wrapper = null; + $wrapper = function() use ($eventName, $callBack, &$wrapper) { + + $this->removeListener($eventName, $wrapper); + return call_user_func_array($callBack, func_get_args()); + + }; + + $this->on($eventName, $wrapper, $priority); + + } + + /** + * Emits an event. + * + * This method will return true if 0 or more listeners were succesfully + * handled. false is returned if one of the events broke the event chain. + * + * If the continueCallBack is specified, this callback will be called every + * time before the next event handler is called. + * + * If the continueCallback returns false, event propagation stops. This + * allows you to use the eventEmitter as a means for listeners to implement + * functionality in your application, and break the event loop as soon as + * some condition is fulfilled. + * + * Note that returning false from an event subscriber breaks propagation + * and returns false, but if the continue-callback stops propagation, this + * is still considered a 'successful' operation and returns true. + * + * Lastly, if there are 5 event handlers for an event. The continueCallback + * will be called at most 4 times. + * + * @param string $eventName + * @param array $arguments + * @param callback $continueCallBack + * @return bool + */ + function emit($eventName, array $arguments = [], callable $continueCallBack = null) { + + if (is_null($continueCallBack)) { + + foreach ($this->listeners($eventName) as $listener) { + + $result = call_user_func_array($listener, $arguments); + if ($result === false) { + return false; + } + } + + } else { + + $listeners = $this->listeners($eventName); + $counter = count($listeners); + + foreach ($listeners as $listener) { + + $counter--; + $result = call_user_func_array($listener, $arguments); + if ($result === false) { + return false; + } + + if ($counter > 0) { + if (!$continueCallBack()) break; + } + + } + + } + + return true; + + } + + /** + * Returns the list of listeners for an event. + * + * The list is returned as an array, and the list of events are sorted by + * their priority. + * + * @param string $eventName + * @return callable[] + */ + function listeners($eventName) { + + if (!isset($this->listeners[$eventName])) { + return []; + } + + // The list is not sorted + if (!$this->listeners[$eventName][0]) { + + // Sorting + array_multisort($this->listeners[$eventName][1], SORT_NUMERIC, $this->listeners[$eventName][2]); + + // Marking the listeners as sorted + $this->listeners[$eventName][0] = true; + } + + return $this->listeners[$eventName][2]; + + } + + /** + * Removes a specific listener from an event. + * + * If the listener could not be found, this method will return false. If it + * was removed it will return true. + * + * @param string $eventName + * @param callable $listener + * @return bool + */ + function removeListener($eventName, callable $listener) { + + if (!isset($this->listeners[$eventName])) { + return false; + } + foreach ($this->listeners[$eventName][2] as $index => $check) { + if ($check === $listener) { + unset($this->listeners[$eventName][1][$index]); + unset($this->listeners[$eventName][2][$index]); + return true; + } + } + return false; + + } + + /** + * Removes all listeners. + * + * If the eventName argument is specified, all listeners for that event are + * removed. If it is not specified, every listener for every event is + * removed. + * + * @param string $eventName + * @return void + */ + function removeAllListeners($eventName = null) { + + if (!is_null($eventName)) { + unset($this->listeners[$eventName]); + } else { + $this->listeners = []; + } + + } + +} diff --git a/libs/composer/vendor/sabre/event/lib/Loop/Loop.php b/libs/composer/vendor/sabre/event/lib/Loop/Loop.php new file mode 100644 index 000000000000..86ee7c8b0842 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/Loop/Loop.php @@ -0,0 +1,386 @@ +<?php + +namespace Sabre\Event\Loop; + +/** + * A simple eventloop implementation. + * + * This eventloop supports: + * * nextTick + * * setTimeout for delayed functions + * * setInterval for repeating functions + * * stream events using stream_select + * + * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Loop { + + /** + * Executes a function after x seconds. + * + * @param callable $cb + * @param float $timeout timeout in seconds + * @return void + */ + function setTimeout(callable $cb, $timeout) { + + $triggerTime = microtime(true) + ($timeout); + + if (!$this->timers) { + // Special case when the timers array was empty. + $this->timers[] = [$triggerTime, $cb]; + return; + } + + // We need to insert these values in the timers array, but the timers + // array must be in reverse-order of trigger times. + // + // So here we search the array for the insertion point. + $index = count($this->timers) - 1; + while (true) { + if ($triggerTime < $this->timers[$index][0]) { + array_splice( + $this->timers, + $index + 1, + 0, + [[$triggerTime, $cb]] + ); + break; + } elseif ($index === 0) { + array_unshift($this->timers, [$triggerTime, $cb]); + break; + } + $index--; + + } + + } + + /** + * Executes a function every x seconds. + * + * The value this function returns can be used to stop the interval with + * clearInterval. + * + * @param callable $cb + * @param float $timeout + * @return array + */ + function setInterval(callable $cb, $timeout) { + + $keepGoing = true; + $f = null; + + $f = function() use ($cb, &$f, $timeout, &$keepGoing) { + if ($keepGoing) { + $cb(); + $this->setTimeout($f, $timeout); + } + }; + $this->setTimeout($f, $timeout); + + // Really the only thing that matters is returning the $keepGoing + // boolean value. + // + // We need to pack it in an array to allow returning by reference. + // Because I'm worried people will be confused by using a boolean as a + // sort of identifier, I added an extra string. + return ['I\'m an implementation detail', &$keepGoing]; + + } + + /** + * Stops a running internval. + * + * @param array $intervalId + * @return void + */ + function clearInterval($intervalId) { + + $intervalId[1] = false; + + } + + /** + * Runs a function immediately at the next iteration of the loop. + * + * @param callable $cb + * @return void + */ + function nextTick(callable $cb) { + + $this->nextTick[] = $cb; + + } + + + /** + * Adds a read stream. + * + * The callback will be called as soon as there is something to read from + * the stream. + * + * You MUST call removeReadStream after you are done with the stream, to + * prevent the eventloop from never stopping. + * + * @param resource $stream + * @param callable $cb + * @return void + */ + function addReadStream($stream, callable $cb) { + + $this->readStreams[(int)$stream] = $stream; + $this->readCallbacks[(int)$stream] = $cb; + + } + + /** + * Adds a write stream. + * + * The callback will be called as soon as the system reports it's ready to + * receive writes on the stream. + * + * You MUST call removeWriteStream after you are done with the stream, to + * prevent the eventloop from never stopping. + * + * @param resource $stream + * @param callable $cb + * @return void + */ + function addWriteStream($stream, callable $cb) { + + $this->writeStreams[(int)$stream] = $stream; + $this->writeCallbacks[(int)$stream] = $cb; + + } + + /** + * Stop watching a stream for reads. + * + * @param resource $stream + * @return void + */ + function removeReadStream($stream) { + + unset( + $this->readStreams[(int)$stream], + $this->readCallbacks[(int)$stream] + ); + + } + + /** + * Stop watching a stream for writes. + * + * @param resource $stream + * @return void + */ + function removeWriteStream($stream) { + + unset( + $this->writeStreams[(int)$stream], + $this->writeCallbacks[(int)$stream] + ); + + } + + + /** + * Runs the loop. + * + * This function will run continiously, until there's no more events to + * handle. + * + * @return void + */ + function run() { + + $this->running = true; + + do { + + $hasEvents = $this->tick(true); + + } while ($this->running && $hasEvents); + $this->running = false; + + } + + /** + * Executes all pending events. + * + * If $block is turned true, this function will block until any event is + * triggered. + * + * If there are now timeouts, nextTick callbacks or events in the loop at + * all, this function will exit immediately. + * + * This function will return true if there are _any_ events left in the + * loop after the tick. + * + * @param bool $block + * @return bool + */ + function tick($block = false) { + + $this->runNextTicks(); + $nextTimeout = $this->runTimers(); + + // Calculating how long runStreams should at most wait. + if (!$block) { + // Don't wait + $streamWait = 0; + } elseif ($this->nextTick) { + // There's a pending 'nextTick'. Don't wait. + $streamWait = 0; + } elseif (is_numeric($nextTimeout)) { + // Wait until the next Timeout should trigger. + $streamWait = $nextTimeout; + } else { + // Wait indefinitely + $streamWait = null; + } + + $this->runStreams($streamWait); + + return ($this->readStreams || $this->writeStreams || $this->nextTick || $this->timers); + + } + + /** + * Stops a running eventloop + * + * @return void + */ + function stop() { + + $this->running = false; + + } + + /** + * Executes all 'nextTick' callbacks. + * + * return void + */ + protected function runNextTicks() { + + $nextTick = $this->nextTick; + $this->nextTick = []; + + foreach ($nextTick as $cb) { + $cb(); + } + + } + + /** + * Runs all pending timers. + * + * After running the timer callbacks, this function returns the number of + * seconds until the next timer should be executed. + * + * If there's no more pending timers, this function returns null. + * + * @return float + */ + protected function runTimers() { + + $now = microtime(true); + while (($timer = array_pop($this->timers)) && $timer[0] < $now) { + $timer[1](); + } + // Add the last timer back to the array. + if ($timer) { + $this->timers[] = $timer; + return $timer[0] - microtime(true); + } + + } + + /** + * Runs all pending stream events. + * + * @param float $timeout + */ + protected function runStreams($timeout) { + + if ($this->readStreams || $this->writeStreams) { + + $read = $this->readStreams; + $write = $this->writeStreams; + $except = null; + if (stream_select($read, $write, $except, null, $timeout)) { + + // See PHP Bug https://bugs.php.net/bug.php?id=62452 + // Fixed in PHP7 + foreach ($read as $readStream) { + $readCb = $this->readCallbacks[(int)$readStream]; + $readCb(); + } + foreach ($write as $writeStream) { + $writeCb = $this->writeCallbacks[(int)$writeStream]; + $writeCb(); + } + + } + + } elseif ($this->running && ($this->nextTick || $this->timers)) { + usleep($timeout !== null ? $timeout * 1000000 : 200000); + } + + } + + /** + * Is the main loop active + * + * @var bool + */ + protected $running = false; + + /** + * A list of timers, added by setTimeout. + * + * @var array + */ + protected $timers = []; + + /** + * A list of 'nextTick' callbacks. + * + * @var callable[] + */ + protected $nextTick = []; + + /** + * List of readable streams for stream_select, indexed by stream id. + * + * @var resource[] + */ + protected $readStreams = []; + + /** + * List of writable streams for stream_select, indexed by stream id. + * + * @var resource[] + */ + protected $writeStreams = []; + + /** + * List of read callbacks, indexed by stream id. + * + * @var callback[] + */ + protected $readCallbacks = []; + + /** + * List of write callbacks, indexed by stream id. + * + * @var callback[] + */ + protected $writeCallbacks = []; + + +} diff --git a/libs/composer/vendor/sabre/event/lib/Loop/functions.php b/libs/composer/vendor/sabre/event/lib/Loop/functions.php new file mode 100644 index 000000000000..56c5bc8c74a7 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/Loop/functions.php @@ -0,0 +1,183 @@ +<?php + +namespace Sabre\Event\Loop; + +/** + * Executes a function after x seconds. + * + * @param callable $cb + * @param float $timeout timeout in seconds + * @return void + */ +function setTimeout(callable $cb, $timeout) { + + instance()->setTimeout($cb, $timeout); + +} + +/** + * Executes a function every x seconds. + * + * The value this function returns can be used to stop the interval with + * clearInterval. + * + * @param callable $cb + * @param float $timeout + * @return array + */ +function setInterval(callable $cb, $timeout) { + + return instance()->setInterval($cb, $timeout); + +} + +/** + * Stops a running internval. + * + * @param array $intervalId + * @return void + */ +function clearInterval($intervalId) { + + instance()->clearInterval($intervalId); + +} + +/** + * Runs a function immediately at the next iteration of the loop. + * + * @param callable $cb + * @return void + */ +function nextTick(callable $cb) { + + instance()->nextTick($cb); + +} + + +/** + * Adds a read stream. + * + * The callback will be called as soon as there is something to read from + * the stream. + * + * You MUST call removeReadStream after you are done with the stream, to + * prevent the eventloop from never stopping. + * + * @param resource $stream + * @param callable $cb + * @return void + */ +function addReadStream($stream, callable $cb) { + + instance()->addReadStream($stream, $cb); + +} + +/** + * Adds a write stream. + * + * The callback will be called as soon as the system reports it's ready to + * receive writes on the stream. + * + * You MUST call removeWriteStream after you are done with the stream, to + * prevent the eventloop from never stopping. + * + * @param resource $stream + * @param callable $cb + * @return void + */ +function addWriteStream($stream, callable $cb) { + + instance()->addWriteStream($stream, $cb); + +} + +/** + * Stop watching a stream for reads. + * + * @param resource $stream + * @return void + */ +function removeReadStream($stream) { + + instance()->removeReadStream($stream); + +} + +/** + * Stop watching a stream for writes. + * + * @param resource $stream + * @return void + */ +function removeWriteStream($stream) { + + instance()->removeWriteStream($stream); + +} + + +/** + * Runs the loop. + * + * This function will run continiously, until there's no more events to + * handle. + * + * @return void + */ +function run() { + + instance()->run(); + +} + +/** + * Executes all pending events. + * + * If $block is turned true, this function will block until any event is + * triggered. + * + * If there are now timeouts, nextTick callbacks or events in the loop at + * all, this function will exit immediately. + * + * This function will return true if there are _any_ events left in the + * loop after the tick. + * + * @param bool $block + * @return bool + */ +function tick($block = false) { + + return instance()->tick($block); + +} + +/** + * Stops a running eventloop + * + * @return void + */ +function stop() { + + instance()->stop(); + +} + +/** + * Retrieves or sets the global Loop object. + * + * @param Loop $newLoop + */ +function instance(Loop $newLoop = null) { + + static $loop; + if ($newLoop) { + $loop = $newLoop; + } elseif (!$loop) { + $loop = new Loop(); + } + return $loop; + +} diff --git a/libs/composer/vendor/sabre/event/lib/Promise.php b/libs/composer/vendor/sabre/event/lib/Promise.php new file mode 100644 index 000000000000..1c874c1bda31 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/Promise.php @@ -0,0 +1,320 @@ +<?php + +namespace Sabre\Event; + +use Exception; + +/** + * An implementation of the Promise pattern. + * + * A promise represents the result of an asynchronous operation. + * At any given point a promise can be in one of three states: + * + * 1. Pending (the promise does not have a result yet). + * 2. Fulfilled (the asynchronous operation has completed with a result). + * 3. Rejected (the asynchronous operation has completed with an error). + * + * To get a callback when the operation has finished, use the `then` method. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Promise { + + /** + * The asynchronous operation is pending. + */ + const PENDING = 0; + + /** + * The asynchronous operation has completed, and has a result. + */ + const FULFILLED = 1; + + /** + * The asynchronous operation has completed with an error. + */ + const REJECTED = 2; + + /** + * The current state of this promise. + * + * @var int + */ + public $state = self::PENDING; + + /** + * Creates the promise. + * + * The passed argument is the executor. The executor is automatically + * called with two arguments. + * + * Each are callbacks that map to $this->fulfill and $this->reject. + * Using the executor is optional. + * + * @param callable $executor + */ + function __construct(callable $executor = null) { + + if ($executor) { + $executor( + [$this, 'fulfill'], + [$this, 'reject'] + ); + } + + } + + /** + * This method allows you to specify the callback that will be called after + * the promise has been fulfilled or rejected. + * + * Both arguments are optional. + * + * This method returns a new promise, which can be used for chaining. + * If either the onFulfilled or onRejected callback is called, you may + * return a result from this callback. + * + * If the result of this callback is yet another promise, the result of + * _that_ promise will be used to set the result of the returned promise. + * + * If either of the callbacks return any other value, the returned promise + * is automatically fulfilled with that value. + * + * If either of the callbacks throw an exception, the returned promise will + * be rejected and the exception will be passed back. + * + * @param callable $onFulfilled + * @param callable $onRejected + * @return Promise + */ + function then(callable $onFulfilled = null, callable $onRejected = null) { + + // This new subPromise will be returned from this function, and will + // be fulfilled with the result of the onFulfilled or onRejected event + // handlers. + $subPromise = new self(); + + switch ($this->state) { + case self::PENDING : + // The operation is pending, so we keep a reference to the + // event handlers so we can call them later. + $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected]; + break; + case self::FULFILLED : + // The async operation is already fulfilled, so we trigger the + // onFulfilled callback asap. + $this->invokeCallback($subPromise, $onFulfilled); + break; + case self::REJECTED : + // The async operation failed, so we call teh onRejected + // callback asap. + $this->invokeCallback($subPromise, $onRejected); + break; + } + return $subPromise; + + } + + /** + * Add a callback for when this promise is rejected. + * + * Its usage is identical to then(). However, the otherwise() function is + * preferred. + * + * @param callable $onRejected + * @return Promise + */ + function otherwise(callable $onRejected) { + + return $this->then(null, $onRejected); + + } + + /** + * Marks this promise as fulfilled and sets its return value. + * + * @param mixed $value + * @return void + */ + function fulfill($value = null) { + if ($this->state !== self::PENDING) { + throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once'); + } + $this->state = self::FULFILLED; + $this->value = $value; + foreach ($this->subscribers as $subscriber) { + $this->invokeCallback($subscriber[0], $subscriber[1]); + } + } + + /** + * Marks this promise as rejected, and set it's rejection reason. + * + * While it's possible to use any PHP value as the reason, it's highly + * recommended to use an Exception for this. + * + * @param mixed $reason + * @return void + */ + function reject($reason = null) { + if ($this->state !== self::PENDING) { + throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once'); + } + $this->state = self::REJECTED; + $this->value = $reason; + foreach ($this->subscribers as $subscriber) { + $this->invokeCallback($subscriber[0], $subscriber[2]); + } + + } + + /** + * Stops execution until this promise is resolved. + * + * This method stops exection completely. If the promise is successful with + * a value, this method will return this value. If the promise was + * rejected, this method will throw an exception. + * + * This effectively turns the asynchronous operation into a synchronous + * one. In PHP it might be useful to call this on the last promise in a + * chain. + * + * @throws Exception + * @return mixed + */ + function wait() { + + $hasEvents = true; + while ($this->state === self::PENDING) { + + if (!$hasEvents) { + throw new \LogicException('There were no more events in the loop. This promise will never be fulfilled.'); + } + + // As long as the promise is not fulfilled, we tell the event loop + // to handle events, and to block. + $hasEvents = Loop\tick(true); + + } + + if ($this->state === self::FULFILLED) { + // If the state of this promise is fulfilled, we can return the value. + return $this->value; + } else { + // If we got here, it means that the asynchronous operation + // errored. Therefore we need to throw an exception. + $reason = $this->value; + if ($reason instanceof Exception) { + throw $reason; + } elseif (is_scalar($reason)) { + throw new Exception($reason); + } else { + $type = is_object($reason) ? get_class($reason) : gettype($reason); + throw new Exception('Promise was rejected with reason of type: ' . $type); + } + } + + + } + + + /** + * A list of subscribers. Subscribers are the callbacks that want us to let + * them know if the callback was fulfilled or rejected. + * + * @var array + */ + protected $subscribers = []; + + /** + * The result of the promise. + * + * If the promise was fulfilled, this will be the result value. If the + * promise was rejected, this property hold the rejection reason. + * + * @var mixed + */ + protected $value = null; + + /** + * This method is used to call either an onFulfilled or onRejected callback. + * + * This method makes sure that the result of these callbacks are handled + * correctly, and any chained promises are also correctly fulfilled or + * rejected. + * + * @param Promise $subPromise + * @param callable $callBack + * @return void + */ + private function invokeCallback(Promise $subPromise, callable $callBack = null) { + + // We use 'nextTick' to ensure that the event handlers are always + // triggered outside of the calling stack in which they were originally + // passed to 'then'. + // + // This makes the order of execution more predictable. + Loop\nextTick(function() use ($callBack, $subPromise) { + if (is_callable($callBack)) { + try { + + $result = $callBack($this->value); + if ($result instanceof self) { + // If the callback (onRejected or onFulfilled) + // returned a promise, we only fulfill or reject the + // chained promise once that promise has also been + // resolved. + $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']); + } else { + // If the callback returned any other value, we + // immediately fulfill the chained promise. + $subPromise->fulfill($result); + } + } catch (Exception $e) { + // If the event handler threw an exception, we need to make sure that + // the chained promise is rejected as well. + $subPromise->reject($e); + } + } else { + if ($this->state === self::FULFILLED) { + $subPromise->fulfill($this->value); + } else { + $subPromise->reject($this->value); + } + } + }); + } + + /** + * Alias for 'otherwise'. + * + * This function is now deprecated and will be removed in a future version. + * + * @param callable $onRejected + * @deprecated + * @return Promise + */ + function error(callable $onRejected) { + + return $this->otherwise($onRejected); + + } + + /** + * Deprecated. + * + * Please use Sabre\Event\Promise::all + * + * @param Promise[] $promises + * @deprecated + * @return Promise + */ + static function all(array $promises) { + + return Promise\all($promises); + + } + +} diff --git a/libs/composer/vendor/sabre/event/lib/Promise/functions.php b/libs/composer/vendor/sabre/event/lib/Promise/functions.php new file mode 100644 index 000000000000..3604b8aaa952 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/Promise/functions.php @@ -0,0 +1,135 @@ +<?php + +namespace Sabre\Event\Promise; + +use Sabre\Event\Promise; + +/** + * This file contains a set of functions that are useful for dealing with the + * Promise object. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ + + +/** + * This function takes an array of Promises, and returns a Promise that + * resolves when all of the given arguments have resolved. + * + * The returned Promise will resolve with a value that's an array of all the + * values the given promises have been resolved with. + * + * This array will be in the exact same order as the array of input promises. + * + * If any of the given Promises fails, the returned promise will immidiately + * fail with the first Promise that fails, and its reason. + * + * @param Promise[] $promises + * @return Promise + */ +function all(array $promises) { + + return new Promise(function($success, $fail) use ($promises) { + + $successCount = 0; + $completeResult = []; + + foreach ($promises as $promiseIndex => $subPromise) { + + $subPromise->then( + function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) { + $completeResult[$promiseIndex] = $result; + $successCount++; + if ($successCount === count($promises)) { + $success($completeResult); + } + return $result; + } + )->error( + function($reason) use ($fail) { + $fail($reason); + } + ); + + } + }); + +} + +/** + * The race function returns a promise that resolves or rejects as soon as + * one of the promises in the argument resolves or rejects. + * + * The returned promise will resolve or reject with the value or reason of + * that first promise. + * + * @param Promise[] $promises + * @return Promise + */ +function race(array $promises) { + + return new Promise(function($success, $fail) use ($promises) { + + $alreadyDone = false; + foreach ($promises as $promise) { + + $promise->then( + function($result) use ($success, &$alreadyDone) { + if ($alreadyDone) { + return; + } + $alreadyDone = true; + $success($result); + }, + function($reason) use ($fail, &$alreadyDone) { + if ($alreadyDone) { + return; + } + $alreadyDone = true; + $fail($reason); + } + ); + + } + + }); + +} + + +/** + * Returns a Promise that resolves with the given value. + * + * If the value is a promise, the returned promise will attach itself to that + * promise and eventually get the same state as the followed promise. + * + * @param mixed $value + * @return Promise + */ +function resolve($value) { + + if ($value instanceof Promise) { + return $value->then(); + } else { + $promise = new Promise(); + $promise->fulfill($value); + return $promise; + } + +} + +/** + * Returns a Promise that will reject with the given reason. + * + * @param mixed $reason + * @return Promise + */ +function reject($reason) { + + $promise = new Promise(); + $promise->reject($reason); + return $promise; + +} diff --git a/libs/composer/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php b/libs/composer/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php new file mode 100644 index 000000000000..86a6c5b3f1a4 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php @@ -0,0 +1,15 @@ +<?php + +namespace Sabre\Event; + +/** + * This exception is thrown when the user tried to reject or fulfill a promise, + * after either of these actions were already performed. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PromiseAlreadyResolvedException extends \LogicException { + +} diff --git a/libs/composer/vendor/sabre/event/lib/Version.php b/libs/composer/vendor/sabre/event/lib/Version.php new file mode 100644 index 000000000000..5de22193ff51 --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\Event; + +/** + * This class contains the version number for this package. + * + * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '3.0.0'; + +} diff --git a/libs/composer/vendor/sabre/event/lib/coroutine.php b/libs/composer/vendor/sabre/event/lib/coroutine.php new file mode 100644 index 000000000000..19c0ba8a714e --- /dev/null +++ b/libs/composer/vendor/sabre/event/lib/coroutine.php @@ -0,0 +1,120 @@ +<?php + +namespace Sabre\Event; + +use Generator; +use Exception; + +/** + * Turn asynchronous promise-based code into something that looks synchronous + * again, through the use of generators. + * + * Example without coroutines: + * + * $promise = $httpClient->request('GET', '/foo'); + * $promise->then(function($value) { + * + * return $httpClient->request('DELETE','/foo'); + * + * })->then(function($value) { + * + * return $httpClient->request('PUT', '/foo'); + * + * })->error(function($reason) { + * + * echo "Failed because: $reason\n"; + * + * }); + * + * Example with coroutines: + * + * coroutine(function() { + * + * try { + * yield $httpClient->request('GET', '/foo'); + * yield $httpClient->request('DELETE', /foo'); + * yield $httpClient->request('PUT', '/foo'); + * } catch(\Exception $reason) { + * echo "Failed because: $reason\n"; + * } + * + * }); + * + * @copyright Copyright (C) 2013-2015 fruux GmbH. All rights reserved. + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +function coroutine(callable $gen) { + + $generator = $gen(); + if (!$generator instanceof Generator) { + throw new \InvalidArgumentException('You must pass a generator function'); + } + + // This is the value we're returning. + $promise = new Promise(); + + $lastYieldResult = null; + + /** + * So tempted to use the mythical y-combinator here, but it's not needed in + * PHP. + */ + $advanceGenerator = function() use (&$advanceGenerator, $generator, $promise, &$lastYieldResult) { + + while ($generator->valid()) { + + $yieldedValue = $generator->current(); + if ($yieldedValue instanceof Promise) { + $yieldedValue->then( + function($value) use ($generator, &$advanceGenerator, &$lastYieldResult) { + $lastYieldResult = $value; + $generator->send($value); + $advanceGenerator(); + }, + function($reason) use ($generator, $advanceGenerator) { + if ($reason instanceof Exception) { + $generator->throw($reason); + } elseif (is_scalar($reason)) { + $generator->throw(new Exception($reason)); + } else { + $type = is_object($reason) ? get_class($reason) : gettype($reason); + $generator->throw(new Exception('Promise was rejected with reason of type: ' . $type)); + } + $advanceGenerator(); + } + )->error(function($reason) use ($promise) { + // This error handler would be called, if something in the + // generator throws an exception, and it's not caught + // locally. + $promise->reject($reason); + }); + // We need to break out of the loop, because $advanceGenerator + // will be called asynchronously when the promise has a result. + break; + } else { + // If the value was not a promise, we'll just let it pass through. + $lastYieldResult = $yieldedValue; + $generator->send($yieldedValue); + } + + } + + // If the generator is at the end, and we didn't run into an exception, + // we can fullfill the promise with the last thing that was yielded to + // us. + if (!$generator->valid() && $promise->state === Promise::PENDING) { + $promise->fulfill($lastYieldResult); + } + + }; + + try { + $advanceGenerator(); + } catch (Exception $e) { + $promise->reject($e); + } + + return $promise; + +} diff --git a/libs/composer/vendor/sabre/event/phpunit.xml.dist b/libs/composer/vendor/sabre/event/phpunit.xml.dist new file mode 100644 index 000000000000..ccd59be9c2a1 --- /dev/null +++ b/libs/composer/vendor/sabre/event/phpunit.xml.dist @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="vendor/autoload.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="sabre-event"> + <directory>tests/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">./lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/libs/composer/vendor/sabre/event/tests/ContinueCallbackTest.php b/libs/composer/vendor/sabre/event/tests/ContinueCallbackTest.php new file mode 100644 index 000000000000..c469913795f9 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/ContinueCallbackTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\Event; + +class ContinueCallbackTest extends \PHPUnit_Framework_TestCase { + + function testContinueCallBack() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return true; + }); + $this->assertTrue($r); + $this->assertEquals(3, $handlerCounter); + $this->assertEquals(2, $continueCounter); + + } + + function testContinueCallBackBreak() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return false; + }); + $this->assertTrue($r); + $this->assertEquals(1, $handlerCounter); + $this->assertEquals(1, $continueCounter); + + } + + function testContinueCallBackBreakByHandler() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + return false; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return false; + }); + $this->assertFalse($r); + $this->assertEquals(1, $handlerCounter); + $this->assertEquals(0, $continueCounter); + + } +} diff --git a/libs/composer/vendor/sabre/event/tests/CoroutineTest.php b/libs/composer/vendor/sabre/event/tests/CoroutineTest.php new file mode 100644 index 000000000000..6e4b666b0420 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/CoroutineTest.php @@ -0,0 +1,262 @@ +<?php + +namespace Sabre\Event; + +class CoroutineTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \InvalidArgumentException + */ + function testNonGenerator() { + + coroutine(function() {}); + + } + + function testBasicCoroutine() { + + $start = 0; + + coroutine(function() use (&$start) { + + $start += 1; + yield; + + }); + + $this->assertEquals(1, $start); + + } + + function testFulfilledPromise() { + + $start = 0; + $promise = new Promise(function($fulfill, $reject) { + $fulfill(2); + }); + + coroutine(function() use (&$start, $promise) { + + $start += 1; + $start += (yield $promise); + + }); + + Loop\run(); + $this->assertEquals(3, $start); + + } + + function testRejectedPromise() { + + $start = 0; + $promise = new Promise(function($fulfill, $reject) { + $reject(2); + }); + + coroutine(function() use (&$start, $promise) { + + $start += 1; + try { + $start += (yield $promise); + // This line is unreachable, but it's our control + $start += 4; + } catch (\Exception $e) { + $start += $e->getMessage(); + } + + }); + + Loop\run(); + $this->assertEquals(3, $start); + + } + + function testRejectedPromiseException() { + + $start = 0; + $promise = new Promise(function($fulfill, $reject) { + $reject(new \LogicException('2')); + }); + + coroutine(function() use (&$start, $promise) { + + $start += 1; + try { + $start += (yield $promise); + // This line is unreachable, but it's our control + $start += 4; + } catch (\LogicException $e) { + $start += $e->getMessage(); + } + + }); + + Loop\run(); + $this->assertEquals(3, $start); + + } + + function testRejectedPromiseArray() { + + $start = 0; + $promise = new Promise(function($fulfill, $reject) { + $reject([]); + }); + + coroutine(function() use (&$start, $promise) { + + $start += 1; + try { + $start += (yield $promise); + // This line is unreachable, but it's our control + $start += 4; + } catch (\Exception $e) { + $this->assertTrue(strpos($e->getMessage(), 'Promise was rejected with') === 0); + $start += 2; + } + + })->wait(); + + $this->assertEquals(3, $start); + + } + + function testFulfilledPromiseAsync() { + + $start = 0; + $promise = new Promise(); + coroutine(function() use (&$start, $promise) { + + $start += 1; + $start += (yield $promise); + + }); + Loop\run(); + + $this->assertEquals(1, $start); + + $promise->fulfill(2); + Loop\run(); + + $this->assertEquals(3, $start); + + } + + function testRejectedPromiseAsync() { + + $start = 0; + $promise = new Promise(); + coroutine(function() use (&$start, $promise) { + + $start += 1; + try { + $start += (yield $promise); + // This line is unreachable, but it's our control + $start += 4; + } catch (\Exception $e) { + $start += $e->getMessage(); + } + + }); + + $this->assertEquals(1, $start); + + $promise->reject(new \Exception(2)); + Loop\run(); + + $this->assertEquals(3, $start); + + } + + function testCoroutineException() { + + $start = 0; + coroutine(function() use (&$start) { + + $start += 1; + $start += (yield 2); + + throw new \Exception('4'); + + })->error(function($e) use (&$start) { + + $start += $e->getMessage(); + + }); + Loop\run(); + + $this->assertEquals(7, $start); + + } + + function testDeepException() { + + $start = 0; + $promise = new Promise(); + coroutine(function() use (&$start, $promise) { + + $start += 1; + $start += (yield $promise); + + })->error(function($e) use (&$start) { + + $start += $e->getMessage(); + + }); + + $this->assertEquals(1, $start); + + $promise->reject(new \Exception(2)); + Loop\run(); + + $this->assertEquals(3, $start); + + } + + function testResolveToLastYield() { + + $ok = false; + coroutine(function() { + + yield 1; + yield 2; + $hello = 'hi'; + + })->then(function($value) use (&$ok) { + $this->assertEquals(2, $value); + $ok = true; + })->error(function($reason) { + $this->fail($reason); + }); + Loop\run(); + + $this->assertTrue($ok); + + } + + function testResolveToLastYieldPromise() { + + $ok = false; + + $promise = new Promise(); + + coroutine(function() use ($promise) { + + yield 'fail'; + yield $promise; + $hello = 'hi'; + + })->then(function($value) use (&$ok) { + $ok = $value; + $this->fail($reason); + }); + + $promise->fulfill('omg it worked'); + Loop\run(); + + $this->assertEquals('omg it worked', $ok); + + } + +} diff --git a/libs/composer/vendor/sabre/event/tests/EventEmitterTest.php b/libs/composer/vendor/sabre/event/tests/EventEmitterTest.php new file mode 100644 index 000000000000..df08e9cd84a7 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/EventEmitterTest.php @@ -0,0 +1,318 @@ +<?php + +namespace Sabre\Event; + +class EventEmitterTest extends \PHPUnit_Framework_TestCase { + + function testInit() { + + $ee = new EventEmitter(); + $this->assertInstanceOf('Sabre\\Event\\EventEmitter', $ee); + + } + + function testListeners() { + + $ee = new EventEmitter(); + + $callback1 = function() { }; + $callback2 = function() { }; + $ee->on('foo', $callback1, 200); + $ee->on('foo', $callback2, 100); + + $this->assertEquals([$callback2, $callback1], $ee->listeners('foo')); + + } + + /** + * @depends testInit + */ + function testHandleEvent() { + + $argResult = null; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = $arg; + + }); + + $this->assertTrue( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals('bar', $argResult); + + } + + /** + * @depends testHandleEvent + */ + function testCancelEvent() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 2; + + }); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(1, $argResult); + + } + + /** + * @depends testCancelEvent + */ + function testPriority() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 2; + return false; + + }, 1); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(2, $argResult); + + } + + /** + * @depends testPriority + */ + function testPriority2() { + + $result = []; + $ee = new EventEmitter(); + + $ee->on('foo', function() use (&$result) { + + $result[] = 'a'; + + }, 200); + $ee->on('foo', function() use (&$result) { + + $result[] = 'b'; + + }, 50); + $ee->on('foo', function() use (&$result) { + + $result[] = 'c'; + + }, 300); + $ee->on('foo', function() use (&$result) { + + $result[] = 'd'; + + }); + + $ee->emit('foo'); + $this->assertEquals(['b', 'd', 'a', 'c'], $result); + + } + + function testRemoveListener() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertTrue( + $ee->removeListener('foo', $callBack) + ); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveUnknownListener() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertFalse($ee->removeListener('bar', $callBack)); + + $ee->emit('foo'); + $this->assertTrue($result); + + } + + function testRemoveListenerTwice() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertTrue( + $ee->removeListener('foo', $callBack) + ); + $this->assertFalse( + $ee->removeListener('foo', $callBack) + ); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveAllListeners() { + + $result = false; + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $ee->removeAllListeners('foo'); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveAllListenersNoArg() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + + $ee = new EventEmitter(); + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $ee->removeAllListeners(); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testOnce() { + + $result = 0; + + $callBack = function() use (&$result) { + + $result++; + + }; + + $ee = new EventEmitter(); + $ee->once('foo', $callBack); + + $ee->emit('foo'); + $ee->emit('foo'); + + $this->assertEquals(1, $result); + + } + + /** + * @depends testCancelEvent + */ + function testPriorityOnce() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->once('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->once('foo', function($arg) use (&$argResult) { + + $argResult = 2; + return false; + + }, 1); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(2, $argResult); + + } +} diff --git a/libs/composer/vendor/sabre/event/tests/Loop/FunctionsTest.php b/libs/composer/vendor/sabre/event/tests/Loop/FunctionsTest.php new file mode 100644 index 000000000000..08bf306c37fb --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/Loop/FunctionsTest.php @@ -0,0 +1,160 @@ +<?php + +namespace Sabre\Event\Loop; + +class FunctionsTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + // Always creating a fresh loop object. + instance(new Loop()); + + } + + function tearDown() { + + // Removing the global loop object. + instance(null); + + } + + function testNextTick() { + + $check = 0; + nextTick(function() use (&$check) { + + $check++; + + }); + + run(); + + $this->assertEquals(1, $check); + + } + + function testTimeout() { + + $check = 0; + setTimeout(function() use (&$check) { + + $check++; + + }, 0.02); + + run(); + + $this->assertEquals(1, $check); + + } + + function testTimeoutOrder() { + + $check = []; + setTimeout(function() use (&$check) { + + $check[] = 'a'; + + }, 0.2); + setTimeout(function() use (&$check) { + + $check[] = 'b'; + + }, 0.1); + setTimeout(function() use (&$check) { + + $check[] = 'c'; + + }, 0.3); + + run(); + + $this->assertEquals(['b', 'a', 'c'], $check); + + } + + function testSetInterval() { + + $check = 0; + $intervalId = null; + $intervalId = setInterval(function() use (&$check, &$intervalId) { + + $check++; + if ($check > 5) { + clearInterval($intervalId); + } + + }, 0.02); + + run(); + $this->assertEquals(6, $check); + + } + + function testAddWriteStream() { + + $h = fopen('php://temp', 'r+'); + addWriteStream($h, function() use ($h) { + + fwrite($h, 'hello world'); + removeWriteStream($h); + + }); + run(); + rewind($h); + $this->assertEquals('hello world', stream_get_contents($h)); + + } + + function testAddReadStream() { + + $h = fopen('php://temp', 'r+'); + fwrite($h, 'hello world'); + rewind($h); + + $result = null; + + addReadStream($h, function() use ($h, &$result) { + + $result = fgets($h); + removeReadStream($h); + + }); + run(); + $this->assertEquals('hello world', $result); + + } + + function testStop() { + + $check = 0; + setTimeout(function() use (&$check) { + $check++; + }, 200); + + nextTick(function() { + stop(); + }); + run(); + + $this->assertEquals(0, $check); + + } + + function testTick() { + + $check = 0; + setTimeout(function() use (&$check) { + $check++; + }, 1); + + nextTick(function() use (&$check) { + $check++; + }); + tick(); + + $this->assertEquals(1, $check); + + } + +} diff --git a/libs/composer/vendor/sabre/event/tests/Loop/LoopTest.php b/libs/composer/vendor/sabre/event/tests/Loop/LoopTest.php new file mode 100644 index 000000000000..a9cf551bddbb --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/Loop/LoopTest.php @@ -0,0 +1,180 @@ +<?php + +namespace Sabre\Event\Loop; + +class LoopTest extends \PHPUnit_Framework_TestCase { + + function testNextTick() { + + $loop = new Loop(); + $check = 0; + $loop->nextTick(function() use (&$check) { + + $check++; + + }); + + $loop->run(); + + $this->assertEquals(1, $check); + + } + + function testTimeout() { + + $loop = new Loop(); + $check = 0; + $loop->setTimeout(function() use (&$check) { + + $check++; + + }, 0.02); + + $loop->run(); + + $this->assertEquals(1, $check); + + } + + function testTimeoutOrder() { + + $loop = new Loop(); + $check = []; + $loop->setTimeout(function() use (&$check) { + + $check[] = 'a'; + + }, 0.2); + $loop->setTimeout(function() use (&$check) { + + $check[] = 'b'; + + }, 0.1); + $loop->setTimeout(function() use (&$check) { + + $check[] = 'c'; + + }, 0.3); + + $loop->run(); + + $this->assertEquals(['b', 'a', 'c'], $check); + + } + + function testSetInterval() { + + $loop = new Loop(); + $check = 0; + $intervalId = null; + $intervalId = $loop->setInterval(function() use (&$check, &$intervalId, $loop) { + + $check++; + if ($check > 5) { + $loop->clearInterval($intervalId); + } + + }, 0.02); + + $loop->run(); + $this->assertEquals(6, $check); + + } + + function testAddWriteStream() { + + $h = fopen('php://temp', 'r+'); + $loop = new Loop(); + $loop->addWriteStream($h, function() use ($h, $loop) { + + fwrite($h, 'hello world'); + $loop->removeWriteStream($h); + + }); + $loop->run(); + rewind($h); + $this->assertEquals('hello world', stream_get_contents($h)); + + } + + function testAddReadStream() { + + $h = fopen('php://temp', 'r+'); + fwrite($h, 'hello world'); + rewind($h); + + $loop = new Loop(); + + $result = null; + + $loop->addReadStream($h, function() use ($h, $loop, &$result) { + + $result = fgets($h); + $loop->removeReadStream($h); + + }); + $loop->run(); + $this->assertEquals('hello world', $result); + + } + + function testStop() { + + $check = 0; + $loop = new Loop(); + $loop->setTimeout(function() use (&$check) { + $check++; + }, 200); + + $loop->nextTick(function() use ($loop) { + $loop->stop(); + }); + $loop->run(); + + $this->assertEquals(0, $check); + + } + + function testTick() { + + $check = 0; + $loop = new Loop(); + $loop->setTimeout(function() use (&$check) { + $check++; + }, 1); + + $loop->nextTick(function() use ($loop, &$check) { + $check++; + }); + $loop->tick(); + + $this->assertEquals(1, $check); + + } + + /** + * Here we add a new nextTick function as we're in the middle of a current + * nextTick. + */ + function testNextTickStacking() { + + $loop = new Loop(); + $check = 0; + $loop->nextTick(function() use (&$check, $loop) { + + $loop->nextTick(function() use (&$check) { + + $check++; + + }); + $check++; + + }); + + $loop->run(); + + $this->assertEquals(2, $check); + + } + +} diff --git a/libs/composer/vendor/sabre/event/tests/Promise/FunctionsTest.php b/libs/composer/vendor/sabre/event/tests/Promise/FunctionsTest.php new file mode 100644 index 000000000000..51e47ae297b2 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/Promise/FunctionsTest.php @@ -0,0 +1,184 @@ +<?php + +namespace Sabre\Event\Promise; + +use Sabre\Event\Loop; +use Sabre\Event\Promise; + +class FunctionsTest extends \PHPUnit_Framework_TestCase { + + function testAll() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\all([$promise1, $promise2])->then(function($value) use (&$finalValue) { + + $finalValue = $value; + + }); + + $promise1->fulfill(1); + Loop\run(); + $this->assertEquals(0, $finalValue); + + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals([1, 2], $finalValue); + + } + + function testAllReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testAllRejectThenResolve() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testRace() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\race([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = $value; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->fulfill(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testRaceReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\race([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = $value; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testResolve() { + + $finalValue = 0; + + $promise = resolve(1); + $promise->then(function($value) use (&$finalValue) { + + $finalValue = $value; + + }); + + $this->assertEquals(0, $finalValue); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + /** + * @expectedException \Exception + */ + function testResolvePromise() { + + $finalValue = 0; + + $promise = new Promise(); + $promise->reject(new \Exception('uh oh')); + + $newPromise = resolve($promise); + $newPromise->wait(); + + } + + function testReject() { + + $finalValue = 0; + + $promise = reject(1); + $promise->then(function($value) use (&$finalValue) { + + $finalValue = 'im broken'; + + }, function($reason) use (&$finalValue) { + + $finalValue = $reason; + + }); + + $this->assertEquals(0, $finalValue); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + +} diff --git a/libs/composer/vendor/sabre/event/tests/Promise/PromiseTest.php b/libs/composer/vendor/sabre/event/tests/Promise/PromiseTest.php new file mode 100644 index 000000000000..698383978819 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/Promise/PromiseTest.php @@ -0,0 +1,341 @@ +<?php + +namespace Sabre\Event\Promise; + +use Sabre\Event\Loop; +use Sabre\Event\Promise; + +class PromiseTest extends \PHPUnit_Framework_TestCase { + + function testSuccess() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + Loop\run(); + + $this->assertEquals(3, $finalValue); + + } + + function testFail() { + + $finalValue = 0; + $promise = new Promise(); + $promise->reject(1); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + Loop\run(); + + $this->assertEquals(3, $finalValue); + + } + + function testChain() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + return $finalValue; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + Loop\run(); + + $this->assertEquals(7, $finalValue); + + } + function testChainPromise() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $subPromise = new Promise(); + + $promise->then(function($value) use ($subPromise) { + return $subPromise; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + + $subPromise->fulfill(2); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testPendingResult() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->fulfill(4); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testPendingFail() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->reject(4); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testExecutorSuccess() { + + $promise = (new Promise(function($success, $fail) { + + $success('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = $result; + + }); + Loop\run(); + + $this->assertEquals('hi', $realResult); + + } + + function testExecutorFail() { + + $promise = (new Promise(function($success, $fail) { + + $fail('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = 'incorrect'; + + }, function($reason) use (&$realResult) { + + $realResult = $reason; + + }); + Loop\run(); + + $this->assertEquals('hi', $realResult); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testFulfillTwice() { + + $promise = new Promise(); + $promise->fulfill(1); + $promise->fulfill(1); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testRejectTwice() { + + $promise = new Promise(); + $promise->reject(1); + $promise->reject(1); + + } + + function testFromFailureHandler() { + + $ok = 0; + $promise = new Promise(); + $promise->otherwise(function($reason) { + + $this->assertEquals('foo', $reason); + throw new \Exception('hi'); + + })->then(function() use (&$ok) { + + $ok = -1; + + }, function() use (&$ok) { + + $ok = 1; + + }); + + $this->assertEquals(0, $ok); + $promise->reject('foo'); + Loop\run(); + + $this->assertEquals(1, $ok); + + } + + function testAll() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then(function($value) use (&$finalValue) { + + $finalValue = $value; + + }); + + $promise1->fulfill(1); + Loop\run(); + $this->assertEquals(0, $finalValue); + + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals([1, 2], $finalValue); + + } + + function testAllReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testAllRejectThenResolve() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testWaitResolve() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->fulfill(1); + }); + $this->assertEquals( + 1, + $promise->wait() + ); + + } + + /** + * @expectedException \LogicException + */ + function testWaitWillNeverResolve() { + + $promise = new Promise(); + $promise->wait(); + + } + + function testWaitRejectedException() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject(new \OutOfBoundsException('foo')); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('OutOfBoundsException', $e); + $this->assertEquals('foo', $e->getMessage()); + } + + } + + function testWaitRejectedScalar() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject('foo'); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('Exception', $e); + $this->assertEquals('foo', $e->getMessage()); + } + + } + + function testWaitRejectedNonScalar() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject([]); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('Exception', $e); + $this->assertEquals('Promise was rejected with reason of type: array', $e->getMessage()); + } + + } +} diff --git a/libs/composer/vendor/sabre/event/tests/PromiseTest.php b/libs/composer/vendor/sabre/event/tests/PromiseTest.php new file mode 100644 index 000000000000..0029d898e536 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/PromiseTest.php @@ -0,0 +1,386 @@ +<?php + +namespace Sabre\Event; + +class PromiseTest extends \PHPUnit_Framework_TestCase { + + function testSuccess() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + Loop\run(); + + $this->assertEquals(3, $finalValue); + + } + + function testFail() { + + $finalValue = 0; + $promise = new Promise(); + $promise->reject(1); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + Loop\run(); + + $this->assertEquals(3, $finalValue); + + } + + function testChain() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + return $finalValue; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + Loop\run(); + + $this->assertEquals(7, $finalValue); + + } + function testChainPromise() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $subPromise = new Promise(); + + $promise->then(function($value) use ($subPromise) { + return $subPromise; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + + $subPromise->fulfill(2); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testPendingResult() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->fulfill(4); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testPendingFail() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->reject(4); + Loop\run(); + + $this->assertEquals(6, $finalValue); + + } + + function testExecutorSuccess() { + + $promise = (new Promise(function($success, $fail) { + + $success('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = $result; + + }); + Loop\run(); + + $this->assertEquals('hi', $realResult); + + } + + function testExecutorFail() { + + $promise = (new Promise(function($success, $fail) { + + $fail('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = 'incorrect'; + + }, function($reason) use (&$realResult) { + + $realResult = $reason; + + }); + Loop\run(); + + $this->assertEquals('hi', $realResult); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testFulfillTwice() { + + $promise = new Promise(); + $promise->fulfill(1); + $promise->fulfill(1); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testRejectTwice() { + + $promise = new Promise(); + $promise->reject(1); + $promise->reject(1); + + } + + function testFromFailureHandler() { + + $ok = 0; + $promise = new Promise(); + $promise->otherwise(function($reason) { + + $this->assertEquals('foo', $reason); + throw new \Exception('hi'); + + })->then(function() use (&$ok) { + + $ok = -1; + + }, function() use (&$ok) { + + $ok = 1; + + }); + + $this->assertEquals(0, $ok); + $promise->reject('foo'); + Loop\run(); + + $this->assertEquals(1, $ok); + + } + + function testAll() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then(function($value) use (&$finalValue) { + + $finalValue = $value; + + }); + + $promise1->fulfill(1); + Loop\run(); + $this->assertEquals(0, $finalValue); + + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals([1, 2], $finalValue); + + } + + function testAllReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testAllRejectThenResolve() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testRace() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\race([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = $value; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->fulfill(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->fulfill(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testRaceReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise\race([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = $value; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + Loop\run(); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + Loop\run(); + $this->assertEquals(1, $finalValue); + + } + + function testWaitResolve() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->fulfill(1); + }); + $this->assertEquals( + 1, + $promise->wait() + ); + + } + + /** + * @expectedException \LogicException + */ + function testWaitWillNeverResolve() { + + $promise = new Promise(); + $promise->wait(); + + } + + function testWaitRejectedException() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject(new \OutOfBoundsException('foo')); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('OutOfBoundsException', $e); + $this->assertEquals('foo', $e->getMessage()); + } + + } + + function testWaitRejectedScalar() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject('foo'); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('Exception', $e); + $this->assertEquals('foo', $e->getMessage()); + } + + } + + function testWaitRejectedNonScalar() { + + $promise = new Promise(); + Loop\nextTick(function() use ($promise) { + $promise->reject([]); + }); + try { + $promise->wait(); + $this->fail('We did not get the expected exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('Exception', $e); + $this->assertEquals('Promise was rejected with reason of type: array', $e->getMessage()); + } + + } +} diff --git a/libs/composer/vendor/sabre/event/tests/benchmark/bench.php b/libs/composer/vendor/sabre/event/tests/benchmark/bench.php new file mode 100644 index 000000000000..b1e6b1d47879 --- /dev/null +++ b/libs/composer/vendor/sabre/event/tests/benchmark/bench.php @@ -0,0 +1,116 @@ +<?php + +use Sabre\Event\EventEmitter; + +include __DIR__ . '/../../vendor/autoload.php'; + +abstract class BenchMark { + + protected $startTime; + protected $iterations = 10000; + protected $totalTime; + + function setUp() { + + } + + abstract function test(); + + function go() { + + $this->setUp(); + $this->startTime = microtime(true); + $this->test(); + $this->totalTime = microtime(true) - $this->startTime; + return $this->totalTime; + + } + +} + +class OneCallBack extends BenchMark { + + protected $emitter; + protected $iterations = 100000; + + function setUp() { + + $this->emitter = new EventEmitter(); + $this->emitter->on('foo', function() { + // NOOP + }); + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +class ManyCallBacks extends BenchMark { + + protected $emitter; + + function setUp() { + + $this->emitter = new EventEmitter(); + for ($i = 0;$i < 100;$i++) { + $this->emitter->on('foo', function() { + // NOOP + }); + } + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +class ManyPrioritizedCallBacks extends BenchMark { + + protected $emitter; + + function setUp() { + + $this->emitter = new EventEmitter(); + for ($i = 0;$i < 100;$i++) { + $this->emitter->on('foo', function() { + }, 1000 - $i); + } + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +$tests = [ + 'OneCallBack', + 'ManyCallBacks', + 'ManyPrioritizedCallBacks', +]; + +foreach ($tests as $test) { + + $testObj = new $test(); + $result = $testObj->go(); + echo $test . " " . $result . "\n"; + +} diff --git a/libs/composer/vendor/sabre/http/.gitignore b/libs/composer/vendor/sabre/http/.gitignore new file mode 100644 index 000000000000..8c97686fb571 --- /dev/null +++ b/libs/composer/vendor/sabre/http/.gitignore @@ -0,0 +1,15 @@ +# Composer +vendor/ +composer.lock + +# Tests +tests/cov/ + +# Composer binaries +bin/phpunit +bin/phpcs +bin/php-cs-fixer +bin/sabre-cs-fixer + +# Vim +.*.swp diff --git a/libs/composer/vendor/sabre/http/.travis.yml b/libs/composer/vendor/sabre/http/.travis.yml new file mode 100644 index 000000000000..9e4964b9d6d2 --- /dev/null +++ b/libs/composer/vendor/sabre/http/.travis.yml @@ -0,0 +1,28 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - 7.1 + - hhvm + +matrix: + fast_finish: true + allow_failures: + - php: hhvm + +env: + matrix: + - PREFER_LOWEST="" + - PREFER_LOWEST="--prefer-lowest" + + +before_script: + - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini +# - composer self-update + - composer update --prefer-source $PREFER_LOWEST + +script: + - ./bin/phpunit --configuration tests/phpunit.xml + - ./bin/sabre-cs-fixer fix . --dry-run --diff diff --git a/libs/composer/vendor/sabre/http/CHANGELOG.md b/libs/composer/vendor/sabre/http/CHANGELOG.md new file mode 100644 index 000000000000..d3dd8b2d963b --- /dev/null +++ b/libs/composer/vendor/sabre/http/CHANGELOG.md @@ -0,0 +1,269 @@ +ChangeLog +========= + +4.2.4 (2018-02-23) +------------------ + +* #69: Sending `charset="UTF-8"` on Basic authentication challenges per + [rfc7617][rfc7617]. + + +4.2.3 (2017-06-12) +------------------ + +* #74, #77: Work around 4GB file size limit at 32 Bit systems + + +4.2.2 (2017-01-02) +------------------ + +* #72: Handling clients that send invalid `Content-Length` headers. + +4.2.1 (2016-01-06) +------------------ + +* #56: `getBodyAsString` now returns at most as many bytes as the contents of + the `Content-Length` header. This allows users to pass much larger strings + without having to copy and truncate them. +* The client now sets a default `User-Agent` header identifying this library. + + +4.2.0 (2016-01-04) +------------------ + +* This package now supports sabre/event 3.0. + + +4.1.0 (2015-09-04) +------------------ + +* The async client wouldn't `wait()` for new http requests being started + after the (previous) last request in the queue was resolved. +* Added `Sabre\HTTP\Auth\Bearer`, to easily extract a OAuth2 bearer token. + + +4.0.0 (2015-05-20) +------------------ + +* Deprecated: All static functions from `Sabre\HTTP\URLUtil` and + `Sabre\HTTP\Util` moved to a separate `functions.php`, which is also + autoloaded. The old functions are still there, but will be removed in a + future version. (#49) + + +4.0.0-alpha3 (2015-05-19) +------------------------- + +* Added a parser for the HTTP `Prefer` header, as defined in [RFC7240][rfc7240]. +* Deprecated `Sabre\HTTP\Util::parseHTTPDate`, use `Sabre\HTTP\parseDate()`. +* Deprecated `Sabre\HTTP\Util::toHTTPDate` use `Sabre\HTTP\toDate()`. + + +4.0.0-alpha2 (2015-05-18) +------------------------- + +* #45: Don't send more data than what is promised in the HTTP content-length. + (@dratini0). +* #43: `getCredentials` returns null if incomplete. (@Hywan) +* #48: Now using php-cs-fixer to make our CS consistent (yay!) +* This includes fixes released in version 3.0.5. + + +4.0.0-alpha1 (2015-02-25) +------------------------- + +* #41: Fixing bugs related to comparing URLs in `Request::getPath()`. +* #41: This library now uses the `sabre/uri` package for uri handling. +* Added `421 Misdirected Request` from the HTTP/2.0 spec. + + +3.0.5 (2015-05-11) +------------------ + +* #47 #35: When re-using the client and doing any request after a `HEAD` + request, the client discards the body. + + +3.0.4 (2014-12-10) +------------------ + +* #38: The Authentication helpers no longer overwrite any existing + `WWW-Authenticate` headers, but instead append new headers. This ensures + that multiple authentication systems can exist in the same environment. + + +3.0.3 (2014-12-03) +------------------ + +* Hiding `Authorization` header value from `Request::__toString`. + + +3.0.2 (2014-10-09) +------------------ + +* When parsing `Accept:` headers, we're ignoring invalid parts. Before we + would throw a PHP E_NOTICE. + + +3.0.1 (2014-09-29) +------------------ + +* Minor change in unittests. + + +3.0.0 (2014-09-23) +------------------ + +* `getHeaders()` now returns header values as an array, just like psr/http. +* Added `hasHeader()`. + + +2.1.0-alpha1 (2014-09-15) +------------------------- + +* Changed: Copied most of the header-semantics for the PSR draft for + representing HTTP messages. [Reference here][psr-http]. +* This means that `setHeaders()` does not wipe out every existing header + anymore. +* We also support multiple headers with the same name. +* Use `Request::getHeaderAsArray()` and `Response::getHeaderAsArray()` to + get a hold off multiple headers with the same name. +* If you use `getHeader()`, and there's more than 1 header with that name, we + concatenate all these with a comma. +* `addHeader()` will now preserve an existing header with that name, and add a + second header with the same name. +* The message class should be a lot faster now for looking up headers. No more + array traversal, because we maintain a tiny index. +* Added: `URLUtil::resolve()` to make resolving relative urls super easy. +* Switched to PSR-4. +* #12: Circumventing CURL's FOLLOW_LOCATION and doing it in PHP instead. This + fixes compatibility issues with people that have open_basedir turned on. +* Added: Content negotiation now correctly support mime-type parameters such as + charset. +* Changed: `Util::negotiate()` is now deprecated. Use + `Util::negotiateContentType()` instead. +* #14: The client now only follows http and https urls. + + +2.0.4 (2014-07-14) +------------------ + +* Changed: No longer escaping @ in urls when it's not needed. +* Fixed: #7: Client now correctly deals with responses without a body. + + +2.0.3 (2014-04-17) +------------------ + +* Now works on hhvm! +* Fixed: Now throwing an error when a Request object is being created with + arguments that were valid for sabre/http 1.0. Hopefully this will aid with + debugging for upgraders. + + +2.0.2 (2014-02-09) +------------------ + +* Fixed: Potential security problem in the client. + + +2.0.1 (2014-01-09) +------------------ + +* Fixed: getBodyAsString on an empty body now works. +* Fixed: Version string + + +2.0.0 (2014-01-08) +------------------ + +* Removed: Request::createFromPHPRequest. This is now handled by + Sapi::getRequest. + + +2.0.0alpha6 (2014-01-03) +------------------------ + +* Added: Asynchronous HTTP client. See examples/asyncclient.php. +* Fixed: Issue #4: Don't escape colon (:) when it's not needed. +* Fixed: Fixed a bug in the content negotation script. +* Fixed: Fallback for when CURLOPT_POSTREDIR is not defined (mainly for hhvm). +* Added: The Request and Response object now have a `__toString()` method that + serializes the objects into a standard HTTP message. This is mainly for + debugging purposes. +* Changed: Added Response::getStatusText(). This method returns the + human-readable HTTP status message. This part has been removed from + Response::getStatus(), which now always returns just the status code as an + int. +* Changed: Response::send() is now Sapi::sendResponse($response). +* Changed: Request::createFromPHPRequest is now Sapi::getRequest(). +* Changed: Message::getBodyAsStream and Message::getBodyAsString were added. The + existing Message::getBody changed it's behavior, so be careful. + + +2.0.0alpha5 (2013-11-07) +------------------------ + +* Added: HTTP Status 451 Unavailable For Legal Reasons. Fight government + censorship! +* Added: Ability to catch and retry http requests in the client when a curl + error occurs. +* Changed: Request::getPath does not return the query part of the url, so + everything after the ? is stripped. +* Added: a reverse proxy example. + + +2.0.0alpha4 (2013-08-07) +------------------------ + +* Fixed: Doing a GET request with the client uses the last used HTTP method + instead. +* Added: HttpException +* Added: The Client class can now automatically emit exceptions when HTTP errors + occurred. + + +2.0.0alpha3 (2013-07-24) +------------------------ + +* Changed: Now depends on sabre/event package. +* Changed: setHeaders() now overwrites any existing http headers. +* Added: getQueryParameters to RequestInterface. +* Added: Util::negotiate. +* Added: RequestDecorator, ResponseDecorator. +* Added: A very simple HTTP client. +* Added: addHeaders() to append a list of new headers. +* Fixed: Not erroring on unknown HTTP status codes. +* Fixed: Throwing exceptions on invalid HTTP status codes (not 3 digits). +* Fixed: Much better README.md +* Changed: getBody() now uses a bitfield to specify what type to return. + + +2.0.0alpha2 (2013-07-02) +------------------------ + +* Added: Digest & AWS Authentication. +* Added: Message::getHttpVersion and Message::setHttpVersion. +* Added: Request::setRawServerArray, getRawServerValue. +* Added: Request::createFromPHPRequest +* Added: Response::send +* Added: Request::getQueryParameters +* Added: Utility for dealing with HTTP dates. +* Added: Request::setPostData and Request::getPostData. +* Added: Request::setAbsoluteUrl and Request::getAbsoluteUrl. +* Added: URLUtil, methods for calculation relative and base urls. +* Removed: Response::sendBody + + +2.0.0alpha1 (2012-10-07) +------------------------ + +* Fixed: Lots of small naming improvements +* Added: Introduction of Message, MessageInterface, Response, ResponseInterface. + +Before 2.0.0, this package was built-into SabreDAV, where it first appeared in +January 2009. + +[psr-http]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md +[rfc7240]: http://tools.ietf.org/html/rfc7240 +[rfc7617]: https://tools.ietf.org/html/rfc7617 diff --git a/libs/composer/vendor/sabre/http/LICENSE b/libs/composer/vendor/sabre/http/LICENSE new file mode 100644 index 000000000000..864041b22ca1 --- /dev/null +++ b/libs/composer/vendor/sabre/http/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2009-2017 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/http/README.md b/libs/composer/vendor/sabre/http/README.md new file mode 100644 index 000000000000..ae03a796e133 --- /dev/null +++ b/libs/composer/vendor/sabre/http/README.md @@ -0,0 +1,746 @@ +sabre/http +========== + +This library provides a toolkit to make working with the HTTP protocol easier. + +Most PHP scripts run within a HTTP request but accessing information about the +HTTP request is cumbersome at least. + +There's bad practices, inconsistencies and confusion. This library is +effectively a wrapper around the following PHP constructs: + +For Input: + +* `$_GET`, +* `$_POST`, +* `$_SERVER`, +* `php://input` or `$HTTP_RAW_POST_DATA`. + +For output: + +* `php://output` or `echo`, +* `header()`. + +What this library provides, is a `Request` object, and a `Response` object. + +The objects are extendable and easily mockable. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) | +| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) | + +Installation +------------ + +Make sure you have [composer][1] installed. In your project directory, create, +or edit a `composer.json` file, and make sure it contains something like this: + +```json +{ + "require" : { + "sabre/http" : "~3.0.0" + } +} +``` + +After that, just hit `composer install` and you should be rolling. + +Quick history +------------- + +This library came to existence in 2009, as a part of the [`sabre/dav`][2] +project, which uses it heavily. + +It got split off into a separate library to make it easier to manage +releases and hopefully giving it use outside of the scope of just `sabre/dav`. + +Although completely independently developed, this library has a LOT of +overlap with [Symfony's `HttpFoundation`][3]. + +Said library does a lot more stuff and is significantly more popular, +so if you are looking for something to fulfill this particular requirement, +I'd recommend also considering [`HttpFoundation`][3]. + + +Getting started +--------------- + +First and foremost, this library wraps the superglobals. The easiest way to +instantiate a request object is as follows: + +```php +use Sabre\HTTP; + +include 'vendor/autoload.php'; + +$request = HTTP\Sapi::getRequest(); +``` + +This line should only happen once in your entire application. Everywhere else +you should pass this request object around using dependency injection. + +You should always typehint on it's interface: + +```php +function handleRequest(HTTP\RequestInterface $request) { + + // Do something with this request :) + +} +``` + +A response object you can just create as such: + +```php +use Sabre\HTTP; + +include 'vendor/autoload.php'; + +$response = new HTTP\Response(); +$response->setStatus(201); // created ! +$response->setHeader('X-Foo', 'bar'); +$response->setBody( + 'success!' +); + +``` + +After you fully constructed your response, you must call: + +```php +HTTP\Sapi::sendResponse($response); +``` + +This line should generally also appear once in your application (at the very +end). + +Decorators +---------- + +It may be useful to extend the `Request` and `Response` objects in your +application, if you for example would like them to carry a bit more +information about the current request. + +For instance, you may want to add an `isLoggedIn` method to the Request +object. + +Simply extending Request and Response may pose some problems: + +1. You may want to extend the objects with new behaviors differently, in + different subsystems of your application, +2. The `Sapi::getRequest` factory always returns a instance of + `Request` so you would have to override the factory method as well, +3. By controlling the instantation and depend on specific `Request` and + `Response` instances in your library or application, you make it harder to + work with other applications which also use `sabre/http`. + +In short: it would be bad design. Instead, it's recommended to use the +[decorator pattern][6] to add new behavior where you need it. `sabre/http` +provides helper classes to quickly do this. + +Example: + +```php +use Sabre\HTTP; + +class MyRequest extends HTTP\RequestDecorator { + + function isLoggedIn() { + + return true; + + } + +} +``` + +Our application assumes that the true `Request` object was instantiated +somewhere else, by some other subsystem. This could simply be a call like +`$request = Sapi::getRequest()` at the top of your application, +but could also be somewhere in a unittest. + +All we know in the current subsystem, is that we received a `$request` and +that it implements `Sabre\HTTP\RequestInterface`. To decorate this object, +all we need to do is: + +```php +$request = new MyRequest($request); +``` + +And that's it, we now have an `isLoggedIn` method, without having to mess +with the core instances. + + +Client +------ + +This package also contains a simple wrapper around [cURL][4], which will allow +you to write simple clients, using the `Request` and `Response` objects you're +already familiar with. + +It's by no means a replacement for something like [Guzzle][7], but it provides +a simple and lightweight API for making the occasional API call. + +### Usage + +```php +use Sabre\HTTP; + +$request = new HTTP\Request('GET', 'http://example.org/'); +$request->setHeader('X-Foo', 'Bar'); + +$client = new HTTP\Client(); +$response = $client->send($request); + +echo $response->getBodyAsString(); +``` + +The client emits 3 event using [`sabre/event`][5]. `beforeRequest`, +`afterRequest` and `error`. + +```php +$client = new HTTP\Client(); +$client->on('beforeRequest', function($request) { + + // You could use beforeRequest to for example inject a few extra headers. + // into the Request object. + +}); + +$client->on('afterRequest', function($request, $response) { + + // The afterRequest event could be a good time to do some logging, or + // do some rewriting in the response. + +}); + +$client->on('error', function($request, $response, &$retry, $retryCount) { + + // The error event is triggered for every response with a HTTP code higher + // than 399. + +}); + +$client->on('error:401', function($request, $response, &$retry, $retryCount) { + + // You can also listen for specific error codes. This example shows how + // to inject HTTP authentication headers if a 401 was returned. + + if ($retryCount > 1) { + // We're only going to retry exactly once. + } + + $request->setHeader('Authorization', 'Basic xxxxxxxxxx'); + $retry = true; + +}); +``` + +### Asynchronous requests + +The `Client` also supports doing asynchronous requests. This is especially handy +if you need to perform a number of requests, that are allowed to be executed +in parallel. + +The underlying system for this is simply [cURL's multi request handler][8], +but this provides a much nicer API to handle this. + +Sample usage: + +```php + +use Sabre\HTTP; + +$request = new Request('GET', 'http://localhost/'); +$client = new Client(); + +// Executing 1000 requests +for ($i = 0; $i < 1000; $i++) { + $client->sendAsync( + $request, + function(ResponseInterface $response) { + // Success handler + }, + function($error) { + // Error handler + } + ); +} + +// Wait for all requests to get a result. +$client->wait(); + +``` + +Check out `examples/asyncclient.php` for more information. + +Writing a reverse proxy +----------------------- + +With all these tools combined, it becomes very easy to write a simple reverse +http proxy. + +```php +use + Sabre\HTTP\Sapi, + Sabre\HTTP\Client; + +// The url we're proxying to. +$remoteUrl = 'http://example.org/'; + +// The url we're proxying from. Please note that this must be a relative url, +// and basically acts as the base url. +// +// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't +// either. +$myBaseUrl = '/reverseproxy.php'; +// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/'; + +$request = Sapi::getRequest(); +$request->setBaseUrl($myBaseUrl); + +$subRequest = clone $request; + +// Removing the Host header. +$subRequest->removeHeader('Host'); + +// Rewriting the url. +$subRequest->setUrl($remoteUrl . $request->getPath()); + +$client = new Client(); + +// Sends the HTTP request to the server +$response = $client->send($subRequest); + +// Sends the response back to the client that connected to the proxy. +Sapi::sendResponse($response); +``` + +The Request and Response API's +------------------------------ + +### Request + +```php + +/** + * Creates the request object + * + * @param string $method + * @param string $url + * @param array $headers + * @param resource $body + */ +public function __construct($method = null, $url = null, array $headers = null, $body = null); + +/** + * Returns the current HTTP method + * + * @return string + */ +function getMethod(); + +/** + * Sets the HTTP method + * + * @param string $method + * @return void + */ +function setMethod($method); + +/** + * Returns the request url. + * + * @return string + */ +function getUrl(); + +/** + * Sets the request url. + * + * @param string $url + * @return void + */ +function setUrl($url); + +/** + * Returns the absolute url. + * + * @return string + */ +function getAbsoluteUrl(); + +/** + * Sets the absolute url. + * + * @param string $url + * @return void + */ +function setAbsoluteUrl($url); + +/** + * Returns the current base url. + * + * @return string + */ +function getBaseUrl(); + +/** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ +function setBaseUrl($url); + +/** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ +function getPath(); + +/** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ +function getQueryParameters(); + +/** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ +function getPostData(); + +/** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ +function setPostData(array $postData); + +/** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ +function getRawServerValue($valueName); + +/** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ +function setRawServerData(array $data); + +/** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ +function getBodyAsStream(); + +/** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ +function getBodyAsString(); + +/** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ +function getBody(); + +/** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ +function setBody($body); + +/** + * Returns all the HTTP headers as an array. + * + * @return array + */ +function getHeaders(); + +/** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * + * If the header does not exist, this method must return null. + * + * @param string $name + * @return string|null + */ +function getHeader($name); + +/** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * @param string $name + * @param string $value + * @return void + */ +function setHeader($name, $value); + +/** + * Resets HTTP headers + * + * This method overwrites all existing HTTP headers + * + * @param array $headers + * @return void + */ +function setHeaders(array $headers); + +/** + * Adds a new set of HTTP headers. + * + * Any header specified in the array that already exists will be + * overwritten, but any other existing headers will be retained. + * + * @param array $headers + * @return void + */ +function addHeaders(array $headers); + +/** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ +function removeHeader($name); + +/** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ +function setHttpVersion($version); + +/** + * Returns the HTTP version. + * + * @return string + */ +function getHttpVersion(); +``` + +### Response + +```php +/** + * Returns the current HTTP status. + * + * This is the status-code as well as the human readable string. + * + * @return string + */ +function getStatus(); + +/** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentExeption + * @return void + */ +function setStatus($status); + +/** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ +function getBodyAsStream(); + +/** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ +function getBodyAsString(); + +/** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ +function getBody(); + + +/** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ +function setBody($body); + +/** + * Returns all the HTTP headers as an array. + * + * @return array + */ +function getHeaders(); + +/** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * + * If the header does not exist, this method must return null. + * + * @param string $name + * @return string|null + */ +function getHeader($name); + +/** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * @param string $name + * @param string $value + * @return void + */ +function setHeader($name, $value); + +/** + * Resets HTTP headers + * + * This method overwrites all existing HTTP headers + * + * @param array $headers + * @return void + */ +function setHeaders(array $headers); + +/** + * Adds a new set of HTTP headers. + * + * Any header specified in the array that already exists will be + * overwritten, but any other existing headers will be retained. + * + * @param array $headers + * @return void + */ +function addHeaders(array $headers); + +/** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ +function removeHeader($name); + +/** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ +function setHttpVersion($version); + +/** + * Returns the HTTP version. + * + * @return string + */ +function getHttpVersion(); +``` + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://getcomposer.org/ +[2]: http://sabre.io/ +[3]: https://github.com/symfony/HttpFoundation +[4]: http://php.net/curl +[5]: https://github.com/fruux/sabre-event +[6]: http://en.wikipedia.org/wiki/Decorator_pattern +[7]: http://guzzlephp.org/ +[8]: http://php.net/curl_multi_init diff --git a/libs/composer/vendor/sabre/http/bin/.empty b/libs/composer/vendor/sabre/http/bin/.empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/composer/vendor/sabre/http/composer.json b/libs/composer/vendor/sabre/http/composer.json new file mode 100644 index 000000000000..507d5d28df01 --- /dev/null +++ b/libs/composer/vendor/sabre/http/composer.json @@ -0,0 +1,44 @@ +{ + "name": "sabre/http", + "description" : "The sabre/http library provides utilities for dealing with http requests and responses. ", + "keywords" : [ "HTTP" ], + "homepage" : "https://github.com/fruux/sabre-http", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.4", + "ext-mbstring" : "*", + "ext-ctype" : "*", + "sabre/event" : ">=1.0.0,<4.0.0", + "sabre/uri" : "~1.0" + }, + "require-dev" : { + "phpunit/phpunit" : "~4.3", + "sabre/cs" : "~0.0.1" + }, + "suggest" : { + "ext-curl" : " to make http requests with the Client class" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-http" + }, + "autoload" : { + "files" : [ + "lib/functions.php" + ], + "psr-4" : { + "Sabre\\HTTP\\" : "lib/" + } + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/libs/composer/vendor/sabre/http/examples/asyncclient.php b/libs/composer/vendor/sabre/http/examples/asyncclient.php new file mode 100644 index 000000000000..b399e1e4f7b9 --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/asyncclient.php @@ -0,0 +1,65 @@ +<?php + +/** + * This example demonstrates the ability for clients to work asynchronously. + * + * By default up to 10 requests will be executed in paralel. HTTP connections + * are re-used and DNS is cached, all thanks to the power of curl. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Client; +use Sabre\HTTP\Request; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +// This is the request we're repeating a 1000 times. +$request = new Request('GET', 'http://localhost/'); +$client = new Client(); + +for ($i = 0; $i < 1000; $i++) { + + echo "$i sending\n"; + $client->sendAsync( + $request, + + // This is the 'success' callback + function($response) use ($i) { + echo "$i -> " . $response->getStatus() . "\n"; + }, + + // This is the 'error' callback. It is called for general connection + // problems (such as not being able to connect to a host, dns errors, + // etc.) and also cases where a response was returned, but it had a + // status code of 400 or higher. + function($error) use ($i) { + + if ($error['status'] === Client::STATUS_CURLERROR) { + // Curl errors + echo "$i -> curl error: " . $error['curl_errmsg'] . "\n"; + } else { + // HTTP errors + echo "$i -> " . $error['response']->getStatus() . "\n"; + } + } + ); +} + +// After everything is done, we call 'wait'. This causes the client to wait for +// all outstanding http requests to complete. +$client->wait(); diff --git a/libs/composer/vendor/sabre/http/examples/basicauth.php b/libs/composer/vendor/sabre/http/examples/basicauth.php new file mode 100644 index 000000000000..58bb78992689 --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/basicauth.php @@ -0,0 +1,55 @@ +<?php + +/** + * This example shows how to do Basic authentication. + * * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +$userList = [ + "user1" => "password", + "user2" => "password", +]; + +use Sabre\HTTP\Auth; +use Sabre\HTTP\Response; +use Sabre\HTTP\Sapi; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = Sapi::getRequest(); +$response = new Response(); + +$basicAuth = new Auth\Basic("Locked down area", $request, $response); +if (!$userPass = $basicAuth->getCredentials()) { + + // No username or password given + $basicAuth->requireLogin(); + +} elseif (!isset($userList[$userPass[0]]) || $userList[$userPass[0]] !== $userPass[1]) { + + // Username or password are incorrect + $basicAuth->requireLogin(); +} else { + + // Success ! + $response->setBody('You are logged in!'); + +} + +// Sending the response +Sapi::sendResponse($response); diff --git a/libs/composer/vendor/sabre/http/examples/client.php b/libs/composer/vendor/sabre/http/examples/client.php new file mode 100644 index 000000000000..d16c1651b6cb --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/client.php @@ -0,0 +1,38 @@ +<?php + +/** + * This example shows how to make a HTTP request with the Request and Response + * objects. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Client; +use Sabre\HTTP\Request; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +// Constructing the request. +$request = new Request('GET', 'http://localhost/'); + +$client = new Client(); +//$client->addCurlSetting(CURLOPT_PROXY,'localhost:8888'); +$response = $client->send($request); + +echo "Response:\n"; + +echo (string)$response; diff --git a/libs/composer/vendor/sabre/http/examples/digestauth.php b/libs/composer/vendor/sabre/http/examples/digestauth.php new file mode 100644 index 000000000000..30a5501eb562 --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/digestauth.php @@ -0,0 +1,56 @@ +<?php + +/** + * This example shows how to do Digest authentication. + * * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Markus Staab + * @license http://sabre.io/license/ Modified BSD License + */ +$userList = [ + "user1" => "password", + "user2" => "password", +]; + +use Sabre\HTTP\Auth; +use Sabre\HTTP\Response; +use Sabre\HTTP\Sapi; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = Sapi::getRequest(); +$response = new Response(); + +$digestAuth = new Auth\Digest("Locked down area", $request, $response); +$digestAuth->init(); +if (!$userName = $digestAuth->getUsername()) { + + // No username given + $digestAuth->requireLogin(); + +} elseif (!isset($userList[$userName]) || !$digestAuth->validatePassword($userList[$userName])) { + + // Username or password are incorrect + $digestAuth->requireLogin(); +} else { + + // Success ! + $response->setBody('You are logged in!'); + +} + +// Sending the response +Sapi::sendResponse($response); diff --git a/libs/composer/vendor/sabre/http/examples/reverseproxy.php b/libs/composer/vendor/sabre/http/examples/reverseproxy.php new file mode 100644 index 000000000000..289e2b55198f --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/reverseproxy.php @@ -0,0 +1,50 @@ +<?php + +// The url we're proxying to. +$remoteUrl = 'http://example.org/'; + +// The url we're proxying from. Please note that this must be a relative url, +// and basically acts as the base url. +// +// If your $remoteUrl doesn't end with a slash, this one probably shouldn't +// either. +$myBaseUrl = '/reverseproxy.php'; +// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/'; + +use Sabre\HTTP\Client; +use Sabre\HTTP\Sapi; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + + +$request = Sapi::getRequest(); +$request->setBaseUrl($myBaseUrl); + +$subRequest = clone $request; + +// Removing the Host header. +$subRequest->removeHeader('Host'); + +// Rewriting the url. +$subRequest->setUrl($remoteUrl . $request->getPath()); + +$client = new Client(); + +// Sends the HTTP request to the server +$response = $client->send($subRequest); + +// Sends the response back to the client that connected to the proxy. +Sapi::sendResponse($response); diff --git a/libs/composer/vendor/sabre/http/examples/stringify.php b/libs/composer/vendor/sabre/http/examples/stringify.php new file mode 100644 index 000000000000..9f56201af9e4 --- /dev/null +++ b/libs/composer/vendor/sabre/http/examples/stringify.php @@ -0,0 +1,51 @@ +<?php + +/** + * This simple example shows the capability of Request and Response objects to + * serialize themselves as strings. + * + * This is mainly useful for debugging purposes. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = new Request('POST', '/foo'); +$request->setHeaders([ + 'Host' => 'example.org', + 'Content-Type' => 'application/json' + ]); + +$request->setBody(json_encode(['foo' => 'bar'])); + +echo $request; +echo "\r\n\r\n"; + +$response = new Response(424); +$response->setHeaders([ + 'Content-Type' => 'text/plain', + 'Connection' => 'close', + ]); + +$response->setBody("ABORT! ABORT!"); + +echo $response; + +echo "\r\n"; diff --git a/libs/composer/vendor/sabre/http/lib/Auth/AWS.php b/libs/composer/vendor/sabre/http/lib/Auth/AWS.php new file mode 100644 index 000000000000..5e176646aafc --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Auth/AWS.php @@ -0,0 +1,234 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Util; + +/** + * HTTP AWS Authentication handler + * + * Use this class to leverage amazon's AWS authentication header + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class AWS extends AbstractAuth { + + /** + * The signature supplied by the HTTP client + * + * @var string + */ + private $signature = null; + + /** + * The accesskey supplied by the HTTP client + * + * @var string + */ + private $accessKey = null; + + /** + * An error code, if any + * + * This value will be filled with one of the ERR_* constants + * + * @var int + */ + public $errorCode = 0; + + const ERR_NOAWSHEADER = 1; + const ERR_MD5CHECKSUMWRONG = 2; + const ERR_INVALIDDATEFORMAT = 3; + const ERR_REQUESTTIMESKEWED = 4; + const ERR_INVALIDSIGNATURE = 5; + + /** + * Gathers all information from the headers + * + * This method needs to be called prior to anything else. + * + * @return bool + */ + function init() { + + $authHeader = $this->request->getHeader('Authorization'); + $authHeader = explode(' ', $authHeader); + + if ($authHeader[0] != 'AWS' || !isset($authHeader[1])) { + $this->errorCode = self::ERR_NOAWSHEADER; + return false; + } + + list($this->accessKey, $this->signature) = explode(':', $authHeader[1]); + + return true; + + } + + /** + * Returns the username for the request + * + * @return string + */ + function getAccessKey() { + + return $this->accessKey; + + } + + /** + * Validates the signature based on the secretKey + * + * @param string $secretKey + * @return bool + */ + function validate($secretKey) { + + $contentMD5 = $this->request->getHeader('Content-MD5'); + + if ($contentMD5) { + // We need to validate the integrity of the request + $body = $this->request->getBody(); + $this->request->setBody($body); + + if ($contentMD5 != base64_encode(md5($body, true))) { + // content-md5 header did not match md5 signature of body + $this->errorCode = self::ERR_MD5CHECKSUMWRONG; + return false; + } + + } + + if (!$requestDate = $this->request->getHeader('x-amz-date')) + $requestDate = $this->request->getHeader('Date'); + + if (!$this->validateRFC2616Date($requestDate)) + return false; + + $amzHeaders = $this->getAmzHeaders(); + + $signature = base64_encode( + $this->hmacsha1($secretKey, + $this->request->getMethod() . "\n" . + $contentMD5 . "\n" . + $this->request->getHeader('Content-type') . "\n" . + $requestDate . "\n" . + $amzHeaders . + $this->request->getUrl() + ) + ); + + if ($this->signature != $signature) { + + $this->errorCode = self::ERR_INVALIDSIGNATURE; + return false; + + } + + return true; + + } + + + /** + * Returns an HTTP 401 header, forcing login + * + * This should be called when username and password are incorrect, or not supplied at all + * + * @return void + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'AWS'); + $this->response->setStatus(401); + + } + + /** + * Makes sure the supplied value is a valid RFC2616 date. + * + * If we would just use strtotime to get a valid timestamp, we have no way of checking if a + * user just supplied the word 'now' for the date header. + * + * This function also makes sure the Date header is within 15 minutes of the operating + * system date, to prevent replay attacks. + * + * @param string $dateHeader + * @return bool + */ + protected function validateRFC2616Date($dateHeader) { + + $date = Util::parseHTTPDate($dateHeader); + + // Unknown format + if (!$date) { + $this->errorCode = self::ERR_INVALIDDATEFORMAT; + return false; + } + + $min = new \DateTime('-15 minutes'); + $max = new \DateTime('+15 minutes'); + + // We allow 15 minutes around the current date/time + if ($date > $max || $date < $min) { + $this->errorCode = self::ERR_REQUESTTIMESKEWED; + return false; + } + + return $date; + + } + + /** + * Returns a list of AMZ headers + * + * @return string + */ + protected function getAmzHeaders() { + + $amzHeaders = []; + $headers = $this->request->getHeaders(); + foreach ($headers as $headerName => $headerValue) { + if (strpos(strtolower($headerName), 'x-amz-') === 0) { + $amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\n"; + } + } + ksort($amzHeaders); + + $headerStr = ''; + foreach ($amzHeaders as $h => $v) { + $headerStr .= $h . ':' . $v; + } + + return $headerStr; + + } + + /** + * Generates an HMAC-SHA1 signature + * + * @param string $key + * @param string $message + * @return string + */ + private function hmacsha1($key, $message) { + + if (function_exists('hash_hmac')) { + return hash_hmac('sha1', $message, $key, true); + } + + $blocksize = 64; + if (strlen($key) > $blocksize) { + $key = pack('H*', sha1($key)); + } + $key = str_pad($key, $blocksize, chr(0x00)); + $ipad = str_repeat(chr(0x36), $blocksize); + $opad = str_repeat(chr(0x5c), $blocksize); + $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message)))); + return $hmac; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Auth/AbstractAuth.php b/libs/composer/vendor/sabre/http/lib/Auth/AbstractAuth.php new file mode 100644 index 000000000000..ae45b3ee22ab --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Auth/AbstractAuth.php @@ -0,0 +1,73 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Authentication base class. + * + * This class provides some common functionality for the various base classes. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractAuth { + + /** + * Authentication realm + * + * @var string + */ + protected $realm; + + /** + * Request object + * + * @var RequestInterface + */ + protected $request; + + /** + * Response object + * + * @var ResponseInterface + */ + protected $response; + + /** + * Creates the object + * + * @param string $realm + * @return void + */ + function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) { + + $this->realm = $realm; + $this->request = $request; + $this->response = $response; + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * the user to login. + * + * @return void + */ + abstract function requireLogin(); + + /** + * Returns the HTTP realm + * + * @return string + */ + function getRealm() { + + return $this->realm; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Auth/Basic.php b/libs/composer/vendor/sabre/http/lib/Auth/Basic.php new file mode 100644 index 000000000000..c263e3f9b7fb --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Auth/Basic.php @@ -0,0 +1,63 @@ +<?php + +namespace Sabre\HTTP\Auth; + +/** + * HTTP Basic authentication utility. + * + * This class helps you setup basic auth. The process is fairly simple: + * + * 1. Instantiate the class. + * 2. Call getCredentials (this will return null or a user/pass pair) + * 3. If you didn't get valid credentials, call 'requireLogin' + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Basic extends AbstractAuth { + + /** + * This method returns a numeric array with a username and password as the + * only elements. + * + * If no credentials were found, this method returns null. + * + * @return null|array + */ + function getCredentials() { + + $auth = $this->request->getHeader('Authorization'); + + if (!$auth) { + return null; + } + + if (strtolower(substr($auth, 0, 6)) !== 'basic ') { + return null; + } + + $credentials = explode(':', base64_decode(substr($auth, 6)), 2); + + if (2 !== count($credentials)) { + return null; + } + + return $credentials; + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * the user to login. + * + * @return void + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'Basic realm="' . $this->realm . '", charset="UTF-8"'); + $this->response->setStatus(401); + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Auth/Bearer.php b/libs/composer/vendor/sabre/http/lib/Auth/Bearer.php new file mode 100644 index 000000000000..eefdf11ee975 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Auth/Bearer.php @@ -0,0 +1,56 @@ +<?php + +namespace Sabre\HTTP\Auth; + +/** + * HTTP Bearer authentication utility. + * + * This class helps you setup bearer auth. The process is fairly simple: + * + * 1. Instantiate the class. + * 2. Call getToken (this will return null or a token as string) + * 3. If you didn't get a valid token, call 'requireLogin' + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author François Kooman (fkooman@tuxed.net) + * @license http://sabre.io/license/ Modified BSD License + */ +class Bearer extends AbstractAuth { + + /** + * This method returns a string with an access token. + * + * If no token was found, this method returns null. + * + * @return null|string + */ + function getToken() { + + $auth = $this->request->getHeader('Authorization'); + + if (!$auth) { + return null; + } + + if (strtolower(substr($auth, 0, 7)) !== 'bearer ') { + return null; + } + + return substr($auth, 7); + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * authentication. + * + * @return void + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->realm . '"'); + $this->response->setStatus(401); + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Auth/Digest.php b/libs/composer/vendor/sabre/http/lib/Auth/Digest.php new file mode 100644 index 000000000000..4b3f0746fc6a --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Auth/Digest.php @@ -0,0 +1,231 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Digest Authentication handler + * + * Use this class for easy http digest authentication. + * Instructions: + * + * 1. Create the object + * 2. Call the setRealm() method with the realm you plan to use + * 3. Call the init method function. + * 4. Call the getUserName() function. This function may return null if no + * authentication information was supplied. Based on the username you + * should check your internal database for either the associated password, + * or the so-called A1 hash of the digest. + * 5. Call either validatePassword() or validateA1(). This will return true + * or false. + * 6. To make sure an authentication prompt is displayed, call the + * requireLogin() method. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Digest extends AbstractAuth { + + /** + * These constants are used in setQOP(); + */ + const QOP_AUTH = 1; + const QOP_AUTHINT = 2; + + protected $nonce; + protected $opaque; + protected $digestParts; + protected $A1; + protected $qop = self::QOP_AUTH; + + /** + * Initializes the object + */ + function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) { + + $this->nonce = uniqid(); + $this->opaque = md5($realm); + parent::__construct($realm, $request, $response); + + } + + /** + * Gathers all information from the headers + * + * This method needs to be called prior to anything else. + * + * @return void + */ + function init() { + + $digest = $this->getDigest(); + $this->digestParts = $this->parseDigest($digest); + + } + + /** + * Sets the quality of protection value. + * + * Possible values are: + * Sabre\HTTP\DigestAuth::QOP_AUTH + * Sabre\HTTP\DigestAuth::QOP_AUTHINT + * + * Multiple values can be specified using logical OR. + * + * QOP_AUTHINT ensures integrity of the request body, but this is not + * supported by most HTTP clients. QOP_AUTHINT also requires the entire + * request body to be md5'ed, which can put strains on CPU and memory. + * + * @param int $qop + * @return void + */ + function setQOP($qop) { + + $this->qop = $qop; + + } + + /** + * Validates the user. + * + * The A1 parameter should be md5($username . ':' . $realm . ':' . $password); + * + * @param string $A1 + * @return bool + */ + function validateA1($A1) { + + $this->A1 = $A1; + return $this->validate(); + + } + + /** + * Validates authentication through a password. The actual password must be provided here. + * It is strongly recommended not store the password in plain-text and use validateA1 instead. + * + * @param string $password + * @return bool + */ + function validatePassword($password) { + + $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password); + return $this->validate(); + + } + + /** + * Returns the username for the request + * + * @return string + */ + function getUsername() { + + return $this->digestParts['username']; + + } + + /** + * Validates the digest challenge + * + * @return bool + */ + protected function validate() { + + $A2 = $this->request->getMethod() . ':' . $this->digestParts['uri']; + + if ($this->digestParts['qop'] == 'auth-int') { + // Making sure we support this qop value + if (!($this->qop & self::QOP_AUTHINT)) return false; + // We need to add an md5 of the entire request body to the A2 part of the hash + $body = $this->request->getBody($asString = true); + $this->request->setBody($body); + $A2 .= ':' . md5($body); + } else { + + // We need to make sure we support this qop value + if (!($this->qop & self::QOP_AUTH)) return false; + } + + $A2 = md5($A2); + + $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}"); + + return $this->digestParts['response'] == $validResponse; + + + } + + /** + * Returns an HTTP 401 header, forcing login + * + * This should be called when username and password are incorrect, or not supplied at all + * + * @return void + */ + function requireLogin() { + + $qop = ''; + switch ($this->qop) { + case self::QOP_AUTH : + $qop = 'auth'; + break; + case self::QOP_AUTHINT : + $qop = 'auth-int'; + break; + case self::QOP_AUTH | self::QOP_AUTHINT : + $qop = 'auth,auth-int'; + break; + } + + $this->response->addHeader('WWW-Authenticate', 'Digest realm="' . $this->realm . '",qop="' . $qop . '",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"'); + $this->response->setStatus(401); + + } + + + /** + * This method returns the full digest string. + * + * It should be compatibile with mod_php format and other webservers. + * + * If the header could not be found, null will be returned + * + * @return mixed + */ + function getDigest() { + + return $this->request->getHeader('Authorization'); + + } + + + /** + * Parses the different pieces of the digest string into an array. + * + * This method returns false if an incomplete digest was supplied + * + * @param string $digest + * @return mixed + */ + protected function parseDigest($digest) { + + // protect against missing data + $needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1]; + $data = []; + + preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER); + + foreach ($matches as $m) { + $data[$m[1]] = $m[2] ? $m[2] : $m[3]; + unset($needed_parts[$m[1]]); + } + + return $needed_parts ? false : $data; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Client.php b/libs/composer/vendor/sabre/http/lib/Client.php new file mode 100644 index 000000000000..0810c4a25b1c --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Client.php @@ -0,0 +1,601 @@ +<?php + +namespace Sabre\HTTP; + +use Sabre\Event\EventEmitter; +use Sabre\Uri; + +/** + * A rudimentary HTTP client. + * + * This object wraps PHP's curl extension and provides an easy way to send it a + * Request object, and return a Response object. + * + * This is by no means intended as the next best HTTP client, but it does the + * job and provides a simple integration with the rest of sabre/http. + * + * This client emits the following events: + * beforeRequest(RequestInterface $request) + * afterRequest(RequestInterface $request, ResponseInterface $response) + * error(RequestInterface $request, ResponseInterface $response, bool &$retry, int $retryCount) + * exception(RequestInterface $request, ClientException $e, bool &$retry, int $retryCount) + * + * The beforeRequest event allows you to do some last minute changes to the + * request before it's done, such as adding authentication headers. + * + * The afterRequest event will be emitted after the request is completed + * succesfully. + * + * If a HTTP error is returned (status code higher than 399) the error event is + * triggered. It's possible using this event to retry the request, by setting + * retry to true. + * + * The amount of times a request has retried is passed as $retryCount, which + * can be used to avoid retrying indefinitely. The first time the event is + * called, this will be 0. + * + * It's also possible to intercept specific http errors, by subscribing to for + * example 'error:401'. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Client extends EventEmitter { + + /** + * List of curl settings + * + * @var array + */ + protected $curlSettings = []; + + /** + * Wether or not exceptions should be thrown when a HTTP error is returned. + * + * @var bool + */ + protected $throwExceptions = false; + + /** + * The maximum number of times we'll follow a redirect. + * + * @var int + */ + protected $maxRedirects = 5; + + /** + * Initializes the client. + * + * @return void + */ + function __construct() { + + $this->curlSettings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + } + + /** + * Sends a request to a HTTP server, and returns a response. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + function send(RequestInterface $request) { + + $this->emit('beforeRequest', [$request]); + + $retryCount = 0; + $redirects = 0; + + do { + + $doRedirect = false; + $retry = false; + + try { + + $response = $this->doRequest($request); + + $code = (int)$response->getStatus(); + + // We are doing in-PHP redirects, because curl's + // FOLLOW_LOCATION throws errors when PHP is configured with + // open_basedir. + // + // https://github.com/fruux/sabre-http/issues/12 + if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) { + + $oldLocation = $request->getUrl(); + + // Creating a new instance of the request object. + $request = clone $request; + + // Setting the new location + $request->setUrl(Uri\resolve( + $oldLocation, + $response->getHeader('Location') + )); + + $doRedirect = true; + $redirects++; + + } + + // This was a HTTP error + if ($code >= 400) { + + $this->emit('error', [$request, $response, &$retry, $retryCount]); + $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]); + + } + + } catch (ClientException $e) { + + $this->emit('exception', [$request, $e, &$retry, $retryCount]); + + // If retry was still set to false, it means no event handler + // dealt with the problem. In this case we just re-throw the + // exception. + if (!$retry) { + throw $e; + } + + } + + if ($retry) { + $retryCount++; + } + + } while ($retry || $doRedirect); + + $this->emit('afterRequest', [$request, $response]); + + if ($this->throwExceptions && $code >= 400) { + throw new ClientHttpException($response); + } + + return $response; + + } + + /** + * Sends a HTTP request asynchronously. + * + * Due to the nature of PHP, you must from time to time poll to see if any + * new responses came in. + * + * After calling sendAsync, you must therefore occasionally call the poll() + * method, or wait(). + * + * @param RequestInterface $request + * @param callable $success + * @param callable $error + * @return void + */ + function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) { + + $this->emit('beforeRequest', [$request]); + $this->sendAsyncInternal($request, $success, $error); + $this->poll(); + + } + + + /** + * This method checks if any http requests have gotten results, and if so, + * call the appropriate success or error handlers. + * + * This method will return true if there are still requests waiting to + * return, and false if all the work is done. + * + * @return bool + */ + function poll() { + + // nothing to do? + if (!$this->curlMultiMap) { + return false; + } + + do { + $r = curl_multi_exec( + $this->curlMultiHandle, + $stillRunning + ); + } while ($r === CURLM_CALL_MULTI_PERFORM); + + do { + + messageQueue: + + $status = curl_multi_info_read( + $this->curlMultiHandle, + $messagesInQueue + ); + + if ($status && $status['msg'] === CURLMSG_DONE) { + + $resourceId = intval($status['handle']); + list( + $request, + $successCallback, + $errorCallback, + $retryCount, + ) = $this->curlMultiMap[$resourceId]; + unset($this->curlMultiMap[$resourceId]); + $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']); + $retry = false; + + if ($curlResult['status'] === self::STATUS_CURLERROR) { + + $e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']); + $this->emit('exception', [$request, $e, &$retry, $retryCount]); + + if ($retry) { + $retryCount++; + $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount); + goto messageQueue; + } + + $curlResult['request'] = $request; + + if ($errorCallback) { + $errorCallback($curlResult); + } + + } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) { + + $this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]); + $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]); + + if ($retry) { + + $retryCount++; + $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount); + goto messageQueue; + + } + + $curlResult['request'] = $request; + + if ($errorCallback) { + $errorCallback($curlResult); + } + + } else { + + $this->emit('afterRequest', [$request, $curlResult['response']]); + + if ($successCallback) { + $successCallback($curlResult['response']); + } + + } + } + + } while ($messagesInQueue > 0); + + return count($this->curlMultiMap) > 0; + + } + + /** + * Processes every HTTP request in the queue, and waits till they are all + * completed. + * + * @return void + */ + function wait() { + + do { + curl_multi_select($this->curlMultiHandle); + $stillRunning = $this->poll(); + } while ($stillRunning); + + } + + /** + * If this is set to true, the Client will automatically throw exceptions + * upon HTTP errors. + * + * This means that if a response came back with a status code greater than + * or equal to 400, we will throw a ClientHttpException. + * + * This only works for the send() method. Throwing exceptions for + * sendAsync() is not supported. + * + * @param bool $throwExceptions + * @return void + */ + function setThrowExceptions($throwExceptions) { + + $this->throwExceptions = $throwExceptions; + + } + + /** + * Adds a CURL setting. + * + * These settings will be included in every HTTP request. + * + * @param int $name + * @param mixed $value + * @return void + */ + function addCurlSetting($name, $value) { + + $this->curlSettings[$name] = $value; + + } + + /** + * This method is responsible for performing a single request. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + protected function doRequest(RequestInterface $request) { + + $settings = $this->createCurlSettingsArray($request); + + if (!$this->curlHandle) { + $this->curlHandle = curl_init(); + } + + curl_setopt_array($this->curlHandle, $settings); + $response = $this->curlExec($this->curlHandle); + $response = $this->parseCurlResult($response, $this->curlHandle); + + if ($response['status'] === self::STATUS_CURLERROR) { + throw new ClientException($response['curl_errmsg'], $response['curl_errno']); + } + + return $response['response']; + + } + + /** + * Cached curl handle. + * + * By keeping this resource around for the lifetime of this object, things + * like persistent connections are possible. + * + * @var resource + */ + private $curlHandle; + + /** + * Handler for curl_multi requests. + * + * The first time sendAsync is used, this will be created. + * + * @var resource + */ + private $curlMultiHandle; + + /** + * Has a list of curl handles, as well as their associated success and + * error callbacks. + * + * @var array + */ + private $curlMultiMap = []; + + /** + * Turns a RequestInterface object into an array with settings that can be + * fed to curl_setopt + * + * @param RequestInterface $request + * @return array + */ + protected function createCurlSettingsArray(RequestInterface $request) { + + $settings = $this->curlSettings; + + switch ($request->getMethod()) { + case 'HEAD' : + $settings[CURLOPT_NOBODY] = true; + $settings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; + $settings[CURLOPT_POSTFIELDS] = ''; + $settings[CURLOPT_PUT] = false; + break; + case 'GET' : + $settings[CURLOPT_CUSTOMREQUEST] = 'GET'; + $settings[CURLOPT_POSTFIELDS] = ''; + $settings[CURLOPT_PUT] = false; + break; + default : + $body = $request->getBody(); + if (is_resource($body)) { + // This needs to be set to PUT, regardless of the actual + // method used. Without it, INFILE will be ignored for some + // reason. + $settings[CURLOPT_PUT] = true; + $settings[CURLOPT_INFILE] = $request->getBody(); + } else { + // For security we cast this to a string. If somehow an array could + // be passed here, it would be possible for an attacker to use @ to + // post local files. + $settings[CURLOPT_POSTFIELDS] = (string)$body; + } + $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); + break; + + } + + $nHeaders = []; + foreach ($request->getHeaders() as $key => $values) { + + foreach ($values as $value) { + $nHeaders[] = $key . ': ' . $value; + } + + } + $settings[CURLOPT_HTTPHEADER] = $nHeaders; + $settings[CURLOPT_URL] = $request->getUrl(); + // FIXME: CURLOPT_PROTOCOLS is currently unsupported by HHVM + if (defined('CURLOPT_PROTOCOLS')) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + // FIXME: CURLOPT_REDIR_PROTOCOLS is currently unsupported by HHVM + if (defined('CURLOPT_REDIR_PROTOCOLS')) { + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + return $settings; + + } + + const STATUS_SUCCESS = 0; + const STATUS_CURLERROR = 1; + const STATUS_HTTPERROR = 2; + + /** + * Parses the result of a curl call in a format that's a bit more + * convenient to work with. + * + * The method returns an array with the following elements: + * * status - one of the 3 STATUS constants. + * * curl_errno - A curl error number. Only set if status is + * STATUS_CURLERROR. + * * curl_errmsg - A current error message. Only set if status is + * STATUS_CURLERROR. + * * response - Response object. Only set if status is STATUS_SUCCESS, or + * STATUS_HTTPERROR. + * * http_code - HTTP status code, as an int. Only set if Only set if + * status is STATUS_SUCCESS, or STATUS_HTTPERROR + * + * @param string $response + * @param resource $curlHandle + * @return Response + */ + protected function parseCurlResult($response, $curlHandle) { + + list( + $curlInfo, + $curlErrNo, + $curlErrMsg + ) = $this->curlStuff($curlHandle); + + if ($curlErrNo) { + return [ + 'status' => self::STATUS_CURLERROR, + 'curl_errno' => $curlErrNo, + 'curl_errmsg' => $curlErrMsg, + ]; + } + + $headerBlob = substr($response, 0, $curlInfo['header_size']); + // In the case of 204 No Content, strlen($response) == $curlInfo['header_size]. + // This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL + // An exception will be thrown when calling getBodyAsString then + $responseBody = substr($response, $curlInfo['header_size']) ?: null; + + unset($response); + + // In the case of 100 Continue, or redirects we'll have multiple lists + // of headers for each separate HTTP response. We can easily split this + // because they are separated by \r\n\r\n + $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n")); + + // We only care about the last set of headers + $headerBlob = $headerBlob[count($headerBlob) - 1]; + + // Splitting headers + $headerBlob = explode("\r\n", $headerBlob); + + $response = new Response(); + $response->setStatus($curlInfo['http_code']); + + foreach ($headerBlob as $header) { + $parts = explode(':', $header, 2); + if (count($parts) == 2) { + $response->addHeader(trim($parts[0]), trim($parts[1])); + } + } + + $response->setBody($responseBody); + + $httpCode = intval($response->getStatus()); + + return [ + 'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS, + 'response' => $response, + 'http_code' => $httpCode, + ]; + + } + + /** + * Sends an asynchronous HTTP request. + * + * We keep this in a separate method, so we can call it without triggering + * the beforeRequest event and don't do the poll(). + * + * @param RequestInterface $request + * @param callable $success + * @param callable $error + * @param int $retryCount + */ + protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) { + + if (!$this->curlMultiHandle) { + $this->curlMultiHandle = curl_multi_init(); + } + $curl = curl_init(); + curl_setopt_array( + $curl, + $this->createCurlSettingsArray($request) + ); + curl_multi_add_handle($this->curlMultiHandle, $curl); + $this->curlMultiMap[intval($curl)] = [ + $request, + $success, + $error, + $retryCount + ]; + + } + + // @codeCoverageIgnoreStart + + /** + * Calls curl_exec + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return string + */ + protected function curlExec($curlHandle) { + + return curl_exec($curlHandle); + + } + + /** + * Returns a bunch of information about a curl request. + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return array + */ + protected function curlStuff($curlHandle) { + + return [ + curl_getinfo($curlHandle), + curl_errno($curlHandle), + curl_error($curlHandle), + ]; + + } + // @codeCoverageIgnoreEnd + +} diff --git a/libs/composer/vendor/sabre/http/lib/ClientException.php b/libs/composer/vendor/sabre/http/lib/ClientException.php new file mode 100644 index 000000000000..69631f44eef0 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/ClientException.php @@ -0,0 +1,15 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This exception may be emitted by the HTTP\Client class, in case there was a + * problem emitting the request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ClientException extends \Exception { + +} diff --git a/libs/composer/vendor/sabre/http/lib/ClientHttpException.php b/libs/composer/vendor/sabre/http/lib/ClientHttpException.php new file mode 100644 index 000000000000..2923ef3b53a5 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/ClientHttpException.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This exception represents a HTTP error coming from the Client. + * + * By default the Client will not emit these, this has to be explicitly enabled + * with the setThrowExceptions method. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ClientHttpException extends \Exception implements HttpException { + + /** + * Response object + * + * @var ResponseInterface + */ + protected $response; + + /** + * Constructor + * + * @param ResponseInterface $response + */ + function __construct(ResponseInterface $response) { + + $this->response = $response; + parent::__construct($response->getStatusText(), $response->getStatus()); + + } + + /** + * The http status code for the error. + * + * @return int + */ + function getHttpStatus() { + + return $this->response->getStatus(); + + } + + /** + * Returns the full response object. + * + * @return ResponseInterface + */ + function getResponse() { + + return $this->response; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/HttpException.php b/libs/composer/vendor/sabre/http/lib/HttpException.php new file mode 100644 index 000000000000..1303dec970e9 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/HttpException.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\HTTP; + +/** + * An exception representing a HTTP error. + * + * This can be used as a generic exception in your application, if you'd like + * to map HTTP errors to exceptions. + * + * If you'd like to use this, create a new exception class, extending Exception + * and implementing this interface. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface HttpException { + + /** + * The http status code for the error. + * + * This may either be just the number, or a number and a human-readable + * message, separated by a space. + * + * @return string|null + */ + function getHttpStatus(); + +} diff --git a/libs/composer/vendor/sabre/http/lib/Message.php b/libs/composer/vendor/sabre/http/lib/Message.php new file mode 100644 index 000000000000..45bd183980dd --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Message.php @@ -0,0 +1,314 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This is the abstract base class for both the Request and Response objects. + * + * This object contains a few simple methods that are shared by both. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Message implements MessageInterface { + + /** + * Request body + * + * This should be a stream resource + * + * @var resource + */ + protected $body; + + /** + * Contains the list of HTTP headers + * + * @var array + */ + protected $headers = []; + + /** + * HTTP message version (1.0 or 1.1) + * + * @var string + */ + protected $httpVersion = '1.1'; + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream() { + + $body = $this->getBody(); + if (is_string($body) || is_null($body)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $body); + rewind($stream); + return $stream; + } + return $body; + + } + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString() { + + $body = $this->getBody(); + if (is_string($body)) { + return $body; + } + if (is_null($body)) { + return ''; + } + $contentLength = $this->getHeader('Content-Length'); + if (is_int($contentLength) || ctype_digit($contentLength)) { + return stream_get_contents($body, $contentLength); + } else { + return stream_get_contents($body); + } + } + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody() { + + return $this->body; + + } + + /** + * Replaces the body resource with a new stream or string. + * + * @param resource|string $body + */ + function setBody($body) { + + $this->body = $body; + + } + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders() { + + $result = []; + foreach ($this->headers as $headerInfo) { + $result[$headerInfo[0]] = $headerInfo[1]; + } + return $result; + + } + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return isset($this->headers[strtolower($name)]); + + } + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name) { + + $name = strtolower($name); + + if (isset($this->headers[$name])) { + return implode(',', $this->headers[$name][1]); + } + return null; + + } + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name) { + + $name = strtolower($name); + + if (isset($this->headers[$name])) { + return $this->headers[$name][1]; + } + + return []; + + } + + /** + * Updates a HTTP header. + * + * The case-sensitivity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value) { + + $this->headers[strtolower($name)] = [$name, (array)$value]; + + } + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers) { + + foreach ($headers as $name => $value) { + $this->setHeader($name, $value); + } + + } + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value) { + + $lName = strtolower($name); + if (isset($this->headers[$lName])) { + $this->headers[$lName][1] = array_merge( + $this->headers[$lName][1], + (array)$value + ); + } else { + $this->headers[$lName] = [ + $name, + (array)$value + ]; + } + + } + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers) { + + foreach ($headers as $name => $value) { + $this->addHeader($name, $value); + } + + } + + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insensitive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @param string $name + * @return bool + */ + function removeHeader($name) { + + $name = strtolower($name); + if (!isset($this->headers[$name])) { + return false; + } + unset($this->headers[$name]); + return true; + + } + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version) { + + $this->httpVersion = $version; + + } + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion() { + + return $this->httpVersion; + + } +} diff --git a/libs/composer/vendor/sabre/http/lib/MessageDecoratorTrait.php b/libs/composer/vendor/sabre/http/lib/MessageDecoratorTrait.php new file mode 100644 index 000000000000..1cb32da2259d --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/MessageDecoratorTrait.php @@ -0,0 +1,251 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This trait contains a bunch of methods, shared by both the RequestDecorator + * and the ResponseDecorator. + * + * Didn't seem needed to create a full class for this, so we're just + * implementing it as a trait. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +trait MessageDecoratorTrait { + + /** + * The inner request object. + * + * All method calls will be forwarded here. + * + * @var MessageInterface + */ + protected $inner; + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream() { + + return $this->inner->getBodyAsStream(); + + } + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString() { + + return $this->inner->getBodyAsString(); + + } + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody() { + + return $this->inner->getBody(); + + } + + /** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ + function setBody($body) { + + $this->inner->setBody($body); + + } + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders() { + + return $this->inner->getHeaders(); + + } + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return $this->inner->hasHeader($name); + + } + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name) { + + return $this->inner->getHeader($name); + + } + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name) { + + return $this->inner->getHeaderAsArray($name); + + } + + /** + * Updates a HTTP header. + * + * The case-sensitivity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value) { + + $this->inner->setHeader($name, $value); + + } + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers) { + + $this->inner->setHeaders($headers); + + } + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value) { + + $this->inner->addHeader($name, $value); + + } + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers) { + + $this->inner->addHeaders($headers); + + } + + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insensitive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @param string $name + * @return bool + */ + function removeHeader($name) { + + return $this->inner->removeHeader($name); + + } + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version) { + + $this->inner->setHttpVersion($version); + + } + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion() { + + return $this->inner->getHttpVersion(); + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/MessageInterface.php b/libs/composer/vendor/sabre/http/lib/MessageInterface.php new file mode 100644 index 000000000000..df55beb2ffe4 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/MessageInterface.php @@ -0,0 +1,178 @@ +<?php + +namespace Sabre\HTTP; + +/** + * The MessageInterface is the base interface that's used by both + * the RequestInterface and ResponseInterface. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface MessageInterface { + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream(); + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString(); + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody(); + + /** + * Updates the body resource with a new stream. + * + * @param resource|string $body + * @return void + */ + function setBody($body); + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders(); + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name); + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name); + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name); + + /** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value); + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers); + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value); + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers); + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @param string $name + * @return bool + */ + function removeHeader($name); + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version); + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion(); + +} diff --git a/libs/composer/vendor/sabre/http/lib/Request.php b/libs/composer/vendor/sabre/http/lib/Request.php new file mode 100644 index 000000000000..dfa3d5b4857c --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Request.php @@ -0,0 +1,316 @@ +<?php + +namespace Sabre\HTTP; + +use InvalidArgumentException; +use Sabre\Uri; + +/** + * The Request class represents a single HTTP request. + * + * You can either simply construct the object from scratch, or if you need + * access to the current HTTP request, use Sapi::getRequest. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Request extends Message implements RequestInterface { + + /** + * HTTP Method + * + * @var string + */ + protected $method; + + /** + * Request Url + * + * @var string + */ + protected $url; + + /** + * Creates the request object + * + * @param string $method + * @param string $url + * @param array $headers + * @param resource $body + */ + function __construct($method = null, $url = null, array $headers = null, $body = null) { + + if (is_array($method)) { + throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?'); + } + if (!is_null($method)) $this->setMethod($method); + if (!is_null($url)) $this->setUrl($url); + if (!is_null($headers)) $this->setHeaders($headers); + if (!is_null($body)) $this->setBody($body); + + } + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod() { + + return $this->method; + + } + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method) { + + $this->method = $method; + + } + + /** + * Returns the request url. + * + * @return string + */ + function getUrl() { + + return $this->url; + + } + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url) { + + $this->url = $url; + + } + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters() { + + $url = $this->getUrl(); + if (($index = strpos($url, '?')) === false) { + return []; + } else { + parse_str(substr($url, $index + 1), $queryParams); + return $queryParams; + } + + } + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url) { + + $this->absoluteUrl = $url; + + } + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl() { + + return $this->absoluteUrl; + + } + + /** + * Base url + * + * @var string + */ + protected $baseUrl = '/'; + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * @param string $url + * @return void + */ + function setBaseUrl($url) { + + $this->baseUrl = $url; + + } + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl() { + + return $this->baseUrl; + + } + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath() { + + // Removing duplicated slashes. + $uri = str_replace('//', '/', $this->getUrl()); + + $uri = Uri\normalize($uri); + $baseUri = Uri\normalize($this->getBaseUrl()); + + if (strpos($uri, $baseUri) === 0) { + + // We're not interested in the query part (everything after the ?). + list($uri) = explode('?', $uri); + return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/'); + + } + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + elseif ($uri . '/' === $baseUri) { + + return ''; + + } + + throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')'); + } + + /** + * Equivalent of PHP's $_POST. + * + * @var array + */ + protected $postData = []; + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData) { + + $this->postData = $postData; + + } + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData() { + + return $this->postData; + + } + + /** + * An array containing the raw _SERVER array. + * + * @var array + */ + protected $rawServerData; + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName) { + + if (isset($this->rawServerData[$valueName])) { + return $this->rawServerData[$valueName]; + } + + } + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data) { + + $this->rawServerData = $data; + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + $out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n"; + + foreach ($this->getHeaders() as $key => $value) { + foreach ($value as $v) { + if ($key === 'Authorization') { + list($v) = explode(' ', $v, 2); + $v .= ' REDACTED'; + } + $out .= $key . ": " . $v . "\r\n"; + } + } + $out .= "\r\n"; + $out .= $this->getBodyAsString(); + + return $out; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/RequestDecorator.php b/libs/composer/vendor/sabre/http/lib/RequestDecorator.php new file mode 100644 index 000000000000..7ee3f6fc85cb --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/RequestDecorator.php @@ -0,0 +1,231 @@ +<?php + +namespace Sabre\HTTP; + +/** + * Request Decorator + * + * This helper class allows you to easily create decorators for the Request + * object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class RequestDecorator implements RequestInterface { + + use MessageDecoratorTrait; + + /** + * Constructor. + * + * @param RequestInterface $inner + */ + function __construct(RequestInterface $inner) { + + $this->inner = $inner; + + } + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod() { + + return $this->inner->getMethod(); + + } + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method) { + + $this->inner->setMethod($method); + + } + + /** + * Returns the request url. + * + * @return string + */ + function getUrl() { + + return $this->inner->getUrl(); + + } + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url) { + + $this->inner->setUrl($url); + + } + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl() { + + return $this->inner->getAbsoluteUrl(); + + } + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url) { + + $this->inner->setAbsoluteUrl($url); + + } + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl() { + + return $this->inner->getBaseUrl(); + + } + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ + function setBaseUrl($url) { + + $this->inner->setBaseUrl($url); + + } + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath() { + + return $this->inner->getPath(); + + } + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters() { + + return $this->inner->getQueryParameters(); + + } + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData() { + + return $this->inner->getPostData(); + + } + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData) { + + $this->inner->setPostData($postData); + + } + + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName) { + + return $this->inner->getRawServerValue($valueName); + + } + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data) { + + $this->inner->setRawServerData($data); + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + return $this->inner->__toString(); + + } +} diff --git a/libs/composer/vendor/sabre/http/lib/RequestInterface.php b/libs/composer/vendor/sabre/http/lib/RequestInterface.php new file mode 100644 index 000000000000..63d9cbb51a0e --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/RequestInterface.php @@ -0,0 +1,147 @@ +<?php + +namespace Sabre\HTTP; + +/** + * The RequestInterface represents a HTTP request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface RequestInterface extends MessageInterface { + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod(); + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method); + + /** + * Returns the request url. + * + * @return string + */ + function getUrl(); + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url); + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl(); + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url); + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl(); + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ + function setBaseUrl($url); + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath(); + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters(); + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData(); + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData); + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName); + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data); + + +} diff --git a/libs/composer/vendor/sabre/http/lib/Response.php b/libs/composer/vendor/sabre/http/lib/Response.php new file mode 100644 index 000000000000..01920d8d9fb6 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Response.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This class represents a single HTTP response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Response extends Message implements ResponseInterface { + + /** + * This is the list of currently registered HTTP status codes. + * + * @var array + */ + static $statusCodes = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authorative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC 4918 + 208 => 'Already Reported', // RFC 5842 + 226 => 'IM Used', // RFC 3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC 2324 + 421 => 'Misdirected Request', // RFC7540 (HTTP/2) + 422 => 'Unprocessable Entity', // RFC 4918 + 423 => 'Locked', // RFC 4918 + 424 => 'Failed Dependency', // RFC 4918 + 426 => 'Upgrade Required', + 428 => 'Precondition Required', // RFC 6585 + 429 => 'Too Many Requests', // RFC 6585 + 431 => 'Request Header Fields Too Large', // RFC 6585 + 451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-status + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', // RFC 4918 + 508 => 'Loop Detected', // RFC 5842 + 509 => 'Bandwidth Limit Exceeded', // non-standard + 510 => 'Not extended', + 511 => 'Network Authentication Required', // RFC 6585 + ]; + + /** + * HTTP status code + * + * @var int + */ + protected $status; + + /** + * HTTP status text + * + * @var string + */ + protected $statusText; + + /** + * Creates the response object + * + * @param string|int $status + * @param array $headers + * @param resource $body + */ + function __construct($status = null, array $headers = null, $body = null) { + + if (!is_null($status)) $this->setStatus($status); + if (!is_null($headers)) $this->setHeaders($headers); + if (!is_null($body)) $this->setBody($body); + + } + + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus() { + + return $this->status; + + } + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText() { + + return $this->statusText; + + } + + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentException + * @return void + */ + function setStatus($status) { + + if (ctype_digit($status) || is_int($status)) { + + $statusCode = $status; + $statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown'; + + } else { + list( + $statusCode, + $statusText + ) = explode(' ', $status, 2); + } + if ($statusCode < 100 || $statusCode > 999) { + throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits'); + } + + $this->status = $statusCode; + $this->statusText = $statusText; + + } + + /** + * Serializes the response object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n"; + foreach ($this->getHeaders() as $key => $value) { + foreach ($value as $v) { + $str .= $key . ": " . $v . "\r\n"; + } + } + $str .= "\r\n"; + $str .= $this->getBodyAsString(); + return $str; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/ResponseDecorator.php b/libs/composer/vendor/sabre/http/lib/ResponseDecorator.php new file mode 100644 index 000000000000..db3a67507182 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/ResponseDecorator.php @@ -0,0 +1,84 @@ +<?php + +namespace Sabre\HTTP; + +/** + * Response Decorator + * + * This helper class allows you to easily create decorators for the Response + * object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ResponseDecorator implements ResponseInterface { + + use MessageDecoratorTrait; + + /** + * Constructor. + * + * @param ResponseInterface $inner + */ + function __construct(ResponseInterface $inner) { + + $this->inner = $inner; + + } + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus() { + + return $this->inner->getStatus(); + + } + + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText() { + + return $this->inner->getStatusText(); + + } + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @return void + */ + function setStatus($status) { + + $this->inner->setStatus($status); + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + return $this->inner->__toString(); + + } +} diff --git a/libs/composer/vendor/sabre/http/lib/ResponseInterface.php b/libs/composer/vendor/sabre/http/lib/ResponseInterface.php new file mode 100644 index 000000000000..411cdc06cbb0 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/ResponseInterface.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This interface represents a HTTP response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface ResponseInterface extends MessageInterface { + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus(); + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText(); + + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentException + * @return void + */ + function setStatus($status); + +} diff --git a/libs/composer/vendor/sabre/http/lib/Sapi.php b/libs/composer/vendor/sabre/http/lib/Sapi.php new file mode 100644 index 000000000000..054380e73000 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Sapi.php @@ -0,0 +1,202 @@ +<?php + +namespace Sabre\HTTP; + +/** + * PHP SAPI + * + * This object is responsible for: + * 1. Constructing a Request object based on the current HTTP request sent to + * the PHP process. + * 2. Sending the Response object back to the client. + * + * It could be said that this class provides a mapping between the Request and + * Response objects, and php's: + * + * * $_SERVER + * * $_POST + * * $_FILES + * * php://input + * * echo() + * * header() + * * php://output + * + * You can choose to either call all these methods statically, but you can also + * instantiate this as an object to allow for polymorhpism. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Sapi { + + /** + * This static method will create a new Request object, based on the + * current PHP request. + * + * @return Request + */ + static function getRequest() { + + $r = self::createFromServerArray($_SERVER); + $r->setBody(fopen('php://input', 'r')); + $r->setPostData($_POST); + return $r; + + } + + /** + * Sends the HTTP response back to a HTTP client. + * + * This calls php's header() function and streams the body to php://output. + * + * @param ResponseInterface $response + * @return void + */ + static function sendResponse(ResponseInterface $response) { + + header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText()); + foreach ($response->getHeaders() as $key => $value) { + + foreach ($value as $k => $v) { + if ($k === 0) { + header($key . ': ' . $v); + } else { + header($key . ': ' . $v, false); + } + } + + } + + $body = $response->getBody(); + if (is_null($body)) return; + + $contentLength = $response->getHeader('Content-Length'); + if ($contentLength !== null) { + $output = fopen('php://output', 'wb'); + if (is_resource($body) && get_resource_type($body) == 'stream') { + if (PHP_INT_SIZE !== 4){ + // use the dedicated function on 64 Bit systems + stream_copy_to_stream($body, $output, $contentLength); + } else { + // workaround for 32 Bit systems to avoid stream_copy_to_stream + while (!feof($body)) { + fwrite($output, fread($body, 8192)); + } + } + } else { + fwrite($output, $body, $contentLength); + } + } else { + file_put_contents('php://output', $body); + } + + if (is_resource($body)) { + fclose($body); + } + + } + + /** + * This static method will create a new Request object, based on a PHP + * $_SERVER array. + * + * @param array $serverArray + * @return Request + */ + static function createFromServerArray(array $serverArray) { + + $headers = []; + $method = null; + $url = null; + $httpVersion = '1.1'; + + $protocol = 'http'; + $hostName = 'localhost'; + + foreach ($serverArray as $key => $value) { + + switch ($key) { + + case 'SERVER_PROTOCOL' : + if ($value === 'HTTP/1.0') { + $httpVersion = '1.0'; + } + break; + case 'REQUEST_METHOD' : + $method = $value; + break; + case 'REQUEST_URI' : + $url = $value; + break; + + // These sometimes show up without a HTTP_ prefix + case 'CONTENT_TYPE' : + $headers['Content-Type'] = $value; + break; + case 'CONTENT_LENGTH' : + $headers['Content-Length'] = $value; + break; + + // mod_php on apache will put credentials in these variables. + // (fast)cgi does not usually do this, however. + case 'PHP_AUTH_USER' : + if (isset($serverArray['PHP_AUTH_PW'])) { + $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']); + } + break; + + // Similarly, mod_php may also screw around with digest auth. + case 'PHP_AUTH_DIGEST' : + $headers['Authorization'] = 'Digest ' . $value; + break; + + // Apache may prefix the HTTP_AUTHORIZATION header with + // REDIRECT_, if mod_rewrite was used. + case 'REDIRECT_HTTP_AUTHORIZATION' : + $headers['Authorization'] = $value; + break; + + case 'HTTP_HOST' : + $hostName = $value; + $headers['Host'] = $value; + break; + + case 'HTTPS' : + if (!empty($value) && $value !== 'off') { + $protocol = 'https'; + } + break; + + default : + if (substr($key, 0, 5) === 'HTTP_') { + // It's a HTTP header + + // Normalizing it to be prettier + $header = strtolower(substr($key, 5)); + + // Transforming dashes into spaces, and uppercasing + // every first letter. + $header = ucwords(str_replace('_', ' ', $header)); + + // Turning spaces into dashes. + $header = str_replace(' ', '-', $header); + $headers[$header] = $value; + + } + break; + + + } + + } + + $r = new Request($method, $url, $headers); + $r->setHttpVersion($httpVersion); + $r->setRawServerData($serverArray); + $r->setAbsoluteUrl($protocol . '://' . $hostName . $url); + return $r; + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/URLUtil.php b/libs/composer/vendor/sabre/http/lib/URLUtil.php new file mode 100644 index 000000000000..85c0e11504a7 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/URLUtil.php @@ -0,0 +1,103 @@ +<?php + +namespace Sabre\HTTP; + +use Sabre\URI; + +/** + * URL utility class + * + * Note: this class is deprecated. All its functionality moved to functions.php + * or sabre\uri. + * + * @deprecated + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class URLUtil { + + /** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @deprecated use \Sabre\HTTP\encodePath() + * @param string $path + * @return string + */ + static function encodePath($path) { + + return encodePath($path); + + } + + /** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @deprecated use \Sabre\HTTP\encodePathSegment() + * @param string $pathSegment + * @return string + */ + static function encodePathSegment($pathSegment) { + + return encodePathSegment($pathSegment); + + } + + /** + * Decodes a url-encoded path + * + * @deprecated use \Sabre\HTTP\decodePath + * @param string $path + * @return string + */ + static function decodePath($path) { + + return decodePath($path); + + } + + /** + * Decodes a url-encoded path segment + * + * @deprecated use \Sabre\HTTP\decodePathSegment() + * @param string $path + * @return string + */ + static function decodePathSegment($path) { + + return decodePathSegment($path); + + } + + /** + * Returns the 'dirname' and 'basename' for a path. + * + * @deprecated Use Sabre\Uri\split(). + * @param string $path + * @return array + */ + static function splitPath($path) { + + return Uri\split($path); + + } + + /** + * Resolves relative urls, like a browser would. + * + * @deprecated Use Sabre\Uri\resolve(). + * @param string $basePath + * @param string $newPath + * @return string + */ + static function resolve($basePath, $newPath) { + + return Uri\resolve($basePath, $newPath); + + } + +} diff --git a/libs/composer/vendor/sabre/http/lib/Util.php b/libs/composer/vendor/sabre/http/lib/Util.php new file mode 100644 index 000000000000..e3f13a645b9c --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Util.php @@ -0,0 +1,74 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP utility methods + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @author Paul Voegler + * @deprecated All these functions moved to functions.php + * @license http://sabre.io/license/ Modified BSD License + */ +class Util { + + /** + * Content negotiation + * + * @deprecated Use \Sabre\HTTP\negotiateContentType + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ + static function negotiateContentType($acceptHeaderValue, array $availableOptions) { + + return negotiateContentType($acceptHeaderValue, $availableOptions); + + } + + /** + * Deprecated! Use negotiateContentType. + * + * @deprecated Use \Sabre\HTTP\NegotiateContentType + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ + static function negotiate($acceptHeaderValue, array $availableOptions) { + + return negotiateContentType($acceptHeaderValue, $availableOptions); + + } + + /** + * Parses a RFC2616-compatible date string + * + * This method returns false if the date is invalid + * + * @deprecated Use parseDate + * @param string $dateHeader + * @return bool|DateTime + */ + static function parseHTTPDate($dateHeader) { + + return parseDate($dateHeader); + + } + + /** + * Transforms a DateTime object to HTTP's most common date format. + * + * We're serializing it as the RFC 1123 date, which, for HTTP must be + * specified as GMT. + * + * @deprecated Use toDate + * @param \DateTime $dateTime + * @return string + */ + static function toHTTPDate(\DateTime $dateTime) { + + return toDate($dateTime); + + } +} diff --git a/libs/composer/vendor/sabre/http/lib/Version.php b/libs/composer/vendor/sabre/http/lib/Version.php new file mode 100644 index 000000000000..c40532ae8fcc --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This class contains the version number for the HTTP package + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '4.2.4'; + +} diff --git a/libs/composer/vendor/sabre/http/lib/functions.php b/libs/composer/vendor/sabre/http/lib/functions.php new file mode 100644 index 000000000000..d94119623996 --- /dev/null +++ b/libs/composer/vendor/sabre/http/lib/functions.php @@ -0,0 +1,445 @@ +<?php + +namespace Sabre\HTTP; + +use DateTime; + +/** + * A collection of useful helpers for parsing or generating various HTTP + * headers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ + +/** + * Parses a HTTP date-string. + * + * This method returns false if the date is invalid. + * + * The following formats are supported: + * Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate + * Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format + * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + * + * See: + * http://tools.ietf.org/html/rfc7231#section-7.1.1.1 + * + * @param string $dateString + * @return bool|DateTime + */ +function parseDate($dateString) { + + // Only the format is checked, valid ranges are checked by strtotime below + $month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'; + $weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)'; + $wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'; + $time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}'; + $date3 = $month . ' ([12]\d|3[01]| [1-9])'; + $date2 = '(0[1-9]|[12]\d|3[01])\-' . $month . '\-\d{2}'; + // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 + $date1 = '(0[1-9]|[12]\d|3[01]) ' . $month . ' [1-9]\d{3}'; + + // ANSI C's asctime() format + // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 + $asctime_date = $wkday . ' ' . $date3 . ' ' . $time . ' [1-9]\d{3}'; + // RFC 850, obsoleted by RFC 1036 + $rfc850_date = $weekday . ', ' . $date2 . ' ' . $time . ' GMT'; + // RFC 822, updated by RFC 1123 + $rfc1123_date = $wkday . ', ' . $date1 . ' ' . $time . ' GMT'; + // allowed date formats by RFC 2616 + $HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)"; + + // allow for space around the string and strip it + $dateString = trim($dateString, ' '); + if (!preg_match('/^' . $HTTP_date . '$/', $dateString)) + return false; + + // append implicit GMT timezone to ANSI C time format + if (strpos($dateString, ' GMT') === false) + $dateString .= ' GMT'; + + try { + return new DateTime($dateString, new \DateTimeZone('UTC')); + } catch (\Exception $e) { + return false; + } + +} + +/** + * Transforms a DateTime object to a valid HTTP/1.1 Date header value + * + * @param DateTime $dateTime + * @return string + */ +function toDate(DateTime $dateTime) { + + // We need to clone it, as we don't want to affect the existing + // DateTime. + $dateTime = clone $dateTime; + $dateTime->setTimezone(new \DateTimeZone('GMT')); + return $dateTime->format('D, d M Y H:i:s \G\M\T'); + +} + +/** + * This function can be used to aid with content negotiation. + * + * It takes 2 arguments, the $acceptHeaderValue, which usually comes from + * an Accept header, and $availableOptions, which contains an array of + * items that the server can support. + * + * The result of this function will be the 'best possible option'. If no + * best possible option could be found, null is returned. + * + * When it's null you can according to the spec either return a default, or + * you can choose to emit 406 Not Acceptable. + * + * The method also accepts sending 'null' for the $acceptHeaderValue, + * implying that no accept header was sent. + * + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ +function negotiateContentType($acceptHeaderValue, array $availableOptions) { + + if (!$acceptHeaderValue) { + // Grabbing the first in the list. + return reset($availableOptions); + } + + $proposals = array_map( + 'Sabre\HTTP\parseMimeType', + explode(',', $acceptHeaderValue) + ); + + // Ensuring array keys are reset. + $availableOptions = array_values($availableOptions); + + $options = array_map( + 'Sabre\HTTP\parseMimeType', + $availableOptions + ); + + $lastQuality = 0; + $lastSpecificity = 0; + $lastOptionIndex = 0; + $lastChoice = null; + + foreach ($proposals as $proposal) { + + // Ignoring broken values. + if (is_null($proposal)) continue; + + // If the quality is lower we don't have to bother comparing. + if ($proposal['quality'] < $lastQuality) { + continue; + } + + foreach ($options as $optionIndex => $option) { + + if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { + // no match on type. + continue; + } + if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { + // no match on subtype. + continue; + } + + // Any parameters appearing on the options must appear on + // proposals. + foreach ($option['parameters'] as $paramName => $paramValue) { + if (!array_key_exists($paramName, $proposal['parameters'])) { + continue 2; + } + if ($paramValue !== $proposal['parameters'][$paramName]) { + continue 2; + } + } + + // If we got here, we have a match on parameters, type and + // subtype. We need to calculate a score for how specific the + // match was. + $specificity = + ($proposal['type'] !== '*' ? 20 : 0) + + ($proposal['subType'] !== '*' ? 10 : 0) + + count($option['parameters']); + + + // Does this entry win? + if ( + ($proposal['quality'] > $lastQuality) || + ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || + ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) + ) { + + $lastQuality = $proposal['quality']; + $lastSpecificity = $specificity; + $lastOptionIndex = $optionIndex; + $lastChoice = $availableOptions[$optionIndex]; + + } + + } + + } + + return $lastChoice; + +} + +/** + * Parses the Prefer header, as defined in RFC7240. + * + * Input can be given as a single header value (string) or multiple headers + * (array of string). + * + * This method will return a key->value array with the various Prefer + * parameters. + * + * Prefer: return=minimal will result in: + * + * [ 'return' => 'minimal' ] + * + * Prefer: foo, wait=10 will result in: + * + * [ 'foo' => true, 'wait' => '10'] + * + * This method also supports the formats from older drafts of RFC7240, and + * it will automatically map them to the new values, as the older values + * are still pretty common. + * + * Parameters are currently discarded. There's no known prefer value that + * uses them. + * + * @param string|string[] $input + * @return array + */ +function parsePrefer($input) { + + $token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+'; + + // Work in progress + $word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )'; + + $regex = <<<REGEX +/ +^ +(?<name> $token) # Prefer property name +\s* # Optional space +(?: = \s* # Prefer property value + (?<value> $word) +)? +(?: \s* ; (?: .*))? # Prefer parameters (ignored) +$ +/x +REGEX; + + $output = []; + foreach (getHeaderValues($input) as $value) { + + if (!preg_match($regex, $value, $matches)) { + // Ignore + continue; + } + + // Mapping old values to their new counterparts + switch ($matches['name']) { + case 'return-asynch' : + $output['respond-async'] = true; + break; + case 'return-representation' : + $output['return'] = 'representation'; + break; + case 'return-minimal' : + $output['return'] = 'minimal'; + break; + case 'strict' : + $output['handling'] = 'strict'; + break; + case 'lenient' : + $output['handling'] = 'lenient'; + break; + default : + if (isset($matches['value'])) { + $value = trim($matches['value'], '"'); + } else { + $value = true; + } + $output[strtolower($matches['name'])] = empty($value) ? true : $value; + break; + } + + } + + return $output; + +} + +/** + * This method splits up headers into all their individual values. + * + * A HTTP header may have more than one header, such as this: + * Cache-Control: private, no-store + * + * Header values are always split with a comma. + * + * You can pass either a string, or an array. The resulting value is always + * an array with each spliced value. + * + * If the second headers argument is set, this value will simply be merged + * in. This makes it quicker to merge an old list of values with a new set. + * + * @param string|string[] $values + * @param string|string[] $values2 + * @return string[] + */ +function getHeaderValues($values, $values2 = null) { + + $values = (array)$values; + if ($values2) { + $values = array_merge($values, (array)$values2); + } + foreach ($values as $l1) { + foreach (explode(',', $l1) as $l2) { + $result[] = trim($l2); + } + } + return $result; + +} + +/** + * Parses a mime-type and splits it into: + * + * 1. type + * 2. subtype + * 3. quality + * 4. parameters + * + * @param string $str + * @return array + */ +function parseMimeType($str) { + + $parameters = []; + // If no q= parameter appears, then quality = 1. + $quality = 1; + + $parts = explode(';', $str); + + // The first part is the mime-type. + $mimeType = array_shift($parts); + + $mimeType = explode('/', trim($mimeType)); + if (count($mimeType) !== 2) { + // Illegal value + return null; + } + list($type, $subType) = $mimeType; + + foreach ($parts as $part) { + + $part = trim($part); + if (strpos($part, '=')) { + list($partName, $partValue) = + explode('=', $part, 2); + } else { + $partName = $part; + $partValue = null; + } + + // The quality parameter, if it appears, also marks the end of + // the parameter list. Anything after the q= counts as an + // 'accept extension' and could introduce new semantics in + // content-negotation. + if ($partName !== 'q') { + $parameters[$partName] = $part; + } else { + $quality = (float)$partValue; + break; // Stop parsing parts + } + + } + + return [ + 'type' => $type, + 'subType' => $subType, + 'quality' => $quality, + 'parameters' => $parameters, + ]; + +} + +/** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @param string $path + * @return string + */ +function encodePath($path) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $path); + +} + +/** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @param string $pathSegment + * @return string + */ +function encodePathSegment($pathSegment) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $pathSegment); +} + +/** + * Decodes a url-encoded path + * + * @param string $path + * @return string + */ +function decodePath($path) { + + return decodePathSegment($path); + +} + +/** + * Decodes a url-encoded path segment + * + * @param string $path + * @return string + */ +function decodePathSegment($path) { + + $path = rawurldecode($path); + $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']); + + switch ($encoding) { + + case 'ISO-8859-1' : + $path = utf8_encode($path); + + } + + return $path; + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php new file mode 100644 index 000000000000..650761acae34 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php @@ -0,0 +1,235 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class AWSTest extends \PHPUnit_Framework_TestCase { + + /** + * @var Sabre\HTTP\Response + */ + private $response; + + /** + * @var Sabre\HTTP\Request + */ + private $request; + + /** + * @var Sabre\HTTP\Auth\AWS + */ + private $auth; + + const REALM = 'SabreDAV unittest'; + + function setUp() { + + $this->response = new Response(); + $this->request = new Request(); + $this->auth = new AWS(self::REALM, $this->request, $this->response); + + } + + function testNoHeader() { + + $this->request->setMethod('GET'); + $result = $this->auth->init(); + + $this->assertFalse($result, 'No AWS Authorization header was supplied, so we should have gotten false'); + $this->assertEquals(AWS::ERR_NOAWSHEADER, $this->auth->errorCode); + + } + + function testIncorrectContentMD5() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + + $this->request->setMethod('GET'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => 'garbage', + ]); + $this->request->setUrl('/'); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_MD5CHECKSUMWRONG, $this->auth->errorCode); + + } + + function testNoDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + ]); + $this->request->setUrl('/'); + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_INVALIDDATEFORMAT, $this->auth->errorCode); + + } + + function testFutureDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('@' . (time() + (60 * 20))); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'Date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode); + + } + + function testPastDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('@' . (time() - (60 * 20))); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'Date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode); + + } + + function testIncorrectSignature() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('now'); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setUrl('/'); + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'X-amz-date' => $date, + ]); + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_INVALIDSIGNATURE, $this->auth->errorCode); + + } + + function testValidRequest() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('now'); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + + $sig = base64_encode($this->hmacsha1($secretKey, + "POST\n$contentMD5\n\n$date\nx-amz-date:$date\n/evert" + )); + + $this->request->setUrl('/evert'); + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:$sig", + 'Content-MD5' => $contentMD5, + 'X-amz-date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertTrue($result, 'Signature did not validate, got errorcode ' . $this->auth->errorCode); + $this->assertEquals($accessKey, $this->auth->getAccessKey()); + + } + + function test401() { + + $this->auth->requireLogin(); + $test = preg_match('/^AWS$/', $this->response->getHeader('WWW-Authenticate'), $matches); + $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern'); + + } + + /** + * Generates an HMAC-SHA1 signature + * + * @param string $key + * @param string $message + * @return string + */ + private function hmacsha1($key, $message) { + + $blocksize = 64; + if (strlen($key) > $blocksize) + $key = pack('H*', sha1($key)); + $key = str_pad($key, $blocksize, chr(0x00)); + $ipad = str_repeat(chr(0x36), $blocksize); + $opad = str_repeat(chr(0x5c), $blocksize); + $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message)))); + return $hmac; + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php new file mode 100644 index 000000000000..2a236f58b761 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class BasicTest extends \PHPUnit_Framework_TestCase { + + function testGetCredentials() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Basic ' . base64_encode('user:pass:bla') + ]); + + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertEquals([ + 'user', + 'pass:bla', + ], $basic->getCredentials()); + + } + + function testGetInvalidCredentialsColonMissing() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Basic ' . base64_encode('userpass') + ]); + + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testGetCredentialsNoheader() { + + $request = new Request('GET', '/', []); + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testGetCredentialsNotBasic() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'QBasic ' . base64_encode('user:pass:bla') + ]); + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testRequireLogin() { + + $response = new Response(); + $basic = new Basic('Dagger', new Request(), $response); + + $basic->requireLogin(); + + $this->assertEquals('Basic realm="Dagger", charset="UTF-8"', $response->getHeader('WWW-Authenticate')); + $this->assertEquals(401, $response->getStatus()); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php new file mode 100644 index 000000000000..ee2e9e0bd720 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class BearerTest extends \PHPUnit_Framework_TestCase { + + function testGetToken() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Bearer 12345' + ]); + + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertEquals( + '12345', + $bearer->getToken() + ); + + } + + function testGetCredentialsNoheader() { + + $request = new Request('GET', '/', []); + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertNull($bearer->getToken()); + + } + + function testGetCredentialsNotBearer() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'QBearer 12345' + ]); + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertNull($bearer->getToken()); + + } + + function testRequireLogin() { + + $response = new Response(); + $bearer = new Bearer('Dagger', new Request(), $response); + + $bearer->requireLogin(); + + $this->assertEquals('Bearer realm="Dagger"', $response->getHeader('WWW-Authenticate')); + $this->assertEquals(401, $response->getStatus()); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php new file mode 100644 index 000000000000..ffb69c76d6d3 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php @@ -0,0 +1,191 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class DigestTest extends \PHPUnit_Framework_TestCase { + + /** + * @var Sabre\HTTP\Response + */ + private $response; + + /** + * request + * + * @var Sabre\HTTP\Request + */ + private $request; + + /** + * @var Sabre\HTTP\Auth\Digest + */ + private $auth; + + const REALM = 'SabreDAV unittest'; + + function setUp() { + + $this->response = new Response(); + $this->request = new Request(); + $this->auth = new Digest(self::REALM, $this->request, $this->response); + + + } + + function testDigest() { + + list($nonce, $opaque) = $this->getServerTokens(); + + $username = 'admin'; + $password = 12345; + $nc = '00002'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth:' . + md5('GET' . ':' . '/') + ); + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + + $this->auth->init(); + + $this->assertEquals($username, $this->auth->getUsername()); + $this->assertEquals(self::REALM, $this->auth->getRealm()); + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + $this->assertTrue($this->auth->validatePassword($password), 'Authentication is deemed invalid through validatePassword'); + + } + + function testInvalidDigest() { + + list($nonce, $opaque) = $this->getServerTokens(); + + $username = 'admin'; + $password = 12345; + $nc = '00002'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth:' . + md5('GET' . ':' . '/') + ); + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + + $this->auth->init(); + + $this->assertFalse($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . ($password . 'randomness'))), 'Authentication is deemed invalid through validateA1'); + + } + + function testInvalidDigest2() { + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'basic blablabla'); + + $this->auth->init(); + $this->assertFalse($this->auth->validateA1(md5('user:realm:password'))); + + } + + + function testDigestAuthInt() { + + $this->auth->setQOP(Digest::QOP_AUTHINT); + list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT); + + $username = 'admin'; + $password = 12345; + $nc = '00003'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth-int:' . + md5('POST' . ':' . '/' . ':' . md5('body')) + ); + + $this->request->setMethod('POST'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + $this->request->setBody('body'); + + $this->auth->init(); + + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + + } + + function testDigestAuthBoth() { + + $this->auth->setQOP(Digest::QOP_AUTHINT | Digest::QOP_AUTH); + list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT | Digest::QOP_AUTH); + + $username = 'admin'; + $password = 12345; + $nc = '00003'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth-int:' . + md5('POST' . ':' . '/' . ':' . md5('body')) + ); + + $this->request->setMethod('POST'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + $this->request->setBody('body'); + + $this->auth->init(); + + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + + } + + + private function getServerTokens($qop = Digest::QOP_AUTH) { + + $this->auth->requireLogin(); + + switch ($qop) { + case Digest::QOP_AUTH : $qopstr = 'auth'; break; + case Digest::QOP_AUTHINT : $qopstr = 'auth-int'; break; + default : $qopstr = 'auth,auth-int'; break; + } + + $test = preg_match('/Digest realm="' . self::REALM . '",qop="' . $qopstr . '",nonce="([0-9a-f]*)",opaque="([0-9a-f]*)"/', + $this->response->getHeader('WWW-Authenticate'), $matches); + + $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern. We received: ' . $this->response->getHeader('WWW-Authenticate')); + + $nonce = $matches[1]; + $opaque = $matches[2]; + + // Reset our environment + $this->setUp(); + $this->auth->setQOP($qop); + + return [$nonce,$opaque]; + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/ClientTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/ClientTest.php new file mode 100644 index 000000000000..ea25907df2d0 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/ClientTest.php @@ -0,0 +1,474 @@ +<?php + +namespace Sabre\HTTP; + +class ClientTest extends \PHPUnit_Framework_TestCase { + + function testCreateCurlSettingsArrayGET() { + + $client = new ClientMock(); + $client->addCurlSetting(CURLOPT_POSTREDIR, 0); + + $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_POSTREDIR => 0, + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_NOBODY => false, + CURLOPT_URL => 'http://example.org/', + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayHEAD() { + + $client = new ClientMock(); + $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); + + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => true, + CURLOPT_CUSTOMREQUEST => 'HEAD', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayGETAfterHEAD() { + + $client = new ClientMock(); + $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); + + // Parsing the settings for this method, and discarding the result. + // This will cause the client to automatically persist previous + // settings and will help us detect problems. + $client->createCurlSettingsArray($request); + + // This is the real request. + $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); + + $settings = [ + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_NOBODY => false, + CURLOPT_URL => 'http://example.org/', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayPUTStream() { + + $client = new ClientMock(); + + $h = fopen('php://memory', 'r+'); + fwrite($h, 'booh'); + $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], $h); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_PUT => true, + CURLOPT_INFILE => $h, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayPUTString() { + + $client = new ClientMock(); + $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], 'boo'); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_POSTFIELDS => 'boo', + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testSend() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(200); + }); + + $response = $client->send($request); + + $this->assertEquals(200, $response->getStatus()); + + } + + function testSendClientError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + throw new ClientException('aaah', 1); + }); + $called = false; + $client->on('exception', function() use (&$called) { + $called = true; + }); + + try { + $client->send($request); + $this->fail('send() should have thrown an exception'); + } catch (ClientException $e) { + + } + $this->assertTrue($called); + + } + + function testSendHttpError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(404); + }); + $called = 0; + $client->on('error', function() use (&$called) { + $called++; + }); + $client->on('error:404', function() use (&$called) { + $called++; + }); + + $client->send($request); + $this->assertEquals(2, $called); + + } + + function testSendRetry() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $called = 0; + $client->on('doRequest', function($request, &$response) use (&$called) { + $called++; + if ($called < 3) { + $response = new Response(404); + } else { + $response = new Response(200); + } + }); + + $errorCalled = 0; + $client->on('error', function($request, $response, &$retry, $retryCount) use (&$errorCalled) { + + $errorCalled++; + $retry = true; + + }); + + $response = $client->send($request); + $this->assertEquals(3, $called); + $this->assertEquals(2, $errorCalled); + $this->assertEquals(200, $response->getStatus()); + + } + + function testHttpErrorException() { + + $client = new ClientMock(); + $client->setThrowExceptions(true); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(404); + }); + + try { + $client->send($request); + $this->fail('An exception should have been thrown'); + } catch (ClientHttpException $e) { + $this->assertEquals(404, $e->getHttpStatus()); + $this->assertInstanceOf('Sabre\HTTP\Response', $e->getResponse()); + } + + } + + function testParseCurlResult() { + + $client = new ClientMock(); + $client->on('curlStuff', function(&$return) { + + $return = [ + [ + 'header_size' => 33, + 'http_code' => 200, + ], + 0, + '', + ]; + + }); + + $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + $result = $client->parseCurlResult($body, 'foobar'); + + $this->assertEquals(Client::STATUS_SUCCESS, $result['status']); + $this->assertEquals(200, $result['http_code']); + $this->assertEquals(200, $result['response']->getStatus()); + $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders()); + $this->assertEquals('Foo', $result['response']->getBodyAsString()); + + } + + function testParseCurlError() { + + $client = new ClientMock(); + $client->on('curlStuff', function(&$return) { + + $return = [ + [], + 1, + 'Curl error', + ]; + + }); + + $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + $result = $client->parseCurlResult($body, 'foobar'); + + $this->assertEquals(Client::STATUS_CURLERROR, $result['status']); + $this->assertEquals(1, $result['curl_errno']); + $this->assertEquals('Curl error', $result['curl_errmsg']); + + } + + function testDoRequest() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + $client->on('curlExec', function(&$return) { + + $return = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + + }); + $client->on('curlStuff', function(&$return) { + + $return = [ + [ + 'header_size' => 33, + 'http_code' => 200, + ], + 0, + '', + ]; + + }); + $response = $client->doRequest($request); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders()); + $this->assertEquals('Foo', $response->getBodyAsString()); + + } + + function testDoRequestCurlError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + $client->on('curlExec', function(&$return) { + + $return = ""; + + }); + $client->on('curlStuff', function(&$return) { + + $return = [ + [], + 1, + 'Curl error', + ]; + + }); + + try { + $response = $client->doRequest($request); + $this->fail('This should have thrown an exception'); + } catch (ClientException $e) { + $this->assertEquals(1, $e->getCode()); + $this->assertEquals('Curl error', $e->getMessage()); + } + + } + +} + +class ClientMock extends Client { + + protected $persistedSettings = []; + + /** + * Making this method public. + * + * We are also going to persist all settings this method generates. While + * the underlying object doesn't behave exactly the same, it helps us + * simulate what curl does internally, and helps us identify problems with + * settings that are set by _some_ methods and not correctly reset by other + * methods after subsequent use. + * forces + */ + function createCurlSettingsArray(RequestInterface $request) { + + $settings = parent::createCurlSettingsArray($request); + $settings = $settings + $this->persistedSettings; + $this->persistedSettings = $settings; + return $settings; + + } + /** + * Making this method public. + */ + function parseCurlResult($response, $curlHandle) { + + return parent::parseCurlResult($response, $curlHandle); + + } + + /** + * This method is responsible for performing a single request. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + function doRequest(RequestInterface $request) { + + $response = null; + $this->emit('doRequest', [$request, &$response]); + + // If nothing modified $response, we're using the default behavior. + if (is_null($response)) { + return parent::doRequest($request); + } else { + return $response; + } + + } + + /** + * Returns a bunch of information about a curl request. + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return array + */ + protected function curlStuff($curlHandle) { + + $return = null; + $this->emit('curlStuff', [&$return]); + + // If nothing modified $return, we're using the default behavior. + if (is_null($return)) { + return parent::curlStuff($curlHandle); + } else { + return $return; + } + + } + + /** + * Calls curl_exec + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return string + */ + protected function curlExec($curlHandle) { + + $return = null; + $this->emit('curlExec', [&$return]); + + // If nothing modified $return, we're using the default behavior. + if (is_null($return)) { + return parent::curlExec($curlHandle); + } else { + return $return; + } + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/FunctionsTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/FunctionsTest.php new file mode 100644 index 000000000000..a107d1f007a6 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/FunctionsTest.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\HTTP; + +class FunctionsTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider getHeaderValuesData + */ + function testGetHeaderValues($input, $output) { + + $this->assertEquals( + $output, + getHeaderValues($input) + ); + + } + + function getHeaderValuesData() { + + return [ + [ + "a", + ["a"] + ], + [ + "a,b", + ["a", "b"] + ], + [ + "a, b", + ["a", "b"] + ], + [ + ["a, b"], + ["a", "b"] + ], + [ + ["a, b", "c", "d,e"], + ["a", "b", "c", "d", "e"] + ], + ]; + + } + + /** + * @dataProvider preferData + */ + function testPrefer($input, $output) { + + $this->assertEquals( + $output, + parsePrefer($input) + ); + + } + + function preferData() { + + return [ + [ + 'foo; bar', + ['foo' => true] + ], + [ + 'foo; bar=""', + ['foo' => true] + ], + [ + 'foo=""; bar', + ['foo' => true] + ], + [ + 'FOO', + ['foo' => true] + ], + [ + 'respond-async', + ['respond-async' => true] + ], + [ + + ['respond-async, wait=100', 'handling=lenient'], + ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient'] + ], + [ + + ['respond-async, wait=100, handling=lenient'], + ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient'] + ], + // Old values + [ + + 'return-asynch, return-representation', + ['respond-async' => true, 'return' => 'representation'], + ], + [ + + 'return-minimal', + ['return' => 'minimal'], + ], + [ + + 'strict', + ['handling' => 'strict'], + ], + [ + + 'lenient', + ['handling' => 'lenient'], + ], + // Invalid token + [ + ['foo=%bar%'], + [], + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php new file mode 100644 index 000000000000..a4052c60c0c9 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php @@ -0,0 +1,93 @@ +<?php + +namespace Sabre\HTTP; + +class MessageDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Request(); + $this->outer = new RequestDecorator($this->inner); + + } + + function testBody() { + + $this->outer->setBody('foo'); + $this->assertEquals('foo', stream_get_contents($this->inner->getBodyAsStream())); + $this->assertEquals('foo', stream_get_contents($this->outer->getBodyAsStream())); + $this->assertEquals('foo', $this->inner->getBodyAsString()); + $this->assertEquals('foo', $this->outer->getBodyAsString()); + $this->assertEquals('foo', $this->inner->getBody()); + $this->assertEquals('foo', $this->outer->getBody()); + + } + + function testHeaders() { + + $this->outer->setHeaders([ + 'a' => 'b', + ]); + + $this->assertEquals(['a' => ['b']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b']], $this->outer->getHeaders()); + + $this->outer->setHeaders([ + 'c' => 'd', + ]); + + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->outer->getHeaders()); + + $this->outer->addHeaders([ + 'e' => 'f', + ]); + + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->outer->getHeaders()); + } + + function testHeader() { + + $this->assertFalse($this->outer->hasHeader('a')); + $this->assertFalse($this->inner->hasHeader('a')); + $this->outer->setHeader('a', 'c'); + $this->assertTrue($this->outer->hasHeader('a')); + $this->assertTrue($this->inner->hasHeader('a')); + + $this->assertEquals('c', $this->inner->getHeader('A')); + $this->assertEquals('c', $this->outer->getHeader('A')); + + $this->outer->addHeader('A', 'd'); + + $this->assertEquals( + ['c', 'd'], + $this->inner->getHeaderAsArray('A') + ); + $this->assertEquals( + ['c', 'd'], + $this->outer->getHeaderAsArray('A') + ); + + $success = $this->outer->removeHeader('a'); + + $this->assertTrue($success); + $this->assertNull($this->inner->getHeader('A')); + $this->assertNull($this->outer->getHeader('A')); + + $this->assertFalse($this->outer->removeHeader('i-dont-exist')); + } + + function testHttpVersion() { + + $this->outer->setHttpVersion('1.0'); + + $this->assertEquals('1.0', $this->inner->getHttpVersion()); + $this->assertEquals('1.0', $this->outer->getHttpVersion()); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/MessageTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/MessageTest.php new file mode 100644 index 000000000000..cb5aadc416ca --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/MessageTest.php @@ -0,0 +1,246 @@ +<?php + +namespace Sabre\HTTP; + +class MessageTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $message = new MessageMock(); + $this->assertInstanceOf('Sabre\HTTP\Message', $message); + + } + + function testStreamBody() { + + $body = 'foo'; + $h = fopen('php://memory', 'r+'); + fwrite($h, $body); + rewind($h); + + $message = new MessageMock(); + $message->setBody($h); + + $this->assertEquals($body, $message->getBodyAsString()); + rewind($h); + $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); + rewind($h); + $this->assertEquals($body, stream_get_contents($message->getBody())); + + } + + function testStringBody() { + + $body = 'foo'; + + $message = new MessageMock(); + $message->setBody($body); + + $this->assertEquals($body, $message->getBodyAsString()); + $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); + $this->assertEquals($body, $message->getBody()); + + } + + /** + * It's possible that streams contains more data than the Content-Length. + * + * The request object should make sure to never emit more than + * Content-Length, if Content-Length is set. + * + * This is in particular useful when respoding to range requests with + * streams that represent files on the filesystem, as it's possible to just + * seek the stream to a certain point, set the content-length and let the + * request object do the rest. + */ + function testLongStreamToStringBody() { + + $body = fopen('php://memory', 'r+'); + fwrite($body, 'abcdefg'); + fseek($body, 2); + + $message = new MessageMock(); + $message->setBody($body); + $message->setHeader('Content-Length', '4'); + + $this->assertEquals( + 'cdef', + $message->getBodyAsString() + ); + + } + + /** + * Some clients include a content-length header, but the header is empty. + * This is definitely broken behavior, but we should support it. + */ + function testEmptyContentLengthHeader() { + + $body = fopen('php://memory', 'r+'); + fwrite($body, 'abcdefg'); + fseek($body, 2); + + $message = new MessageMock(); + $message->setBody($body); + $message->setHeader('Content-Length', ''); + + $this->assertEquals( + 'cdefg', + $message->getBodyAsString() + ); + + } + + + function testGetEmptyBodyStream() { + + $message = new MessageMock(); + $body = $message->getBodyAsStream(); + + $this->assertEquals('', stream_get_contents($body)); + + } + + function testGetEmptyBodyString() { + + $message = new MessageMock(); + $body = $message->getBodyAsString(); + + $this->assertEquals('', $body); + + } + + function testHeaders() { + + $message = new MessageMock(); + $message->setHeader('X-Foo', 'bar'); + + // Testing caselessness + $this->assertEquals('bar', $message->getHeader('X-Foo')); + $this->assertEquals('bar', $message->getHeader('x-fOO')); + + $this->assertTrue( + $message->removeHeader('X-FOO') + ); + $this->assertNull($message->getHeader('X-Foo')); + $this->assertFalse( + $message->removeHeader('X-FOO') + ); + + } + + function testSetHeaders() { + + $message = new MessageMock(); + + $headers = [ + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], + ]; + + $message->setHeaders($headers); + $this->assertEquals($headers, $message->getHeaders()); + + $message->setHeaders([ + 'X-Foo' => ['3', '4'], + 'X-Bar' => '5', + ]); + + $expected = [ + 'X-Foo' => ['3','4'], + 'X-Bar' => ['5'], + ]; + + $this->assertEquals($expected, $message->getHeaders()); + + } + + function testAddHeaders() { + + $message = new MessageMock(); + + $headers = [ + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], + ]; + + $message->addHeaders($headers); + $this->assertEquals($headers, $message->getHeaders()); + + $message->addHeaders([ + 'X-Foo' => ['3', '4'], + 'X-Bar' => '5', + ]); + + $expected = [ + 'X-Foo' => ['1','3','4'], + 'X-Bar' => ['2','5'], + ]; + + $this->assertEquals($expected, $message->getHeaders()); + + } + + function testSendBody() { + + $message = new MessageMock(); + + // String + $message->setBody('foo'); + + // Stream + $h = fopen('php://memory', 'r+'); + fwrite($h, 'bar'); + rewind($h); + $message->setBody($h); + + $body = $message->getBody(); + rewind($body); + + $this->assertEquals('bar', stream_get_contents($body)); + + } + + function testMultipleHeaders() { + + $message = new MessageMock(); + $message->setHeader('a', '1'); + $message->addHeader('A', '2'); + + $this->assertEquals( + "1,2", + $message->getHeader('A') + ); + $this->assertEquals( + "1,2", + $message->getHeader('a') + ); + + $this->assertEquals( + ['1', '2'], + $message->getHeaderAsArray('a') + ); + $this->assertEquals( + ['1', '2'], + $message->getHeaderAsArray('A') + ); + $this->assertEquals( + [], + $message->getHeaderAsArray('B') + ); + + } + + function testHasHeaders() { + + $message = new MessageMock(); + + $this->assertFalse($message->hasHeader('X-Foo')); + $message->setHeader('X-Foo', 'Bar'); + $this->assertTrue($message->hasHeader('X-Foo')); + + } + +} + +class MessageMock extends Message { } diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php new file mode 100644 index 000000000000..08af48749bd4 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php @@ -0,0 +1,112 @@ +<?php + +namespace Sabre\HTTP; + +class RequestDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Request(); + $this->outer = new RequestDecorator($this->inner); + + } + + function testMethod() { + + $this->outer->setMethod('FOO'); + $this->assertEquals('FOO', $this->inner->getMethod()); + $this->assertEquals('FOO', $this->outer->getMethod()); + + } + + function testUrl() { + + $this->outer->setUrl('/foo'); + $this->assertEquals('/foo', $this->inner->getUrl()); + $this->assertEquals('/foo', $this->outer->getUrl()); + + } + + function testAbsoluteUrl() { + + $this->outer->setAbsoluteUrl('http://example.org/foo'); + $this->assertEquals('http://example.org/foo', $this->inner->getAbsoluteUrl()); + $this->assertEquals('http://example.org/foo', $this->outer->getAbsoluteUrl()); + + } + + function testBaseUrl() { + + $this->outer->setBaseUrl('/foo'); + $this->assertEquals('/foo', $this->inner->getBaseUrl()); + $this->assertEquals('/foo', $this->outer->getBaseUrl()); + + } + + function testPath() { + + $this->outer->setBaseUrl('/foo'); + $this->outer->setUrl('/foo/bar'); + $this->assertEquals('bar', $this->inner->getPath()); + $this->assertEquals('bar', $this->outer->getPath()); + + } + + function testQueryParams() { + + $this->outer->setUrl('/foo?a=b&c=d&e'); + $expected = [ + 'a' => 'b', + 'c' => 'd', + 'e' => null, + ]; + + $this->assertEquals($expected, $this->inner->getQueryParameters()); + $this->assertEquals($expected, $this->outer->getQueryParameters()); + + } + + function testPostData() { + + $postData = [ + 'a' => 'b', + 'c' => 'd', + 'e' => null, + ]; + + $this->outer->setPostData($postData); + $this->assertEquals($postData, $this->inner->getPostData()); + $this->assertEquals($postData, $this->outer->getPostData()); + + } + + + function testServerData() { + + $serverData = [ + 'HTTPS' => 'On', + ]; + + $this->outer->setRawServerData($serverData); + $this->assertEquals('On', $this->inner->getRawServerValue('HTTPS')); + $this->assertEquals('On', $this->outer->getRawServerValue('HTTPS')); + + $this->assertNull($this->inner->getRawServerValue('FOO')); + $this->assertNull($this->outer->getRawServerValue('FOO')); + } + + function testToString() { + + $this->inner->setMethod('POST'); + $this->inner->setUrl('/foo/bar/'); + $this->inner->setBody('foo'); + $this->inner->setHeader('foo', 'bar'); + + $this->assertEquals((string)$this->inner, (string)$this->outer); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/RequestTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/RequestTest.php new file mode 100644 index 000000000000..e3daab4d354f --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/RequestTest.php @@ -0,0 +1,167 @@ +<?php + +namespace Sabre\HTTP; + +class RequestTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $request = new Request('GET', '/foo', [ + 'User-Agent' => 'Evert', + ]); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'User-Agent' => ['Evert'], + ], $request->getHeaders()); + + } + + function testGetQueryParameters() { + + $request = new Request('GET', '/foo?a=b&c&d=e'); + $this->assertEquals([ + 'a' => 'b', + 'c' => null, + 'd' => 'e', + ], $request->getQueryParameters()); + + } + + function testGetQueryParametersNoData() { + + $request = new Request('GET', '/foo'); + $this->assertEquals([], $request->getQueryParameters()); + + } + + /** + * @backupGlobals + */ + function testCreateFromPHPRequest() { + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $request = Sapi::getRequest(); + $this->assertEquals('PUT', $request->getMethod()); + + } + + function testGetAbsoluteUrl() { + + $s = [ + 'HTTP_HOST' => 'sabredav.org', + 'REQUEST_URI' => '/foo' + ]; + + $r = Sapi::createFromServerArray($s); + + $this->assertEquals('http://sabredav.org/foo', $r->getAbsoluteUrl()); + + $s = [ + 'HTTP_HOST' => 'sabredav.org', + 'REQUEST_URI' => '/foo', + 'HTTPS' => 'on', + ]; + + $r = Sapi::createFromServerArray($s); + + $this->assertEquals('https://sabredav.org/foo', $r->getAbsoluteUrl()); + + } + + function testGetPostData() { + + $post = [ + 'bla' => 'foo', + ]; + $r = new Request(); + $r->setPostData($post); + $this->assertEquals($post, $r->getPostData()); + + } + + function testGetPath() { + + $request = new Request(); + $request->setBaseUrl('/foo'); + $request->setUrl('/foo/bar/'); + + $this->assertEquals('bar', $request->getPath()); + + } + + function testGetPathStrippedQuery() { + + $request = new Request(); + $request->setBaseUrl('/foo'); + $request->setUrl('/foo/bar/?a=b'); + + $this->assertEquals('bar', $request->getPath()); + + } + + function testGetPathMissingSlash() { + + $request = new Request(); + $request->setBaseUrl('/foo/'); + $request->setUrl('/foo'); + + $this->assertEquals('', $request->getPath()); + + } + + /** + * @expectedException \LogicException + */ + function testGetPathOutsideBaseUrl() { + + $request = new Request(); + $request->setBaseUrl('/foo/'); + $request->setUrl('/bar/'); + + $request->getPath(); + + } + + function testToString() { + + $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml']); + $request->setBody('foo'); + + $expected = <<<HI +PUT /foo/bar HTTP/1.1\r +Content-Type: text/xml\r +\r +foo +HI; + $this->assertEquals($expected, (string)$request); + + } + + function testToStringAuthorization() { + + $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml', 'Authorization' => 'Basic foobar']); + $request->setBody('foo'); + + $expected = <<<HI +PUT /foo/bar HTTP/1.1\r +Content-Type: text/xml\r +Authorization: Basic REDACTED\r +\r +foo +HI; + $this->assertEquals($expected, (string)$request); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testConstructorWithArray() { + + $request = new Request([]); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php new file mode 100644 index 000000000000..838953b3144a --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\HTTP; + +class ResponseDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Response(); + $this->outer = new ResponseDecorator($this->inner); + + } + + function testStatus() { + + $this->outer->setStatus(201); + $this->assertEquals(201, $this->inner->getStatus()); + $this->assertEquals(201, $this->outer->getStatus()); + $this->assertEquals('Created', $this->inner->getStatusText()); + $this->assertEquals('Created', $this->outer->getStatusText()); + + } + + function testToString() { + + $this->inner->setStatus(201); + $this->inner->setBody('foo'); + $this->inner->setHeader('foo', 'bar'); + + $this->assertEquals((string)$this->inner, (string)$this->outer); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/ResponseTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/ResponseTest.php new file mode 100644 index 000000000000..117551bb9694 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/ResponseTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\HTTP; + +class ResponseTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $response = new Response(200, ['Content-Type' => 'text/xml']); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('OK', $response->getStatusText()); + + } + + function testSetStatus() { + + $response = new Response(); + $response->setStatus('402 Where\'s my money?'); + $this->assertEquals(402, $response->getStatus()); + $this->assertEquals('Where\'s my money?', $response->getStatusText()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidStatus() { + + $response = new Response(1000); + + } + + function testToString() { + + $response = new Response(200, ['Content-Type' => 'text/xml']); + $response->setBody('foo'); + + $expected = <<<HI +HTTP/1.1 200 OK\r +Content-Type: text/xml\r +\r +foo +HI; + $this->assertEquals($expected, (string)$response); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/SapiTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/SapiTest.php new file mode 100644 index 000000000000..158ce2171b99 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/SapiTest.php @@ -0,0 +1,167 @@ +<?php + +namespace Sabre\HTTP; + +class SapiTest extends \PHPUnit_Framework_TestCase { + + function testConstructFromServerArray() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'Evert', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '400', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'User-Agent' => ['Evert'], + 'Content-Type' => ['text/xml'], + 'Content-Length' => ['400'], + ], $request->getHeaders()); + + $this->assertEquals('1.0', $request->getHttpVersion()); + + $this->assertEquals('400', $request->getRawServerValue('CONTENT_LENGTH')); + $this->assertNull($request->getRawServerValue('FOO')); + + } + + function testConstructPHPAuth() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Basic ' . base64_encode('user:pass')], + ], $request->getHeaders()); + + } + + function testConstructPHPAuthDigest() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_DIGEST' => 'blabla', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Digest blabla'], + ], $request->getHeaders()); + + } + + function testConstructRedirectAuth() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'REDIRECT_HTTP_AUTHORIZATION' => 'Basic bla', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Basic bla'], + ], $request->getHeaders()); + + } + + /** + * @runInSeparateProcess + * + * Unfortunately we have no way of testing if the HTTP response code got + * changed. + */ + function testSend() { + + if (!function_exists('xdebug_get_headers')) { + $this->markTestSkipped('XDebug needs to be installed for this test to run'); + } + + $response = new Response(204, ['Content-Type' => 'text/xml;charset=UTF-8']); + + // Second Content-Type header. Normally this doesn't make sense. + $response->addHeader('Content-Type', 'application/xml'); + $response->setBody('foo'); + + ob_start(); + + Sapi::sendResponse($response); + $headers = xdebug_get_headers(); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals( + [ + "Content-Type: text/xml;charset=UTF-8", + "Content-Type: application/xml", + ], + $headers + ); + + $this->assertEquals('foo', $result); + + } + + /** + * @runInSeparateProcess + * @depends testSend + */ + function testSendLimitedByContentLengthString() { + + $response = new Response(200); + + $response->addHeader('Content-Length', 19); + $response->setBody('Send this sentence. Ignore this one.'); + + ob_start(); + + Sapi::sendResponse($response); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals('Send this sentence.', $result); + + } + + /** + * @runInSeparateProcess + * @depends testSend + */ + function testSendLimitedByContentLengthStream() { + + $response = new Response(200, ['Content-Length' => 19]); + + $body = fopen('php://memory', 'w'); + fwrite($body, 'Ignore this. Send this sentence. Ignore this too.'); + rewind($body); + fread($body, 13); + $response->setBody($body); + + ob_start(); + + Sapi::sendResponse($response); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals('Send this sentence.', $result); + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/URLUtilTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/URLUtilTest.php new file mode 100644 index 000000000000..a2d65a5e36dd --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/URLUtilTest.php @@ -0,0 +1,187 @@ +<?php + +namespace Sabre\HTTP; + +class URLUtilTest extends \PHPUnit_Framework_TestCase{ + + function testEncodePath() { + + $str = ''; + for ($i = 0;$i < 128;$i++) $str .= chr($i); + + $newStr = URLUtil::encodePath($str); + + $this->assertEquals( + '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' . + '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' . + '%20%21%22%23%24%25%26%27()%2a%2b%2c-./' . + '0123456789:%3b%3c%3d%3e%3f' . + '@ABCDEFGHIJKLMNO' . + 'PQRSTUVWXYZ%5b%5c%5d%5e_' . + '%60abcdefghijklmno' . + 'pqrstuvwxyz%7b%7c%7d~%7f', + $newStr); + + $this->assertEquals($str, URLUtil::decodePath($newStr)); + + } + + function testEncodePathSegment() { + + $str = ''; + for ($i = 0;$i < 128;$i++) $str .= chr($i); + + $newStr = URLUtil::encodePathSegment($str); + + // Note: almost exactly the same as the last test, with the + // exception of the encoding of / (ascii code 2f) + $this->assertEquals( + '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' . + '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' . + '%20%21%22%23%24%25%26%27()%2a%2b%2c-.%2f' . + '0123456789:%3b%3c%3d%3e%3f' . + '@ABCDEFGHIJKLMNO' . + 'PQRSTUVWXYZ%5b%5c%5d%5e_' . + '%60abcdefghijklmno' . + 'pqrstuvwxyz%7b%7c%7d~%7f', + $newStr); + + $this->assertEquals($str, URLUtil::decodePathSegment($newStr)); + + } + + function testDecode() { + + $str = 'Hello%20Test+Test2.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals('Hello Test+Test2.txt', $newStr); + + } + + /** + * @depends testDecode + */ + function testDecodeUmlaut() { + + $str = 'Hello%C3%BC.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals("Hello\xC3\xBC.txt", $newStr); + + } + + /** + * @depends testDecodeUmlaut + */ + function testDecodeUmlautLatin1() { + + $str = 'Hello%FC.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals("Hello\xC3\xBC.txt", $newStr); + + } + + /** + * This testcase was sent by a bug reporter + * + * @depends testDecode + */ + function testDecodeAccentsWindows7() { + + $str = '/webdav/%C3%A0fo%C3%B3'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals(strtolower($str), URLUtil::encodePath($newStr)); + + } + + function testSplitPath() { + + $strings = [ + + // input // expected result + '/foo/bar' => ['/foo','bar'], + '/foo/bar/' => ['/foo','bar'], + 'foo/bar/' => ['foo','bar'], + 'foo/bar' => ['foo','bar'], + 'foo/bar/baz' => ['foo/bar','baz'], + 'foo/bar/baz/' => ['foo/bar','baz'], + 'foo' => ['','foo'], + 'foo/' => ['','foo'], + '/foo/' => ['','foo'], + '/foo' => ['','foo'], + '' => [null,null], + + // UTF-8 + "/\xC3\xA0fo\xC3\xB3/bar" => ["/\xC3\xA0fo\xC3\xB3",'bar'], + "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"], + "foo/\xC3\xA0\xC3\xBCr" => ["foo","\xC3\xA0\xC3\xBCr"], + + ]; + + foreach ($strings as $input => $expected) { + + $output = URLUtil::splitPath($input); + $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect'); + + + } + + } + + /** + * @dataProvider resolveData + */ + function testResolve($base, $update, $expected) { + + $this->assertEquals( + $expected, + URLUtil::resolve($base, $update) + ); + + } + + function resolveData() { + + return [ + [ + 'http://example.org/foo/baz', + '/bar', + 'http://example.org/bar', + ], + [ + 'https://example.org/foo', + '//example.net/', + 'https://example.net/', + ], + [ + 'https://example.org/foo', + '?a=b', + 'https://example.org/foo?a=b', + ], + [ + '//example.org/foo', + '?a=b', + '//example.org/foo?a=b', + ], + // Ports and fragments + [ + 'https://example.org:81/foo#hey', + '?a=b#c=d', + 'https://example.org:81/foo?a=b#c=d', + ], + // Relative.. in-directory paths + [ + 'http://example.org/foo/bar', + 'bar2', + 'http://example.org/foo/bar2', + ], + // Now the base path ended with a slash + [ + 'http://example.org/foo/bar/', + 'bar2/bar3', + 'http://example.org/foo/bar/bar2/bar3', + ], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/http/tests/HTTP/UtilTest.php b/libs/composer/vendor/sabre/http/tests/HTTP/UtilTest.php new file mode 100644 index 000000000000..5659bdd2e82f --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/HTTP/UtilTest.php @@ -0,0 +1,206 @@ +<?php + +namespace Sabre\HTTP; + +class UtilTest extends \PHPUnit_Framework_TestCase { + + function testParseHTTPDate() { + + $times = [ + 'Wed, 13 Oct 2010 10:26:00 GMT', + 'Wednesday, 13-Oct-10 10:26:00 GMT', + 'Wed Oct 13 10:26:00 2010', + ]; + + $expected = 1286965560; + + foreach ($times as $time) { + $result = Util::parseHTTPDate($time); + $this->assertEquals($expected, $result->format('U')); + } + + $result = Util::parseHTTPDate('Wed Oct 6 10:26:00 2010'); + $this->assertEquals(1286360760, $result->format('U')); + + } + + function testParseHTTPDateFail() { + + $times = [ + //random string + 'NOW', + // not-GMT timezone + 'Wednesday, 13-Oct-10 10:26:00 UTC', + // No space before the 6 + 'Wed Oct 6 10:26:00 2010', + // Invalid day + 'Wed Oct 0 10:26:00 2010', + 'Wed Oct 32 10:26:00 2010', + 'Wed, 0 Oct 2010 10:26:00 GMT', + 'Wed, 32 Oct 2010 10:26:00 GMT', + 'Wednesday, 32-Oct-10 10:26:00 GMT', + // Invalid hour + 'Wed, 13 Oct 2010 24:26:00 GMT', + 'Wednesday, 13-Oct-10 24:26:00 GMT', + 'Wed Oct 13 24:26:00 2010', + ]; + + foreach ($times as $time) { + $this->assertFalse(Util::parseHTTPDate($time), 'We used the string: ' . $time); + } + + } + + function testTimezones() { + + $default = date_default_timezone_get(); + date_default_timezone_set('Europe/Amsterdam'); + + $this->testParseHTTPDate(); + + date_default_timezone_set($default); + + } + + function testToHTTPDate() { + + $dt = new \DateTime('2011-12-10 12:00:00 +0200'); + + $this->assertEquals( + 'Sat, 10 Dec 2011 10:00:00 GMT', + Util::toHTTPDate($dt) + ); + + } + + /** + * @dataProvider negotiateData + */ + function testNegotiate($acceptHeader, $available, $expected) { + + $this->assertEquals( + $expected, + Util::negotiate($acceptHeader, $available) + ); + + } + + function negotiateData() { + + return [ + [ // simple + 'application/xml', + ['application/xml'], + 'application/xml', + ], + [ // no header + null, + ['application/xml'], + 'application/xml', + ], + [ // 2 options + 'application/json', + ['application/xml', 'application/json'], + 'application/json', + ], + [ // 2 choices + 'application/json, application/xml', + ['application/xml'], + 'application/xml', + ], + [ // quality + 'application/xml;q=0.2, application/json', + ['application/xml', 'application/json'], + 'application/json', + ], + [ // wildcard + 'image/jpeg, image/png, */*', + ['application/xml', 'application/json'], + 'application/xml', + ], + [ // wildcard + quality + 'image/jpeg, image/png; q=0.5, */*', + ['application/xml', 'application/json', 'image/png'], + 'application/xml', + ], + [ // no match + 'image/jpeg', + ['application/xml'], + null, + ], + [ // This is used in sabre/dav + 'text/vcard; version=4.0', + [ + // Most often used mime-type. Version 3 + 'text/x-vcard', + // The correct standard mime-type. Defaults to version 3 as + // well. + 'text/vcard', + // vCard 4 + 'text/vcard; version=4.0', + // vCard 3 + 'text/vcard; version=3.0', + // jCard + 'application/vcard+json', + ], + 'text/vcard; version=4.0', + + ], + [ // rfc7231 example 1 + 'audio/*; q=0.2, audio/basic', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/basic', + ], + [ // Lower quality after + 'audio/pcm; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], + [ // Random parameter, should be ignored + 'audio/pcm; hello; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], + [ // No whitepace after type, should pick the one that is the most specific. + 'text/vcard;version=3.0, text/vcard', + [ + 'text/vcard', + 'text/vcard; version=3.0' + ], + 'text/vcard; version=3.0', + ], + [ // Same as last one, but order is different + 'text/vcard, text/vcard;version=3.0', + [ + 'text/vcard; version=3.0', + 'text/vcard', + ], + 'text/vcard; version=3.0', + ], + [ // Charset should be ignored here. + 'text/vcard; charset=utf-8; version=3.0, text/vcard', + [ + 'text/vcard', + 'text/vcard; version=3.0' + ], + 'text/vcard; version=3.0', + ], + [ // Undefined offset issue. + 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2', + ['application/xml', 'application/json', 'image/png'], + 'application/xml', + ], + + ]; + + } +} diff --git a/libs/composer/vendor/sabre/http/tests/bootstrap.php b/libs/composer/vendor/sabre/http/tests/bootstrap.php new file mode 100644 index 000000000000..74931b6f1180 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/bootstrap.php @@ -0,0 +1,8 @@ +<?php + +date_default_timezone_set('UTC'); + +ini_set('error_reporting', E_ALL | E_STRICT | E_DEPRECATED); + +// Composer autoloader +include __DIR__ . '/../vendor/autoload.php'; diff --git a/libs/composer/vendor/sabre/http/tests/phpcs/ruleset.xml b/libs/composer/vendor/sabre/http/tests/phpcs/ruleset.xml new file mode 100644 index 000000000000..ec2c4c84b1d8 --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/libs/composer/vendor/sabre/http/tests/phpunit.xml b/libs/composer/vendor/sabre/http/tests/phpunit.xml new file mode 100644 index 000000000000..32d701a37f7e --- /dev/null +++ b/libs/composer/vendor/sabre/http/tests/phpunit.xml @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="Sabre_HTTP"> + <directory>HTTP/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/libs/composer/vendor/sabre/uri/.gitignore b/libs/composer/vendor/sabre/uri/.gitignore new file mode 100644 index 000000000000..19d1affd4a7d --- /dev/null +++ b/libs/composer/vendor/sabre/uri/.gitignore @@ -0,0 +1,13 @@ +# Composer +vendor/ +composer.lock + +# Tests +tests/cov/ + +# Composer binaries +bin/phpunit +bin/phpcs + +# Vim +.*.swp diff --git a/libs/composer/vendor/sabre/uri/.travis.yml b/libs/composer/vendor/sabre/uri/.travis.yml new file mode 100644 index 000000000000..75c8270df219 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/.travis.yml @@ -0,0 +1,14 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - 7.1 + +script: + - ./bin/phpunit --configuration tests/phpunit.xml.dist + - ./bin/sabre-cs-fixer fix lib/ --dry-run --diff + +before_script: composer install --dev + diff --git a/libs/composer/vendor/sabre/uri/CHANGELOG.md b/libs/composer/vendor/sabre/uri/CHANGELOG.md new file mode 100644 index 000000000000..92aaa75072e2 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/CHANGELOG.md @@ -0,0 +1,57 @@ +ChangeLog +========= + +1.2.1 (2017-02-20) +------------------ + +* #16: Correctly parse urls that are only a fragment `#`. + + +1.2.0 (2016-12-06) +------------------ + +* Now throwing `InvalidUriException` if a uri passed to the `parse` function + is invalid or could not be parsed. +* #11: Fix support for URIs that start with a triple slash. PHP's `parse_uri()` + doesn't support them, so we now have a pure-php fallback in case it fails. +* #9: Fix support for relative URI's that have a non-uri encoded colon `:` in + them. + + +1.1.1 (2016-10-27) +------------------ + +* #10: Correctly support file:// URIs in the build() method. (@yuloh) + + +1.1.0 (2016-03-07) +------------------ + +* #6: PHP's `parse_url()` corrupts strings if they contain certain + non ascii-characters such as Chinese or Hebrew. sabre/uri's `parse()` + function now percent-encodes these characters beforehand. + + +1.0.1 (2015-04-28) +------------------ + +* #4: Using php-cs-fixer to automatically enforce conding standards. +* #5: Resolving to and building `mailto:` urls were not correctly handled. + + +1.0.0 (2015-01-27) +------------------ + +* Added a `normalize` function. +* Added a `buildUri` function. +* Fixed a bug in the `resolve` when only a new fragment is specified. + +San José, CalConnect XXXII release! + +0.0.1 (2014-11-17) +------------------ + +* First version! +* Source was lifted from sabre/http package. +* Provides a `resolve` and a `split` function. +* Requires PHP 5.4.8 and up. diff --git a/libs/composer/vendor/sabre/uri/LICENSE b/libs/composer/vendor/sabre/uri/LICENSE new file mode 100644 index 000000000000..087996be7c5f --- /dev/null +++ b/libs/composer/vendor/sabre/uri/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2014-2017 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/uri/README.md b/libs/composer/vendor/sabre/uri/README.md new file mode 100644 index 000000000000..aa21bfe06f42 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/README.md @@ -0,0 +1,47 @@ +sabre/uri +========= + +sabre/uri is a lightweight library that provides several functions for working +with URIs, staying true to the rules of [RFC3986][2]. + +Partially inspired by [Node.js URL library][3], and created to solve real +problems in PHP applications. 100% unitested and many tests are based on +examples from RFC3986. + +The library provides the following functions: + +1. `resolve` to resolve relative urls. +2. `normalize` to aid in comparing urls. +3. `parse`, which works like PHP's [parse_url][6]. +4. `build` to do the exact opposite of `parse`. +5. `split` to easily get the 'dirname' and 'basename' of a URL without all the + problems those two functions have. + + +Further reading +--------------- + +* [Installation][7] +* [Usage][8] + + +Questions? +---------- + +Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket +on [GitHub][5]. + + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://sabre.io/uri/ +[2]: https://tools.ietf.org/html/rfc3986/ +[3]: http://nodejs.org/api/url.html +[4]: http://groups.google.com/group/sabredav-discuss +[5]: https://github.com/fruux/sabre-uri/issues/ +[6]: http://php.net/manual/en/function.parse-url.php +[7]: http://sabre.io/uri/install/ +[8]: http://sabre.io/uri/usage/ diff --git a/libs/composer/vendor/sabre/uri/composer.json b/libs/composer/vendor/sabre/uri/composer.json new file mode 100644 index 000000000000..49d69e723421 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/composer.json @@ -0,0 +1,41 @@ +{ + "name": "sabre/uri", + "description": "Functions for making sense out of URIs.", + "keywords": [ + "URI", + "URL", + "rfc3986" + ], + "homepage": "http://sabre.io/uri/", + "license": "BSD-3-Clause", + "require": { + "php": ">=5.4.7" + }, + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "source": "https://github.com/fruux/sabre-uri" + }, + "autoload": { + "files" : [ + "lib/functions.php" + ], + "psr-4" : { + "Sabre\\Uri\\" : "lib/" + } + }, + "require-dev": { + "sabre/cs": "~1.0.0", + "phpunit/phpunit" : ">=4.0,<6.0" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/libs/composer/vendor/sabre/uri/lib/InvalidUriException.php b/libs/composer/vendor/sabre/uri/lib/InvalidUriException.php new file mode 100644 index 000000000000..0385fd4628de --- /dev/null +++ b/libs/composer/vendor/sabre/uri/lib/InvalidUriException.php @@ -0,0 +1,17 @@ +<?php + +namespace Sabre\Uri; + +/** + * Invalid Uri + * + * This is thrown when an attempt was made to use Sabre\Uri parse a uri that + * it could not. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (https://evertpot.com/) + * @license http://sabre.io/license/ + */ +class InvalidUriException extends \Exception { + +} diff --git a/libs/composer/vendor/sabre/uri/lib/Version.php b/libs/composer/vendor/sabre/uri/lib/Version.php new file mode 100644 index 000000000000..fa544538b779 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\Uri; + +/** + * This class contains the version number for this package. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ + */ +class Version { + + /** + * Full version number + */ + const VERSION = '1.2.1'; + +} diff --git a/libs/composer/vendor/sabre/uri/lib/functions.php b/libs/composer/vendor/sabre/uri/lib/functions.php new file mode 100644 index 000000000000..39b4a6f0844f --- /dev/null +++ b/libs/composer/vendor/sabre/uri/lib/functions.php @@ -0,0 +1,373 @@ +<?php + +namespace Sabre\Uri; + +/** + * This file contains all the uri handling functions. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ + */ + +/** + * Resolves relative urls, like a browser would. + * + * This function takes a basePath, which itself _may_ also be relative, and + * then applies the relative path on top of it. + * + * @param string $basePath + * @param string $newPath + * @return string + */ +function resolve($basePath, $newPath) { + + $base = parse($basePath); + $delta = parse($newPath); + + $pick = function($part) use ($base, $delta) { + + if ($delta[$part]) { + return $delta[$part]; + } elseif ($base[$part]) { + return $base[$part]; + } + return null; + + }; + + // If the new path defines a scheme, it's absolute and we can just return + // that. + if ($delta['scheme']) { + return build($delta); + } + + $newParts = []; + + $newParts['scheme'] = $pick('scheme'); + $newParts['host'] = $pick('host'); + $newParts['port'] = $pick('port'); + + $path = ''; + if ($delta['path']) { + // If the path starts with a slash + if ($delta['path'][0] === '/') { + $path = $delta['path']; + } else { + // Removing last component from base path. + $path = $base['path']; + if (strpos($path, '/') !== false) { + $path = substr($path, 0, strrpos($path, '/')); + } + $path .= '/' . $delta['path']; + } + } else { + $path = $base['path'] ?: '/'; + } + // Removing .. and . + $pathParts = explode('/', $path); + $newPathParts = []; + foreach ($pathParts as $pathPart) { + + switch ($pathPart) { + //case '' : + case '.' : + break; + case '..' : + array_pop($newPathParts); + break; + default : + $newPathParts[] = $pathPart; + break; + } + } + + $path = implode('/', $newPathParts); + + // If the source url ended with a /, we want to preserve that. + $newParts['path'] = $path; + if ($delta['query']) { + $newParts['query'] = $delta['query']; + } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) { + // Keep the old query if host and path didn't change + $newParts['query'] = $base['query']; + } + if ($delta['fragment']) { + $newParts['fragment'] = $delta['fragment']; + } + return build($newParts); + +} + +/** + * Takes a URI or partial URI as its argument, and normalizes it. + * + * After normalizing a URI, you can safely compare it to other URIs. + * This function will for instance convert a %7E into a tilde, according to + * rfc3986. + * + * It will also change a %3a into a %3A. + * + * @param string $uri + * @return string + */ +function normalize($uri) { + + $parts = parse($uri); + + if (!empty($parts['path'])) { + $pathParts = explode('/', ltrim($parts['path'], '/')); + $newPathParts = []; + foreach ($pathParts as $pathPart) { + switch ($pathPart) { + case '.': + // skip + break; + case '..' : + // One level up in the hierarchy + array_pop($newPathParts); + break; + default : + // Ensuring that everything is correctly percent-encoded. + $newPathParts[] = rawurlencode(rawurldecode($pathPart)); + break; + } + } + $parts['path'] = '/' . implode('/', $newPathParts); + } + + if ($parts['scheme']) { + $parts['scheme'] = strtolower($parts['scheme']); + $defaultPorts = [ + 'http' => '80', + 'https' => '443', + ]; + + if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) { + // Removing default ports. + unset($parts['port']); + } + // A few HTTP specific rules. + switch ($parts['scheme']) { + case 'http' : + case 'https' : + if (empty($parts['path'])) { + // An empty path is equivalent to / in http. + $parts['path'] = '/'; + } + break; + } + } + + if ($parts['host']) $parts['host'] = strtolower($parts['host']); + + return build($parts); + +} + +/** + * Parses a URI and returns its individual components. + * + * This method largely behaves the same as PHP's parse_url, except that it will + * return an array with all the array keys, including the ones that are not + * set by parse_url, which makes it a bit easier to work with. + * + * Unlike PHP's parse_url, it will also convert any non-ascii characters to + * percent-encoded strings. PHP's parse_url corrupts these characters on OS X. + * + * @param string $uri + * @return array + */ +function parse($uri) { + + // Normally a URI must be ASCII, however. However, often it's not and + // parse_url might corrupt these strings. + // + // For that reason we take any non-ascii characters from the uri and + // uriencode them first. + $uri = preg_replace_callback( + '/[^[:ascii:]]/u', + function($matches) { + return rawurlencode($matches[0]); + }, + $uri + ); + + $result = parse_url($uri); + if (!$result) { + $result = _parse_fallback($uri); + } + + return + $result + [ + 'scheme' => null, + 'host' => null, + 'path' => null, + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ]; + +} + +/** + * This function takes the components returned from PHP's parse_url, and uses + * it to generate a new uri. + * + * @param array $parts + * @return string + */ +function build(array $parts) { + + $uri = ''; + + $authority = ''; + if (!empty($parts['host'])) { + $authority = $parts['host']; + if (!empty($parts['user'])) { + $authority = $parts['user'] . '@' . $authority; + } + if (!empty($parts['port'])) { + $authority = $authority . ':' . $parts['port']; + } + } + + if (!empty($parts['scheme'])) { + // If there's a scheme, there's also a host. + $uri = $parts['scheme'] . ':'; + + } + if ($authority || (!empty($parts['scheme']) && $parts['scheme'] === 'file')) { + // No scheme, but there is a host. + $uri .= '//' . $authority; + + } + + if (!empty($parts['path'])) { + $uri .= $parts['path']; + } + if (!empty($parts['query'])) { + $uri .= '?' . $parts['query']; + } + if (!empty($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; + } + + return $uri; + +} + +/** + * Returns the 'dirname' and 'basename' for a path. + * + * The reason there is a custom function for this purpose, is because + * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale + * is used) and we need a method that just operates on UTF-8 characters. + * + * In addition basename and dirname are platform aware, and will treat + * backslash (\) as a directory separator on windows. + * + * This method returns the 2 components as an array. + * + * If there is no dirname, it will return an empty string. Any / appearing at + * the end of the string is stripped off. + * + * @param string $path + * @return array + */ +function split($path) { + + $matches = []; + if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) { + return [$matches[1], $matches[2]]; + } + return [null,null]; + +} + +/** + * This function is another implementation of parse_url, except this one is + * fully written in PHP. + * + * The reason is that the PHP bug team is not willing to admit that there are + * bugs in the parse_url implementation. + * + * This function is only called if the main parse method fails. It's pretty + * crude and probably slow, so the original parse_url is usually preferred. + * + * @param string $uri + * @return array + */ +function _parse_fallback($uri) { + + // Normally a URI must be ASCII, however. However, often it's not and + // parse_url might corrupt these strings. + // + // For that reason we take any non-ascii characters from the uri and + // uriencode them first. + $uri = preg_replace_callback( + '/[^[:ascii:]]/u', + function($matches) { + return rawurlencode($matches[0]); + }, + $uri + ); + + $result = [ + 'scheme' => null, + 'host' => null, + 'port' => null, + 'user' => null, + 'path' => null, + 'fragment' => null, + 'query' => null, + ]; + + if (preg_match('% ^([A-Za-z][A-Za-z0-9+-\.]+): %x', $uri, $matches)) { + + $result['scheme'] = $matches[1]; + // Take what's left. + $uri = substr($uri, strlen($result['scheme']) + 1); + + } + + // Taking off a fragment part + if (strpos($uri, '#') !== false) { + list($uri, $result['fragment']) = explode('#', $uri, 2); + } + // Taking off the query part + if (strpos($uri, '?') !== false) { + list($uri, $result['query']) = explode('?', $uri, 2); + } + + if (substr($uri, 0, 3) === '///') { + // The triple slash uris are a bit unusual, but we have special handling + // for them. + $result['path'] = substr($uri, 2); + $result['host'] = ''; + } elseif (substr($uri, 0, 2) === '//') { + // Uris that have an authority part. + $regex = ' + %^ + // + (?: (?<user> [^:@]+) (: (?<pass> [^@]+)) @)? + (?<host> ( [^:/]* | \[ [^\]]+ \] )) + (?: : (?<port> [0-9]+))? + (?<path> / .*)? + $%x + '; + if (!preg_match($regex, $uri, $matches)) { + throw new InvalidUriException('Invalid, or could not parse URI'); + } + if ($matches['host']) $result['host'] = $matches['host']; + if ($matches['port']) $result['port'] = (int)$matches['port']; + if (isset($matches['path'])) $result['path'] = $matches['path']; + if ($matches['user']) $result['user'] = $matches['user']; + if ($matches['pass']) $result['pass'] = $matches['pass']; + } else { + $result['path'] = $uri; + } + + return $result; +} diff --git a/libs/composer/vendor/sabre/uri/tests/BuildTest.php b/libs/composer/vendor/sabre/uri/tests/BuildTest.php new file mode 100644 index 000000000000..ae4b4ba27196 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/BuildTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\Uri; + +class BuildTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider buildUriData + */ + function testBuild($value) { + + $this->assertEquals( + $value, + build(parse_url($value)) + ); + + } + + function buildUriData() { + + return [ + ['http://example.org/'], + ['http://example.org/foo/bar'], + ['//example.org/foo/bar'], + ['/foo/bar'], + ['http://example.org:81/'], + ['http://user@example.org:81/'], + ['http://example.org:81/hi?a=b'], + ['http://example.org:81/hi?a=b#c=d'], + // [ '//example.org:81/hi?a=b#c=d'], // Currently fails due to a + // PHP bug. + ['/hi?a=b#c=d'], + ['?a=b#c=d'], + ['#c=d'], + ['file:///etc/hosts'], + ['file://localhost/etc/hosts'], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/uri/tests/NormalizeTest.php b/libs/composer/vendor/sabre/uri/tests/NormalizeTest.php new file mode 100644 index 000000000000..4dbe943205e8 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/NormalizeTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Sabre\Uri; + +class NormalizeTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider normalizeData + */ + function testNormalize($in, $out) { + + $this->assertEquals( + $out, + normalize($in) + ); + + } + + function normalizeData() { + + return [ + ['http://example.org/', 'http://example.org/'], + ['HTTP://www.EXAMPLE.com/', 'http://www.example.com/'], + ['http://example.org/%7Eevert', 'http://example.org/~evert'], + ['http://example.org/./evert', 'http://example.org/evert'], + ['http://example.org/../evert', 'http://example.org/evert'], + ['http://example.org/foo/../evert', 'http://example.org/evert'], + ['/%41', '/A'], + ['/%3F', '/%3F'], + ['/%3f', '/%3F'], + ['http://example.org', 'http://example.org/'], + ['http://example.org:/', 'http://example.org/'], + ['http://example.org:80/', 'http://example.org/'], + // See issue #6. parse_url corrupts strings like this, but only on + // macs. + //[ 'http://example.org/有词法别名.zh','http://example.org/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh'], + + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/uri/tests/ParseTest.php b/libs/composer/vendor/sabre/uri/tests/ParseTest.php new file mode 100644 index 000000000000..d797c2c08ea7 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/ParseTest.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\Uri; + +class ParseTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider parseData + */ + function testParse($in, $out) { + + $this->assertEquals( + $out, + parse($in) + ); + + } + + /** + * @dataProvider parseData + */ + function testParseFallback($in, $out) { + + $result = _parse_fallback($in); + $result = $result + [ + 'scheme' => null, + 'host' => null, + 'path' => null, + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ]; + + $this->assertEquals( + $out, + $result + ); + + } + + function parseData() { + + return [ + [ + 'http://example.org/hello?foo=bar#test', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/hello', + 'port' => null, + 'user' => null, + 'query' => 'foo=bar', + 'fragment' => 'test' + ] + ], + // See issue #6. parse_url corrupts strings like this, but only on + // macs. + [ + 'http://example.org/有词法别名.zh', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null + ] + ], + [ + 'ftp://user:password@ftp.example.org/', + [ + 'scheme' => 'ftp', + 'host' => 'ftp.example.org', + 'path' => '/', + 'port' => null, + 'user' => 'user', + 'pass' => 'password', + 'query' => null, + 'fragment' => null, + ] + ], + // See issue #9, parse_url doesn't like colons followed by numbers even + // though they are allowed since RFC 3986 + [ + 'http://example.org/hello:12?foo=bar#test', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/hello:12', + 'port' => null, + 'user' => null, + 'query' => 'foo=bar', + 'fragment' => 'test' + ] + ], + [ + '/path/to/colon:34', + [ + 'scheme' => null, + 'host' => null, + 'path' => '/path/to/colon:34', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ] + ], + // File scheme + [ + 'file:///foo/bar', + [ + 'scheme' => 'file', + 'host' => '', + 'path' => '/foo/bar', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ] + ], + // Weird scheme with triple-slash. See Issue #11. + [ + 'vfs:///somefile', + [ + 'scheme' => 'vfs', + 'host' => '', + 'path' => '/somefile', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ] + ], + // Examples from RFC3986 + [ + 'ldap://[2001:db8::7]/c=GB?objectClass?one', + [ + 'scheme' => 'ldap', + 'host' => '[2001:db8::7]', + 'path' => '/c=GB', + 'port' => null, + 'user' => null, + 'query' => 'objectClass?one', + 'fragment' => null, + ] + ], + [ + 'news:comp.infosystems.www.servers.unix', + [ + 'scheme' => 'news', + 'host' => null, + 'path' => 'comp.infosystems.www.servers.unix', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ] + ], + // Port + [ + 'http://example.org:8080/', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/', + 'port' => 8080, + 'user' => null, + 'query' => null, + 'fragment' => null, + ] + ], + // Parial url + [ + '#foo', + [ + 'scheme' => null, + 'host' => null, + 'path' => null, + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => 'foo', + ] + + ] + + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/uri/tests/ResolveTest.php b/libs/composer/vendor/sabre/uri/tests/ResolveTest.php new file mode 100644 index 000000000000..f0d123512321 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/ResolveTest.php @@ -0,0 +1,99 @@ +<?php + +namespace Sabre\Uri; + +class ResolveTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider resolveData + */ + function testResolve($base, $update, $expected) { + + $this->assertEquals( + $expected, + resolve($base, $update) + ); + + } + + function resolveData() { + + return [ + [ + 'http://example.org/foo/baz', + '/bar', + 'http://example.org/bar', + ], + [ + 'https://example.org/foo', + '//example.net/', + 'https://example.net/', + ], + [ + 'https://example.org/foo', + '?a=b', + 'https://example.org/foo?a=b', + ], + [ + '//example.org/foo', + '?a=b', + '//example.org/foo?a=b', + ], + // Ports and fragments + [ + 'https://example.org:81/foo#hey', + '?a=b#c=d', + 'https://example.org:81/foo?a=b#c=d', + ], + // Relative.. in-directory paths + [ + 'http://example.org/foo/bar', + 'bar2', + 'http://example.org/foo/bar2', + ], + // Now the base path ended with a slash + [ + 'http://example.org/foo/bar/', + 'bar2/bar3', + 'http://example.org/foo/bar/bar2/bar3', + ], + // .. and . + [ + 'http://example.org/foo/bar/', + '../bar2/.././/bar3/', + 'http://example.org/foo//bar3/', + ], + // Only updating the fragment + [ + 'https://example.org/foo?a=b', + '#comments', + 'https://example.org/foo?a=b#comments', + ], + // Switching to mailto! + [ + 'https://example.org/foo?a=b', + 'mailto:foo@example.org', + 'mailto:foo@example.org', + ], + // Resolving empty path + [ + 'http://www.example.org', + '#foo', + 'http://www.example.org/#foo', + ], + // Another fragment test + [ + 'http://example.org/path.json', + '#', + 'http://example.org/path.json', + ], + [ + 'http://www.example.com', + '#', + 'http://www.example.com/', + ] + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/uri/tests/SplitTest.php b/libs/composer/vendor/sabre/uri/tests/SplitTest.php new file mode 100644 index 000000000000..2d73c9b255da --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/SplitTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\Uri; + +class SplitTest extends \PHPUnit_Framework_TestCase{ + + function testSplit() { + + $strings = [ + + // input // expected result + '/foo/bar' => ['/foo','bar'], + '/foo/bar/' => ['/foo','bar'], + 'foo/bar/' => ['foo','bar'], + 'foo/bar' => ['foo','bar'], + 'foo/bar/baz' => ['foo/bar','baz'], + 'foo/bar/baz/' => ['foo/bar','baz'], + 'foo' => ['','foo'], + 'foo/' => ['','foo'], + '/foo/' => ['','foo'], + '/foo' => ['','foo'], + '' => [null,null], + + // UTF-8 + "/\xC3\xA0fo\xC3\xB3/bar" => ["/\xC3\xA0fo\xC3\xB3",'bar'], + "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"], + "foo/\xC3\xA0\xC3\xBCr" => ["foo","\xC3\xA0\xC3\xBCr"], + + ]; + + foreach ($strings as $input => $expected) { + + $output = split($input); + $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect'); + + + } + + } + +} diff --git a/libs/composer/vendor/sabre/uri/tests/phpcs/ruleset.xml b/libs/composer/vendor/sabre/uri/tests/phpcs/ruleset.xml new file mode 100644 index 000000000000..ec2c4c84b1d8 --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/libs/composer/vendor/sabre/uri/tests/phpunit.xml.dist b/libs/composer/vendor/sabre/uri/tests/phpunit.xml.dist new file mode 100644 index 000000000000..338d24d3c06c --- /dev/null +++ b/libs/composer/vendor/sabre/uri/tests/phpunit.xml.dist @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="../vendor/autoload.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="sabre-uri"> + <directory>.</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/libs/composer/vendor/sabre/vobject/.gitignore b/libs/composer/vendor/sabre/vobject/.gitignore new file mode 100644 index 000000000000..95935f7988e8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/.gitignore @@ -0,0 +1,21 @@ +# Composer stuff +vendor/ +composer.lock +tests/cov/ +tests/temp + +#vim +.*.swp + +#binaries +bin/phpunit +bin/phpcs +bin/php-cs-fixer +bin/sabre-cs-fixer +bin/hoa + +# Development stuff +testdata/ + +# OS X +.DS_Store diff --git a/libs/composer/vendor/sabre/vobject/.travis.yml b/libs/composer/vendor/sabre/vobject/.travis.yml new file mode 100644 index 000000000000..531ad5be4d66 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/.travis.yml @@ -0,0 +1,20 @@ +language: php +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +sudo: false + +script: + - ./bin/phpunit --configuration tests/phpunit.xml + - ./bin/sabre-cs-fixer fix . --dry-run --diff + +before_script: + - composer install + +cache: + directories: + - $HOME/.composer/cache diff --git a/libs/composer/vendor/sabre/vobject/CHANGELOG.md b/libs/composer/vendor/sabre/vobject/CHANGELOG.md new file mode 100644 index 000000000000..6cfec988e50a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/CHANGELOG.md @@ -0,0 +1,814 @@ +ChangeLog +========= + +4.1.6 (2018-04-20) +------------------ + +* #406, #407, #408, #409: Another round of performance improvements in serialization of properties (@gharlan, @staabm) +* #410: Fixes in iTip for handling `BYDAY=SA,SO` (@gharlan) +* #381: Fixes in iTip handling of `SCHEDULE-FORCE-SEND` (@alecpl) + +4.1.5 (2018-03-08) +------------------ + +* #404: Serialization: Performance boost for long properties (@gharlan) + +4.1.4 (2017-12-22) +------------------ + +* #383: Fix possible infinite loop in RRuleIterator, when the RRule FREQ + is YEARLY and it uses BYYEARDAY only (@mvdnes). +* #392: Improved significant change detection. This should reduce the number of + unneeded update emails in scheduling systems. (@alecpl). +* #395: Removed `Canada/East-Saskatchewan` timezone, as it got removed + from PHP as well. (@remicollet). + + +4.1.3 (2017-10-18) +------------------ + +* #363: Repair script and de-duplicate properties that are only allowed once, + but appear more than once. (@ddolcimascolo). +* #377: Addes Pacific Time (US & Canada) as exchange timezone +* #384: Added fallback for VCards without `FN` + + +4.1.2 (2016-12-15) +------------------ + +* #340: Support for `BYYEARDAY` recurrence when `FREQ=YEARLY`. (@PHPGangsta) +* #341: Support for `BYWEEKNO` recurrence when `FREQ=YEARLY`. (@PHPGangsta) +* Updated to the latest windows timezone data mappings. +* #344: Auto-detecting more Outlook 365-generated timezone identifiers. + (@jpirkey) +* #348: `FreeBusyGenerator` can now accept streams. +* Support sabre/xml 1.5 and 2.0. +* #355: Support `DateTimeInterface` in more places where only `DateTime` was + supported. (@gharlan). +* #351: Fixing an inclusive/exclusive problem with `isInTimeRange` and + `fastForward` with all-day events. (@strokyl, thanks you are brilliant). + + +4.1.1 (2016-07-15) +------------------ + +* #327: Throwing `InvalidDataException` in more cases where invalid iCalendar + dates and times were provided. (@rsto) +* #331: Fix dealing with multiple overridden instances falling on the same + date/time (@afedyk-sugarcrm). +* #333: Fix endless loop on invalid `BYMONTH` values in recurrence. + (@PHPGangsta) +* #339: Fixed a few `validate()` results when repair is off. (@PHPGangsta) +* #338: Stripping invalid `BYMONTH=` rules during `validate()` (@PHPGangsta) +* #336: Fix incorrect `BYSECOND=` validation. (@PHPGangsta) + + +4.1.0 (2016-04-06) +------------------ + +* #309: When expanding recurring events, the first event should also have a + `RECURRENCE-ID` property. +* #306: iTip REPLYs to the first instance of a recurring event was not handled + correctly. +* Slightly better error message during validation of `N` and `ADR` properties. +* #312: Correctly extracing timezone in the iTip broker, even when we don't + have a master event. (@vkomrakov-sugar). +* When validating a component's property that must appear once and which could + automatically be repaired, make sure we report the change as 'repaired'. +* Added a PHPUnitAssertions trait. This trait makes it easy to compare two + vcards or iCalendar objects semantically. +* Better error message when parsing objects with an invalid `VALUE` parameter. + + +4.0.3 (2016-03-12) +------------------ + +* #300: Added `VCard::getByType()` to quickly get a property with a specific + `TYPE` parameter. (@kbond) +* #302: `UNTIL` was not encoded correctly when converting to jCal. + (@GrahamLinagora) +* #303: `COUNT` is now encoded as an int in jCal instead of a string. (@strokyl) +* #295: `RRULE` now has more validation and repair rules. + + +4.0.2 (2016-01-11) +------------------ + +* #288: Only decode `CHARSET` if we're reading vCard 2.1. If it appears + in any other document, we must ignore it. + + +4.0.1 (2016-01-04) +------------------ + +* #284: When generating `CANCEL` iTip messages, we now include `DTEND`. + (@kewisch) + + +4.0.0 (2015-12-11) +------------------ + +* #274: When creating new vCards, the default vCard version is now 4.0. +* #275: `VEVENT`, `VTODO` and `VCARD` now automatically get a `UID` and + `DTSTAMP` property if this was not already specified. +* `ParseException` now extends `\Exception`. +* `Sabre\VObject\Reader::read` now has a `$charset` argument. +* #272: `Sabre\VObject\Recur\EventIterator::$maxInstances` is now + `Sabre\VObject\Settings::$maxRecurrences` and is also honored by the + FreeBusyGenerator. +* #278: `expand()` did not work correctly on events with sub-components. + + +4.0.0-beta1 (2015-12-02) +------------------------ + +* #258: Support for expanding events that use `RDATE`. (@jabdoa2) +* #258: Correctly support TZID for events that use `RDATE`. (@jabdoa2) +* #240: `Component\VCalendar::expand()` now returns a new expanded `VCalendar` + object, instead of editing the existing `VCalendar` in-place. This is a BC + break. +* #265: Using the new `InvalidDataException` in place of + `InvalidArgumentException` and `LogicException` in all places where we fail + because there was something wrong with input data. +* #227: Always add `VALUE=URI` to `PHOTO` properties. +* #235: Always add `VALUE=URI` to `URL` properties. +* It's now possible to override which class is used instead of + `Component\VCalendar` or `Component\VCard` during parsing. +* #263: Lots of small cleanups. (@jakobsack) +* #220: Automatically stop recurring after 3500 recurrences. +* #41: Allow user to set different encoding than UTF-8 when decoding vCards. +* #41: Support the `ENCODING` parameter from vCard 2.1. + Both ISO-8859-1 and Windows-1252 are currently supported. +* #185: Fix encoding/decoding of `TIME` values in jCal/jCard. + + +4.0.0-alpha2 (2015-09-04) +------------------------- + +* Updated windows timezone file to support new mexican timezone. +* #239: Added a `BirthdayCalendarGenerator`. (@DominikTo) +* #250: `isInTimeRange()` now considers the timezone for floating dates and + times. (@armin-hackmann) +* Added a duplicate vcard merging tool for the command line. +* #253: `isInTimeRange()` now correctly handles events that throw the + `NoInstancesException` exception. (@migrax, @DominikTo) +* #254: The parser threw an `E_NOTICE` for certain invalid objects. It now + correctly throws a `ParseException`. + + +4.0.0-alpha1 (2015-07-17) +------------------------- + +* sabre/vobject now requires PHP 5.5. +* #244: PHP7 support. +* Lots of speedups and reduced memory usage! +* #160: Support for xCal a.k.a. RFC6321! (@Hywan) +* #192: Support for xCard a.k.a. RFC6351! (@Hywan) +* #139: We now accept `DateTimeInterface` wherever it accepted `DateTime` + before in arguments. This means that either `DateTime` or + `DateTimeImmutable` may be used everywhere. +* #242: Full support for the `VAVAILABILITY` component, and calculating + `VFREEBUSY` based on `VAVAILABILITY` data. +* #186: Fixing conversion of `UTC-OFFSET` properties when going back and + forward between jCal and iCalendar. +* Properties, Components and Parameters now implement PHP's `JsonSerializable` + interface. +* #139: We now _always_ return `DateTimeImmutable` from any method. This could + potentially have big implications if you manipulate Date objects anywhere. +* #161: Simplified `ElementList` by extending `ArrayIterator`. +* Removed `RecurrenceIterator` (use Recur\EventIterator instead). +* Now using php-cs-fixer to automatically enforce and correct CS. +* #233: The `+00:00` timezone is now recognized as UTC. (@c960657) +* #237: Added a `destroy()` method to all documents. This method breaks any + circular references, allowing PHP to free up memory. +* #197: Made accessing properties and objects by their name a lot faster. This + especially helps objects that have a lot of sub-components or properties, + such as large iCalendar objects. +* #197: The `$children` property on components has been changed from `public` + to `protected`. Use the `children()` method instead to get a flat list of + objects. +* #244: The `Float` and `Integer` classes have been renamed to `FloatValue` + and `IntegerValue` to allow PHP 7 compatibility. + + +3.5.3 (2016-10-06) +------------------ + +* #331: Fix dealing with multiple overridden instances falling on the same + date/time (@afedyk-sugarcrm). + + +3.5.2 (2016-04-24) +----------------- + +* #312: Backported a fix related to iTip processing of events with timezones, + without a master event. + + +3.5.1 (2016-04-06) +------------------ + +* #309: When expanding recurring events, the first event should also have a + `RECURRENCE-ID` property. +* #306: iTip REPLYs to the first instance of a recurring event was not handled + correctly. + + +3.5.0 (2016-01-11) +------------------ + +* This release supports PHP 7, contrary to 3.4.x versions. +* BC Break: `Sabre\VObject\Property\Float` has been renamed to + `Sabre\VObject\Property\FloatValue`. +* BC Break: `Sabre\VObject\Property\Integer` has been renamed to + `Sabre\VObject\Property\IntegerValue`. + + +3.4.9 (2016-01-11) +------------------ + +* This package now specifies in composer.json that it does not support PHP 7. + For PHP 7, use version 3.5.x or 4.x. + + +3.4.8 (2016-01-04) +------------------ + +* #284: When generating `CANCEL` iTip messages, we now include `DTEND`. + (@kewisch). + + +3.4.7 (2015-09-05) +------------------ + +* #253: Handle `isInTimeRange` for recurring events that have 0 valid + instances. (@DominikTo, @migrax). + + +3.4.6 (2015-08-06) +------------------ + +* #250: Recurring all-day events are incorrectly included in time range + requests when not using UTC in the time range. (@armin-hackmann) + + +3.4.5 (2015-06-02) +------------------ + +* #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property + would throw an error. + + +3.4.4 (2015-05-27) +------------------ + +* #228: Fixed a 'party crasher' bug in the iTip broker. This would break + scheduling in some cases. + + +3.4.3 (2015-05-19) +------------------ + +* #219: Corrected validation of `EXDATE` properties with more than one value. +* #212: `BYSETPOS` with values below `-1` was broken and could cause infinite + loops. +* #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist) +* #216: `ENCODING` parameter is now validated for all document types. +* #217: Initializing vCard `DATE` objects with a PHP DateTime object will now + work correctly. (@thomascube) + + +3.4.2 (2015-02-25) +------------------ + +* #210: iTip: Replying to an event without a master event was broken. + + +3.4.1 (2015-02-24) +------------------ + +* A minor change to ensure that unittests work correctly in the sabre/dav + test-suite. + + +3.4.0 (2015-02-23) +------------------ + +* #196: Made parsing recurrence rules a lot faster on big calendars. +* Updated windows timezone mappings to latest unicode version. +* #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan) +* #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan) +* #205: Improving handling of multiple `EXDATE` when processing iTip changes. + (@armin-hackmann) +* #187: Fixed validator rules for `LAST-MODIFIED` properties. +* #188: Retain floating times when generating instances using + `Recur\EventIterator`. +* #203: Skip tests for timezones that are not supported on older PHP versions, + instead of a hard fail. +* #204: Dealing a bit better with vCard date-time values that contained + milliseconds. (which is normally invalid). (@armin-hackmann) + + +3.3.5 (2015-01-09) +------------------ + +* #168: Expanding calendars now removes objects with recurrence rules that + don't have a valid recurrence instance. +* #177: SCHEDULE-STATUS should not contain a reason phrase, only a status + code. +* #175: Parser can now read and skip the UTF-8 BOM. +* #179: Added `isFloating` to `DATE-TIME` properties. +* #179: Fixed jCal serialization of floating `DATE-TIME` properties. +* #173: vCard converter failed for `X-ABDATE` properties that had no + `X-ABLABEL`. +* #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules + specific for CalDAV/CardDAV servers. +* #176: A missing `UID` is no longer an error, but a warning for the vCard + validator, unless `PROFILE_CARDDAV` is specified. + + +3.3.4 (2014-11-19) +------------------ + +* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and + vice-versa when converting to/from vCard 4. +* #154: It's now possible to easily select all vCard properties belonging to + a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann) +* #156: Simpler way to check if a string is UTF-8. (@Hywan) +* Unittest improvements. +* #159: The recurrence iterator, freebusy generator and iCalendar DATE and + DATE-TIME properties can now all accept a reference timezone when working + floating times or all-day events. +* #159: Master events will no longer get a `RECURRENCE-ID` when expanding. +* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding. +* #163: Added a `getTimeZone()` method to `VTIMEZONE` components. + + +3.3.3 (2014-10-09) +------------------ + +* #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the + original event. +* #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected. +* #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is + set to support the inbox feature of iOS. +* #147: Bugs related to scheduling all-day events. +* #148: Ignore events that have attendees but no organizer. +* #149: Avoiding logging errors during timezone detection. This is a workaround + for a PHP bug. +* Support for "Line Islands Standard Time" windows timezone. +* #154: Correctly work around vCard parameters that have a value but no name. + + +3.3.2 (2014-09-19) +------------------ + +* Changed: iTip broker now sets RSVP status to false when replies are received. +* #118: iTip Message now has a `getScheduleStatus()` method. +* #119: Support for detecting 'significant changes'. +* #120: Support for `SCHEDULE-FORCE-SEND`. +* #121: iCal demands parameters containing the + sign to be quoted. +* #122: Don't generate REPLY messages for events that have been cancelled. +* #123: Added `SUMMARY` to iTip messages. +* #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`). +* #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`. +* #131: RRULE that doesn't provide a single valid instance now throws an + exception. +* #136: Validator rejects *all* control characters. We were missing a few. +* #133: Splitter objects will throw exceptions when receiving incompatible + objects. +* #127: Attendees who delete recurring event instances events they had already + declined earlier will no longer generate another reply. +* #125: Send CANCEL messages when ORGANIZER property gets deleted. + + +3.3.1 (2014-08-18) +------------------ + +* Changed: It's now possible to pass DateTime objects when using the magic + setters on properties. (`$event->DTSTART = new DateTime('now')`). +* #111: iTip Broker does not process attendee adding events to EXDATE. +* #112: EventIterator now sets TZID on RECURRENCE-ID. +* #113: Timezone support during creation of iTip REPLY messages. +* #114: VTIMEZONE is retained when generating new REQUEST objects. +* #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip + broker. This improves evolution support. +* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into + SCHEDULE-STATUS. + + +3.3.0 (2014-08-07) +------------------ + +* We now use PSR-4 for the directory structure. This means that everything + that was used to be in the `lib/Sabre/VObject` directory is now moved to + `lib/`. If you use composer to load this library, you shouldn't have to do + anything about that though. +* VEVENT now get populated with a DTSTAMP and UID property by default. +* BC Break: Removed the 'includes.php' file. Use composer instead. +* #103: Added support for processing [iTip][iTip] messages. This allows a user + to parse incoming iTip messages and apply the result on existing calendars, + or automatically generate invites/replies/cancellations based on changes that + a user made on objects. +* #75, #58, #18: Fixes related to overriding the first event in recurrences. +* Added: VCalendar::getBaseComponent to find the 'master' component in a + calendar. +* #51: Support for iterating RDATE properties. +* Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are + excluded events with wrong time + + +3.2.4 (2014-07-14) +------------------ + +* Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into + consideration when converting between vCard 3 and 4. +* Fixed: Issue #96. Some support for Yahoo's broken vcards. +* Fixed: PHP 5.3 support was broken in the cli tool. + + +3.2.3 (2014-06-12) +------------------ + +* Validator now checks if DUE and DTSTART are of the same type in VTODO, and + ensures that DUE is always after DTSTART. +* Removed documentation from source repository, to http://sabre.io/vobject/ +* Expanded the vobject cli tool validation output to make it easier to find + issues. +* Fixed: vobject repair. It was not working for iCalendar objects. + + +3.2.2 (2014-05-07) +------------------ + +* Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying + slightly changed which caused the test to fail. + + +3.2.1 (2014-05-03) +------------------ + +* Minor tweak to make the unittests run with the latest hhvm on travis. +* Updated timezone definitions. +* Updated copyright links to point to http://sabre.io/ + + +3.2.0 (2014-04-02) +------------------ + +* Now hhvm compatible! +* The validator can now detect a _lot_ more problems. Many rules for both + iCalendar and vCard were added. +* Added: bin/generate_vcards, a utility to generate random vcards for testing + purposes. Patches are welcome to add more data. +* Updated: Windows timezone mapping to latest version from unicode.org +* Changed: The timezone maps are now loaded in from external files, in + lib/Sabre/VObject/timezonedata. +* Added: Fixing badly encoded URL's from google contacts vcards. +* Fixed: Issue #68. Couldn't decode properties ending in a colon. +* Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL + clause. +* Fixed: Issue #67. BYMONTH limit on DAILY recurrences. +* Fixed: Issue #26. Return a more descriptive error when coming across broken + BYDAY rules. +* Fixed: Issue #28. Incorrect timezone detection for some timezones. +* Fixed: Issue #70. Casting a parameter with a null value to string would fail. +* Added: Support for rfc6715 and rfc6474. +* Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property. +* Added: UUIDUtil, for easily creating unique identifiers. +* Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime. +* Fixed: Issue #86. Don't go into an infinite loop when php errors are + disabled and an invalid file is read. + + +3.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +3.1.3 (2013-10-02) +------------------ + +* Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue + #56. +* Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than + one newline. Thanks @Vedmak for the patch. +* Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to + be inserted. +* Added: VCardConverter removed properties that are no longer supported in vCard + 4.0. +* Added: vCards with a minimum number of values (such as N), but don't have that + many, are now automatically padded with empty components. +* Added: The vCard validator now also checks for a minimum number of components, + and has the ability to repair these. +* Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard + 3.0 or 4.0. +* Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level + VCalendar and VCard components. +* Fixed: Issue 62: Parsing iCalendar parameters with no value. +* Added: --forgiving option to vobject utility. +* Fixed: Compound properties such as ADR were not correctly split up in vCard + 2.1 quoted printable-encoded properties. +* Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks + @DominikTo for the patch. + + +3.1.2 (2013-08-13) +------------------ + +* Fixed: Setting correct property group on VCard conversion + + +3.1.1 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +3.1.0 (2013-07-27) +------------------ + +* Added: bad-ass new cli debugging utility (in bin/vobject). +* Added: jCal and jCard parser. +* Fixed: URI properties should not escape ; and ,. +* Fixed: VCard 4 documents now correctly use URI as a default value-type for + PHOTO and others. BINARY no longer exists in vCard 4. +* Added: Utility to convert between 2.1, 3.0 and 4.0 vCards. +* Added: You can now add() multiple parameters to a property in one call. +* Added: Parameter::has() for easily checking if a parameter value exists. +* Added: VCard::preferred() to find a preferred email, phone number, etc for a + contact. +* Changed: All $duration properties are now public. +* Added: A few validators for iCalendar documents. +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. +* Added: getDuration for DURATION values such as TRIGGER. Thanks to + @SimonSimCity. +* Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's + more than 1. Thanks to @Vedmak. + + +3.0.0 (2013-06-21) +------------------ + +* Fixed: includes.php file was still broken. Our tool to generate it had some + bugs. + + +3.0.0-beta4 (2013-06-21) +------------------------ + +* Fixed: includes.php was no longer up to date. + + +3.0.0-beta3 (2013-06-17) +------------------------ + +* Added: OPTION_FORGIVING now also allows slashes in property names. +* Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999 +* Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with + a missing base event. +* Fixed: jCard encoding of TIME properties. +* Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values. + + +3.0.0-beta2 (2013-06-10) +------------------------ + +* Fixed: Corrected includes.php file. +* Fixed: vCard date-time parser supported extended-format dates as well. +* Changed: Properties have been moved to an ICalendar or VCard directory. +* Fixed: Couldn't parse vCard 3 extended format dates and times. +* Fixed: Couldn't export jCard DATE values correctly. +* Fixed: Recursive loop in ICalendar\DateTime property. + + +3.0.0-beta1 (2013-06-07) +------------------------ + +* Added: jsonSerialize() for creating jCal and jCard documents. +* Added: helper method to parse vCard dates and times. +* Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP, + DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties. +* Removed: CommaSeparatedText property. Now included into Text. +* Fixed: Multiple parameters with the same name are now correctly encoded. +* Fixed: Parameter values containing a comma are now enclosed in double-quotes. +* Fixed: Iterating parameter values should now fully work as expected. +* Fixed: Support for vCard 2.1 nameless parameters. +* Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified + class names, so they are actually overridable. +* Fixed: Updating DATE-TIME to DATE values now behaves like expected. + + +3.0.0-alpha4 (2013-05-31) +------------------------- + +* Added: It's now possible to send parser options to the splitter classes. +* Added: A few tweaks to improve component and property creation. + + +3.0.0-alpha3 (2013-05-13) +------------------------- + +* Changed: propertyMap, valueMap and componentMap are now static properties. +* Changed: Component::remove() will throw an exception when trying to a node + that's not a child of said component. +* Added: Splitter objects are now faster, line numbers are accurately reported + and use less memory. +* Added: MimeDir parser can now continue parsing with the same stream buffer. +* Fixed: vobjectvalidate.php is operational again. +* Fixed: \r is properly stripped in text values. +* Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for + vCards 2.1. +* Fixed: Parser assumes vCard 2.1, if no version was supplied. + + +3.0.0-alpha2 (2013-05-22) +------------------------- + +* Fixed: vCard URL properties were referencing a non-existant class. + + +3.0.0-alpha1 (2013-05-21) +------------------------- + +* Fixed: Now correctly dealing with escaping of properties. This solves the + problem with double-backslashes where they don't belong. +* Added: Easy support for properties with more than one value, using setParts + and getParts. +* Added: Support for broken 2.1 vCards produced by microsoft. +* Added: Automatically decoding quoted-printable values. +* Added: Automatically decoding base64 values. +* Added: Decoding RFC6868 parameter values (uses ^ as an escape character). +* Added: Fancy new MimeDir parser that can also parse streams. +* Added: Automatically mapping many, many properties to a property-class with + specialized API's. +* Added: remove() method for easily removing properties and sub-components + components. +* Changed: Components, Properties and Parameters can no longer be created with + Component::create, Property::create and Parameter::create. They must instead + be created through the root component. (A VCalendar or VCard object). +* Changed: API for DateTime properties has slightly changed. +* Changed: the ->value property is now protected everywhere. Use getParts() and + getValue() instead. +* BC Break: No support for mac newlines (\r). Never came across these anyway. +* Added: add() method to the Property class. +* Added: It's now possible to easy set multi-value properties as arrays. +* Added: When setting date-time properties you can just pass PHP's DateTime + object. +* Added: New components automatically get a bunch of default properties, such as + VERSION and CALSCALE. +* Added: You can add new sub-components much quicker with the magic setters, and + add() method. + + +2.1.7 (2015-01-21) +------------------ + +* Fixed: Issue #94, a workaround for bad escaping of ; and , in compound + properties. It's not a full solution, but it's an improvement for those + stuck in the 2.1 versions. + + +2.1.6 (2014-12-10) +------------------ + +* Fixed: Minor change to make sure that unittests succeed on every PHP version. + + +2.1.5 (2014-06-03) +------------------ + +* Fixed: #94: Better parameter escaping. +* Changed: Documentation cleanups. + + +2.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +2.1.3 (2013-10-02) +------------------ + +* Fixed: Issue #55. \r must be stripped from property values. +* Fixed: Issue #65. Putting quotes around parameter values that contain a colon. + + +2.1.2 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +2.1.1 (2013-07-27) +------------------ + +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. + + +2.1.0 (2013-06-17) +------------------ + +* This version is fully backwards compatible with 2.0.\*. However, it contains a + few new API's that mimic the VObject 3 API. This allows it to be used a + 'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and + 1.8 can run with both the 2 and 3 versions of this library. +* Added: Property\DateTime::hasTime(). +* Added: Property\MultiDateTime::hasTime(). +* Added: Property::getValue(). +* Added: Document class. +* Added: Document::createComponent and Document::createProperty. +* Added: Parameter::getValue(). + + +2.0.7 (2013-03-05) +------------------ + +* Fixed: Microsoft re-uses their magic numbers for different timezones, + specifically id 2 for both Sarajevo and Lisbon). A workaround was added to + deal with this. + + +2.0.6 (2013-02-17) +------------------ + +* Fixed: The reader now properly parses parameters without a value. + + +2.0.5 (2012-11-05) +------------------ + +* Fixed: The FreeBusyGenerator is now properly using the factory methods for + creation of components and properties. + + +2.0.4 (2012-11-02) +------------------ + +* Added: Known Lotus Notes / Domino timezone id's. + + +2.0.3 (2012-10-29) +------------------ + +* Added: Support for 'GMT+????' format in TZID's. +* Added: Support for formats like SystemV/EST5EDT in TZID's. +* Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART. +* Added: Support for BYHOUR in FREQ=DAILY (@hollodk). +* Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY. + + +2.0.2 (2012-10-06) +------------------ + +* Added: includes.php file, to load the entire library in one go. +* Fixed: A problem with determining alarm triggers for TODO's. + + +2.0.1 (2012-09-22) +------------------ + +* Removed: Element class. It wasn't used. +* Added: Basic validation and repair methods for broken input data. +* Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was + specified. +* Added: A cli script that can validate and automatically repair vcards and + iCalendar objects. +* Added: A new 'Compound' property, that can automatically split up parts for + properties such as N, ADR, ORG and CATEGORIES. +* Added: Splitter classes, that can split up large objects (such as exports) + into individual objects (thanks @DominikTo and @armin-hackmann). +* Added: VFREEBUSY component, which allows easily checking wether timeslots are + available. +* Added: The Reader class now has a 'FORGIVING' option, which allows it to parse + properties with incorrect characters in the name (at this time, it just allows + underscores). +* Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard + any invalid lines. +* Fixed: A bug in Windows timezone-id mappings for times created in Greenlands + timezone (sorry Greenlanders! I do care!). +* Fixed: DTEND was not generated correctly for VFREEBUSY reports. +* Fixed: Parser is at least 25% faster with real-world data. + + +2.0.0 (2012-08-08) +------------------ + +* VObject is now a separate project from SabreDAV. See the SabreDAV changelog + for version information before 2.0. +* New: VObject library now uses PHP 5.3 namespaces. +* New: It's possible to specify lists of parameters when constructing + properties. +* New: made it easier to construct the FreeBusyGenerator. + +[iTip]: http://tools.ietf.org/html/rfc5546 diff --git a/libs/composer/vendor/sabre/vobject/LICENSE b/libs/composer/vendor/sabre/vobject/LICENSE new file mode 100644 index 000000000000..a99c8da1988e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/vobject/README.md b/libs/composer/vendor/sabre/vobject/README.md new file mode 100644 index 000000000000..5030cf276588 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/README.md @@ -0,0 +1,55 @@ +sabre/vobject +============= + +The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) +and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. + +The goal of the VObject library is to create a very complete library, with an easy to use API. + + +Installation +------------ + +Make sure you have [Composer][1] installed, and then run: + + composer require sabre/vobject "^4.0" + +This package requires PHP 5.5. If you need the PHP 5.3/5.4 version of this package instead, use: + + + composer require sabre/vobject "^3.4" + + +Usage +----- + +* [Working with vCards](http://sabre.io/vobject/vcard/) +* [Working with iCalendar](http://sabre.io/vobject/icalendar/) + + + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=master)](https://travis-ci.org/sabre-io/vobject) | +| 3.5 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.5)](https://travis-ci.org/sabre-io/vobject) | +| 3.4 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.4)](https://travis-ci.org/sabre-io/vobject) | +| 3.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=3.1)](https://travis-ci.org/sabre-io/vobject) | +| 2.1 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.1)](https://travis-ci.org/sabre-io/vobject) | +| 2.0 | [![Build Status](https://travis-ci.org/sabre-io/vobject.svg?branch=2.0)](https://travis-ci.org/sabre-io/vobject) | + + + +Support +------- + +Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: https://getcomposer.org/ diff --git a/libs/composer/vendor/sabre/vobject/bin/bench.php b/libs/composer/vendor/sabre/vobject/bin/bench.php new file mode 100755 index 000000000000..807b40777c6a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/bench.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +$data = stream_get_contents(STDIN); + +$start = microtime(true); + +$lol = Sabre\VObject\Reader::read($data); + +echo "time: " . (microtime(true) - $start) . "\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/bench_freebusygenerator.php b/libs/composer/vendor/sabre/vobject/bin/bench_freebusygenerator.php new file mode 100644 index 000000000000..2c51b2a32b01 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/bench_freebusygenerator.php @@ -0,0 +1,62 @@ +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 2) { + echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " freebusy benchmark\n"; + echo "\n"; + echo "This script can be used to measure the speed of generating a\n"; + echo "free-busy report based on a calendar.\n"; + echo "\n"; + echo "The process will be repeated 100 times to get accurate stats\n"; + echo "\n"; + echo "Usage: " . $argv[0] . " inputfile.ics\n"; + die(); +} + +list(, $inputFile) = $argv; + +$bench = new Hoa\Bench\Bench(); +$bench->parse->start(); + +$vcal = Sabre\VObject\Reader::read(fopen($inputFile, 'r')); + +$bench->parse->stop(); + +$repeat = 100; +$start = new \DateTime('2000-01-01'); +$end = new \DateTime('2020-01-01'); +$timeZone = new \DateTimeZone('America/Toronto'); + +$bench->fb->start(); + +for ($i = 0; $i < $repeat; $i++) { + + $fb = new Sabre\VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone); + $results = $fb->getResult(); + +} +$bench->fb->stop(); + + + +echo $bench,"\n"; + +function formatMemory($input) { + + if (strlen($input) > 6) { + + return round($input / (1024 * 1024)) . 'M'; + + } elseif (strlen($input) > 3) { + + return round($input / 1024) . 'K'; + + } + +} + +unset($input, $splitter); + +echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n"; +echo "current memory usage: " . formatMemory(memory_get_usage()), "\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/bench_manipulatevcard.php b/libs/composer/vendor/sabre/vobject/bin/bench_manipulatevcard.php new file mode 100644 index 000000000000..adc198e9bc6b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/bench_manipulatevcard.php @@ -0,0 +1,69 @@ +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 2) { + echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " manipulation benchmark\n"; + echo "\n"; + echo "This script can be used to measure the speed of opening a large amount of\n"; + echo "vcards, making a few alterations and serializing them again.\n"; + echo "system."; + echo "\n"; + echo "Usage: " . $argv[0] . " inputfile.vcf\n"; + die(); +} + +list(, $inputFile) = $argv; + +$input = file_get_contents($inputFile); + +$splitter = new Sabre\VObject\Splitter\VCard($input); + +$bench = new Hoa\Bench\Bench(); + +while (true) { + + $bench->parse->start(); + $vcard = $splitter->getNext(); + $bench->parse->pause(); + + if (!$vcard) break; + + $bench->manipulate->start(); + $vcard->{'X-FOO'} = 'Random new value!'; + $emails = []; + if (isset($vcard->EMAIL)) foreach ($vcard->EMAIL as $email) { + $emails[] = (string)$email; + } + $bench->manipulate->pause(); + + $bench->serialize->start(); + $vcard2 = $vcard->serialize(); + $bench->serialize->pause(); + + $vcard->destroy(); + +} + + + +echo $bench,"\n"; + +function formatMemory($input) { + + if (strlen($input) > 6) { + + return round($input / (1024 * 1024)) . 'M'; + + } elseif (strlen($input) > 3) { + + return round($input / 1024) . 'K'; + + } + +} + +unset($input, $splitter); + +echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n"; +echo "current memory usage: " . formatMemory(memory_get_usage()), "\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/fetch_windows_zones.php b/libs/composer/vendor/sabre/vobject/bin/fetch_windows_zones.php new file mode 100755 index 000000000000..3f2a00f7ae06 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/fetch_windows_zones.php @@ -0,0 +1,51 @@ +#!/usr/bin/env php +<?php + +$windowsZonesUrl = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml'; +$outputFile = __DIR__ . '/../lib/timezonedata/windowszones.php'; + +echo "Fetching timezone map from: " . $windowsZonesUrl, "\n"; + +$data = file_get_contents($windowsZonesUrl); + +$xml = simplexml_load_string($data); + +$map = []; + +foreach ($xml->xpath('//mapZone') as $mapZone) { + + $from = (string)$mapZone['other']; + $to = (string)$mapZone['type']; + + list($to) = explode(' ', $to, 2); + + if (!isset($map[$from])) { + $map[$from] = $to; + } + +} + +ksort($map); +echo "Writing to: $outputFile\n"; + +$f = fopen($outputFile, 'w'); +fwrite($f, "<?php\n\n"); +fwrite($f, "/**\n"); +fwrite($f, " * Automatically generated timezone file\n"); +fwrite($f, " *\n"); +fwrite($f, " * Last update: " . date(DATE_W3C) . "\n"); +fwrite($f, " * Source: " . $windowsZonesUrl . "\n"); +fwrite($f, " *\n"); +fwrite($f, " * @copyright Copyright (C) fruux GmbH (https://fruux.com/).\n"); +fwrite($f, " * @license http://sabre.io/license/ Modified BSD License\n"); +fwrite($f, " */\n"); +fwrite($f, "\n"); +fwrite($f, "return "); +fwrite($f, var_export($map, true) . ';'); +fclose($f); + +echo "Formatting\n"; + +exec(__DIR__ . '/sabre-cs-fixer fix ' . escapeshellarg($outputFile)); + +echo "Done\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/generate_vcards b/libs/composer/vendor/sabre/vobject/bin/generate_vcards new file mode 100755 index 000000000000..4663c3c16d5c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/generate_vcards @@ -0,0 +1,241 @@ +#!/usr/bin/env php +<?php + +namespace Sabre\VObject; + +// This sucks.. we have to try to find the composer autoloader. But chances +// are, we can't find it this way. So we'll do our bestest +$paths = [ + __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly + __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. +]; + +foreach($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +if (!class_exists('Sabre\\VObject\\Version')) { + fwrite(STDERR, "Composer autoloader could not be properly loaded.\n"); + die(1); +} + +if ($argc < 2) { + + $version = Version::VERSION; + + $help = <<<HI +sabre/vobject $version +Usage: + generate_vcards [count] + +Options: + count The number of random vcards to generate + +Examples: + generate_vcards 1000 > testdata.vcf + +HI; + + fwrite(STDERR, $help); + exit(2); +} + +$count = (int)$argv[1]; +if ($count < 1) { + fwrite(STDERR, "Count must be at least 1\n"); + exit(2); +} + +fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n"); +fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n"); + +/** + * The following list is just some random data we compiled from various + * sources online. + * + * Very little thought went into compiling this list, and certainly nothing + * political or ethical. + * + * We would _love_ more additions to this to add more variation to this list. + * + * Send us PR's and don't be shy adding your own first and last name for fun. + */ + +$sets = array( + "nl" => array( + "country" => "Netherlands", + "boys" => array( + "Anno", + "Bram", + "Daan", + "Evert", + "Finn", + "Jayden", + "Jens", + "Jesse", + "Levi", + "Lucas", + "Luuk", + "Milan", + "René", + "Sem", + "Sibrand", + "Willem", + ), + "girls" => array( + "Celia", + "Emma", + "Fenna", + "Geke", + "Inge", + "Julia", + "Lisa", + "Lotte", + "Mila", + "Sara", + "Sophie", + "Tess", + "Zoë", + ), + "last" => array( + "Bakker", + "Bos", + "De Boer", + "De Groot", + "De Jong", + "De Vries", + "Jansen", + "Janssen", + "Meyer", + "Mulder", + "Peters", + "Smit", + "Van Dijk", + "Van den Berg", + "Visser", + "Vos", + ), + ), + "us" => array( + "country" => "United States", + "boys" => array( + "Aiden", + "Alexander", + "Charles", + "David", + "Ethan", + "Jacob", + "James", + "Jayden", + "John", + "Joseph", + "Liam", + "Mason", + "Michael", + "Noah", + "Richard", + "Robert", + "Thomas", + "William", + ), + "girls" => array( + "Ava", + "Barbara", + "Chloe", + "Dorothy", + "Elizabeth", + "Emily", + "Emma", + "Isabella", + "Jennifer", + "Lily", + "Linda", + "Margaret", + "Maria", + "Mary", + "Mia", + "Olivia", + "Patricia", + "Roxy", + "Sophia", + "Susan", + "Zoe", + ), + "last" => array( + "Smith", + "Johnson", + "Williams", + "Jones", + "Brown", + "Davis", + "Miller", + "Wilson", + "Moore", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + "Harris", + "Martin", + "Thompson", + "Garcia", + "Martinez", + "Robinson", + ), + ), +); + +$current = 0; + +$r = function($arr) { + + return $arr[mt_rand(0,count($arr)-1)]; + +}; + +$bdayStart = strtotime('-85 years'); +$bdayEnd = strtotime('-20 years'); + +while($current < $count) { + + $current++; + fwrite(STDERR, "\033[100D$current/$count"); + + $country = array_rand($sets); + $gender = mt_rand(0,1)?'girls':'boys'; + + $vcard = new Component\VCard(array( + 'VERSION' => '4.0', + 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']), + 'UID' => UUIDUtil::getUUID(), + )); + + $bdayRatio = mt_rand(0,9); + + if($bdayRatio < 2) { + // 20% has a birthday property with a full date + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', $dt->format('Ymd')); + + } elseif ($bdayRatio < 3) { + // 10% we only know the month and date of + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', '--' . $dt->format('md')); + } + if ($result = $vcard->validate()) { + ob_start(); + echo "\nWe produced an invalid vcard somehow!\n"; + foreach($result as $message) { + echo " " . $message['message'] . "\n"; + } + fwrite(STDERR, ob_get_clean()); + } + echo $vcard->serialize(); + +} + +fwrite(STDERR,"\nDone.\n"); diff --git a/libs/composer/vendor/sabre/vobject/bin/generateicalendardata.php b/libs/composer/vendor/sabre/vobject/bin/generateicalendardata.php new file mode 100755 index 000000000000..a2df3c63a0c8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/generateicalendardata.php @@ -0,0 +1,88 @@ +#!/usr/bin/env php +<?php + +use Sabre\VObject; + +if ($argc < 2) { + $cmd = $argv[0]; + fwrite(STDERR, <<<HI +Fruux test data generator + +This script generates a lot of test data. This is used for profiling and stuff. +Currently it just generates events in a single calendar. + +The iCalendar output goes to stdout. Other messages to stderr. + +{$cmd} [events] + + +HI + ); + die(); +} + +$events = 100; + +if (isset($argv[1])) $events = (int)$argv[1]; + +include __DIR__ . '/../vendor/autoload.php'; + +fwrite(STDERR, "Generating " . $events . " events\n"); + +$currentDate = new DateTime('-' . round($events / 2) . ' days'); + +$calendar = new VObject\Component\VCalendar(); + +$ii = 0; + +while ($ii < $events) { + + $ii++; + + $event = $calendar->add('VEVENT'); + $event->DTSTART = 'bla'; + $event->SUMMARY = 'Event #' . $ii; + $event->UID = md5(microtime(true)); + + $doctorRandom = mt_rand(1, 1000); + + switch ($doctorRandom) { + // All-day event + case 1 : + $event->DTEND = 'bla'; + $dtStart = clone $currentDate; + $dtEnd = clone $currentDate; + $dtEnd->modify('+' . mt_rand(1, 3) . ' days'); + $event->DTSTART->setDateTime($dtStart); + $event->DTSTART['VALUE'] = 'DATE'; + $event->DTEND->setDateTime($dtEnd); + break; + case 2 : + $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1, 10); + // No break intentional + default : + $dtStart = clone $currentDate; + $dtStart->setTime(mt_rand(1, 23), mt_rand(0, 59), mt_rand(0, 59)); + $event->DTSTART->setDateTime($dtStart); + $event->DURATION = 'PT' . mt_rand(1, 3) . 'H'; + break; + + } + + $currentDate->modify('+ ' . mt_rand(0, 3) . ' days'); + +} +fwrite(STDERR, "Validating\n"); + +$result = $calendar->validate(); +if ($result) { + fwrite(STDERR, "Errors!\n"); + fwrite(STDERR, print_r($result, true)); + die(-1); +} + +fwrite(STDERR, "Serializing this beast\n"); + +echo $calendar->serialize(); + +fwrite(STDERR, "done.\n"); diff --git a/libs/composer/vendor/sabre/vobject/bin/mergeduplicates.php b/libs/composer/vendor/sabre/vobject/bin/mergeduplicates.php new file mode 100755 index 000000000000..076524d36a62 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/mergeduplicates.php @@ -0,0 +1,184 @@ +#!/usr/bin/env php +<?php + +namespace Sabre\VObject; + +// This sucks.. we have to try to find the composer autoloader. But chances +// are, we can't find it this way. So we'll do our bestest +$paths = [ + __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly + __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +if (!class_exists('Sabre\\VObject\\Version')) { + fwrite(STDERR, "Composer autoloader could not be loaded.\n"); + die(1); +} + +echo "sabre/vobject ", Version::VERSION, " duplicate contact merge tool\n"; + +if ($argc < 3) { + + echo "\n"; + echo "Usage: ", $argv[0], " input.vcf output.vcf [debug.log]\n"; + die(1); + +} + +$input = fopen($argv[1], 'r'); +$output = fopen($argv[2], 'w'); +$debug = isset($argv[3]) ? fopen($argv[3], 'w') : null; + +$splitter = new Splitter\VCard($input); + +// The following properties are ignored. If they appear in some vcards +// but not in others, we don't consider them for the sake of finding +// differences. +$ignoredProperties = [ + "PRODID", + "VERSION", + "REV", + "UID", + "X-ABLABEL", +]; + + +$collectedNames = []; + +$stats = [ + "Total vcards" => 0, + "No FN property" => 0, + "Ignored duplicates" => 0, + "Merged values" => 0, + "Error" => 0, + "Unique cards" => 0, + "Total written" => 0, +]; + +function writeStats() { + + global $stats; + foreach ($stats as $name => $value) { + echo str_pad($name, 23, " ", STR_PAD_RIGHT), str_pad($value, 6, " ", STR_PAD_LEFT), "\n"; + } + // Moving cursor back a few lines. + echo "\033[" . count($stats) . "A"; + +} + +function write($vcard) { + + global $stats, $output; + + $stats["Total written"]++; + fwrite($output, $vcard->serialize() . "\n"); + +} + +while ($vcard = $splitter->getNext()) { + + $stats["Total vcards"]++; + writeStats(); + + $fn = isset($vcard->FN) ? (string)$vcard->FN : null; + + if (empty($fn)) { + + // Immediately write this vcard, we don't compare it. + $stats["No FN property"]++; + $stats['Unique cards']++; + write($vcard); + $vcard->destroy(); + continue; + + } + + if (!isset($collectedNames[$fn])) { + + $collectedNames[$fn] = $vcard; + $stats['Unique cards']++; + continue; + + } else { + + // Starting comparison for all properties. We only check if properties + // in the current vcard exactly appear in the earlier vcard as well. + foreach ($vcard->children() as $newProp) { + + if (in_array($newProp->name, $ignoredProperties)) { + // We don't care about properties such as UID and REV. + continue; + } + $ok = false; + foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { + + if ($compareProp->serialize() === $newProp->serialize()) { + $ok = true; + break; + } + } + + if (!$ok) { + + if ($newProp->name === 'EMAIL' || $newProp->name === 'TEL') { + + // We're going to make another attempt to find this + // property, this time just by value. If we find it, we + // consider it a success. + foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { + + if ($compareProp->getValue() === $newProp->getValue()) { + $ok = true; + break; + } + } + + if (!$ok) { + + // Merging the new value in the old vcard. + $collectedNames[$fn]->add(clone $newProp); + $ok = true; + $stats['Merged values']++; + + } + + } + + } + + if (!$ok) { + + // echo $newProp->serialize() . " does not appear in earlier vcard!\n"; + $stats['Error']++; + if ($debug) fwrite($debug, "Missing '" . $newProp->name . "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n"); + + $vcard->destroy(); + continue 2; + } + + } + + } + + $vcard->destroy(); + $stats['Ignored duplicates']++; + +} + +foreach ($collectedNames as $vcard) { + + // Overwriting any old PRODID + $vcard->PRODID = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN'; + write($vcard); + writeStats(); + +} + +echo str_repeat("\n", count($stats)), "\nDone.\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/rrulebench.php b/libs/composer/vendor/sabre/vobject/bin/rrulebench.php new file mode 100644 index 000000000000..af26b4765ca9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/rrulebench.php @@ -0,0 +1,32 @@ +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 4) { + echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " RRULE benchmark\n"; + echo "\n"; + echo "This script can be used to measure the speed of the 'recurrence expansion'\n"; + echo "system."; + echo "\n"; + echo "Usage: " . $argv[0] . " inputfile.ics startdate enddate\n"; + die(); +} + +list(, $inputFile, $startDate, $endDate) = $argv; + +$bench = new Hoa\Bench\Bench(); +$bench->parse->start(); + +echo "Parsing.\n"; +$vobj = Sabre\VObject\Reader::read(fopen($inputFile, 'r')); + +$bench->parse->stop(); + +echo "Expanding.\n"; +$bench->expand->start(); + +$vobj->expand(new DateTime($startDate), new DateTime($endDate)); + +$bench->expand->stop(); + +echo $bench,"\n"; diff --git a/libs/composer/vendor/sabre/vobject/bin/vobject b/libs/composer/vendor/sabre/vobject/bin/vobject new file mode 100755 index 000000000000..2aca7e729659 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/bin/vobject @@ -0,0 +1,27 @@ +#!/usr/bin/env php +<?php + +namespace Sabre\VObject; + +// This sucks.. we have to try to find the composer autoloader. But chances +// are, we can't find it this way. So we'll do our bestest +$paths = [ + __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly + __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. +]; + +foreach($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +if (!class_exists('Sabre\\VObject\\Version')) { + fwrite(STDERR, "Composer autoloader could not be loaded.\n"); + die(1); +} + +$cli = new Cli(); +exit($cli->main($argv)); + diff --git a/libs/composer/vendor/sabre/vobject/composer.json b/libs/composer/vendor/sabre/vobject/composer.json new file mode 100644 index 000000000000..2407cabb186a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/composer.json @@ -0,0 +1,88 @@ +{ + "name": "sabre/vobject", + "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "keywords" : [ + "iCalendar", + "iCal", + "vCalendar", + "vCard", + "jCard", + "jCal", + "ics", + "vcf", + "xCard", + "xCal", + "freebusy", + "recurrence", + "availability", + "rfc2425", + "rfc2426", + "rfc2739", + "rfc4770", + "rfc5545", + "rfc5546", + "rfc6321", + "rfc6350", + "rfc6351", + "rfc6474", + "rfc6638", + "rfc6715", + "rfc6868" + ], + "homepage" : "http://sabre.io/vobject/", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.5", + "ext-mbstring" : "*", + "sabre/xml" : ">=1.5 <3.0" + }, + "require-dev" : { + "phpunit/phpunit" : "> 4.8.35, <6.0.0", + "sabre/cs" : "^1.0.0" + + }, + "suggest" : { + "hoa/bench" : "If you would like to run the benchmark scripts" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + }, + { + "name" : "Dominik Tobschall", + "email" : "dominik@fruux.com", + "homepage" : "http://tobschall.de/", + "role" : "Developer" + }, + { + "name" : "Ivan Enderlin", + "email" : "ivan.enderlin@hoa-project.net", + "homepage" : "http://mnt.io/", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-vobject" + }, + "autoload" : { + "psr-4" : { + "Sabre\\VObject\\" : "lib/" + } + }, + "bin" : [ + "bin/vobject", + "bin/generate_vcards" + ], + "extra" : { + "branch-alias" : { + "dev-master" : "4.0.x-dev" + } + }, + "config" : { + "bin-dir" : "bin" + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/BirthdayCalendarGenerator.php b/libs/composer/vendor/sabre/vobject/lib/BirthdayCalendarGenerator.php new file mode 100644 index 000000000000..553912249975 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/BirthdayCalendarGenerator.php @@ -0,0 +1,191 @@ +<?php + +namespace Sabre\VObject; + +use Sabre\VObject\Component\VCalendar; + +/** + * This class generates birthday calendars. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Dominik Tobschall (http://tobschall.de/) + * @license http://sabre.io/license/ Modified BSD License + */ +class BirthdayCalendarGenerator { + + /** + * Input objects. + * + * @var array + */ + protected $objects = []; + + /** + * Default year. + * Used for dates without a year. + */ + const DEFAULT_YEAR = 2000; + + /** + * Output format for the SUMMARY. + * + * @var string + */ + protected $format = '%1$s\'s Birthday'; + + /** + * Creates the generator. + * + * Check the setTimeRange and setObjects methods for details about the + * arguments. + * + * @param mixed $objects + */ + function __construct($objects = null) { + + if ($objects) { + $this->setObjects($objects); + } + + } + + /** + * Sets the input objects. + * + * You must either supply a vCard as a string or as a Component/VCard object. + * It's also possible to supply an array of strings or objects. + * + * @param mixed $objects + * + * @return void + */ + function setObjects($objects) { + + if (!is_array($objects)) { + $objects = [$objects]; + } + + $this->objects = []; + foreach ($objects as $object) { + + if (is_string($object)) { + + $vObj = Reader::read($object); + if (!$vObj instanceof Component\VCard) { + throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects'); + } + + $this->objects[] = $vObj; + + } elseif ($object instanceof Component\VCard) { + + $this->objects[] = $object; + + } else { + + throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects'); + + } + + } + + } + + /** + * Sets the output format for the SUMMARY + * + * @param string $format + * + * @return void + */ + function setFormat($format) { + + $this->format = $format; + + } + + /** + * Parses the input data and returns a VCALENDAR. + * + * @return Component/VCalendar + */ + function getResult() { + + $calendar = new VCalendar(); + + foreach ($this->objects as $object) { + + // Skip if there is no BDAY property. + if (!$object->select('BDAY')) { + continue; + } + + // We've seen clients (ez-vcard) putting "BDAY:" properties + // without a value into vCards. If we come across those, we'll + // skip them. + if (empty($object->BDAY->getValue())) { + continue; + } + + // We're always converting to vCard 4.0 so we can rely on the + // VCardConverter handling the X-APPLE-OMIT-YEAR property for us. + $object = $object->convert(Document::VCARD40); + + // Skip if the card has no FN property. + if (!isset($object->FN)) { + continue; + } + + // Skip if the BDAY property is not of the right type. + if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) { + continue; + } + + // Skip if we can't parse the BDAY value. + try { + $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue()); + } catch (InvalidDataException $e) { + continue; + } + + // Set a year if it's not set. + $unknownYear = false; + + if (!$dateParts['year']) { + $object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date']; + + $unknownYear = true; + } + + // Create event. + $event = $calendar->add('VEVENT', [ + 'SUMMARY' => sprintf($this->format, $object->FN->getValue()), + 'DTSTART' => new \DateTime($object->BDAY->getValue()), + 'RRULE' => 'FREQ=YEARLY', + 'TRANSP' => 'TRANSPARENT', + ]); + + // add VALUE=date + $event->DTSTART['VALUE'] = 'DATE'; + + // Add X-SABRE-BDAY property. + if ($unknownYear) { + $event->add('X-SABRE-BDAY', 'BDAY', [ + 'X-SABRE-VCARD-UID' => $object->UID->getValue(), + 'X-SABRE-VCARD-FN' => $object->FN->getValue(), + 'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR, + ]); + } else { + $event->add('X-SABRE-BDAY', 'BDAY', [ + 'X-SABRE-VCARD-UID' => $object->UID->getValue(), + 'X-SABRE-VCARD-FN' => $object->FN->getValue(), + ]); + } + + } + + return $calendar; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Cli.php b/libs/composer/vendor/sabre/vobject/lib/Cli.php new file mode 100644 index 000000000000..df7ac22f3ccb --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Cli.php @@ -0,0 +1,771 @@ +<?php + +namespace Sabre\VObject; + +use + InvalidArgumentException; + +/** + * This is the CLI interface for sabre-vobject. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Cli { + + /** + * No output. + * + * @var bool + */ + protected $quiet = false; + + /** + * Help display. + * + * @var bool + */ + protected $showHelp = false; + + /** + * Wether to spit out 'mimedir' or 'json' format. + * + * @var string + */ + protected $format; + + /** + * JSON pretty print. + * + * @var bool + */ + protected $pretty; + + /** + * Source file. + * + * @var string + */ + protected $inputPath; + + /** + * Destination file. + * + * @var string + */ + protected $outputPath; + + /** + * output stream. + * + * @var resource + */ + protected $stdout; + + /** + * stdin. + * + * @var resource + */ + protected $stdin; + + /** + * stderr. + * + * @var resource + */ + protected $stderr; + + /** + * Input format (one of json or mimedir). + * + * @var string + */ + protected $inputFormat; + + /** + * Makes the parser less strict. + * + * @var bool + */ + protected $forgiving = false; + + /** + * Main function. + * + * @return int + */ + function main(array $argv) { + + // @codeCoverageIgnoreStart + // We cannot easily test this, so we'll skip it. Pretty basic anyway. + + if (!$this->stderr) { + $this->stderr = fopen('php://stderr', 'w'); + } + if (!$this->stdout) { + $this->stdout = fopen('php://stdout', 'w'); + } + if (!$this->stdin) { + $this->stdin = fopen('php://stdin', 'r'); + } + + // @codeCoverageIgnoreEnd + + + try { + + list($options, $positional) = $this->parseArguments($argv); + + if (isset($options['q'])) { + $this->quiet = true; + } + $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION)); + + foreach ($options as $name => $value) { + + switch ($name) { + + case 'q' : + // Already handled earlier. + break; + case 'h' : + case 'help' : + $this->showHelp(); + return 0; + break; + case 'format' : + switch ($value) { + + // jcard/jcal documents + case 'jcard' : + case 'jcal' : + + // specific document versions + case 'vcard21' : + case 'vcard30' : + case 'vcard40' : + case 'icalendar20' : + + // specific formats + case 'json' : + case 'mimedir' : + + // icalendar/vcad + case 'icalendar' : + case 'vcard' : + $this->format = $value; + break; + + default : + throw new InvalidArgumentException('Unknown format: ' . $value); + + } + break; + case 'pretty' : + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $this->pretty = true; + } + break; + case 'forgiving' : + $this->forgiving = true; + break; + case 'inputformat' : + switch ($value) { + // json formats + case 'jcard' : + case 'jcal' : + case 'json' : + $this->inputFormat = 'json'; + break; + + // mimedir formats + case 'mimedir' : + case 'icalendar' : + case 'vcard' : + case 'vcard21' : + case 'vcard30' : + case 'vcard40' : + case 'icalendar20' : + + $this->inputFormat = 'mimedir'; + break; + + default : + throw new InvalidArgumentException('Unknown format: ' . $value); + + } + break; + default : + throw new InvalidArgumentException('Unknown option: ' . $name); + + } + + } + + if (count($positional) === 0) { + $this->showHelp(); + return 1; + } + + if (count($positional) === 1) { + throw new InvalidArgumentException('Inputfile is a required argument'); + } + + if (count($positional) > 3) { + throw new InvalidArgumentException('Too many arguments'); + } + + if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) { + throw new InvalidArgumentException('Uknown command: ' . $positional[0]); + } + + } catch (InvalidArgumentException $e) { + $this->showHelp(); + $this->log('Error: ' . $e->getMessage(), 'red'); + return 1; + } + + $command = $positional[0]; + + $this->inputPath = $positional[1]; + $this->outputPath = isset($positional[2]) ? $positional[2] : '-'; + + if ($this->outputPath !== '-') { + $this->stdout = fopen($this->outputPath, 'w'); + } + + if (!$this->inputFormat) { + if (substr($this->inputPath, -5) === '.json') { + $this->inputFormat = 'json'; + } else { + $this->inputFormat = 'mimedir'; + } + } + if (!$this->format) { + if (substr($this->outputPath, -5) === '.json') { + $this->format = 'json'; + } else { + $this->format = 'mimedir'; + } + } + + + $realCode = 0; + + try { + + while ($input = $this->readInput()) { + + $returnCode = $this->$command($input); + if ($returnCode !== 0) $realCode = $returnCode; + + } + + } catch (EofException $e) { + // end of file + } catch (\Exception $e) { + $this->log('Error: ' . $e->getMessage(), 'red'); + return 2; + } + + return $realCode; + + } + + /** + * Shows the help message. + * + * @return void + */ + protected function showHelp() { + + $this->log('Usage:', 'yellow'); + $this->log(" vobject [options] command [arguments]"); + $this->log(''); + $this->log('Options:', 'yellow'); + $this->log($this->colorize('green', ' -q ') . "Don't output anything."); + $this->log($this->colorize('green', ' -help -h ') . "Display this help message."); + $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,"); + $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict."); + $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir."); + $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it"); + $this->log(" must be specified here."); + // Only PHP 5.4 and up + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $this->log($this->colorize('green', ' --pretty ') . "json pretty-print."); + } + $this->log(''); + $this->log('Commands:', 'yellow'); + $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.'); + $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.'); + $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.'); + $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.'); + $this->log( + <<<HELP + +If source_file is set as '-', STDIN will be used. +If output_file is omitted, STDOUT will be used. +All other output is sent to STDERR. + +HELP + ); + + $this->log('Examples:', 'yellow'); + $this->log(' vobject convert contact.vcf contact.json'); + $this->log(' vobject convert --format=vcard40 old.vcf new.vcf'); + $this->log(' vobject convert --inputformat=json --format=mimedir - -'); + $this->log(' vobject color calendar.ics'); + $this->log(''); + $this->log('https://github.com/fruux/sabre-vobject', 'purple'); + + } + + /** + * Validates a VObject file. + * + * @param Component $vObj + * + * @return int + */ + protected function validate(Component $vObj) { + + $returnCode = 0; + + switch ($vObj->name) { + case 'VCALENDAR' : + $this->log("iCalendar: " . (string)$vObj->VERSION); + break; + case 'VCARD' : + $this->log("vCard: " . (string)$vObj->VERSION); + break; + } + + $warnings = $vObj->validate(); + if (!count($warnings)) { + $this->log(" No warnings!"); + } else { + + $levels = [ + 1 => 'REPAIRED', + 2 => 'WARNING', + 3 => 'ERROR', + ]; + $returnCode = 2; + foreach ($warnings as $warn) { + + $extra = ''; + if ($warn['node'] instanceof Property) { + $extra = ' (property: "' . $warn['node']->name . '")'; + } + $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); + + } + + } + + return $returnCode; + + } + + /** + * Repairs a VObject file. + * + * @param Component $vObj + * + * @return int + */ + protected function repair(Component $vObj) { + + $returnCode = 0; + + switch ($vObj->name) { + case 'VCALENDAR' : + $this->log("iCalendar: " . (string)$vObj->VERSION); + break; + case 'VCARD' : + $this->log("vCard: " . (string)$vObj->VERSION); + break; + } + + $warnings = $vObj->validate(Node::REPAIR); + if (!count($warnings)) { + $this->log(" No warnings!"); + } else { + + $levels = [ + 1 => 'REPAIRED', + 2 => 'WARNING', + 3 => 'ERROR', + ]; + $returnCode = 2; + foreach ($warnings as $warn) { + + $extra = ''; + if ($warn['node'] instanceof Property) { + $extra = ' (property: "' . $warn['node']->name . '")'; + } + $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra); + + } + + } + fwrite($this->stdout, $vObj->serialize()); + + return $returnCode; + + } + + /** + * Converts a vObject file to a new format. + * + * @param Component $vObj + * + * @return int + */ + protected function convert($vObj) { + + $json = false; + $convertVersion = null; + $forceInput = null; + + switch ($this->format) { + case 'json' : + $json = true; + if ($vObj->name === 'VCARD') { + $convertVersion = Document::VCARD40; + } + break; + case 'jcard' : + $json = true; + $forceInput = 'VCARD'; + $convertVersion = Document::VCARD40; + break; + case 'jcal' : + $json = true; + $forceInput = 'VCALENDAR'; + break; + case 'mimedir' : + case 'icalendar' : + case 'icalendar20' : + case 'vcard' : + break; + case 'vcard21' : + $convertVersion = Document::VCARD21; + break; + case 'vcard30' : + $convertVersion = Document::VCARD30; + break; + case 'vcard40' : + $convertVersion = Document::VCARD40; + break; + + } + + if ($forceInput && $vObj->name !== $forceInput) { + throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format); + } + if ($convertVersion) { + $vObj = $vObj->convert($convertVersion); + } + if ($json) { + $jsonOptions = 0; + if ($this->pretty) { + $jsonOptions = JSON_PRETTY_PRINT; + } + fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions)); + } else { + fwrite($this->stdout, $vObj->serialize()); + } + + return 0; + + } + + /** + * Colorizes a file. + * + * @param Component $vObj + * + * @return int + */ + protected function color($vObj) { + + fwrite($this->stdout, $this->serializeComponent($vObj)); + + } + + /** + * Returns an ansi color string for a color name. + * + * @param string $color + * + * @return string + */ + protected function colorize($color, $str, $resetTo = 'default') { + + $colors = [ + 'cyan' => '1;36', + 'red' => '1;31', + 'yellow' => '1;33', + 'blue' => '0;34', + 'green' => '0;32', + 'default' => '0', + 'purple' => '0;35', + ]; + return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m"; + + } + + /** + * Writes out a string in specific color. + * + * @param string $color + * @param string $str + * + * @return void + */ + protected function cWrite($color, $str) { + + fwrite($this->stdout, $this->colorize($color, $str)); + + } + + protected function serializeComponent(Component $vObj) { + + $this->cWrite('cyan', 'BEGIN'); + $this->cWrite('red', ':'); + $this->cWrite('yellow', $vObj->name . "\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 = $vObj->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) { + if ($child instanceof Component) { + $this->serializeComponent($child); + } else { + $this->serializeProperty($child); + } + } + + $this->cWrite('cyan', 'END'); + $this->cWrite('red', ':'); + $this->cWrite('yellow', $vObj->name . "\n"); + + } + + /** + * Colorizes a property. + * + * @param Property $property + * + * @return void + */ + protected function serializeProperty(Property $property) { + + if ($property->group) { + $this->cWrite('default', $property->group); + $this->cWrite('red', '.'); + } + + $this->cWrite('yellow', $property->name); + + foreach ($property->parameters as $param) { + + $this->cWrite('red', ';'); + $this->cWrite('blue', $param->serialize()); + + } + $this->cWrite('red', ':'); + + if ($property instanceof Property\Binary) { + + $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)'); + + } else { + + $parts = $property->getParts(); + $first1 = true; + // Looping through property values + foreach ($parts as $part) { + if ($first1) { + $first1 = false; + } else { + $this->cWrite('red', $property->delimiter); + } + $first2 = true; + // Looping through property sub-values + foreach ((array)$part as $subPart) { + if ($first2) { + $first2 = false; + } else { + // The sub-value delimiter is always comma + $this->cWrite('red', ','); + } + + $subPart = strtr( + $subPart, + [ + '\\' => $this->colorize('purple', '\\\\', 'green'), + ';' => $this->colorize('purple', '\;', 'green'), + ',' => $this->colorize('purple', '\,', 'green'), + "\n" => $this->colorize('purple', "\\n\n\t", 'green'), + "\r" => "", + ] + ); + + $this->cWrite('green', $subPart); + } + } + + } + $this->cWrite("default", "\n"); + + } + + /** + * Parses the list of arguments. + * + * @param array $argv + * + * @return void + */ + protected function parseArguments(array $argv) { + + $positional = []; + $options = []; + + for ($ii = 0; $ii < count($argv); $ii++) { + + // Skipping the first argument. + if ($ii === 0) continue; + + $v = $argv[$ii]; + + if (substr($v, 0, 2) === '--') { + // This is a long-form option. + $optionName = substr($v, 2); + $optionValue = true; + if (strpos($optionName, '=')) { + list($optionName, $optionValue) = explode('=', $optionName); + } + $options[$optionName] = $optionValue; + } elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) { + // This is a short-form option. + foreach (str_split(substr($v, 1)) as $option) { + $options[$option] = true; + } + + } else { + + $positional[] = $v; + + } + + } + + return [$options, $positional]; + + } + + protected $parser; + + /** + * Reads the input file. + * + * @return Component + */ + protected function readInput() { + + if (!$this->parser) { + if ($this->inputPath !== '-') { + $this->stdin = fopen($this->inputPath, 'r'); + } + + if ($this->inputFormat === 'mimedir') { + $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0)); + } else { + $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0)); + } + } + + return $this->parser->parse(); + + } + + /** + * Sends a message to STDERR. + * + * @param string $msg + * + * @return void + */ + protected function log($msg, $color = 'default') { + + if (!$this->quiet) { + if ($color !== 'default') { + $msg = $this->colorize($color, $msg); + } + fwrite($this->stderr, $msg . "\n"); + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component.php b/libs/composer/vendor/sabre/vobject/lib/Component.php new file mode 100644 index 000000000000..ac87a10ec9f9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component.php @@ -0,0 +1,715 @@ +<?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) { + $level = 3; + + // We try to repair the same property appearing multiple times with the exact same value + // by removing the duplicates and keeping only one property + if ($options & self::REPAIR) { + $properties = array_unique($this->select($propName), SORT_REGULAR); + + if (count($properties) === 1) { + $this->remove($propName); + $this->add($properties[0]); + + $level = 1; + } + } + + $messages[] = [ + 'level' => $level, + '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 = []; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/Available.php b/libs/composer/vendor/sabre/vobject/lib/Component/Available.php new file mode 100644 index 000000000000..b3aaf08afad0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/Available.php @@ -0,0 +1,126 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; + +/** + * The Available sub-component. + * + * This component adds functionality to a component, specific for AVAILABLE + * components. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class Available extends VObject\Component { + + /** + * Returns the 'effective start' and 'effective end' of this VAVAILABILITY + * component. + * + * We use the DTSTART and DTEND or DURATION to determine this. + * + * The returned value is an array containing DateTimeImmutable instances. + * If either the start or end is 'unbounded' its value will be null + * instead. + * + * @return array + */ + function getEffectiveStartEnd() { + + $effectiveStart = $this->DTSTART->getDateTime(); + if (isset($this->DTEND)) { + $effectiveEnd = $this->DTEND->getDateTime(); + } else { + $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); + } + + return [$effectiveStart, $effectiveEnd]; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'UID' => 1, + 'DTSTART' => 1, + 'DTSTAMP' => 1, + + 'DTEND' => '?', + 'DURATION' => '?', + + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'LAST-MODIFIED' => '?', + 'RECURRENCE-ID' => '?', + 'RRULE' => '?', + 'SUMMARY' => '?', + + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'RDATE' => '*', + + 'AVAILABLE' => '*', + ]; + + } + + /** + * 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) { + + $result = parent::validate($options); + + if (isset($this->DTEND) && isset($this->DURATION)) { + $result[] = [ + 'level' => 3, + 'message' => 'DTEND and DURATION cannot both be present', + 'node' => $this + ]; + } + + return $result; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VAlarm.php b/libs/composer/vendor/sabre/vobject/lib/Component/VAlarm.php new file mode 100644 index 000000000000..faa8a5e74bee --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VAlarm.php @@ -0,0 +1,142 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeImmutable; +use DateTimeInterface; +use Sabre\VObject; +use Sabre\VObject\InvalidDataException; + +/** + * VAlarm component. + * + * This component contains some additional functionality specific for VALARMs. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VAlarm extends VObject\Component { + + /** + * Returns a DateTime object when this alarm is going to trigger. + * + * This ignores repeated alarm, only the first trigger is returned. + * + * @return DateTimeImmutable + */ + function getEffectiveTriggerTime() { + + $trigger = $this->TRIGGER; + if (!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') { + $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); + $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START'; + + $parentComponent = $this->parent; + if ($related === 'START') { + + if ($parentComponent->name === 'VTODO') { + $propName = 'DUE'; + } else { + $propName = 'DTSTART'; + } + + $effectiveTrigger = $parentComponent->$propName->getDateTime(); + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); + } else { + if ($parentComponent->name === 'VTODO') { + $endProp = 'DUE'; + } elseif ($parentComponent->name === 'VEVENT') { + $endProp = 'DTEND'; + } else { + throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); + } + + if (isset($parentComponent->$endProp)) { + $effectiveTrigger = $parentComponent->$endProp->getDateTime(); + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); + } elseif (isset($parentComponent->DURATION)) { + $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); + $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION); + $effectiveTrigger = $effectiveTrigger->add($duration); + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); + } else { + $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); + } + } + } else { + $effectiveTrigger = $trigger->getDateTime(); + } + return $effectiveTrigger; + + } + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param DateTime $start + * @param DateTime $end + * + * @return bool + */ + function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { + + $effectiveTrigger = $this->getEffectiveTriggerTime(); + + if (isset($this->DURATION)) { + $duration = VObject\DateTimeParser::parseDuration($this->DURATION); + $repeat = (string)$this->REPEAT; + if (!$repeat) { + $repeat = 1; + } + + $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat); + + foreach ($period as $occurrence) { + + if ($start <= $occurrence && $end > $occurrence) { + return true; + } + } + return false; + } else { + return ($start <= $effectiveTrigger && $end > $effectiveTrigger); + } + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'ACTION' => 1, + 'TRIGGER' => 1, + + 'DURATION' => '?', + 'REPEAT' => '?', + + 'ATTACH' => '?', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VAvailability.php b/libs/composer/vendor/sabre/vobject/lib/Component/VAvailability.php new file mode 100644 index 000000000000..66b3310c5e8b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VAvailability.php @@ -0,0 +1,156 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use Sabre\VObject; + +/** + * The VAvailability component. + * + * This component adds functionality to a component, specific for VAVAILABILITY + * components. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class VAvailability extends VObject\Component { + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on: + * + * https://tools.ietf.org/html/draft-daboo-calendar-availability-05#section-3.1 + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return bool + */ + function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { + + list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd(); + return ( + (is_null($effectiveStart) || $start < $effectiveEnd) && + (is_null($effectiveEnd) || $end > $effectiveStart) + ); + + } + + /** + * Returns the 'effective start' and 'effective end' of this VAVAILABILITY + * component. + * + * We use the DTSTART and DTEND or DURATION to determine this. + * + * The returned value is an array containing DateTimeImmutable instances. + * If either the start or end is 'unbounded' its value will be null + * instead. + * + * @return array + */ + function getEffectiveStartEnd() { + + $effectiveStart = null; + $effectiveEnd = null; + + if (isset($this->DTSTART)) { + $effectiveStart = $this->DTSTART->getDateTime(); + } + if (isset($this->DTEND)) { + $effectiveEnd = $this->DTEND->getDateTime(); + } elseif ($effectiveStart && isset($this->DURATION)) { + $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); + } + + return [$effectiveStart, $effectiveEnd]; + + } + + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'UID' => 1, + 'DTSTAMP' => 1, + + 'BUSYTYPE' => '?', + 'CLASS' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'DTSTART' => '?', + 'LAST-MODIFIED' => '?', + 'ORGANIZER' => '?', + 'PRIORITY' => '?', + 'SEQUENCE' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + 'DTEND' => '?', + 'DURATION' => '?', + + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + ]; + + } + + /** + * 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) { + + $result = parent::validate($options); + + if (isset($this->DTEND) && isset($this->DURATION)) { + $result[] = [ + 'level' => 3, + 'message' => 'DTEND and DURATION cannot both be present', + 'node' => $this + ]; + } + + return $result; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VCalendar.php b/libs/composer/vendor/sabre/vobject/lib/Component/VCalendar.php new file mode 100644 index 000000000000..1b3137d38ebf --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VCalendar.php @@ -0,0 +1,561 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use DateTimeZone; +use Sabre\VObject; +use Sabre\VObject\Component; +use Sabre\VObject\InvalidDataException; +use Sabre\VObject\Property; +use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Recur\NoInstancesException; + +/** + * The VCalendar component. + * + * This component adds functionality to a component, specific for a VCALENDAR. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VCalendar extends VObject\Document { + + /** + * The default name for this component. + * + * This should be 'VCALENDAR' or 'VCARD'. + * + * @var string + */ + static $defaultName = 'VCALENDAR'; + + /** + * This is a list of components, and which classes they should map to. + * + * @var array + */ + static $componentMap = [ + 'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar', + 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm', + 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', + 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', + 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability', + 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available', + 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', + 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', + 'VTODO' => 'Sabre\\VObject\\Component\\VTodo', + ]; + + /** + * List of value-types, and which classes they map to. + * + * @var array + */ + static $valueMap = [ + 'BINARY' => 'Sabre\\VObject\\Property\\Binary', + 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', + 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', + 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', + 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', + 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', + 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', + 'TEXT' => 'Sabre\\VObject\\Property\\Text', + 'TIME' => 'Sabre\\VObject\\Property\\Time', + 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. + 'URI' => 'Sabre\\VObject\\Property\\Uri', + 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + ]; + + /** + * List of properties, and which classes they map to. + * + * @var array + */ + static $propertyMap = [ + // Calendar properties + 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', + 'METHOD' => 'Sabre\\VObject\\Property\\FlatText', + 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', + 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', + + // Component properties + 'ATTACH' => 'Sabre\\VObject\\Property\\Uri', + 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', + 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', + 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', + 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', + 'GEO' => 'Sabre\\VObject\\Property\\FloatValue', + 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', + 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue', + 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue', + 'RESOURCES' => 'Sabre\\VObject\\Property\\Text', + 'STATUS' => 'Sabre\\VObject\\Property\\FlatText', + 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', + + // Date and Time Component Properties + 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', + 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', + + // Time Zone Component Properties + 'TZID' => 'Sabre\\VObject\\Property\\FlatText', + 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', + 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', + 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', + 'TZURL' => 'Sabre\\VObject\\Property\\Uri', + + // Relationship Component Properties + 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', + 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', + 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', + 'URL' => 'Sabre\\VObject\\Property\\Uri', + 'UID' => 'Sabre\\VObject\\Property\\FlatText', + + // Recurrence Component Properties + 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', + 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 + + // Alarm Component Properties + 'ACTION' => 'Sabre\\VObject\\Property\\FlatText', + 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue', + 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', + + // Change Management Component Properties + 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue', + + // Request Status + 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', + + // Additions from draft-daboo-valarm-extensions-04 + 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', + 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', + 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', + 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', + + // Additions from draft-daboo-calendar-availability-05 + 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text', + + ]; + + /** + * Returns the current document type. + * + * @return int + */ + function getDocumentType() { + + return self::ICALENDAR20; + + } + + /** + * Returns a list of all 'base components'. For instance, if an Event has + * a recurrence rule, and one instance is overridden, the overridden event + * will have the same UID, but will be excluded from this list. + * + * VTIMEZONE components will always be excluded. + * + * @param string $componentName filter by component name + * + * @return VObject\Component[] + */ + function getBaseComponents($componentName = null) { + + $isBaseComponent = function($component) { + + if (!$component instanceof VObject\Component) { + return false; + } + if ($component->name === 'VTIMEZONE') { + return false; + } + if (isset($component->{'RECURRENCE-ID'})) { + return false; + } + return true; + + }; + + if ($componentName) { + // Early exit + return array_filter( + $this->select($componentName), + $isBaseComponent + ); + } + + $components = []; + foreach ($this->children as $childGroup) { + + foreach ($childGroup as $child) { + + if (!$child instanceof Component) { + // If one child is not a component, they all are so we skip + // the entire group. + continue 2; + } + if ($isBaseComponent($child)) { + $components[] = $child; + } + + } + + } + return $components; + + } + + /** + * Returns the first component that is not a VTIMEZONE, and does not have + * an RECURRENCE-ID. + * + * If there is no such component, null will be returned. + * + * @param string $componentName filter by component name + * + * @return VObject\Component|null + */ + function getBaseComponent($componentName = null) { + + $isBaseComponent = function($component) { + + if (!$component instanceof VObject\Component) { + return false; + } + if ($component->name === 'VTIMEZONE') { + return false; + } + if (isset($component->{'RECURRENCE-ID'})) { + return false; + } + return true; + + }; + + if ($componentName) { + foreach ($this->select($componentName) as $child) { + if ($isBaseComponent($child)) { + return $child; + } + } + return null; + } + + // Searching all components + foreach ($this->children as $childGroup) { + foreach ($childGroup as $child) { + if ($isBaseComponent($child)) { + return $child; + } + } + + } + return null; + + } + + /** + * Expand all events in this VCalendar object and return a new VCalendar + * with the expanded events. + * + * If this calendar object, has events with recurrence rules, this method + * can be used to expand the event into multiple sub-events. + * + * Each event will be stripped from it's recurrence information, and only + * the instances of the event in the specified timerange will be left + * alone. + * + * In addition, this method will cause timezone information to be stripped, + * and normalized to UTC. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * @param DateTimeZone $timeZone reference timezone for floating dates and + * times. + * @return VCalendar + */ + function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) { + + $newChildren = []; + $recurringEvents = []; + + if (!$timeZone) { + $timeZone = new DateTimeZone('UTC'); + } + + $stripTimezones = function(Component $component) use ($timeZone, &$stripTimezones) { + + foreach ($component->children() as $componentChild) { + if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) { + + $dt = $componentChild->getDateTimes($timeZone); + // We only need to update the first timezone, because + // setDateTimes will match all other timezones to the + // first. + $dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC')); + $componentChild->setDateTimes($dt); + } elseif ($componentChild instanceof Component) { + $stripTimezones($componentChild); + } + + } + return $component; + + }; + + foreach ($this->children() as $child) { + + if ($child instanceof Property && $child->name !== 'PRODID') { + // We explictly want to ignore PRODID, because we want to + // overwrite it with our own. + $newChildren[] = clone $child; + } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') { + + // We're also stripping all VTIMEZONE objects because we're + // converting everything to UTC. + if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) { + // Handle these a bit later. + $uid = (string)$child->UID; + if (!$uid) { + throw new InvalidDataException('Every VEVENT object must have a UID property'); + } + if (isset($recurringEvents[$uid])) { + $recurringEvents[$uid][] = clone $child; + } else { + $recurringEvents[$uid] = [clone $child]; + } + } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) { + $newChildren[] = $stripTimezones(clone $child); + } + + } + + } + + foreach ($recurringEvents as $events) { + + try { + $it = new EventIterator($events, $timeZone); + + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + continue; + } + $it->fastForward($start); + + while ($it->valid() && $it->getDTStart() < $end) { + + if ($it->getDTEnd() > $start) { + + $newChildren[] = $stripTimezones($it->getEventObject()); + + } + $it->next(); + + } + + } + + return new self($newChildren); + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return [ + 'VERSION' => '2.0', + 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', + 'CALSCALE' => 'GREGORIAN', + ]; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'PRODID' => 1, + 'VERSION' => 1, + + 'CALSCALE' => '?', + 'METHOD' => '?', + ]; + + } + + /** + * 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) { + + $warnings = parent::validate($options); + + if ($ver = $this->VERSION) { + if ((string)$ver !== '2.0') { + $warnings[] = [ + 'level' => 3, + 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', + 'node' => $this, + ]; + } + + } + + $uidList = []; + $componentsFound = 0; + $componentTypes = []; + + foreach ($this->children() as $child) { + if ($child instanceof Component) { + $componentsFound++; + + if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) { + continue; + } + $componentTypes[] = $child->name; + + $uid = (string)$child->UID; + $isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1; + if (isset($uidList[$uid])) { + $uidList[$uid]['count']++; + if ($isMaster && $uidList[$uid]['hasMaster']) { + $warnings[] = [ + 'level' => 3, + 'message' => 'More than one master object was found for the object with UID ' . $uid, + 'node' => $this, + ]; + } + $uidList[$uid]['hasMaster'] += $isMaster; + } else { + $uidList[$uid] = [ + 'count' => 1, + 'hasMaster' => $isMaster, + ]; + } + + } + } + + if ($componentsFound === 0) { + $warnings[] = [ + 'level' => 3, + 'message' => 'An iCalendar object must have at least 1 component.', + 'node' => $this, + ]; + } + + if ($options & self::PROFILE_CALDAV) { + if (count($uidList) > 1) { + $warnings[] = [ + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', + 'node' => $this, + ]; + } + if (count($componentTypes) === 0) { + $warnings[] = [ + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', + 'node' => $this, + ]; + } + if (count(array_unique($componentTypes)) > 1) { + $warnings[] = [ + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', + 'node' => $this, + ]; + } + + if (isset($this->METHOD)) { + $warnings[] = [ + 'level' => 3, + 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', + 'node' => $this, + ]; + } + } + + return $warnings; + + } + + /** + * Returns all components with a specific UID value. + * + * @return array + */ + function getByUID($uid) { + + return array_filter($this->getComponents(), function($item) use ($uid) { + + if (!$itemUid = $item->select('UID')) { + return false; + } + $itemUid = current($itemUid)->getValue(); + return $uid === $itemUid; + + }); + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VCard.php b/libs/composer/vendor/sabre/vobject/lib/Component/VCard.php new file mode 100644 index 000000000000..bca623d5e075 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VCard.php @@ -0,0 +1,558 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; +use Sabre\Xml; + +/** + * The VCard component. + * + * This component represents the BEGIN:VCARD and END:VCARD found in every + * vcard. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VCard extends VObject\Document { + + /** + * The default name for this component. + * + * This should be 'VCALENDAR' or 'VCARD'. + * + * @var string + */ + static $defaultName = 'VCARD'; + + /** + * Caching the version number. + * + * @var int + */ + private $version = null; + + /** + * This is a list of components, and which classes they should map to. + * + * @var array + */ + static $componentMap = [ + 'VCARD' => 'Sabre\\VObject\\Component\\VCard', + ]; + + /** + * List of value-types, and which classes they map to. + * + * @var array + */ + static $valueMap = [ + 'BINARY' => 'Sabre\\VObject\\Property\\Binary', + 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', + 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only + 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date', + 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime', + 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only + 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue', + 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue', + 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', + 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', + 'TEXT' => 'Sabre\\VObject\\Property\\Text', + 'TIME' => 'Sabre\\VObject\\Property\\Time', + 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. + 'URI' => 'Sabre\\VObject\\Property\\Uri', + 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only + 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', + ]; + + /** + * List of properties, and which classes they map to. + * + * @var array + */ + static $propertyMap = [ + + // vCard 2.1 properties and up + 'N' => 'Sabre\\VObject\\Property\\Text', + 'FN' => 'Sabre\\VObject\\Property\\FlatText', + 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', + 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + 'ADR' => 'Sabre\\VObject\\Property\\Text', + 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + 'TEL' => 'Sabre\\VObject\\Property\\FlatText', + 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText', + 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + 'GEO' => 'Sabre\\VObject\\Property\\FlatText', + 'TITLE' => 'Sabre\\VObject\\Property\\FlatText', + 'ROLE' => 'Sabre\\VObject\\Property\\FlatText', + 'LOGO' => 'Sabre\\VObject\\Property\\Binary', + // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so + // not supported at the moment + 'ORG' => 'Sabre\\VObject\\Property\\Text', + 'NOTE' => 'Sabre\\VObject\\Property\\FlatText', + 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp', + 'SOUND' => 'Sabre\\VObject\\Property\\FlatText', + 'URL' => 'Sabre\\VObject\\Property\\Uri', + 'UID' => 'Sabre\\VObject\\Property\\FlatText', + 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', + 'KEY' => 'Sabre\\VObject\\Property\\FlatText', + 'TZ' => 'Sabre\\VObject\\Property\\Text', + + // vCard 3.0 properties + 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', + 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText', + 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', + 'NICKNAME' => 'Sabre\\VObject\\Property\\Text', + 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0 + + // rfc2739 properties + 'FBURL' => 'Sabre\\VObject\\Property\\Uri', + 'CAPURI' => 'Sabre\\VObject\\Property\\Uri', + 'CALURI' => 'Sabre\\VObject\\Property\\Uri', + 'CALADRURI' => 'Sabre\\VObject\\Property\\Uri', + + // rfc4770 properties + 'IMPP' => 'Sabre\\VObject\\Property\\Uri', + + // vCard 4.0 properties + 'SOURCE' => 'Sabre\\VObject\\Property\\Uri', + 'XML' => 'Sabre\\VObject\\Property\\FlatText', + 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text', + 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag', + 'GENDER' => 'Sabre\\VObject\\Property\\Text', + 'KIND' => 'Sabre\\VObject\\Property\\FlatText', + 'MEMBER' => 'Sabre\\VObject\\Property\\Uri', + 'RELATED' => 'Sabre\\VObject\\Property\\Uri', + + // rfc6474 properties + 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText', + 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText', + 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', + + // rfc6715 properties + 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText', + 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText', + 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText', + 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText', + + ]; + + /** + * Returns the current document type. + * + * @return int + */ + function getDocumentType() { + + if (!$this->version) { + + $version = (string)$this->VERSION; + + switch ($version) { + case '2.1' : + $this->version = self::VCARD21; + break; + case '3.0' : + $this->version = self::VCARD30; + break; + case '4.0' : + $this->version = self::VCARD40; + break; + default : + // We don't want to cache the version if it's unknown, + // because we might get a version property in a bit. + return self::UNKNOWN; + } + } + + return $this->version; + + } + + /** + * Converts the document to a different vcard version. + * + * Use one of the VCARD constants for the target. This method will return + * a copy of the vcard in the new version. + * + * At the moment the only supported conversion is from 3.0 to 4.0. + * + * If input and output version are identical, a clone is returned. + * + * @param int $target + * + * @return VCard + */ + function convert($target) { + + $converter = new VObject\VCardConverter(); + return $converter->convert($this, $target); + + } + + /** + * VCards with version 2.1, 3.0 and 4.0 are found. + * + * If the VCARD doesn't know its version, 2.1 is assumed. + */ + const DEFAULT_VERSION = self::VCARD21; + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $warnings = []; + + $versionMap = [ + self::VCARD21 => '2.1', + self::VCARD30 => '3.0', + self::VCARD40 => '4.0', + ]; + + $version = $this->select('VERSION'); + if (count($version) === 1) { + $version = (string)$this->VERSION; + if ($version !== '2.1' && $version !== '3.0' && $version !== '4.0') { + $warnings[] = [ + 'level' => 3, + 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + 'node' => $this, + ]; + if ($options & self::REPAIR) { + $this->VERSION = $versionMap[self::DEFAULT_VERSION]; + } + } + if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) { + $warnings[] = [ + 'level' => 3, + 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.', + 'node' => $this, + ]; + } + + } + $uid = $this->select('UID'); + if (count($uid) === 0) { + if ($options & self::PROFILE_CARDDAV) { + // Required for CardDAV + $warningLevel = 3; + $message = 'vCards on CardDAV servers MUST have a UID property.'; + } else { + // Not required for regular vcards + $warningLevel = 2; + $message = 'Adding a UID to a vCard property is recommended.'; + } + if ($options & self::REPAIR) { + $this->UID = VObject\UUIDUtil::getUUID(); + $warningLevel = 1; + } + $warnings[] = [ + 'level' => $warningLevel, + 'message' => $message, + 'node' => $this, + ]; + } + + $fn = $this->select('FN'); + if (count($fn) !== 1) { + + $repaired = false; + if (($options & self::REPAIR) && count($fn) === 0) { + // We're going to try to see if we can use the contents of the + // N property. + if (isset($this->N)) { + $value = explode(';', (string)$this->N); + if (isset($value[1]) && $value[1]) { + $this->FN = $value[1] . ' ' . $value[0]; + } else { + $this->FN = $value[0]; + } + $repaired = true; + + // Otherwise, the ORG property may work + } elseif (isset($this->ORG)) { + $this->FN = (string)$this->ORG; + $repaired = true; + + // Otherwise, the EMAIL property may work + } elseif (isset($this->EMAIL)) { + $this->FN = (string)$this->EMAIL; + $repaired = true; + } + + } + $warnings[] = [ + 'level' => $repaired ? 1 : 3, + 'message' => 'The FN property must appear in the VCARD component exactly 1 time', + 'node' => $this, + ]; + } + + return array_merge( + parent::validate($options), + $warnings + ); + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'ADR' => '*', + 'ANNIVERSARY' => '?', + 'BDAY' => '?', + 'CALADRURI' => '*', + 'CALURI' => '*', + 'CATEGORIES' => '*', + 'CLIENTPIDMAP' => '*', + 'EMAIL' => '*', + 'FBURL' => '*', + 'IMPP' => '*', + 'GENDER' => '?', + 'GEO' => '*', + 'KEY' => '*', + 'KIND' => '?', + 'LANG' => '*', + 'LOGO' => '*', + 'MEMBER' => '*', + 'N' => '?', + 'NICKNAME' => '*', + 'NOTE' => '*', + 'ORG' => '*', + 'PHOTO' => '*', + 'PRODID' => '?', + 'RELATED' => '*', + 'REV' => '?', + 'ROLE' => '*', + 'SOUND' => '*', + 'SOURCE' => '*', + 'TEL' => '*', + 'TITLE' => '*', + 'TZ' => '*', + 'URL' => '*', + 'VERSION' => '1', + 'XML' => '*', + + // FN is commented out, because it's already handled by the + // validate function, which may also try to repair it. + // 'FN' => '+', + 'UID' => '?', + ]; + + } + + /** + * Returns a preferred field. + * + * VCards can indicate wether a field such as ADR, TEL or EMAIL is + * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x + * being a number between 1 and 100). + * + * If neither of those parameters are specified, the first is returned, if + * a field with that name does not exist, null is returned. + * + * @param string $fieldName + * + * @return VObject\Property|null + */ + function preferred($propertyName) { + + $preferred = null; + $lastPref = 101; + foreach ($this->select($propertyName) as $field) { + + $pref = 101; + if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) { + $pref = 1; + } elseif (isset($field['PREF'])) { + $pref = $field['PREF']->getValue(); + } + + if ($pref < $lastPref || is_null($preferred)) { + $preferred = $field; + $lastPref = $pref; + } + + } + return $preferred; + + } + + /** + * Returns a property with a specific TYPE value (ADR, TEL, or EMAIL). + * + * This function will return null if the property does not exist. If there are + * multiple properties with the same TYPE value, only one will be returned. + * + * @param string $propertyName + * @param string $type + * + * @return VObject\Property|null + */ + function getByType($propertyName, $type) { + foreach ($this->select($propertyName) as $field) { + if (isset($field['TYPE']) && $field['TYPE']->has($type)) { + return $field; + } + } + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return [ + 'VERSION' => '4.0', + 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', + 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), + ]; + + } + + /** + * 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() { + + // A vcard does not have sub-components, so we're overriding this + // method to remove that array element. + $properties = []; + + foreach ($this->children() as $child) { + $properties[] = $child->jsonSerialize(); + } + + return [ + strtolower($this->name), + $properties, + ]; + + } + + /** + * 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) { + + $propertiesByGroup = []; + + foreach ($this->children() as $property) { + + $group = $property->group; + + if (!isset($propertiesByGroup[$group])) { + $propertiesByGroup[$group] = []; + } + + $propertiesByGroup[$group][] = $property; + + } + + $writer->startElement(strtolower($this->name)); + + foreach ($propertiesByGroup as $group => $properties) { + + if (!empty($group)) { + + $writer->startElement('group'); + $writer->writeAttribute('name', strtolower($group)); + + } + + foreach ($properties as $property) { + switch ($property->name) { + + case 'VERSION': + continue; + + case 'XML': + $value = $property->getParts(); + $fragment = new Xml\Element\XmlFragment($value[0]); + $writer->write($fragment); + break; + + default: + $property->xmlSerialize($writer); + break; + + } + } + + if (!empty($group)) { + $writer->endElement(); + } + + } + + $writer->endElement(); + + } + + /** + * Returns the default class for a property name. + * + * @param string $propertyName + * + * @return string + */ + function getClassNameForPropertyName($propertyName) { + + $className = parent::getClassNameForPropertyName($propertyName); + + // In vCard 4, BINARY no longer exists, and we need URI instead. + if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType() === self::VCARD40) { + return 'Sabre\\VObject\\Property\\Uri'; + } + return $className; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VEvent.php b/libs/composer/vendor/sabre/vobject/lib/Component/VEvent.php new file mode 100644 index 000000000000..7f6861190049 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VEvent.php @@ -0,0 +1,153 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use Sabre\VObject; +use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Recur\NoInstancesException; + +/** + * VEvent component. + * + * This component contains some additional functionality specific for VEVENT's. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VEvent extends VObject\Component { + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return bool + */ + function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { + + if ($this->RRULE) { + + try { + + $it = new EventIterator($this, null, $start->getTimezone()); + + } catch (NoInstancesException $e) { + + // If we've catched this exception, there are no instances + // for the event that fall into the specified time-range. + return false; + + } + + $it->fastForward($start); + + // We fast-forwarded to a spot where the end-time of the + // recurrence instance exceeded the start of the requested + // time-range. + // + // If the starttime of the recurrence did not exceed the + // end of the time range as well, we have a match. + return ($it->getDTStart() < $end && $it->getDTEnd() > $start); + + } + + $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); + if (isset($this->DTEND)) { + + // The DTEND property is considered non inclusive. So for a 3 day + // event in july, dtstart and dtend would have to be July 1st and + // July 4th respectively. + // + // See: + // http://tools.ietf.org/html/rfc5545#page-54 + $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); + + } elseif (isset($this->DURATION)) { + $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); + } elseif (!$this->DTSTART->hasTime()) { + $effectiveEnd = $effectiveStart->modify('+1 day'); + } else { + $effectiveEnd = $effectiveStart; + } + return ( + ($start < $effectiveEnd) && ($end > $effectiveStart) + ); + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return [ + 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), + 'DTSTAMP' => date('Ymd\\THis\\Z'), + ]; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + $hasMethod = isset($this->parent->METHOD); + return [ + 'UID' => 1, + 'DTSTAMP' => 1, + 'DTSTART' => $hasMethod ? '?' : '1', + 'CLASS' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'GEO' => '?', + 'LAST-MODIFIED' => '?', + 'LOCATION' => '?', + 'ORGANIZER' => '?', + 'PRIORITY' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'TRANSP' => '?', + 'URL' => '?', + 'RECURRENCE-ID' => '?', + 'RRULE' => '?', + 'DTEND' => '?', + 'DURATION' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'REQUEST-STATUS' => '*', + 'RELATED-TO' => '*', + 'RESOURCES' => '*', + 'RDATE' => '*', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VFreeBusy.php b/libs/composer/vendor/sabre/vobject/lib/Component/VFreeBusy.php new file mode 100644 index 000000000000..72294cc9f3b7 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VFreeBusy.php @@ -0,0 +1,102 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use Sabre\VObject; + +/** + * The VFreeBusy component. + * + * This component adds functionality to a component, specific for VFREEBUSY + * components. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VFreeBusy extends VObject\Component { + + /** + * Checks based on the contained FREEBUSY information, if a timeslot is + * available. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return bool + */ + function isFree(DateTimeInterface $start, DatetimeInterface $end) { + + foreach ($this->select('FREEBUSY') as $freebusy) { + + // We are only interested in FBTYPE=BUSY (the default), + // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. + if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'], 0, 4)) !== 'BUSY') { + continue; + } + + // The freebusy component can hold more than 1 value, separated by + // commas. + $periods = explode(',', (string)$freebusy); + + foreach ($periods as $period) { + // Every period is formatted as [start]/[end]. The start is an + // absolute UTC time, the end may be an absolute UTC time, or + // duration (relative) value. + list($busyStart, $busyEnd) = explode('/', $period); + + $busyStart = VObject\DateTimeParser::parse($busyStart); + $busyEnd = VObject\DateTimeParser::parse($busyEnd); + if ($busyEnd instanceof \DateInterval) { + $busyEnd = $busyStart->add($busyEnd); + } + + if ($start < $busyEnd && $end > $busyStart) { + return false; + } + + } + + } + + return true; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CONTACT' => '?', + 'DTSTART' => '?', + 'DTEND' => '?', + 'ORGANIZER' => '?', + 'URL' => '?', + + 'ATTENDEE' => '*', + 'COMMENT' => '*', + 'FREEBUSY' => '*', + 'REQUEST-STATUS' => '*', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VJournal.php b/libs/composer/vendor/sabre/vobject/lib/Component/VJournal.php new file mode 100644 index 000000000000..a1b1a863d2de --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VJournal.php @@ -0,0 +1,107 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use Sabre\VObject; + +/** + * VJournal component. + * + * This component contains some additional functionality specific for VJOURNALs. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VJournal extends VObject\Component { + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return bool + */ + function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { + + $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; + if ($dtstart) { + $effectiveEnd = $dtstart; + if (!$this->DTSTART->hasTime()) { + $effectiveEnd = $effectiveEnd->modify('+1 day'); + } + + return ($start <= $effectiveEnd && $end > $dtstart); + + } + return false; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CLASS' => '?', + 'CREATED' => '?', + 'DTSTART' => '?', + 'LAST-MODIFIED' => '?', + 'ORGANIZER' => '?', + 'RECURRENCE-ID' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + + 'RRULE' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'DESCRIPTION' => '*', + 'EXDATE' => '*', + 'RELATED-TO' => '*', + 'RDATE' => '*', + ]; + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return [ + 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), + 'DTSTAMP' => date('Ymd\\THis\\Z'), + ]; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VTimeZone.php b/libs/composer/vendor/sabre/vobject/lib/Component/VTimeZone.php new file mode 100644 index 000000000000..f6eb6cba18a3 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VTimeZone.php @@ -0,0 +1,66 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; + +/** + * The VTimeZone component. + * + * This component adds functionality to a component, specific for VTIMEZONE + * components. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VTimeZone extends VObject\Component { + + /** + * Returns the PHP DateTimeZone for this VTIMEZONE component. + * + * If we can't accurately determine the timezone, this method will return + * UTC. + * + * @return \DateTimeZone + */ + function getTimeZone() { + + return VObject\TimeZoneUtil::getTimeZone((string)$this->TZID, $this->root); + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'TZID' => 1, + + 'LAST-MODIFIED' => '?', + 'TZURL' => '?', + + // At least 1 STANDARD or DAYLIGHT must appear. + // + // The validator is not specific yet to pick this up, so these + // rules are too loose. + 'STANDARD' => '*', + 'DAYLIGHT' => '*', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Component/VTodo.php b/libs/composer/vendor/sabre/vobject/lib/Component/VTodo.php new file mode 100644 index 000000000000..144ced694ac5 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Component/VTodo.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeInterface; +use Sabre\VObject; + +/** + * VTodo component. + * + * This component contains some additional functionality specific for VTODOs. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VTodo extends VObject\Component { + + /** + * Returns true or false depending on if the event falls in the specified + * time-range. This is used for filtering purposes. + * + * The rules used to determine if an event falls within the specified + * time-range is based on the CalDAV specification. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return bool + */ + function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) { + + $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; + $duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null; + $due = isset($this->DUE) ? $this->DUE->getDateTime() : null; + $completed = isset($this->COMPLETED) ? $this->COMPLETED->getDateTime() : null; + $created = isset($this->CREATED) ? $this->CREATED->getDateTime() : null; + + if ($dtstart) { + if ($duration) { + $effectiveEnd = $dtstart->add($duration); + return $start <= $effectiveEnd && $end > $dtstart; + } elseif ($due) { + return + ($start < $due || $start <= $dtstart) && + ($end > $dtstart || $end >= $due); + } else { + return $start <= $dtstart && $end > $dtstart; + } + } + if ($due) { + return ($start < $due && $end >= $due); + } + if ($completed && $created) { + return + ($start <= $created || $start <= $completed) && + ($end >= $created || $end >= $completed); + } + if ($completed) { + return ($start <= $completed && $end >= $completed); + } + if ($created) { + return ($end > $created); + } + return true; + + } + + /** + * 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. + * + * @var array + */ + function getValidationRules() { + + return [ + 'UID' => 1, + 'DTSTAMP' => 1, + + 'CLASS' => '?', + 'COMPLETED' => '?', + 'CREATED' => '?', + 'DESCRIPTION' => '?', + 'DTSTART' => '?', + 'GEO' => '?', + 'LAST-MODIFIED' => '?', + 'LOCATION' => '?', + 'ORGANIZER' => '?', + 'PERCENT' => '?', + 'PRIORITY' => '?', + 'RECURRENCE-ID' => '?', + 'SEQUENCE' => '?', + 'STATUS' => '?', + 'SUMMARY' => '?', + 'URL' => '?', + + 'RRULE' => '?', + 'DUE' => '?', + 'DURATION' => '?', + + 'ATTACH' => '*', + 'ATTENDEE' => '*', + 'CATEGORIES' => '*', + 'COMMENT' => '*', + 'CONTACT' => '*', + 'EXDATE' => '*', + 'REQUEST-STATUS' => '*', + 'RELATED-TO' => '*', + 'RESOURCES' => '*', + 'RDATE' => '*', + ]; + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $result = parent::validate($options); + if (isset($this->DUE) && isset($this->DTSTART)) { + + $due = $this->DUE; + $dtStart = $this->DTSTART; + + if ($due->getValueType() !== $dtStart->getValueType()) { + + $result[] = [ + 'level' => 3, + 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', + 'node' => $due, + ]; + + } elseif ($due->getDateTime() < $dtStart->getDateTime()) { + + $result[] = [ + 'level' => 3, + 'message' => 'DUE must occur after DTSTART', + 'node' => $due, + ]; + + } + + } + + return $result; + + } + + /** + * This method should return a list of default property values. + * + * @return array + */ + protected function getDefaults() { + + return [ + 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(), + 'DTSTAMP' => date('Ymd\\THis\\Z'), + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/DateTimeParser.php b/libs/composer/vendor/sabre/vobject/lib/DateTimeParser.php new file mode 100644 index 000000000000..f9a802d2533f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/DateTimeParser.php @@ -0,0 +1,580 @@ +<?php + +namespace Sabre\VObject; + +use DateInterval; +use DateTimeImmutable; +use DateTimeZone; + +/** + * DateTimeParser. + * + * This class is responsible for parsing the several different date and time + * formats iCalendar and vCards have. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class DateTimeParser { + + /** + * Parses an iCalendar (rfc5545) formatted datetime and returns a + * DateTimeImmutable object. + * + * Specifying a reference timezone is optional. It will only be used + * if the non-UTC format is used. The argument is used as a reference, the + * returned DateTimeImmutable object will still be in the UTC timezone. + * + * @param string $dt + * @param DateTimeZone $tz + * + * @return DateTimeImmutable + */ + static function parseDateTime($dt, DateTimeZone $tz = null) { + + // Format is YYYYMMDD + "T" + hhmmss + $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/', $dt, $matches); + + if (!$result) { + throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt); + } + + if ($matches[7] === 'Z' || is_null($tz)) { + $tz = new DateTimeZone('UTC'); + } + + try { + $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6], $tz); + } catch (\Exception $e) { + throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt); + } + + return $date; + + } + + /** + * Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object. + * + * @param string $date + * @param DateTimeZone $tz + * + * @return DateTimeImmutable + */ + static function parseDate($date, DateTimeZone $tz = null) { + + // Format is YYYYMMDD + $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches); + + if (!$result) { + throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date); + } + + if (is_null($tz)) { + $tz = new DateTimeZone('UTC'); + } + + try { + $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz); + } catch (\Exception $e) { + throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date); + } + + return $date; + + } + + /** + * Parses an iCalendar (RFC5545) formatted duration value. + * + * This method will either return a DateTimeInterval object, or a string + * suitable for strtotime or DateTime::modify. + * + * @param string $duration + * @param bool $asString + * + * @return DateInterval|string + */ + static function parseDuration($duration, $asString = false) { + + $result = preg_match('/^(?<plusminus>\+|-)?P((?<week>\d+)W)?((?<day>\d+)D)?(T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?$/', $duration, $matches); + if (!$result) { + throw new InvalidDataException('The supplied iCalendar duration value is incorrect: ' . $duration); + } + + if (!$asString) { + + $invert = false; + + if ($matches['plusminus'] === '-') { + $invert = true; + } + + $parts = [ + 'week', + 'day', + 'hour', + 'minute', + 'second', + ]; + + foreach ($parts as $part) { + $matches[$part] = isset($matches[$part]) && $matches[$part] ? (int)$matches[$part] : 0; + } + + // We need to re-construct the $duration string, because weeks and + // days are not supported by DateInterval in the same string. + $duration = 'P'; + $days = $matches['day']; + + if ($matches['week']) { + $days += $matches['week'] * 7; + } + + if ($days) { + $duration .= $days . 'D'; + } + + if ($matches['minute'] || $matches['second'] || $matches['hour']) { + + $duration .= 'T'; + + if ($matches['hour']) { + $duration .= $matches['hour'] . 'H'; + } + + if ($matches['minute']) { + $duration .= $matches['minute'] . 'M'; + } + + if ($matches['second']) { + $duration .= $matches['second'] . 'S'; + } + + } + + if ($duration === 'P') { + $duration = 'PT0S'; + } + + $iv = new DateInterval($duration); + + if ($invert) { + $iv->invert = true; + } + + return $iv; + + } + + $parts = [ + 'week', + 'day', + 'hour', + 'minute', + 'second', + ]; + + $newDur = ''; + + foreach ($parts as $part) { + if (isset($matches[$part]) && $matches[$part]) { + $newDur .= ' ' . $matches[$part] . ' ' . $part . 's'; + } + } + + $newDur = ($matches['plusminus'] === '-' ? '-' : '+') . trim($newDur); + + if ($newDur === '+') { + $newDur = '+0 seconds'; + }; + + return $newDur; + + } + + /** + * Parses either a Date or DateTime, or Duration value. + * + * @param string $date + * @param DateTimeZone|string $referenceTz + * + * @return DateTimeImmutable|DateInterval + */ + static function parse($date, $referenceTz = null) { + + if ($date[0] === 'P' || ($date[0] === '-' && $date[1] === 'P')) { + return self::parseDuration($date); + } elseif (strlen($date) === 8) { + return self::parseDate($date, $referenceTz); + } else { + return self::parseDateTime($date, $referenceTz); + } + + } + + /** + * This method parses a vCard date and or time value. + * + * This can be used for the DATE, DATE-TIME, TIMESTAMP and + * DATE-AND-OR-TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * year, month, date, hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the year, etc. + * + * Timezone is either returned as 'Z' or as '+0800' + * + * For any non-specified values null is returned. + * + * List of date formats that are supported: + * YYYY + * YYYY-MM + * YYYYMMDD + * --MMDD + * ---DD + * + * YYYY-MM-DD + * --MM-DD + * ---DD + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format date-time string looks like : + * 20130603T133901 + * + * A full extended-format date-time string looks like : + * 2013-06-03T13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +1100. + * + * @param string $date + * + * @return array + */ + static function parseVCardDateTime($date) { + + $regex = '/^ + (?: # date part + (?: + (?: (?<year> [0-9]{4}) (?: -)?| --) + (?<month> [0-9]{2})? + |---) + (?<date> [0-9]{2})? + )? + (?:T # time part + (?<hour> [0-9]{2} | -) + (?<minute> [0-9]{2} | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: # date part + (?: (?<year> [0-9]{4}) - | -- ) + (?<month> [0-9]{2}) - + (?<date> [0-9]{2}) + )? + (?:T # time part + + (?: (?<hour> [0-9]{2}) : | -) + (?: (?<minute> [0-9]{2}) : | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidDataException('Invalid vCard date-time string: ' . $date); + } + + } + $parts = [ + 'year', + 'month', + 'date', + 'hour', + 'minute', + 'second', + 'timezone' + ]; + + $result = []; + foreach ($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-' || $matches[$part] === '--') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } + + /** + * This method parses a vCard TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the hour etc. + * + * Timezone is either returned as 'Z' or as '+08:00' + * + * For any non-specified values null is returned. + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format time string looks like : + * 133901 + * + * A full extended-format time string looks like : + * 13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +11:00. + * + * @param string $date + * + * @return array + */ + static function parseVCardTime($date) { + + $regex = '/^ + (?<hour> [0-9]{2} | -) + (?<minute> [0-9]{2} | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + $/x'; + + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: (?<hour> [0-9]{2}) : | -) + (?: (?<minute> [0-9]{2}) : | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidDataException('Invalid vCard time string: ' . $date); + } + + } + $parts = [ + 'hour', + 'minute', + 'second', + 'timezone' + ]; + + $result = []; + foreach ($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } + + /** + * This method parses a vCard date and or time value. + * + * This can be used for the DATE, DATE-TIME and + * DATE-AND-OR-TIME value. + * + * This method returns an array, not a DateTime value. + * The elements in the array are in the following order: + * year, month, date, hour, minute, second, timezone + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the year, etc. + * + * Timezone is either returned as 'Z' or as '+0800' + * + * For any non-specified values null is returned. + * + * List of date formats that are supported: + * 20150128 + * 2015-01 + * --01 + * --0128 + * ---28 + * + * List of supported time formats: + * 13 + * 1353 + * 135301 + * -53 + * -5301 + * --01 (unreachable, see the tests) + * --01Z + * --01+1234 + * + * List of supported date-time formats: + * 20150128T13 + * --0128T13 + * ---28T13 + * ---28T1353 + * ---28T135301 + * ---28T13Z + * ---28T13+1234 + * + * See the regular expressions for all the possible patterns. + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +1100. + * + * @param string $date + * + * @return array + */ + static function parseVCardDateAndOrTime($date) { + + // \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d + $valueDate = '/^(?J)(?:' . + '(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)' . + '|(?<year>\d{4})-(?<month>\d\d)' . + '|--(?<month>\d\d)(?<date>\d\d)?' . + '|---(?<date>\d\d)' . + ')$/'; + + // (\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)(Z|[+\-]\d\d(\d\d)?)? + $valueTime = '/^(?J)(?:' . + '((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' . + '|-(?<minute>\d\d)(?<second>\d\d)?' . + '|--(?<second>\d\d))' . + '(?<timezone>(Z|[+\-]\d\d(\d\d)?))?' . + ')$/'; + + // (\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?(Z|[+\-]\d\d(\d\d?)? + $valueDateTime = '/^(?:' . + '((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)' . + '|--(?<month1>\d\d)(?<date1>\d\d)' . + '|---(?<date2>\d\d))' . + 'T' . + '(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' . + '(?<timezone>(Z|[+\-]\d\d(\d\d?)))?' . + ')$/'; + + // date-and-or-time is date | date-time | time + // in this strict order. + + if (0 === preg_match($valueDate, $date, $matches) + && 0 === preg_match($valueDateTime, $date, $matches) + && 0 === preg_match($valueTime, $date, $matches)) { + throw new InvalidDataException('Invalid vCard date-time string: ' . $date); + } + + $parts = [ + 'year' => null, + 'month' => null, + 'date' => null, + 'hour' => null, + 'minute' => null, + 'second' => null, + 'timezone' => null + ]; + + // The $valueDateTime expression has a bug with (?J) so we simulate it. + $parts['date0'] = &$parts['date']; + $parts['date1'] = &$parts['date']; + $parts['date2'] = &$parts['date']; + $parts['month0'] = &$parts['month']; + $parts['month1'] = &$parts['month']; + $parts['year0'] = &$parts['year']; + + foreach ($parts as $part => &$value) { + if (!empty($matches[$part])) { + $value = $matches[$part]; + } + } + + unset($parts['date0']); + unset($parts['date1']); + unset($parts['date2']); + unset($parts['month0']); + unset($parts['month1']); + unset($parts['year0']); + + return $parts; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Document.php b/libs/composer/vendor/sabre/vobject/lib/Document.php new file mode 100644 index 000000000000..03252ab06a23 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Document.php @@ -0,0 +1,270 @@ +<?php + +namespace Sabre\VObject; + +/** + * Document. + * + * A document is just like a component, except that it's also the top level + * element. + * + * Both a VCALENDAR and a VCARD are considered documents. + * + * This class also provides a registry for document types. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Document extends Component { + + /** + * Unknown document type. + */ + const UNKNOWN = 1; + + /** + * vCalendar 1.0. + */ + const VCALENDAR10 = 2; + + /** + * iCalendar 2.0. + */ + const ICALENDAR20 = 3; + + /** + * vCard 2.1. + */ + const VCARD21 = 4; + + /** + * vCard 3.0. + */ + const VCARD30 = 5; + + /** + * vCard 4.0. + */ + const VCARD40 = 6; + + /** + * The default name for this component. + * + * This should be 'VCALENDAR' or 'VCARD'. + * + * @var string + */ + static $defaultName; + + /** + * List of properties, and which classes they map to. + * + * @var array + */ + static $propertyMap = []; + + /** + * List of components, along with which classes they map to. + * + * @var array + */ + static $componentMap = []; + + /** + * List of value-types, and which classes they map to. + * + * @var array + */ + static $valueMap = []; + + /** + * Creates a new document. + * + * We're changing the default behavior slightly here. First, we don't want + * to have to specify a name (we already know it), and we want to allow + * children to be specified in the first argument. + * + * But, the default behavior also works. + * + * So the two sigs: + * + * new Document(array $children = [], $defaults = true); + * new Document(string $name, array $children = [], $defaults = true) + * + * @return void + */ + function __construct() { + + $args = func_get_args(); + if (count($args) === 0 || is_array($args[0])) { + array_unshift($args, $this, static::$defaultName); + call_user_func_array(['parent', '__construct'], $args); + } else { + array_unshift($args, $this); + call_user_func_array(['parent', '__construct'], $args); + } + + } + + /** + * Returns the current document type. + * + * @return int + */ + function getDocumentType() { + + return self::UNKNOWN; + + } + + /** + * Creates a new component or property. + * + * If it's a known component, we will automatically call createComponent. + * otherwise, we'll assume it's a property and call createProperty instead. + * + * @param string $name + * @param string $arg1,... Unlimited number of args + * + * @return mixed + */ + function create($name) { + + if (isset(static::$componentMap[strtoupper($name)])) { + + return call_user_func_array([$this, 'createComponent'], func_get_args()); + + } else { + + return call_user_func_array([$this, 'createProperty'], func_get_args()); + + } + + } + + /** + * Creates a new component. + * + * This method automatically searches for the correct component class, based + * on its name. + * + * 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 string $name + * @param array $children + * @param bool $defaults + * + * @return Component + */ + function createComponent($name, array $children = null, $defaults = true) { + + $name = strtoupper($name); + $class = 'Sabre\\VObject\\Component'; + + if (isset(static::$componentMap[$name])) { + $class = static::$componentMap[$name]; + } + if (is_null($children)) $children = []; + return new $class($this, $name, $children, $defaults); + + } + + /** + * Factory method for creating new properties. + * + * This method automatically searches for the correct property class, based + * on its name. + * + * You can specify the parameters either in key=>value syntax, in which case + * parameters will automatically be created, or you can just pass a list of + * Parameter objects. + * + * @param string $name + * @param mixed $value + * @param array $parameters + * @param string $valueType Force a specific valuetype, such as URI or TEXT + * + * @return Property + */ + function createProperty($name, $value = null, array $parameters = null, $valueType = null) { + + // If there's a . in the name, it means it's prefixed by a groupname. + if (($i = strpos($name, '.')) !== false) { + $group = substr($name, 0, $i); + $name = strtoupper(substr($name, $i + 1)); + } else { + $name = strtoupper($name); + $group = null; + } + + $class = null; + + if ($valueType) { + // The valueType argument comes first to figure out the correct + // class. + $class = $this->getClassNameForPropertyValue($valueType); + } + + if (is_null($class)) { + // If a VALUE parameter is supplied, we should use that. + if (isset($parameters['VALUE'])) { + $class = $this->getClassNameForPropertyValue($parameters['VALUE']); + if (is_null($class)) { + throw new InvalidDataException('Unsupported VALUE parameter for ' . $name . ' property. You supplied "' . $parameters['VALUE'] . '"'); + } + } + else { + $class = $this->getClassNameForPropertyName($name); + } + } + if (is_null($parameters)) $parameters = []; + + return new $class($this, $name, $value, $parameters, $group); + + } + + /** + * This method returns a full class-name for a value parameter. + * + * For instance, DTSTART may have VALUE=DATE. In that case we will look in + * our valueMap table and return the appropriate class name. + * + * This method returns null if we don't have a specialized class. + * + * @param string $valueParam + * @return string|null + */ + function getClassNameForPropertyValue($valueParam) { + + $valueParam = strtoupper($valueParam); + if (isset(static::$valueMap[$valueParam])) { + return static::$valueMap[$valueParam]; + } + + } + + /** + * Returns the default class for a property name. + * + * @param string $propertyName + * + * @return string + */ + function getClassNameForPropertyName($propertyName) { + + if (isset(static::$propertyMap[$propertyName])) { + return static::$propertyMap[$propertyName]; + } else { + return 'Sabre\\VObject\\Property\\Unknown'; + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ElementList.php b/libs/composer/vendor/sabre/vobject/lib/ElementList.php new file mode 100644 index 000000000000..959249247020 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ElementList.php @@ -0,0 +1,54 @@ +<?php + +namespace Sabre\VObject; + +use ArrayIterator; +use LogicException; + +/** + * VObject ElementList. + * + * This class represents a list of elements. Lists are the result of queries, + * such as doing $vcalendar->vevent where there's multiple VEVENT objects. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ElementList extends ArrayIterator { + + + /* {{{ ArrayAccess Interface */ + + /** + * Sets an item through ArrayAccess. + * + * @param int $offset + * @param mixed $value + * + * @return void + */ + function offsetSet($offset, $value) { + + throw new LogicException('You can not add new objects to an ElementList'); + + } + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * + * @return void + */ + function offsetUnset($offset) { + + throw new LogicException('You can not remove objects from an ElementList'); + + } + + /* }}} */ + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/EofException.php b/libs/composer/vendor/sabre/vobject/lib/EofException.php new file mode 100644 index 000000000000..e9bd55878357 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/EofException.php @@ -0,0 +1,15 @@ +<?php + +namespace Sabre\VObject; + +/** + * Exception thrown by parser when the end of the stream has been reached, + * before this was expected. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class EofException extends ParseException { + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/FreeBusyData.php b/libs/composer/vendor/sabre/vobject/lib/FreeBusyData.php new file mode 100644 index 000000000000..0a6c72bb230a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/FreeBusyData.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\VObject; + +/** + * FreeBusyData is a helper class that manages freebusy information. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class FreeBusyData { + + /** + * Start timestamp + * + * @var int + */ + protected $start; + + /** + * End timestamp + * + * @var int + */ + protected $end; + + /** + * A list of free-busy times. + * + * @var array + */ + protected $data; + + function __construct($start, $end) { + + $this->start = $start; + $this->end = $end; + $this->data = []; + + $this->data[] = [ + 'start' => $this->start, + 'end' => $this->end, + 'type' => 'FREE', + ]; + + } + + /** + * Adds free or busytime to the data. + * + * @param int $start + * @param int $end + * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE + * @return void + */ + function add($start, $end, $type) { + + if ($start > $this->end || $end < $this->start) { + + // This new data is outside our timerange. + return; + + } + + if ($start < $this->start) { + // The item starts before our requested time range + $start = $this->start; + } + if ($end > $this->end) { + // The item ends after our requested time range + $end = $this->end; + } + + // Finding out where we need to insert the new item. + $currentIndex = 0; + while ($start > $this->data[$currentIndex]['end']) { + $currentIndex++; + } + + // The standard insertion point will be one _after_ the first + // overlapping item. + $insertStartIndex = $currentIndex + 1; + + $newItem = [ + 'start' => $start, + 'end' => $end, + 'type' => $type, + ]; + + $preceedingItem = $this->data[$insertStartIndex - 1]; + if ($this->data[$insertStartIndex - 1]['start'] === $start) { + // The old item starts at the exact same point as the new item. + $insertStartIndex--; + } + + // Now we know where to insert the item, we need to know where it + // starts overlapping with items on the tail end. We need to start + // looking one item before the insertStartIndex, because it's possible + // that the new item 'sits inside' the previous old item. + if ($insertStartIndex > 0) { + $currentIndex = $insertStartIndex - 1; + } else { + $currentIndex = 0; + } + + while ($end > $this->data[$currentIndex]['end']) { + + $currentIndex++; + + } + + // What we are about to insert into the array + $newItems = [ + $newItem + ]; + + // This is the amount of items that are completely overwritten by the + // new item. + $itemsToDelete = $currentIndex - $insertStartIndex; + if ($this->data[$currentIndex]['end'] <= $end) $itemsToDelete++; + + // If itemsToDelete was -1, it means that the newly inserted item is + // actually sitting inside an existing one. This means we need to split + // the item at the current position in two and insert the new item in + // between. + if ($itemsToDelete === -1) { + $itemsToDelete = 0; + if ($newItem['end'] < $preceedingItem['end']) { + $newItems[] = [ + 'start' => $newItem['end'] + 1, + 'end' => $preceedingItem['end'], + 'type' => $preceedingItem['type'] + ]; + } + } + + array_splice( + $this->data, + $insertStartIndex, + $itemsToDelete, + $newItems + ); + + $doMerge = false; + $mergeOffset = $insertStartIndex; + $mergeItem = $newItem; + $mergeDelete = 1; + + if (isset($this->data[$insertStartIndex - 1])) { + // Updating the start time of the previous item. + $this->data[$insertStartIndex - 1]['end'] = $start; + + // If the previous and the current are of the same type, we can + // merge them into one item. + if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) { + $doMerge = true; + $mergeOffset--; + $mergeDelete++; + $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start']; + } + } + if (isset($this->data[$insertStartIndex + 1])) { + // Updating the start time of the next item. + $this->data[$insertStartIndex + 1]['start'] = $end; + + // If the next and the current are of the same type, we can + // merge them into one item. + if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) { + $doMerge = true; + $mergeDelete++; + $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end']; + } + + } + if ($doMerge) { + array_splice( + $this->data, + $mergeOffset, + $mergeDelete, + [$mergeItem] + ); + } + + } + + function getData() { + + return $this->data; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/FreeBusyGenerator.php b/libs/composer/vendor/sabre/vobject/lib/FreeBusyGenerator.php new file mode 100644 index 000000000000..e30b136c43c1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/FreeBusyGenerator.php @@ -0,0 +1,604 @@ +<?php + +namespace Sabre\VObject; + +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Recur\NoInstancesException; + +/** + * This class helps with generating FREEBUSY reports based on existing sets of + * objects. + * + * It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and + * generates a single VFREEBUSY object. + * + * VFREEBUSY components are described in RFC5545, The rules for what should + * go in a single freebusy report is taken from RFC4791, section 7.10. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class FreeBusyGenerator { + + /** + * Input objects. + * + * @var array + */ + protected $objects = []; + + /** + * Start of range. + * + * @var DateTimeInterface|null + */ + protected $start; + + /** + * End of range. + * + * @var DateTimeInterface|null + */ + protected $end; + + /** + * VCALENDAR object. + * + * @var Document + */ + protected $baseObject; + + /** + * Reference timezone. + * + * When we are calculating busy times, and we come across so-called + * floating times (times without a timezone), we use the reference timezone + * instead. + * + * This is also used for all-day events. + * + * This defaults to UTC. + * + * @var DateTimeZone + */ + protected $timeZone; + + /** + * A VAVAILABILITY document. + * + * If this is set, it's information will be included when calculating + * freebusy time. + * + * @var Document + */ + protected $vavailability; + + /** + * Creates the generator. + * + * Check the setTimeRange and setObjects methods for details about the + * arguments. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * @param mixed $objects + * @param DateTimeZone $timeZone + */ + function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) { + + $this->setTimeRange($start, $end); + + if ($objects) { + $this->setObjects($objects); + } + if (is_null($timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->setTimeZone($timeZone); + + } + + /** + * Sets the VCALENDAR object. + * + * If this is set, it will not be generated for you. You are responsible + * for setting things like the METHOD, CALSCALE, VERSION, etc.. + * + * The VFREEBUSY object will be automatically added though. + * + * @param Document $vcalendar + * @return void + */ + function setBaseObject(Document $vcalendar) { + + $this->baseObject = $vcalendar; + + } + + /** + * Sets a VAVAILABILITY document. + * + * @param Document $vcalendar + * @return void + */ + function setVAvailability(Document $vcalendar) { + + $this->vavailability = $vcalendar; + + } + + /** + * Sets the input objects. + * + * You must either specify a valendar object as a string, or as the parse + * Component. + * It's also possible to specify multiple objects as an array. + * + * @param mixed $objects + * + * @return void + */ + function setObjects($objects) { + + if (!is_array($objects)) { + $objects = [$objects]; + } + + $this->objects = []; + foreach ($objects as $object) { + + if (is_string($object) || is_resource($object)) { + $this->objects[] = Reader::read($object); + } elseif ($object instanceof Component) { + $this->objects[] = $object; + } else { + throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); + } + + } + + } + + /** + * Sets the time range. + * + * Any freebusy object falling outside of this time range will be ignored. + * + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * + * @return void + */ + function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) { + + if (!$start) { + $start = new DateTimeImmutable(Settings::$minDate); + } + if (!$end) { + $end = new DateTimeImmutable(Settings::$maxDate); + } + $this->start = $start; + $this->end = $end; + + } + + /** + * Sets the reference timezone for floating times. + * + * @param DateTimeZone $timeZone + * + * @return void + */ + function setTimeZone(DateTimeZone $timeZone) { + + $this->timeZone = $timeZone; + + } + + /** + * Parses the input data and returns a correct VFREEBUSY object, wrapped in + * a VCALENDAR. + * + * @return Component + */ + function getResult() { + + $fbData = new FreeBusyData( + $this->start->getTimeStamp(), + $this->end->getTimeStamp() + ); + if ($this->vavailability) { + + $this->calculateAvailability($fbData, $this->vavailability); + + } + + $this->calculateBusy($fbData, $this->objects); + + return $this->generateFreeBusyCalendar($fbData); + + + } + + /** + * This method takes a VAVAILABILITY component and figures out all the + * available times. + * + * @param FreeBusyData $fbData + * @param VCalendar $vavailability + * @return void + */ + protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) { + + $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); + usort( + $vavailComps, + function($a, $b) { + + // We need to order the components by priority. Priority 1 + // comes first, up until priority 9. Priority 0 comes after + // priority 9. No priority implies priority 0. + // + // Yes, I'm serious. + $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0; + $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0; + + if ($priorityA === 0) $priorityA = 10; + if ($priorityB === 0) $priorityB = 10; + + return $priorityA - $priorityB; + + } + ); + + // Now we go over all the VAVAILABILITY components and figure if + // there's any we don't need to consider. + // + // This is can be because of one of two reasons: either the + // VAVAILABILITY component falls outside the time we are interested in, + // or a different VAVAILABILITY component with a higher priority has + // already completely covered the time-range. + $old = $vavailComps; + $new = []; + + foreach ($old as $vavail) { + + list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); + + // We don't care about datetimes that are earlier or later than the + // start and end of the freebusy report, so this gets normalized + // first. + if (is_null($compStart) || $compStart < $this->start) { + $compStart = $this->start; + } + if (is_null($compEnd) || $compEnd > $this->end) { + $compEnd = $this->end; + } + + // If the item fell out of the timerange, we can just skip it. + if ($compStart > $this->end || $compEnd < $this->start) { + continue; + } + + // Going through our existing list of components to see if there's + // a higher priority component that already fully covers this one. + foreach ($new as $higherVavail) { + + list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); + if ( + (is_null($higherStart) || $higherStart < $compStart) && + (is_null($higherEnd) || $higherEnd > $compEnd) + ) { + + // Component is fully covered by a higher priority + // component. We can skip this component. + continue 2; + + } + + } + + // We're keeping it! + $new[] = $vavail; + + } + + // Lastly, we need to traverse the remaining components and fill in the + // freebusydata slots. + // + // We traverse the components in reverse, because we want the higher + // priority components to override the lower ones. + foreach (array_reverse($new) as $vavail) { + + $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; + list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); + + // Making the component size no larger than the requested free-busy + // report range. + if (!$vavailStart || $vavailStart < $this->start) { + $vavailStart = $this->start; + } + if (!$vavailEnd || $vavailEnd > $this->end) { + $vavailEnd = $this->end; + } + + // Marking the entire time range of the VAVAILABILITY component as + // busy. + $fbData->add( + $vavailStart->getTimeStamp(), + $vavailEnd->getTimeStamp(), + $busyType + ); + + // Looping over the AVAILABLE components. + if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) { + + list($availStart, $availEnd) = $available->getEffectiveStartEnd(); + $fbData->add( + $availStart->getTimeStamp(), + $availEnd->getTimeStamp(), + 'FREE' + ); + + if ($available->RRULE) { + // Our favourite thing: recurrence!! + + $rruleIterator = new Recur\RRuleIterator( + $available->RRULE->getValue(), + $availStart + ); + $rruleIterator->fastForward($vavailStart); + + $startEndDiff = $availStart->diff($availEnd); + + while ($rruleIterator->valid()) { + + $recurStart = $rruleIterator->current(); + $recurEnd = $recurStart->add($startEndDiff); + + if ($recurStart > $vavailEnd) { + // We're beyond the legal timerange. + break; + } + + if ($recurEnd > $vavailEnd) { + // Truncating the end if it exceeds the + // VAVAILABILITY end. + $recurEnd = $vavailEnd; + } + + $fbData->add( + $recurStart->getTimeStamp(), + $recurEnd->getTimeStamp(), + 'FREE' + ); + + $rruleIterator->next(); + + } + } + + } + + } + + } + + /** + * This method takes an array of iCalendar objects and applies its busy + * times on fbData. + * + * @param FreeBusyData $fbData + * @param VCalendar[] $objects + */ + protected function calculateBusy(FreeBusyData $fbData, array $objects) { + + foreach ($objects as $key => $object) { + + foreach ($object->getBaseComponents() as $component) { + + switch ($component->name) { + + case 'VEVENT' : + + $FBTYPE = 'BUSY'; + if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { + break; + } + if (isset($component->STATUS)) { + $status = strtoupper($component->STATUS); + if ($status === 'CANCELLED') { + break; + } + if ($status === 'TENTATIVE') { + $FBTYPE = 'BUSY-TENTATIVE'; + } + } + + $times = []; + + if ($component->RRULE) { + try { + $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone); + } catch (NoInstancesException $e) { + // This event is recurring, but it doesn't have a single + // instance. We are skipping this event from the output + // entirely. + unset($this->objects[$key]); + continue; + } + + if ($this->start) { + $iterator->fastForward($this->start); + } + + $maxRecurrences = Settings::$maxRecurrences; + + while ($iterator->valid() && --$maxRecurrences) { + + $startTime = $iterator->getDTStart(); + if ($this->end && $startTime > $this->end) { + break; + } + $times[] = [ + $iterator->getDTStart(), + $iterator->getDTEnd(), + ]; + + $iterator->next(); + + } + + } else { + + $startTime = $component->DTSTART->getDateTime($this->timeZone); + if ($this->end && $startTime > $this->end) { + break; + } + $endTime = null; + if (isset($component->DTEND)) { + $endTime = $component->DTEND->getDateTime($this->timeZone); + } elseif (isset($component->DURATION)) { + $duration = DateTimeParser::parseDuration((string)$component->DURATION); + $endTime = clone $startTime; + $endTime = $endTime->add($duration); + } elseif (!$component->DTSTART->hasTime()) { + $endTime = clone $startTime; + $endTime = $endTime->modify('+1 day'); + } else { + // The event had no duration (0 seconds) + break; + } + + $times[] = [$startTime, $endTime]; + + } + + foreach ($times as $time) { + + if ($this->end && $time[0] > $this->end) break; + if ($this->start && $time[1] < $this->start) break; + + $fbData->add( + $time[0]->getTimeStamp(), + $time[1]->getTimeStamp(), + $FBTYPE + ); + } + break; + + case 'VFREEBUSY' : + foreach ($component->FREEBUSY as $freebusy) { + + $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; + + // Skipping intervals marked as 'free' + if ($fbType === 'FREE') + continue; + + $values = explode(',', $freebusy); + foreach ($values as $value) { + list($startTime, $endTime) = explode('/', $value); + $startTime = DateTimeParser::parseDateTime($startTime); + + if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') { + $duration = DateTimeParser::parseDuration($endTime); + $endTime = clone $startTime; + $endTime = $endTime->add($duration); + } else { + $endTime = DateTimeParser::parseDateTime($endTime); + } + + if ($this->start && $this->start > $endTime) continue; + if ($this->end && $this->end < $startTime) continue; + $fbData->add( + $startTime->getTimeStamp(), + $endTime->getTimeStamp(), + $fbType + ); + + } + + + } + break; + + } + + + } + + } + + } + + /** + * This method takes a FreeBusyData object and generates the VCALENDAR + * object associated with it. + * + * @return VCalendar + */ + protected function generateFreeBusyCalendar(FreeBusyData $fbData) { + + if ($this->baseObject) { + $calendar = $this->baseObject; + } else { + $calendar = new VCalendar(); + } + + $vfreebusy = $calendar->createComponent('VFREEBUSY'); + $calendar->add($vfreebusy); + + if ($this->start) { + $dtstart = $calendar->createProperty('DTSTART'); + $dtstart->setDateTime($this->start); + $vfreebusy->add($dtstart); + } + if ($this->end) { + $dtend = $calendar->createProperty('DTEND'); + $dtend->setDateTime($this->end); + $vfreebusy->add($dtend); + } + + $tz = new \DateTimeZone('UTC'); + $dtstamp = $calendar->createProperty('DTSTAMP'); + $dtstamp->setDateTime(new DateTimeImmutable('now', $tz)); + $vfreebusy->add($dtstamp); + + foreach ($fbData->getData() as $busyTime) { + + $busyType = strtoupper($busyTime['type']); + + // Ignoring all the FREE parts, because those are already assumed. + if ($busyType === 'FREE') { + continue; + } + + $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz); + $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz); + + $prop = $calendar->createProperty( + 'FREEBUSY', + $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') + ); + + // Only setting FBTYPE if it's not BUSY, because BUSY is the + // default anyway. + if ($busyType !== 'BUSY') { + $prop['FBTYPE'] = $busyType; + } + $vfreebusy->add($prop); + + } + + return $calendar; + + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ITip/Broker.php b/libs/composer/vendor/sabre/vobject/lib/ITip/Broker.php new file mode 100644 index 000000000000..b954cdc8db9a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ITip/Broker.php @@ -0,0 +1,1002 @@ +<?php + +namespace Sabre\VObject\ITip; + +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Reader; +use Sabre\VObject\Recur\EventIterator; + +/** + * The ITip\Broker class is a utility class that helps with processing + * so-called iTip messages. + * + * iTip is defined in rfc5546, stands for iCalendar Transport-Independent + * Interoperability Protocol, and describes the underlying mechanism for + * using iCalendar for scheduling for for example through email (also known as + * IMip) and CalDAV Scheduling. + * + * This class helps by: + * + * 1. Creating individual invites based on an iCalendar event for each + * attendee. + * 2. Generating invite updates based on an iCalendar update. This may result + * in new invites, updates and cancellations for attendees, if that list + * changed. + * 3. On the receiving end, it can create a local iCalendar event based on + * a received invite. + * 4. It can also process an invite update on a local event, ensuring that any + * overridden properties from attendees are retained. + * 5. It can create a accepted or declined iTip reply based on an invite. + * 6. It can process a reply from an invite and update an events attendee + * status based on a reply. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Broker { + + /** + * This setting determines whether the rules for the SCHEDULE-AGENT + * parameter should be followed. + * + * This is a parameter defined on ATTENDEE properties, introduced by RFC + * 6638. This parameter allows a caldav client to tell the server 'Don't do + * any scheduling operations'. + * + * If this setting is turned on, any attendees with SCHEDULE-AGENT set to + * CLIENT will be ignored. This is the desired behavior for a CalDAV + * server, but if you're writing an iTip application that doesn't deal with + * CalDAV, you may want to ignore this parameter. + * + * @var bool + */ + public $scheduleAgentServerRules = true; + + /** + * The broker will try during 'parseEvent' figure out whether the change + * was significant. + * + * It uses a few different ways to do this. One of these ways is seeing if + * certain properties changed values. This list of specified here. + * + * This list is taken from: + * * http://tools.ietf.org/html/rfc5546#section-2.1.4 + * + * @var string[] + */ + public $significantChangeProperties = [ + 'DTSTART', + 'DTEND', + 'DURATION', + 'DUE', + 'RRULE', + 'RDATE', + 'EXDATE', + 'STATUS', + ]; + + /** + * This method is used to process an incoming itip message. + * + * Examples: + * + * 1. A user is an attendee to an event. The organizer sends an updated + * meeting using a new iTip message with METHOD:REQUEST. This function + * will process the message and update the attendee's event accordingly. + * + * 2. The organizer cancelled the event using METHOD:CANCEL. We will update + * the users event to state STATUS:CANCELLED. + * + * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can + * update the organizers event to update the ATTENDEE with its correct + * PARTSTAT. + * + * The $existingObject is updated in-place. If there is no existing object + * (because it's a new invite for example) a new object will be created. + * + * If an existing object does not exist, and the method was CANCEL or + * REPLY, the message effectively gets ignored, and no 'existingObject' + * will be created. + * + * The updated $existingObject is also returned from this function. + * + * If the iTip message was not supported, we will always return false. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * + * @return VCalendar|null + */ + function processMessage(Message $itipMessage, VCalendar $existingObject = null) { + + // We only support events at the moment. + if ($itipMessage->component !== 'VEVENT') { + return false; + } + + switch ($itipMessage->method) { + + case 'REQUEST' : + return $this->processMessageRequest($itipMessage, $existingObject); + + case 'CANCEL' : + return $this->processMessageCancel($itipMessage, $existingObject); + + case 'REPLY' : + return $this->processMessageReply($itipMessage, $existingObject); + + default : + // Unsupported iTip message + return; + + } + + return $existingObject; + + } + + /** + * This function parses a VCALENDAR object and figure out if any messages + * need to be sent. + * + * A VCALENDAR object will be created from the perspective of either an + * attendee, or an organizer. You must pass a string identifying the + * current user, so we can figure out who in the list of attendees or the + * organizer we are sending this message on behalf of. + * + * It's possible to specify the current user as an array, in case the user + * has more than one identifying href (such as multiple emails). + * + * It $oldCalendar is specified, it is assumed that the operation is + * updating an existing event, which means that we need to look at the + * differences between events, and potentially send old attendees + * cancellations, and current attendees updates. + * + * If $calendar is null, but $oldCalendar is specified, we treat the + * operation as if the user has deleted an event. If the user was an + * organizer, this means that we need to send cancellation notices to + * people. If the user was an attendee, we need to make sure that the + * organizer gets the 'declined' message. + * + * @param VCalendar|string $calendar + * @param string|array $userHref + * @param VCalendar|string $oldCalendar + * + * @return array + */ + function parseEvent($calendar = null, $userHref, $oldCalendar = null) { + + if ($oldCalendar) { + if (is_string($oldCalendar)) { + $oldCalendar = Reader::read($oldCalendar); + } + if (!isset($oldCalendar->VEVENT)) { + // We only support events at the moment + return []; + } + + $oldEventInfo = $this->parseEventInfo($oldCalendar); + } else { + $oldEventInfo = [ + 'organizer' => null, + 'significantChangeHash' => '', + 'attendees' => [], + ]; + } + + $userHref = (array)$userHref; + + if (!is_null($calendar)) { + + if (is_string($calendar)) { + $calendar = Reader::read($calendar); + } + if (!isset($calendar->VEVENT)) { + // We only support events at the moment + return []; + } + $eventInfo = $this->parseEventInfo($calendar); + if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) { + // If there were no attendees on either side of the equation, + // we don't need to do anything. + return []; + } + if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) { + // There was no organizer before or after the change. + return []; + } + + $baseCalendar = $calendar; + + // If the new object didn't have an organizer, the organizer + // changed the object from a scheduling object to a non-scheduling + // object. We just copy the info from the old object. + if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) { + $eventInfo['organizer'] = $oldEventInfo['organizer']; + $eventInfo['organizerName'] = $oldEventInfo['organizerName']; + } + + } else { + // The calendar object got deleted, we need to process this as a + // cancellation / decline. + if (!$oldCalendar) { + // No old and no new calendar, there's no thing to do. + return []; + } + + $eventInfo = $oldEventInfo; + + if (in_array($eventInfo['organizer'], $userHref)) { + // This is an organizer deleting the event. + $eventInfo['attendees'] = []; + // Increasing the sequence, but only if the organizer deleted + // the event. + $eventInfo['sequence']++; + } else { + // This is an attendee deleting the event. + foreach ($eventInfo['attendees'] as $key => $attendee) { + if (in_array($attendee['href'], $userHref)) { + $eventInfo['attendees'][$key]['instances'] = ['master' => + ['id' => 'master', 'partstat' => 'DECLINED'] + ]; + } + } + } + $baseCalendar = $oldCalendar; + + } + + if (in_array($eventInfo['organizer'], $userHref)) { + return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); + } elseif ($oldCalendar) { + // We need to figure out if the user is an attendee, but we're only + // doing so if there's an oldCalendar, because we only want to + // process updates, not creation of new events. + foreach ($eventInfo['attendees'] as $attendee) { + if (in_array($attendee['href'], $userHref)) { + return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); + } + } + } + return []; + + } + + /** + * Processes incoming REQUEST messages. + * + * This is message from an organizer, and is either a new event + * invite, or an update to an existing one. + * + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * + * @return VCalendar|null + */ + protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // This is a new invite, and we're just going to copy over + // all the components from the invite. + $existingObject = new VCalendar(); + foreach ($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } else { + // We need to update an existing object with all the new + // information. We can just remove all existing components + // and create new ones. + foreach ($existingObject->getComponents() as $component) { + $existingObject->remove($component); + } + foreach ($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } + return $existingObject; + + } + + /** + * Processes incoming CANCEL messages. + * + * This is a message from an organizer, and means that either an + * attendee got removed from an event, or an event got cancelled + * altogether. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * + * @return VCalendar|null + */ + protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // The event didn't exist in the first place, so we're just + // ignoring this message. + } else { + foreach ($existingObject->VEVENT as $vevent) { + $vevent->STATUS = 'CANCELLED'; + $vevent->SEQUENCE = $itipMessage->sequence; + } + } + return $existingObject; + + } + + /** + * Processes incoming REPLY messages. + * + * The message is a reply. This is for example an attendee telling + * an organizer he accepted the invite, or declined it. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * + * @return VCalendar|null + */ + protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) { + + // A reply can only be processed based on an existing object. + // If the object is not available, the reply is ignored. + if (!$existingObject) { + return; + } + $instances = []; + $requestStatus = '2.0'; + + // Finding all the instances the attendee replied to. + foreach ($itipMessage->message->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + $attendee = $vevent->ATTENDEE; + $instances[$recurId] = $attendee['PARTSTAT']->getValue(); + if (isset($vevent->{'REQUEST-STATUS'})) { + $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); + list($requestStatus) = explode(';', $requestStatus); + } + } + + // Now we need to loop through the original organizer event, to find + // all the instances where we have a reply for. + $masterObject = null; + foreach ($existingObject->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + if ($recurId === 'master') { + $masterObject = $vevent; + } + if (isset($instances[$recurId])) { + $attendeeFound = false; + if (isset($vevent->ATTENDEE)) { + foreach ($vevent->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $instances[$recurId]; + $attendee['SCHEDULE-STATUS'] = $requestStatus; + // Un-setting the RSVP status, because we now know + // that the attendee already replied. + unset($attendee['RSVP']); + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee. The iTip documentation calls this + // a party crasher. + $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [ + 'PARTSTAT' => $instances[$recurId] + ]); + if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName; + } + unset($instances[$recurId]); + } + } + + if (!$masterObject) { + // No master object, we can't add new instances. + return; + } + // If we got replies to instances that did not exist in the + // original list, it means that new exceptions must be created. + foreach ($instances as $recurId => $partstat) { + + $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); + $found = false; + $iterations = 1000; + do { + + $newObject = $recurrenceIterator->getEventObject(); + $recurrenceIterator->next(); + + if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) { + $found = true; + } + $iterations--; + + } while ($recurrenceIterator->valid() && !$found && $iterations); + + // Invalid recurrence id. Skipping this object. + if (!$found) continue; + + unset( + $newObject->RRULE, + $newObject->EXDATE, + $newObject->RDATE + ); + $attendeeFound = false; + if (isset($newObject->ATTENDEE)) { + foreach ($newObject->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $partstat; + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee + $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [ + 'PARTSTAT' => $partstat + ]); + if ($itipMessage->senderName) { + $attendee['CN'] = $itipMessage->senderName; + } + } + $existingObject->add($newObject); + + } + return $existingObject; + + } + + /** + * This method is used in cases where an event got updated, and we + * potentially need to send emails to attendees to let them know of updates + * in the events. + * + * We will detect which attendees got added, which got removed and create + * specific messages for these situations. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * + * @return array + */ + protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) { + + // Merging attendee lists. + $attendees = []; + foreach ($oldEventInfo['attendees'] as $attendee) { + $attendees[$attendee['href']] = [ + 'href' => $attendee['href'], + 'oldInstances' => $attendee['instances'], + 'newInstances' => [], + 'name' => $attendee['name'], + 'forceSend' => null, + ]; + } + foreach ($eventInfo['attendees'] as $attendee) { + if (isset($attendees[$attendee['href']])) { + $attendees[$attendee['href']]['name'] = $attendee['name']; + $attendees[$attendee['href']]['newInstances'] = $attendee['instances']; + $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; + } else { + $attendees[$attendee['href']] = [ + 'href' => $attendee['href'], + 'oldInstances' => [], + 'newInstances' => $attendee['instances'], + 'name' => $attendee['name'], + 'forceSend' => $attendee['forceSend'], + ]; + } + } + + $messages = []; + + foreach ($attendees as $attendee) { + + // An organizer can also be an attendee. We should not generate any + // messages for those. + if ($attendee['href'] === $eventInfo['organizer']) { + continue; + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $eventInfo['organizer']; + $message->senderName = $eventInfo['organizerName']; + $message->recipient = $attendee['href']; + $message->recipientName = $attendee['name']; + + if (!$attendee['newInstances']) { + + // If there are no instances the attendee is a part of, it + // means the attendee was removed and we need to send him a + // CANCEL. + $message->method = 'CANCEL'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + $event = $icalMsg->add('VEVENT', [ + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + ]); + if (isset($calendar->VEVENT->SUMMARY)) { + $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue()); + } + $event->add(clone $calendar->VEVENT->DTSTART); + if (isset($calendar->VEVENT->DTEND)) { + $event->add(clone $calendar->VEVENT->DTEND); + } elseif (isset($calendar->VEVENT->DURATION)) { + $event->add(clone $calendar->VEVENT->DURATION); + } + $org = $event->add('ORGANIZER', $eventInfo['organizer']); + if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName']; + $event->add('ATTENDEE', $attendee['href'], [ + 'CN' => $attendee['name'], + ]); + $message->significantChange = true; + + } else { + + // The attendee gets the updated event body + $message->method = 'REQUEST'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + + foreach ($calendar->select('VTIMEZONE') as $timezone) { + $icalMsg->add(clone $timezone); + } + + // We need to find out that this change is significant. If it's + // not, systems may opt to not send messages. + // + // We do this based on the 'significantChangeHash' which is + // some value that changes if there's a certain set of + // properties changed in the event, or simply if there's a + // difference in instances that the attendee is invited to. + + $message->significantChange = + $attendee['forceSend'] === 'REQUEST' || + array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || + $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash']; + + foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) { + + $currentEvent = clone $eventInfo['instances'][$instanceId]; + if ($instanceId === 'master') { + + // We need to find a list of events that the attendee + // is not a part of to add to the list of exceptions. + $exceptions = []; + foreach ($eventInfo['instances'] as $instanceId => $vevent) { + if (!isset($attendee['newInstances'][$instanceId])) { + $exceptions[] = $instanceId; + } + } + + // If there were exceptions, we need to add it to an + // existing EXDATE property, if it exists. + if ($exceptions) { + if (isset($currentEvent->EXDATE)) { + $currentEvent->EXDATE->setParts(array_merge( + $currentEvent->EXDATE->getParts(), + $exceptions + )); + } else { + $currentEvent->EXDATE = $exceptions; + } + } + + // Cleaning up any scheduling information that + // shouldn't be sent along. + unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']); + unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']); + + foreach ($currentEvent->ATTENDEE as $attendee) { + unset($attendee['SCHEDULE-FORCE-SEND']); + unset($attendee['SCHEDULE-STATUS']); + + // We're adding PARTSTAT=NEEDS-ACTION to ensure that + // iOS shows an "Inbox Item" + if (!isset($attendee['PARTSTAT'])) { + $attendee['PARTSTAT'] = 'NEEDS-ACTION'; + } + + } + + } + + $icalMsg->add($currentEvent); + + } + + } + + $message->message = $icalMsg; + $messages[] = $message; + + } + + return $messages; + + } + + /** + * Parse an event update for an attendee. + * + * This function figures out if we need to send a reply to an organizer. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * @param string $attendee + * + * @return Message[] + */ + protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { + + if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') { + return []; + } + + // Don't bother generating messages for events that have already been + // cancelled. + if ($eventInfo['status'] === 'CANCELLED') { + return []; + } + + $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? + $oldEventInfo['attendees'][$attendee]['instances'] : + []; + + $instances = []; + foreach ($oldInstances as $instance) { + + $instances[$instance['id']] = [ + 'id' => $instance['id'], + 'oldstatus' => $instance['partstat'], + 'newstatus' => null, + ]; + + } + foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) { + + if (isset($instances[$instance['id']])) { + $instances[$instance['id']]['newstatus'] = $instance['partstat']; + } else { + $instances[$instance['id']] = [ + 'id' => $instance['id'], + 'oldstatus' => null, + 'newstatus' => $instance['partstat'], + ]; + } + + } + + // We need to also look for differences in EXDATE. If there are new + // items in EXDATE, it means that an attendee deleted instances of an + // event, which means we need to send DECLINED specifically for those + // instances. + // We only need to do that though, if the master event is not declined. + if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') { + foreach ($eventInfo['exdate'] as $exDate) { + + if (!in_array($exDate, $oldEventInfo['exdate'])) { + if (isset($instances[$exDate])) { + $instances[$exDate]['newstatus'] = 'DECLINED'; + } else { + $instances[$exDate] = [ + 'id' => $exDate, + 'oldstatus' => null, + 'newstatus' => 'DECLINED', + ]; + } + } + + } + } + + // Gathering a few extra properties for each instance. + foreach ($instances as $recurId => $instanceInfo) { + + if (isset($eventInfo['instances'][$recurId])) { + $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; + } else { + $instances[$recurId]['dtstart'] = $recurId; + } + + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->method = 'REPLY'; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $attendee; + $message->senderName = $eventInfo['attendees'][$attendee]['name']; + $message->recipient = $eventInfo['organizer']; + $message->recipientName = $eventInfo['organizerName']; + + $icalMsg = new VCalendar(); + $icalMsg->METHOD = 'REPLY'; + + $hasReply = false; + + foreach ($instances as $instance) { + + if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') { + // Skip + continue; + } + + $event = $icalMsg->add('VEVENT', [ + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + ]); + $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : ''; + // Adding properties from the correct source instance + if (isset($eventInfo['instances'][$instance['id']])) { + $instanceObj = $eventInfo['instances'][$instance['id']]; + $event->add(clone $instanceObj->DTSTART); + if (isset($instanceObj->DTEND)) { + $event->add(clone $instanceObj->DTEND); + } elseif (isset($instanceObj->DURATION)) { + $event->add(clone $instanceObj->DURATION); + } + if (isset($instanceObj->SUMMARY)) { + $event->add('SUMMARY', $instanceObj->SUMMARY->getValue()); + } elseif ($summary) { + $event->add('SUMMARY', $summary); + } + } else { + // This branch of the code is reached, when a reply is + // generated for an instance of a recurring event, through the + // fact that the instance has disappeared by showing up in + // EXDATE + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $event->add('DTSTART', $dt, ['VALUE' => 'DATE']); + } else { + $event->add('DTSTART', $dt); + } + if ($summary) { + $event->add('SUMMARY', $summary); + } + } + if ($instance['id'] !== 'master') { + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']); + } else { + $event->add('RECURRENCE-ID', $dt); + } + } + $organizer = $event->add('ORGANIZER', $message->recipient); + if ($message->recipientName) { + $organizer['CN'] = $message->recipientName; + } + $attendee = $event->add('ATTENDEE', $message->sender, [ + 'PARTSTAT' => $instance['newstatus'] + ]); + if ($message->senderName) { + $attendee['CN'] = $message->senderName; + } + $hasReply = true; + + } + + if ($hasReply) { + $message->message = $icalMsg; + return [$message]; + } else { + return []; + } + + } + + /** + * Returns attendee information and information about instances of an + * event. + * + * Returns an array with the following keys: + * + * 1. uid + * 2. organizer + * 3. organizerName + * 4. organizerScheduleAgent + * 5. organizerForceSend + * 6. instances + * 7. attendees + * 8. sequence + * 9. exdate + * 10. timezone - strictly the timezone on which the recurrence rule is + * based on. + * 11. significantChangeHash + * 12. status + * @param VCalendar $calendar + * + * @return array + */ + protected function parseEventInfo(VCalendar $calendar = null) { + + $uid = null; + $organizer = null; + $organizerName = null; + $organizerForceSend = null; + $sequence = null; + $timezone = null; + $status = null; + $organizerScheduleAgent = 'SERVER'; + + $significantChangeHash = ''; + + // Now we need to collect a list of attendees, and which instances they + // are a part of. + $attendees = []; + + $instances = []; + $exdate = []; + + foreach ($calendar->VEVENT as $vevent) { + $rrule = []; + + if (is_null($uid)) { + $uid = $vevent->UID->getValue(); + } else { + if ($uid !== $vevent->UID->getValue()) { + throw new ITipException('If a calendar contained more than one event, they must have the same UID.'); + } + } + + if (!isset($vevent->DTSTART)) { + throw new ITipException('An event MUST have a DTSTART property.'); + } + + if (isset($vevent->ORGANIZER)) { + if (is_null($organizer)) { + $organizer = $vevent->ORGANIZER->getNormalizedValue(); + $organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null; + } else { + if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) { + throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); + } + } + $organizerForceSend = + isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ? + strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) : + null; + $organizerScheduleAgent = + isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? + strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) : + 'SERVER'; + } + if (is_null($sequence) && isset($vevent->SEQUENCE)) { + $sequence = $vevent->SEQUENCE->getValue(); + } + if (isset($vevent->EXDATE)) { + foreach ($vevent->select('EXDATE') as $val) { + $exdate = array_merge($exdate, $val->getParts()); + } + sort($exdate); + } + if (isset($vevent->RRULE)) { + foreach ($vevent->select('RRULE') as $rr) { + foreach ($rr->getParts() as $key => $val) { + // ignore default values (https://github.com/sabre-io/vobject/issues/126) + if ($key === 'INTERVAL' && $val == 1) { + continue; + } + if (is_array($val)) { + $val = implode(',', $val); + } + $rrule[] = "$key=$val"; + } + } + sort($rrule); + } + if (isset($vevent->STATUS)) { + $status = strtoupper($vevent->STATUS->getValue()); + } + + $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; + if (is_null($timezone)) { + if ($recurId === 'master') { + $timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); + } else { + $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone(); + } + } + if (isset($vevent->ATTENDEE)) { + foreach ($vevent->ATTENDEE as $attendee) { + + if ($this->scheduleAgentServerRules && + isset($attendee['SCHEDULE-AGENT']) && + strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT' + ) { + continue; + } + $partStat = + isset($attendee['PARTSTAT']) ? + strtoupper($attendee['PARTSTAT']) : + 'NEEDS-ACTION'; + + $forceSend = + isset($attendee['SCHEDULE-FORCE-SEND']) ? + strtoupper($attendee['SCHEDULE-FORCE-SEND']) : + null; + + + if (isset($attendees[$attendee->getNormalizedValue()])) { + $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [ + 'id' => $recurId, + 'partstat' => $partStat, + 'forceSend' => $forceSend, + ]; + } else { + $attendees[$attendee->getNormalizedValue()] = [ + 'href' => $attendee->getNormalizedValue(), + 'instances' => [ + $recurId => [ + 'id' => $recurId, + 'partstat' => $partStat, + ], + ], + 'name' => isset($attendee['CN']) ? (string)$attendee['CN'] : null, + 'forceSend' => $forceSend, + ]; + } + + } + $instances[$recurId] = $vevent; + + } + + foreach ($this->significantChangeProperties as $prop) { + if (isset($vevent->$prop)) { + $propertyValues = $vevent->select($prop); + + $significantChangeHash .= $prop . ':'; + + if ($prop === 'EXDATE') { + $significantChangeHash .= implode(',', $exdate) . ';'; + } elseif ($prop === 'RRULE') { + $significantChangeHash .= implode(',', $rrule) . ';'; + } else { + foreach ($propertyValues as $val) { + $significantChangeHash .= $val->getValue() . ';'; + } + } + } + } + } + $significantChangeHash = md5($significantChangeHash); + + return compact( + 'uid', + 'organizer', + 'organizerName', + 'organizerScheduleAgent', + 'organizerForceSend', + 'instances', + 'attendees', + 'sequence', + 'exdate', + 'timezone', + 'significantChangeHash', + 'status' + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ITip/ITipException.php b/libs/composer/vendor/sabre/vobject/lib/ITip/ITipException.php new file mode 100644 index 000000000000..ad5e53ab4ae0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ITip/ITipException.php @@ -0,0 +1,15 @@ +<?php + +namespace Sabre\VObject\ITip; + +use Exception; + +/** + * This message is emitted in case of serious problems with iTip messages. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ITipException extends Exception { +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ITip/Message.php b/libs/composer/vendor/sabre/vobject/lib/ITip/Message.php new file mode 100644 index 000000000000..bebe2e4fc174 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ITip/Message.php @@ -0,0 +1,141 @@ +<?php + +namespace Sabre\VObject\ITip; + +/** + * This class represents an iTip message. + * + * A message holds all the information relevant to the message, including the + * object itself. + * + * It should for the most part be treated as immutable. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Message { + + /** + * The object's UID. + * + * @var string + */ + public $uid; + + /** + * The component type, such as VEVENT. + * + * @var string + */ + public $component; + + /** + * Contains the ITip method, which is something like REQUEST, REPLY or + * CANCEL. + * + * @var string + */ + public $method; + + /** + * The current sequence number for the event. + * + * @var int + */ + public $sequence; + + /** + * The senders' email address. + * + * Note that this does not imply that this has to be used in a From: field + * if the message is sent by email. It may also be populated in Reply-To: + * or not at all. + * + * @var string + */ + public $sender; + + /** + * The name of the sender. This is often populated from a CN parameter from + * either the ORGANIZER or ATTENDEE, depending on the message. + * + * @var string|null + */ + public $senderName; + + /** + * The recipient's email address. + * + * @var string + */ + public $recipient; + + /** + * The name of the recipient. This is usually populated with the CN + * parameter from the ATTENDEE or ORGANIZER property, if it's available. + * + * @var string|null + */ + public $recipientName; + + /** + * After the message has been delivered, this should contain a string such + * as : 1.1;Sent or 1.2;Delivered. + * + * In case of a failure, this will hold the error status code. + * + * See: + * http://tools.ietf.org/html/rfc6638#section-7.3 + * + * @var string + */ + public $scheduleStatus; + + /** + * The iCalendar / iTip body. + * + * @var \Sabre\VObject\Component\VCalendar + */ + public $message; + + /** + * This will be set to true, if the iTip broker considers the change + * 'significant'. + * + * In practice, this means that we'll only mark it true, if for instance + * DTSTART changed. This allows systems to only send iTip messages when + * significant changes happened. This is especially useful for iMip, as + * normally a ton of messages may be generated for normal calendar use. + * + * To see the list of properties that are considered 'significant', check + * out Sabre\VObject\ITip\Broker::$significantChangeProperties. + * + * @var bool + */ + public $significantChange = true; + + /** + * Returns the schedule status as a string. + * + * For example: + * 1.2 + * + * @return mixed bool|string + */ + function getScheduleStatus() { + + if (!$this->scheduleStatus) { + + return false; + + } else { + + list($scheduleStatus) = explode(';', $this->scheduleStatus); + return $scheduleStatus; + + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php b/libs/composer/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php new file mode 100644 index 000000000000..423b39831724 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php @@ -0,0 +1,18 @@ +<?php + +namespace Sabre\VObject\ITip; + +/** + * SameOrganizerForAllComponentsException. + * + * This exception is emitted when an event is encountered with more than one + * component (e.g.: exceptions), but the organizer is not identical in every + * component. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SameOrganizerForAllComponentsException extends ITipException { + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/InvalidDataException.php b/libs/composer/vendor/sabre/vobject/lib/InvalidDataException.php new file mode 100644 index 000000000000..50ebc0f49841 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/InvalidDataException.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\VObject; + +/** + * This exception is thrown whenever an invalid value is found anywhere in a + * iCalendar or vCard object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class InvalidDataException extends \Exception { +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Node.php b/libs/composer/vendor/sabre/vobject/lib/Node.php new file mode 100644 index 000000000000..e2845da75f7f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Node.php @@ -0,0 +1,265 @@ +<?php + +namespace Sabre\VObject; + +use Sabre\Xml; + +/** + * A node is the root class for every element in an iCalendar of vCard object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Node + implements \IteratorAggregate, + \ArrayAccess, + \Countable, + \JsonSerializable, + Xml\XmlSerializable { + + /** + * The following constants are used by the validate() method. + * + * If REPAIR is set, the validator will attempt to repair any broken data + * (if possible). + */ + const REPAIR = 1; + + /** + * If this option is set, the validator will operate on the vcards on the + * assumption that the vcards need to be valid for CardDAV. + * + * This means for example that the UID is required, whereas it is not for + * regular vcards. + */ + const PROFILE_CARDDAV = 2; + + /** + * If this option is set, the validator will operate on iCalendar objects + * on the assumption that the vcards need to be valid for CalDAV. + * + * This means for example that calendars can only contain objects with + * identical component types and UIDs. + */ + const PROFILE_CALDAV = 4; + + /** + * Reference to the parent object, if this is not the top object. + * + * @var Node + */ + public $parent; + + /** + * Iterator override. + * + * @var ElementList + */ + protected $iterator = null; + + /** + * The root document. + * + * @var Component + */ + protected $root; + + /** + * Serializes the node into a mimedir format. + * + * @return string + */ + abstract function serialize(); + + /** + * 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 + */ + abstract function jsonSerialize(); + + /** + * This method serializes the data into XML. This is used to create xCard or + * xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + abstract function xmlSerialize(Xml\Writer $writer); + + /** + * 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() { + + $this->parent = null; + $this->root = null; + + } + + /* {{{ IteratorAggregator interface */ + + /** + * Returns the iterator for this object. + * + * @return ElementList + */ + function getIterator() { + + if (!is_null($this->iterator)) { + return $this->iterator; + } + + return new ElementList([$this]); + + } + + /** + * Sets the overridden iterator. + * + * Note that this is not actually part of the iterator interface + * + * @param ElementList $iterator + * + * @return void + */ + function setIterator(ElementList $iterator) { + + $this->iterator = $iterator; + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + return []; + + } + + /* }}} */ + + /* {{{ Countable interface */ + + /** + * Returns the number of elements. + * + * @return int + */ + function count() { + + $it = $this->getIterator(); + return $it->count(); + + } + + /* }}} */ + + /* {{{ ArrayAccess Interface */ + + + /** + * Checks if an item exists through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * + * @return bool + */ + function offsetExists($offset) { + + $iterator = $this->getIterator(); + return $iterator->offsetExists($offset); + + } + + /** + * Gets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * + * @return mixed + */ + function offsetGet($offset) { + + $iterator = $this->getIterator(); + return $iterator->offsetGet($offset); + + } + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * @param mixed $value + * + * @return void + */ + function offsetSet($offset, $value) { + + $iterator = $this->getIterator(); + $iterator->offsetSet($offset, $value); + + // @codeCoverageIgnoreStart + // + // This method always throws an exception, so we ignore the closing + // brace + } + // @codeCoverageIgnoreEnd + + /** + * Sets an item through ArrayAccess. + * + * This method just forwards the request to the inner iterator + * + * @param int $offset + * + * @return void + */ + function offsetUnset($offset) { + + $iterator = $this->getIterator(); + $iterator->offsetUnset($offset); + + // @codeCoverageIgnoreStart + // + // This method always throws an exception, so we ignore the closing + // brace + } + // @codeCoverageIgnoreEnd + + /* }}} */ +} diff --git a/libs/composer/vendor/sabre/vobject/lib/PHPUnitAssertions.php b/libs/composer/vendor/sabre/vobject/lib/PHPUnitAssertions.php new file mode 100644 index 000000000000..87ec75e8f5fb --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/PHPUnitAssertions.php @@ -0,0 +1,82 @@ +<?php + +namespace Sabre\VObject; + +/** + * PHPUnit Assertions + * + * This trait can be added to your unittest to make it easier to test iCalendar + * and/or vCards. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +trait PHPUnitAssertions { + + /** + * This method tests wether two vcards or icalendar objects are + * semantically identical. + * + * It supports objects being supplied as strings, streams or + * Sabre\VObject\Component instances. + * + * PRODID is removed from both objects as this is often changes and would + * just get in the way. + * + * CALSCALE will automatically get removed if it's set to GREGORIAN. + * + * Any property that has the value **ANY** will be treated as a wildcard. + * + * @param resource|string|Component $expected + * @param resource|string|Component $actual + * @param string $message + */ + function assertVObjectEqualsVObject($expected, $actual, $message = '') { + + $self = $this; + $getObj = function($input) use ($self) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + if (is_string($input)) { + $input = Reader::read($input); + } + if (!$input instanceof Component) { + $this->fail('Input must be a string, stream or VObject component'); + } + unset($input->PRODID); + if ($input instanceof Component\VCalendar && (string)$input->CALSCALE === 'GREGORIAN') { + unset($input->CALSCALE); + } + return $input; + + }; + + $expected = $getObj($expected)->serialize(); + $actual = $getObj($actual)->serialize(); + + // Finding wildcards in expected. + preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + + $actual = preg_replace( + '|^' . preg_quote($match[1], '|') . ':(.*)\r$|m', + $match[1] . ':**ANY**' . "\r", + $actual + ); + + } + + $this->assertEquals( + $expected, + $actual, + $message + ); + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parameter.php b/libs/composer/vendor/sabre/vobject/lib/Parameter.php new file mode 100644 index 000000000000..a99a33eec0ea --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parameter.php @@ -0,0 +1,394 @@ +<?php + +namespace Sabre\VObject; + +use ArrayIterator; +use Sabre\Xml; + +/** + * VObject Parameter. + * + * This class represents a parameter. A parameter is always tied to a property. + * In the case of: + * DTSTART;VALUE=DATE:20101108 + * VALUE=DATE would be the parameter name and value. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Parameter extends Node { + + /** + * Parameter name. + * + * @var string + */ + public $name; + + /** + * vCard 2.1 allows parameters to be encoded without a name. + * + * We can deduce the parameter name based on it's value. + * + * @var bool + */ + public $noName = false; + + /** + * Parameter value. + * + * @var string + */ + protected $value; + + /** + * Sets up the object. + * + * It's recommended to use the create:: factory method instead. + * + * @param string $name + * @param string $value + */ + function __construct(Document $root, $name, $value = null) { + + $this->name = strtoupper($name); + $this->root = $root; + if (is_null($name)) { + $this->noName = true; + $this->name = static::guessParameterNameByValue($value); + } + + // If guessParameterNameByValue() returns an empty string + // above, we're actually dealing with a parameter that has no value. + // In that case we have to move the value to the name. + if ($this->name === '') { + $this->noName = false; + $this->name = strtoupper($value); + } else { + $this->setValue($value); + } + + } + + /** + * Try to guess property name by value, can be used for vCard 2.1 nameless parameters. + * + * Figuring out what the name should have been. Note that a ton of + * these are rather silly in 2014 and would probably rarely be + * used, but we like to be complete. + * + * @param string $value + * + * @return string + */ + static function guessParameterNameByValue($value) { + switch (strtoupper($value)) { + + // Encodings + case '7-BIT' : + case 'QUOTED-PRINTABLE' : + case 'BASE64' : + $name = 'ENCODING'; + break; + + // Common types + case 'WORK' : + case 'HOME' : + case 'PREF' : + + // Delivery Label Type + case 'DOM' : + case 'INTL' : + case 'POSTAL' : + case 'PARCEL' : + + // Telephone types + case 'VOICE' : + case 'FAX' : + case 'MSG' : + case 'CELL' : + case 'PAGER' : + case 'BBS' : + case 'MODEM' : + case 'CAR' : + case 'ISDN' : + case 'VIDEO' : + + // EMAIL types (lol) + case 'AOL' : + case 'APPLELINK' : + case 'ATTMAIL' : + case 'CIS' : + case 'EWORLD' : + case 'INTERNET' : + case 'IBMMAIL' : + case 'MCIMAIL' : + case 'POWERSHARE' : + case 'PRODIGY' : + case 'TLX' : + case 'X400' : + + // Photo / Logo format types + case 'GIF' : + case 'CGM' : + case 'WMF' : + case 'BMP' : + case 'DIB' : + case 'PICT' : + case 'TIFF' : + case 'PDF' : + case 'PS' : + case 'JPEG' : + case 'MPEG' : + case 'MPEG2' : + case 'AVI' : + case 'QTIME' : + + // Sound Digital Audio Type + case 'WAVE' : + case 'PCM' : + case 'AIFF' : + + // Key types + case 'X509' : + case 'PGP' : + $name = 'TYPE'; + break; + + // Value types + case 'INLINE' : + case 'URL' : + case 'CONTENT-ID' : + case 'CID' : + $name = 'VALUE'; + break; + + default: + $name = ''; + } + + return $name; + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * + * @return void + */ + function setValue($value) { + + $this->value = $value; + + } + + /** + * Returns the current value. + * + * This method will always return a string, or null. If there were multiple + * values, it will automatically concatenate them (separated by comma). + * + * @return string|null + */ + function getValue() { + + if (is_array($this->value)) { + return implode(',', $this->value); + } else { + return $this->value; + } + + } + + /** + * Sets multiple values for this parameter. + * + * @param array $value + * + * @return void + */ + function setParts(array $value) { + + $this->value = $value; + + } + + /** + * Returns all values for this parameter. + * + * If there were no values, an empty array will be returned. + * + * @return array + */ + function getParts() { + + if (is_array($this->value)) { + return $this->value; + } elseif (is_null($this->value)) { + return []; + } else { + return [$this->value]; + } + + } + + /** + * Adds a value to this parameter. + * + * If the argument is specified as an array, all items will be added to the + * parameter value list. + * + * @param string|array $part + * + * @return void + */ + function addValue($part) { + + if (is_null($this->value)) { + $this->value = $part; + } else { + $this->value = array_merge((array)$this->value, (array)$part); + } + + } + + /** + * Checks if this parameter contains the specified value. + * + * This is a case-insensitive match. It makes sense to call this for for + * instance the TYPE parameter, to see if it contains a keyword such as + * 'WORK' or 'FAX'. + * + * @param string $value + * + * @return bool + */ + function has($value) { + + return in_array( + strtolower($value), + array_map('strtolower', (array)$this->value) + ); + + } + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + function serialize() { + + $value = $this->getParts(); + + if (count($value) === 0) { + return $this->name . '='; + } + + if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) { + + return implode(';', $value); + + } + + return $this->name . '=' . array_reduce( + $value, + function($out, $item) { + + if (!is_null($out)) $out .= ','; + + // If there's no special characters in the string, we'll use the simple + // format. + // + // The list of special characters is defined as: + // + // Any character except CONTROL, DQUOTE, ";", ":", "," + // + // by the iCalendar spec: + // https://tools.ietf.org/html/rfc5545#section-3.1 + // + // And we add ^ to that because of: + // https://tools.ietf.org/html/rfc6868 + // + // But we've found that iCal (7.0, shipped with OSX 10.9) + // severaly trips on + characters not being quoted, so we + // added + as well. + if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) { + return $out . $item; + } else { + // Enclosing in double-quotes, and using RFC6868 for encoding any + // special characters + $out .= '"' . strtr( + $item, + [ + '^' => '^^', + "\n" => '^n', + '"' => '^\'', + ] + ) . '"'; + return $out; + } + + } + ); + + } + + /** + * 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() { + + return $this->value; + + } + + /** + * 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) { + + foreach (explode(',', $this->value) as $value) { + $writer->writeElement('text', $value); + } + + } + + /** + * Called when this object is being cast to a string. + * + * @return string + */ + function __toString() { + + return (string)$this->getValue(); + + } + + /** + * Returns the iterator for this object. + * + * @return ElementList + */ + function getIterator() { + + if (!is_null($this->iterator)) + return $this->iterator; + + return $this->iterator = new ArrayIterator((array)$this->value); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/ParseException.php b/libs/composer/vendor/sabre/vobject/lib/ParseException.php new file mode 100644 index 000000000000..d96d20720d6c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/ParseException.php @@ -0,0 +1,13 @@ +<?php + +namespace Sabre\VObject; + +/** + * Exception thrown by Reader if an invalid object was attempted to be parsed. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ParseException extends \Exception { +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parser/Json.php b/libs/composer/vendor/sabre/vobject/lib/Parser/Json.php new file mode 100644 index 000000000000..a77258a2ef4e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parser/Json.php @@ -0,0 +1,197 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\EofException; +use Sabre\VObject\ParseException; + +/** + * Json Parser. + * + * This parser parses both the jCal and jCard formats. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Json extends Parser { + + /** + * The input data. + * + * @var array + */ + protected $input; + + /** + * Root component. + * + * @var Document + */ + protected $root; + + /** + * This method starts the parsing process. + * + * If the input was not supplied during construction, it's possible to pass + * it here instead. + * + * If either input or options are not supplied, the defaults will be used. + * + * @param resource|string|array|null $input + * @param int $options + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + if (is_null($this->input)) { + throw new EofException('End of input stream, or no input supplied'); + } + + if (0 !== $options) { + $this->options = $options; + } + + switch ($this->input[0]) { + case 'vcalendar' : + $this->root = new VCalendar([], false); + break; + case 'vcard' : + $this->root = new VCard([], false); + break; + default : + throw new ParseException('The root component must either be a vcalendar, or a vcard'); + + } + foreach ($this->input[1] as $prop) { + $this->root->add($this->parseProperty($prop)); + } + if (isset($this->input[2])) foreach ($this->input[2] as $comp) { + $this->root->add($this->parseComponent($comp)); + } + + // Resetting the input so we can throw an feof exception the next time. + $this->input = null; + + return $this->root; + + } + + /** + * Parses a component. + * + * @param array $jComp + * + * @return \Sabre\VObject\Component + */ + function parseComponent(array $jComp) { + + // We can remove $self from PHP 5.4 onward. + $self = $this; + + $properties = array_map( + function($jProp) use ($self) { + return $self->parseProperty($jProp); + }, + $jComp[1] + ); + + if (isset($jComp[2])) { + + $components = array_map( + function($jComp) use ($self) { + return $self->parseComponent($jComp); + }, + $jComp[2] + ); + + } else $components = []; + + return $this->root->createComponent( + $jComp[0], + array_merge($properties, $components), + $defaults = false + ); + + } + + /** + * Parses properties. + * + * @param array $jProp + * + * @return \Sabre\VObject\Property + */ + function parseProperty(array $jProp) { + + list( + $propertyName, + $parameters, + $valueType + ) = $jProp; + + $propertyName = strtoupper($propertyName); + + // This is the default class we would be using if we didn't know the + // value type. We're using this value later in this function. + $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); + + $parameters = (array)$parameters; + + $value = array_slice($jProp, 3); + + $valueType = strtoupper($valueType); + + if (isset($parameters['group'])) { + $propertyName = $parameters['group'] . '.' . $propertyName; + unset($parameters['group']); + } + + $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType); + $prop->setJsonValue($value); + + // We have to do something awkward here. FlatText as well as Text + // represents TEXT values. We have to normalize these here. In the + // future we can get rid of FlatText once we're allowed to break BC + // again. + if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') { + $defaultPropertyClass = 'Sabre\VObject\Property\Text'; + } + + // If the value type we received (e.g.: TEXT) was not the default value + // type for the given property (e.g.: BDAY), we need to add a VALUE= + // parameter. + if ($defaultPropertyClass !== get_class($prop)) { + $prop["VALUE"] = $valueType; + } + + return $prop; + + } + + /** + * Sets the input data. + * + * @param resource|string|array $input + * + * @return void + */ + function setInput($input) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + if (is_string($input)) { + $input = json_decode($input); + } + $this->input = $input; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parser/MimeDir.php b/libs/composer/vendor/sabre/vobject/lib/Parser/MimeDir.php new file mode 100644 index 000000000000..7426412365b1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parser/MimeDir.php @@ -0,0 +1,697 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\Component; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Document; +use Sabre\VObject\EofException; +use Sabre\VObject\ParseException; + +/** + * MimeDir parser. + * + * This class parses iCalendar 2.0 and vCard 2.1, 3.0 and 4.0 files. This + * parser will return one of the following two objects from the parse method: + * + * Sabre\VObject\Component\VCalendar + * Sabre\VObject\Component\VCard + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MimeDir extends Parser { + + /** + * The input stream. + * + * @var resource + */ + protected $input; + + /** + * Root component. + * + * @var Component + */ + protected $root; + + /** + * By default all input will be assumed to be UTF-8. + * + * However, both iCalendar and vCard might be encoded using different + * character sets. The character set is usually set in the mime-type. + * + * If this is the case, use setEncoding to specify that a different + * encoding will be used. If this is set, the parser will automatically + * convert all incoming data to UTF-8. + * + * @var string + */ + protected $charset = 'UTF-8'; + + /** + * The list of character sets we support when decoding. + * + * This would be a const expression but for now we need to support PHP 5.5 + */ + protected static $SUPPORTED_CHARSETS = [ + 'UTF-8', + 'ISO-8859-1', + 'Windows-1252', + ]; + + /** + * Parses an iCalendar or vCard file. + * + * Pass a stream or a string. If null is parsed, the existing buffer is + * used. + * + * @param string|resource|null $input + * @param int $options + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + $this->root = null; + + if (!is_null($input)) { + $this->setInput($input); + } + + if (0 !== $options) { + $this->options = $options; + } + + $this->parseDocument(); + + return $this->root; + + } + + /** + * By default all input will be assumed to be UTF-8. + * + * However, both iCalendar and vCard might be encoded using different + * character sets. The character set is usually set in the mime-type. + * + * If this is the case, use setEncoding to specify that a different + * encoding will be used. If this is set, the parser will automatically + * convert all incoming data to UTF-8. + * + * @param string $charset + */ + function setCharset($charset) { + + if (!in_array($charset, self::$SUPPORTED_CHARSETS)) { + throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')'); + } + $this->charset = $charset; + + } + + /** + * Sets the input buffer. Must be a string or stream. + * + * @param resource|string $input + * + * @return void + */ + function setInput($input) { + + // Resetting the parser + $this->lineIndex = 0; + $this->startLine = 0; + + if (is_string($input)) { + // Convering to a stream. + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $input); + rewind($stream); + $this->input = $stream; + } elseif (is_resource($input)) { + $this->input = $input; + } else { + throw new \InvalidArgumentException('This parser can only read from strings or streams.'); + } + + } + + /** + * Parses an entire document. + * + * @return void + */ + protected function parseDocument() { + + $line = $this->readLine(); + + // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF). + // It's 0xEF 0xBB 0xBF in UTF-8 hex. + if (3 <= strlen($line) + && ord($line[0]) === 0xef + && ord($line[1]) === 0xbb + && ord($line[2]) === 0xbf) { + $line = substr($line, 3); + } + + switch (strtoupper($line)) { + case 'BEGIN:VCALENDAR' : + $class = VCalendar::$componentMap['VCALENDAR']; + break; + case 'BEGIN:VCARD' : + $class = VCard::$componentMap['VCARD']; + break; + default : + throw new ParseException('This parser only supports VCARD and VCALENDAR files'); + } + + $this->root = new $class([], false); + + while (true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line, 0, 4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $this->root->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name !== $this->root->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"'); + } + + } + + /** + * Parses a line, and if it hits a component, it will also attempt to parse + * the entire component. + * + * @param string $line Unfolded line + * + * @return Node + */ + protected function parseLine($line) { + + // Start of a new component + if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') { + + $component = $this->root->createComponent(substr($line, 6), [], false); + + while (true) { + + // Reading until we hit END: + $line = $this->readLine(); + if (strtoupper(substr($line, 0, 4)) === 'END:') { + break; + } + $result = $this->parseLine($line); + if ($result) { + $component->add($result); + } + + } + + $name = strtoupper(substr($line, 4)); + if ($name !== $component->name) { + throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"'); + } + + return $component; + + } else { + + // Property reader + $property = $this->readProperty($line); + if (!$property) { + // Ignored line + return false; + } + return $property; + + } + + } + + /** + * We need to look ahead 1 line every time to see if we need to 'unfold' + * the next line. + * + * If that was not the case, we store it here. + * + * @var null|string + */ + protected $lineBuffer; + + /** + * The real current line number. + */ + protected $lineIndex = 0; + + /** + * In the case of unfolded lines, this property holds the line number for + * the start of the line. + * + * @var int + */ + protected $startLine = 0; + + /** + * Contains a 'raw' representation of the current line. + * + * @var string + */ + protected $rawLine; + + /** + * Reads a single line from the buffer. + * + * This method strips any newlines and also takes care of unfolding. + * + * @throws \Sabre\VObject\EofException + * + * @return string + */ + protected function readLine() { + + if (!\is_null($this->lineBuffer)) { + $rawLine = $this->lineBuffer; + $this->lineBuffer = null; + } else { + do { + $eof = \feof($this->input); + + $rawLine = \fgets($this->input); + + if ($eof || (\feof($this->input) && $rawLine === false)) { + throw new EofException('End of document reached prematurely'); + } + if ($rawLine === false) { + throw new ParseException('Error reading from input stream'); + } + $rawLine = \rtrim($rawLine, "\r\n"); + } while ($rawLine === ''); // Skipping empty lines + $this->lineIndex++; + } + $line = $rawLine; + + $this->startLine = $this->lineIndex; + + // Looking ahead for folded lines. + while (true) { + + $nextLine = \rtrim(\fgets($this->input), "\r\n"); + $this->lineIndex++; + if (!$nextLine) { + break; + } + if ($nextLine[0] === "\t" || $nextLine[0] === " ") { + $curLine = \substr($nextLine, 1); + $line .= $curLine; + $rawLine .= "\n " . $curLine; + } else { + $this->lineBuffer = $nextLine; + break; + } + + } + $this->rawLine = $rawLine; + return $line; + + } + + /** + * Reads a property or component from a line. + * + * @return void + */ + protected function readProperty($line) { + + if ($this->options & self::OPTION_FORGIVING) { + $propNameToken = 'A-Z0-9\-\._\\/'; + } else { + $propNameToken = 'A-Z0-9\-\.'; + } + + $paramNameToken = 'A-Z0-9\-'; + $safeChar = '^";:,'; + $qSafeChar = '^"'; + + $regex = "/ + ^(?P<name> [$propNameToken]+ ) (?=[;:]) # property name + | + (?<=:)(?P<propValue> .+)$ # property value + | + ;(?P<paramName> [$paramNameToken]+) (?=[=;:]) # parameter name + | + (=|,)(?P<paramValue> # parameter value + (?: [$safeChar]*) | + \"(?: [$qSafeChar]+)\" + ) (?=[;:,]) + /xi"; + + //echo $regex, "\n"; die(); + preg_match_all($regex, $line, $matches, PREG_SET_ORDER); + + $property = [ + 'name' => null, + 'parameters' => [], + 'value' => null + ]; + + $lastParam = null; + + /** + * Looping through all the tokens. + * + * Note that we are looping through them in reverse order, because if a + * sub-pattern matched, the subsequent named patterns will not show up + * in the result. + */ + foreach ($matches as $match) { + + if (isset($match['paramValue'])) { + if ($match['paramValue'] && $match['paramValue'][0] === '"') { + $value = substr($match['paramValue'], 1, -1); + } else { + $value = $match['paramValue']; + } + + $value = $this->unescapeParam($value); + + if (is_null($lastParam)) { + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } + if (is_null($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = $value; + } elseif (is_array($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam][] = $value; + } else { + $property['parameters'][$lastParam] = [ + $property['parameters'][$lastParam], + $value + ]; + } + continue; + } + if (isset($match['paramName'])) { + $lastParam = strtoupper($match['paramName']); + if (!isset($property['parameters'][$lastParam])) { + $property['parameters'][$lastParam] = null; + } + continue; + } + if (isset($match['propValue'])) { + $property['value'] = $match['propValue']; + continue; + } + if (isset($match['name']) && $match['name']) { + $property['name'] = strtoupper($match['name']); + continue; + } + + // @codeCoverageIgnoreStart + throw new \LogicException('This code should not be reachable'); + // @codeCoverageIgnoreEnd + + } + + if (is_null($property['value'])) { + $property['value'] = ''; + } + if (!$property['name']) { + if ($this->options & self::OPTION_IGNORE_INVALID_LINES) { + return false; + } + throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions'); + } + + // vCard 2.1 states that parameters may appear without a name, and only + // a value. We can deduce the value based on it's name. + // + // Our parser will get those as parameters without a value instead, so + // we're filtering these parameters out first. + $namedParameters = []; + $namelessParameters = []; + + foreach ($property['parameters'] as $name => $value) { + if (!is_null($value)) { + $namedParameters[$name] = $value; + } else { + $namelessParameters[] = $name; + } + } + + $propObj = $this->root->createProperty($property['name'], null, $namedParameters); + + foreach ($namelessParameters as $namelessParameter) { + $propObj->add(null, $namelessParameter); + } + + if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') { + $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue()); + } else { + $charset = $this->charset; + if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) { + // vCard 2.1 allows the character set to be specified per property. + $charset = (string)$propObj['CHARSET']; + } + switch (strtolower($charset)) { + case 'utf-8' : + break; + case 'iso-8859-1' : + $property['value'] = utf8_encode($property['value']); + break; + case 'windows-1252' : + $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset); + break; + default : + throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']); + } + $propObj->setRawMimeDirValue($property['value']); + } + + return $propObj; + + } + + /** + * Unescapes a property value. + * + * vCard 2.1 says: + * * Semi-colons must be escaped in some property values, specifically + * ADR, ORG and N. + * * Semi-colons must be escaped in parameter values, because semi-colons + * are also use to separate values. + * * No mention of escaping backslashes with another backslash. + * * newlines are not escaped either, instead QUOTED-PRINTABLE is used to + * span values over more than 1 line. + * + * vCard 3.0 says: + * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be + * escaped, all time time. + * * Comma's are used for delimeters in multiple values + * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped, + * as in some properties semi-colon is used for separators. + * * Properties using semi-colons: N, ADR, GEO, ORG + * * Both ADR and N's individual parts may be broken up further with a + * comma. + * * Properties using commas: NICKNAME, CATEGORIES + * + * vCard 4.0 (rfc6350) says: + * * Commas must be escaped. + * * Semi-colons may be escaped, an unescaped semi-colon _may_ be a + * delimiter, depending on the property. + * * Backslashes must be escaped + * * Newlines must be escaped as either \N or \n. + * * Some compound properties may contain multiple parts themselves, so a + * comma within a semi-colon delimited property may also be unescaped + * to denote multiple parts _within_ the compound property. + * * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP. + * * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID. + * + * Even though the spec says that commas must always be escaped, the + * example for GEO in Section 6.5.2 seems to violate this. + * + * iCalendar 2.0 (rfc5545) says: + * * Commas or semi-colons may be used as delimiters, depending on the + * property. + * * Commas, semi-colons, backslashes, newline (\N or \n) are always + * escaped, unless they are delimiters. + * * Colons shall not be escaped. + * * Commas can be considered the 'default delimiter' and is described as + * the delimiter in cases where the order of the multiple values is + * insignificant. + * * Semi-colons are described as the delimiter for 'structured values'. + * They are specifically used in Semi-colons are used as a delimiter in + * REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however. + * + * Now for the parameters + * + * If delimiter is not set (null) this method will just return a string. + * If it's a comma or a semi-colon the string will be split on those + * characters, and always return an array. + * + * @param string $input + * @param string $delimiter + * + * @return string|string[] + */ + static function unescapeValue($input, $delimiter = ';') { + + $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )'; + if ($delimiter) { + $regex .= ' | (' . $delimiter . ')'; + } + $regex .= ') #x'; + + $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + $resultArray = []; + $result = ''; + + foreach ($matches as $match) { + + switch ($match) { + case '\\\\' : + $result .= '\\'; + break; + case '\N' : + case '\n' : + $result .= "\n"; + break; + case '\;' : + $result .= ';'; + break; + case '\,' : + $result .= ','; + break; + case $delimiter : + $resultArray[] = $result; + $result = ''; + break; + default : + $result .= $match; + break; + + } + + } + + $resultArray[] = $result; + return $delimiter ? $resultArray : $result; + + } + + /** + * Unescapes a parameter value. + * + * vCard 2.1: + * * Does not mention a mechanism for this. In addition, double quotes + * are never used to wrap values. + * * This means that parameters can simply not contain colons or + * semi-colons. + * + * vCard 3.0 (rfc2425, rfc2426): + * * Parameters _may_ be surrounded by double quotes. + * * If this is not the case, semi-colon, colon and comma may simply not + * occur (the comma used for multiple parameter values though). + * * If it is surrounded by double-quotes, it may simply not contain + * double-quotes. + * * This means that a parameter can in no case encode double-quotes, or + * newlines. + * + * vCard 4.0 (rfc6350) + * * Behavior seems to be identical to vCard 3.0 + * + * iCalendar 2.0 (rfc5545) + * * Behavior seems to be identical to vCard 3.0 + * + * Parameter escaping mechanism (rfc6868) : + * * This rfc describes a new way to escape parameter values. + * * New-line is encoded as ^n + * * ^ is encoded as ^^. + * * " is encoded as ^' + * + * @param string $input + * + * @return void + */ + private function unescapeParam($input) { + + return + preg_replace_callback( + '#(\^(\^|n|\'))#', + function($matches) { + switch ($matches[2]) { + case 'n' : + return "\n"; + case '^' : + return '^'; + case '\'' : + return '"'; + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + }, + $input + ); + } + + /** + * Gets the full quoted printable value. + * + * We need a special method for this, because newlines have both a meaning + * in vCards, and in QuotedPrintable. + * + * This method does not do any decoding. + * + * @return string + */ + private function extractQuotedPrintableValue() { + + // We need to parse the raw line again to get the start of the value. + // + // We are basically looking for the first colon (:), but we need to + // skip over the parameters first, as they may contain one. + $regex = '/^ + (?: [^:])+ # Anything but a colon + (?: "[^"]")* # A parameter in double quotes + : # start of the value we really care about + (.*)$ + /xs'; + + preg_match($regex, $this->rawLine, $matches); + + $value = $matches[1]; + // Removing the first whitespace character from every line. Kind of + // like unfolding, but we keep the newline. + $value = str_replace("\n ", "\n", $value); + + // Microsoft products don't always correctly fold lines, they may be + // missing a whitespace. So if 'forgiving' is turned on, we will take + // those as well. + if ($this->options & self::OPTION_FORGIVING) { + while (substr($value, -1) === '=') { + // Reading the line + $this->readLine(); + // Grabbing the raw form + $value .= "\n" . $this->rawLine; + } + } + + return $value; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parser/Parser.php b/libs/composer/vendor/sabre/vobject/lib/Parser/Parser.php new file mode 100644 index 000000000000..ca8bc0addd30 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parser/Parser.php @@ -0,0 +1,80 @@ +<?php + +namespace Sabre\VObject\Parser; + +/** + * Abstract parser. + * + * This class serves as a base-class for the different parsers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Parser { + + /** + * Turning on this option makes the parser more forgiving. + * + * In the case of the MimeDir parser, this means that the parser will + * accept slashes and underscores in property names, and it will also + * attempt to fix Microsoft vCard 2.1's broken line folding. + */ + const OPTION_FORGIVING = 1; + + /** + * If this option is turned on, any lines we cannot parse will be ignored + * by the reader. + */ + const OPTION_IGNORE_INVALID_LINES = 2; + + /** + * Bitmask of parser options. + * + * @var int + */ + protected $options; + + /** + * Creates the parser. + * + * Optionally, it's possible to parse the input stream here. + * + * @param mixed $input + * @param int $options Any parser options (OPTION constants). + * + * @return void + */ + function __construct($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + $this->options = $options; + } + + /** + * This method starts the parsing process. + * + * If the input was not supplied during construction, it's possible to pass + * it here instead. + * + * If either input or options are not supplied, the defaults will be used. + * + * @param mixed $input + * @param int $options + * + * @return array + */ + abstract function parse($input = null, $options = 0); + + /** + * Sets the input data. + * + * @param mixed $input + * + * @return void + */ + abstract function setInput($input); + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parser/XML.php b/libs/composer/vendor/sabre/vobject/lib/Parser/XML.php new file mode 100644 index 000000000000..5ac423984776 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parser/XML.php @@ -0,0 +1,428 @@ +<?php + +namespace Sabre\VObject\Parser; + +use Sabre\VObject\Component; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\EofException; +use Sabre\VObject\ParseException; +use Sabre\Xml as SabreXml; + +/** + * XML Parser. + * + * This parser parses both the xCal and xCard formats. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class XML extends Parser { + + const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0'; + const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0'; + + /** + * The input data. + * + * @var array + */ + protected $input; + + /** + * A pointer/reference to the input. + * + * @var array + */ + private $pointer; + + /** + * Document, root component. + * + * @var Sabre\VObject\Document + */ + protected $root; + + /** + * Creates the parser. + * + * Optionally, it's possible to parse the input stream here. + * + * @param mixed $input + * @param int $options Any parser options (OPTION constants). + * + * @return void + */ + function __construct($input = null, $options = 0) { + + if (0 === $options) { + $options = parent::OPTION_FORGIVING; + } + + parent::__construct($input, $options); + + } + + /** + * Parse xCal or xCard. + * + * @param resource|string $input + * @param int $options + * + * @throws \Exception + * + * @return Sabre\VObject\Document + */ + function parse($input = null, $options = 0) { + + if (!is_null($input)) { + $this->setInput($input); + } + + if (0 !== $options) { + $this->options = $options; + } + + if (is_null($this->input)) { + throw new EofException('End of input stream, or no input supplied'); + } + + switch ($this->input['name']) { + + case '{' . self::XCAL_NAMESPACE . '}icalendar': + $this->root = new VCalendar([], false); + $this->pointer = &$this->input['value'][0]; + $this->parseVCalendarComponents($this->root); + break; + + case '{' . self::XCARD_NAMESPACE . '}vcards': + foreach ($this->input['value'] as &$vCard) { + + $this->root = new VCard(['version' => '4.0'], false); + $this->pointer = &$vCard; + $this->parseVCardComponents($this->root); + + // We just parse the first <vcard /> element. + break; + + } + break; + + default: + throw new ParseException('Unsupported XML standard'); + + } + + return $this->root; + } + + /** + * Parse a xCalendar component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseVCalendarComponents(Component $parentComponent) { + + foreach ($this->pointer['value'] ?: [] as $children) { + + switch (static::getTagName($children['name'])) { + + case 'properties': + $this->pointer = &$children['value']; + $this->parseProperties($parentComponent); + break; + + case 'components': + $this->pointer = &$children; + $this->parseComponent($parentComponent); + break; + } + } + + } + + /** + * Parse a xCard component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseVCardComponents(Component $parentComponent) { + + $this->pointer = &$this->pointer['value']; + $this->parseProperties($parentComponent); + + } + + /** + * Parse xCalendar and xCard properties. + * + * @param Component $parentComponent + * @param string $propertyNamePrefix + * + * @return void + */ + protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') { + + foreach ($this->pointer ?: [] as $xmlProperty) { + + list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']); + + $propertyName = $tagName; + $propertyValue = []; + $propertyParameters = []; + $propertyType = 'text'; + + // A property which is not part of the standard. + if ($namespace !== self::XCAL_NAMESPACE + && $namespace !== self::XCARD_NAMESPACE) { + + $propertyName = 'xml'; + $value = '<' . $tagName . ' xmlns="' . $namespace . '"'; + + foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) { + $value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"'; + } + + $value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>'; + + $propertyValue = [$value]; + + $this->createProperty( + $parentComponent, + $propertyName, + $propertyParameters, + $propertyType, + $propertyValue + ); + + continue; + } + + // xCard group. + if ($propertyName === 'group') { + + if (!isset($xmlProperty['attributes']['name'])) { + continue; + } + + $this->pointer = &$xmlProperty['value']; + $this->parseProperties( + $parentComponent, + strtoupper($xmlProperty['attributes']['name']) . '.' + ); + + continue; + + } + + // Collect parameters. + foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) { + + if (!is_array($xmlPropertyChild) + || 'parameters' !== static::getTagName($xmlPropertyChild['name'])) + continue; + + $xmlParameters = $xmlPropertyChild['value']; + + foreach ($xmlParameters as $xmlParameter) { + + $propertyParameterValues = []; + + foreach ($xmlParameter['value'] as $xmlParameterValues) { + $propertyParameterValues[] = $xmlParameterValues['value']; + } + + $propertyParameters[static::getTagName($xmlParameter['name'])] + = implode(',', $propertyParameterValues); + + } + + array_splice($xmlProperty['value'], $i, 1); + + } + + $propertyNameExtended = ($this->root instanceof VCalendar + ? 'xcal' + : 'xcard') . ':' . $propertyName; + + switch ($propertyNameExtended) { + + case 'xcal:geo': + $propertyType = 'float'; + $propertyValue['latitude'] = 0; + $propertyValue['longitude'] = 0; + + foreach ($xmlProperty['value'] as $xmlRequestChild) { + $propertyValue[static::getTagName($xmlRequestChild['name'])] + = $xmlRequestChild['value']; + } + break; + + case 'xcal:request-status': + $propertyType = 'text'; + + foreach ($xmlProperty['value'] as $xmlRequestChild) { + $propertyValue[static::getTagName($xmlRequestChild['name'])] + = $xmlRequestChild['value']; + } + break; + + case 'xcal:freebusy': + $propertyType = 'freebusy'; + // We don't break because we only want to set + // another property type. + + case 'xcal:categories': + case 'xcal:resources': + case 'xcal:exdate': + foreach ($xmlProperty['value'] as $specialChild) { + $propertyValue[static::getTagName($specialChild['name'])] + = $specialChild['value']; + } + break; + + case 'xcal:rdate': + $propertyType = 'date-time'; + + foreach ($xmlProperty['value'] as $specialChild) { + + $tagName = static::getTagName($specialChild['name']); + + if ('period' === $tagName) { + + $propertyParameters['value'] = 'PERIOD'; + $propertyValue[] = implode('/', $specialChild['value']); + + } + else { + $propertyValue[] = $specialChild['value']; + } + } + break; + + default: + $propertyType = static::getTagName($xmlProperty['value'][0]['name']); + + foreach ($xmlProperty['value'] as $value) { + $propertyValue[] = $value['value']; + } + + if ('date' === $propertyType) { + $propertyParameters['value'] = 'DATE'; + } + break; + } + + $this->createProperty( + $parentComponent, + $propertyNamePrefix . $propertyName, + $propertyParameters, + $propertyType, + $propertyValue + ); + + } + + } + + /** + * Parse a component. + * + * @param Component $parentComponent + * + * @return void + */ + protected function parseComponent(Component $parentComponent) { + + $components = $this->pointer['value'] ?: []; + + foreach ($components as $component) { + + $componentName = static::getTagName($component['name']); + $currentComponent = $this->root->createComponent( + $componentName, + null, + false + ); + + $this->pointer = &$component; + $this->parseVCalendarComponents($currentComponent); + + $parentComponent->add($currentComponent); + + } + + } + + /** + * Create a property. + * + * @param Component $parentComponent + * @param string $name + * @param array $parameters + * @param string $type + * @param mixed $value + * + * @return void + */ + protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) { + + $property = $this->root->createProperty( + $name, + null, + $parameters, + $type + ); + $parentComponent->add($property); + $property->setXmlValue($value); + + } + + /** + * Sets the input data. + * + * @param resource|string $input + * + * @return void + */ + function setInput($input) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + + if (is_string($input)) { + + $reader = new SabreXml\Reader(); + $reader->elementMap['{' . self::XCAL_NAMESPACE . '}period'] + = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + $reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur'] + = 'Sabre\VObject\Parser\XML\Element\KeyValue'; + $reader->xml($input); + $input = $reader->parse(); + + } + + $this->input = $input; + + } + + /** + * Get tag name from a Clark notation. + * + * @param string $clarkedTagName + * + * @return string + */ + protected static function getTagName($clarkedTagName) { + + list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName); + return $tagName; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php b/libs/composer/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php new file mode 100644 index 000000000000..14d7984332bf --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\VObject\Parser\XML\Element; + +use Sabre\Xml as SabreXml; + +/** + * Our own sabre/xml key-value element. + * + * It just removes the clark notation. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class KeyValue extends SabreXml\Element\KeyValue { + + /** + * The deserialize method is called during xml parsing. + * + * This method is called staticly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param XML\Reader $reader + * + * @return mixed + */ + static function xmlDeserialize(SabreXml\Reader $reader) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + $reader->read(); + + do { + + if ($reader->nodeType === SabreXml\Reader::ELEMENT) { + + $name = $reader->localName; + $values[$name] = $reader->parseCurrentElement()['value']; + + } else { + $reader->read(); + } + + } while ($reader->nodeType !== SabreXml\Reader::END_ELEMENT); + + $reader->read(); + + return $values; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property.php b/libs/composer/vendor/sabre/vobject/lib/Property.php new file mode 100644 index 000000000000..3d1775fa2e07 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property.php @@ -0,0 +1,661 @@ +<?php + +namespace Sabre\VObject; + +use Sabre\Xml; + +/** + * Property. + * + * A property is always in a KEY:VALUE structure, and may optionally contain + * parameters. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Property extends Node { + + /** + * Property name. + * + * This will contain a string such as DTSTART, SUMMARY, FN. + * + * @var string + */ + public $name; + + /** + * Property group. + * + * This is only used in vcards + * + * @var string + */ + public $group; + + /** + * List of parameters. + * + * @var array + */ + public $parameters = []; + + /** + * Current value. + * + * @var mixed + */ + protected $value; + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = ';'; + + /** + * Creates the generic property. + * + * Parameters must be specified in key=>value syntax. + * + * @param Component $root The root document + * @param string $name + * @param string|array|null $value + * @param array $parameters List of parameters + * @param string $group The vcard property group + * + * @return void + */ + function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) { + + $this->name = $name; + $this->group = $group; + + $this->root = $root; + + foreach ($parameters as $k => $v) { + $this->add($k, $v); + } + + if (!is_null($value)) { + $this->setValue($value); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * + * @return void + */ + function setValue($value) { + + $this->value = $value; + + } + + /** + * Returns the current value. + * + * This method will always return a singular value. If this was a + * multi-value object, some decision will be made first on how to represent + * it as a string. + * + * To get the correct multi-value version, use getParts. + * + * @return string + */ + function getValue() { + + if (is_array($this->value)) { + if (count($this->value) == 0) { + return; + } elseif (count($this->value) === 1) { + return $this->value[0]; + } else { + return $this->getRawMimeDirValue(); + } + } else { + return $this->value; + } + + } + + /** + * Sets a multi-valued property. + * + * @param array $parts + * + * @return void + */ + function setParts(array $parts) { + + $this->value = $parts; + + } + + /** + * Returns a multi-valued property. + * + * This method always returns an array, if there was only a single value, + * it will still be wrapped in an array. + * + * @return array + */ + function getParts() { + + if (is_null($this->value)) { + return []; + } elseif (is_array($this->value)) { + return $this->value; + } else { + return [$this->value]; + } + + } + + /** + * Adds a new parameter. + * + * If a parameter with same name already existed, the values will be + * combined. + * If nameless parameter is added, we try to guess it's name. + * + * @param string $name + * @param string|null|array $value + */ + function add($name, $value = null) { + $noName = false; + if ($name === null) { + $name = Parameter::guessParameterNameByValue($value); + $noName = true; + } + + if (isset($this->parameters[strtoupper($name)])) { + $this->parameters[strtoupper($name)]->addValue($value); + } + else { + $param = new Parameter($this->root, $name, $value); + $param->noName = $noName; + $this->parameters[$param->name] = $param; + } + } + + /** + * Returns an iterable list of children. + * + * @return array + */ + function parameters() { + + return $this->parameters; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + abstract function getValueType(); + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + abstract function setRawMimeDirValue($val); + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + abstract function getRawMimeDirValue(); + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + function serialize() { + + $str = $this->name; + if ($this->group) $str = $this->group . '.' . $this->name; + + foreach ($this->parameters() as $param) { + + $str .= ';' . $param->serialize(); + + } + + $str .= ':' . $this->getRawMimeDirValue(); + + $str = \preg_replace( + '/( + (?:^.)? # 1 additional byte in first line because of missing single space (see next line) + .{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF) + (?![\x80-\xbf]) # prevent splitting multibyte characters + )/x', + "$1\r\n ", + $str + ); + + // remove single space after last CRLF + return \substr($str, 0, -1); + + } + + /** + * Returns the value, in the format it should be encoded for JSON. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return $this->getParts(); + + } + + /** + * Sets the JSON value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + if (count($value) === 1) { + $this->setValue(reset($value)); + } else { + $this->setValue($value); + } + + } + + /** + * 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() { + + $parameters = []; + + foreach ($this->parameters as $parameter) { + if ($parameter->name === 'VALUE') { + continue; + } + $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize(); + } + // In jCard, we need to encode the property-group as a separate 'group' + // parameter. + if ($this->group) { + $parameters['group'] = $this->group; + } + + return array_merge( + [ + strtolower($this->name), + (object)$parameters, + strtolower($this->getValueType()), + ], + $this->getJsonValue() + ); + } + + /** + * Hydrate data from a XML subtree, as it would appear in a xCard or xCal + * object. + * + * @param array $value + * + * @return void + */ + function setXmlValue(array $value) { + + $this->setJsonValue($value); + + } + + /** + * 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) { + + $parameters = []; + + foreach ($this->parameters as $parameter) { + + if ($parameter->name === 'VALUE') { + continue; + } + + $parameters[] = $parameter; + + } + + $writer->startElement(strtolower($this->name)); + + if (!empty($parameters)) { + + $writer->startElement('parameters'); + + foreach ($parameters as $parameter) { + + $writer->startElement(strtolower($parameter->name)); + $writer->write($parameter); + $writer->endElement(); + + } + + $writer->endElement(); + + } + + $this->xmlSerializeValue($writer); + $writer->endElement(); + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + $valueType = strtolower($this->getValueType()); + + foreach ($this->getJsonValue() as $values) { + foreach ((array)$values as $value) { + $writer->writeElement($valueType, $value); + } + } + + } + + /** + * Called when this object is being cast to a string. + * + * If the property only had a single value, you will get just that. In the + * case the property had multiple values, the contents will be escaped and + * combined with ,. + * + * @return string + */ + function __toString() { + + return (string)$this->getValue(); + + } + + /* ArrayAccess interface {{{ */ + + /** + * Checks if an array element exists. + * + * @param mixed $name + * + * @return bool + */ + function offsetExists($name) { + + if (is_int($name)) return parent::offsetExists($name); + + $name = strtoupper($name); + + foreach ($this->parameters as $parameter) { + if ($parameter->name == $name) return true; + } + return false; + + } + + /** + * Returns a parameter. + * + * If the parameter does not exist, null is returned. + * + * @param string $name + * + * @return Node + */ + function offsetGet($name) { + + if (is_int($name)) return parent::offsetGet($name); + $name = strtoupper($name); + + if (!isset($this->parameters[$name])) { + return; + } + + return $this->parameters[$name]; + + } + + /** + * Creates a new parameter. + * + * @param string $name + * @param mixed $value + * + * @return void + */ + function offsetSet($name, $value) { + + if (is_int($name)) { + parent::offsetSet($name, $value); + // @codeCoverageIgnoreStart + // This will never be reached, because an exception is always + // thrown. + return; + // @codeCoverageIgnoreEnd + } + + $param = new Parameter($this->root, $name, $value); + $this->parameters[$param->name] = $param; + + } + + /** + * Removes one or more parameters with the specified name. + * + * @param string $name + * + * @return void + */ + function offsetUnset($name) { + + if (is_int($name)) { + parent::offsetUnset($name); + // @codeCoverageIgnoreStart + // This will never be reached, because an exception is always + // thrown. + return; + // @codeCoverageIgnoreEnd + } + + unset($this->parameters[strtoupper($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->parameters as $key => $child) { + $this->parameters[$key] = clone $child; + $this->parameters[$key]->parent = $this; + } + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * - Node::REPAIR - If something is broken, and automatic repair may + * be attempted. + * + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $warnings = []; + + // Checking if our value is UTF-8 + if (!StringUtil::isUTF8($this->getRawMimeDirValue())) { + + $oldValue = $this->getRawMimeDirValue(); + $level = 3; + if ($options & self::REPAIR) { + $newValue = StringUtil::convertToUTF8($oldValue); + if (true || StringUtil::isUTF8($newValue)) { + $this->setRawMimeDirValue($newValue); + $level = 1; + } + + } + + + if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) { + $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')'; + } else { + $message = 'Property is not valid UTF-8! ' . $oldValue; + } + + $warnings[] = [ + 'level' => $level, + 'message' => $message, + 'node' => $this, + ]; + } + + // Checking if the propertyname does not contain any invalid bytes. + if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) { + $warnings[] = [ + 'level' => $options & self::REPAIR ? 1 : 3, + 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed', + 'node' => $this, + ]; + if ($options & self::REPAIR) { + // Uppercasing and converting underscores to dashes. + $this->name = strtoupper( + str_replace('_', '-', $this->name) + ); + // Removing every other invalid character + $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name); + + } + + } + + if ($encoding = $this->offsetGet('ENCODING')) { + + if ($this->root->getDocumentType() === Document::VCARD40) { + $warnings[] = [ + 'level' => 3, + 'message' => 'ENCODING parameter is not valid in vCard 4.', + 'node' => $this + ]; + } else { + + $encoding = (string)$encoding; + + $allowedEncoding = []; + + switch ($this->root->getDocumentType()) { + case Document::ICALENDAR20 : + $allowedEncoding = ['8BIT', 'BASE64']; + break; + case Document::VCARD21 : + $allowedEncoding = ['QUOTED-PRINTABLE', 'BASE64', '8BIT']; + break; + case Document::VCARD30 : + $allowedEncoding = ['B']; + break; + + } + if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) { + $warnings[] = [ + 'level' => 3, + 'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.', + 'node' => $this + ]; + } + } + + } + + // Validating inner parameters + foreach ($this->parameters as $param) { + $warnings = array_merge($warnings, $param->validate($options)); + } + + return $warnings; + + } + + /** + * 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->parameters as $param) { + $param->destroy(); + } + $this->parameters = []; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Binary.php b/libs/composer/vendor/sabre/vobject/lib/Property/Binary.php new file mode 100644 index 000000000000..d54cae25da2c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Binary.php @@ -0,0 +1,128 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Property; + +/** + * BINARY property. + * + * This object represents BINARY values. + * + * Binary values are most commonly used by the iCalendar ATTACH property, and + * the vCard PHOTO property. + * + * This property will transparently encode and decode to base64. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Binary extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * + * @return void + */ + function setValue($value) { + + if (is_array($value)) { + + if (count($value) === 1) { + $this->value = $value[0]; + } else { + throw new \InvalidArgumentException('The argument must either be a string or an array with only one child'); + } + + } else { + + $this->value = $value; + + } + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->value = base64_decode($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return base64_encode($this->value); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'BINARY'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return [base64_encode($this->getValue())]; + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + $value = array_map('base64_decode', $value); + parent::setJsonValue($value); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Boolean.php b/libs/composer/vendor/sabre/vobject/lib/Property/Boolean.php new file mode 100644 index 000000000000..6f5887e25cdd --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Boolean.php @@ -0,0 +1,84 @@ +<?php + +namespace Sabre\VObject\Property; + +use + Sabre\VObject\Property; + +/** + * Boolean property. + * + * This object represents BOOLEAN values. These are always the case-insenstive + * string TRUE or FALSE. + * + * Automatic conversion to PHP's true and false are done. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Boolean extends Property { + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $val = strtoupper($val) === 'TRUE' ? true : false; + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return $this->value ? 'TRUE' : 'FALSE'; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'BOOLEAN'; + + } + + /** + * Hydrate data from a XML subtree, as it would appear in a xCard or xCal + * object. + * + * @param array $value + * + * @return void + */ + function setXmlValue(array $value) { + + $value = array_map( + function($value) { + return 'true' === $value; + }, + $value + ); + parent::setXmlValue($value); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/FlatText.php b/libs/composer/vendor/sabre/vobject/lib/Property/FlatText.php new file mode 100644 index 000000000000..2c7b43c29d89 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/FlatText.php @@ -0,0 +1,50 @@ +<?php + +namespace Sabre\VObject\Property; + +/** + * FlatText property. + * + * This object represents certain TEXT values. + * + * Specifically, this property is used for text values where there is only 1 + * part. Semi-colons and colons will be de-escaped when deserializing, but if + * any semi-colons or commas appear without a backslash, we will not assume + * that they are delimiters. + * + * vCard 2.1 specifically has a whole bunch of properties where this may + * happen, as it only defines a delimiter for a few properties. + * + * vCard 4.0 states something similar. An unescaped semi-colon _may_ be a + * delimiter, depending on the property. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class FlatText extends Text { + + /** + * Field separator. + * + * @var string + */ + public $delimiter = ','; + + /** + * Sets the value as a quoted-printable encoded string. + * + * Overriding this so we're not splitting on a ; delimiter. + * + * @param string $val + * + * @return void + */ + function setQuotedPrintableValue($val) { + + $val = quoted_printable_decode($val); + $this->setValue($val); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/FloatValue.php b/libs/composer/vendor/sabre/vobject/lib/Property/FloatValue.php new file mode 100644 index 000000000000..15b1195491cf --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/FloatValue.php @@ -0,0 +1,142 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Property; +use Sabre\Xml; + +/** + * Float property. + * + * This object represents FLOAT values. These can be 1 or more floating-point + * numbers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class FloatValue extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = ';'; + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $val = explode($this->delimiter, $val); + foreach ($val as &$item) { + $item = (float)$item; + } + $this->setParts($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return implode( + $this->delimiter, + $this->getParts() + ); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'FLOAT'; + + } + + /** + * Returns the value, in the format it should be encoded for JSON. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $val = array_map('floatval', $this->getParts()); + + // Special-casing the GEO property. + // + // See: + // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2 + if ($this->name === 'GEO') { + return [$val]; + } + + return $val; + + } + + /** + * Hydrate data from a XML subtree, as it would appear in a xCard or xCal + * object. + * + * @param array $value + * + * @return void + */ + function setXmlValue(array $value) { + + $value = array_map('floatval', $value); + parent::setXmlValue($value); + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + // Special-casing the GEO property. + // + // See: + // http://tools.ietf.org/html/rfc6321#section-3.4.1.2 + if ($this->name === 'GEO') { + + $value = array_map('floatval', $this->getParts()); + + $writer->writeElement('latitude', $value[0]); + $writer->writeElement('longitude', $value[1]); + + } + else { + parent::xmlSerializeValue($writer); + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php new file mode 100644 index 000000000000..a0c4a9b9addf --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php @@ -0,0 +1,61 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use + Sabre\VObject\Property\Text; + +/** + * CalAddress property. + * + * This object encodes CAL-ADDRESS values, as defined in rfc5545 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class CalAddress extends Text { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'CAL-ADDRESS'; + + } + + /** + * This returns a normalized form of the value. + * + * This is primarily used right now to turn mixed-cased schemes in user + * uris to lower-case. + * + * Evolution in particular tends to encode mailto: as MAILTO:. + * + * @return string + */ + function getNormalizedValue() { + + $input = $this->getValue(); + if (!strpos($input, ':')) { + return $input; + } + list($schema, $everythingElse) = explode(':', $input, 2); + return strtolower($schema) . ':' . $everythingElse; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Date.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Date.php new file mode 100644 index 000000000000..378a0d60a3ee --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Date.php @@ -0,0 +1,18 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +/** + * DateTime property. + * + * This object represents DATE values, as defined here: + * + * http://tools.ietf.org/html/rfc5545#section-3.3.5 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Date extends DateTime { + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php new file mode 100644 index 000000000000..d580d4f68fe1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php @@ -0,0 +1,404 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use DateTimeInterface; +use DateTimeZone; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\InvalidDataException; +use Sabre\VObject\Property; +use Sabre\VObject\TimeZoneUtil; + +/** + * DateTime property. + * + * This object represents DATE-TIME values, as defined here: + * + * http://tools.ietf.org/html/rfc5545#section-3.3.4 + * + * This particular object has a bit of hackish magic that it may also in some + * cases represent a DATE value. This is because it's a common usecase to be + * able to change a DATE-TIME into a DATE. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class DateTime extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = ','; + + /** + * Sets a multi-valued property. + * + * You may also specify DateTime objects here. + * + * @param array $parts + * + * @return void + */ + function setParts(array $parts) { + + if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) { + $this->setDateTimes($parts); + } else { + parent::setParts($parts); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * Instead of strings, you may also use DateTime here. + * + * @param string|array|DateTimeInterface $value + * + * @return void + */ + function setValue($value) { + + if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) { + $this->setDateTimes($value); + } elseif ($value instanceof DateTimeInterface) { + $this->setDateTimes([$value]); + } else { + parent::setValue($value); + } + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns true if this is a DATE-TIME value, false if it's a DATE. + * + * @return bool + */ + function hasTime() { + + return strtoupper((string)$this['VALUE']) !== 'DATE'; + + } + + /** + * Returns true if this is a floating DATE or DATE-TIME. + * + * Note that DATE is always floating. + */ + function isFloating() { + + return + !$this->hasTime() || + ( + !isset($this['TZID']) && + strpos($this->getValue(), 'Z') === false + ); + + } + + /** + * Returns a date-time value. + * + * Note that if this property contained more than 1 date-time, only the + * first will be returned. To get an array with multiple values, call + * getDateTimes. + * + * If no timezone information is known, because it's either an all-day + * property or floating time, we will use the DateTimeZone argument to + * figure out the exact date. + * + * @param DateTimeZone $timeZone + * + * @return DateTimeImmutable + */ + function getDateTime(DateTimeZone $timeZone = null) { + + $dt = $this->getDateTimes($timeZone); + if (!$dt) return; + + return $dt[0]; + + } + + /** + * Returns multiple date-time values. + * + * If no timezone information is known, because it's either an all-day + * property or floating time, we will use the DateTimeZone argument to + * figure out the exact date. + * + * @param DateTimeZone $timeZone + * + * @return DateTimeImmutable[] + * @return \DateTime[] + */ + function getDateTimes(DateTimeZone $timeZone = null) { + + // Does the property have a TZID? + $tzid = $this['TZID']; + + if ($tzid) { + $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); + } + + $dts = []; + foreach ($this->getParts() as $part) { + $dts[] = DateTimeParser::parse($part, $timeZone); + } + return $dts; + + } + + /** + * Sets the property as a DateTime object. + * + * @param DateTimeInterface $dt + * @param bool isFloating If set to true, timezones will be ignored. + * + * @return void + */ + function setDateTime(DateTimeInterface $dt, $isFloating = false) { + + $this->setDateTimes([$dt], $isFloating); + + } + + /** + * Sets the property as multiple date-time objects. + * + * The first value will be used as a reference for the timezones, and all + * the otehr values will be adjusted for that timezone + * + * @param DateTimeInterface[] $dt + * @param bool isFloating If set to true, timezones will be ignored. + * + * @return void + */ + function setDateTimes(array $dt, $isFloating = false) { + + $values = []; + + if ($this->hasTime()) { + + $tz = null; + $isUtc = false; + + foreach ($dt as $d) { + + if ($isFloating) { + $values[] = $d->format('Ymd\\THis'); + continue; + } + if (is_null($tz)) { + $tz = $d->getTimeZone(); + $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']); + if (!$isUtc) { + $this->offsetSet('TZID', $tz->getName()); + } + } else { + $d = $d->setTimeZone($tz); + } + + if ($isUtc) { + $values[] = $d->format('Ymd\\THis\\Z'); + } else { + $values[] = $d->format('Ymd\\THis'); + } + + } + if ($isUtc || $isFloating) { + $this->offsetUnset('TZID'); + } + + } else { + + foreach ($dt as $d) { + + $values[] = $d->format('Ymd'); + + } + $this->offsetUnset('TZID'); + + } + + $this->value = $values; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return $this->hasTime() ? 'DATE-TIME' : 'DATE'; + + } + + /** + * Returns the value, in the format it should be encoded for JSON. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $dts = $this->getDateTimes(); + $hasTime = $this->hasTime(); + $isFloating = $this->isFloating(); + + $tz = $dts[0]->getTimeZone(); + $isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']); + + return array_map( + function(DateTimeInterface $dt) use ($hasTime, $isUtc) { + + if ($hasTime) { + return $dt->format('Y-m-d\\TH:i:s') . ($isUtc ? 'Z' : ''); + } else { + return $dt->format('Y-m-d'); + } + + }, + $dts + ); + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + // dates and times in jCal have one difference to dates and times in + // iCalendar. In jCal date-parts are separated by dashes, and + // time-parts are separated by colons. It makes sense to just remove + // those. + $this->setValue( + array_map( + function($item) { + + return strtr($item, [':' => '', '-' => '']); + + }, + $value + ) + ); + + } + + /** + * We need to intercept offsetSet, because it may be used to alter the + * VALUE from DATE-TIME to DATE or vice-versa. + * + * @param string $name + * @param mixed $value + * + * @return void + */ + function offsetSet($name, $value) { + + parent::offsetSet($name, $value); + if (strtoupper($name) !== 'VALUE') { + return; + } + + // This will ensure that dates are correctly encoded. + $this->setDateTimes($this->getDateTimes()); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $messages = parent::validate($options); + $valueType = $this->getValueType(); + $values = $this->getParts(); + try { + foreach ($values as $value) { + switch ($valueType) { + case 'DATE' : + DateTimeParser::parseDate($value); + break; + case 'DATE-TIME' : + DateTimeParser::parseDateTime($value); + break; + } + } + } catch (InvalidDataException $e) { + $messages[] = [ + 'level' => 3, + 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, + 'node' => $this, + ]; + } + return $messages; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php new file mode 100644 index 000000000000..7b7e1ce8ea53 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php @@ -0,0 +1,85 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Property; + +/** + * Duration property. + * + * This object represents DURATION values, as defined here: + * + * http://tools.ietf.org/html/rfc5545#section-3.3.6 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Duration extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = ','; + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'DURATION'; + + } + + /** + * Returns a DateInterval representation of the Duration property. + * + * If the property has more than one value, only the first is returned. + * + * @return \DateInterval + */ + function getDateInterval() { + + $parts = $this->getParts(); + $value = $parts[0]; + return DateTimeParser::parseDuration($value); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Period.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Period.php new file mode 100644 index 000000000000..d35b425aa8ef --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Period.php @@ -0,0 +1,155 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Property; +use Sabre\Xml; + +/** + * Period property. + * + * This object represents PERIOD values, as defined here: + * + * http://tools.ietf.org/html/rfc5545#section-3.8.2.6 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Period extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = ','; + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue(explode($this->delimiter, $val)); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'PERIOD'; + + } + + /** + * Sets the json value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + $value = array_map( + function($item) { + + return strtr(implode('/', $item), [':' => '', '-' => '']); + + }, + $value + ); + parent::setJsonValue($value); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $return = []; + foreach ($this->getParts() as $item) { + + list($start, $end) = explode('/', $item, 2); + + $start = DateTimeParser::parseDateTime($start); + + // This is a duration value. + if ($end[0] === 'P') { + $return[] = [ + $start->format('Y-m-d\\TH:i:s'), + $end + ]; + } else { + $end = DateTimeParser::parseDateTime($end); + $return[] = [ + $start->format('Y-m-d\\TH:i:s'), + $end->format('Y-m-d\\TH:i:s'), + ]; + } + + } + + return $return; + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + $writer->startElement(strtolower($this->getValueType())); + $value = $this->getJsonValue(); + $writer->writeElement('start', $value[0][0]); + + if ($value[0][1][0] === 'P') { + $writer->writeElement('duration', $value[0][1]); + } + else { + $writer->writeElement('end', $value[0][1]); + } + + $writer->endElement(); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php new file mode 100644 index 000000000000..434b770884b0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php @@ -0,0 +1,359 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\Property; +use Sabre\Xml; + +/** + * Recur property. + * + * This object represents RECUR properties. + * These values are just used for RRULE and the now deprecated EXRULE. + * + * The RRULE property may look something like this: + * + * RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5. + * + * This property exposes this as a key=>value array that is accessible using + * getParts, and may be set using setParts. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Recur extends Property { + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * @param string|array $value + * + * @return void + */ + function setValue($value) { + + // If we're getting the data from json, we'll be receiving an object + if ($value instanceof \StdClass) { + $value = (array)$value; + } + + if (is_array($value)) { + $newVal = []; + foreach ($value as $k => $v) { + + if (is_string($v)) { + $v = strtoupper($v); + + // The value had multiple sub-values + if (strpos($v, ',') !== false) { + $v = explode(',', $v); + } + if (strcmp($k, 'until') === 0) { + $v = strtr($v, [':' => '', '-' => '']); + } + } elseif (is_array($v)) { + $v = array_map('strtoupper', $v); + } + + $newVal[strtoupper($k)] = $v; + } + $this->value = $newVal; + } elseif (is_string($value)) { + $this->value = self::stringToArray($value); + } else { + throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); + } + + } + + /** + * Returns the current value. + * + * This method will always return a singular value. If this was a + * multi-value object, some decision will be made first on how to represent + * it as a string. + * + * To get the correct multi-value version, use getParts. + * + * @return string + */ + function getValue() { + + $out = []; + foreach ($this->value as $key => $value) { + $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value); + } + return strtoupper(implode(';', $out)); + + } + + /** + * Sets a multi-valued property. + * + * @param array $parts + * @return void + */ + function setParts(array $parts) { + + $this->setValue($parts); + + } + + /** + * Returns a multi-valued property. + * + * This method always returns an array, if there was only a single value, + * it will still be wrapped in an array. + * + * @return array + */ + function getParts() { + + return $this->value; + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return $this->getValue(); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'RECUR'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $values = []; + foreach ($this->getParts() as $k => $v) { + if (strcmp($k, 'UNTIL') === 0) { + $date = new DateTime($this->root, null, $v); + $values[strtolower($k)] = $date->getJsonValue()[0]; + } elseif (strcmp($k, 'COUNT') === 0) { + $values[strtolower($k)] = intval($v); + } else { + $values[strtolower($k)] = $v; + } + } + return [$values]; + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + $valueType = strtolower($this->getValueType()); + + foreach ($this->getJsonValue() as $value) { + $writer->writeElement($valueType, $value); + } + + } + + /** + * Parses an RRULE value string, and turns it into a struct-ish array. + * + * @param string $value + * + * @return array + */ + static function stringToArray($value) { + + $value = strtoupper($value); + $newValue = []; + foreach (explode(';', $value) as $part) { + + // Skipping empty parts. + if (empty($part)) { + continue; + } + list($partName, $partValue) = explode('=', $part); + + // The value itself had multiple values.. + if (strpos($partValue, ',') !== false) { + $partValue = explode(',', $partValue); + } + $newValue[$partName] = $partValue; + + } + + return $newValue; + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $repair = ($options & self::REPAIR); + + $warnings = parent::validate($options); + $values = $this->getParts(); + + foreach ($values as $key => $value) { + + if ($value === '') { + $warnings[] = [ + 'level' => $repair ? 1 : 3, + 'message' => 'Invalid value for ' . $key . ' in ' . $this->name, + 'node' => $this + ]; + if ($repair) { + unset($values[$key]); + } + } elseif ($key == 'BYMONTH') { + $byMonth = (array)$value; + foreach ($byMonth as $i => $v) { + if (!is_numeric($v) || (int)$v < 1 || (int)$v > 12) { + $warnings[] = [ + 'level' => $repair ? 1 : 3, + 'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!', + 'node' => $this + ]; + if ($repair) { + if (is_array($value)) { + unset($values[$key][$i]); + } else { + unset($values[$key]); + } + } + } + } + // if there is no valid entry left, remove the whole value + if (is_array($value) && empty($values[$key])) { + unset($values[$key]); + } + } elseif ($key == 'BYWEEKNO') { + $byWeekNo = (array)$value; + foreach ($byWeekNo as $i => $v) { + if (!is_numeric($v) || (int)$v < -53 || (int)$v == 0 || (int)$v > 53) { + $warnings[] = [ + 'level' => $repair ? 1 : 3, + 'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', + 'node' => $this + ]; + if ($repair) { + if (is_array($value)) { + unset($values[$key][$i]); + } else { + unset($values[$key]); + } + } + } + } + // if there is no valid entry left, remove the whole value + if (is_array($value) && empty($values[$key])) { + unset($values[$key]); + } + } elseif ($key == 'BYYEARDAY') { + $byYearDay = (array)$value; + foreach ($byYearDay as $i => $v) { + if (!is_numeric($v) || (int)$v < -366 || (int)$v == 0 || (int)$v > 366) { + $warnings[] = [ + 'level' => $repair ? 1 : 3, + 'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', + 'node' => $this + ]; + if ($repair) { + if (is_array($value)) { + unset($values[$key][$i]); + } else { + unset($values[$key]); + } + } + } + } + // if there is no valid entry left, remove the whole value + if (is_array($value) && empty($values[$key])) { + unset($values[$key]); + } + } + + } + if (!isset($values['FREQ'])) { + $warnings[] = [ + 'level' => $repair ? 1 : 3, + 'message' => 'FREQ is required in ' . $this->name, + 'node' => $this + ]; + if ($repair) { + $this->parent->remove($this); + } + } + if ($repair) { + $this->setValue($values); + } + + return $warnings; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/IntegerValue.php b/libs/composer/vendor/sabre/vobject/lib/Property/IntegerValue.php new file mode 100644 index 000000000000..5bd1887fad2b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/IntegerValue.php @@ -0,0 +1,88 @@ +<?php + +namespace Sabre\VObject\Property; + +use + Sabre\VObject\Property; + +/** + * Integer property. + * + * This object represents INTEGER values. These are always a single integer. + * They may be preceeded by either + or -. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class IntegerValue extends Property { + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue((int)$val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return $this->value; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'INTEGER'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return [(int)$this->getValue()]; + + } + + /** + * Hydrate data from a XML subtree, as it would appear in a xCard or xCal + * object. + * + * @param array $value + * + * @return void + */ + function setXmlValue(array $value) { + + $value = array_map('intval', $value); + parent::setXmlValue($value); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Text.php b/libs/composer/vendor/sabre/vobject/lib/Property/Text.php new file mode 100644 index 000000000000..47a86ccc9a5f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Text.php @@ -0,0 +1,413 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Component; +use Sabre\VObject\Document; +use Sabre\VObject\Parser\MimeDir; +use Sabre\VObject\Property; +use Sabre\Xml; + +/** + * Text property. + * + * This object represents TEXT values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Text extends Property { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string + */ + public $delimiter = ','; + + /** + * List of properties that are considered 'structured'. + * + * @var array + */ + protected $structuredValues = [ + // vCard + 'N', + 'ADR', + 'ORG', + 'GENDER', + 'CLIENTPIDMAP', + + // iCalendar + 'REQUEST-STATUS', + ]; + + /** + * Some text components have a minimum number of components. + * + * N must for instance be represented as 5 components, separated by ;, even + * if the last few components are unused. + * + * @var array + */ + protected $minimumPropertyValues = [ + 'N' => 5, + 'ADR' => 7, + ]; + + /** + * Creates the property. + * + * You can specify the parameters either in key=>value syntax, in which case + * parameters will automatically be created, or you can just pass a list of + * Parameter objects. + * + * @param Component $root The root document + * @param string $name + * @param string|array|null $value + * @param array $parameters List of parameters + * @param string $group The vcard property group + * + * @return void + */ + function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) { + + // There's two types of multi-valued text properties: + // 1. multivalue properties. + // 2. structured value properties + // + // The former is always separated by a comma, the latter by semi-colon. + if (in_array($name, $this->structuredValues)) { + $this->delimiter = ';'; + } + + parent::__construct($root, $name, $value, $parameters, $group); + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue(MimeDir::unescapeValue($val, $this->delimiter)); + + } + + /** + * Sets the value as a quoted-printable encoded string. + * + * @param string $val + * + * @return void + */ + function setQuotedPrintableValue($val) { + + $val = quoted_printable_decode($val); + + // Quoted printable only appears in vCard 2.1, and the only character + // that may be escaped there is ;. So we are simply splitting on just + // that. + // + // We also don't have to unescape \\, so all we need to look for is a ; + // that's not preceeded with a \. + $regex = '# (?<!\\\\) ; #x'; + $matches = preg_split($regex, $val); + $this->setValue($matches); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + $val = $this->getParts(); + + if (isset($this->minimumPropertyValues[$this->name])) { + $val = array_pad($val, $this->minimumPropertyValues[$this->name], ''); + } + + foreach ($val as &$item) { + + if (!is_array($item)) { + $item = [$item]; + } + + foreach ($item as &$subItem) { + $subItem = strtr( + $subItem, + [ + '\\' => '\\\\', + ';' => '\;', + ',' => '\,', + "\n" => '\n', + "\r" => "", + ] + ); + } + $item = implode(',', $item); + + } + + return implode($this->delimiter, $val); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + // Structured text values should always be returned as a single + // array-item. Multi-value text should be returned as multiple items in + // the top-array. + if (in_array($this->name, $this->structuredValues)) { + return [$this->getParts()]; + } + return $this->getParts(); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'TEXT'; + + } + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + function serialize() { + + // We need to kick in a special type of encoding, if it's a 2.1 vcard. + if ($this->root->getDocumentType() !== Document::VCARD21) { + return parent::serialize(); + } + + $val = $this->getParts(); + + if (isset($this->minimumPropertyValues[$this->name])) { + $val = \array_pad($val, $this->minimumPropertyValues[$this->name], ''); + } + + // Imploding multiple parts into a single value, and splitting the + // values with ;. + if (\count($val) > 1) { + foreach ($val as $k => $v) { + $val[$k] = \str_replace(';', '\;', $v); + } + $val = \implode(';', $val); + } else { + $val = $val[0]; + } + + $str = $this->name; + if ($this->group) $str = $this->group . '.' . $this->name; + foreach ($this->parameters as $param) { + + if ($param->getValue() === 'QUOTED-PRINTABLE') { + continue; + } + $str .= ';' . $param->serialize(); + + } + + + + // If the resulting value contains a \n, we must encode it as + // quoted-printable. + if (\strpos($val, "\n") !== false) { + + $str .= ';ENCODING=QUOTED-PRINTABLE:'; + $lastLine = $str; + $out = null; + + // The PHP built-in quoted-printable-encode does not correctly + // encode newlines for us. Specifically, the \r\n sequence must in + // vcards be encoded as =0D=OA and we must insert soft-newlines + // every 75 bytes. + for ($ii = 0;$ii < \strlen($val);$ii++) { + $ord = \ord($val[$ii]); + // These characters are encoded as themselves. + if ($ord >= 32 && $ord <= 126) { + $lastLine .= $val[$ii]; + } else { + $lastLine .= '=' . \strtoupper(\bin2hex($val[$ii])); + } + if (\strlen($lastLine) >= 75) { + // Soft line break + $out .= $lastLine . "=\r\n "; + $lastLine = null; + } + + } + if (!\is_null($lastLine)) $out .= $lastLine . "\r\n"; + return $out; + + } else { + $str .= ':' . $val; + + $str = \preg_replace( + '/( + (?:^.)? # 1 additional byte in first line because of missing single space (see next line) + .{1,74} # max 75 bytes per line (1 byte is used for a single space added after every CRLF) + (?![\x80-\xbf]) # prevent splitting multibyte characters + )/x', + "$1\r\n ", + $str + ); + + // remove single space after last CRLF + return \substr($str, 0, -1); + + } + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + $values = $this->getParts(); + + $map = function($items) use ($values, $writer) { + foreach ($items as $i => $item) { + $writer->writeElement( + $item, + !empty($values[$i]) ? $values[$i] : null + ); + } + }; + + switch ($this->name) { + + // Special-casing the REQUEST-STATUS property. + // + // See: + // http://tools.ietf.org/html/rfc6321#section-3.4.1.3 + case 'REQUEST-STATUS': + $writer->writeElement('code', $values[0]); + $writer->writeElement('description', $values[1]); + + if (isset($values[2])) { + $writer->writeElement('data', $values[2]); + } + break; + + case 'N': + $map([ + 'surname', + 'given', + 'additional', + 'prefix', + 'suffix' + ]); + break; + + case 'GENDER': + $map([ + 'sex', + 'text' + ]); + break; + + case 'ADR': + $map([ + 'pobox', + 'ext', + 'street', + 'locality', + 'region', + 'code', + 'country' + ]); + break; + + case 'CLIENTPIDMAP': + $map([ + 'sourceid', + 'uri' + ]); + break; + + default: + parent::xmlSerializeValue($writer); + } + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * - Node::REPAIR - If something is broken, and automatic repair may + * be attempted. + * + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $warnings = parent::validate($options); + + if (isset($this->minimumPropertyValues[$this->name])) { + + $minimum = $this->minimumPropertyValues[$this->name]; + $parts = $this->getParts(); + if (count($parts) < $minimum) { + $warnings[] = [ + 'level' => $options & self::REPAIR ? 1 : 3, + 'message' => 'The ' . $this->name . ' property must have at least ' . $minimum . ' values. It only has ' . count($parts), + 'node' => $this, + ]; + if ($options & self::REPAIR) { + $parts = array_pad($parts, $minimum, ''); + $this->setParts($parts); + } + } + + } + return $warnings; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Time.php b/libs/composer/vendor/sabre/vobject/lib/Property/Time.php new file mode 100644 index 000000000000..dbafc3b85cd1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Time.php @@ -0,0 +1,144 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\DateTimeParser; + +/** + * Time property. + * + * This object encodes TIME values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Time extends Text { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'TIME'; + + } + + /** + * Sets the JSON value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + // Removing colons from value. + $value = str_replace( + ':', + '', + $value + ); + + if (count($value) === 1) { + $this->setValue(reset($value)); + } else { + $this->setValue($value); + } + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $parts = DateTimeParser::parseVCardTime($this->getValue()); + $timeStr = ''; + + // Hour + if (!is_null($parts['hour'])) { + $timeStr .= $parts['hour']; + + if (!is_null($parts['minute'])) { + $timeStr .= ':'; + } + } else { + // We know either minute or second _must_ be set, so we insert a + // dash for an empty value. + $timeStr .= '-'; + } + + // Minute + if (!is_null($parts['minute'])) { + $timeStr .= $parts['minute']; + + if (!is_null($parts['second'])) { + $timeStr .= ':'; + } + } else { + if (isset($parts['second'])) { + // Dash for empty minute + $timeStr .= '-'; + } + } + + // Second + if (!is_null($parts['second'])) { + $timeStr .= $parts['second']; + } + + // Timezone + if (!is_null($parts['timezone'])) { + if ($parts['timezone'] === 'Z') { + $timeStr .= 'Z'; + } else { + $timeStr .= + preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']); + } + } + + return [$timeStr]; + + } + + /** + * Hydrate data from a XML subtree, as it would appear in a xCard or xCal + * object. + * + * @param array $value + * + * @return void + */ + function setXmlValue(array $value) { + + $value = array_map( + function($value) { + return str_replace(':', '', $value); + }, + $value + ); + parent::setXmlValue($value); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Unknown.php b/libs/composer/vendor/sabre/vobject/lib/Property/Unknown.php new file mode 100644 index 000000000000..7a337386835f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Unknown.php @@ -0,0 +1,44 @@ +<?php + +namespace Sabre\VObject\Property; + +/** + * Unknown property. + * + * This object represents any properties not recognized by the parser. + * This type of value has been introduced by the jCal, jCard specs. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Unknown extends Text { + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return [$this->getRawMimeDirValue()]; + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'UNKNOWN'; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/Uri.php b/libs/composer/vendor/sabre/vobject/lib/Property/Uri.php new file mode 100644 index 000000000000..88fcfaab8886 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/Uri.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Parameter; +use Sabre\VObject\Property; + +/** + * URI property. + * + * This object encodes URI values. vCard 2.1 calls these URL. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Uri extends Text { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'URI'; + + } + + /** + * Returns an iterable list of children. + * + * @return array + */ + function parameters() { + + $parameters = parent::parameters(); + if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) { + // If we are encoding a URI value, and this URI value has no + // VALUE=URI parameter, we add it anyway. + // + // This is not required by any spec, but both Apple iCal and Apple + // AddressBook (at least in version 10.8) will trip over this if + // this is not set, and so it improves compatibility. + // + // See Issue #227 and #235 + $parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI'); + } + return $parameters; + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + // Normally we don't need to do any type of unescaping for these + // properties, however.. we've noticed that Google Contacts + // specifically escapes the colon (:) with a blackslash. While I have + // no clue why they thought that was a good idea, I'm unescaping it + // anyway. + // + // Good thing backslashes are not allowed in urls. Makes it easy to + // assume that a backslash is always intended as an escape character. + if ($this->name === 'URL') { + $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x'; + $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $newVal = ''; + foreach ($matches as $match) { + switch ($match) { + case '\:' : + $newVal .= ':'; + break; + default : + $newVal .= $match; + break; + } + } + $this->value = $newVal; + } else { + $this->value = strtr($val, ['\,' => ',']); + } + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + if (is_array($this->value)) { + $value = $this->value[0]; + } else { + $value = $this->value; + } + + return strtr($value, [',' => '\,']); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/UtcOffset.php b/libs/composer/vendor/sabre/vobject/lib/Property/UtcOffset.php new file mode 100644 index 000000000000..61895c48eb7a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/UtcOffset.php @@ -0,0 +1,77 @@ +<?php + +namespace Sabre\VObject\Property; + +/** + * UtcOffset property. + * + * This object encodes UTC-OFFSET values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class UtcOffset extends Text { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'UTC-OFFSET'; + + } + + /** + * Sets the JSON value, as it would appear in a jCard or jCal object. + * + * The value must always be an array. + * + * @param array $value + * + * @return void + */ + function setJsonValue(array $value) { + + $value = array_map( + function($value) { + return str_replace(':', '', $value); + }, + $value + ); + parent::setJsonValue($value); + + } + + /** + * Returns the value, in the format it should be encoded for JSON. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + return array_map( + function($value) { + return substr($value, 0, -2) . ':' . + substr($value, -2); + }, + parent::getJsonValue() + ); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/VCard/Date.php b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/Date.php new file mode 100644 index 000000000000..1ef6dff34437 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/Date.php @@ -0,0 +1,43 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +/** + * Date property. + * + * This object encodes vCard DATE values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Date extends DateAndOrTime { + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'DATE'; + + } + + /** + * Sets the property as a DateTime object. + * + * @param \DateTimeInterface $dt + * + * @return void + */ + function setDateTime(\DateTimeInterface $dt) { + + $this->value = $dt->format('Ymd'); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php new file mode 100644 index 000000000000..3b4ae3bb50f7 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php @@ -0,0 +1,405 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\InvalidDataException; +use Sabre\VObject\Property; +use Sabre\Xml; + +/** + * DateAndOrTime property. + * + * This object encodes DATE-AND-OR-TIME values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class DateAndOrTime extends Property { + + /** + * Field separator. + * + * @var null|string + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'DATE-AND-OR-TIME'; + + } + + /** + * Sets a multi-valued property. + * + * You may also specify DateTimeInterface objects here. + * + * @param array $parts + * + * @return void + */ + function setParts(array $parts) { + + if (count($parts) > 1) { + throw new \InvalidArgumentException('Only one value allowed'); + } + if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) { + $this->setDateTime($parts[0]); + } else { + parent::setParts($parts); + } + + } + + /** + * Updates the current value. + * + * This may be either a single, or multiple strings in an array. + * + * Instead of strings, you may also use DateTimeInterface here. + * + * @param string|array|DateTimeInterface $value + * + * @return void + */ + function setValue($value) { + + if ($value instanceof DateTimeInterface) { + $this->setDateTime($value); + } else { + parent::setValue($value); + } + + } + + /** + * Sets the property as a DateTime object. + * + * @param DateTimeInterface $dt + * + * @return void + */ + function setDateTime(DateTimeInterface $dt) { + + $tz = $dt->getTimeZone(); + $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']); + + if ($isUtc) { + $value = $dt->format('Ymd\\THis\\Z'); + } else { + // Calculating the offset. + $value = $dt->format('Ymd\\THisO'); + } + + $this->value = $value; + + } + + /** + * Returns a date-time value. + * + * Note that if this property contained more than 1 date-time, only the + * first will be returned. To get an array with multiple values, call + * getDateTimes. + * + * If no time was specified, we will always use midnight (in the default + * timezone) as the time. + * + * If parts of the date were omitted, such as the year, we will grab the + * current values for those. So at the time of writing, if the year was + * omitted, we would have filled in 2014. + * + * @return DateTimeImmutable + */ + function getDateTime() { + + $now = new DateTime(); + + $tzFormat = $now->getTimezone()->getOffset($now) === 0 ? '\\Z' : 'O'; + $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat)); + + $dateParts = DateTimeParser::parseVCardDateTime($this->getValue()); + + // This sets all the missing parts to the current date/time. + // So if the year was missing for a birthday, we're making it 'this + // year'. + foreach ($dateParts as $k => $v) { + if (is_null($v)) { + $dateParts[$k] = $nowParts[$k]; + } + } + return new DateTimeImmutable("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]"); + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $parts = DateTimeParser::parseVCardDateTime($this->getValue()); + + $dateStr = ''; + + // Year + if (!is_null($parts['year'])) { + + $dateStr .= $parts['year']; + + if (!is_null($parts['month'])) { + // If a year and a month is set, we need to insert a separator + // dash. + $dateStr .= '-'; + } + + } else { + + if (!is_null($parts['month']) || !is_null($parts['date'])) { + // Inserting two dashes + $dateStr .= '--'; + } + + } + + // Month + if (!is_null($parts['month'])) { + + $dateStr .= $parts['month']; + + if (isset($parts['date'])) { + // If month and date are set, we need the separator dash. + $dateStr .= '-'; + } + + } elseif (isset($parts['date'])) { + // If the month is empty, and a date is set, we need a 'empty + // dash' + $dateStr .= '-'; + } + + // Date + if (!is_null($parts['date'])) { + $dateStr .= $parts['date']; + } + + + // Early exit if we don't have a time string. + if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) { + return [$dateStr]; + } + + $dateStr .= 'T'; + + // Hour + if (!is_null($parts['hour'])) { + + $dateStr .= $parts['hour']; + + if (!is_null($parts['minute'])) { + $dateStr .= ':'; + } + + } else { + // We know either minute or second _must_ be set, so we insert a + // dash for an empty value. + $dateStr .= '-'; + } + + // Minute + if (!is_null($parts['minute'])) { + + $dateStr .= $parts['minute']; + + if (!is_null($parts['second'])) { + $dateStr .= ':'; + } + + } elseif (isset($parts['second'])) { + // Dash for empty minute + $dateStr .= '-'; + } + + // Second + if (!is_null($parts['second'])) { + $dateStr .= $parts['second']; + } + + // Timezone + if (!is_null($parts['timezone'])) { + $dateStr .= $parts['timezone']; + } + + return [$dateStr]; + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + $valueType = strtolower($this->getValueType()); + $parts = DateTimeParser::parseVCardDateAndOrTime($this->getValue()); + $value = ''; + + // $d = defined + $d = function($part) use ($parts) { + return !is_null($parts[$part]); + }; + + // $r = read + $r = function($part) use ($parts) { + return $parts[$part]; + }; + + // From the Relax NG Schema. + // + // # 4.3.1 + // value-date = element date { + // xsd:string { pattern = "\d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d" } + // } + if (($d('year') || $d('month') || $d('date')) + && (!$d('hour') && !$d('minute') && !$d('second') && !$d('timezone'))) { + + if ($d('year') && $d('month') && $d('date')) { + $value .= $r('year') . $r('month') . $r('date'); + } elseif ($d('year') && $d('month') && !$d('date')) { + $value .= $r('year') . '-' . $r('month'); + } elseif (!$d('year') && $d('month')) { + $value .= '--' . $r('month') . $r('date'); + } elseif (!$d('year') && !$d('month') && $d('date')) { + $value .= '---' . $r('date'); + } + + // # 4.3.2 + // value-time = element time { + // xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d?)|--\d\d)" + // ~ "(Z|[+\-]\d\d(\d\d)?)?" } + // } + } elseif ((!$d('year') && !$d('month') && !$d('date')) + && ($d('hour') || $d('minute') || $d('second'))) { + + if ($d('hour')) { + $value .= $r('hour') . $r('minute') . $r('second'); + } elseif ($d('minute')) { + $value .= '-' . $r('minute') . $r('second'); + } elseif ($d('second')) { + $value .= '--' . $r('second'); + } + + $value .= $r('timezone'); + + // # 4.3.3 + // value-date-time = element date-time { + // xsd:string { pattern = "(\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?" + // ~ "(Z|[+\-]\d\d(\d\d)?)?" } + // } + } elseif ($d('date') && $d('hour')) { + + if ($d('year') && $d('month') && $d('date')) { + $value .= $r('year') . $r('month') . $r('date'); + } elseif (!$d('year') && $d('month') && $d('date')) { + $value .= '--' . $r('month') . $r('date'); + } elseif (!$d('year') && !$d('month') && $d('date')) { + $value .= '---' . $r('date'); + } + + $value .= 'T' . $r('hour') . $r('minute') . $r('second') . + $r('timezone'); + + } + + $writer->writeElement($valueType, $value); + + } + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return implode($this->delimiter, $this->getParts()); + + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * Node::REPAIR - May attempt to automatically repair the problem. + * + * 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 - An inconsequential issue + * 3 - A severe issue. + * + * @param int $options + * + * @return array + */ + function validate($options = 0) { + + $messages = parent::validate($options); + $value = $this->getValue(); + + try { + DateTimeParser::parseVCardDateTime($value); + } catch (InvalidDataException $e) { + $messages[] = [ + 'level' => 3, + 'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property', + 'node' => $this, + ]; + } + + return $messages; + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateTime.php b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateTime.php new file mode 100644 index 000000000000..e7c804ca7842 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/DateTime.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +/** + * DateTime property. + * + * This object encodes DATE-TIME values for vCards. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class DateTime extends DateAndOrTime { + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'DATE-TIME'; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php new file mode 100644 index 000000000000..aa7e9178d51a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php @@ -0,0 +1,60 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +use + Sabre\VObject\Property; + +/** + * LanguageTag property. + * + * This object represents LANGUAGE-TAG values as used in vCards. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LanguageTag extends Property { + + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * This has been 'unfolded', so only 1 line will be passed. Unescaping is + * not yet done, but parameters are not included. + * + * @param string $val + * + * @return void + */ + function setRawMimeDirValue($val) { + + $this->setValue($val); + + } + + /** + * Returns a raw mime-dir representation of the value. + * + * @return string + */ + function getRawMimeDirValue() { + + return $this->getValue(); + + } + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'LANGUAGE-TAG'; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php new file mode 100644 index 000000000000..9d311f99d1b2 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Property\Text; +use Sabre\Xml; + +/** + * TimeStamp property. + * + * This object encodes TIMESTAMP values. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class TimeStamp extends Text { + + /** + * In case this is a multi-value property. This string will be used as a + * delimiter. + * + * @var string|null + */ + public $delimiter = null; + + /** + * Returns the type of value. + * + * This corresponds to the VALUE= parameter. Every property also has a + * 'default' valueType. + * + * @return string + */ + function getValueType() { + + return 'TIMESTAMP'; + + } + + /** + * Returns the value, in the format it should be encoded for json. + * + * This method must always return an array. + * + * @return array + */ + function getJsonValue() { + + $parts = DateTimeParser::parseVCardDateTime($this->getValue()); + + $dateStr = + $parts['year'] . '-' . + $parts['month'] . '-' . + $parts['date'] . 'T' . + $parts['hour'] . ':' . + $parts['minute'] . ':' . + $parts['second']; + + // Timezone + if (!is_null($parts['timezone'])) { + $dateStr .= $parts['timezone']; + } + + return [$dateStr]; + + } + + /** + * This method serializes only the value of a property. This is used to + * create xCard or xCal documents. + * + * @param Xml\Writer $writer XML writer. + * + * @return void + */ + protected function xmlSerializeValue(Xml\Writer $writer) { + + // xCard is the only XML and JSON format that has the same date and time + // format than vCard. + $valueType = strtolower($this->getValueType()); + $writer->writeElement($valueType, $this->getValue()); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Reader.php b/libs/composer/vendor/sabre/vobject/lib/Reader.php new file mode 100644 index 000000000000..70992933796e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Reader.php @@ -0,0 +1,98 @@ +<?php + +namespace Sabre\VObject; + +/** + * iCalendar/vCard/jCal/jCard/xCal/xCard reader object. + * + * This object provides a few (static) convenience methods to quickly access + * the parsers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Reader { + + /** + * If this option is passed to the reader, it will be less strict about the + * validity of the lines. + */ + const OPTION_FORGIVING = 1; + + /** + * If this option is turned on, any lines we cannot parse will be ignored + * by the reader. + */ + const OPTION_IGNORE_INVALID_LINES = 2; + + /** + * Parses a vCard or iCalendar object, and returns the top component. + * + * The options argument is a bitfield. Pass any of the OPTIONS constant to + * alter the parsers' behaviour. + * + * You can either supply a string, or a readable stream for input. + * + * @param string|resource $data + * @param int $options + * @param string $charset + * @return Document + */ + static function read($data, $options = 0, $charset = 'UTF-8') { + + $parser = new Parser\MimeDir(); + $parser->setCharset($charset); + $result = $parser->parse($data, $options); + + return $result; + + } + + /** + * Parses a jCard or jCal object, and returns the top component. + * + * The options argument is a bitfield. Pass any of the OPTIONS constant to + * alter the parsers' behaviour. + * + * You can either a string, a readable stream, or an array for it's input. + * Specifying the array is useful if json_decode was already called on the + * input. + * + * @param string|resource|array $data + * @param int $options + * + * @return Document + */ + static function readJson($data, $options = 0) { + + $parser = new Parser\Json(); + $result = $parser->parse($data, $options); + + return $result; + + } + + /** + * Parses a xCard or xCal object, and returns the top component. + * + * The options argument is a bitfield. Pass any of the OPTIONS constant to + * alter the parsers' behaviour. + * + * You can either supply a string, or a readable stream for input. + * + * @param string|resource $data + * @param int $options + * + * @return Document + */ + static function readXML($data, $options = 0) { + + $parser = new Parser\XML(); + $result = $parser->parse($data, $options); + + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Recur/EventIterator.php b/libs/composer/vendor/sabre/vobject/lib/Recur/EventIterator.php new file mode 100644 index 000000000000..d313305a0dda --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Recur/EventIterator.php @@ -0,0 +1,513 @@ +<?php + +namespace Sabre\VObject\Recur; + +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use Sabre\VObject\Component; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\Settings; + +/** + * This class is used to determine new for a recurring event, when the next + * events occur. + * + * This iterator may loop infinitely in the future, therefore it is important + * that if you use this class, you set hard limits for the amount of iterations + * you want to handle. + * + * Note that currently there is not full support for the entire iCalendar + * specification, as it's very complex and contains a lot of permutations + * that's not yet used very often in software. + * + * For the focus has been on features as they actually appear in Calendaring + * software, but this may well get expanded as needed / on demand + * + * The following RRULE properties are supported + * * UNTIL + * * INTERVAL + * * COUNT + * * FREQ=DAILY + * * BYDAY + * * BYHOUR + * * BYMONTH + * * FREQ=WEEKLY + * * BYDAY + * * BYHOUR + * * WKST + * * FREQ=MONTHLY + * * BYMONTHDAY + * * BYDAY + * * BYSETPOS + * * FREQ=YEARLY + * * BYMONTH + * * BYYEARDAY + * * BYWEEKNO + * * BYMONTHDAY (only if BYMONTH is also set) + * * BYDAY (only if BYMONTH is also set) + * + * Anything beyond this is 'undefined', which means that it may get ignored, or + * you may get unexpected results. The effect is that in some applications the + * specified recurrence may look incorrect, or is missing. + * + * The recurrence iterator also does not yet support THISANDFUTURE. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class EventIterator implements \Iterator { + + /** + * Reference timeZone for floating dates and times. + * + * @var DateTimeZone + */ + protected $timeZone; + + /** + * True if we're iterating an all-day event. + * + * @var bool + */ + protected $allDay = false; + + /** + * Creates the iterator. + * + * There's three ways to set up the iterator. + * + * 1. You can pass a VCALENDAR component and a UID. + * 2. You can pass an array of VEVENTs (all UIDS should match). + * 3. You can pass a single VEVENT component. + * + * Only the second method is recomended. The other 1 and 3 will be removed + * at some point in the future. + * + * The $uid parameter is only required for the first method. + * + * @param Component|array $input + * @param string|null $uid + * @param DateTimeZone $timeZone Reference timezone for floating dates and + * times. + */ + function __construct($input, $uid = null, DateTimeZone $timeZone = null) { + + if (is_null($timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->timeZone = $timeZone; + + if (is_array($input)) { + $events = $input; + } elseif ($input instanceof VEvent) { + // Single instance mode. + $events = [$input]; + } else { + // Calendar + UID mode. + $uid = (string)$uid; + if (!$uid) { + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); + } + if (!isset($input->VEVENT)) { + throw new InvalidArgumentException('No events found in this calendar'); + } + $events = $input->getByUID($uid); + + } + + foreach ($events as $vevent) { + + if (!isset($vevent->{'RECURRENCE-ID'})) { + + $this->masterEvent = $vevent; + + } else { + + $this->exceptions[ + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() + ] = true; + $this->overriddenEvents[] = $vevent; + + } + + } + + if (!$this->masterEvent) { + // No base event was found. CalDAV does allow cases where only + // overridden instances are stored. + // + // In this particular case, we're just going to grab the first + // event and use that instead. This may not always give the + // desired result. + if (!count($this->overriddenEvents)) { + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); + } + $this->masterEvent = array_shift($this->overriddenEvents); + } + + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); + + if (isset($this->masterEvent->EXDATE)) { + + foreach ($this->masterEvent->EXDATE as $exDate) { + + foreach ($exDate->getDateTimes($this->timeZone) as $dt) { + $this->exceptions[$dt->getTimeStamp()] = true; + } + + } + + } + + if (isset($this->masterEvent->DTEND)) { + $this->eventDuration = + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - + $this->startDate->getTimeStamp(); + } elseif (isset($this->masterEvent->DURATION)) { + $duration = $this->masterEvent->DURATION->getDateInterval(); + $end = clone $this->startDate; + $end = $end->add($duration); + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); + } elseif ($this->allDay) { + $this->eventDuration = 3600 * 24; + } else { + $this->eventDuration = 0; + } + + if (isset($this->masterEvent->RDATE)) { + $this->recurIterator = new RDateIterator( + $this->masterEvent->RDATE->getParts(), + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { + $this->recurIterator = new RRuleIterator( + $this->masterEvent->RRULE->getParts(), + $this->startDate + ); + } else { + $this->recurIterator = new RRuleIterator( + [ + 'FREQ' => 'DAILY', + 'COUNT' => 1, + ], + $this->startDate + ); + } + + $this->rewind(); + if (!$this->valid()) { + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); + } + + } + + /** + * Returns the date for the current position of the iterator. + * + * @return DateTimeImmutable + */ + function current() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the start date for the current iteration of the + * event. + * + * @return DateTimeImmutable + */ + function getDtStart() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the end date for the current iteration of the + * event. + * + * @return DateTimeImmutable + */ + function getDtEnd() { + + if (!$this->valid()) { + return; + } + $end = clone $this->currentDate; + return $end->modify('+' . $this->eventDuration . ' seconds'); + + } + + /** + * Returns a VEVENT for the current iterations of the event. + * + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND + * altered. + * + * @return VEvent + */ + function getEventObject() { + + if ($this->currentOverriddenEvent) { + return $this->currentOverriddenEvent; + } + + $event = clone $this->masterEvent; + + // Ignoring the following block, because PHPUnit's code coverage + // ignores most of these lines, and this messes with our stats. + // + // @codeCoverageIgnoreStart + unset( + $event->RRULE, + $event->EXDATE, + $event->RDATE, + $event->EXRULE, + $event->{'RECURRENCE-ID'} + ); + // @codeCoverageIgnoreEnd + + $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); + } + $recurid = clone $event->DTSTART; + $recurid->name = 'RECURRENCE-ID'; + $event->add($recurid); + return $event; + + } + + /** + * Returns the current position of the iterator. + * + * This is for us simply a 0-based index. + * + * @return int + */ + function key() { + + // The counter is always 1 ahead. + return $this->counter - 1; + + } + + /** + * This is called after next, to see if the iterator is still at a valid + * position, or if it's at the end. + * + * @return bool + */ + function valid() { + + if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) { + throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences); + } + return !!$this->currentDate; + + } + + /** + * Sets the iterator back to the starting point. + */ + function rewind() { + + $this->recurIterator->rewind(); + // re-creating overridden event index. + $index = []; + foreach ($this->overriddenEvents as $key => $event) { + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); + $index[$stamp][] = $key; + } + krsort($index); + $this->counter = 0; + $this->overriddenEventsIndex = $index; + $this->currentOverriddenEvent = null; + + $this->nextDate = null; + $this->currentDate = clone $this->startDate; + + $this->next(); + + } + + /** + * Advances the iterator with one step. + * + * @return void + */ + function next() { + + $this->currentOverriddenEvent = null; + $this->counter++; + if ($this->nextDate) { + // We had a stored value. + $nextDate = $this->nextDate; + $this->nextDate = null; + } else { + // We need to ask rruleparser for the next date. + // We need to do this until we find a date that's not in the + // exception list. + do { + if (!$this->recurIterator->valid()) { + $nextDate = null; + break; + } + $nextDate = $this->recurIterator->current(); + $this->recurIterator->next(); + } while (isset($this->exceptions[$nextDate->getTimeStamp()])); + + } + + + // $nextDate now contains what rrule thinks is the next one, but an + // overridden event may cut ahead. + if ($this->overriddenEventsIndex) { + + $offsets = end($this->overriddenEventsIndex); + $timestamp = key($this->overriddenEventsIndex); + $offset = end($offsets); + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { + // Overridden event comes first. + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; + + // Putting the rrule next date aside. + $this->nextDate = $nextDate; + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); + + // Ensuring that this item will only be used once. + array_pop($this->overriddenEventsIndex[$timestamp]); + if (!$this->overriddenEventsIndex[$timestamp]) { + array_pop($this->overriddenEventsIndex); + } + + // Exit point! + return; + + } + + } + + $this->currentDate = $nextDate; + + } + + /** + * Quickly jump to a date in the future. + * + * @param DateTimeInterface $dateTime + */ + function fastForward(DateTimeInterface $dateTime) { + + while ($this->valid() && $this->getDtEnd() <= $dateTime) { + $this->next(); + } + + } + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + function isInfinite() { + + return $this->recurIterator->isInfinite(); + + } + + /** + * RRULE parser. + * + * @var RRuleIterator + */ + protected $recurIterator; + + /** + * The duration, in seconds, of the master event. + * + * We use this to calculate the DTEND for subsequent events. + */ + protected $eventDuration; + + /** + * A reference to the main (master) event. + * + * @var VEVENT + */ + protected $masterEvent; + + /** + * List of overridden events. + * + * @var array + */ + protected $overriddenEvents = []; + + /** + * Overridden event index. + * + * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent + * property. + * + * @var array + */ + protected $overriddenEventsIndex; + + /** + * A list of recurrence-id's that are either part of EXDATE, or are + * overridden. + * + * @var array + */ + protected $exceptions = []; + + /** + * Internal event counter. + * + * @var int + */ + protected $counter; + + /** + * The very start of the iteration process. + * + * @var DateTimeImmutable + */ + protected $startDate; + + /** + * Where we are currently in the iteration process. + * + * @var DateTimeImmutable + */ + protected $currentDate; + + /** + * The next date from the rrule parser. + * + * Sometimes we need to temporary store the next date, because an + * overridden event came before. + * + * @var DateTimeImmutable + */ + protected $nextDate; + + /** + * The event that overwrites the current iteration + * + * @var VEVENT + */ + protected $currentOverriddenEvent; + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Recur/MaxInstancesExceededException.php b/libs/composer/vendor/sabre/vobject/lib/Recur/MaxInstancesExceededException.php new file mode 100644 index 000000000000..264df7d2bd52 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Recur/MaxInstancesExceededException.php @@ -0,0 +1,16 @@ +<?php + +namespace Sabre\VObject\Recur; + +use Exception; + +/** + * This exception will get thrown when a recurrence rule generated more than + * the maximum number of instances. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class MaxInstancesExceededException extends Exception { +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Recur/NoInstancesException.php b/libs/composer/vendor/sabre/vobject/lib/Recur/NoInstancesException.php new file mode 100644 index 000000000000..8f8bb472bf59 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Recur/NoInstancesException.php @@ -0,0 +1,18 @@ +<?php + +namespace Sabre\VObject\Recur; + +use Exception; + +/** + * This exception gets thrown when a recurrence iterator produces 0 instances. + * + * This may happen when every occurence in a rrule is also in EXDATE. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class NoInstancesException extends Exception { + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Recur/RDateIterator.php b/libs/composer/vendor/sabre/vobject/lib/Recur/RDateIterator.php new file mode 100644 index 000000000000..f44960e123f9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Recur/RDateIterator.php @@ -0,0 +1,182 @@ +<?php + +namespace Sabre\VObject\Recur; + +use DateTimeInterface; +use Iterator; +use Sabre\VObject\DateTimeParser; + +/** + * RRuleParser. + * + * This class receives an RRULE string, and allows you to iterate to get a list + * of dates in that recurrence. + * + * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain + * 5 items, one for each day. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class RDateIterator implements Iterator { + + /** + * Creates the Iterator. + * + * @param string|array $rrule + * @param DateTimeInterface $start + */ + function __construct($rrule, DateTimeInterface $start) { + + $this->startDate = $start; + $this->parseRDate($rrule); + $this->currentDate = clone $this->startDate; + + } + + /* Implementation of the Iterator interface {{{ */ + + function current() { + + if (!$this->valid()) return; + return clone $this->currentDate; + + } + + /** + * Returns the current item number. + * + * @return int + */ + function key() { + + return $this->counter; + + } + + /** + * Returns whether the current item is a valid item for the recurrence + * iterator. + * + * @return bool + */ + function valid() { + + return ($this->counter <= count($this->dates)); + + } + + /** + * Resets the iterator. + * + * @return void + */ + function rewind() { + + $this->currentDate = clone $this->startDate; + $this->counter = 0; + + } + + /** + * Goes on to the next iteration. + * + * @return void + */ + function next() { + + $this->counter++; + if (!$this->valid()) return; + + $this->currentDate = + DateTimeParser::parse( + $this->dates[$this->counter - 1], + $this->startDate->getTimezone() + ); + + } + + /* End of Iterator implementation }}} */ + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + function isInfinite() { + + return false; + + } + + /** + * This method allows you to quickly go to the next occurrence after the + * specified date. + * + * @param DateTimeInterface $dt + * + * @return void + */ + function fastForward(DateTimeInterface $dt) { + + while ($this->valid() && $this->currentDate < $dt) { + $this->next(); + } + + } + + /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + * + * @var DateTimeInterface + */ + protected $startDate; + + /** + * The date of the current iteration. You can get this by calling + * ->current(). + * + * @var DateTimeInterface + */ + protected $currentDate; + + /** + * The current item in the list. + * + * You can get this number with the key() method. + * + * @var int + */ + protected $counter = 0; + + /* }}} */ + + /** + * This method receives a string from an RRULE property, and populates this + * class with all the values. + * + * @param string|array $rrule + * + * @return void + */ + protected function parseRDate($rdate) { + + if (is_string($rdate)) { + $rdate = explode(',', $rdate); + } + + $this->dates = $rdate; + + } + + /** + * Array with the RRULE dates + * + * @var array + */ + protected $dates = []; + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Recur/RRuleIterator.php b/libs/composer/vendor/sabre/vobject/lib/Recur/RRuleIterator.php new file mode 100644 index 000000000000..dbea1155dce3 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Recur/RRuleIterator.php @@ -0,0 +1,1013 @@ +<?php + +namespace Sabre\VObject\Recur; + +use DateTimeImmutable; +use DateTimeInterface; +use Iterator; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\InvalidDataException; +use Sabre\VObject\Property; + +/** + * RRuleParser. + * + * This class receives an RRULE string, and allows you to iterate to get a list + * of dates in that recurrence. + * + * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain + * 5 items, one for each day. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class RRuleIterator implements Iterator { + + /** + * Creates the Iterator. + * + * @param string|array $rrule + * @param DateTimeInterface $start + */ + function __construct($rrule, DateTimeInterface $start) { + + $this->startDate = $start; + $this->parseRRule($rrule); + $this->currentDate = clone $this->startDate; + + } + + /* Implementation of the Iterator interface {{{ */ + + function current() { + + if (!$this->valid()) return; + return clone $this->currentDate; + + } + + /** + * Returns the current item number. + * + * @return int + */ + function key() { + + return $this->counter; + + } + + /** + * Returns whether the current item is a valid item for the recurrence + * iterator. This will return false if we've gone beyond the UNTIL or COUNT + * statements. + * + * @return bool + */ + function valid() { + + if (!is_null($this->count)) { + return $this->counter < $this->count; + } + return is_null($this->until) || $this->currentDate <= $this->until; + + } + + /** + * Resets the iterator. + * + * @return void + */ + function rewind() { + + $this->currentDate = clone $this->startDate; + $this->counter = 0; + + } + + /** + * Goes on to the next iteration. + * + * @return void + */ + function next() { + + // Otherwise, we find the next event in the normal RRULE + // sequence. + switch ($this->frequency) { + + case 'hourly' : + $this->nextHourly(); + break; + + case 'daily' : + $this->nextDaily(); + break; + + case 'weekly' : + $this->nextWeekly(); + break; + + case 'monthly' : + $this->nextMonthly(); + break; + + case 'yearly' : + $this->nextYearly(); + break; + + } + $this->counter++; + + } + + /* End of Iterator implementation }}} */ + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + function isInfinite() { + + return !$this->count && !$this->until; + + } + + /** + * This method allows you to quickly go to the next occurrence after the + * specified date. + * + * @param DateTimeInterface $dt + * + * @return void + */ + function fastForward(DateTimeInterface $dt) { + + while ($this->valid() && $this->currentDate < $dt) { + $this->next(); + } + + } + + /** + * The reference start date/time for the rrule. + * + * All calculations are based on this initial date. + * + * @var DateTimeInterface + */ + protected $startDate; + + /** + * The date of the current iteration. You can get this by calling + * ->current(). + * + * @var DateTimeInterface + */ + protected $currentDate; + + /** + * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly, + * yearly. + * + * @var string + */ + protected $frequency; + + /** + * The number of recurrences, or 'null' if infinitely recurring. + * + * @var int + */ + protected $count; + + /** + * The interval. + * + * If for example frequency is set to daily, interval = 2 would mean every + * 2 days. + * + * @var int + */ + protected $interval = 1; + + /** + * The last instance of this recurrence, inclusively. + * + * @var DateTimeInterface|null + */ + protected $until; + + /** + * Which seconds to recur. + * + * This is an array of integers (between 0 and 60) + * + * @var array + */ + protected $bySecond; + + /** + * Which minutes to recur. + * + * This is an array of integers (between 0 and 59) + * + * @var array + */ + protected $byMinute; + + /** + * Which hours to recur. + * + * This is an array of integers (between 0 and 23) + * + * @var array + */ + protected $byHour; + + /** + * The current item in the list. + * + * You can get this number with the key() method. + * + * @var int + */ + protected $counter = 0; + + /** + * Which weekdays to recur. + * + * This is an array of weekdays + * + * This may also be preceeded by a positive or negative integer. If present, + * this indicates the nth occurrence of a specific day within the monthly or + * yearly rrule. For instance, -2TU indicates the second-last tuesday of + * the month, or year. + * + * @var array + */ + protected $byDay; + + /** + * Which days of the month to recur. + * + * This is an array of days of the months (1-31). The value can also be + * negative. -5 for instance means the 5th last day of the month. + * + * @var array + */ + protected $byMonthDay; + + /** + * Which days of the year to recur. + * + * This is an array with days of the year (1 to 366). The values can also + * be negative. For instance, -1 will always represent the last day of the + * year. (December 31st). + * + * @var array + */ + protected $byYearDay; + + /** + * Which week numbers to recur. + * + * This is an array of integers from 1 to 53. The values can also be + * negative. -1 will always refer to the last week of the year. + * + * @var array + */ + protected $byWeekNo; + + /** + * Which months to recur. + * + * This is an array of integers from 1 to 12. + * + * @var array + */ + protected $byMonth; + + /** + * Which items in an existing st to recur. + * + * These numbers work together with an existing by* rule. It specifies + * exactly which items of the existing by-rule to filter. + * + * Valid values are 1 to 366 and -1 to -366. As an example, this can be + * used to recur the last workday of the month. + * + * This would be done by setting frequency to 'monthly', byDay to + * 'MO,TU,WE,TH,FR' and bySetPos to -1. + * + * @var array + */ + protected $bySetPos; + + /** + * When the week starts. + * + * @var string + */ + protected $weekStart = 'MO'; + + /* Functions that advance the iterator {{{ */ + + /** + * Does the processing for advancing the iterator for hourly frequency. + * + * @return void + */ + protected function nextHourly() { + + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' hours'); + + } + + /** + * Does the processing for advancing the iterator for daily frequency. + * + * @return void + */ + protected function nextDaily() { + + if (!$this->byHour && !$this->byDay) { + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days'); + return; + } + + if (!empty($this->byHour)) { + $recurrenceHours = $this->getHours(); + } + + if (!empty($this->byDay)) { + $recurrenceDays = $this->getDays(); + } + + if (!empty($this->byMonth)) { + $recurrenceMonths = $this->getMonths(); + } + + do { + if ($this->byHour) { + if ($this->currentDate->format('G') == '23') { + // to obey the interval rule + $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' days'); + } + + $this->currentDate = $this->currentDate->modify('+1 hours'); + + } else { + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days'); + + } + + // Current month of the year + $currentMonth = $this->currentDate->format('n'); + + // Current day of the week + $currentDay = $this->currentDate->format('w'); + + // Current hour of the day + $currentHour = $this->currentDate->format('G'); + + } while ( + ($this->byDay && !in_array($currentDay, $recurrenceDays)) || + ($this->byHour && !in_array($currentHour, $recurrenceHours)) || + ($this->byMonth && !in_array($currentMonth, $recurrenceMonths)) + ); + + } + + /** + * Does the processing for advancing the iterator for weekly frequency. + * + * @return void + */ + protected function nextWeekly() { + + if (!$this->byHour && !$this->byDay) { + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' weeks'); + return; + } + + if ($this->byHour) { + $recurrenceHours = $this->getHours(); + } + + if ($this->byDay) { + $recurrenceDays = $this->getDays(); + } + + // First day of the week: + $firstDay = $this->dayMap[$this->weekStart]; + + do { + + if ($this->byHour) { + $this->currentDate = $this->currentDate->modify('+1 hours'); + } else { + $this->currentDate = $this->currentDate->modify('+1 days'); + } + + // Current day of the week + $currentDay = (int)$this->currentDate->format('w'); + + // Current hour of the day + $currentHour = (int)$this->currentDate->format('G'); + + // We need to roll over to the next week + if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) { + $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' weeks'); + + // We need to go to the first day of this week, but only if we + // are not already on this first day of this week. + if ($this->currentDate->format('w') != $firstDay) { + $this->currentDate = $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]); + } + } + + // We have a match + } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours))); + } + + /** + * Does the processing for advancing the iterator for monthly frequency. + * + * @return void + */ + protected function nextMonthly() { + + $currentDayOfMonth = $this->currentDate->format('j'); + if (!$this->byMonthDay && !$this->byDay) { + + // If the current day is higher than the 28th, rollover can + // occur to the next month. We Must skip these invalid + // entries. + if ($currentDayOfMonth < 29) { + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' months'); + } else { + $increase = 0; + do { + $increase++; + $tempDate = clone $this->currentDate; + $tempDate = $tempDate->modify('+ ' . ($this->interval * $increase) . ' months'); + } while ($tempDate->format('j') != $currentDayOfMonth); + $this->currentDate = $tempDate; + } + return; + } + + while (true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach ($occurrences as $occurrence) { + + // The first occurrence thats higher than the current + // day of the month wins. + if ($occurrence > $currentDayOfMonth) { + break 2; + } + + } + + // If we made it all the way here, it means there were no + // valid occurrences, and we need to advance to the next + // month. + // + // This line does not currently work in hhvm. Temporary workaround + // follows: + // $this->currentDate->modify('first day of this month'); + $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone()); + // end of workaround + $this->currentDate = $this->currentDate->modify('+ ' . $this->interval . ' months'); + + // This goes to 0 because we need to start counting at the + // beginning. + $currentDayOfMonth = 0; + + } + + $this->currentDate = $this->currentDate->setDate( + (int)$this->currentDate->format('Y'), + (int)$this->currentDate->format('n'), + (int)$occurrence + ); + + } + + /** + * Does the processing for advancing the iterator for yearly frequency. + * + * @return void + */ + protected function nextYearly() { + + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + // No sub-rules, so we just advance by year + if (empty($this->byMonth)) { + + // Unless it was a leap day! + if ($currentMonth == 2 && $currentDayOfMonth == 29) { + + $counter = 0; + do { + $counter++; + // Here we increase the year count by the interval, until + // we hit a date that's also in a leap year. + // + // We could just find the next interval that's dividable by + // 4, but that would ignore the rule that there's no leap + // year every year that's dividable by a 100, but not by + // 400. (1800, 1900, 2100). So we just rely on the datetime + // functions instead. + $nextDate = clone $this->currentDate; + $nextDate = $nextDate->modify('+ ' . ($this->interval * $counter) . ' years'); + } while ($nextDate->format('n') != 2); + + $this->currentDate = $nextDate; + + return; + + } + + if ($this->byWeekNo !== null) { // byWeekNo is an array with values from -53 to -1, or 1 to 53 + $dayOffsets = []; + if ($this->byDay) { + foreach ($this->byDay as $byDay) { + $dayOffsets[] = $this->dayMap[$byDay]; + } + } else { // default is Monday + $dayOffsets[] = 1; + } + + $currentYear = $this->currentDate->format('Y'); + + while (true) { + $checkDates = []; + + // loop through all WeekNo and Days to check all the combinations + foreach ($this->byWeekNo as $byWeekNo) { + foreach ($dayOffsets as $dayOffset) { + $date = clone $this->currentDate; + $date->setISODate($currentYear, $byWeekNo, $dayOffset); + + if ($date > $this->currentDate) { + $checkDates[] = $date; + } + } + } + + if (count($checkDates) > 0) { + $this->currentDate = min($checkDates); + return; + } + + // if there is no date found, check the next year + $currentYear += $this->interval; + } + } + + if ($this->byYearDay !== null) { // byYearDay is an array with values from -366 to -1, or 1 to 366 + $dayOffsets = []; + if ($this->byDay) { + foreach ($this->byDay as $byDay) { + $dayOffsets[] = $this->dayMap[$byDay]; + } + } else { // default is Monday-Sunday + $dayOffsets = [1,2,3,4,5,6,7]; + } + + $currentYear = $this->currentDate->format('Y'); + + while (true) { + $checkDates = []; + + // loop through all YearDay and Days to check all the combinations + foreach ($this->byYearDay as $byYearDay) { + $date = clone $this->currentDate; + $date = $date->setDate($currentYear, 1, 1); + if ($byYearDay > 0) { + $date = $date->add(new \DateInterval('P' . $byYearDay . 'D')); + } else { + $date = $date->sub(new \DateInterval('P' . abs($byYearDay) . 'D')); + } + + if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) { + $checkDates[] = $date; + } + } + + if (count($checkDates) > 0) { + $this->currentDate = min($checkDates); + return; + } + + // if there is no date found, check the next year + $currentYear += $this->interval; + } + } + + // The easiest form + $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' years'); + return; + + } + + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + $advancedToNewMonth = false; + + // If we got a byDay or getMonthDay filter, we must first expand + // further. + if ($this->byDay || $this->byMonthDay) { + + while (true) { + + $occurrences = $this->getMonthlyOccurrences(); + + foreach ($occurrences as $occurrence) { + + // The first occurrence that's higher than the current + // day of the month wins. + // If we advanced to the next month or year, the first + // occurrence is always correct. + if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) { + break 2; + } + + } + + // If we made it here, it means we need to advance to + // the next month or year. + $currentDayOfMonth = 1; + $advancedToNewMonth = true; + do { + + $currentMonth++; + if ($currentMonth > 12) { + $currentYear += $this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + + $this->currentDate = $this->currentDate->setDate( + (int)$currentYear, + (int)$currentMonth, + (int)$currentDayOfMonth + ); + + } + + // If we made it here, it means we got a valid occurrence + $this->currentDate = $this->currentDate->setDate( + (int)$currentYear, + (int)$currentMonth, + (int)$occurrence + ); + return; + + } else { + + // These are the 'byMonth' rules, if there are no byDay or + // byMonthDay sub-rules. + do { + + $currentMonth++; + if ($currentMonth > 12) { + $currentYear += $this->interval; + $currentMonth = 1; + } + } while (!in_array($currentMonth, $this->byMonth)); + $this->currentDate = $this->currentDate->setDate( + (int)$currentYear, + (int)$currentMonth, + (int)$currentDayOfMonth + ); + + return; + + } + + } + + /* }}} */ + + /** + * This method receives a string from an RRULE property, and populates this + * class with all the values. + * + * @param string|array $rrule + * + * @return void + */ + protected function parseRRule($rrule) { + + if (is_string($rrule)) { + $rrule = Property\ICalendar\Recur::stringToArray($rrule); + } + + foreach ($rrule as $key => $value) { + + $key = strtoupper($key); + switch ($key) { + + case 'FREQ' : + $value = strtolower($value); + if (!in_array( + $value, + ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'] + )) { + throw new InvalidDataException('Unknown value for FREQ=' . strtoupper($value)); + } + $this->frequency = $value; + break; + + case 'UNTIL' : + $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone()); + + // In some cases events are generated with an UNTIL= + // parameter before the actual start of the event. + // + // Not sure why this is happening. We assume that the + // intention was that the event only recurs once. + // + // So we are modifying the parameter so our code doesn't + // break. + if ($this->until < $this->startDate) { + $this->until = $this->startDate; + } + break; + + case 'INTERVAL' : + // No break + + case 'COUNT' : + $val = (int)$value; + if ($val < 1) { + throw new InvalidDataException(strtoupper($key) . ' in RRULE must be a positive integer!'); + } + $key = strtolower($key); + $this->$key = $val; + break; + + case 'BYSECOND' : + $this->bySecond = (array)$value; + break; + + case 'BYMINUTE' : + $this->byMinute = (array)$value; + break; + + case 'BYHOUR' : + $this->byHour = (array)$value; + break; + + case 'BYDAY' : + $value = (array)$value; + foreach ($value as $part) { + if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) { + throw new InvalidDataException('Invalid part in BYDAY clause: ' . $part); + } + } + $this->byDay = $value; + break; + + case 'BYMONTHDAY' : + $this->byMonthDay = (array)$value; + break; + + case 'BYYEARDAY' : + $this->byYearDay = (array)$value; + foreach ($this->byYearDay as $byYearDay) { + if (!is_numeric($byYearDay) || (int)$byYearDay < -366 || (int)$byYearDay == 0 || (int)$byYearDay > 366) { + throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!'); + } + } + break; + + case 'BYWEEKNO' : + $this->byWeekNo = (array)$value; + foreach ($this->byWeekNo as $byWeekNo) { + if (!is_numeric($byWeekNo) || (int)$byWeekNo < -53 || (int)$byWeekNo == 0 || (int)$byWeekNo > 53) { + throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!'); + } + } + break; + + case 'BYMONTH' : + $this->byMonth = (array)$value; + foreach ($this->byMonth as $byMonth) { + if (!is_numeric($byMonth) || (int)$byMonth < 1 || (int)$byMonth > 12) { + throw new InvalidDataException('BYMONTH in RRULE must have value(s) betweeen 1 and 12!'); + } + } + break; + + case 'BYSETPOS' : + $this->bySetPos = (array)$value; + break; + + case 'WKST' : + $this->weekStart = strtoupper($value); + break; + + default: + throw new InvalidDataException('Not supported: ' . strtoupper($key)); + + } + + } + + } + + /** + * Mappings between the day number and english day name. + * + * @var array + */ + protected $dayNames = [ + 0 => 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + ]; + + /** + * Returns all the occurrences for a monthly frequency with a 'byDay' or + * 'byMonthDay' expansion for the current month. + * + * The returned list is an array of integers with the day of month (1-31). + * + * @return array + */ + protected function getMonthlyOccurrences() { + + $startDate = clone $this->currentDate; + + $byDayResults = []; + + // Our strategy is to simply go through the byDays, advance the date to + // that point and add it to the results. + if ($this->byDay) foreach ($this->byDay as $day) { + + $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]]; + + + // Dayname will be something like 'wednesday'. Now we need to find + // all wednesdays in this month. + $dayHits = []; + + // workaround for missing 'first day of the month' support in hhvm + $checkDate = new \DateTime($startDate->format('Y-m-1')); + // workaround modify always advancing the date even if the current day is a $dayName in hhvm + if ($checkDate->format('l') !== $dayName) { + $checkDate = $checkDate->modify($dayName); + } + + do { + $dayHits[] = $checkDate->format('j'); + $checkDate = $checkDate->modify('next ' . $dayName); + } while ($checkDate->format('n') === $startDate->format('n')); + + // So now we have 'all wednesdays' for month. It is however + // possible that the user only really wanted the 1st, 2nd or last + // wednesday. + if (strlen($day) > 2) { + $offset = (int)substr($day, 0, -2); + + if ($offset > 0) { + // It is possible that the day does not exist, such as a + // 5th or 6th wednesday of the month. + if (isset($dayHits[$offset - 1])) { + $byDayResults[] = $dayHits[$offset - 1]; + } + } else { + + // if it was negative we count from the end of the array + // might not exist, fx. -5th tuesday + if (isset($dayHits[count($dayHits) + $offset])) { + $byDayResults[] = $dayHits[count($dayHits) + $offset]; + } + } + } else { + // There was no counter (first, second, last wednesdays), so we + // just need to add the all to the list). + $byDayResults = array_merge($byDayResults, $dayHits); + + } + + } + + $byMonthDayResults = []; + if ($this->byMonthDay) foreach ($this->byMonthDay as $monthDay) { + + // Removing values that are out of range for this month + if ($monthDay > $startDate->format('t') || + $monthDay < 0 - $startDate->format('t')) { + continue; + } + if ($monthDay > 0) { + $byMonthDayResults[] = $monthDay; + } else { + // Negative values + $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay; + } + } + + // If there was just byDay or just byMonthDay, they just specify our + // (almost) final list. If both were provided, then byDay limits the + // list. + if ($this->byMonthDay && $this->byDay) { + $result = array_intersect($byMonthDayResults, $byDayResults); + } elseif ($this->byMonthDay) { + $result = $byMonthDayResults; + } else { + $result = $byDayResults; + } + $result = array_unique($result); + sort($result, SORT_NUMERIC); + + // The last thing that needs checking is the BYSETPOS. If it's set, it + // means only certain items in the set survive the filter. + if (!$this->bySetPos) { + return $result; + } + + $filteredResult = []; + foreach ($this->bySetPos as $setPos) { + + if ($setPos < 0) { + $setPos = count($result) + ($setPos + 1); + } + if (isset($result[$setPos - 1])) { + $filteredResult[] = $result[$setPos - 1]; + } + } + + sort($filteredResult, SORT_NUMERIC); + return $filteredResult; + + } + + /** + * Simple mapping from iCalendar day names to day numbers. + * + * @var array + */ + protected $dayMap = [ + 'SU' => 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6, + ]; + + protected function getHours() { + + $recurrenceHours = []; + foreach ($this->byHour as $byHour) { + $recurrenceHours[] = $byHour; + } + + return $recurrenceHours; + } + + protected function getDays() { + + $recurrenceDays = []; + foreach ($this->byDay as $byDay) { + + // The day may be preceeded with a positive (+n) or + // negative (-n) integer. However, this does not make + // sense in 'weekly' so we ignore it here. + $recurrenceDays[] = $this->dayMap[substr($byDay, -2)]; + + } + + return $recurrenceDays; + } + + protected function getMonths() { + + $recurrenceMonths = []; + foreach ($this->byMonth as $byMonth) { + $recurrenceMonths[] = $byMonth; + } + + return $recurrenceMonths; + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Settings.php b/libs/composer/vendor/sabre/vobject/lib/Settings.php new file mode 100644 index 000000000000..3f274ba8eee6 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Settings.php @@ -0,0 +1,56 @@ +<?php + +namespace Sabre\VObject; + +/** + * This class provides a list of global defaults for vobject. + * + * Some of these started to appear in various classes, so it made a bit more + * sense to centralize them, so it's easier for user to find and change these. + * + * The global nature of them does mean that changing the settings for one + * instance has a global influence. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Settings { + + /** + * The minimum date we accept for various calculations with dates, such as + * recurrences. + * + * The choice of 1900 is pretty arbitrary, but it covers most common + * use-cases. In particular, it covers birthdates for virtually everyone + * alive on earth, which is less than 5 people at the time of writing. + */ + static $minDate = '1900-01-01'; + + /** + * The maximum date we accept for various calculations with dates, such as + * recurrences. + * + * The choice of 2100 is pretty arbitrary, but should cover most + * appointments made for many years to come. + */ + static $maxDate = '2100-01-01'; + + /** + * The maximum number of recurrences that will be generated. + * + * This setting limits the maximum of recurring events that this library + * generates in its recurrence iterators. + * + * This is a security measure. Without this, it would be possible to craft + * specific events that recur many, many times, potentially DDOSing the + * server. + * + * The default (3500) allows creation of a dialy event that goes on for 10 + * years, which is hopefully long enough for most. + * + * Set this value to -1 to disable this control altogether. + */ + static $maxRecurrences = 3500; + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Splitter/ICalendar.php b/libs/composer/vendor/sabre/vobject/lib/Splitter/ICalendar.php new file mode 100644 index 000000000000..c0007ba01aef --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Splitter/ICalendar.php @@ -0,0 +1,113 @@ +<?php + +namespace Sabre\VObject\Splitter; + +use Sabre\VObject; +use Sabre\VObject\Component\VCalendar; + +/** + * Splitter. + * + * This class is responsible for splitting up iCalendar objects. + * + * This class expects a single VCALENDAR object with one or more + * calendar-objects inside. Objects with identical UID's will be combined into + * a single object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Dominik Tobschall (http://tobschall.de/) + * @author Armin Hackmann + * @license http://sabre.io/license/ Modified BSD License + */ +class ICalendar implements SplitterInterface { + + /** + * Timezones. + * + * @var array + */ + protected $vtimezones = []; + + /** + * iCalendar objects. + * + * @var array + */ + protected $objects = []; + + /** + * Constructor. + * + * The splitter should receive an readable file stream as it's input. + * + * @param resource $input + * @param int $options Parser options, see the OPTIONS constants. + */ + function __construct($input, $options = 0) { + + $data = VObject\Reader::read($input, $options); + + if (!$data instanceof VObject\Component\VCalendar) { + throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.'); + } + + foreach ($data->children() as $component) { + if (!$component instanceof VObject\Component) { + continue; + } + + // Get all timezones + if ($component->name === 'VTIMEZONE') { + $this->vtimezones[(string)$component->TZID] = $component; + continue; + } + + // Get component UID for recurring Events search + if (!$component->UID) { + $component->UID = sha1(microtime()) . '-vobjectimport'; + } + $uid = (string)$component->UID; + + // Take care of recurring events + if (!array_key_exists($uid, $this->objects)) { + $this->objects[$uid] = new VCalendar(); + } + + $this->objects[$uid]->add(clone $component); + } + + } + + /** + * Every time getNext() is called, a new object will be parsed, until we + * hit the end of the stream. + * + * When the end is reached, null will be returned. + * + * @return Sabre\VObject\Component|null + */ + function getNext() { + + if ($object = array_shift($this->objects)) { + + // create our baseobject + $object->version = '2.0'; + $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN'; + $object->calscale = 'GREGORIAN'; + + // add vtimezone information to obj (if we have it) + foreach ($this->vtimezones as $vtimezone) { + $object->add($vtimezone); + } + + return $object; + + } else { + + return; + + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php b/libs/composer/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php new file mode 100644 index 000000000000..8f827cc4b476 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\VObject\Splitter; + +/** + * VObject splitter. + * + * The splitter is responsible for reading a large vCard or iCalendar object, + * and splitting it into multiple objects. + * + * This is for example for Card and CalDAV, which require every event and vcard + * to exist in their own objects, instead of one large one. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Dominik Tobschall (http://tobschall.de/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface SplitterInterface { + + /** + * Constructor. + * + * The splitter should receive an readable file stream as it's input. + * + * @param resource $input + */ + function __construct($input); + + /** + * Every time getNext() is called, a new object will be parsed, until we + * hit the end of the stream. + * + * When the end is reached, null will be returned. + * + * @return Sabre\VObject\Component|null + */ + function getNext(); + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Splitter/VCard.php b/libs/composer/vendor/sabre/vobject/lib/Splitter/VCard.php new file mode 100644 index 000000000000..0bb82abe93b6 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Splitter/VCard.php @@ -0,0 +1,78 @@ +<?php + +namespace Sabre\VObject\Splitter; + +use Sabre\VObject; +use Sabre\VObject\Parser\MimeDir; + +/** + * Splitter. + * + * This class is responsible for splitting up VCard objects. + * + * It is assumed that the input stream contains 1 or more VCARD objects. This + * class checks for BEGIN:VCARD and END:VCARD and parses each encountered + * component individually. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Dominik Tobschall (http://tobschall.de/) + * @author Armin Hackmann + * @license http://sabre.io/license/ Modified BSD License + */ +class VCard implements SplitterInterface { + + /** + * File handle. + * + * @var resource + */ + protected $input; + + /** + * Persistent parser. + * + * @var MimeDir + */ + protected $parser; + + /** + * Constructor. + * + * The splitter should receive an readable file stream as it's input. + * + * @param resource $input + * @param int $options Parser options, see the OPTIONS constants. + */ + function __construct($input, $options = 0) { + + $this->input = $input; + $this->parser = new MimeDir($input, $options); + + } + + /** + * Every time getNext() is called, a new object will be parsed, until we + * hit the end of the stream. + * + * When the end is reached, null will be returned. + * + * @return Sabre\VObject\Component|null + */ + function getNext() { + + try { + $object = $this->parser->parse(); + + if (!$object instanceof VObject\Component\VCard) { + throw new VObject\ParseException('The supplied input contained non-VCARD data.'); + } + + } catch (VObject\EofException $e) { + return; + } + + return $object; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/StringUtil.php b/libs/composer/vendor/sabre/vobject/lib/StringUtil.php new file mode 100644 index 000000000000..b8615f2ba12a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/StringUtil.php @@ -0,0 +1,66 @@ +<?php + +namespace Sabre\VObject; + +/** + * Useful utilities for working with various strings. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class StringUtil { + + /** + * Returns true or false depending on if a string is valid UTF-8. + * + * @param string $str + * + * @return bool + */ + static function isUTF8($str) { + + // Control characters + if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) { + return false; + } + + return (bool)preg_match('%%u', $str); + + } + + /** + * This method tries its best to convert the input string to UTF-8. + * + * Currently only ISO-5991-1 input and UTF-8 input is supported, but this + * may be expanded upon if we receive other examples. + * + * @param string $str + * + * @return string + */ + static function convertToUTF8($str) { + + $encoding = mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'WINDOWS-1252'], true); + + switch ($encoding) { + case 'ISO-8859-1' : + $newStr = utf8_encode($str); + break; + /* Unreachable code. Not sure yet how we can improve this + * situation. + case 'WINDOWS-1252' : + $newStr = iconv('cp1252', 'UTF-8', $str); + break; + */ + default : + $newStr = $str; + + } + + // Removing any control characters + return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $newStr)); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/TimeZoneUtil.php b/libs/composer/vendor/sabre/vobject/lib/TimeZoneUtil.php new file mode 100644 index 000000000000..925183e8d948 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/TimeZoneUtil.php @@ -0,0 +1,276 @@ +<?php + +namespace Sabre\VObject; + +/** + * Time zone name translation. + * + * This file translates well-known time zone names into "Olson database" time zone names. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Frank Edelhaeuser (fedel@users.sourceforge.net) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class TimeZoneUtil { + + static $map = null; + + /** + * List of microsoft exchange timezone ids. + * + * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx + */ + static $microsoftExchangeMap = [ + 0 => 'UTC', + 31 => 'Africa/Casablanca', + + // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. + // I'm not even kidding.. We handle this special case in the + // getTimeZone method. + 2 => 'Europe/Lisbon', + 1 => 'Europe/London', + 4 => 'Europe/Berlin', + 6 => 'Europe/Prague', + 3 => 'Europe/Paris', + 69 => 'Africa/Luanda', // This was a best guess + 7 => 'Europe/Athens', + 5 => 'Europe/Bucharest', + 49 => 'Africa/Cairo', + 50 => 'Africa/Harare', + 59 => 'Europe/Helsinki', + 27 => 'Asia/Jerusalem', + 26 => 'Asia/Baghdad', + 74 => 'Asia/Kuwait', + 51 => 'Europe/Moscow', + 56 => 'Africa/Nairobi', + 25 => 'Asia/Tehran', + 24 => 'Asia/Muscat', // Best guess + 54 => 'Asia/Baku', + 48 => 'Asia/Kabul', + 58 => 'Asia/Yekaterinburg', + 47 => 'Asia/Karachi', + 23 => 'Asia/Calcutta', + 62 => 'Asia/Kathmandu', + 46 => 'Asia/Almaty', + 71 => 'Asia/Dhaka', + 66 => 'Asia/Colombo', + 61 => 'Asia/Rangoon', + 22 => 'Asia/Bangkok', + 64 => 'Asia/Krasnoyarsk', + 45 => 'Asia/Shanghai', + 63 => 'Asia/Irkutsk', + 21 => 'Asia/Singapore', + 73 => 'Australia/Perth', + 75 => 'Asia/Taipei', + 20 => 'Asia/Tokyo', + 72 => 'Asia/Seoul', + 70 => 'Asia/Yakutsk', + 19 => 'Australia/Adelaide', + 44 => 'Australia/Darwin', + 18 => 'Australia/Brisbane', + 76 => 'Australia/Sydney', + 43 => 'Pacific/Guam', + 42 => 'Australia/Hobart', + 68 => 'Asia/Vladivostok', + 41 => 'Asia/Magadan', + 17 => 'Pacific/Auckland', + 40 => 'Pacific/Fiji', + 67 => 'Pacific/Tongatapu', + 29 => 'Atlantic/Azores', + 53 => 'Atlantic/Cape_Verde', + 30 => 'America/Noronha', + 8 => 'America/Sao_Paulo', // Best guess + 32 => 'America/Argentina/Buenos_Aires', + 60 => 'America/Godthab', + 28 => 'America/St_Johns', + 9 => 'America/Halifax', + 33 => 'America/Caracas', + 65 => 'America/Santiago', + 35 => 'America/Bogota', + 10 => 'America/New_York', + 34 => 'America/Indiana/Indianapolis', + 55 => 'America/Guatemala', + 11 => 'America/Chicago', + 37 => 'America/Mexico_City', + 36 => 'America/Edmonton', + 38 => 'America/Phoenix', + 12 => 'America/Denver', // Best guess + 13 => 'America/Los_Angeles', // Best guess + 14 => 'America/Anchorage', + 15 => 'Pacific/Honolulu', + 16 => 'Pacific/Midway', + 39 => 'Pacific/Kwajalein', + ]; + + /** + * This method will try to find out the correct timezone for an iCalendar + * date-time value. + * + * You must pass the contents of the TZID parameter, as well as the full + * calendar. + * + * If the lookup fails, this method will return the default PHP timezone + * (as configured using date_default_timezone_set, or the date.timezone ini + * setting). + * + * Alternatively, if $failIfUncertain is set to true, it will throw an + * exception if we cannot accurately determine the timezone. + * + * @param string $tzid + * @param Sabre\VObject\Component $vcalendar + * + * @return DateTimeZone + */ + static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { + + // First we will just see if the tzid is a support timezone identifier. + // + // The only exception is if the timezone starts with (. This is to + // handle cases where certain microsoft products generate timezone + // identifiers that for instance look like: + // + // (GMT+01.00) Sarajevo/Warsaw/Zagreb + // + // Since PHP 5.5.10, the first bit will be used as the timezone and + // this method will return just GMT+01:00. This is wrong, because it + // doesn't take DST into account. + if ($tzid[0] !== '(') { + + // PHP has a bug that logs PHP warnings even it shouldn't: + // https://bugs.php.net/bug.php?id=67881 + // + // That's why we're checking if we'll be able to successfull instantiate + // \DateTimeZone() before doing so. Otherwise we could simply instantiate + // and catch the exception. + $tzIdentifiers = \DateTimeZone::listIdentifiers(); + + try { + if ( + (in_array($tzid, $tzIdentifiers)) || + (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) || + (in_array($tzid, self::getIdentifiersBC())) + ) { + return new \DateTimeZone($tzid); + } + } catch (\Exception $e) { + } + + } + + self::loadTzMaps(); + + // Next, we check if the tzid is somewhere in our tzid map. + if (isset(self::$map[$tzid])) { + return new \DateTimeZone(self::$map[$tzid]); + } + + // Some Microsoft products prefix the offset first, so let's strip that off + // and see if it is our tzid map. We don't want to check for this first just + // in case there are overrides in our tzid map. + if (preg_match('/^\((UTC|GMT)(\+|\-)[\d]{2}\:[\d]{2}\) (.*)/', $tzid, $matches)) { + $tzidAlternate = $matches[3]; + if (isset(self::$map[$tzidAlternate])) { + return new \DateTimeZone(self::$map[$tzidAlternate]); + } + } + + // Maybe the author was hyper-lazy and just included an offset. We + // support it, but we aren't happy about it. + if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) { + + // Note that the path in the source will never be taken from PHP 5.5.10 + // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it + // already gets returned early in this function. Once we drop support + // for versions under PHP 5.5.10, this bit can be taken out of the + // source. + // @codeCoverageIgnoreStart + return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2], 0, 2), '0')); + // @codeCoverageIgnoreEnd + } + + if ($vcalendar) { + + // If that didn't work, we will scan VTIMEZONE objects + foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { + + if ((string)$vtimezone->TZID === $tzid) { + + // Some clients add 'X-LIC-LOCATION' with the olson name. + if (isset($vtimezone->{'X-LIC-LOCATION'})) { + + $lic = (string)$vtimezone->{'X-LIC-LOCATION'}; + + // Libical generators may specify strings like + // "SystemV/EST5EDT". For those we must remove the + // SystemV part. + if (substr($lic, 0, 8) === 'SystemV/') { + $lic = substr($lic, 8); + } + + return self::getTimeZone($lic, null, $failIfUncertain); + + } + // Microsoft may add a magic number, which we also have an + // answer for. + if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { + $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); + + // 2 can mean both Europe/Lisbon and Europe/Sarajevo. + if ($cdoId === 2 && strpos((string)$vtimezone->TZID, 'Sarajevo') !== false) { + return new \DateTimeZone('Europe/Sarajevo'); + } + + if (isset(self::$microsoftExchangeMap[$cdoId])) { + return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); + } + } + + } + + } + + } + + if ($failIfUncertain) { + throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); + } + + // If we got all the way here, we default to UTC. + return new \DateTimeZone(date_default_timezone_get()); + + } + + /** + * This method will load in all the tz mapping information, if it's not yet + * done. + */ + static function loadTzMaps() { + + if (!is_null(self::$map)) return; + + self::$map = array_merge( + include __DIR__ . '/timezonedata/windowszones.php', + include __DIR__ . '/timezonedata/lotuszones.php', + include __DIR__ . '/timezonedata/exchangezones.php', + include __DIR__ . '/timezonedata/php-workaround.php' + ); + + } + + /** + * This method returns an array of timezone identifiers, that are supported + * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers(). + * + * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: + * - It's not supported by some PHP versions as well as HHVM. + * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. + * (See timezonedata/php-bc.php and timezonedata php-workaround.php) + * + * @return array + */ + static function getIdentifiersBC() { + return include __DIR__ . '/timezonedata/php-bc.php'; + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/UUIDUtil.php b/libs/composer/vendor/sabre/vobject/lib/UUIDUtil.php new file mode 100644 index 000000000000..24ebe3cf80d1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/UUIDUtil.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\VObject; + +/** + * UUID Utility. + * + * This class has static methods to generate and validate UUID's. + * UUIDs are used a decent amount within various *DAV standards, so it made + * sense to include it. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class UUIDUtil { + + /** + * Returns a pseudo-random v4 UUID. + * + * This function is based on a comment by Andrew Moore on php.net + * + * @see http://www.php.net/manual/en/function.uniqid.php#94959 + * + * @return string + */ + static function getUUID() { + + return sprintf( + + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Checks if a string is a valid UUID. + * + * @param string $uuid + * + * @return bool + */ + static function validateUUID($uuid) { + + return preg_match( + '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', + $uuid + ) !== 0; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/VCardConverter.php b/libs/composer/vendor/sabre/vobject/lib/VCardConverter.php new file mode 100644 index 000000000000..1f6d016f1428 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/VCardConverter.php @@ -0,0 +1,467 @@ +<?php + +namespace Sabre\VObject; + +/** + * This utility converts vcards from one version to another. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class VCardConverter { + + /** + * Converts a vCard object to a new version. + * + * targetVersion must be one of: + * Document::VCARD21 + * Document::VCARD30 + * Document::VCARD40 + * + * Currently only 3.0 and 4.0 as input and output versions. + * + * 2.1 has some minor support for the input version, it's incomplete at the + * moment though. + * + * If input and output version are identical, a clone is returned. + * + * @param Component\VCard $input + * @param int $targetVersion + */ + function convert(Component\VCard $input, $targetVersion) { + + $inputVersion = $input->getDocumentType(); + if ($inputVersion === $targetVersion) { + return clone $input; + } + + if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) { + throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); + } + if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) { + throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); + } + + $newVersion = $targetVersion === Document::VCARD40 ? '4.0' : '3.0'; + + $output = new Component\VCard([ + 'VERSION' => $newVersion, + ]); + + // We might have generated a default UID. Remove it! + unset($output->UID); + + foreach ($input->children() as $property) { + + $this->convertProperty($input, $output, $property, $targetVersion); + + } + + return $output; + + } + + /** + * Handles conversion of a single property. + * + * @param Component\VCard $input + * @param Component\VCard $output + * @param Property $property + * @param int $targetVersion + * + * @return void + */ + protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) { + + // Skipping these, those are automatically added. + if (in_array($property->name, ['VERSION', 'PRODID'])) { + return; + } + + $parameters = $property->parameters(); + $valueType = null; + if (isset($parameters['VALUE'])) { + $valueType = $parameters['VALUE']->getValue(); + unset($parameters['VALUE']); + } + if (!$valueType) { + $valueType = $property->getValueType(); + } + $newProperty = $output->createProperty( + $property->name, + $property->getParts(), + [], // parameters will get added a bit later. + $valueType + ); + + + if ($targetVersion === Document::VCARD30) { + + if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { + + $newProperty = $this->convertUriToBinary($output, $newProperty); + + } elseif ($property instanceof Property\VCard\DateAndOrTime) { + + // In vCard 4, the birth year may be optional. This is not the + // case for vCard 3. Apple has a workaround for this that + // allows applications that support Apple's extension still + // omit birthyears in vCard 3, but applications that do not + // support this, will just use a random birthyear. We're + // choosing 1604 for the birthyear, because that's what apple + // uses. + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); + if (is_null($parts['year'])) { + $newValue = '1604-' . $parts['month'] . '-' . $parts['date']; + $newProperty->setValue($newValue); + $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; + } + + if ($newProperty->name == 'ANNIVERSARY') { + // Microsoft non-standard anniversary + $newProperty->name = 'X-ANNIVERSARY'; + + // We also need to add a new apple property for the same + // purpose. This apple property needs a 'label' in the same + // group, so we first need to find a groupname that doesn't + // exist yet. + $x = 1; + while ($output->select('ITEM' . $x . '.')) { + $x++; + } + $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']); + $output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_'); + } + + } elseif ($property->name === 'KIND') { + + switch (strtolower($property->getValue())) { + case 'org' : + // vCard 3.0 does not have an equivalent to KIND:ORG, + // but apple has an extension that means the same + // thing. + $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY'); + break; + + case 'individual' : + // Individual is implicit, so we skip it. + return; + + case 'group' : + // OS X addressbook property + $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP'); + break; + } + + + } + + } elseif ($targetVersion === Document::VCARD40) { + + // These properties were removed in vCard 4.0 + if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) { + return; + } + + if ($property instanceof Property\Binary) { + + $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); + + } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { + + // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', + // then we're stripping the year from the vcard 4 value. + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); + if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) { + $newValue = '--' . $parts['month'] . '-' . $parts['date']; + $newProperty->setValue($newValue); + } + + // Regardless if the year matched or not, we do need to strip + // X-APPLE-OMIT-YEAR. + unset($parameters['X-APPLE-OMIT-YEAR']); + + } + switch ($property->name) { + case 'X-ABSHOWAS' : + if (strtoupper($property->getValue()) === 'COMPANY') { + $newProperty = $output->createProperty('KIND', 'ORG'); + } + break; + case 'X-ADDRESSBOOKSERVER-KIND' : + if (strtoupper($property->getValue()) === 'GROUP') { + $newProperty = $output->createProperty('KIND', 'GROUP'); + } + break; + case 'X-ANNIVERSARY' : + $newProperty->name = 'ANNIVERSARY'; + // If we already have an anniversary property with the same + // value, ignore. + foreach ($output->select('ANNIVERSARY') as $anniversary) { + if ($anniversary->getValue() === $newProperty->getValue()) { + return; + } + } + break; + case 'X-ABDATE' : + // Find out what the label was, if it exists. + if (!$property->group) { + break; + } + $label = $input->{$property->group . '.X-ABLABEL'}; + + // We only support converting anniversaries. + if (!$label || $label->getValue() !== '_$!<Anniversary>!$_') { + break; + } + + // If we already have an anniversary property with the same + // value, ignore. + foreach ($output->select('ANNIVERSARY') as $anniversary) { + if ($anniversary->getValue() === $newProperty->getValue()) { + return; + } + } + $newProperty->name = 'ANNIVERSARY'; + break; + // Apple's per-property label system. + case 'X-ABLABEL' : + if ($newProperty->getValue() === '_$!<Anniversary>!$_') { + // We can safely remove these, as they are converted to + // ANNIVERSARY properties. + return; + } + break; + + } + + } + + // set property group + $newProperty->group = $property->group; + + if ($targetVersion === Document::VCARD40) { + $this->convertParameters40($newProperty, $parameters); + } else { + $this->convertParameters30($newProperty, $parameters); + } + + // Lastly, we need to see if there's a need for a VALUE parameter. + // + // We can do that by instantating a empty property with that name, and + // seeing if the default valueType is identical to the current one. + $tempProperty = $output->createProperty($newProperty->name); + if ($tempProperty->getValueType() !== $newProperty->getValueType()) { + $newProperty['VALUE'] = $newProperty->getValueType(); + } + + $output->add($newProperty); + + + } + + /** + * Converts a BINARY property to a URI property. + * + * vCard 4.0 no longer supports BINARY properties. + * + * @param Component\VCard $output + * @param Property\Uri $property The input property. + * @param $parameters List of parameters that will eventually be added to + * the new property. + * + * @return Property\Uri + */ + protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) { + + $value = $newProperty->getValue(); + $newProperty = $output->createProperty( + $newProperty->name, + null, // no value + [], // no parameters yet + 'URI' // Forcing the BINARY type + ); + + $mimeType = 'application/octet-stream'; + + // See if we can find a better mimetype. + if (isset($parameters['TYPE'])) { + + $newTypes = []; + foreach ($parameters['TYPE']->getParts() as $typePart) { + if (in_array( + strtoupper($typePart), + ['JPEG', 'PNG', 'GIF'] + )) { + $mimeType = 'image/' . strtolower($typePart); + } else { + $newTypes[] = $typePart; + } + } + + // If there were any parameters we're not converting to a + // mime-type, we need to keep them. + if ($newTypes) { + $parameters['TYPE']->setParts($newTypes); + } else { + unset($parameters['TYPE']); + } + + } + + $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value)); + return $newProperty; + + } + + /** + * Converts a URI property to a BINARY property. + * + * In vCard 4.0 attachments are encoded as data: uri. Even though these may + * be valid in vCard 3.0 as well, we should convert those to BINARY if + * possible, to improve compatibility. + * + * @param Component\VCard $output + * @param Property\Uri $property The input property. + * + * @return Property\Binary|null + */ + protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) { + + $value = $newProperty->getValue(); + + // Only converting data: uris + if (substr($value, 0, 5) !== 'data:') { + return $newProperty; + } + + $newProperty = $output->createProperty( + $newProperty->name, + null, // no value + [], // no parameters yet + 'BINARY' + ); + + $mimeType = substr($value, 5, strpos($value, ',') - 5); + if (strpos($mimeType, ';')) { + $mimeType = substr($mimeType, 0, strpos($mimeType, ';')); + $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1))); + } else { + $newProperty->setValue(substr($value, strpos($value, ',') + 1)); + } + unset($value); + + $newProperty['ENCODING'] = 'b'; + switch ($mimeType) { + + case 'image/jpeg' : + $newProperty['TYPE'] = 'JPEG'; + break; + case 'image/png' : + $newProperty['TYPE'] = 'PNG'; + break; + case 'image/gif' : + $newProperty['TYPE'] = 'GIF'; + break; + + } + + + return $newProperty; + + } + + /** + * Adds parameters to a new property for vCard 4.0. + * + * @param Property $newProperty + * @param array $parameters + * + * @return void + */ + protected function convertParameters40(Property $newProperty, array $parameters) { + + // Adding all parameters. + foreach ($parameters as $param) { + + // vCard 2.1 allowed parameters with no name + if ($param->noName) $param->noName = false; + + switch ($param->name) { + + // We need to see if there's any TYPE=PREF, because in vCard 4 + // that's now PREF=1. + case 'TYPE' : + foreach ($param->getParts() as $paramPart) { + + if (strtoupper($paramPart) === 'PREF') { + $newProperty->add('PREF', '1'); + } else { + $newProperty->add($param->name, $paramPart); + } + + } + break; + // These no longer exist in vCard 4 + case 'ENCODING' : + case 'CHARSET' : + break; + + default : + $newProperty->add($param->name, $param->getParts()); + break; + + } + + } + + } + + /** + * Adds parameters to a new property for vCard 3.0. + * + * @param Property $newProperty + * @param array $parameters + * + * @return void + */ + protected function convertParameters30(Property $newProperty, array $parameters) { + + // Adding all parameters. + foreach ($parameters as $param) { + + // vCard 2.1 allowed parameters with no name + if ($param->noName) $param->noName = false; + + switch ($param->name) { + + case 'ENCODING' : + // This value only existed in vCard 2.1, and should be + // removed for anything else. + if (strtoupper($param->getValue()) !== 'QUOTED-PRINTABLE') { + $newProperty->add($param->name, $param->getParts()); + } + break; + + /* + * Converting PREF=1 to TYPE=PREF. + * + * Any other PREF numbers we'll drop. + */ + case 'PREF' : + if ($param->getValue() == '1') { + $newProperty->add('TYPE', 'PREF'); + } + break; + + default : + $newProperty->add($param->name, $param->getParts()); + break; + + } + + } + + } +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Version.php b/libs/composer/vendor/sabre/vobject/lib/Version.php new file mode 100644 index 000000000000..074b06c4b705 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\VObject; + +/** + * This class contains the version number for the VObject package. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number. + */ + const VERSION = '4.1.6'; + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/Writer.php b/libs/composer/vendor/sabre/vobject/lib/Writer.php new file mode 100644 index 000000000000..f8a58758d6cc --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/Writer.php @@ -0,0 +1,81 @@ +<?php + +namespace Sabre\VObject; + +use Sabre\Xml; + +/** + * iCalendar/vCard/jCal/jCard/xCal/xCard writer object. + * + * This object provides a few (static) convenience methods to quickly access + * the serializers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Ivan Enderlin + * @license http://sabre.io/license/ Modified BSD License + */ +class Writer { + + /** + * Serializes a vCard or iCalendar object. + * + * @param Component $component + * + * @return string + */ + static function write(Component $component) { + + return $component->serialize(); + + } + + /** + * Serializes a jCal or jCard object. + * + * @param Component $component + * @param int $options + * + * @return string + */ + static function writeJson(Component $component, $options = 0) { + + return json_encode($component, $options); + + } + + /** + * Serializes a xCal or xCard object. + * + * @param Component $component + * + * @return string + */ + static function writeXml(Component $component) { + + $writer = new Xml\Writer(); + $writer->openMemory(); + $writer->setIndent(true); + + $writer->startDocument('1.0', 'utf-8'); + + if ($component instanceof Component\VCalendar) { + + $writer->startElement('icalendar'); + $writer->writeAttribute('xmlns', Parser\Xml::XCAL_NAMESPACE); + + } else { + + $writer->startElement('vcards'); + $writer->writeAttribute('xmlns', Parser\Xml::XCARD_NAMESPACE); + + } + + $component->xmlSerialize($writer); + + $writer->endElement(); + + return $writer->outputMemory(); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/lib/timezonedata/exchangezones.php b/libs/composer/vendor/sabre/vobject/lib/timezonedata/exchangezones.php new file mode 100644 index 000000000000..edba5b473888 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/timezonedata/exchangezones.php @@ -0,0 +1,94 @@ +<?php + +/** + * Microsoft exchange timezones + * Source: + * http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx. + * + * Correct timezones deduced with help from: + * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +return [ + 'Universal Coordinated Time' => 'UTC', + 'Casablanca, Monrovia' => 'Africa/Casablanca', + 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon', + 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London', + 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', + 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague', + 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', + 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris', + 'Prague, Central Europe' => 'Europe/Prague', + 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo', + 'West Central Africa' => 'Africa/Luanda', // This was a best guess + 'Athens, Istanbul, Minsk' => 'Europe/Athens', + 'Bucharest' => 'Europe/Bucharest', + 'Cairo' => 'Africa/Cairo', + 'Harare, Pretoria' => 'Africa/Harare', + 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki', + 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem', + 'Baghdad' => 'Asia/Baghdad', + 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait', + 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', + 'East Africa, Nairobi' => 'Africa/Nairobi', + 'Tehran' => 'Asia/Tehran', + 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess + 'Baku, Tbilisi, Yerevan' => 'Asia/Baku', + 'Kabul' => 'Asia/Kabul', + 'Ekaterinburg' => 'Asia/Yekaterinburg', + 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi', + 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta', + 'Kathmandu, Nepal' => 'Asia/Kathmandu', + 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty', + 'Astana, Dhaka' => 'Asia/Dhaka', + 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo', + 'Rangoon' => 'Asia/Rangoon', + 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', + 'Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai', + 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk', + 'Kuala Lumpur, Singapore' => 'Asia/Singapore', + 'Perth, Western Australia' => 'Australia/Perth', + 'Taipei' => 'Asia/Taipei', + 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', + 'Seoul, Korea Standard time' => 'Asia/Seoul', + 'Yakutsk' => 'Asia/Yakutsk', + 'Adelaide, Central Australia' => 'Australia/Adelaide', + 'Darwin' => 'Australia/Darwin', + 'Brisbane, East Australia' => 'Australia/Brisbane', + 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney', + 'Guam, Port Moresby' => 'Pacific/Guam', + 'Hobart, Tasmania' => 'Australia/Hobart', + 'Vladivostok' => 'Asia/Vladivostok', + 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan', + 'Auckland, Wellington' => 'Pacific/Auckland', + 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji', + 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu', + 'Azores' => 'Atlantic/Azores', + 'Cape Verde Is.' => 'Atlantic/Cape_Verde', + 'Mid-Atlantic' => 'America/Noronha', + 'Brasilia' => 'America/Sao_Paulo', // Best guess + 'Buenos Aires' => 'America/Argentina/Buenos_Aires', + 'Greenland' => 'America/Godthab', + 'Newfoundland' => 'America/St_Johns', + 'Atlantic Time (Canada)' => 'America/Halifax', + 'Caracas, La Paz' => 'America/Caracas', + 'Santiago' => 'America/Santiago', + 'Bogota, Lima, Quito' => 'America/Bogota', + 'Eastern Time (US & Canada)' => 'America/New_York', + 'Indiana (East)' => 'America/Indiana/Indianapolis', + 'Central America' => 'America/Guatemala', + 'Central Time (US & Canada)' => 'America/Chicago', + 'Mexico City, Tegucigalpa' => 'America/Mexico_City', + 'Saskatchewan' => 'America/Edmonton', + 'Arizona' => 'America/Phoenix', + 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess + 'Pacific Time (US & Canada)' => 'America/Los_Angeles', // Best guess + 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess + 'Alaska' => 'America/Anchorage', + 'Hawaii' => 'Pacific/Honolulu', + 'Midway Island, Samoa' => 'Pacific/Midway', + 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein', +]; diff --git a/libs/composer/vendor/sabre/vobject/lib/timezonedata/lotuszones.php b/libs/composer/vendor/sabre/vobject/lib/timezonedata/lotuszones.php new file mode 100644 index 000000000000..79d555a92f03 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/timezonedata/lotuszones.php @@ -0,0 +1,101 @@ +<?php + +/** + * The following list are timezone names that could be generated by + * Lotus / Domino. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +return [ + 'Dateline' => 'Etc/GMT-12', + 'Samoa' => 'Pacific/Apia', + 'Hawaiian' => 'Pacific/Honolulu', + 'Alaskan' => 'America/Anchorage', + 'Pacific' => 'America/Los_Angeles', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Mexico Standard Time 2' => 'America/Chihuahua', + 'Mountain' => 'America/Denver', + // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones. + 'US Mountain' => 'America/Phoenix', + 'Canada Central' => 'America/Edmonton', + 'Central America' => 'America/Guatemala', + 'Central' => 'America/Chicago', + // 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones. + 'Mexico' => 'America/Mexico_City', + 'Eastern' => 'America/New_York', + 'SA Pacific' => 'America/Bogota', + 'US Eastern' => 'America/Indiana/Indianapolis', + 'Venezuela' => 'America/Caracas', + 'Atlantic' => 'America/Halifax', + 'Central Brazilian' => 'America/Manaus', + 'Pacific SA' => 'America/Santiago', + 'SA Western' => 'America/La_Paz', + 'Newfoundland' => 'America/St_Johns', + 'Argentina' => 'America/Argentina/Buenos_Aires', + 'E. South America' => 'America/Belem', + 'Greenland' => 'America/Godthab', + 'Montevideo' => 'America/Montevideo', + 'SA Eastern' => 'America/Belem', + // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones. + 'Azores' => 'Atlantic/Azores', + 'Cape Verde' => 'Atlantic/Cape_Verde', + 'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT. + 'Morocco' => 'Africa/Casablanca', + 'Central Europe' => 'Europe/Prague', + 'Central European' => 'Europe/Sarajevo', + 'Romance' => 'Europe/Paris', + 'W. Central Africa' => 'Africa/Lagos', // Best guess + 'W. Europe' => 'Europe/Amsterdam', + 'E. Europe' => 'Europe/Minsk', + 'Egypt' => 'Africa/Cairo', + 'FLE' => 'Europe/Helsinki', + 'GTB' => 'Europe/Athens', + 'Israel' => 'Asia/Jerusalem', + 'Jordan' => 'Asia/Amman', + 'Middle East' => 'Asia/Beirut', + 'Namibia' => 'Africa/Windhoek', + 'South Africa' => 'Africa/Harare', + 'Arab' => 'Asia/Kuwait', + 'Arabic' => 'Asia/Baghdad', + 'E. Africa' => 'Africa/Nairobi', + 'Georgian' => 'Asia/Tbilisi', + 'Russian' => 'Europe/Moscow', + 'Iran' => 'Asia/Tehran', + 'Arabian' => 'Asia/Muscat', + 'Armenian' => 'Asia/Yerevan', + 'Azerbijan' => 'Asia/Baku', + 'Caucasus' => 'Asia/Yerevan', + 'Mauritius' => 'Indian/Mauritius', + 'Afghanistan' => 'Asia/Kabul', + 'Ekaterinburg' => 'Asia/Yekaterinburg', + 'Pakistan' => 'Asia/Karachi', + 'West Asia' => 'Asia/Tashkent', + 'India' => 'Asia/Calcutta', + 'Sri Lanka' => 'Asia/Colombo', + 'Nepal' => 'Asia/Kathmandu', + 'Central Asia' => 'Asia/Dhaka', + 'N. Central Asia' => 'Asia/Almaty', + 'Myanmar' => 'Asia/Rangoon', + 'North Asia' => 'Asia/Krasnoyarsk', + 'SE Asia' => 'Asia/Bangkok', + 'China' => 'Asia/Shanghai', + 'North Asia East' => 'Asia/Irkutsk', + 'Singapore' => 'Asia/Singapore', + 'Taipei' => 'Asia/Taipei', + 'W. Australia' => 'Australia/Perth', + 'Korea' => 'Asia/Seoul', + 'Tokyo' => 'Asia/Tokyo', + 'Yakutsk' => 'Asia/Yakutsk', + 'AUS Central' => 'Australia/Darwin', + 'Cen. Australia' => 'Australia/Adelaide', + 'AUS Eastern' => 'Australia/Sydney', + 'E. Australia' => 'Australia/Brisbane', + 'Tasmania' => 'Australia/Hobart', + 'Vladivostok' => 'Asia/Vladivostok', + 'West Pacific' => 'Pacific/Guam', + 'Central Pacific' => 'Asia/Magadan', + 'Fiji' => 'Pacific/Fiji', + 'New Zealand' => 'Pacific/Auckland', + 'Tonga' => 'Pacific/Tongatapu', +]; diff --git a/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-bc.php b/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-bc.php new file mode 100644 index 000000000000..83f38f507f07 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-bc.php @@ -0,0 +1,153 @@ +<?php + +/** + * A list of additional PHP timezones that are returned by + * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) + * valid for new DateTimeZone(). + * + * This list does not include those timezone identifiers that we have to map to + * a different identifier for some PHP versions (see php-workaround.php). + * + * Instead of using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) + * directly, we use this file because DateTimeZone::ALL_WITH_BC is not properly + * supported by all PHP version and HHVM. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +return [ + 'Africa/Asmera', + 'Africa/Timbuktu', + 'America/Argentina/ComodRivadavia', + 'America/Atka', + 'America/Buenos_Aires', + 'America/Catamarca', + 'America/Coral_Harbour', + 'America/Cordoba', + 'America/Ensenada', + 'America/Fort_Wayne', + 'America/Indianapolis', + 'America/Jujuy', + 'America/Knox_IN', + 'America/Louisville', + 'America/Mendoza', + 'America/Montreal', + 'America/Porto_Acre', + 'America/Rosario', + 'America/Shiprock', + 'America/Virgin', + 'Antarctica/South_Pole', + 'Asia/Ashkhabad', + 'Asia/Calcutta', + 'Asia/Chungking', + 'Asia/Dacca', + 'Asia/Istanbul', + 'Asia/Katmandu', + 'Asia/Macao', + 'Asia/Saigon', + 'Asia/Tel_Aviv', + 'Asia/Thimbu', + 'Asia/Ujung_Pandang', + 'Asia/Ulan_Bator', + 'Atlantic/Faeroe', + 'Atlantic/Jan_Mayen', + 'Australia/ACT', + 'Australia/Canberra', + 'Australia/LHI', + 'Australia/North', + 'Australia/NSW', + 'Australia/Queensland', + 'Australia/South', + 'Australia/Tasmania', + 'Australia/Victoria', + 'Australia/West', + 'Australia/Yancowinna', + 'Brazil/Acre', + 'Brazil/DeNoronha', + 'Brazil/East', + 'Brazil/West', + 'Canada/Atlantic', + 'Canada/Central', + 'Canada/Eastern', + 'Canada/Mountain', + 'Canada/Newfoundland', + 'Canada/Pacific', + 'Canada/Saskatchewan', + 'Canada/Yukon', + 'CET', + 'Chile/Continental', + 'Chile/EasterIsland', + 'EET', + 'EST', + 'Etc/GMT', + 'Etc/GMT+0', + 'Etc/GMT+1', + 'Etc/GMT+10', + 'Etc/GMT+11', + 'Etc/GMT+12', + 'Etc/GMT+2', + 'Etc/GMT+3', + 'Etc/GMT+4', + 'Etc/GMT+5', + 'Etc/GMT+6', + 'Etc/GMT+7', + 'Etc/GMT+8', + 'Etc/GMT+9', + 'Etc/GMT-0', + 'Etc/GMT-1', + 'Etc/GMT-10', + 'Etc/GMT-11', + 'Etc/GMT-12', + 'Etc/GMT-13', + 'Etc/GMT-14', + 'Etc/GMT-2', + 'Etc/GMT-3', + 'Etc/GMT-4', + 'Etc/GMT-5', + 'Etc/GMT-6', + 'Etc/GMT-7', + 'Etc/GMT-8', + 'Etc/GMT-9', + 'Etc/GMT0', + 'Etc/Greenwich', + 'Etc/UCT', + 'Etc/Universal', + 'Etc/UTC', + 'Etc/Zulu', + 'Europe/Belfast', + 'Europe/Nicosia', + 'Europe/Tiraspol', + 'GB', + 'GMT', + 'GMT+0', + 'GMT-0', + 'HST', + 'MET', + 'Mexico/BajaNorte', + 'Mexico/BajaSur', + 'Mexico/General', + 'MST', + 'NZ', + 'Pacific/Ponape', + 'Pacific/Samoa', + 'Pacific/Truk', + 'Pacific/Yap', + 'PRC', + 'ROC', + 'ROK', + 'UCT', + 'US/Alaska', + 'US/Aleutian', + 'US/Arizona', + 'US/Central', + 'US/East-Indiana', + 'US/Eastern', + 'US/Hawaii', + 'US/Indiana-Starke', + 'US/Michigan', + 'US/Mountain', + 'US/Pacific', + 'US/Pacific-New', + 'US/Samoa', + 'WET', +]; diff --git a/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-workaround.php b/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-workaround.php new file mode 100644 index 000000000000..6b9cb6ef745d --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/timezonedata/php-workaround.php @@ -0,0 +1,46 @@ +<?php + +/** + * A list of PHP timezones that were supported until 5.5.9, removed in + * PHP 5.5.10 and re-introduced in PHP 5.5.17. + * + * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) returns them, + * but they are invalid for new DateTimeZone(). Fixed in PHP 5.5.17. + * https://bugs.php.net/bug.php?id=66985 + * + * Some more info here: + * http://evertpot.com/php-5-5-10-timezone-changes/ + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +return [ + 'CST6CDT' => 'America/Chicago', + 'Cuba' => 'America/Havana', + 'Egypt' => 'Africa/Cairo', + 'Eire' => 'Europe/Dublin', + 'EST5EDT' => 'America/New_York', + 'Factory' => 'UTC', + 'GB-Eire' => 'Europe/London', + 'GMT0' => 'UTC', + 'Greenwich' => 'UTC', + 'Hongkong' => 'Asia/Hong_Kong', + 'Iceland' => 'Atlantic/Reykjavik', + 'Iran' => 'Asia/Tehran', + 'Israel' => 'Asia/Jerusalem', + 'Jamaica' => 'America/Jamaica', + 'Japan' => 'Asia/Tokyo', + 'Kwajalein' => 'Pacific/Kwajalein', + 'Libya' => 'Africa/Tripoli', + 'MST7MDT' => 'America/Denver', + 'Navajo' => 'America/Denver', + 'NZ-CHAT' => 'Pacific/Chatham', + 'Poland' => 'Europe/Warsaw', + 'Portugal' => 'Europe/Lisbon', + 'PST8PDT' => 'America/Los_Angeles', + 'Singapore' => 'Asia/Singapore', + 'Turkey' => 'Europe/Istanbul', + 'Universal' => 'UTC', + 'W-SU' => 'Europe/Moscow', + 'Zulu' => 'UTC', +]; diff --git a/libs/composer/vendor/sabre/vobject/lib/timezonedata/windowszones.php b/libs/composer/vendor/sabre/vobject/lib/timezonedata/windowszones.php new file mode 100644 index 000000000000..29f3a6cb8099 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/lib/timezonedata/windowszones.php @@ -0,0 +1,143 @@ +<?php + +/** + * Automatically generated timezone file + * + * Last update: 2016-08-24T17:35:38-04:00 + * Source: http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml + * + * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). + * @license http://sabre.io/license/ Modified BSD License + */ + +return [ + 'AUS Central Standard Time' => 'Australia/Darwin', + 'AUS Eastern Standard Time' => 'Australia/Sydney', + 'Afghanistan Standard Time' => 'Asia/Kabul', + 'Alaskan Standard Time' => 'America/Anchorage', + 'Aleutian Standard Time' => 'America/Adak', + 'Altai Standard Time' => 'Asia/Barnaul', + 'Arab Standard Time' => 'Asia/Riyadh', + 'Arabian Standard Time' => 'Asia/Dubai', + 'Arabic Standard Time' => 'Asia/Baghdad', + 'Argentina Standard Time' => 'America/Buenos_Aires', + 'Astrakhan Standard Time' => 'Europe/Astrakhan', + 'Atlantic Standard Time' => 'America/Halifax', + 'Aus Central W. Standard Time' => 'Australia/Eucla', + 'Azerbaijan Standard Time' => 'Asia/Baku', + 'Azores Standard Time' => 'Atlantic/Azores', + 'Bahia Standard Time' => 'America/Bahia', + 'Bangladesh Standard Time' => 'Asia/Dhaka', + 'Belarus Standard Time' => 'Europe/Minsk', + 'Bougainville Standard Time' => 'Pacific/Bougainville', + 'Canada Central Standard Time' => 'America/Regina', + 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', + 'Caucasus Standard Time' => 'Asia/Yerevan', + 'Cen. Australia Standard Time' => 'Australia/Adelaide', + 'Central America Standard Time' => 'America/Guatemala', + 'Central Asia Standard Time' => 'Asia/Almaty', + 'Central Brazilian Standard Time' => 'America/Cuiaba', + 'Central Europe Standard Time' => 'Europe/Budapest', + 'Central European Standard Time' => 'Europe/Warsaw', + 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', + 'Central Standard Time' => 'America/Chicago', + 'Central Standard Time (Mexico)' => 'America/Mexico_City', + 'Chatham Islands Standard Time' => 'Pacific/Chatham', + 'China Standard Time' => 'Asia/Shanghai', + 'Cuba Standard Time' => 'America/Havana', + 'Dateline Standard Time' => 'Etc/GMT+12', + 'E. Africa Standard Time' => 'Africa/Nairobi', + 'E. Australia Standard Time' => 'Australia/Brisbane', + 'E. Europe Standard Time' => 'Europe/Chisinau', + 'E. South America Standard Time' => 'America/Sao_Paulo', + 'Easter Island Standard Time' => 'Pacific/Easter', + 'Eastern Standard Time' => 'America/New_York', + 'Eastern Standard Time (Mexico)' => 'America/Cancun', + 'Egypt Standard Time' => 'Africa/Cairo', + 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', + 'FLE Standard Time' => 'Europe/Kiev', + 'Fiji Standard Time' => 'Pacific/Fiji', + 'GMT Standard Time' => 'Europe/London', + 'GTB Standard Time' => 'Europe/Bucharest', + 'Georgian Standard Time' => 'Asia/Tbilisi', + 'Greenland Standard Time' => 'America/Godthab', + 'Greenwich Standard Time' => 'Atlantic/Reykjavik', + 'Haiti Standard Time' => 'America/Port-au-Prince', + 'Hawaiian Standard Time' => 'Pacific/Honolulu', + 'India Standard Time' => 'Asia/Calcutta', + 'Iran Standard Time' => 'Asia/Tehran', + 'Israel Standard Time' => 'Asia/Jerusalem', + 'Jordan Standard Time' => 'Asia/Amman', + 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', + 'Korea Standard Time' => 'Asia/Seoul', + 'Libya Standard Time' => 'Africa/Tripoli', + 'Line Islands Standard Time' => 'Pacific/Kiritimati', + 'Lord Howe Standard Time' => 'Australia/Lord_Howe', + 'Magadan Standard Time' => 'Asia/Magadan', + 'Marquesas Standard Time' => 'Pacific/Marquesas', + 'Mauritius Standard Time' => 'Indian/Mauritius', + 'Middle East Standard Time' => 'Asia/Beirut', + 'Montevideo Standard Time' => 'America/Montevideo', + 'Morocco Standard Time' => 'Africa/Casablanca', + 'Mountain Standard Time' => 'America/Denver', + 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', + 'Myanmar Standard Time' => 'Asia/Rangoon', + 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', + 'Namibia Standard Time' => 'Africa/Windhoek', + 'Nepal Standard Time' => 'Asia/Katmandu', + 'New Zealand Standard Time' => 'Pacific/Auckland', + 'Newfoundland Standard Time' => 'America/St_Johns', + 'Norfolk Standard Time' => 'Pacific/Norfolk', + 'North Asia East Standard Time' => 'Asia/Irkutsk', + 'North Asia Standard Time' => 'Asia/Krasnoyarsk', + 'North Korea Standard Time' => 'Asia/Pyongyang', + 'Pacific SA Standard Time' => 'America/Santiago', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Pacific Standard Time (Mexico)' => 'America/Tijuana', + 'Pakistan Standard Time' => 'Asia/Karachi', + 'Paraguay Standard Time' => 'America/Asuncion', + 'Romance Standard Time' => 'Europe/Paris', + 'Russia Time Zone 10' => 'Asia/Srednekolymsk', + 'Russia Time Zone 11' => 'Asia/Kamchatka', + 'Russia Time Zone 3' => 'Europe/Samara', + 'Russian Standard Time' => 'Europe/Moscow', + 'SA Eastern Standard Time' => 'America/Cayenne', + 'SA Pacific Standard Time' => 'America/Bogota', + 'SA Western Standard Time' => 'America/La_Paz', + 'SE Asia Standard Time' => 'Asia/Bangkok', + 'Saint Pierre Standard Time' => 'America/Miquelon', + 'Sakhalin Standard Time' => 'Asia/Sakhalin', + 'Samoa Standard Time' => 'Pacific/Apia', + 'Singapore Standard Time' => 'Asia/Singapore', + 'South Africa Standard Time' => 'Africa/Johannesburg', + 'Sri Lanka Standard Time' => 'Asia/Colombo', + 'Syria Standard Time' => 'Asia/Damascus', + 'Taipei Standard Time' => 'Asia/Taipei', + 'Tasmania Standard Time' => 'Australia/Hobart', + 'Tocantins Standard Time' => 'America/Araguaina', + 'Tokyo Standard Time' => 'Asia/Tokyo', + 'Tomsk Standard Time' => 'Asia/Tomsk', + 'Tonga Standard Time' => 'Pacific/Tongatapu', + 'Transbaikal Standard Time' => 'Asia/Chita', + 'Turkey Standard Time' => 'Europe/Istanbul', + 'Turks And Caicos Standard Time' => 'America/Grand_Turk', + 'US Eastern Standard Time' => 'America/Indianapolis', + 'US Mountain Standard Time' => 'America/Phoenix', + 'UTC' => 'Etc/GMT', + 'UTC+12' => 'Etc/GMT-12', + 'UTC-02' => 'Etc/GMT+2', + 'UTC-08' => 'Etc/GMT+8', + 'UTC-09' => 'Etc/GMT+9', + 'UTC-11' => 'Etc/GMT+11', + 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', + 'Venezuela Standard Time' => 'America/Caracas', + 'Vladivostok Standard Time' => 'Asia/Vladivostok', + 'W. Australia Standard Time' => 'Australia/Perth', + 'W. Central Africa Standard Time' => 'Africa/Lagos', + 'W. Europe Standard Time' => 'Europe/Berlin', + 'W. Mongolia Standard Time' => 'Asia/Hovd', + 'West Asia Standard Time' => 'Asia/Tashkent', + 'West Bank Standard Time' => 'Asia/Hebron', + 'West Pacific Standard Time' => 'Pacific/Port_Moresby', + 'Yakutsk Standard Time' => 'Asia/Yakutsk', +]; diff --git a/libs/composer/vendor/sabre/vobject/resources/schema/xcal.rng b/libs/composer/vendor/sabre/vobject/resources/schema/xcal.rng new file mode 100644 index 000000000000..4a51460e7427 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/resources/schema/xcal.rng @@ -0,0 +1,1192 @@ +# RELAX NG Schema for iCalendar in XML +# Extract from RFC6321. +# Erratum 3042 applied. +# Erratum 3050 applied. +# Erratum 3314 applied. + +default namespace = "urn:ietf:params:xml:ns:icalendar-2.0" + +# 3.2 Property Parameters + +# 3.2.1 Alternate Text Representation + +altrepparam = element altrep { + value-uri +} + +# 3.2.2 Common Name + +cnparam = element cn { + value-text +} + +# 3.2.3 Calendar User Type + +cutypeparam = element cutype { + element text { + "INDIVIDUAL" | + "GROUP" | + "RESOURCE" | + "ROOM" | + "UNKNOWN" + } +} + +# 3.2.4 Delegators + +delfromparam = element delegated-from { + value-cal-address+ +} + +# 3.2.5 Delegatees + +deltoparam = element delegated-to { + value-cal-address+ +} + +# 3.2.6 Directory Entry Reference + +dirparam = element dir { + value-uri +} + +# 3.2.7 Inline Encoding + +encodingparam = element encoding { + element text { + "8BIT" | + "BASE64" + } +} + +# 3.2.8 Format Type + +fmttypeparam = element fmttype { + value-text +} + +# 3.2.9 Free/Busy Time Type + +fbtypeparam = element fbtype { + element text { + "FREE" | + "BUSY" | + "BUSY-UNAVAILABLE" | + "BUSY-TENTATIVE" + } +} + +# 3.2.10 Language + +languageparam = element language { + value-text +} + +# 3.2.11 Group or List Membership + +memberparam = element member { + value-cal-address+ +} + +# 3.2.12 Participation Status + +partstatparam = element partstat { + type-partstat-event | + type-partstat-todo | + type-partstat-jour +} + +type-partstat-event = ( + element text { + "NEEDS-ACTION" | + "ACCEPTED" | + "DECLINED" | + "TENTATIVE" | + "DELEGATED" + } +) + +type-partstat-todo = ( + element text { + "NEEDS-ACTION" | + "ACCEPTED" | + "DECLINED" | + "TENTATIVE" | + "DELEGATED" | + "COMPLETED" | + "IN-PROCESS" + } +) + +type-partstat-jour = ( + element text { + "NEEDS-ACTION" | + "ACCEPTED" | + "DECLINED" + } +) + +# 3.2.13 Recurrence Identifier Range + +rangeparam = element range { + element text { + "THISANDFUTURE" + } +} + +# 3.2.14 Alarm Trigger Relationship + +trigrelparam = element related { + element text { + "START" | + "END" + } +} + +# 3.2.15 Relationship Type + +reltypeparam = element reltype { + element text { + "PARENT" | + "CHILD" | + "SIBLING" + } +} + +# 3.2.16 Participation Role + +roleparam = element role { + element text { + "CHAIR" | + "REQ-PARTICIPANT" | + "OPT-PARTICIPANT" | + "NON-PARTICIPANT" + } +} + +# 3.2.17 RSVP Expectation + +rsvpparam = element rsvp { + value-boolean +} + +# 3.2.18 Sent By + +sentbyparam = element sent-by { + value-cal-address +} + +# 3.2.19 Time Zone Identifier + +tzidparam = element tzid { + value-text +} + +# 3.3 Property Value Data Types + +# 3.3.1 BINARY + +value-binary = element binary { + xsd:string +} + +# 3.3.2 BOOLEAN + +value-boolean = element boolean { + xsd:boolean +} + +# 3.3.3 CAL-ADDRESS + +value-cal-address = element cal-address { + xsd:anyURI +} + +# 3.3.4 DATE + +pattern-date = xsd:string { + pattern = "\d\d\d\d-\d\d-\d\d" +} + +value-date = element date { + pattern-date +} + +# 3.3.5 DATE-TIME + +pattern-date-time = xsd:string { + pattern = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ?" +} + +value-date-time = element date-time { + pattern-date-time +} + +# 3.3.6 DURATION + +pattern-duration = xsd:string { + pattern = "(+|-)?P(\d+W)|(\d+D)?" + ~ "(T(\d+H(\d+M)?(\d+S)?)|" + ~ "(\d+M(\d+S)?)|" + ~ "(\d+S))?" +} + +value-duration = element duration { + pattern-duration +} + +# 3.3.7 FLOAT + +value-float = element float { + xsd:float +} + +# 3.3.8 INTEGER + +value-integer = element integer { + xsd:integer +} + +# 3.3.9 PERIOD + +value-period = element period { + element start { + pattern-date-time + }, + ( + element end { + pattern-date-time + } | + element duration { + pattern-duration + } + ) +} + +# 3.3.10 RECUR + +value-recur = element recur { + type-freq, + (type-until | type-count)?, + element interval { + xsd:positiveInteger + }?, + type-bysecond*, + type-byminute*, + type-byhour*, + type-byday*, + type-bymonthday*, + type-byyearday*, + type-byweekno*, + type-bymonth*, + type-bysetpos*, + element wkst { type-weekday }? +} + +type-freq = element freq { + "SECONDLY" | + "MINUTELY" | + "HOURLY" | + "DAILY" | + "WEEKLY" | + "MONTHLY" | + "YEARLY" +} + +type-until = element until { + type-date | + type-date-time +} + +type-count = element count { + xsd:positiveInteger +} + +type-bysecond = element bysecond { + xsd:nonNegativeInteger +} + +type-byminute = element byminute { + xsd:nonNegativeInteger +} + +type-byhour = element byhour { + xsd:nonNegativeInteger +} + +type-weekday = ( + "SU" | + "MO" | + "TU" | + "WE" | + "TH" | + "FR" | + "SA" +) + +type-byday = element byday { + xsd:integer?, + type-weekday +} + +type-bymonthday = element bymonthday { + xsd:integer +} + +type-byyearday = element byyearday { + xsd:integer +} + +type-byweekno = element byweekno { + xsd:integer +} + +type-bymonth = element bymonth { + xsd:positiveInteger +} + +type-bysetpos = element bysetpos { + xsd:integer +} + +# 3.3.11 TEXT + +value-text = element text { + xsd:string +} + +# 3.3.12 TIME + +pattern-time = xsd:string { + pattern = "\d\d:\d\d:\d\dZ?" +} + +value-time = element time { + pattern-time +} + +# 3.3.13 URI + +value-uri = element uri { + xsd:anyURI +} + +# 3.3.14 UTC-OFFSET + +value-utc-offset = element utc-offset { + xsd:string { pattern = "(+|-)\d\d:\d\d(:\d\d)?" } +} + +# UNKNOWN + +value-unknown = element unknown { + xsd:string +} + +# 3.4 iCalendar Stream + +start = element icalendar { + vcalendar+ +} + +# 3.6 Calendar Components + +vcalendar = element vcalendar { + type-calprops, + type-component +} + +type-calprops = element properties { + property-prodid & + property-version & + property-calscale? & + property-method? +} + +type-component = element components { + ( + component-vevent | + component-vtodo | + component-vjournal | + component-vfreebusy | + component-vtimezone + )* +} + +# 3.6.1 Event Component + +component-vevent = element vevent { + type-eventprop, + element components { + component-valarm+ + }? +} + +type-eventprop = element properties { + property-dtstamp & + property-dtstart & + property-uid & + + property-class? & + property-created? & + property-description? & + property-geo? & + property-last-mod? & + property-location? & + property-organizer? & + property-priority? & + property-seq? & + property-status-event? & + property-summary? & + property-transp? & + property-url? & + property-recurid? & + + property-rrule? & + + (property-dtend | property-duration)? & + + property-attach* & + property-attendee* & + property-categories* & + property-comment* & + property-contact* & + property-exdate* & + property-rstatus* & + property-related* & + property-resources* & + property-rdate* +} + +# 3.6.2 To-do Component + +component-vtodo = element vtodo { + type-todoprop, + element components { + component-valarm+ + }? +} + +type-todoprop = element properties { + property-dtstamp & + property-uid & + + property-class? & + property-completed? & + property-created? & + property-description? & + property-geo? & + property-last-mod? & + property-location? & + property-organizer? & + property-percent? & + property-priority? & + property-recurid? & + property-seq? & + property-status-todo? & + property-summary? & + property-url? & + + property-rrule? & + + ( + (property-dtstart?, property-dtend? ) | + (property-dtstart, property-duration)? + ) & + + property-attach* & + property-attendee* & + property-categories* & + property-comment* & + property-contact* & + property-exdate* & + property-rstatus* & + property-related* & + property-resources* & + property-rdate* +} + +# 3.6.3 Journal Component + +component-vjournal = element vjournal { + type-jourprop +} + +type-jourprop = element properties { + property-dtstamp & + property-uid & + + property-class? & + property-created? & + property-dtstart? & + property-last-mod? & + property-organizer? & + property-recurid? & + property-seq? & + property-status-jour? & + property-summary? & + property-url? & + + property-rrule? & + + property-attach* & + property-attendee* & + property-categories* & + property-comment* & + property-contact* & + property-description? & + property-exdate* & + property-related* & + property-rdate* & + property-rstatus* +} + +# 3.6.4 Free/Busy Component + +component-vfreebusy = element vfreebusy { + type-fbprop +} + +type-fbprop = element properties { + property-dtstamp & + property-uid & + + property-contact? & + property-dtstart? & + property-dtend? & + property-duration? & + property-organizer? & + property-url? & + + property-attendee* & + property-comment* & + property-freebusy* & + property-rstatus* +} + +# 3.6.5 Time Zone Component + +component-vtimezone = element vtimezone { + element properties { + property-tzid & + + property-last-mod? & + property-tzurl? + }, + element components { + (component-standard | component-daylight) & + component-standard* & + component-daylight* + } +} + +component-standard = element standard { + type-tzprop +} + +component-daylight = element daylight { + type-tzprop +} + +type-tzprop = element properties { + property-dtstart & + property-tzoffsetto & + property-tzoffsetfrom & + + property-rrule? & + + property-comment* & + property-rdate* & + property-tzname* +} + +# 3.6.6 Alarm Component + +component-valarm = element valarm { + type-audioprop | type-dispprop | type-emailprop +} + +type-audioprop = element properties { + property-action & + + property-trigger & + + (property-duration, property-repeat)? & + + property-attach? +} + +type-emailprop = element properties { + property-action & + property-description & + property-trigger & + property-summary & + + property-attendee+ & + + (property-duration, property-repeat)? & + + property-attach* +} + +type-dispprop = element properties { + property-action & + property-description & + property-trigger & + + (property-duration, property-repeat)? +} + +# 3.7 Calendar Properties + +# 3.7.1 Calendar Scale + +property-calscale = element calscale { + + element parameters { empty }?, + + element text { "GREGORIAN" } +} + +# 3.7.2 Method + +property-method = element method { + + element parameters { empty }?, + + value-text +} + +# 3.7.3 Product Identifier + +property-prodid = element prodid { + + element parameters { empty }?, + + value-text +} + +# 3.7.4 Version + +property-version = element version { + + element parameters { empty }?, + + element text { "2.0" } +} + +# 3.8 Component Properties + +# 3.8.1 Descriptive Component Properties + +# 3.8.1.1 Attachment + +property-attach = element attach { + + element parameters { + fmttypeparam? & + encodingparam? + }?, + + value-uri | value-binary +} + +# 3.8.1.2 Categories + +property-categories = element categories { + + element parameters { + languageparam? & + }?, + + value-text+ +} + +# 3.8.1.3 Classification + +property-class = element class { + + element parameters { empty }?, + + element text { + "PUBLIC" | + "PRIVATE" | + "CONFIDENTIAL" + } +} + +# 3.8.1.4 Comment + +property-comment = element comment { + + element parameters { + altrepparam? & + languageparam? + }?, + + value-text +} + +# 3.8.1.5 Description + +property-description = element description { + + element parameters { + altrepparam? & + languageparam? + }?, + + value-text +} + +# 3.8.1.6 Geographic Position + +property-geo = element geo { + + element parameters { empty }?, + + element latitude { xsd:float }, + element longitude { xsd:float } +} + +# 3.8.1.7 Location + +property-location = element location { + + element parameters { + + altrepparam? & + languageparam? + }?, + + value-text +} + +# 3.8.1.8 Percent Complete + +property-percent = element percent-complete { + + element parameters { empty }?, + + value-integer +} + +# 3.8.1.9 Priority + +property-priority = element priority { + + element parameters { empty }?, + + value-integer +} + +# 3.8.1.10 Resources + +property-resources = element resources { + + element parameters { + altrepparam? & + languageparam? + }?, + + value-text+ +} + +# 3.8.1.11 Status + +property-status-event = element status { + + element parameters { empty }?, + + element text { + "TENTATIVE" | + "CONFIRMED" | + "CANCELLED" + } +} + +property-status-todo = element status { + + element parameters { empty }?, + + element text { + "NEEDS-ACTION" | + "COMPLETED" | + "IN-PROCESS" | + "CANCELLED" + } +} + +property-status-jour = element status { + + element parameters { empty }?, + + element text { + "DRAFT" | + "FINAL" | + "CANCELLED" + } +} + +# 3.8.1.12 Summary + +property-summary = element summary { + + element parameters { + altrepparam? & + languageparam? + }?, + + value-text +} + +# 3.8.2 Date and Time Component Properties + +# 3.8.2.1 Date/Time Completed + +property-completed = element completed { + + element parameters { empty }?, + + value-date-time +} + +# 3.8.2.2 Date/Time End + +property-dtend = element dtend { + + element parameters { + tzidparam? + }?, + + value-date-time | + value-date +} + +# 3.8.2.3 Date/Time Due + +property-due = element due { + + element parameters { + tzidparam? + }?, + + value-date-time | + value-date +} + +# 3.8.2.4 Date/Time Start + +property-dtstart = element dtstart { + + element parameters { + tzidparam? + }?, + + value-date-time | + value-date +} + +# 3.8.2.5 Duration + +property-duration = element duration { + + element parameters { empty }?, + + value-duration +} + +# 3.8.2.6 Free/Busy Time + +property-freebusy = element freebusy { + + element parameters { + fbtypeparam? + }?, + + + value-period+ +} + +# 3.8.2.7 Time Transparency + +property-transp = element transp { + + element parameters { empty }?, + + element text { + "OPAQUE" | + "TRANSPARENT" + } +} + +# 3.8.3 Time Zone Component Properties + +# 3.8.3.1 Time Zone Identifier + +property-tzid = element tzid { + + element parameters { empty }?, + + value-text +} + +# 3.8.3.2 Time Zone Name + +property-tzname = element tzname { + + element parameters { + languageparam? + }?, + + value-text +} + +# 3.8.3.3 Time Zone Offset From + +property-tzoffsetfrom = element tzoffsetfrom { + + element parameters { empty }?, + + value-utc-offset +} + +# 3.8.3.4 Time Zone Offset To + +property-tzoffsetto = element tzoffsetto { + + element parameters { empty }?, + + value-utc-offset +} + +# 3.8.3.5 Time Zone URL + +property-tzurl = element tzurl { + + element parameters { empty }?, + + value-uri +} + +# 3.8.4 Relationship Component Properties + +# 3.8.4.1 Attendee + +property-attendee = element attendee { + + element parameters { + cutypeparam? & + memberparam? & + roleparam? & + partstatparam? & + rsvpparam? & + deltoparam? & + delfromparam? & + sentbyparam? & + cnparam? & + dirparam? & + languageparam? + }?, + + value-cal-address +} + +# 3.8.4.2 Contact + +property-contact = element contact { + + element parameters { + altrepparam? & + languageparam? + }?, + + value-text +} + +# 3.8.4.3 Organizer + +property-organizer = element organizer { + + element parameters { + cnparam? & + dirparam? & + sentbyparam? & + languageparam? + }?, + + value-cal-address +} + +# 3.8.4.4 Recurrence ID + +property-recurid = element recurrence-id { + + element parameters { + tzidparam? & + rangeparam? + }?, + + value-date-time | + value-date +} + +# 3.8.4.5 Related-To + +property-related = element related-to { + + element parameters { + reltypeparam? + }?, + + value-text +} + +# 3.8.4.6 Uniform Resource Locator + +property-url = element url { + + element parameters { empty }?, + + value-uri +} + +# 3.8.4.7 Unique Identifier + +property-uid = element uid { + + element parameters { empty }?, + + value-text +} + +# 3.8.5 Recurrence Component Properties + +# 3.8.5.1 Exception Date/Times + +property-exdate = element exdate { + + element parameters { + tzidparam? + }?, + + value-date-time+ | + value-date+ +} + +# 3.8.5.2 Recurrence Date/Times + +property-rdate = element rdate { + + element parameters { + tzidparam? + }?, + + value-date-time+ | + value-date+ | + value-period+ +} + +# 3.8.5.3 Recurrence Rule + +property-rrule = element rrule { + + element parameters { empty }?, + + value-recur +} + +# 3.8.6 Alarm Component Properties + +# 3.8.6.1 Action + +property-action = element action { + + element parameters { empty }?, + + element text { + "AUDIO" | + "DISPLAY" | + "EMAIL" + } +} + +# 3.8.6.2 Repeat Count + +property-repeat = element repeat { + + element parameters { empty }?, + + value-integer +} + +# 3.8.6.3 Trigger + +property-trigger = element trigger { + + ( + element parameters { + trigrelparam? + }?, + + value-duration + ) | + ( + element parameters { empty }?, + + value-date-time + ) +} + +# 3.8.7 Change Management Component Properties + +# 3.8.7.1 Date/Time Created + +property-created = element created { + + element parameters { empty }?, + + value-date-time +} + +# 3.8.7.2 Date/Time Stamp + +property-dtstamp = element dtstamp { + + element parameters { empty }?, + + value-date-time +} + +# 3.8.7.3 Last Modified + +property-last-mod = element last-modified { + + element parameters { empty }?, + + value-date-time +} + +# 3.8.7.4 Sequence Number + +property-seq = element sequence { + + element parameters { empty }?, + + value-integer +} + +# 3.8.8 Miscellaneous Component Properties + +# 3.8.8.3 Request Status + +property-rstatus = element request-status { + + element parameters { + languageparam? + }?, + + element code { xsd:string }, + element description { xsd:string }, + element data { xsd:string }? +} diff --git a/libs/composer/vendor/sabre/vobject/resources/schema/xcard.rng b/libs/composer/vendor/sabre/vobject/resources/schema/xcard.rng new file mode 100644 index 000000000000..c0b7cfb35456 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/resources/schema/xcard.rng @@ -0,0 +1,388 @@ +# RELAX NG Schema for vCard in XML +# Extract from RFC6351. +# Erratum 2994 applied. +# Erratum 3047 applied. +# Erratum 3008 applied. +# Erratum 4247 applied. + +default namespace = "urn:ietf:params:xml:ns:vcard-4.0" + +### Section 3.3: vCard Format Specification +# +# 3.3 +iana-token = xsd:string { pattern = "[a-zA-Z0-9\-]+" } +x-name = xsd:string { pattern = "x-[a-zA-Z0-9\-]+" } + +### Section 4: Value types +# +# 4.1 +value-text = element text { text } +value-text-list = value-text+ + +# 4.2 +value-uri = element uri { xsd:anyURI } + +# 4.3.1 +value-date = element date { + xsd:string { pattern = "\d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d" } + } + +# 4.3.2 +value-time = element time { + xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)" + ~ "(Z|[+\-]\d\d(\d\d)?)?" } + } + +# 4.3.3 +value-date-time = element date-time { + xsd:string { pattern = "(\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?" + ~ "(Z|[+\-]\d\d(\d\d)?)?" } + } + +# 4.3.4 +value-date-and-or-time = value-date | value-date-time | value-time + +# 4.3.5 +value-timestamp = element timestamp { + xsd:string { pattern = "\d{8}T\d{6}(Z|[+\-]\d\d(\d\d)?)?" } + } + +# 4.4 +value-boolean = element boolean { xsd:boolean } + +# 4.5 +value-integer = element integer { xsd:integer } + +# 4.6 +value-float = element float { xsd:float } + +# 4.7 +value-utc-offset = element utc-offset { + xsd:string { pattern = "[+\-]\d\d(\d\d)?" } + } + +# 4.8 +value-language-tag = element language-tag { + xsd:string { pattern = "([a-z]{2,3}((-[a-z]{3}){0,3})?|[a-z]{4,8})" + ~ "(-[a-z]{4})?(-([a-z]{2}|\d{3}))?" + ~ "(-([0-9a-z]{5,8}|\d[0-9a-z]{3}))*" + ~ "(-[0-9a-wyz](-[0-9a-z]{2,8})+)*" + ~ "(-x(-[0-9a-z]{1,8})+)?|x(-[0-9a-z]{1,8})+|" + ~ "[a-z]{1,3}(-[0-9a-z]{2,8}){1,2}" } + } + +### Section 5: Parameters +# +# 5.1 +param-language = element language { value-language-tag }? + +# 5.2 +param-pref = element pref { + element integer { + xsd:integer { minInclusive = "1" maxInclusive = "100" } + } + }? + +# 5.4 +param-altid = element altid { value-text }? + +# 5.5 +param-pid = element pid { + element text { xsd:string { pattern = "\d+(\.\d+)?" } }+ + }? + +# 5.6 +param-type = element type { element text { "work" | "home" }+ }? + +# 5.7 +param-mediatype = element mediatype { value-text }? + +# 5.8 +param-calscale = element calscale { element text { "gregorian" } }? + +# 5.9 +param-sort-as = element sort-as { value-text+ }? + +# 5.10 +param-geo = element geo { value-uri }? + +# 5.11 +param-tz = element tz { value-text | value-uri }? + +### Section 6: Properties +# +# 6.1.3 +property-source = element source { + element parameters { param-altid, param-pid, param-pref, + param-mediatype }?, + value-uri + } + +# 6.1.4 +property-kind = element kind { + element text { "individual" | "group" | "org" | "location" | + x-name | iana-token }* + } + +# 6.2.1 +property-fn = element fn { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type }?, + value-text + } + +# 6.2.2 +property-n = element n { + element parameters { param-language, param-sort-as, param-altid }?, + element surname { text }+, + element given { text }+, + element additional { text }+, + element prefix { text }+, + element suffix { text }+ + } + +# 6.2.3 +property-nickname = element nickname { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type }?, + value-text-list + } + +# 6.2.4 +property-photo = element photo { + element parameters { param-altid, param-pid, param-pref, param-type, + param-mediatype }?, + value-uri + } + +# 6.2.5 +property-bday = element bday { + element parameters { param-altid, param-calscale }?, + (value-date-and-or-time | value-text) + } + +# 6.2.6 +property-anniversary = element anniversary { + element parameters { param-altid, param-calscale }?, + (value-date-and-or-time | value-text) + } + +# 6.2.7 +property-gender = element gender { + element sex { "" | "M" | "F" | "O" | "N" | "U" }, + element identity { text }? + } + +# 6.3.1 +param-label = element label { value-text }? +property-adr = element adr { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type, param-geo, param-tz, + param-label }?, + element pobox { text }+, + element ext { text }+, + element street { text }+, + element locality { text }+, + element region { text }+, + element code { text }+, + element country { text }+ + } + +# 6.4.1 +property-tel = element tel { + element parameters { + param-altid, + param-pid, + param-pref, + element type { + element text { "work" | "home" | "text" | "voice" + | "fax" | "cell" | "video" | "pager" + | "textphone" | x-name | iana-token }+ + }?, + param-mediatype + }?, + (value-text | value-uri) + } + +# 6.4.2 +property-email = element email { + element parameters { param-altid, param-pid, param-pref, + param-type }?, + value-text + } + +# 6.4.3 +property-impp = element impp { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# 6.4.4 +property-lang = element lang { + element parameters { param-altid, param-pid, param-pref, + param-type }?, + value-language-tag + } + +# 6.5.1 +property-tz = element tz { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + (value-text | value-uri | value-utc-offset) + } + +# 6.5.2 +property-geo = element geo { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# 6.6.1 +property-title = element title { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type }?, + value-text + } + +# 6.6.2 +property-role = element role { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type }?, + value-text + } + +# 6.6.3 +property-logo = element logo { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type, param-mediatype }?, + value-uri + } + +# 6.6.4 +property-org = element org { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type, param-sort-as }?, + value-text-list + } + +# 6.6.5 +property-member = element member { + element parameters { param-altid, param-pid, param-pref, + param-mediatype }?, + value-uri + } + +# 6.6.6 +property-related = element related { + element parameters { + param-altid, + param-pid, + param-pref, + element type { + element text { + "work" | "home" | "contact" | "acquaintance" | + "friend" | "met" | "co-worker" | "colleague" | "co-resident" | + "neighbor" | "child" | "parent" | "sibling" | "spouse" | + "kin" | "muse" | "crush" | "date" | "sweetheart" | "me" | + "agent" | "emergency" + }+ + }?, + param-mediatype + }?, + (value-uri | value-text) + } + +# 6.7.1 +property-categories = element categories { + element parameters { param-altid, param-pid, param-pref, + param-type }?, + value-text-list + } + +# 6.7.2 +property-note = element note { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type }?, + value-text + } + +# 6.7.3 +property-prodid = element prodid { value-text } + +# 6.7.4 +property-rev = element rev { value-timestamp } + +# 6.7.5 +property-sound = element sound { + element parameters { param-language, param-altid, param-pid, + param-pref, param-type, param-mediatype }?, + value-uri + } + +# 6.7.6 +property-uid = element uid { value-uri } + +# 6.7.7 +property-clientpidmap = element clientpidmap { + element sourceid { xsd:positiveInteger }, + value-uri + } + +# 6.7.8 +property-url = element url { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# 6.8.1 +property-key = element key { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + (value-uri | value-text) + } + +# 6.9.1 +property-fburl = element fburl { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# 6.9.2 +property-caladruri = element caladruri { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# 6.9.3 +property-caluri = element caluri { + element parameters { param-altid, param-pid, param-pref, + param-type, param-mediatype }?, + value-uri + } + +# Top-level grammar +property = property-adr | property-anniversary | property-bday + | property-caladruri | property-caluri | property-categories + | property-clientpidmap | property-email | property-fburl + | property-fn | property-geo | property-impp | property-key + | property-kind | property-lang | property-logo + | property-member | property-n | property-nickname + | property-note | property-org | property-photo + | property-prodid | property-related | property-rev + | property-role | property-gender | property-sound + | property-source | property-tel | property-title + | property-tz | property-uid | property-url +start = element vcards { + element vcard { + (property + | element group { + attribute name { text }, + property* + })+ + }+ + } diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php new file mode 100644 index 000000000000..a012433b0707 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php @@ -0,0 +1,24 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class AttachIssueTest extends TestCase { + + function testRead() { + + $event = <<<ICS +BEGIN:VCALENDAR\r +BEGIN:VEVENT\r +ATTACH;FMTTYPE=;ENCODING=:Zm9v\r +END:VEVENT\r +END:VCALENDAR\r + +ICS; + $obj = Reader::read($event); + $this->assertEquals($event, $obj->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/BirthdayCalendarGeneratorTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/BirthdayCalendarGeneratorTest.php new file mode 100644 index 000000000000..6d729b658162 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/BirthdayCalendarGeneratorTest.php @@ -0,0 +1,564 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class BirthdayCalendarGeneratorTest extends TestCase { + + use PHPUnitAssertions; + + function testVcardStringWithValidBirthday() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testArrayOfVcardStringsWithValidBirthdays() { + + $generator = new BirthdayCalendarGenerator(); + $input = []; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Doe;John;;Mr. +FN:John Doe +BDAY:19820210 +UID:bar +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:John Doe's Birthday +DTSTART;VALUE=DATE:19820210 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=bar;X-SABRE-VCARD-FN=John Doe:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testArrayOfVcardStringsWithValidBirthdaysViaConstructor() { + + $input = []; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Doe;John;;Mr. +FN:John Doe +BDAY:19820210 +UID:bar +END:VCARD +VCF; + + $generator = new BirthdayCalendarGenerator($input); + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:John Doe's Birthday +DTSTART;VALUE=DATE:19820210 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=bar;X-SABRE-VCARD-FN=John Doe:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardObjectWithValidBirthday() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $input = Reader::read($input); + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testArrayOfVcardObjectsWithValidBirthdays() { + + $generator = new BirthdayCalendarGenerator(); + $input = []; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Doe;John;;Mr. +FN:John Doe +BDAY:19820210 +UID:bar +END:VCARD +VCF; + + foreach ($input as $key => $value) { + $input[$key] = Reader::read($value); + } + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:John Doe's Birthday +DTSTART;VALUE=DATE:19820210 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=bar;X-SABRE-VCARD-FN=John Doe:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithValidBirthdayWithXAppleOmitYear() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-07 +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:20000407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump;X-SABRE-OMIT-YEAR=2000:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithValidBirthdayWithoutYear() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:4.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:--04-07 +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Birthday +DTSTART;VALUE=DATE:20000407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump;X-SABRE-OMIT-YEAR=2000:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithInvalidBirthday() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:foo +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithNoBirthday() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithValidBirthdayLocalized() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:**ANY** +DTSTAMP:**ANY** +SUMMARY:Forrest Gump's Geburtstag +DTSTART;VALUE=DATE:19850407 +RRULE:FREQ=YEARLY +TRANSP:TRANSPARENT +X-SABRE-BDAY;X-SABRE-VCARD-UID=foo;X-SABRE-VCARD-FN=Forrest Gump:BDAY +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $generator->setFormat('%1$s\'s Geburtstag'); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + function testVcardStringWithEmptyBirthdayProperty() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY: +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseException() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<FOO +BEGIN:FOO +FOO:Bar +END:FOO +FOO; + + $generator->setObjects($input); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testInvalidArgumentException() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Foo +DTSTART;VALUE=DATE:19850407 +END:VEVENT +END:VCALENDAR +ICS; + + $generator->setObjects($input); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testInvalidArgumentExceptionForPartiallyInvalidArray() { + + $generator = new BirthdayCalendarGenerator(); + $input = []; + + $input[] = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +FN:Forrest Gump +BDAY:19850407 +UID:foo +END:VCARD +VCF; + $calendar = new Component\VCalendar(); + + $input = $calendar->add('VEVENT', [ + 'SUMMARY' => 'Foo', + 'DTSTART' => new \DateTime('NOW'), + ]); + + $generator->setObjects($input); + + } + + function testBrokenVcardWithoutFN() { + + $generator = new BirthdayCalendarGenerator(); + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +N:Gump;Forrest;;Mr. +BDAY:19850407 +UID:foo +END:VCARD +VCF; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +END:VCALENDAR +ICS; + + $generator->setObjects($input); + $output = $generator->getResult(); + + $this->assertVObjectEqualsVObject( + $expected, + $output + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/CliTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/CliTest.php new file mode 100644 index 000000000000..607f616a279c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/CliTest.php @@ -0,0 +1,644 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +/** + * Tests the cli. + * + * Warning: these tests are very rudimentary. + */ +class CliTest extends TestCase { + + function setUp() { + + $this->cli = new CliMock(); + $this->cli->stderr = fopen('php://memory', 'r+'); + $this->cli->stdout = fopen('php://memory', 'r+'); + + } + + function testInvalidArg() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', '--hi']) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + function testQuiet() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', '-q']) + ); + $this->assertTrue($this->cli->quiet); + + rewind($this->cli->stderr); + $this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr))); + + } + + function testHelp() { + + $this->assertEquals( + 0, + $this->cli->main(['vobject', '-h']) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + function testFormat() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', '--format=jcard']) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertEquals('jcard', $this->cli->format); + + } + + function testFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', '--format=foo']) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + function testInputFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', '--inputformat=foo']) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + + function testNoInputFile() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', 'color']) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + function testTooManyArgs() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', 'color', 'a', 'b', 'c']) + ); + + } + + function testUnknownCommand() { + + $this->assertEquals( + 1, + $this->cli->main(['vobject', 'foo', '-']) + ); + + } + + function testConvertJson() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(['vobject', 'convert', '--format=json', '-']) + ); + + rewind($this->cli->stdout); + $version = Version::VERSION; + $this->assertEquals( + '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject ' . $version . '\/\/EN"],["fn",{},"text","Cowboy Henk"]]]', + stream_get_contents($this->cli->stdout) + ); + + } + + function testConvertJCardPretty() { + + if (version_compare(PHP_VERSION, '5.4.0') < 0) { + $this->markTestSkipped('This test required PHP 5.4.0'); + } + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(['vobject', 'convert', '--format=jcard', '--pretty', '-']) + ); + + rewind($this->cli->stdout); + + // PHP 5.5.12 changed the output + + $expected = <<<JCARD +[ + "vcard", + [ + [ + "versi +JCARD; + + $this->assertStringStartsWith( + $expected, + stream_get_contents($this->cli->stdout) + ); + + } + + function testConvertJCalFail() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'convert', '--format=jcal', '--inputformat=mimedir', '-']) + ); + + } + + function testConvertMimeDir() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<JCARD +[ + "vcard", + [ + [ + "version", + { + + }, + "text", + "4.0" + ], + [ + "prodid", + { + + }, + "text", + "-\/\/Sabre\/\/Sabre VObject 3.1.0\/\/EN" + ], + [ + "fn", + { + + }, + "text", + "Cowboy Henk" + ] + ] +] +JCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(['vobject', 'convert', '--format=mimedir', '--inputformat=json', '--pretty', '-']) + ); + + rewind($this->cli->stdout); + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, ["\n" => "\r\n"]), + stream_get_contents($this->cli->stdout) + ); + + } + + function testConvertDefaultFormats() { + + $outputFile = SABRE_TEMPDIR . 'bar.json'; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'convert', 'foo.json', $outputFile]) + ); + + $this->assertEquals('json', $this->cli->inputFormat); + $this->assertEquals('json', $this->cli->format); + + } + + function testConvertDefaultFormats2() { + + $outputFile = SABRE_TEMPDIR . 'bar.ics'; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'convert', 'foo.ics', $outputFile]) + ); + + $this->assertEquals('mimedir', $this->cli->inputFormat); + $this->assertEquals('mimedir', $this->cli->format); + + } + + function testVCard3040() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(['vobject', 'convert', '--format=vcard40', '--pretty', '-']) + ); + + rewind($this->cli->stdout); + + $version = Version::VERSION; + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject $version//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, ["\n" => "\r\n"]), + stream_get_contents($this->cli->stdout) + ); + + } + + function testVCard4030() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(['vobject', 'convert', '--format=vcard30', '--pretty', '-']) + ); + + $version = Version::VERSION; + + rewind($this->cli->stdout); + $expected = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject $version//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, ["\n" => "\r\n"]), + stream_get_contents($this->cli->stdout) + ); + + } + + function testVCard4021() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'convert', '--format=vcard21', '--pretty', '-']) + ); + + } + + function testValidate() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +UID:foo +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + $result = $this->cli->main(['vobject', 'validate', '-']); + + $this->assertEquals( + 0, + $result + ); + + } + + function testValidateFail() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.0 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'validate', '-']) + ); + + } + + function testValidateFail2() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:5.0 +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'validate', '-']) + ); + + } + + function testRepair() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:5.0 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(['vobject', 'repair', '-']) + ); + + rewind($this->cli->stdout); + $this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout)); + } + + function testRepairNothing() { + + $inputStream = fopen('php://memory', 'r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +BEGIN:VEVENT +UID:foo +DTSTAMP:20140122T233226Z +DTSTART:20140101T120000Z +END:VEVENT +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $result = $this->cli->main(['vobject', 'repair', '-']); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorCalendar() { + + $inputStream = fopen('php://memory', 'r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject {$version}//EN +BEGIN:VTIMEZONE +END:VTIMEZONE +BEGIN:VEVENT +ATTENDEE;RSVP=TRUE:mailto:foo@example.org +REQUEST-STATUS:5;foo +ATTACH:blabla +END:VEVENT +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $result = $this->cli->main(['vobject', 'color', '-']); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorVCard() { + + $inputStream = fopen('php://memory', 'r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject {$version}//EN +ADR:1;2;3;4a,4b;5;6 +group.TEL:123454768 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $result = $this->cli->main(['vobject', 'color', '-']); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } +} + +class CliMock extends Cli { + + public $quiet = false; + + public $format; + + public $pretty; + + public $stdin; + + public $stdout; + + public $stderr; + + public $inputFormat; + + public $outputFormat; + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/AvailableTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/AvailableTest.php new file mode 100644 index 000000000000..3d38784cffb2 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/AvailableTest.php @@ -0,0 +1,74 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +/** + * We use `RFCxxx` has a placeholder for the + * https://tools.ietf.org/html/draft-daboo-calendar-availability-05 name. + */ +class AvailableTest extends TestCase { + + function testAvailableComponent() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:AVAILABLE +END:AVAILABLE +END:VCALENDAR +VCAL; + $document = Reader::read($vcal); + $this->assertInstanceOf(__NAMESPACE__ . '\Available', $document->AVAILABLE); + + } + + function testGetEffectiveStartEnd() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:AVAILABLE +DTSTART:20150717T162200Z +DTEND:20150717T172200Z +END:AVAILABLE +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $tz = new DateTimeZone('UTC'); + $this->assertEquals( + [ + new DateTimeImmutable('2015-07-17 16:22:00', $tz), + new DateTimeImmutable('2015-07-17 17:22:00', $tz), + ], + $document->AVAILABLE->getEffectiveStartEnd() + ); + + } + + function testGetEffectiveStartEndDuration() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:AVAILABLE +DTSTART:20150717T162200Z +DURATION:PT1H +END:AVAILABLE +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $tz = new DateTimeZone('UTC'); + $this->assertEquals( + [ + new DateTimeImmutable('2015-07-17 16:22:00', $tz), + new DateTimeImmutable('2015-07-17 17:22:00', $tz), + ], + $document->AVAILABLE->getEffectiveStartEnd() + ); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php new file mode 100644 index 000000000000..16c08ec2df26 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php @@ -0,0 +1,178 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTime; +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +class VAlarmTest extends TestCase { + + /** + * @dataProvider timeRangeTestData + */ + function testInTimeRange(VAlarm $valarm, $start, $end, $outcome) { + + $this->assertEquals($outcome, $valarm->isInTimeRange($start, $end)); + + } + + function timeRangeTestData() { + + $tests = []; + + $calendar = new VCalendar(); + + // Hard date and time + $valarm1 = $calendar->createComponent('VALARM'); + $valarm1->add( + $calendar->createProperty('TRIGGER', '20120312T130000Z', ['VALUE' => 'DATE-TIME']) + ); + + $tests[] = [$valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true]; + $tests[] = [$valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false]; + + // Relation to start time of event + $valarm2 = $calendar->createComponent('VALARM'); + $valarm2->add( + $calendar->createProperty('TRIGGER', '-P1D', ['VALUE' => 'DURATION']) + ); + + $vevent2 = $calendar->createComponent('VEVENT'); + $vevent2->DTSTART = '20120313T130000Z'; + $vevent2->add($valarm2); + + $tests[] = [$valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true]; + $tests[] = [$valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false]; + + // Relation to end time of event + $valarm3 = $calendar->createComponent('VALARM'); + $valarm3->add($calendar->createProperty('TRIGGER', '-P1D', ['VALUE' => 'DURATION', 'RELATED' => 'END'])); + + $vevent3 = $calendar->createComponent('VEVENT'); + $vevent3->DTSTART = '20120301T130000Z'; + $vevent3->DTEND = '20120401T130000Z'; + $vevent3->add($valarm3); + + $tests[] = [$valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false]; + $tests[] = [$valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true]; + + // Relation to end time of todo + $valarm4 = $calendar->createComponent('VALARM'); + $valarm4->TRIGGER = '-P1D'; + $valarm4->TRIGGER['VALUE'] = 'DURATION'; + $valarm4->TRIGGER['RELATED'] = 'END'; + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DTSTART = '20120301T130000Z'; + $vtodo4->DUE = '20120401T130000Z'; + $vtodo4->add($valarm4); + + $tests[] = [$valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false]; + $tests[] = [$valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true]; + + // Relation to start time of event + repeat + $valarm5 = $calendar->createComponent('VALARM'); + $valarm5->TRIGGER = '-P1D'; + $valarm5->TRIGGER['VALUE'] = 'DURATION'; + $valarm5->REPEAT = 10; + $valarm5->DURATION = 'P1D'; + + $vevent5 = $calendar->createComponent('VEVENT'); + $vevent5->DTSTART = '20120301T130000Z'; + $vevent5->add($valarm5); + + $tests[] = [$valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true]; + + // Relation to start time of event + duration, but no repeat + $valarm6 = $calendar->createComponent('VALARM'); + $valarm6->TRIGGER = '-P1D'; + $valarm6->TRIGGER['VALUE'] = 'DURATION'; + $valarm6->DURATION = 'P1D'; + + $vevent6 = $calendar->createComponent('VEVENT'); + $vevent6->DTSTART = '20120313T130000Z'; + $vevent6->add($valarm6); + + $tests[] = [$valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true]; + $tests[] = [$valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false]; + + + // Relation to end time of event (DURATION instead of DTEND) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED'] = 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->DURATION = 'P30D'; + $vevent7->add($valarm7); + + $tests[] = [$valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false]; + $tests[] = [$valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true]; + + // Relation to end time of event (No DTEND or DURATION) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED'] = 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->add($valarm7); + + $tests[] = [$valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true]; + $tests[] = [$valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false]; + + + return $tests; + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testInTimeRangeInvalidComponent() { + + $calendar = new VCalendar(); + $valarm = $calendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P1D'; + $valarm->TRIGGER['RELATED'] = 'END'; + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->add($valarm); + + $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00')); + + } + + /** + * This bug was found and reported on the mailing list. + */ + function testInTimeRangeBuggy() { + +$input = <<<BLA +BEGIN:VCALENDAR +BEGIN:VTODO +DTSTAMP:20121003T064931Z +UID:b848cb9a7bb16e464a06c222ca1f8102@examle.com +STATUS:NEEDS-ACTION +DUE:20121005T000000Z +SUMMARY:Task 1 +CATEGORIES:AlarmCategory +BEGIN:VALARM +TRIGGER:-PT10M +ACTION:DISPLAY +DESCRIPTION:Task 1 +END:VALARM +END:VTODO +END:VCALENDAR +BLA; + + $vobj = Reader::read($input); + + $this->assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00'))); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php new file mode 100644 index 000000000000..bb3c1ba60263 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php @@ -0,0 +1,491 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\TestCase; +use Sabre\VObject; +use Sabre\VObject\Reader; + +/** + * We use `RFCxxx` has a placeholder for the + * https://tools.ietf.org/html/draft-daboo-calendar-availability-05 name. + */ +class VAvailabilityTest extends TestCase { + + function testVAvailabilityComponent() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL; + $document = Reader::read($vcal); + + $this->assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY); + + } + + function testGetEffectiveStartEnd() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20150717T162200Z +DTEND:20150717T172200Z +END:VAVAILABILITY +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $tz = new DateTimeZone('UTC'); + $this->assertEquals( + [ + new DateTimeImmutable('2015-07-17 16:22:00', $tz), + new DateTimeImmutable('2015-07-17 17:22:00', $tz), + ], + $document->VAVAILABILITY->getEffectiveStartEnd() + ); + + } + + function testGetEffectiveStartDuration() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20150717T162200Z +DURATION:PT1H +END:VAVAILABILITY +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $tz = new DateTimeZone('UTC'); + $this->assertEquals( + [ + new DateTimeImmutable('2015-07-17 16:22:00', $tz), + new DateTimeImmutable('2015-07-17 17:22:00', $tz), + ], + $document->VAVAILABILITY->getEffectiveStartEnd() + ); + + } + + function testGetEffectiveStartEndUnbound() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $this->assertEquals( + [ + null, + null, + ], + $document->VAVAILABILITY->getEffectiveStartEnd() + ); + + } + + function testIsInTimeRangeUnbound() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $this->assertTrue( + $document->VAVAILABILITY->isInTimeRange(new DateTimeImmutable('2015-07-17'), new DateTimeImmutable('2015-07-18')) + ); + + } + + function testIsInTimeRangeOutside() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20140101T000000Z +DTEND:20140102T000000Z +END:VAVAILABILITY +END:VCALENDAR +VCAL; + + $document = Reader::read($vcal); + $this->assertFalse( + $document->VAVAILABILITY->isInTimeRange(new DateTimeImmutable('2015-07-17'), new DateTimeImmutable('2015-07-18')) + ); + + } + + function testRFCxxxSection3_1_availabilityprop_required() { + + // UID and DTSTAMP are present. + $this->assertIsValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID and DTSTAMP are missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTAMP is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +DTSTAMP:20111005T133225Z +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + } + + function testRFCxxxSection3_1_availabilityprop_optional_once() { + + $properties = [ + 'BUSYTYPE:BUSY', + 'CLASS:PUBLIC', + 'CREATED:20111005T135125Z', + 'DESCRIPTION:Long bla bla', + 'DTSTART:20111005T020000', + 'LAST-MODIFIED:20111005T135325Z', + 'ORGANIZER:mailto:foo@example.com', + 'PRIORITY:1', + 'SEQUENCE:0', + 'SUMMARY:Bla bla', + 'URL:http://example.org/' + ]; + + // They are all present, only once. + $this->assertIsValid(Reader::read($this->template($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->template([ + $property, + $property + ]))); + } + + } + + function testRFCxxxSection3_1_availabilityprop_dtend_duration() { + + // Only DTEND. + $this->assertIsValid(Reader::read($this->template([ + 'DTEND:21111005T133225Z' + ]))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->template([ + 'DURATION:PT1H' + ]))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->template([ + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + ]))); + } + + function testAvailableSubComponent() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +BEGIN:AVAILABLE +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL; + $document = Reader::read($vcal); + + $this->assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE); + + } + + function testRFCxxxSection3_1_availableprop_required() { + + // UID, DTSTAMP and DTSTART are present. + $this->assertIsValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID, DTSTAMP and DTSTART are missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTAMP is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTART is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + } + + function testRFCxxxSection3_1_available_dtend_duration() { + + // Only DTEND. + $this->assertIsValid(Reader::read($this->templateAvailable([ + 'DTEND:21111005T133225Z' + ]))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->templateAvailable([ + 'DURATION:PT1H' + ]))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->templateAvailable([ + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + ]))); + } + + function testRFCxxxSection3_1_available_optional_once() { + + $properties = [ + 'CREATED:20111005T135125Z', + 'DESCRIPTION:Long bla bla', + 'LAST-MODIFIED:20111005T135325Z', + 'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z', + 'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', + 'SUMMARY:Bla bla' + ]; + + // They are all present, only once. + $this->assertIsValid(Reader::read($this->templateAvailable($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->templateAvailable([ + $property, + $property + ]))); + } + + } + function testRFCxxxSection3_2() { + + $this->assertEquals( + 'BUSY', + Reader::read($this->templateAvailable([ + 'BUSYTYPE:BUSY' + ])) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-UNAVAILABLE', + Reader::read($this->templateAvailable([ + 'BUSYTYPE:BUSY-UNAVAILABLE' + ])) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-TENTATIVE', + Reader::read($this->templateAvailable([ + 'BUSYTYPE:BUSY-TENTATIVE' + ])) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + } + + protected function assertIsValid(VObject\Document $document) { + + $validationResult = $document->validate(); + if ($validationResult) { + $messages = array_map(function($item) { return $item['message']; }, $validationResult); + $this->fail('Failed to assert that the supplied document is a valid document. Validation messages: ' . implode(', ', $messages)); + } + $this->assertEmpty($document->validate()); + + } + + protected function assertIsNotValid(VObject\Document $document) { + + $this->assertNotEmpty($document->validate()); + + } + + protected function template(array $properties) { + + return $this->_template( + <<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +… +END:VAVAILABILITY +END:VCALENDAR +VCAL +, + $properties + ); + + } + + protected function templateAvailable(array $properties) { + + return $this->_template( + <<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +… +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL +, + $properties + ); + + } + + protected function _template($template, array $properties) { + + return str_replace('…', implode("\r\n", $properties), $template); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php new file mode 100644 index 000000000000..9443b3c48836 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php @@ -0,0 +1,783 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeZone; +use PHPUnit\Framework\TestCase; +use Sabre\VObject; + +class VCalendarTest extends TestCase { + + use VObject\PHPUnitAssertions; + + /** + * @dataProvider expandData + */ + function testExpand($input, $output, $timeZone = 'UTC', $start = '2011-12-01', $end = '2011-12-31') { + + $vcal = VObject\Reader::read($input); + + $timeZone = new DateTimeZone($timeZone); + + $vcal = $vcal->expand( + new \DateTime($start), + new \DateTime($end), + $timeZone + ); + + // This will normalize the output + $output = VObject\Reader::read($output)->serialize(); + + $this->assertVObjectEqualsVObject($output, $vcal->serialize()); + + } + + function expandData() { + + $tests = []; + + // No data + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +END:VCALENDAR +'; + + $output = $input; + $tests[] = [$input,$output]; + + + // Simple events + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +BEGIN:VEVENT +UID:bla2 +SUMMARY:NotInExpand +DTSTART;VALUE=DATE:20120101 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + + // Removing timezone info + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +END:VTIMEZONE +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART;TZID=Europe/Paris:20111203T130102 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART:20111203T120102Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + + // Removing timezone info from sub-components. See Issue #278 + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +END:VTIMEZONE +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART;TZID=Europe/Paris:20111203T130102 +BEGIN:VALARM +TRIGGER;VALUE=DATE-TIME;TZID=America/New_York:20151209T133200 +END:VALARM +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART:20111203T120102Z +BEGIN:VALARM +TRIGGER;VALUE=DATE-TIME:20151209T183200Z +END:VALARM +END:VEVENT +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + + // Recurrence rule + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111209T120000Z +DTEND:20111209T130000Z +RECURRENCE-ID:20111209T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + + // Recurrence rule + override + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + + // Floating dates and times. + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:bla1 +DTSTART:20141112T195000 +END:VEVENT +BEGIN:VEVENT +UID:bla2 +DTSTART;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141112 +RRULE:FREQ=DAILY;COUNT=2 +END:VEVENT +END:VCALENDAR +ICS; + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:bla1 +DTSTART:20141112T225000Z +END:VEVENT +BEGIN:VEVENT +UID:bla2 +DTSTART;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141112 +RECURRENCE-ID;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141113 +RECURRENCE-ID;VALUE=DATE:20141113 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = [$input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01']; + + // Recurrence rule with no valid instances + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule3 +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY;COUNT=1 +EXDATE:20111125T120000Z +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +END:VCALENDAR +'; + + $tests[] = [$input, $output]; + return $tests; + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testBrokenEventExpand() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +RRULE:FREQ=WEEKLY +DTSTART;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + $vcal = VObject\Reader::read($input); + $vcal->expand( + new \DateTime('2011-12-01'), + new \DateTime('2011-12-31') + ); + + } + + function testGetDocumentType() { + + $vcard = new VCalendar(); + $vcard->VERSION = '2.0'; + $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType()); + + } + + function testValidateCorrect() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +DTSTAMP:20140122T233226Z +UID:foo +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals([], $vcal->validate(), 'Got an error'); + + } + + function testValidateNoVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateWrongVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:3.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateNoProdId() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleCalScale() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +CALSCALE:GREGORIAN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleMethod() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateTwoMasterEvents() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateOneMasterEvent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(0, count($vcal->validate())); + + } + + function testGetBaseComponent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertEquals('test', $result->SUMMARY->getValue()); + + } + + function testGetBaseComponentNoResult() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +RECURRENCE-ID;VALUE=DATE:20111202 +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertNull($result); + + } + + function testGetBaseComponentWithFilter() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent('VEVENT'); + $this->assertEquals('test', $result->SUMMARY->getValue()); + + } + + function testGetBaseComponentWithFilterNoResult() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VTODO +SUMMARY:test +UID:foo +DTSTAMP:20140122T234434Z +END:VTODO +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent('VEVENT'); + $this->assertNull($result); + + } + + function testNoComponents() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + 0, + 3, + "An iCalendar object must have at least 1 component." + ); + + } + + function testCalDAVNoComponents() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)." + ); + + } + + function testCalDAVMultiUID() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VEVENT +UID:foo +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +BEGIN:VEVENT +UID:bar +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have components with the same UID." + ); + + } + + function testCalDAVMultiComponent() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VEVENT +UID:foo +RECURRENCE-ID:20150109T185200Z +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +BEGIN:VTODO +UID:foo +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VTODO +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)." + ); + + } + + function testCalDAVMETHOD() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +PRODID:vobject +BEGIN:VEVENT +UID:foo +RECURRENCE-ID:20150109T185200Z +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server MUST NOT have a METHOD property." + ); + + } + + function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($ics); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = []; + foreach ($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php new file mode 100644 index 000000000000..c64450ece3a8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php @@ -0,0 +1,312 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject; + +class VCardTest extends TestCase { + + /** + * @dataProvider validateData + */ + function testValidate($input, $expectedWarnings, $expectedRepairedOutput) { + + $vcard = VObject\Reader::read($input); + + $warnings = $vcard->validate(); + + $warnMsg = []; + foreach ($warnings as $warning) { + $warnMsg[] = $warning['message']; + } + + $this->assertEquals($expectedWarnings, $warnMsg); + + $vcard->validate(VObject\Component::REPAIR); + + $this->assertEquals( + $expectedRepairedOutput, + $vcard->serialize() + ); + + } + + function validateData() { + + $tests = []; + + // Correct + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + [], + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ]; + + // No VERSION + $tests[] = [ + "BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + [ + 'VERSION MUST appear exactly once in a VCARD component', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ]; + + // Unknown version + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + [ + 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + ], + "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ]; + + // No FN + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + [ + 'The FN property must appear in the VCARD component exactly 1 time', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + ]; + // No FN, N fallback + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n", + [ + 'The FN property must appear in the VCARD component exactly 1 time', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n", + ]; + // No FN, N fallback, no first name + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n", + [ + 'The FN property must appear in the VCARD component exactly 1 time', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n", + ]; + // No FN, ORG fallback + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nEND:VCARD\r\n", + [ + 'The FN property must appear in the VCARD component exactly 1 time', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n", + ]; + // No FN, EMAIL fallback + $tests[] = [ + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEMAIL:1@example.org\r\nEND:VCARD\r\n", + [ + 'The FN property must appear in the VCARD component exactly 1 time', + ], + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEMAIL:1@example.org\r\nFN:1@example.org\r\nEND:VCARD\r\n", + ]; + return $tests; + + } + + function testGetDocumentType() { + + $vcard = new VCard([], false); + $vcard->VERSION = '2.1'; + $this->assertEquals(VCard::VCARD21, $vcard->getDocumentType()); + + $vcard = new VCard([], false); + $vcard->VERSION = '3.0'; + $this->assertEquals(VCard::VCARD30, $vcard->getDocumentType()); + + $vcard = new VCard([], false); + $vcard->VERSION = '4.0'; + $this->assertEquals(VCard::VCARD40, $vcard->getDocumentType()); + + $vcard = new VCard([], false); + $this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType()); + } + + function testGetByType() { + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +EMAIL;TYPE=home:1@example.org +EMAIL;TYPE=work:2@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('1@example.org', $vcard->getByType('EMAIL', 'home')->getValue()); + $this->assertEquals('2@example.org', $vcard->getByType('EMAIL', 'work')->getValue()); + $this->assertNull($vcard->getByType('EMAIL', 'non-existant')); + $this->assertNull($vcard->getByType('ADR', 'non-existant')); + } + + function testPreferredNoPref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +EMAIL:1@example.org +EMAIL:2@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWithPref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +EMAIL:1@example.org +EMAIL;TYPE=PREF:2@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWith40Pref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +EMAIL:1@example.org +EMAIL;PREF=3:2@example.org +EMAIL;PREF=2:3@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredNotFound() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertNull($vcard->preferred('EMAIL')); + + } + + function testNoUIDCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'vCards on CardDAV servers MUST have a UID property.' + ); + + } + + function testNoUIDNoCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + 0, + 2, + 'Adding a UID to a vCard property is recommended.' + ); + + } + function testNoUIDNoCardDAVRepair() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::REPAIR, + 1, + 'Adding a UID to a vCard property is recommended.' + ); + + } + + function testVCard21CardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN:John Doe +UID:foo +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'CardDAV servers are not allowed to accept vCard 2.1.' + ); + + } + + function testVCard21NoCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN:John Doe +UID:foo +END:VCARD +VCF; + $this->assertValidate( + $vcard, + 0, + 0 + ); + + } + + function assertValidate($vcf, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($vcf); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = []; + foreach ($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php new file mode 100644 index 000000000000..dd5d850581e3 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php @@ -0,0 +1,97 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; + +class VEventTest extends TestCase { + + /** + * @dataProvider timeRangeTestData + */ + function testInTimeRange(VEvent $vevent, $start, $end, $outcome) { + + $this->assertEquals($outcome, $vevent->isInTimeRange($start, $end)); + + } + + function timeRangeTestData() { + + $tests = []; + + $calendar = new VCalendar(); + + $vevent = $calendar->createComponent('VEVENT'); + $vevent->DTSTART = '20111223T120000Z'; + $tests[] = [$vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vevent2 = clone $vevent; + $vevent2->DTEND = '20111225T120000Z'; + $tests[] = [$vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vevent3 = clone $vevent; + $vevent3->DURATION = 'P1D'; + $tests[] = [$vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vevent4 = clone $vevent; + $vevent4->DTSTART = '20111225'; + $vevent4->DTSTART['VALUE'] = 'DATE'; + $tests[] = [$vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + // Event with no end date should be treated as lasting the entire day. + $tests[] = [$vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true]; + // DTEND is non inclusive so all day events should not be returned on the next day. + $tests[] = [$vevent4, new \DateTime('2011-12-26 00:00:00'), new \DateTime('2011-12-26 17:00:00'), false]; + // The timezone of timerange in question also needs to be considered. + $tests[] = [$vevent4, new \DateTime('2011-12-26 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2011-12-26 17:00:00', new \DateTimeZone('Europe/Berlin')), false]; + + $vevent5 = clone $vevent; + $vevent5->DURATION = 'P1D'; + $vevent5->RRULE = 'FREQ=YEARLY'; + $tests[] = [$vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + $tests[] = [$vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true]; + + $vevent6 = clone $vevent; + $vevent6->DTSTART = '20111225'; + $vevent6->DTSTART['VALUE'] = 'DATE'; + $vevent6->DTEND = '20111225'; + $vevent6->DTEND['VALUE'] = 'DATE'; + + $tests[] = [$vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + // Added this test to ensure that recurrence rules with no DTEND also + // get checked for the entire day. + $vevent7 = clone $vevent; + $vevent7->DTSTART = '20120101'; + $vevent7->DTSTART['VALUE'] = 'DATE'; + $vevent7->RRULE = 'FREQ=MONTHLY'; + $tests[] = [$vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true]; + // The timezone of timerange in question should also be considered. + $tests[] = [$vevent7, new \DateTime('2012-02-02 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2012-02-03 00:00:00', new \DateTimeZone('Europe/Berlin')), false]; + + // Added this test to check recurring events that have no instances. + $vevent8 = clone $vevent; + $vevent8->DTSTART = '20130329T140000'; + $vevent8->DTEND = '20130329T153000'; + $vevent8->RRULE = ['FREQ' => 'WEEKLY', 'BYDAY' => ['FR'], 'UNTIL' => '20130412T115959Z']; + $vevent8->add('EXDATE', '20130405T140000'); + $vevent8->add('EXDATE', '20130329T140000'); + $tests[] = [$vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false]; + + // Added this test to check recurring all day event that repeat every day + $vevent9 = clone $vevent; + $vevent9->DTSTART = '20161027'; + $vevent9->DTEND = '20161028'; + $vevent9->RRULE = 'FREQ=DAILY'; + $tests[] = [$vevent9, new \DateTime('2016-10-31'), new \DateTime('2016-12-12'), true]; + + return $tests; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php new file mode 100644 index 000000000000..49f91d55f715 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php @@ -0,0 +1,67 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject; +use Sabre\VObject\Reader; + +class VFreeBusyTest extends TestCase { + + function testIsFree() { + + $input = <<<BLA +BEGIN:VCALENDAR +BEGIN:VFREEBUSY +FREEBUSY;FBTYPE=FREE:20120912T000500Z/PT1H +FREEBUSY;FBTYPE=BUSY:20120912T010000Z/20120912T020000Z +FREEBUSY;FBTYPE=BUSY-TENTATIVE:20120912T020000Z/20120912T030000Z +FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20120912T030000Z/20120912T040000Z +FREEBUSY;FBTYPE=BUSY:20120912T050000Z/20120912T060000Z,20120912T080000Z/20120912T090000Z +FREEBUSY;FBTYPE=BUSY:20120912T100000Z/PT1H +END:VFREEBUSY +END:VCALENDAR +BLA; + + $obj = VObject\Reader::read($input); + $vfb = $obj->VFREEBUSY; + + $tz = new \DateTimeZone('UTC'); + + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz))); + + // Checking whether the end time is treated as non-inclusive + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz))); + + } + + function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VFREEBUSY +UID:some-random-id +DTSTAMP:20140402T180200Z +END:VFREEBUSY +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([], $messages); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php new file mode 100644 index 000000000000..61aca55b720d --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php @@ -0,0 +1,101 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component; +use Sabre\VObject\Reader; + +class VJournalTest extends TestCase { + + /** + * @dataProvider timeRangeTestData + */ + function testInTimeRange(VJournal $vtodo, $start, $end, $outcome) { + + $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VJOURNAL +UID:12345678 +DTSTAMP:20140402T174100Z +END:VJOURNAL +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([], $messages); + + } + + function testValidateBroken() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VJOURNAL +UID:12345678 +DTSTAMP:20140402T174100Z +URL:http://example.org/ +URL:http://example.com/ +END:VJOURNAL +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals( + ["URL MUST NOT appear more than once in a VJOURNAL component"], + $messages + ); + + } + + function timeRangeTestData() { + + $calendar = new VCalendar(); + + $tests = []; + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->DTSTART = '20111223T120000Z'; + $tests[] = [$vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vjournal2 = $calendar->createComponent('VJOURNAL'); + $vjournal2->DTSTART = '20111223'; + $vjournal2->DTSTART['VALUE'] = 'DATE'; + $tests[] = [$vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vjournal3 = $calendar->createComponent('VJOURNAL'); + $tests[] = [$vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false]; + $tests[] = [$vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + return $tests; + } + + + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php new file mode 100644 index 000000000000..e91b1a941a95 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +class VTimeZoneTest extends TestCase { + + function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([], $messages); + + } + + function testGetTimeZone() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $tz = new \DateTimeZone('America/Toronto'); + + $this->assertEquals( + $tz, + $obj->VTIMEZONE->getTimeZone() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php new file mode 100644 index 000000000000..8f76d04b841b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php @@ -0,0 +1,179 @@ +<?php + +namespace Sabre\VObject\Component; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component; +use Sabre\VObject\Reader; + +class VTodoTest extends TestCase { + + /** + * @dataProvider timeRangeTestData + */ + function testInTimeRange(VTodo $vtodo, $start, $end, $outcome) { + + $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + function timeRangeTestData() { + + $tests = []; + + $calendar = new VCalendar(); + + $vtodo = $calendar->createComponent('VTODO'); + $vtodo->DTSTART = '20111223T120000Z'; + $tests[] = [$vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo2 = clone $vtodo; + $vtodo2->DURATION = 'P1D'; + $tests[] = [$vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo3 = clone $vtodo; + $vtodo3->DUE = '20111225'; + $tests[] = [$vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DUE = '20111225'; + $tests[] = [$vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo5 = $calendar->createComponent('VTODO'); + $vtodo5->COMPLETED = '20111225'; + $tests[] = [$vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo6 = $calendar->createComponent('VTODO'); + $vtodo6->CREATED = '20111225'; + $tests[] = [$vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo7 = $calendar->createComponent('VTODO'); + $vtodo7->CREATED = '20111225'; + $vtodo7->COMPLETED = '20111226'; + $tests[] = [$vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false]; + + $vtodo7 = $calendar->createComponent('VTODO'); + $tests[] = [$vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true]; + $tests[] = [$vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true]; + + return $tests; + + } + + function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:1234-21355-123156 +DTSTAMP:20140402T183400Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([], $messages); + + } + + function testValidateInvalid() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([ + "UID MUST appear exactly once in a VTODO component", + "DTSTAMP MUST appear exactly once in a VTODO component", + ], $messages); + + } + + function testValidateDUEDTSTARTMisMatch() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:FOO +DTSTART;VALUE=DATE-TIME:20140520T131600Z +DUE;VALUE=DATE:20140520 +DTSTAMP;VALUE=DATE-TIME:20140520T131600Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([ + "The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART", + ], $messages); + + } + + function testValidateDUEbeforeDTSTART() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:FOO +DTSTART;VALUE=DATE:20140520 +DUE;VALUE=DATE:20140518 +DTSTAMP;VALUE=DATE-TIME:20140520T131600Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = []; + foreach ($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals([ + "DUE must occur after DTSTART", + ], $messages); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ComponentTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ComponentTest.php new file mode 100644 index 000000000000..cb4a3c853446 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ComponentTest.php @@ -0,0 +1,588 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VCard; + +class ComponentTest extends TestCase { + + function testIterate() { + + $comp = new VCalendar([], false); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $count = 0; + foreach ($comp->children() as $key => $subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component', $subcomponent); + + } + $this->assertEquals(2, $count); + $this->assertEquals(1, $key); + + } + + function testMagicGet() { + + $comp = new VCalendar([], false); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $event = $comp->vevent; + $this->assertInstanceOf('Sabre\\VObject\\Component', $event); + $this->assertEquals('VEVENT', $event->name); + + $this->assertInternalType('null', $comp->vjournal); + + } + + function testMagicGetGroups() { + + $comp = new VCard(); + + $sub = $comp->createProperty('GROUP1.EMAIL', '1@1.com'); + $comp->add($sub); + + $sub = $comp->createProperty('GROUP2.EMAIL', '2@2.com'); + $comp->add($sub); + + $sub = $comp->createProperty('EMAIL', '3@3.com'); + $comp->add($sub); + + $emails = $comp->email; + $this->assertEquals(3, count($emails)); + + $email1 = $comp->{"group1.email"}; + $this->assertEquals('EMAIL', $email1[0]->name); + $this->assertEquals('GROUP1', $email1[0]->group); + + $email3 = $comp->{".email"}; + $this->assertEquals('EMAIL', $email3[0]->name); + $this->assertEquals(null, $email3[0]->group); + + } + + function testMagicIsset() { + + $comp = new VCalendar(); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $this->assertTrue(isset($comp->vevent)); + $this->assertTrue(isset($comp->vtodo)); + $this->assertFalse(isset($comp->vjournal)); + + } + + function testMagicSetScalar() { + + $comp = new VCalendar(); + $comp->myProp = 'myValue'; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->MYPROP); + $this->assertEquals('myValue', (string)$comp->MYPROP); + + + } + + function testMagicSetScalarTwice() { + + $comp = new VCalendar([], false); + $comp->myProp = 'myValue'; + $comp->myProp = 'myValue'; + + $this->assertEquals(1, count($comp->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->MYPROP); + $this->assertEquals('myValue', (string)$comp->MYPROP); + + } + + function testMagicSetArray() { + + $comp = new VCalendar(); + $comp->ORG = ['Acme Inc', 'Section 9']; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $comp->ORG); + $this->assertEquals(['Acme Inc', 'Section 9'], $comp->ORG->getParts()); + + } + + function testMagicSetComponent() { + + $comp = new VCalendar(); + + // Note that 'myProp' is ignored here. + $comp->myProp = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp)); + + $this->assertEquals('VEVENT', $comp->VEVENT->name); + + } + + function testMagicSetTwice() { + + $comp = new VCalendar([], false); + + $comp->VEVENT = $comp->createComponent('VEVENT'); + $comp->VEVENT = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT', $comp->VEVENT->name); + + } + + function testArrayAccessGet() { + + $comp = new VCalendar([], false); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertEquals(2, count($comp->children())); + $this->assertTrue($comp->vevent[1] instanceof Component); + $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary); + + } + + function testArrayAccessExists() { + + $comp = new VCalendar(); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertTrue(isset($comp->vevent[0])); + $this->assertTrue(isset($comp->vevent[1])); + + } + + /** + * @expectedException LogicException + */ + function testArrayAccessSet() { + + $comp = new VCalendar(); + $comp['hey'] = 'hi there'; + + } + /** + * @expectedException LogicException + */ + function testArrayAccessUnset() { + + $comp = new VCalendar(); + unset($comp[0]); + + } + + function testAddScalar() { + + $comp = new VCalendar([], false); + + $comp->add('myprop', 'value'); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children()[0]; + + $this->assertTrue($bla instanceof Property); + $this->assertEquals('MYPROP', $bla->name); + $this->assertEquals('value', (string)$bla); + + } + + function testAddScalarParams() { + + $comp = new VCalendar([], false); + + $comp->add('myprop', 'value', ['param1' => 'value1']); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children()[0]; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $bla); + $this->assertEquals('MYPROP', $bla->name); + $this->assertEquals('value', (string)$bla); + + $this->assertEquals(1, count($bla->parameters())); + + $this->assertEquals('PARAM1', $bla->parameters['PARAM1']->name); + $this->assertEquals('value1', $bla->parameters['PARAM1']->getValue()); + + } + + + function testAddComponent() { + + $comp = new VCalendar([], false); + + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT', $comp->VEVENT->name); + + } + + function testAddComponentTwice() { + + $comp = new VCalendar([], false); + + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(2, count($comp->children())); + + $this->assertEquals('VEVENT', $comp->VEVENT->name); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail() { + + $comp = new VCalendar(); + $comp->add($comp->createComponent('VEVENT'), 'hello'); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail2() { + + $comp = new VCalendar(); + $comp->add([]); + + } + + function testMagicUnset() { + + $comp = new VCalendar([], false); + $comp->add($comp->createComponent('VEVENT')); + + unset($comp->vevent); + + $this->assertEquals(0, count($comp->children())); + + } + + + function testCount() { + + $comp = new VCalendar(); + $this->assertEquals(1, $comp->count()); + + } + + function testChildren() { + + $comp = new VCalendar([], false); + + // Note that 'myProp' is ignored here. + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->children(); + $this->assertInternalType('array', $r); + $this->assertEquals(2, count($r)); + } + + function testGetComponents() { + + $comp = new VCalendar(); + + $comp->add($comp->createProperty('FOO', 'BAR')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->getComponents(); + $this->assertInternalType('array', $r); + $this->assertEquals(1, count($r)); + $this->assertEquals('VTODO', $r[0]->name); + } + + function testSerialize() { + + $comp = new VCalendar([], false); + $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize()); + + } + + function testSerializeChildren() { + + $comp = new VCalendar([], false); + $event = $comp->add($comp->createComponent('VEVENT')); + unset($event->DTSTAMP, $event->UID); + $todo = $comp->add($comp->createComponent('VTODO')); + unset($todo->DTSTAMP, $todo->UID); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str); + + } + + function testSerializeOrderCompAndProp() { + + $comp = new VCalendar([], false); + $comp->add($event = $comp->createComponent('VEVENT')); + $comp->add('PROP1', 'BLABLA'); + $comp->add('VERSION', '2.0'); + $comp->add($comp->createComponent('VTIMEZONE')); + + unset($event->DTSTAMP, $event->UID); + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str); + + } + + function testAnotherSerializeOrderProp() { + + $prop4s = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + + $comp = new VCard([], false); + + $comp->__set('SOMEPROP', 'FOO'); + $comp->__set('ANOTHERPROP', 'FOO'); + $comp->__set('THIRDPROP', 'FOO'); + foreach ($prop4s as $prop4) { + $comp->add('PROP4', 'FOO ' . $prop4); + } + $comp->__set('PROPNUMBERFIVE', 'FOO'); + $comp->__set('PROPNUMBERSIX', 'FOO'); + $comp->__set('PROPNUMBERSEVEN', 'FOO'); + $comp->__set('PROPNUMBEREIGHT', 'FOO'); + $comp->__set('PROPNUMBERNINE', 'FOO'); + $comp->__set('PROPNUMBERTEN', 'FOO'); + $comp->__set('VERSION', '2.0'); + $comp->__set('UID', 'FOO'); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str); + + } + + function testInstantiateWithChildren() { + + $comp = new VCard([ + 'ORG' => ['Acme Inc.', 'Section 9'], + 'FN' => 'Finn The Human', + ]); + + $this->assertEquals(['Acme Inc.', 'Section 9'], $comp->ORG->getParts()); + $this->assertEquals('Finn The Human', $comp->FN->getValue()); + + } + + function testInstantiateSubComponent() { + + $comp = new VCalendar(); + $event = $comp->createComponent('VEVENT', [ + $comp->createProperty('UID', '12345'), + ]); + $comp->add($event); + + $this->assertEquals('12345', $comp->VEVENT->UID->getValue()); + + } + + function testRemoveByName() { + + $comp = new VCalendar([], false); + $comp->add('prop1', 'val1'); + $comp->add('prop2', 'val2'); + $comp->add('prop2', 'val2'); + + $comp->remove('prop2'); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + function testRemoveByObj() { + + $comp = new VCalendar([], false); + $comp->add('prop1', 'val1'); + $prop = $comp->add('prop2', 'val2'); + + $comp->remove($prop); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testRemoveNotFound() { + + $comp = new VCalendar([], false); + $prop = $comp->createProperty('A', 'B'); + $comp->remove($prop); + + } + + /** + * @dataProvider ruleData + */ + function testValidateRules($componentList, $errorCount) { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'Hi', [], $defaults = false); + foreach ($componentList as $v) { + $component->add($v, 'Hello.'); + } + + $this->assertEquals($errorCount, count($component->validate())); + + } + + function testValidateRepair() { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'Hi', [], $defaults = false); + $component->validate(Component::REPAIR); + $this->assertEquals('yow', $component->BAR->getValue()); + + } + + function testValidateRepairShouldNotDeduplicatePropertiesWhenValuesDiffer() { + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); + $component->add('BAZ', 'BAZ'); + $component->add('GIR', 'VALUE1'); + $component->add('GIR', 'VALUE2'); // Different values + + $messages = $component->validate(Component::REPAIR); + + $this->assertEquals(1, count($messages)); + $this->assertEquals(3, $messages[0]['level']); + $this->assertEquals(2, count($component->GIR)); + } + + function testValidateRepairShouldNotDeduplicatePropertiesWhenParametersDiffer() { + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); + $component->add('BAZ', 'BAZ'); + $component->add('GIR', 'VALUE')->add('PARAM', '1'); + $component->add('GIR', 'VALUE')->add('PARAM', '2'); // Same value but different parameters + + $messages = $component->validate(Component::REPAIR); + + $this->assertEquals(1, count($messages)); + $this->assertEquals(3, $messages[0]['level']); + $this->assertEquals(2, count($component->GIR)); + } + + function testValidateRepairShouldDeduplicatePropertiesWhenValuesAndParametersAreEqual() { + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); + $component->add('BAZ', 'BAZ'); + $component->add('GIR', 'VALUE')->add('PARAM', 'P'); + $component->add('GIR', 'VALUE')->add('PARAM', 'P'); + + $messages = $component->validate(Component::REPAIR); + + $this->assertEquals(1, count($messages)); + $this->assertEquals(1, $messages[0]['level']); + $this->assertEquals(1, count($component->GIR)); + } + + function testValidateRepairShouldDeduplicatePropertiesWhenValuesAreEqual() { + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard, 'WithDuplicateGIR', []); + $component->add('BAZ', 'BAZ'); + $component->add('GIR', 'VALUE'); + $component->add('GIR', 'VALUE'); + + $messages = $component->validate(Component::REPAIR); + + $this->assertEquals(1, count($messages)); + $this->assertEquals(1, $messages[0]['level']); + $this->assertEquals(1, count($component->GIR)); + } + + function ruleData() { + + return [ + + [[], 2], + [['FOO'], 3], + [['BAR'], 1], + [['BAZ'], 1], + [['BAR','BAZ'], 0], + [['BAR','BAZ','ZIM',], 0], + [['BAR','BAZ','ZIM','GIR'], 0], + [['BAR','BAZ','ZIM','GIR','GIR'], 1], + + ]; + + } + +} + +class FakeComponent extends Component { + + function getValidationRules() { + + return [ + 'FOO' => '0', + 'BAR' => '1', + 'BAZ' => '+', + 'ZIM' => '*', + 'GIR' => '?', + ]; + + } + + function getDefaults() { + + return [ + 'BAR' => 'yow', + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php new file mode 100644 index 000000000000..a633fbacd369 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php @@ -0,0 +1,700 @@ +<?php + +namespace Sabre\VObject; + +use DateInterval; +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\TestCase; + +class DateTimeParserTest extends TestCase { + + function testParseICalendarDuration() { + + $this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W', true)); + $this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D', true)); + $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S', true)); + $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M', true)); + $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S', true)); + $this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S', true)); + $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S')); + + } + + function testParseICalendarDurationDateInterval() { + + $expected = new DateInterval('P7D'); + $this->assertEquals($expected, DateTimeParser::parseDuration('P1W')); + $this->assertEquals($expected, DateTimeParser::parse('P1W')); + + $expected = new DateInterval('PT3M'); + $expected->invert = true; + $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M')); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testParseICalendarDurationFail() { + + DateTimeParser::parseDuration('P1X', true); + + } + + function testParseICalendarDateTime() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405'); + + $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('UTC')); + + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testParseICalendarDateTimeBadFormat() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405 '); + + } + + /** + * @depends testParseICalendarDateTime + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testParseICalendarDateTimeInvalidTime() { + + $dateTime = DateTimeParser::parseDateTime('20100316T251405'); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405Z'); + + $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC2() { + + $dateTime = DateTimeParser::parseDateTime('20101211T160000Z'); + + $compare = new DateTimeImmutable('2010-12-11 16:00:00', new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeCustomTimeZone() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam')); + + $compare = new DateTimeImmutable('2010-03-16 14:14:05', new DateTimeZone('Europe/Amsterdam')); + $this->assertEquals($compare, $dateTime); + + } + + function testParseICalendarDate() { + + $dateTime = DateTimeParser::parseDate('20100316'); + + $expected = new DateTimeImmutable('2010-03-16 00:00:00', new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('20100316'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events + */ + function testParseICalendarDateGreaterThan4000() { + + $dateTime = DateTimeParser::parseDate('45001231'); + + $expected = new DateTimeImmutable('4500-12-31 00:00:00', new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events + */ + function testParseICalendarDateTimeGreaterThan4000() { + + $dateTime = DateTimeParser::parseDateTime('45001231T235959'); + + $expected = new DateTimeImmutable('4500-12-31 23:59:59', new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231T235959'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * @depends testParseICalendarDate + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testParseICalendarDateBadFormat() { + + $dateTime = DateTimeParser::parseDate('20100316T141405'); + + } + + /** + * @depends testParseICalendarDate + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testParseICalendarDateInvalidDate() { + + $dateTime = DateTimeParser::parseDate('20101331'); + + } + + /** + * @dataProvider vcardDates + */ + function testVCardDate($input, $output) { + + $this->assertEquals( + $output, + DateTimeParser::parseVCardDateTime($input) + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testBadVCardDate() { + + DateTimeParser::parseVCardDateTime('1985---01'); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testBadVCardTime() { + + DateTimeParser::parseVCardTime('23:12:166'); + + } + + function vcardDates() { + + return [ + [ + "19961022T140000", + [ + "year" => 1996, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => 00, + "timezone" => null + ], + ], + [ + "--1022T1400", + [ + "year" => null, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => null, + "timezone" => null + ], + ], + [ + "---22T14", + [ + "year" => null, + "month" => null, + "date" => 22, + "hour" => 14, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "19850412", + [ + "year" => 1985, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "1985-04", + [ + "year" => 1985, + "month" => 04, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "1985", + [ + "year" => 1985, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "--0412", + [ + "year" => null, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "---12", + [ + "year" => null, + "month" => null, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "T102200", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 0, + "timezone" => null + ], + ], + [ + "T1022", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => null, + "timezone" => null + ], + ], + [ + "T10", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => null, + "second" => null, + "timezone" => null + ], + ], + [ + "T-2200", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => 22, + "second" => 00, + "timezone" => null + ], + ], + [ + "T--00", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => 00, + "timezone" => null + ], + ], + [ + "T102200Z", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => 'Z' + ], + ], + [ + "T102200-0800", + [ + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => '-0800' + ], + ], + + // extended format + [ + "2012-11-29T15:10:53Z", + [ + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ], + ], + + // with milliseconds + [ + "20121129T151053.123Z", + [ + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ], + ], + + // extended format with milliseconds + [ + "2012-11-29T15:10:53.123Z", + [ + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ], + ], + ]; + + } + + function testDateAndOrTime_DateWithYearMonthDay() { + + $this->assertDateAndOrTimeEqualsTo( + '20150128', + [ + 'year' => '2015', + 'month' => '01', + 'date' => '28' + ] + ); + + } + + function testDateAndOrTime_DateWithYearMonth() { + + $this->assertDateAndOrTimeEqualsTo( + '2015-01', + [ + 'year' => '2015', + 'month' => '01' + ] + ); + + } + + function testDateAndOrTime_DateWithMonth() { + + $this->assertDateAndOrTimeEqualsTo( + '--01', + [ + 'month' => '01' + ] + ); + + } + + function testDateAndOrTime_DateWithMonthDay() { + + $this->assertDateAndOrTimeEqualsTo( + '--0128', + [ + 'month' => '01', + 'date' => '28' + ] + ); + + } + + function testDateAndOrTime_DateWithDay() { + + $this->assertDateAndOrTimeEqualsTo( + '---28', + [ + 'date' => '28' + ] + ); + + } + + function testDateAndOrTime_TimeWithHour() { + + $this->assertDateAndOrTimeEqualsTo( + '13', + [ + 'hour' => '13' + ] + ); + + } + + function testDateAndOrTime_TimeWithHourMinute() { + + $this->assertDateAndOrTimeEqualsTo( + '1353', + [ + 'hour' => '13', + 'minute' => '53' + ] + ); + + } + + function testDateAndOrTime_TimeWithHourSecond() { + + $this->assertDateAndOrTimeEqualsTo( + '135301', + [ + 'hour' => '13', + 'minute' => '53', + 'second' => '01' + ] + + ); + + } + + function testDateAndOrTime_TimeWithMinute() { + + $this->assertDateAndOrTimeEqualsTo( + '-53', + [ + 'minute' => '53' + ] + ); + + } + + function testDateAndOrTime_TimeWithMinuteSecond() { + + $this->assertDateAndOrTimeEqualsTo( + '-5301', + [ + 'minute' => '53', + 'second' => '01' + ] + ); + + } + + function testDateAndOrTime_TimeWithSecond() { + + $this->assertTrue(true); + + /** + * This is unreachable due to a conflict between date and time pattern. + * This is an error in the specification, not in our implementation. + */ + } + + function testDateAndOrTime_TimeWithSecondZ() { + + $this->assertDateAndOrTimeEqualsTo( + '--01Z', + [ + 'second' => '01', + 'timezone' => 'Z' + ] + ); + + } + + function testDateAndOrTime_TimeWithSecondTZ() { + + $this->assertDateAndOrTimeEqualsTo( + '--01+1234', + [ + 'second' => '01', + 'timezone' => '+1234' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithYearMonthDayHour() { + + $this->assertDateAndOrTimeEqualsTo( + '20150128T13', + [ + 'year' => '2015', + 'month' => '01', + 'date' => '28', + 'hour' => '13' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithMonthDayHour() { + + $this->assertDateAndOrTimeEqualsTo( + '--0128T13', + [ + 'month' => '01', + 'date' => '28', + 'hour' => '13' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithDayHour() { + + $this->assertDateAndOrTimeEqualsTo( + '---28T13', + [ + 'date' => '28', + 'hour' => '13' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithDayHourMinute() { + + $this->assertDateAndOrTimeEqualsTo( + '---28T1353', + [ + 'date' => '28', + 'hour' => '13', + 'minute' => '53' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithDayHourMinuteSecond() { + + $this->assertDateAndOrTimeEqualsTo( + '---28T135301', + [ + 'date' => '28', + 'hour' => '13', + 'minute' => '53', + 'second' => '01' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithDayHourZ() { + + $this->assertDateAndOrTimeEqualsTo( + '---28T13Z', + [ + 'date' => '28', + 'hour' => '13', + 'timezone' => 'Z' + ] + ); + + } + + function testDateAndOrTime_DateTimeWithDayHourTZ() { + + $this->assertDateAndOrTimeEqualsTo( + '---28T13+1234', + [ + 'date' => '28', + 'hour' => '13', + 'timezone' => '+1234' + ] + ); + + } + + protected function assertDateAndOrTimeEqualsTo($date, $parts) { + + $this->assertSame( + DateTimeParser::parseVCardDateAndOrTime($date), + array_merge( + [ + 'year' => null, + 'month' => null, + 'date' => null, + 'hour' => null, + 'minute' => null, + 'second' => null, + 'timezone' => null + ], + $parts + ) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/DocumentTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/DocumentTest.php new file mode 100644 index 000000000000..37c6698c89e9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/DocumentTest.php @@ -0,0 +1,93 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class DocumentTest extends TestCase { + + function testGetDocumentType() { + + $doc = new MockDocument(); + $this->assertEquals(Document::UNKNOWN, $doc->getDocumentType()); + + } + + function testConstruct() { + + $doc = new MockDocument('VLIST'); + $this->assertEquals('VLIST', $doc->name); + + } + + function testCreateComponent() { + + $vcal = new Component\VCalendar([], false); + + $event = $vcal->createComponent('VEVENT'); + + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $vcal->add($event); + + $prop = $vcal->createProperty('X-PROP', '1234256', ['X-PARAM' => '3']); + $this->assertInstanceOf('Sabre\VObject\Property', $prop); + + $event->add($prop); + + unset( + $event->DTSTAMP, + $event->UID + ); + + $out = $vcal->serialize(); + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out); + + } + + function testCreate() { + + $vcal = new Component\VCalendar([], false); + + $event = $vcal->create('VEVENT'); + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + + $prop = $vcal->create('CALSCALE'); + $this->assertInstanceOf('Sabre\VObject\Property\Text', $prop); + + } + + function testGetClassNameForPropertyValue() { + + $vcal = new Component\VCalendar([], false); + $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT')); + $this->assertNull($vcal->getClassNameForPropertyValue('FOO')); + + } + + function testDestroy() { + + $vcal = new Component\VCalendar([], false); + $event = $vcal->createComponent('VEVENT'); + + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $vcal->add($event); + + $prop = $vcal->createProperty('X-PROP', '1234256', ['X-PARAM' => '3']); + + $event->add($prop); + + $this->assertEquals($event, $prop->parent); + + $vcal->destroy(); + + $this->assertNull($prop->parent); + + + } + +} + + +class MockDocument extends Document { + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ElementListTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ElementListTest.php new file mode 100644 index 000000000000..884eac361c9a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ElementListTest.php @@ -0,0 +1,35 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class ElementListTest extends TestCase { + + function testIterate() { + + $cal = new Component\VCalendar(); + $sub = $cal->createComponent('VEVENT'); + + $elems = [ + $sub, + clone $sub, + clone $sub + ]; + + $elemList = new ElementList($elems); + + $count = 0; + foreach ($elemList as $key => $subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component', $subcomponent); + + } + $this->assertEquals(3, $count); + $this->assertEquals(2, $key); + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/EmClientTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/EmClientTest.php new file mode 100644 index 000000000000..c83c8807e6c4 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/EmClientTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\VObject; + +use DateTimeImmutable; +use PHPUnit\Framework\TestCase; + +class EmClientTest extends TestCase { + + function testParseTz() { + + $str = 'BEGIN:VCALENDAR +X-WR-CALNAME:Blackhawks Schedule 2011-12 +X-APPLE-CALENDAR-COLOR:#E51717 +X-WR-TIMEZONE:America/Chicago +CALSCALE:GREGORIAN +PRODID:-//eM Client/4.0.13961.0 +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/Chicago +BEGIN:DAYLIGHT +TZOFFSETFROM:-0600 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +DTSTART:20070311T020000 +TZNAME:CDT +TZOFFSETTO:-0500 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +DTSTART:20071104T020000 +TZNAME:CST +TZOFFSETTO:-0600 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20110624T181236Z +UID:be3bbfff-96e8-4c66-9908-ab791a62231d +DTEND;TZID="America/Chicago":20111008T223000 +TRANSP:OPAQUE +SUMMARY:Stars @ Blackhawks (Home Opener) +DTSTART;TZID="America/Chicago":20111008T193000 +DTSTAMP:20120330T013232Z +SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +LAST-MODIFIED:20120330T013237Z +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR'; + + $vObject = Reader::read($str); + $dt = $vObject->VEVENT->DTSTART->getDateTime(); + $this->assertEquals(new DateTimeImmutable('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php new file mode 100644 index 000000000000..53c73160d90a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php @@ -0,0 +1,71 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class EmptyParameterTest extends TestCase { + + function testRead() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:2.1 +N:Doe;Jon;;; +FN:Jon Doe +EMAIL;X-INTERN:foo@example.org +UID:foo +END:VCARD +VCF; + + $vcard = Reader::read($input); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $this->assertTrue(isset($converted->EMAIL['X-INTERN'])); + + $version = Version::VERSION; + + $expected = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject $version//EN +N:Doe;Jon;;; +FN:Jon Doe +EMAIL;X-INTERN=:foo@example.org +UID:foo +END:VCARD + +VCF; + + $this->assertEquals($expected, str_replace("\r", "", $vcard)); + + } + + function testVCard21Parameter() { + + $vcard = new Component\VCard([], false); + $vcard->VERSION = '2.1'; + $vcard->PHOTO = 'random_stuff'; + $vcard->PHOTO->add(null, 'BASE64'); + $vcard->UID = 'foo-bar'; + + $result = $vcard->serialize(); + $expected = [ + "BEGIN:VCARD", + "VERSION:2.1", + "PHOTO;BASE64:" . base64_encode('random_stuff'), + "UID:foo-bar", + "END:VCARD", + "", + ]; + + $this->assertEquals(implode("\r\n", $expected), $result); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php new file mode 100644 index 000000000000..59ba5dfd300f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +/** + * This test is written for Issue 68: + * + * https://github.com/fruux/sabre-vobject/issues/68 + */ +class EmptyValueIssueTest extends TestCase { + + function testDecodeValue() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +DESCRIPTION:This is a descpription\\nwith a linebreak and a \\; \\, and : +END:VEVENT +END:VCALENDAR +ICS; + + $vobj = Reader::read($input); + + // Before this bug was fixed, getValue() would return nothing. + $this->assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyDataTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyDataTest.php new file mode 100644 index 000000000000..a88094a9600f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyDataTest.php @@ -0,0 +1,320 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class FreeBusyDataTest extends TestCase { + + function testGetData() { + + $fb = new FreeBusyData(100, 200); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 200, + 'type' => 'FREE', + ] + ], + $fb->getData() + ); + + } + + /** + * @depends testGetData + */ + function testAddBeginning() { + + $fb = new FreeBusyData(100, 200); + + // Overwriting the first half + $fb->add(100, 150, 'BUSY'); + + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 150, + 'type' => 'BUSY', + ], + [ + 'start' => 150, + 'end' => 200, + 'type' => 'FREE', + ] + ], + $fb->getData() + ); + + // Overwriting the first half again + $fb->add(100, 150, 'BUSY-TENTATIVE'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 150, + 'type' => 'BUSY-TENTATIVE', + ], + [ + 'start' => 150, + 'end' => 200, + 'type' => 'FREE', + ] + ], + $fb->getData() + ); + + } + + /** + * @depends testAddBeginning + */ + function testAddEnd() { + + $fb = new FreeBusyData(100, 200); + + // Overwriting the first half + $fb->add(150, 200, 'BUSY'); + + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 150, + 'type' => 'FREE', + ], + [ + 'start' => 150, + 'end' => 200, + 'type' => 'BUSY', + ], + ], + $fb->getData() + ); + + + } + + /** + * @depends testAddEnd + */ + function testAddMiddle() { + + $fb = new FreeBusyData(100, 200); + + // Overwriting the first half + $fb->add(150, 160, 'BUSY'); + + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 150, + 'type' => 'FREE', + ], + [ + 'start' => 150, + 'end' => 160, + 'type' => 'BUSY', + ], + [ + 'start' => 160, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + + } + + /** + * @depends testAddMiddle + */ + function testAddMultiple() { + + $fb = new FreeBusyData(100, 200); + + $fb->add(110, 120, 'BUSY'); + $fb->add(130, 140, 'BUSY'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 110, + 'type' => 'FREE', + ], + [ + 'start' => 110, + 'end' => 120, + 'type' => 'BUSY', + ], + [ + 'start' => 120, + 'end' => 130, + 'type' => 'FREE', + ], + [ + 'start' => 130, + 'end' => 140, + 'type' => 'BUSY', + ], + [ + 'start' => 140, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + + } + + /** + * @depends testAddMultiple + */ + function testAddMultipleOverlap() { + + $fb = new FreeBusyData(100, 200); + + $fb->add(110, 120, 'BUSY'); + $fb->add(130, 140, 'BUSY'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 110, + 'type' => 'FREE', + ], + [ + 'start' => 110, + 'end' => 120, + 'type' => 'BUSY', + ], + [ + 'start' => 120, + 'end' => 130, + 'type' => 'FREE', + ], + [ + 'start' => 130, + 'end' => 140, + 'type' => 'BUSY', + ], + [ + 'start' => 140, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + + $fb->add(115, 135, 'BUSY-TENTATIVE'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 110, + 'type' => 'FREE', + ], + [ + 'start' => 110, + 'end' => 115, + 'type' => 'BUSY', + ], + [ + 'start' => 115, + 'end' => 135, + 'type' => 'BUSY-TENTATIVE', + ], + [ + 'start' => 135, + 'end' => 140, + 'type' => 'BUSY', + ], + [ + 'start' => 140, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + } + + /** + * @depends testAddMultipleOverlap + */ + function testAddMultipleOverlapAndMerge() { + + $fb = new FreeBusyData(100, 200); + + $fb->add(110, 120, 'BUSY'); + $fb->add(130, 140, 'BUSY'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 110, + 'type' => 'FREE', + ], + [ + 'start' => 110, + 'end' => 120, + 'type' => 'BUSY', + ], + [ + 'start' => 120, + 'end' => 130, + 'type' => 'FREE', + ], + [ + 'start' => 130, + 'end' => 140, + 'type' => 'BUSY', + ], + [ + 'start' => 140, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + + $fb->add(115, 135, 'BUSY'); + + $this->assertEquals( + [ + [ + 'start' => 100, + 'end' => 110, + 'type' => 'FREE', + ], + [ + 'start' => 110, + 'end' => 140, + 'type' => 'BUSY', + ], + [ + 'start' => 140, + 'end' => 200, + 'type' => 'FREE', + ], + ], + $fb->getData() + ); + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php new file mode 100644 index 000000000000..89a48fda6ab2 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php @@ -0,0 +1,753 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class FreeBusyGeneratorTest extends TestCase { + + use PHPUnitAssertions; + + function testGeneratorBaseObject() { + + $obj = new Component\VCalendar(); + $obj->METHOD = 'PUBLISH'; + + $gen = new FreeBusyGenerator(); + $gen->setObjects([]); + $gen->setBaseObject($obj); + + $result = $gen->getResult(); + + $this->assertEquals('PUBLISH', $result->METHOD->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg() { + + $gen = new FreeBusyGenerator( + new \DateTime('2012-01-01'), + new \DateTime('2012-12-31'), + new \StdClass() + ); + + } + + /** + * This function takes a list of objects (icalendar objects), and turns + * them into a freebusy report. + * + * Then it takes the expected output and compares it to what we actually + * got. + * + * It only generates the freebusy report for the following time-range: + * 2011-01-01 11:00:00 until 2011-01-03 11:11:11 + * + * @param string $expected + * @param array $input + * @param string|null $timeZone + * @param string $vavailability + * @return void + */ + function assertFreeBusyReport($expected, $input, $timeZone = null, $vavailability = null) { + + $gen = new FreeBusyGenerator( + new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')), + new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')), + $input, + $timeZone + ); + + if ($vavailability) { + if (is_string($vavailability)) { + $vavailability = Reader::read($vavailability); + } + $gen->setVAvailability($vavailability); + } + + $output = $gen->getResult(); + + // Removing DTSTAMP because it changes every time. + unset($output->VFREEBUSY->DTSTAMP); + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VFREEBUSY +DTSTART:20110101T110000Z +DTEND:20110103T110000Z +$expected +END:VFREEBUSY +END:VCALENDAR +ICS; + + $this->assertVObjectEqualsVObject($expected, $output); + + } + + function testSimple() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T120000Z/20110101T130000Z", + $blob + ); + + } + + function testSource() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + $h = fopen('php://memory', 'r+'); + fwrite($h, $blob); + rewind($h); + + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T120000Z/20110101T130000Z", + $h + ); + + } + + /** + * Testing TRANSP:OPAQUE + */ + function testOpaque() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar2 +TRANSP:OPAQUE +DTSTART:20110101T130000Z +DTEND:20110101T140000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T130000Z/20110101T140000Z", + $blob + ); + + } + + /** + * Testing TRANSP:TRANSPARENT + */ + function testTransparent() { + + // transparent, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar3 +TRANSP:TRANSPARENT +DTSTART:20110101T140000Z +DTEND:20110101T150000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "", + $blob + ); + + } + + /** + * Testing STATUS:CANCELLED + */ + function testCancelled() { + + // transparent, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar4 +STATUS:CANCELLED +DTSTART:20110101T160000Z +DTEND:20110101T170000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "", + $blob + ); + + } + + /** + * Testing STATUS:TENTATIVE + */ + function testTentative() { + + // tentative, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar5 +STATUS:TENTATIVE +DTSTART:20110101T180000Z +DTEND:20110101T190000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY;FBTYPE=BUSY-TENTATIVE:20110101T180000Z/20110101T190000Z', + $blob + ); + + } + + /** + * Testing an event that falls outside of the report time-range. + */ + function testOutsideTimeRange() { + + // outside of time-range, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar6 +DTSTART:20110101T090000Z +DTEND:20110101T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + '', + $blob + ); + + } + + /** + * Testing an event that falls outside of the report time-range. + */ + function testOutsideTimeRange2() { + + // outside of time-range, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar7 +DTSTART:20110104T090000Z +DTEND:20110104T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + '', + $blob + ); + + } + + /** + * Testing an event that uses DURATION + */ + function testDuration() { + + // using duration, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar8 +DTSTART:20110101T190000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY:20110101T190000Z/20110101T200000Z', + $blob + ); + + } + + /** + * Testing an all-day event + */ + function testAllDay() { + + // Day-long event, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar9 +DTSTART;VALUE=DATE:20110102 +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY:20110102T000000Z/20110103T000000Z', + $blob + ); + + } + + /** + * Testing an event that has no end or duration. + */ + function testNoDuration() { + + // No duration, does not show up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar10 +DTSTART:20110101T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + '', + $blob + ); + + } + + /** + * Testing feeding the freebusy generator an object instead of a string. + */ + function testObject() { + + // encoded as object, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar11 +DTSTART:20110101T210000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY:20110101T210000Z/20110101T220000Z', + Reader::read($blob) + ); + + + } + + /** + * Testing feeding VFREEBUSY objects instead of VEVENT + */ + function testVFreeBusy() { + + // Freebusy. Some parts show up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VFREEBUSY +FREEBUSY:20110103T010000Z/20110103T020000Z +FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z +FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z +FREEBUSY:20120101T000000Z/20120101T010000Z +FREEBUSY:20110103T050000Z/PT1H +END:VFREEBUSY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110103T010000Z/20110103T020000Z\n" . + 'FREEBUSY:20110103T030000Z/20110103T060000Z', + $blob + ); + + } + + function testYearlyRecurrence() { + + // Yearly recurrence rule, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar13 +DTSTART:20100101T220000Z +DTEND:20100101T230000Z +RRULE:FREQ=YEARLY +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY:20110101T220000Z/20110101T230000Z', + $blob + ); + + } + + function testYearlyRecurrenceDuration() { + + // Yearly recurrence rule + duration, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar14 +DTSTART:20100101T230000Z +DURATION:PT1H +RRULE:FREQ=YEARLY +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + 'FREEBUSY:20110101T230000Z/20110102T000000Z', + $blob + ); + + } + + function testFloatingTime() { + + // Floating time, no timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000 +DTEND:20110101T130000 +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T120000Z/20110101T130000Z", + $blob + ); + + } + + function testFloatingTimeReferenceTimeZone() { + + // Floating time + reference timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000 +DTEND:20110101T130000 +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T170000Z/20110101T180000Z", + $blob, + new \DateTimeZone('America/Toronto') + ); + + } + + function testAllDay2() { + + // All-day event, slightly outside of the VFREEBUSY range. + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART;VALUE=DATE:20110101 +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T110000Z/20110102T000000Z", + $blob + ); + + } + + function testAllDayReferenceTimeZone() { + + // All-day event + reference timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART;VALUE=DATE:20110101 +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T110000Z/20110102T050000Z", + $blob, + new \DateTimeZone('America/Toronto') + ); + + } + + function testNoValidInstances() { + + // Recurrence rule with no valid instances + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T100000Z +DTEND:20110103T120000Z +RRULE:FREQ=WEEKLY;COUNT=1 +EXDATE:20110101T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "", + $blob + ); + + } + + /** + * This VAVAILABILITY object overlaps with the time-range, but we're just + * busy the entire time. + */ + function testVAvailabilitySimple() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:lalala +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vavail = <<<ICS +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20110101T000000Z +DTEND:20120101T000000Z +BEGIN:AVAILABLE +DTSTART:20110101T000000Z +DTEND:20110101T010000Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20110101T110000Z/20110101T120000Z\n" . + "FREEBUSY:20110101T120000Z/20110101T130000Z\n" . + "FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20110101T130000Z/20110103T110000Z", + $blob, + null, + $vavail + ); + + } + + /** + * This VAVAILABILITY object does not overlap at all with the freebusy + * report, so it should be ignored. + */ + function testVAvailabilityIrrelevant() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:lalala +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vavail = <<<ICS +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20150101T000000Z +DTEND:20160101T000000Z +BEGIN:AVAILABLE +DTSTART:20150101T000000Z +DTEND:20150101T010000Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T120000Z/20110101T130000Z", + $blob, + null, + $vavail + ); + + } + + /** + * This VAVAILABILITY object has a 9am-5pm AVAILABLE object for office + * hours. + */ + function testVAvailabilityOfficeHours() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:lalala +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vavail = <<<ICS +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20100101T000000Z +DTEND:20120101T000000Z +BUSYTYPE:BUSY-TENTATIVE +BEGIN:AVAILABLE +DTSTART:20101213T090000Z +DTEND:20101213T170000Z +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY;FBTYPE=BUSY-TENTATIVE:20110101T110000Z/20110101T120000Z\n" . + "FREEBUSY:20110101T120000Z/20110101T130000Z\n" . + "FREEBUSY;FBTYPE=BUSY-TENTATIVE:20110101T130000Z/20110103T090000Z\n", + $blob, + null, + $vavail + ); + + } + + /** + * This test has the same office hours, but has a vacation blocked off for + * the relevant time, using a higher priority. (lower number). + */ + function testVAvailabilityOfficeHoursVacation() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:lalala +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vavail = <<<ICS +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20100101T000000Z +DTEND:20120101T000000Z +BUSYTYPE:BUSY-TENTATIVE +PRIORITY:2 +BEGIN:AVAILABLE +DTSTART:20101213T090000Z +DTEND:20101213T170000Z +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +END:AVAILABLE +END:VAVAILABILITY +BEGIN:VAVAILABILITY +PRIORITY:1 +DTSTART:20101214T000000Z +DTEND:20110107T000000Z +BUSYTYPE:BUSY +END:VAVAILABILITY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY:20110101T110000Z/20110103T110000Z", + $blob, + null, + $vavail + ); + + } + + /** + * This test has the same input as the last, except somebody mixed up the + * PRIORITY values. + * + * The end-result is that the vacation VAVAILABILITY is completely ignored. + */ + function testVAvailabilityOfficeHoursVacation2() { + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:lalala +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vavail = <<<ICS +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +DTSTART:20100101T000000Z +DTEND:20120101T000000Z +BUSYTYPE:BUSY-TENTATIVE +PRIORITY:1 +BEGIN:AVAILABLE +DTSTART:20101213T090000Z +DTEND:20101213T170000Z +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +END:AVAILABLE +END:VAVAILABILITY +BEGIN:VAVAILABILITY +PRIORITY:2 +DTSTART:20101214T000000Z +DTEND:20110107T000000Z +BUSYTYPE:BUSY +END:VAVAILABILITY +END:VCALENDAR +ICS; + + $this->assertFreeBusyReport( + "FREEBUSY;FBTYPE=BUSY-TENTATIVE:20110101T110000Z/20110101T120000Z\n" . + "FREEBUSY:20110101T120000Z/20110101T130000Z\n" . + "FREEBUSY;FBTYPE=BUSY-TENTATIVE:20110101T130000Z/20110103T090000Z\n", + $blob, + null, + $vavail + ); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php new file mode 100644 index 000000000000..ca2d2273903a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php @@ -0,0 +1,33 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +/** + * Google produces vcards with a weird escaping of urls. + * + * VObject will provide a workaround for this, so end-user still get expected + * values. + */ +class GoogleColonEscapingTest extends TestCase { + + function testDecode() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +FN:Evert Pot +N:Pot;Evert;;; +EMAIL;TYPE=INTERNET;TYPE=WORK:evert@fruux.com +BDAY:1985-04-07 +item7.URL:http\://www.rooftopsolutions.nl/ +END:VCARD +VCF; + + $vobj = Reader::read($vcard); + $this->assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php new file mode 100644 index 000000000000..3665fb6eeb86 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\VObject\ICalendar; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +class AttachParseTest extends TestCase { + + /** + * See issue #128 for more info. + */ + function testParseAttach() { + + $vcal = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/reports/r-960812.ps +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($vcal); + $prop = $vcal->VEVENT->ATTACH; + + $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop); + $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue()); + + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php new file mode 100644 index 000000000000..9519ed368282 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -0,0 +1,1146 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerAttendeeReplyTest extends BrokerTester { + + function testAccepted() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +SUMMARY:B-day party +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringReply() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RRULE;FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140726T120000Z +RECURRENCE-ID:20140726T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +RECURRENCE-ID:20140724T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +DTSTART:20140728T120000Z +RECURRENCE-ID:20140728T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140729T120000Z +RECURRENCE-ID:20140729T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140725T120000Z +RECURRENCE-ID:20140725T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140726T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140726T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140724T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140728T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140728T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140729T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140729T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140725T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140725T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringAllDay() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +RRULE;FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140726 +RECURRENCE-ID;VALUE=DATE:20140726 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +RECURRENCE-ID;VALUE=DATE:20140724 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140728 +RECURRENCE-ID;VALUE=DATE:20140728 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140729 +RECURRENCE-ID;VALUE=DATE:20140729 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140725 +RECURRENCE-ID;VALUE=DATE:20140725 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140726 +RECURRENCE-ID;VALUE=DATE:20140726 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140724 +RECURRENCE-ID;VALUE=DATE:20140724 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140728 +RECURRENCE-ID;VALUE=DATE:20140728 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140729 +RECURRENCE-ID;VALUE=DATE:20140729 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140725 +RECURRENCE-ID;VALUE=DATE:20140725 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $expected = []; + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoChangeForceSend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;SCHEDULE-FORCE-SEND=REPLY;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ] + + ]; + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoRelevantAttendee() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $expected = []; + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, an event exists in an attendees calendar. The event + * is recurring, and the attendee deletes 1 instance of the event. + * This instance shows up in EXDATE + * + * This should automatically generate a DECLINED message for that + * specific instance. + */ + function testCreateReplyByException() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE:20140818T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140818T200000Z +RECURRENCE-ID:20140818T200000Z +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * This test is identical to the last, but now we're working with + * timezones. + * + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionTz() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140811T200000 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140811T200000 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE;TZID=America/Toronto:20140818T200000 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140818T200000 +RECURRENCE-ID;TZID=America/Toronto:20140818T200000 +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionAllDay() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Weekly meeting +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140811 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Weekly meeting +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140811 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE;VALUE=DATE:20140818 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140818 +SUMMARY:Weekly meeting +RECURRENCE-ID;VALUE=DATE:20140818 +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testDeclined() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testDeclinedCancelledEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = []; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, a new exception is created by an attendee as well. + * + * Except in this case, there was already an overridden event, and the + * overridden event was marked as cancelled by the attendee. + * + * For any other attendence status, the new status would have been + * declined, but for this, no message should we sent. + */ + function testDontCreateReplyWhenEventWasDeclined() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +RECURRENCE-ID:20140818T200000Z +UID:foobar +SEQUENCE:1 +DTSTART:20140818T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE:20140818T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $expected = []; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testScheduleAgentOnOrganizer() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;SCHEDULE-AGENT=CLIENT;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = []; + $this->parse($oldMessage, $newMessage, $expected); + + } + + function testAcceptedAllDay() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140716 +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140716 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART;VALUE=DATE:20140716 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * This function tests an attendee updating their status to an event where + * they don't have the master event of. + * + * This is possible in cases an organizer created a recurring event, and + * invited an attendee for one instance of the event. + */ + function testReplyNoMasterEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +RECURRENCE-ID:20140724T120000Z +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +RECURRENCE-ID:20140724T120000Z +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140724T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * A party crasher is an attendee that accepted an event, but was not in + * any original invite. + * + * @depends testAccepted + */ + function testPartyCrasher() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140716T120000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140717T120000Z +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140717T120000Z +RRULE:FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140716T120000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140717T120000Z +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140717T120000Z +RRULE:FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140717T120000Z +SUMMARY:B-day party +RECURRENCE-ID:20140717T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR + +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php new file mode 100644 index 000000000000..935c451fe76e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php @@ -0,0 +1,344 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerDeleteEventTest extends BrokerTester { + + function testOrganizerDeleteWithDtend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ], + + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testOrganizerDeleteWithDuration() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DURATION:PT1H +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ], + + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DURATION:PT1H +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testAttendeeDeleteWithDtend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeReplyWithDuration() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +DURATION:PT1H +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeDeleteCancelledEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $expected = []; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testNoCalendar() { + + $this->parse(null, null, [], 'mailto:one@example.org'); + + } + + function testVTodo() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VTODO +UID:foobar +SEQUENCE:1 +END:VTODO +END:VCALENDAR +ICS; + $this->parse($oldMessage, null, [], 'mailto:one@example.org'); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php new file mode 100644 index 000000000000..3dd10cf3594e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php @@ -0,0 +1,548 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerNewEventTest extends BrokerTester { + + function testNoAttendee() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->parse(null, $message, []); + + } + + function testVTODO() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VTODO +UID:foobar +END:VTODO +END:VCALENDAR +ICS; + + $result = $this->parse(null, $message, []); + + } + + function testSimpleInvite() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expectedMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White;PARTSTAT=NEEDS-ACTION:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:white@example.org', + 'recipientName' => 'White', + 'message' => $expectedMessage, + ], + ]; + + $this->parse(null, $message, $expected, 'mailto:strunk@example.org'); + + } + + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventUIDMisMatch() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventOrganizerMisMatch() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +ORGANIZER:mailto:foo@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + + function testRecurrenceInvite() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z,20140718T120000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse(null, $message, $expected, 'mailto:strunk@example.org'); + + } + + function testRecurrenceInvite2() { + + // This method tests a nearly identical path, but in this case the + // master event does not have an EXDATE. + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +EXDATE:20140718T120000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse(null, $message, $expected, 'mailto:strunk@example.org'); + + } + + function testRecurrenceInvite3() { + + // This method tests a complex rrule + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;BYDAY=SA,SU +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;BYDAY=SA,SU +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse(null, $message, $expected, 'mailto:strunk@example.org'); + + } + + function testScheduleAgentClient() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White;SCHEDULE-AGENT=CLIENT:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + + /** + * @expectedException Sabre\VObject\ITip\ITipException + */ + function testMultipleUID() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar2 +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + + /** + * @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException + */ + function testChangingOrganizers() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:ew@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + function testNoOrganizerHasAttendee() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse(null, $message, [], 'mailto:strunk@example.org'); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php new file mode 100644 index 000000000000..691574a89919 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php @@ -0,0 +1,164 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerProcessMessageTest extends BrokerTester { + + function testRequestNew() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, null, $expected); + + } + + function testRequestUpdate() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testCancel() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:CANCEL +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +STATUS:CANCELLED +SEQUENCE:2 +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testCancelNoExistingEvent() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:CANCEL +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testUnsupportedComponent() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VTODO +SEQUENCE:2 +UID:foobar +END:VTODO +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testUnsupportedMethod() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php new file mode 100644 index 000000000000..533fdce15128 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -0,0 +1,496 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerProcessReplyTest extends BrokerTester { + + function testReplyNoOriginal() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyAccept() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyRequestStatus() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +REQUEST-STATUS:2.3;foo-bar! +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.3:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + + function testReplyPartyCrasher() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewException() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140725T000000Z +DTEND:20140725T010000Z +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140725T000000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionTz() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID;TZID=America/Toronto:20140725T000000 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART;TZID=America/Toronto:20140724T000000 +DTEND;TZID=America/Toronto:20140724T010000 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART;TZID=America/Toronto:20140724T000000 +DTEND;TZID=America/Toronto:20140724T010000 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART;TZID=America/Toronto:20140725T000000 +DTEND;TZID=America/Toronto:20140725T010000 +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID;TZID=America/Toronto:20140725T000000 +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyPartyCrashCreateExcepton() { + + // IN this test there's a recurring event that has an exception. The + // exception is missing the attendee. + // + // The attendee party crashes the instance, so it should show up in the + // resulting object. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140725T000000Z +DTEND:20140725T010000Z +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140725T000000Z +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionNoMasterEvent() { + + /** + * This iTip message would normally create a new exception, but the + * server is not able to create this new instance, because there's no + * master event to clone from. + * + * This test checks if the message is ignored. + */ + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +RECURRENCE-ID:20140724T000000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = null; + $result = $this->process($itip, $old, $expected); + + } + + /** + * @depends testReplyAccept + */ + function testReplyAcceptUpdateRSVP() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;RSVP=TRUE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionFirstOccurence() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140724T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140724T000000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerSignificantChangesTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerSignificantChangesTest.php new file mode 100644 index 000000000000..27b0ebfd22e8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerSignificantChangesTest.php @@ -0,0 +1,110 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerSignificantChangesTest extends BrokerTester { + + /** + * Check significant changes detection (no change) + */ + function testSignificantChangesNoChange() { + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=America/Toronto:20140815T110000 +SEQUENCE:2 +SUMMARY:Evo makes a Meeting +LOCATION:fruux HQ +CLASS:PUBLIC +RRULE:FREQ=WEEKLY +ORGANIZER:MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $new = $old; + $expected = [['significantChange' => false, ]]; + + $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); + } + + /** + * Check significant changes detection (no change) + */ + function testSignificantChangesRRuleNoChange() { + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=America/Toronto:20140815T110000 +SEQUENCE:2 +SUMMARY:Evo makes a Meeting +LOCATION:fruux HQ +CLASS:PUBLIC +RRULE:FREQ=WEEKLY +ORGANIZER:MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $new = str_replace('FREQ=WEEKLY', 'FREQ=WEEKLY;INTERVAL=1', $old); + $expected = [['significantChange' => false, ]]; + + $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); + } + + + /** + * Check significant changes detection (no change) + */ + function testSignificantChangesRRuleOrderNoChange() { + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=America/Toronto:20140815T110000 +SEQUENCE:2 +SUMMARY:Evo makes a Meeting +LOCATION:fruux HQ +CLASS:PUBLIC +RRULE:FREQ=WEEKLY;BYDAY=MO +ORGANIZER:MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $new = str_replace('FREQ=WEEKLY;BYDAY=MO', 'BYDAY=MO;FREQ=WEEKLY', $old); + $expected = [['significantChange' => false, ]]; + + $this->parse($old, $new, $expected, 'mailto:martin@fruux.com'); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php new file mode 100644 index 000000000000..4614264acd42 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php @@ -0,0 +1,97 @@ +<?php + +namespace Sabre\VObject\ITip; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +/** + * Utilities for testing the broker + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class BrokerTester extends TestCase { + + use \Sabre\VObject\PHPUnitAssertions; + + function parse($oldMessage, $newMessage, $expected = [], $currentUser = 'mailto:one@example.org') { + + $broker = new Broker(); + $result = $broker->parseEvent($newMessage, $currentUser, $oldMessage); + + $this->assertEquals(count($expected), count($result)); + + foreach ($expected as $index => $ex) { + + $message = $result[$index]; + + foreach ($ex as $key => $val) { + + if ($key === 'message') { + $this->assertVObjectEqualsVObject( + $val, + $message->message->serialize() + ); + } else { + $this->assertEquals($val, $message->$key); + } + + } + + } + + } + + function process($input, $existingObject = null, $expected = false) { + + $version = \Sabre\VObject\Version::VERSION; + + $vcal = Reader::read($input); + + foreach ($vcal->getComponents() as $mainComponent) { + break; + } + + $message = new Message(); + $message->message = $vcal; + $message->method = isset($vcal->METHOD) ? $vcal->METHOD->getValue() : null; + $message->component = $mainComponent->name; + $message->uid = $mainComponent->UID->getValue(); + $message->sequence = isset($vcal->VEVENT[0]) ? (string)$vcal->VEVENT[0]->SEQUENCE : null; + + if ($message->method === 'REPLY') { + + $message->sender = $mainComponent->ATTENDEE->getValue(); + $message->senderName = isset($mainComponent->ATTENDEE['CN']) ? $mainComponent->ATTENDEE['CN']->getValue() : null; + $message->recipient = $mainComponent->ORGANIZER->getValue(); + $message->recipientName = isset($mainComponent->ORGANIZER['CN']) ? $mainComponent->ORGANIZER['CN'] : null; + + } + + $broker = new Broker(); + + if (is_string($existingObject)) { + $existingObject = str_replace( + '%foo%', + "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN", + $existingObject + ); + $existingObject = Reader::read($existingObject); + } + + $result = $broker->processMessage($message, $existingObject); + + if (is_null($expected)) { + $this->assertTrue(!$result); + return; + } + + $this->assertVObjectEqualsVObject( + $expected, + $result + ); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php new file mode 100644 index 000000000000..8b3ecba541e0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php @@ -0,0 +1,78 @@ +<?php + +namespace Sabre\VObject\ITip; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +class BrokerTimezoneInParseEventInfoWithoutMasterTest extends TestCase { + + function testTimezoneInParseEventInfoWithoutMaster() + { + $calendar = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.9.5//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Minsk +BEGIN:DAYLIGHT +TZOFFSETFROM:+0200 +RRULE:FREQ=YEARLY;UNTIL=20100328T000000Z;BYMONTH=3;BYDAY=-1SU +DTSTART:19930328T020000 +TZNAME:GMT+3 +TZOFFSETTO:+0300 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +DTSTART:20110327T020000 +TZNAME:GMT+3 +TZOFFSETTO:+0300 +RDATE:20110327T020000 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20160331T163031Z +UID:B9301437-417C-4136-8DB3-8D1555863791 +DTEND;TZID=Europe/Minsk:20160405T100000 +TRANSP:OPAQUE +ATTENDEE;CN=User Invitee;CUTYPE=INDIVIDUAL;EMAIL=invitee@test.com;PARTSTAT= + ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:invitee@test.com +ATTENDEE;CN=User Organizer;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:organ + izer@test.com +SUMMARY:Event title +DTSTART;TZID=Europe/Minsk:20160405T090000 +DTSTAMP:20160331T164108Z +ORGANIZER;CN=User Organizer:mailto:organizer@test.com +SEQUENCE:6 +RECURRENCE-ID;TZID=Europe/Minsk:20160405T090000 +END:VEVENT +BEGIN:VEVENT +CREATED:20160331T163031Z +UID:B9301437-417C-4136-8DB3-8D1555863791 +DTEND;TZID=Europe/Minsk:20160406T100000 +TRANSP:OPAQUE +ATTENDEE;CN=User Invitee;CUTYPE=INDIVIDUAL;EMAIL=invitee@test.com;PARTSTAT= + ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:invitee@test.com +ATTENDEE;CN=User Organizer;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:organ + izer@test.com +SUMMARY:Event title +DTSTART;TZID=Europe/Minsk:20160406T090000 +DTSTAMP:20160331T165845Z +ORGANIZER;CN=User Organizer:mailto:organizer@test.com +SEQUENCE:6 +RECURRENCE-ID;TZID=Europe/Minsk:20160406T090000 +END:VEVENT +END:VCALENDAR +ICS; + + $calendar = Reader::read($calendar); + $broker = new Broker(); + + $reflectionMethod = new \ReflectionMethod($broker, 'parseEventInfo'); + $reflectionMethod->setAccessible(true); + $data = $reflectionMethod->invoke($broker, $calendar); + $this->assertInstanceOf('DateTimeZone', $data['timezone']); + $this->assertEquals($data['timezone']->getName(), 'Europe/Minsk'); + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php new file mode 100644 index 000000000000..bc109009e70c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -0,0 +1,846 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerUpdateEventTest extends BrokerTester { + + function testInviteChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromNonSchedulingToSchedulingObject() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromSchedulingToNonSchedulingObject() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testNoAttendees() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = []; + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testRemoveInstance() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +EXDATE;TZID=America/Toronto:20140724T120000 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +EXDATE;TZID=America/Toronto:20140724T120000 +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + /** + * This test is identical to the first test, except this time we change the + * DURATION property. + * + * This should ensure that the message is significant for every attendee, + */ + function testInviteChangeSignificantChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DURATION:PT1H +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChangeForceSend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;SCHEDULE-FORCE-SEND=REQUEST;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteRemoveAttendees() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + [ + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +DTSTAMP:**ANY** +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeExdateOrder() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foobar +SEQUENCE:0 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT;SCHEDULE-STATUS="1.2;Message delivered locally":mailto + :one@example.org +SUMMARY:foo +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20141225T160000Z,20150101T160000Z +EXDATE:20150108T160000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT;SCHEDULE-STATUS=1.2:mailto:one@example.org +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20150101T160000Z +EXDATE:20150108T160000Z,20141225T160000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = [ + [ + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT:mailto:one@example.org +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20150101T160000Z +EXDATE:20150108T160000Z,20141225T160000Z +END:VEVENT +END:VCALENDAR +ICS + + ], + ]; + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php new file mode 100644 index 000000000000..3afe560d5086 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php @@ -0,0 +1,2653 @@ +<?php + +namespace Sabre\VObject\ITip; + +class EvolutionTest extends BrokerTester { + + /** + * Evolution does things as usual a little bit differently. + * + * We're adding a seprate test just for it. + */ + function testNewEvolutionEvent() { + + $ics = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 15T110000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140815 + T113000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Evo makes a Meeting (fruux HQ) (fruux HQ) +LOCATION:fruux HQ +CLASS:PUBLIC +ORGANIZER;SENT-BY="MAILTO:martin+johnny@fruux.com":MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE + ;SENT-BY="MAILTO:martin+johnny@fruux.com";LANGUAGE=en:MAILTO:martin@fruux. + com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expectedICS = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 15T110000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140815 + T113000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Evo makes a Meeting (fruux HQ) (fruux HQ) +LOCATION:fruux HQ +CLASS:PUBLIC +ORGANIZER;SENT-BY="MAILTO:martin+johnny@fruux.com":MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE + ;SENT-BY="MAILTO:martin+johnny@fruux.com";LANGUAGE=en:MAILTO:martin@fruux. + com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $expected = [ + [ + 'uid' => '20140813T153116Z-12176-1000-1065-6@johnny-lubuntu', + 'method' => 'REQUEST', + 'sender' => 'mailto:martin@fruux.com', + 'senderName' => null, + 'recipient' => 'mailto:dominik@fruux.com', + 'recipientName' => null, + 'message' => $expectedICS, + ] + ]; + $this->parse(null, $ics, $expected, 'mailto:martin@fruux.com'); + + } + + /** + * This is an event originally from evolution, then parsed by sabredav and + * again mangled by iCal. This triggered a few bugs related to email + * address scheme casing. + */ + function testAttendeeModify() { + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject 3.3.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T212317Z-6646-1000-1221-23@evert-ubuntu +DTSTAMP:20140813T212221Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 13T180000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140813 + T200000 +TRANSP:OPAQUE +SEQUENCE:4 +SUMMARY:Testing evolution +LOCATION:Online +CLASS:PUBLIC +ORGANIZER:MAILTO:o@example.org +CREATED:20140813T212510Z +LAST-MODIFIED:20140813T212541Z +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE;LANGUAGE=en:MAILTO:o@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a1@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a2@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a3@example.org +STATUS:CANCELLED +END:VEVENT +END:VCALENDAR +ICS; + + $new = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:America/Toronto +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +DTSTART:20070311T020000 +TZNAME:EDT +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +DTSTART:20071104T020000 +TZNAME:EST +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/Toronto:20140813T200000 +ORGANIZER:MAILTO:o@example.org +UID:20140813T212317Z-6646-1000-1221-23@evert-ubuntu +DTSTAMP:20140813T212221Z +LOCATION:Online +STATUS:CANCELLED +SEQUENCE:4 +CLASS:PUBLIC +SUMMARY:Testing evolution +LAST-MODIFIED:20140813T212541Z +DTSTART;TZID=America/Toronto:20140813T180000 +CREATED:20140813T212510Z +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a2@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:o@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a1@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a3@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse($old, $new, [], 'mailto:a1@example.org'); + + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php new file mode 100644 index 000000000000..65735a1454e1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php @@ -0,0 +1,34 @@ +<?php + +namespace Sabre\VObject\ITip; + +use PHPUnit\Framework\TestCase; + +class MessageTest extends TestCase { + + function testNoScheduleStatus() { + + $message = new Message(); + $this->assertFalse($message->getScheduleStatus()); + + } + + function testScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '1.2;Delivered'; + + $this->assertEquals('1.2', $message->getScheduleStatus()); + + } + + function testUnexpectedScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '9.9.9'; + + $this->assertEquals('9.9.9', $message->getScheduleStatus()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue153Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue153Test.php new file mode 100644 index 000000000000..dec178566c06 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue153Test.php @@ -0,0 +1,16 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class Issue153Test extends TestCase { + + function testRead() { + + $obj = Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf')); + $this->assertEquals('Test Benutzer', (string)$obj->FN); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue259Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue259Test.php new file mode 100644 index 000000000000..62ffa52d4d54 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue259Test.php @@ -0,0 +1,23 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class Issue259Test extends TestCase { + + function testParsingJcalWithUntil() { + $jcalWithUntil = '["vcalendar",[],[["vevent",[["uid",{},"text","dd1f7d29"],["organizer",{"cn":"robert"},"cal-address","mailto:robert@robert.com"],["dtstart",{"tzid":"Europe/Berlin"},"date-time","2015-10-21T12:00:00"],["dtend",{"tzid":"Europe/Berlin"},"date-time","2015-10-21T13:00:00"],["transp",{},"text","OPAQUE"],["rrule",{},"recur",{"freq":"MONTHLY","until":"2016-01-01T22:00:00Z"}]],[]]]]'; + $parser = new Parser\Json(); + $parser->setInput($jcalWithUntil); + + $vcalendar = $parser->parse(); + $eventAsArray = $vcalendar->select('VEVENT'); + $event = reset($eventAsArray); + $rruleAsArray = $event->select('RRULE'); + $rrule = reset($rruleAsArray); + $this->assertNotNull($rrule); + $this->assertEquals($rrule->getValue(), 'FREQ=MONTHLY;UNTIL=20160101T220000Z'); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php new file mode 100644 index 000000000000..f001fe6832a7 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class Issue36WorkAroundTest extends TestCase { + + function testWorkaround() { + + // See https://github.com/fruux/sabre-vobject/issues/36 + $event = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Titel +SEQUENCE:1 +TRANSP:TRANSPARENT +RRULE:FREQ=YEARLY +LAST-MODIFIED:20130323T225737Z +DTSTAMP:20130323T225737Z +UID:1833bd44-188b-405c-9f85-1a12105318aa +CATEGORIES:Jubiläum +X-MOZ-GENERATION:3 +RECURRENCE-ID;RANGE=THISANDFUTURE;VALUE=DATE:20131013 +DTSTART;VALUE=DATE:20131013 +CREATED:20100721T121914Z +DURATION:P1D +END:VEVENT +END:VCALENDAR +ICS; + + $obj = Reader::read($event); + + // If this does not throw an exception, it's all good. + $it = new Recur\EventIterator($obj, '1833bd44-188b-405c-9f85-1a12105318aa'); + $this->assertInstanceOf('Sabre\\VObject\\Recur\\EventIterator', $it); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue40Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue40Test.php new file mode 100644 index 000000000000..96b1ec7ed447 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue40Test.php @@ -0,0 +1,34 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +/** + * This test is created to handle the issues brought forward by issue 40. + * + * https://github.com/fruux/sabre-vobject/issues/40 + */ +class Issue40Test extends TestCase { + + function testEncode() { + + $card = new Component\VCard(); + $card->add('N', ['van der Harten', ['Rene', 'J.'], "", 'Sir', 'R.D.O.N.'], ['SORT-AS' => ['Harten', 'Rene']]); + + unset($card->UID); + + $expected = implode("\r\n", [ + "BEGIN:VCARD", + "VERSION:4.0", + "PRODID:-//Sabre//Sabre VObject " . Version::VERSION . '//EN', + "N;SORT-AS=Harten,Rene:van der Harten;Rene,J.;;Sir;R.D.O.N.", + "END:VCARD", + "" + ]); + + $this->assertEquals($expected, $card->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue64Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue64Test.php new file mode 100644 index 000000000000..024b0ac05a6a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue64Test.php @@ -0,0 +1,21 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class Issue64Test extends TestCase { + + function testRead() { + + $vcard = Reader::read(file_get_contents(dirname(__FILE__) . '/issue64.vcf')); + $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Issue96Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue96Test.php new file mode 100644 index 000000000000..60fbea5119d6 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Issue96Test.php @@ -0,0 +1,26 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class Issue96Test extends TestCase { + + function testRead() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:2.1 +SOURCE:Yahoo Contacts (http://contacts.yahoo.com) +URL;CHARSET=utf-8;ENCODING=QUOTED-PRINTABLE:= +http://www.example.org +END:VCARD +VCF; + + $vcard = Reader::read($input, Reader::OPTION_FORGIVING); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertEquals("http://www.example.org", $vcard->URL->getValue()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/IssueUndefinedIndexTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/IssueUndefinedIndexTest.php new file mode 100644 index 000000000000..6a6a61c87d67 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/IssueUndefinedIndexTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class IssueUndefinedIndexTest extends TestCase { + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testRead() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +N:Holmes;Sherlock;;; +FN:Sherlock Holmes +ORG:Acme Inc; +ADR;type=WORK;type=pref:;;, +\\n221B,Baker Street;London;;12345;United Kingdom +UID:foo +END:VCARD +VCF; + + $vcard = Reader::read($input, Reader::OPTION_FORGIVING); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/JCalTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/JCalTest.php new file mode 100644 index 000000000000..1db2dc553591 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/JCalTest.php @@ -0,0 +1,151 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class JCalTest extends TestCase { + + function testToJCal() { + + $cal = new Component\VCalendar(); + + $event = $cal->add('VEVENT', [ + "UID" => "foo", + "DTSTART" => new \DateTime("2013-05-26 18:10:00Z"), + "DURATION" => "P1D", + "CATEGORIES" => ['home', 'testing'], + "CREATED" => new \DateTime("2013-05-26 18:10:00Z"), + + "ATTENDEE" => "mailto:armin@example.org", + "GEO" => [51.96668, 7.61876], + "SEQUENCE" => 5, + "FREEBUSY" => ["20130526T210213Z/PT1H", "20130626T120000Z/20130626T130000Z"], + "URL" => "http://example.org/", + "TZOFFSETFROM" => "+0500", + "RRULE" => ['FREQ' => 'WEEKLY', 'BYDAY' => ['MO', 'TU']], + ], false); + + // Modifying DTSTART to be a date-only. + $event->dtstart['VALUE'] = 'DATE'; + $event->add("X-BOOL", true, ['VALUE' => 'BOOLEAN']); + $event->add("X-TIME", "08:00:00", ['VALUE' => 'TIME']); + $event->add("ATTACH", "attachment", ['VALUE' => 'BINARY']); + $event->add("ATTENDEE", "mailto:dominik@example.org", ["CN" => "Dominik", "PARTSTAT" => "DECLINED"]); + + $event->add('REQUEST-STATUS', ["2.0", "Success"]); + $event->add('REQUEST-STATUS', ["3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"]); + + $event->add('DTEND', '20150108T133000'); + + $expected = [ + "vcalendar", + [ + [ + "version", + new \StdClass(), + "text", + "2.0" + ], + [ + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ], + [ + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ], + ], + [ + ["vevent", + [ + [ + "uid", new \StdClass(), "text", "foo", + ], + [ + "dtstart", new \StdClass(), "date", "2013-05-26", + ], + [ + "duration", new \StdClass(), "duration", "P1D", + ], + [ + "categories", new \StdClass(), "text", "home", "testing", + ], + [ + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ], + [ + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ], + [ + "attendee", + (object)[ + "cn" => "Dominik", + "partstat" => "DECLINED", + ], + "cal-address", + "mailto:dominik@example.org" + ], + [ + "geo", new \StdClass(), "float", [51.96668, 7.61876], + ], + [ + "sequence", new \StdClass(), "integer", 5 + ], + [ + "freebusy", new \StdClass(), "period", ["2013-05-26T21:02:13", "PT1H"], ["2013-06-26T12:00:00", "2013-06-26T13:00:00"], + ], + [ + "url", new \StdClass(), "uri", "http://example.org/", + ], + [ + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ], + [ + "rrule", new \StdClass(), "recur", [ + 'freq' => 'WEEKLY', + 'byday' => ['MO', 'TU'], + ], + ], + [ + "x-bool", new \StdClass(), "boolean", true + ], + [ + "x-time", new \StdClass(), "time", "08:00:00", + ], + [ + "attach", new \StdClass(), "binary", base64_encode('attachment') + ], + [ + "request-status", + new \StdClass(), + "text", + ["2.0", "Success"], + ], + [ + "request-status", + new \StdClass(), + "text", + ["3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"], + ], + [ + 'dtend', + new \StdClass(), + "date-time", + "2015-01-08T13:30:00", + ], + ], + [], + ] + ], + ]; + + $this->assertEquals($expected, $cal->jsonSerialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/JCardTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/JCardTest.php new file mode 100644 index 000000000000..8aa65a992998 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/JCardTest.php @@ -0,0 +1,197 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class JCardTest extends TestCase { + + function testToJCard() { + + $card = new Component\VCard([ + "VERSION" => "4.0", + "UID" => "foo", + "BDAY" => "19850407", + "REV" => "19951031T222710Z", + "LANG" => "nl", + "N" => ["Last", "First", "Middle", "", ""], + "item1.TEL" => "+1 555 123456", + "item1.X-AB-LABEL" => "Walkie Talkie", + "ADR" => [ + "", + "", + ["My Street", "Left Side", "Second Shack"], + "Hometown", + "PA", + "18252", + "U.S.A", + ], + ]); + + $card->add('BDAY', '1979-12-25', ['VALUE' => 'DATE', 'X-PARAM' => [1, 2]]); + $card->add('BDAY', '1979-12-25T02:00:00', ['VALUE' => 'DATE-TIME']); + + + $card->add('X-TRUNCATED', '--1225', ['VALUE' => 'DATE']); + $card->add('X-TIME-LOCAL', '123000', ['VALUE' => 'TIME']); + $card->add('X-TIME-UTC', '12:30:00Z', ['VALUE' => 'TIME']); + $card->add('X-TIME-OFFSET', '12:30:00-08:00', ['VALUE' => 'TIME']); + $card->add('X-TIME-REDUCED', '23', ['VALUE' => 'TIME']); + $card->add('X-TIME-TRUNCATED', '--30', ['VALUE' => 'TIME']); + + $card->add('X-KARMA-POINTS', '42', ['VALUE' => 'INTEGER']); + $card->add('X-GRADE', '1.3', ['VALUE' => 'FLOAT']); + + $card->add('TZ', '-0500', ['VALUE' => 'UTC-OFFSET']); + + $expected = [ + "vcard", + [ + [ + "version", + new \StdClass(), + "text", + "4.0" + ], + [ + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ], + [ + "uid", + new \StdClass(), + "text", + "foo", + ], + [ + "bday", + new \StdClass(), + "date-and-or-time", + "1985-04-07", + ], + [ + "bday", + (object)[ + 'x-param' => [1,2], + ], + "date", + "1979-12-25", + ], + [ + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ], + [ + "rev", + new \StdClass(), + "timestamp", + "1995-10-31T22:27:10Z", + ], + [ + "lang", + new \StdClass(), + "language-tag", + "nl", + ], + [ + "n", + new \StdClass(), + "text", + ["Last", "First", "Middle", "", ""], + ], + [ + "tel", + (object)[ + "group" => "item1", + ], + "text", + "+1 555 123456", + ], + [ + "x-ab-label", + (object)[ + "group" => "item1", + ], + "unknown", + "Walkie Talkie", + ], + [ + "adr", + new \StdClass(), + "text", + [ + "", + "", + ["My Street", "Left Side", "Second Shack"], + "Hometown", + "PA", + "18252", + "U.S.A", + ], + ], + [ + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ], + [ + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ], + [ + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ], + [ + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ], + [ + "x-time-reduced", + new \StdClass(), + "time", + "23" + ], + [ + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ], + [ + "x-karma-points", + new \StdClass(), + "integer", + 42 + ], + [ + "x-grade", + new \StdClass(), + "float", + 1.3 + ], + [ + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ], + ], + ]; + + $this->assertEquals($expected, $card->jsonSerialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php new file mode 100644 index 000000000000..76efe94b0651 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php @@ -0,0 +1,25 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class LineFoldingIssueTest extends TestCase { + + function testRead() { + + $event = <<<ICS +BEGIN:VCALENDAR\r +BEGIN:VEVENT\r +DESCRIPTION:TEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\nTEST\\nTEST, TEST\r +END:VEVENT\r +END:VCALENDAR\r + +ICS; + + $obj = Reader::read($event); + $this->assertEquals($event, $obj->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ParameterTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ParameterTest.php new file mode 100644 index 000000000000..a8f72d6064f3 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ParameterTest.php @@ -0,0 +1,137 @@ +<?php + +namespace Sabre\VObject; + +use PHPUnit\Framework\TestCase; + +class ParameterTest extends TestCase { + + function testSetup() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('NAME', $param->name); + $this->assertEquals('value', $param->getValue()); + + } + + function testSetupNameLess() { + + $card = new Component\VCard(); + + $param = new Parameter($card, null, 'URL'); + $this->assertEquals('VALUE', $param->name); + $this->assertEquals('URL', $param->getValue()); + $this->assertTrue($param->noName); + + } + + function testModify() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', null); + $param->addValue(1); + $this->assertEquals([1], $param->getParts()); + + $param->setParts([1, 2]); + $this->assertEquals([1, 2], $param->getParts()); + + $param->addValue(3); + $this->assertEquals([1, 2, 3], $param->getParts()); + + $param->setValue(4); + $param->addValue(5); + $this->assertEquals([4, 5], $param->getParts()); + + } + + function testCastToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('value', $param->__toString()); + $this->assertEquals('value', (string)$param); + + } + + function testCastNullToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('', $param->__toString()); + $this->assertEquals('', (string)$param); + + } + + function testSerialize() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('NAME=value', $param->serialize()); + + } + + function testSerializeEmpty() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('NAME=', $param->serialize()); + + } + + function testSerializeComplex() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', ["val1", "val2;", "val3^", "val4\n", "val5\""]); + $this->assertEquals('NAME=val1,"val2;","val3^^","val4^n","val5^\'"', $param->serialize()); + + } + + /** + * iCal 7.0 (OSX 10.9) has major issues with the EMAIL property, when the + * value contains a plus sign, and it's not quoted. + * + * So we specifically added support for that. + */ + function testSerializePlusSign() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'EMAIL', "user+something@example.org"); + $this->assertEquals('EMAIL="user+something@example.org"', $param->serialize()); + + } + + function testIterate() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', [1, 2, 3, 4]); + $result = []; + + foreach ($param as $value) { + $result[] = $value; + } + + $this->assertEquals([1, 2, 3, 4], $result); + + } + + function testSerializeColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'va:lue'); + $this->assertEquals('NAME="va:lue"', $param->serialize()); + + } + + function testSerializeSemiColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'va;lue'); + $this->assertEquals('NAME="va;lue"', $param->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php new file mode 100644 index 000000000000..a99a1d5096c0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php @@ -0,0 +1,395 @@ +<?php + +namespace Sabre\VObject\Parser; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject; + +class JsonTest extends TestCase { + + function testRoundTripJCard() { + + $input = [ + "vcard", + [ + [ + "version", + new \StdClass(), + "text", + "4.0" + ], + [ + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN", + ], + [ + "uid", + new \StdClass(), + "text", + "foo", + ], + [ + "bday", + new \StdClass(), + "date-and-or-time", + "1985-04-07", + ], + [ + "bday", + (object)[ + 'x-param' => [1,2], + ], + "date", + "1979-12-25", + ], + [ + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ], + [ + "rev", + new \StdClass(), + "timestamp", + "1995-10-31T22:27:10Z", + ], + [ + "lang", + new \StdClass(), + "language-tag", + "nl", + ], + [ + "n", + new \StdClass(), + "text", + ["Last", "First", "Middle", "", ""], + ], + [ + "tel", + (object)[ + "group" => "item1", + ], + "text", + "+1 555 123456", + ], + [ + "x-ab-label", + (object)[ + "group" => "item1", + ], + "unknown", + "Walkie Talkie", + ], + [ + "adr", + new \StdClass(), + "text", + [ + "", + "", + ["My Street", "Left Side", "Second Shack"], + "Hometown", + "PA", + "18252", + "U.S.A", + ], + ], + + [ + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ], + [ + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ], + [ + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ], + [ + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ], + [ + "x-time-reduced", + new \StdClass(), + "time", + "23" + ], + [ + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ], + [ + "x-karma-points", + new \StdClass(), + "integer", + 42 + ], + [ + "x-grade", + new \StdClass(), + "float", + 1.3 + ], + [ + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ], + ], + ]; + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + + $version = VObject\Version::VERSION; + + $result = $vobj->serialize(); + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject $version//EN +UID:foo +BDAY:1985-04-07 +BDAY;X-PARAM=1,2;VALUE=DATE:1979-12-25 +BDAY;VALUE=DATE-TIME:1979-12-25T02:00:00 +REV:1995-10-31T22:27:10Z +LANG:nl +N:Last;First;Middle;; +item1.TEL:+1 555 123456 +item1.X-AB-LABEL:Walkie Talkie +ADR:;;My Street,Left Side,Second Shack;Hometown;PA;18252;U.S.A +X-TRUNCATED;VALUE=DATE:--12-25 +X-TIME-LOCAL;VALUE=TIME:123000 +X-TIME-UTC;VALUE=TIME:123000Z +X-TIME-OFFSET;VALUE=TIME:123000-0800 +X-TIME-REDUCED;VALUE=TIME:23 +X-TIME-TRUNCATED;VALUE=TIME:--30 +X-KARMA-POINTS;VALUE=INTEGER:42 +X-GRADE;VALUE=FLOAT:1.3 +TZ;VALUE=UTC-OFFSET:-0500 +END:VCARD + +VCF; + $this->assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testRoundTripJCal() { + + $input = [ + "vcalendar", + [ + [ + "version", + new \StdClass(), + "text", + "2.0" + ], + [ + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN", + ], + [ + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ], + ], + [ + ["vevent", + [ + [ + "uid", new \StdClass(), "text", "foo", + ], + [ + "dtstart", new \StdClass(), "date", "2013-05-26", + ], + [ + "duration", new \StdClass(), "duration", "P1D", + ], + [ + "categories", new \StdClass(), "text", "home", "testing", + ], + [ + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ], + [ + "attach", new \StdClass(), "binary", base64_encode('attachment') + ], + [ + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ], + [ + "attendee", + (object)[ + "cn" => "Dominik", + "partstat" => "DECLINED", + ], + "cal-address", + "mailto:dominik@example.org" + ], + [ + "geo", new \StdClass(), "float", [51.96668, 7.61876], + ], + [ + "sequence", new \StdClass(), "integer", 5 + ], + [ + "freebusy", new \StdClass(), "period", ["2013-05-26T21:02:13", "PT1H"], ["2013-06-26T12:00:00", "2013-06-26T13:00:00"], + ], + [ + "url", new \StdClass(), "uri", "http://example.org/", + ], + [ + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ], + [ + "rrule", new \StdClass(), "recur", [ + 'freq' => 'WEEKLY', + 'byday' => ['MO', 'TU'], + ], + ], + [ + "x-bool", new \StdClass(), "boolean", true + ], + [ + "x-time", new \StdClass(), "time", "08:00:00", + ], + [ + "request-status", + new \StdClass(), + "text", + ["2.0", "Success"], + ], + [ + "request-status", + new \StdClass(), + "text", + ["3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"], + ], + ], + [ + ["valarm", + [ + [ + "action", new \StdClass(), "text", "DISPLAY", + ], + ], + [], + ], + ], + ] + ], + ]; + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + $result = $vobj->serialize(); + + $version = VObject\Version::VERSION; + + $expected = <<<VCF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foo +DTSTART;VALUE=DATE:20130526 +DURATION:P1D +CATEGORIES:home,testing +CREATED:20130526T181000Z +ATTACH;VALUE=BINARY:YXR0YWNobWVudA== +ATTENDEE:mailto:armin@example.org +ATTENDEE;CN=Dominik;PARTSTAT=DECLINED:mailto:dominik@example.org +GEO:51.96668;7.61876 +SEQUENCE:5 +FREEBUSY:20130526T210213/PT1H,20130626T120000/20130626T130000 +URL;VALUE=URI:http://example.org/ +TZOFFSETFROM:+0500 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU +X-BOOL;VALUE=BOOLEAN:TRUE +X-TIME;VALUE=TIME:080000 +REQUEST-STATUS:2.0;Success +REQUEST-STATUS:3.7;Invalid Calendar User;ATTENDEE:mailto:jsmith@example.org +BEGIN:VALARM +ACTION:DISPLAY +END:VALARM +END:VEVENT +END:VCALENDAR + +VCF; + $this->assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testParseStreamArg() { + + $input = [ + "vcard", + [ + [ + "FN", new \StdClass(), 'text', "foo", + ], + ], + ]; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, json_encode($input)); + rewind($stream); + + $result = VObject\Reader::readJson($stream, 0); + $this->assertEquals('foo', $result->FN->getValue()); + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseInvalidData() { + + $json = new Json(); + $input = [ + "vlist", + [ + [ + "FN", new \StdClass(), 'text', "foo", + ], + ], + ]; + + $json->parse(json_encode($input), 0); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php new file mode 100644 index 000000000000..9c2057e8ba4d --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php @@ -0,0 +1,163 @@ +<?php + +namespace Sabre\VObject\Parser; + +use PHPUnit\Framework\TestCase; + +/** + * Note that most MimeDir related tests can actually be found in the ReaderTest + * class one level up. + */ +class MimeDirTest extends TestCase { + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseError() { + + $mimeDir = new MimeDir(); + $mimeDir->parse(fopen(__FILE__, 'a')); + + } + + function testDecodeLatin1() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +FN:umlaut u - \xFC +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $mimeDir->setCharset('ISO-8859-1'); + $vcard = $mimeDir->parse($vcard); + $this->assertEquals("umlaut u - \xC3\xBC", $vcard->FN->getValue()); + + } + + function testDecodeInlineLatin1() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN;CHARSET=ISO-8859-1:umlaut u - \xFC +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $vcard = $mimeDir->parse($vcard); + $this->assertEquals("umlaut u - \xC3\xBC", $vcard->FN->getValue()); + + } + + function testIgnoreCharsetVCard30() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +FN;CHARSET=unknown:foo-bar - \xFC +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $vcard = $mimeDir->parse($vcard); + $this->assertEquals("foo-bar - \xFC", $vcard->FN->getValue()); + + } + + function testDontDecodeLatin1() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:umlaut u - \xFC +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $vcard = $mimeDir->parse($vcard); + // This basically tests that we don't touch the input string if + // the encoding was set to UTF-8. The result is actually invalid + // and the validator should report this, but it tests effectively + // that we pass through the string byte-by-byte. + $this->assertEquals("umlaut u - \xFC", $vcard->FN->getValue()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testDecodeUnsupportedCharset() { + + $mimeDir = new MimeDir(); + $mimeDir->setCharset('foobar'); + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testDecodeUnsupportedInlineCharset() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN;CHARSET=foobar:nothing +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $mimeDir->parse($vcard); + + } + + function testDecodeWindows1252() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +FN:Euro \x80 +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $mimeDir->setCharset('Windows-1252'); + $vcard = $mimeDir->parse($vcard); + $this->assertEquals("Euro \xE2\x82\xAC", $vcard->FN->getValue()); + + } + + function testDecodeWindows1252Inline() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN;CHARSET=Windows-1252:Euro \x80 +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $vcard = $mimeDir->parse($vcard); + $this->assertEquals("Euro \xE2\x82\xAC", $vcard->FN->getValue()); + + } + + function testCaseInsensitiveInlineCharset() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN;CHARSET=iSo-8859-1:Euro +N;CHARSET=utf-8:Test2 +END:VCARD\n +VCF; + + $mimeDir = new MimeDir(); + $vcard = $mimeDir->parse($vcard); + // we can do a simple assertion here. As long as we don't get an exception, everything is thing + $this->assertEquals("Euro", $vcard->FN->getValue()); + $this->assertEquals("Test2", $vcard->N->getValue()); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php new file mode 100644 index 000000000000..6d34626fe7d9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php @@ -0,0 +1,108 @@ +<?php + +namespace Sabre\VObject\Parser; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject\Reader; + +class QuotedPrintableTest extends TestCase { + + function testReadQuotedPrintableSimple() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aach=65n\r\nEND:VCARD"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->LABEL)); + + } + + function testReadQuotedPrintableNewlineSoft() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->LABEL)); + + } + + function testReadQuotedPrintableNewlineHard() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->LABEL)); + + + } + + function testReadQuotedPrintableCompatibilityMS() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD"; + $result = Reader::read($data, Reader::OPTION_FORGIVING); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->LABEL)); + + } + + function testReadQuotesPrintableCompoundValues() { + + $data = <<<VCF +BEGIN:VCARD +VERSION:2.1 +N:Doe;John;;; +FN:John Doe +ADR;WORK;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;;M=C3=BCnster = +Str. 1;M=C3=BCnster;;48143;Deutschland +END:VCARD +VCF; + + $result = Reader::read($data, Reader::OPTION_FORGIVING); + $this->assertEquals([ + '', '', 'Münster Str. 1', 'Münster', '', '48143', 'Deutschland' + ], $result->ADR->getParts()); + + + } + + private function getPropertyValue(\Sabre\VObject\Property $property) { + + return (string)$property; + + /* + $param = $property['encoding']; + if ($param !== null) { + $encoding = strtoupper((string)$param); + if ($encoding === 'QUOTED-PRINTABLE') { + $value = quoted_printable_decode($value); + } else { + throw new Exception(); + } + } + + $param = $property['charset']; + if ($param !== null) { + $charset = strtoupper((string)$param); + if ($charset !== 'UTF-8') { + $value = mb_convert_encoding($value, 'UTF-8', $charset); + } + } else { + $value = StringUtil::convertToUTF8($value); + } + + return $value; + */ + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/XmlTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/XmlTest.php new file mode 100644 index 000000000000..6c3aa4788c0a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Parser/XmlTest.php @@ -0,0 +1,2894 @@ +<?php + +namespace Sabre\VObject\Parser; + +use PHPUnit\Framework\TestCase; +use Sabre\VObject; + +class XmlTest extends TestCase { + + use VObject\PHPUnitAssertions; + + function testRFC6321Example1() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <calscale> + <text>GREGORIAN</text> + </calscale> + <prodid> + <text>-//Example Inc.//Example Calendar//EN</text> + </prodid> + <version> + <text>2.0</text> + </version> + </properties> + <components> + <vevent> + <properties> + <dtstamp> + <date-time>2008-02-05T19:12:24Z</date-time> + </dtstamp> + <dtstart> + <date>2008-10-06</date> + </dtstart> + <summary> + <text>Planning meeting</text> + </summary> + <uid> + <text>4088E990AD89CB3DBB484909</text> + </uid> + </properties> + </vevent> + </components> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + // VERSION comes first because this is required by vCard 4.0. + 'VERSION:2.0' . "\n" . + 'CALSCALE:GREGORIAN' . "\n" . + 'PRODID:-//Example Inc.//Example Calendar//EN' . "\n" . + 'BEGIN:VEVENT' . "\n" . + 'DTSTAMP:20080205T191224Z' . "\n" . + 'DTSTART;VALUE=DATE:20081006' . "\n" . + 'SUMMARY:Planning meeting' . "\n" . + 'UID:4088E990AD89CB3DBB484909' . "\n" . + 'END:VEVENT' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + function testRFC6321Example2() { + + $xml = <<<XML +<?xml version="1.0" encoding="UTF-8" ?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <prodid> + <text>-//Example Inc.//Example Client//EN</text> + </prodid> + <version> + <text>2.0</text> + </version> + </properties> + <components> + <vtimezone> + <properties> + <last-modified> + <date-time>2004-01-10T03:28:45Z</date-time> + </last-modified> + <tzid><text>US/Eastern</text></tzid> + </properties> + <components> + <daylight> + <properties> + <dtstart> + <date-time>2000-04-04T02:00:00</date-time> + </dtstart> + <rrule> + <recur> + <freq>YEARLY</freq> + <byday>1SU</byday> + <bymonth>4</bymonth> + </recur> + </rrule> + <tzname> + <text>EDT</text> + </tzname> + <tzoffsetfrom> + <utc-offset>-05:00</utc-offset> + </tzoffsetfrom> + <tzoffsetto> + <utc-offset>-04:00</utc-offset> + </tzoffsetto> + </properties> + </daylight> + <standard> + <properties> + <dtstart> + <date-time>2000-10-26T02:00:00</date-time> + </dtstart> + <rrule> + <recur> + <freq>YEARLY</freq> + <byday>-1SU</byday> + <bymonth>10</bymonth> + </recur> + </rrule> + <tzname> + <text>EST</text> + </tzname> + <tzoffsetfrom> + <utc-offset>-04:00</utc-offset> + </tzoffsetfrom> + <tzoffsetto> + <utc-offset>-05:00</utc-offset> + </tzoffsetto> + </properties> + </standard> + </components> + </vtimezone> + <vevent> + <properties> + <dtstamp> + <date-time>2006-02-06T00:11:21Z</date-time> + </dtstamp> + <dtstart> + <parameters> + <tzid><text>US/Eastern</text></tzid> + </parameters> + <date-time>2006-01-02T12:00:00</date-time> + </dtstart> + <duration> + <duration>PT1H</duration> + </duration> + <rrule> + <recur> + <freq>DAILY</freq> + <count>5</count> + </recur> + </rrule> + <rdate> + <parameters> + <tzid><text>US/Eastern</text></tzid> + </parameters> + <period> + <start>2006-01-02T15:00:00</start> + <duration>PT2H</duration> + </period> + </rdate> + <summary> + <text>Event #2</text> + </summary> + <description> + <text>We are having a meeting all this week at 12 +pm for one hour, with an additional meeting on the first day +2 hours long. Please bring your own lunch for the 12 pm +meetings.</text> + </description> + <uid> + <text>00959BC664CA650E933C892C@example.com</text> + </uid> + </properties> + </vevent> + <vevent> + <properties> + <dtstamp> + <date-time>2006-02-06T00:11:21Z</date-time> + </dtstamp> + <dtstart> + <parameters> + <tzid><text>US/Eastern</text></tzid> + </parameters> + <date-time>2006-01-04T14:00:00</date-time> + </dtstart> + <duration> + <duration>PT1H</duration> + </duration> + <recurrence-id> + <parameters> + <tzid><text>US/Eastern</text></tzid> + </parameters> + <date-time>2006-01-04T12:00:00</date-time> + </recurrence-id> + <summary> + <text>Event #2 bis</text> + </summary> + <uid> + <text>00959BC664CA650E933C892C@example.com</text> + </uid> + </properties> + </vevent> + </components> + </vcalendar> +</icalendar> +XML; + + $component = VObject\Reader::readXML($xml); + $this->assertVObjectEqualsVObject( + 'BEGIN:VCALENDAR' . "\n" . + 'VERSION:2.0' . "\n" . + 'PRODID:-//Example Inc.//Example Client//EN' . "\n" . + 'BEGIN:VTIMEZONE' . "\n" . + 'LAST-MODIFIED:20040110T032845Z' . "\n" . + 'TZID:US/Eastern' . "\n" . + 'BEGIN:DAYLIGHT' . "\n" . + 'DTSTART:20000404T020000' . "\n" . + 'RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4' . "\n" . + 'TZNAME:EDT' . "\n" . + 'TZOFFSETFROM:-0500' . "\n" . + 'TZOFFSETTO:-0400' . "\n" . + 'END:DAYLIGHT' . "\n" . + 'BEGIN:STANDARD' . "\n" . + 'DTSTART:20001026T020000' . "\n" . + 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . "\n" . + 'TZNAME:EST' . "\n" . + 'TZOFFSETFROM:-0400' . "\n" . + 'TZOFFSETTO:-0500' . "\n" . + 'END:STANDARD' . "\n" . + 'END:VTIMEZONE' . "\n" . + 'BEGIN:VEVENT' . "\n" . + 'DTSTAMP:20060206T001121Z' . "\n" . + 'DTSTART;TZID=US/Eastern:20060102T120000' . "\n" . + 'DURATION:PT1H' . "\n" . + 'RRULE:FREQ=DAILY;COUNT=5' . "\n" . + 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H' . "\n" . + 'SUMMARY:Event #2' . "\n" . + 'DESCRIPTION:We are having a meeting all this week at 12\npm for one hour\, ' . "\n" . + ' with an additional meeting on the first day\n2 hours long.\nPlease bring y' . "\n" . + ' our own lunch for the 12 pm\nmeetings.' . "\n" . + 'UID:00959BC664CA650E933C892C@example.com' . "\n" . + 'END:VEVENT' . "\n" . + 'BEGIN:VEVENT' . "\n" . + 'DTSTAMP:20060206T001121Z' . "\n" . + 'DTSTART;TZID=US/Eastern:20060104T140000' . "\n" . + 'DURATION:PT1H' . "\n" . + 'RECURRENCE-ID;TZID=US/Eastern:20060104T120000' . "\n" . + 'SUMMARY:Event #2 bis' . "\n" . + 'UID:00959BC664CA650E933C892C@example.com' . "\n" . + 'END:VEVENT' . "\n" . + 'END:VCALENDAR' . "\n", + VObject\Writer::write($component) + ); + + } + + /** + * iCalendar Stream. + */ + function testRFC6321Section3_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar/> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'END:VCALENDAR' . "\n" + ); + } + + /** + * All components exist. + */ + function testRFC6321Section3_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <components> + <vtimezone/> + <vevent/> + <vtodo/> + <vjournal/> + <vfreebusy/> + <standard/> + <daylight/> + <valarm/> + </components> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'BEGIN:VTIMEZONE' . "\n" . + 'END:VTIMEZONE' . "\n" . + 'BEGIN:VEVENT' . "\n" . + 'END:VEVENT' . "\n" . + 'BEGIN:VTODO' . "\n" . + 'END:VTODO' . "\n" . + 'BEGIN:VJOURNAL' . "\n" . + 'END:VJOURNAL' . "\n" . + 'BEGIN:VFREEBUSY' . "\n" . + 'END:VFREEBUSY' . "\n" . + 'BEGIN:STANDARD' . "\n" . + 'END:STANDARD' . "\n" . + 'BEGIN:DAYLIGHT' . "\n" . + 'END:DAYLIGHT' . "\n" . + 'BEGIN:VALARM' . "\n" . + 'END:VALARM' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Properties, Special Cases, GEO. + */ + function testRFC6321Section3_4_1_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <geo> + <latitude>37.386013</latitude> + <longitude>-122.082932</longitude> + </geo> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'GEO:37.386013;-122.082932' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Properties, Special Cases, REQUEST-STATUS. + */ + function testRFC6321Section3_4_1_3() { + + // Example 1 of RFC5545, Section 3.8.8.3. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <request-status> + <code>2.0</code> + <description>Success</description> + </request-status> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'REQUEST-STATUS:2.0;Success' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // Example 2 of RFC5545, Section 3.8.8.3. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <request-status> + <code>3.1</code> + <description>Invalid property value</description> + <data>DTSTART:96-Apr-01</data> + </request-status> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // Example 3 of RFC5545, Section 3.8.8.3. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <request-status> + <code>2.8</code> + <description>Success, repeating event ignored. Scheduled as a single event.</description> + <data>RRULE:FREQ=WEEKLY;INTERVAL=2</data> + </request-status> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'REQUEST-STATUS:2.8;Success\, repeating event ignored. Scheduled as a single' . "\n" . + ' event.;RRULE:FREQ=WEEKLY\;INTERVAL=2' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // Example 4 of RFC5545, Section 3.8.8.3. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <request-status> + <code>4.1</code> + <description>Event conflict. Date-time is busy.</description> + </request-status> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'REQUEST-STATUS:4.1;Event conflict. Date-time is busy.' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // Example 5 of RFC5545, Section 3.8.8.3. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <request-status> + <code>3.7</code> + <description>Invalid calendar user</description> + <data>ATTENDEE:mailto:jsmith@example.com</data> + </request-status> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'REQUEST-STATUS:3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.com' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Binary. + */ + function testRFC6321Section3_6_1() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <attach> + <binary>SGVsbG8gV29ybGQh</binary> + </attach> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'ATTACH:SGVsbG8gV29ybGQh' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // In vCard 4, BINARY no longer exists and is replaced by URI. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <attach> + <uri>SGVsbG8gV29ybGQh</uri> + </attach> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'ATTACH:SGVsbG8gV29ybGQh' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Boolean. + */ + function testRFC6321Section3_6_2() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <attendee> + <parameters> + <rsvp><boolean>true</boolean></rsvp> + </parameters> + <cal-address>mailto:cyrus@example.com</cal-address> + </attendee> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'ATTENDEE;RSVP=true:mailto:cyrus@example.com' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Calendar User Address. + */ + function testRFC6321Section3_6_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <attendee> + <cal-address>mailto:cyrus@example.com</cal-address> + </attendee> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'ATTENDEE:mailto:cyrus@example.com' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Date. + */ + function testRFC6321Section3_6_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <dtstart> + <date>2011-05-17</date> + </dtstart> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'DTSTART;VALUE=DATE:20110517' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Date-Time. + */ + function testRFC6321Section3_6_5() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <dtstart> + <date-time>2011-05-17T12:00:00</date-time> + </dtstart> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'DTSTART:20110517T120000' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Duration. + */ + function testRFC6321Section3_6_6() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <duration> + <duration>P1D</duration> + </duration> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'DURATION:P1D' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Float. + */ + function testRFC6321Section3_6_7() { + + // GEO uses <float /> with a positive and a non-negative numbers. + $this->testRFC6321Section3_4_1_2(); + + } + + /** + * Values, Integer. + */ + function testRFC6321Section3_6_8() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <foo> + <integer>42</integer> + </foo> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'FOO:42' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <foo> + <integer>-42</integer> + </foo> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'FOO:-42' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Period of Time. + */ + function testRFC6321Section3_6_9() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <freebusy> + <period> + <start>2011-05-17T12:00:00</start> + <duration>P1H</duration> + </period> + </freebusy> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'FREEBUSY:20110517T120000/P1H' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <freebusy> + <period> + <start>2011-05-17T12:00:00</start> + <end>2012-05-17T12:00:00</end> + </period> + </freebusy> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'FREEBUSY:20110517T120000/20120517T120000' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Recurrence Rule. + */ + function testRFC6321Section3_6_10() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rrule> + <recur> + <freq>YEARLY</freq> + <count>5</count> + <byday>-1SU</byday> + <bymonth>10</bymonth> + </recur> + </rrule> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RRULE:FREQ=YEARLY;COUNT=5;BYDAY=-1SU;BYMONTH=10' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Text. + */ + function testRFC6321Section3_6_11() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <calscale> + <text>GREGORIAN</text> + </calscale> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'CALSCALE:GREGORIAN' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, Time. + */ + function testRFC6321Section3_6_12() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <foo> + <time>12:00:00</time> + </foo> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'FOO:120000' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, URI. + */ + function testRFC6321Section3_6_13() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <attach> + <uri>http://calendar.example.com</uri> + </attach> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'ATTACH:http://calendar.example.com' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Values, UTC Offset. + */ + function testRFC6321Section3_6_14() { + + // Example 1 of RFC5545, Section 3.3.14. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <tzoffsetfrom> + <utc-offset>-05:00</utc-offset> + </tzoffsetfrom> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'TZOFFSETFROM:-0500' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + // Example 2 of RFC5545, Section 3.3.14. + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <tzoffsetfrom> + <utc-offset>+01:00</utc-offset> + </tzoffsetfrom> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'TZOFFSETFROM:+0100' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Handling Unrecognized Properties or Parameters. + */ + function testRFC6321Section5() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <x-property> + <unknown>20110512T120000Z</unknown> + </x-property> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'X-PROPERTY:20110512T120000Z' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <dtstart> + <parameters> + <x-param> + <text>PT30M</text> + </x-param> + </parameters> + <date-time>2011-05-12T13:00:00Z</date-time> + </dtstart> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'DTSTART;X-PARAM=PT30M:20110512T130000Z' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + function testRDateWithDateTime() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <date-time>2008-02-05T19:12:24Z</date-time> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE:20080205T191224Z' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <date-time>2008-02-05T19:12:24Z</date-time> + <date-time>2009-02-05T19:12:24Z</date-time> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE:20080205T191224Z,20090205T191224Z' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + function testRDateWithDate() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <date>2008-10-06</date> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE:20081006' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <date>2008-10-06</date> + <date>2009-10-06</date> + <date>2010-10-06</date> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE:20081006,20091006,20101006' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + function testRDateWithPeriod() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <parameters> + <tzid> + <text>US/Eastern</text> + </tzid> + </parameters> + <period> + <start>2006-01-02T15:00:00</start> + <duration>PT2H</duration> + </period> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"> + <vcalendar> + <properties> + <rdate> + <parameters> + <tzid> + <text>US/Eastern</text> + </tzid> + </parameters> + <period> + <start>2006-01-02T15:00:00</start> + <duration>PT2H</duration> + </period> + <period> + <start>2008-01-02T15:00:00</start> + <duration>PT1H</duration> + </period> + </rdate> + </properties> + </vcalendar> +</icalendar> +XML +, + 'BEGIN:VCALENDAR' . "\n" . + 'RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H,20080102T150000/PT1' . "\n" . + ' H' . "\n" . + 'END:VCALENDAR' . "\n" + ); + + } + + /** + * Basic example. + */ + function testRFC6351Basic() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <fn> + <text>J. Doe</text> + </fn> + <n> + <surname>Doe</surname> + <given>J.</given> + <additional/> + <prefix/> + <suffix/> + </n> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'FN:J. Doe' . "\n" . + 'N:Doe;J.;;;' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Example 1. + */ + function testRFC6351Example1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <fn> + <text>J. Doe</text> + </fn> + <n> + <surname>Doe</surname> + <given>J.</given> + <additional/> + <prefix/> + <suffix/> + </n> + <x-file> + <parameters> + <mediatype> + <text>image/jpeg</text> + </mediatype> + </parameters> + <unknown>alien.jpg</unknown> + </x-file> + <x1:a href="http://www.example.com" xmlns:x1="http://www.w3.org/1999/xhtml">My web page!</x1:a> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'FN:J. Doe' . "\n" . + 'N:Doe;J.;;;' . "\n" . + 'X-FILE;MEDIATYPE=image/jpeg:alien.jpg' . "\n" . + 'XML:<a xmlns="http://www.w3.org/1999/xhtml" href="http://www.example.com">M' . "\n" . + ' y web page!</a>' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Design Considerations. + */ + function testRFC6351Section5() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tel> + <parameters> + <type> + <text>voice</text> + <text>video</text> + </type> + </parameters> + <uri>tel:+1-555-555-555</uri> + </tel> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TEL;TYPE="voice,video":tel:+1-555-555-555' . "\n" . + 'END:VCARD' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tel> + <parameters> + <type> + <text>voice</text> + <text>video</text> + </type> + </parameters> + <text>tel:+1-555-555-555</text> + </tel> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TEL;TYPE="voice,video":tel:+1-555-555-555' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Design Considerations. + */ + function testRFC6351Section5Group() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tel> + <text>tel:+1-555-555-556</text> + </tel> + <group name="contact"> + <tel> + <text>tel:+1-555-555-555</text> + </tel> + <fn> + <text>Gordon</text> + </fn> + </group> + <group name="media"> + <fn> + <text>Gordon</text> + </fn> + </group> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TEL:tel:+1-555-555-556' . "\n" . + 'contact.TEL:tel:+1-555-555-555' . "\n" . + 'contact.FN:Gordon' . "\n" . + 'media.FN:Gordon' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Extensibility. + */ + function testRFC6351Section5_1_NoNamespace() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <x-my-prop> + <parameters> + <pref> + <integer>1</integer> + </pref> + </parameters> + <text>value goes here</text> + </x-my-prop> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'X-MY-PROP;PREF=1:value goes here' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.1 of Relax NG Schema: value-date. + */ + function testRFC6351ValueDateWithYearMonthDay() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>20150128</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:20150128' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.1 of Relax NG Schema: value-date. + */ + function testRFC6351ValueDateWithYearMonth() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>2015-01</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:2015-01' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.1 of Relax NG Schema: value-date. + */ + function testRFC6351ValueDateWithMonth() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--01</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--01' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.1 of Relax NG Schema: value-date. + */ + function testRFC6351ValueDateWithMonthDay() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--0128</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--0128' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.1 of Relax NG Schema: value-date. + */ + function testRFC6351ValueDateWithDay() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithHour() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>13</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:13' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithHourMinute() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>1353</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:1353' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithHourMinuteSecond() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>135301</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:135301' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithMinute() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>-53</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:-53' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithMinuteSecond() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>-5301</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:-5301' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithSecond() { + + $this->assertTrue(true); + + /* + * According to the Relax NG Schema, there is a conflict between + * value-date and value-time. The --01 syntax can only match a + * value-date because of the higher priority set in + * value-date-and-or-time. So we basically skip this test. + * + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--01</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--01' . "\n" . + 'END:VCARD' . "\n" + ); + */ + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithSecondZ() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--01Z</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--01Z' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.2 of Relax NG Schema: value-time. + */ + function testRFC6351ValueTimeWithSecondTZ() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--01+1234</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--01+1234' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithYearMonthDayHour() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>20150128T13</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:20150128T13' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithMonthDayHour() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>--0128T13</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:--0128T13' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithDayHour() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28T13</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28T13' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithDayHourMinute() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28T1353</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28T1353' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithDayHourMinuteSecond() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28T135301</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28T135301' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithDayHourZ() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28T13Z</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28T13Z' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Section 4.3.3 of Relax NG Schema: value-date-time. + */ + function testRFC6351ValueDateTimeWithDayHourTZ() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>---28T13+1234</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:---28T13+1234' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: SOURCE. + */ + function testRFC6350Section6_1_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <source> + <uri>ldap://ldap.example.com/cn=Babs%20Jensen,%20o=Babsco,%20c=US</uri> + </source> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'SOURCE:ldap://ldap.example.com/cn=Babs%20Jensen\,%20o=Babsco\,%20c=US' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: KIND. + */ + function testRFC6350Section6_1_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <kind> + <text>individual</text> + </kind> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'KIND:individual' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: FN. + */ + function testRFC6350Section6_2_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <fn> + <text>Mr. John Q. Public, Esq.</text> + </fn> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'FN:Mr. John Q. Public\, Esq.' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: N. + */ + function testRFC6350Section6_2_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <n> + <surname>Stevenson</surname> + <given>John</given> + <additional>Philip,Paul</additional> + <prefix>Dr.</prefix> + <suffix>Jr.,M.D.,A.C.P.</suffix> + </n> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'N:Stevenson;John;Philip\,Paul;Dr.;Jr.\,M.D.\,A.C.P.' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: NICKNAME. + */ + function testRFC6350Section6_2_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <nickname> + <text>Jim</text> + <text>Jimmie</text> + </nickname> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'NICKNAME:Jim,Jimmie' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: PHOTO. + */ + function testRFC6350Section6_2_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <photo> + <uri>http://www.example.com/pub/photos/jqpublic.gif</uri> + </photo> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'PHOTO:http://www.example.com/pub/photos/jqpublic.gif' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + function testRFC6350Section6_2_5() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <bday> + <date-and-or-time>19531015T231000Z</date-and-or-time> + </bday> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'BDAY:19531015T231000Z' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + function testRFC6350Section6_2_6() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <anniversary> + <date-and-or-time>19960415</date-and-or-time> + </anniversary> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'ANNIVERSARY:19960415' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: GENDER. + */ + function testRFC6350Section6_2_7() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <gender> + <sex>Jim</sex> + <text>Jimmie</text> + </gender> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'GENDER:Jim;Jimmie' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: ADR. + */ + function testRFC6350Section6_3_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <adr> + <pobox/> + <ext/> + <street>123 Main Street</street> + <locality>Any Town</locality> + <region>CA</region> + <code>91921-1234</code> + <country>U.S.A.</country> + </adr> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'ADR:;;123 Main Street;Any Town;CA;91921-1234;U.S.A.' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: TEL. + */ + function testRFC6350Section6_4_1() { + + /** + * Quoting RFC: + * > Value type: By default, it is a single free-form text value (for + * > backward compatibility with vCard 3), but it SHOULD be reset to a + * > URI value. It is expected that the URI scheme will be "tel", as + * > specified in [RFC3966], but other schemes MAY be used. + * + * So first, we test xCard/URI to vCard/URI. + * Then, we test xCard/TEXT to vCard/TEXT to xCard/TEXT. + */ + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tel> + <parameters> + <type> + <text>home</text> + </type> + </parameters> + <uri>tel:+33-01-23-45-67</uri> + </tel> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TEL;TYPE=home:tel:+33-01-23-45-67' . "\n" . + 'END:VCARD' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tel> + <parameters> + <type> + <text>home</text> + </type> + </parameters> + <text>tel:+33-01-23-45-67</text> + </tel> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TEL;TYPE=home:tel:+33-01-23-45-67' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: EMAIL. + */ + function testRFC6350Section6_4_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <email> + <parameters> + <type> + <text>work</text> + </type> + </parameters> + <text>jqpublic@xyz.example.com</text> + </email> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'EMAIL;TYPE=work:jqpublic@xyz.example.com' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: IMPP. + */ + function testRFC6350Section6_4_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <impp> + <parameters> + <pref> + <text>1</text> + </pref> + </parameters> + <uri>xmpp:alice@example.com</uri> + </impp> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'IMPP;PREF=1:xmpp:alice@example.com' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: LANG. + */ + function testRFC6350Section6_4_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <lang> + <parameters> + <type> + <text>work</text> + </type> + <pref> + <text>2</text> + </pref> + </parameters> + <language-tag>en</language-tag> + </lang> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'LANG;TYPE=work;PREF=2:en' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: TZ. + */ + function testRFC6350Section6_5_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <tz> + <text>Raleigh/North America</text> + </tz> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TZ:Raleigh/North America' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: GEO. + */ + function testRFC6350Section6_5_2() { + + $this->assertXMLEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <geo> + <uri>geo:37.386013,-122.082932</uri> + </geo> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'GEO:geo:37.386013\,-122.082932' . "\n" . + 'END:VCARD' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <geo> + <text>geo:37.386013,-122.082932</text> + </geo> + </vcard> +</vcards> +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'GEO:geo:37.386013\,-122.082932' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: TITLE. + */ + function testRFC6350Section6_6_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<<<XML +<?xml version="1.0" encoding="UTF-8"?> +<vcards xmlns="urn:ietf:params:xml:ns:vcard-4.0"> + <vcard> + <title> + <text>Research Scientist</text> + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'TITLE:Research Scientist' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: ROLE. + */ + function testRFC6350Section6_6_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + Project Leader + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'ROLE:Project Leader' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: LOGO. + */ + function testRFC6350Section6_6_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + http://www.example.com/pub/logos/abccorp.jpg + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'LOGO:http://www.example.com/pub/logos/abccorp.jpg' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: ORG. + */ + function testRFC6350Section6_6_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + ABC, Inc. + North American Division + Marketing + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'ORG:ABC\, Inc.;North American Division;Marketing' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: MEMBER. + */ + function testRFC6350Section6_6_5() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'MEMBER:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af' . "\n" . + 'END:VCARD' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + mailto:subscriber1@example.com + + + xmpp:subscriber2@example.com + + + sip:subscriber3@example.com + + + tel:+1-418-555-5555 + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'MEMBER:mailto:subscriber1@example.com' . "\n" . + 'MEMBER:xmpp:subscriber2@example.com' . "\n" . + 'MEMBER:sip:subscriber3@example.com' . "\n" . + 'MEMBER:tel:+1-418-555-5555' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: RELATED. + */ + function testRFC6350Section6_6_6() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + + + friend + + + urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'RELATED;TYPE=friend:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: CATEGORIES. + */ + function testRFC6350Section6_7_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + INTERNET + IETF + INDUSTRY + INFORMATION TECHNOLOGY + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: NOTE. + */ + function testRFC6350Section6_7_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + Foo, bar + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'NOTE:Foo\, bar' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: PRODID. + */ + function testRFC6350Section6_7_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + -//ONLINE DIRECTORY//NONSGML Version 1//EN + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + function testRFC6350Section6_7_4() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + 19951031T222710Z + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'REV:19951031T222710Z' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: SOUND. + */ + function testRFC6350Section6_7_5() { + + $this->assertXMLEqualsToMimeDir( +<< + + + + CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com' . "\n" . + 'END:VCARD' . "\n" + ); + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: UID. + */ + function testRFC6350Section6_7_6() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: CLIENTPIDMAP. + */ + function testRFC6350Section6_7_7() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + 1 + urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'CLIENTPIDMAP:1;urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: URL. + */ + function testRFC6350Section6_7_8() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + http://example.org/restaurant.french/~chezchic.html + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'URL:http://example.org/restaurant.french/~chezchic.html' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: VERSION. + */ + function testRFC6350Section6_7_9() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: KEY. + */ + function testRFC6350Section6_8_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + + + application/pgp-keys + + + ftp://example.com/keys/jdoe + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'KEY;MEDIATYPE=application/pgp-keys:ftp://example.com/keys/jdoe' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: FBURL. + */ + function testRFC6350Section6_9_1() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + + + 1 + + + http://www.example.com/busy/janedoe + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'FBURL;PREF=1:http://www.example.com/busy/janedoe' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: CALADRURI. + */ + function testRFC6350Section6_9_2() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + http://example.com/calendar/jdoe + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'CALADRURI:http://example.com/calendar/jdoe' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: CALURI. + */ + function testRFC6350Section6_9_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + + + 1 + + + http://cal.example.com/calA + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'CALURI;PREF=1:http://cal.example.com/calA' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Property: CAPURI. + */ + function testRFC6350SectionA_3() { + + $this->assertXMLReflexivelyEqualsToMimeDir( +<< + + + + http://cap.example.com/capA + + + +XML +, + 'BEGIN:VCARD' . "\n" . + 'VERSION:4.0' . "\n" . + 'CAPURI:http://cap.example.com/capA' . "\n" . + 'END:VCARD' . "\n" + ); + + } + + /** + * Check this equality: + * XML -> object model -> MIME Dir. + */ + protected function assertXMLEqualsToMimeDir($xml, $mimedir) { + + $component = VObject\Reader::readXML($xml); + $this->assertVObjectEqualsVObject($mimedir, $component); + + } + + /** + * Check this (reflexive) equality: + * XML -> object model -> MIME Dir -> object model -> XML. + */ + protected function assertXMLReflexivelyEqualsToMimeDir($xml, $mimedir) { + + $this->assertXMLEqualsToMimeDir($xml, $mimedir); + + $component = VObject\Reader::read($mimedir); + $this->assertXmlStringEqualsXmlString($xml, VObject\Writer::writeXML($component)); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php new file mode 100644 index 000000000000..306ae0f344e7 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php @@ -0,0 +1,20 @@ + '3.0']); + $vcard->add('PHOTO', ['a', 'b']); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php new file mode 100644 index 000000000000..198f4d08e8e6 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php @@ -0,0 +1,23 @@ +assertTrue($vcard->{'X-AWESOME'}->getValue()); + $this->assertFalse($vcard->{'X-SUCKS'}->getValue()); + + $this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType()); + $this->assertEquals($input, $vcard->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php new file mode 100644 index 000000000000..f1f5dc4582bb --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php @@ -0,0 +1,51 @@ +createProperty('ORG'); + $elem->setParts($arr); + + $this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $elem->getValue()); + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + + } + + function testGetParts() { + + $str = 'ABC\, Inc.;North American Division;Marketing\;Sales'; + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG'); + $elem->setRawMimeDirValue($str); + + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + } + + function testGetPartsNull() { + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG', null); + + $this->assertEquals(0, count($elem->getParts())); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php new file mode 100644 index 000000000000..c9d4f0478cfc --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php @@ -0,0 +1,31 @@ +parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'}); + + $this->assertEquals([ + 0.234, + 1.245, + ], $result->{'X-FLOAT'}->getParts()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php new file mode 100644 index 000000000000..4d1ddfdd008a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php @@ -0,0 +1,34 @@ +add('ATTENDEE', $input); + + $this->assertEquals( + $expected, + $property->getNormalizedValue() + ); + + } + + function values() { + + return [ + ['mailto:a@b.com', 'mailto:a@b.com'], + ['mailto:a@b.com', 'MAILTO:a@b.com'], + ['/foo/bar', '/foo/bar'], + ]; + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php new file mode 100644 index 000000000000..8e31ce4c05bf --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -0,0 +1,372 @@ +vcal = new VCalendar(); + + } + + function testSetDateTime() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetDateTimeLOCAL() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt, $isFloating = true); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeUTC() { + + $tz = new \DateTimeZone('GMT'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000Z', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeFromUnixTimestamp() { + + // When initialized from a Unix timestamp, the timezone is set to "+00:00". + $dt = new \DateTime('@489288600'); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000Z', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeLOCALTZ() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeDATE() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem['VALUE'] = 'DATE'; + $elem->setDateTime($dt); + + $this->assertEquals('19850704', (string)$elem); + $this->assertNull($elem['TZID']); + $this->assertEquals('DATE', (string)$elem['VALUE']); + + $this->assertFalse($elem->hasTime()); + } + + function testSetValue() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetValueArray() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue([$dt1, $dt2]); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetParts() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts([$dt1, $dt2]); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + function testSetPartsStrings() { + + $dt1 = '19850704T013000Z'; + $dt2 = '19850704T023000Z'; + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts([$dt1, $dt2]); + + $this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + + function testGetDateTimeCached() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTimeImmutable('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals($elem->getDateTime(), $dt); + + } + + function testGetDateTimeDateNULL() { + + $elem = $this->vcal->createProperty('DTSTART'); + $dt = $elem->getDateTime(); + + $this->assertNull($dt); + + } + + function testGetDateTimeDateDATE() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateDATEReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt = $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloating() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloatingReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt = $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTimeInterface', $dt); + $this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateUTC() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000Z'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('UTC', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeDateLOCALTZ() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000'); + $elem['TZID'] = 'Europe/Amsterdam'; + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testGetDateTimeDateInvalid() { + + $elem = $this->vcal->createProperty('DTSTART', 'bla'); + $dt = $elem->getDateTime(); + + } + + function testGetDateTimeWeirdTZ() { + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000'); + $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam'; + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeBadTimeZone() { + + $default = date_default_timezone_get(); + date_default_timezone_set('Canada/Eastern'); + + $elem = $this->vcal->createProperty('DTSTART', '19850704T013000'); + $elem['TZID'] = 'Moon'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = 'Moon'; + $timezone->{'X-LIC-LOCATION'} = 'Moon'; + + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTimeImmutable', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName()); + date_default_timezone_set($default); + + } + + function testUpdateValueParameter() { + + $dtStart = $this->vcal->createProperty('DTSTART', new \DateTime('2013-06-07 15:05:00')); + $dtStart['VALUE'] = 'DATE'; + + $this->assertEquals("DTSTART;VALUE=DATE:20130607\r\n", $dtStart->serialize()); + + } + + function testValidate() { + + $exDate = $this->vcal->createProperty('EXDATE', '-00011130T143000Z'); + $messages = $exDate->validate(); + $this->assertEquals(1, count($messages)); + $this->assertEquals(3, $messages[0]['level']); + + } + + /** + * This issue was discovered on the sabredav mailing list. + */ + function testCreateDatePropertyThroughAdd() { + + $vcal = new VCalendar(); + $vevent = $vcal->add('VEVENT'); + + $dtstart = $vevent->add( + 'DTSTART', + new \DateTime('2014-03-07'), + ['VALUE' => 'DATE'] + ); + + $this->assertEquals("DTSTART;VALUE=DATE:20140307\r\n", $dtstart->serialize()); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php new file mode 100644 index 000000000000..fa8741dd8f8a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php @@ -0,0 +1,21 @@ +add('VEVENT', ['DURATION' => ['PT1H']]); + + $this->assertEquals( + new \DateInterval('PT1H'), + $event->{'DURATION'}->getDateInterval() + ); + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php new file mode 100644 index 000000000000..8f469dd1807a --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php @@ -0,0 +1,454 @@ +add('RRULE', 'FREQ=Daily'); + + $this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur); + + $this->assertEquals(['FREQ' => 'DAILY'], $recur->getParts()); + $recur->setParts(['freq' => 'MONTHLY']); + + $this->assertEquals(['FREQ' => 'MONTHLY'], $recur->getParts()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testSetValueBadVal() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', 'FREQ=Daily'); + $recur->setValue(new \Exception()); + + } + + function testSetValueWithCount() { + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', 'FREQ=Daily'); + $recur->setValue(['COUNT' => 3]); + $this->assertEquals($recur->getParts()['COUNT'], 3); + } + + function testGetJSONWithCount() { + $input = 'BEGIN:VCALENDAR +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +RRULE:FREQ=DAILY;COUNT=3 +ORGANIZER;CN=robert pipo:mailto:robert@example.org +END:VEVENT +END:VCALENDAR +'; + + $vcal = Reader::read($input); + $rrule = $vcal->VEVENT->RRULE; + $count = $rrule->getJsonValue()[0]['count']; + $this->assertTrue(is_int($count)); + $this->assertEquals(3, $count); + } + + function testSetSubParts() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', ['FREQ' => 'DAILY', 'BYDAY' => 'mo,tu', 'BYMONTH' => [0, 1]]); + + $this->assertEquals([ + 'FREQ' => 'DAILY', + 'BYDAY' => ['MO', 'TU'], + 'BYMONTH' => [0, 1], + ], $recur->getParts()); + + } + + function testGetJSONWithUntil() { + $input = 'BEGIN:VCALENDAR +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +RRULE:FREQ=DAILY;UNTIL=20160305T230000Z +ORGANIZER;CN=robert pipo:mailto:robert@example.org +END:VEVENT +END:VCALENDAR +'; + + $vcal = Reader::read($input); + $rrule = $vcal->VEVENT->RRULE; + $untilJsonString = $rrule->getJsonValue()[0]['until']; + $this->assertEquals('2016-03-05T23:00:00Z', $untilJsonString); + } + + + function testValidateStripEmpties() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foobar +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +RRULE:FREQ=DAILY;BYMONTH=;UNTIL=20160305T230000Z +ORGANIZER;CN=robert pipo:mailto:robert@example.org +DTSTAMP:20160312T183800Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = Reader::read($input); + $this->assertEquals( + 1, + count($vcal->validate()) + ); + $this->assertEquals( + 1, + count($vcal->validate($vcal::REPAIR)) + ); + + $expected = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foobar +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +RRULE:FREQ=DAILY;UNTIL=20160305T230000Z +ORGANIZER;CN=robert pipo:mailto:robert@example.org +DTSTAMP:20160312T183800Z +END:VEVENT +END:VCALENDAR +'; + + $this->assertVObjectEqualsVObject( + $expected, + $vcal + ); + + } + + function testValidateStripNoFreq() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foobar +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +RRULE:UNTIL=20160305T230000Z +ORGANIZER;CN=robert pipo:mailto:robert@example.org +DTSTAMP:20160312T183800Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = Reader::read($input); + $this->assertEquals( + 1, + count($vcal->validate()) + ); + $this->assertEquals( + 1, + count($vcal->validate($vcal::REPAIR)) + ); + + $expected = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foobar +BEGIN:VEVENT +UID:908d53c0-e1a3-4883-b69f-530954d6bd62 +TRANSP:OPAQUE +DTSTART;TZID=Europe/Berlin:20160301T150000 +DTEND;TZID=Europe/Berlin:20160301T170000 +SUMMARY:test +ORGANIZER;CN=robert pipo:mailto:robert@example.org +DTSTAMP:20160312T183800Z +END:VEVENT +END:VCALENDAR +'; + + $this->assertVObjectEqualsVObject( + $expected, + $vcal + ); + + } + + function testValidateInvalidByMonthRruleWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(1, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24', $property->getValue()); + + } + + function testValidateInvalidByMonthRruleWithoutRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0'); + $result = $property->validate(); + + $this->assertCount(1, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0', $property->getValue()); + + } + + function testValidateInvalidByMonthRruleWithRepair2() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=bla'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(1, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24', $property->getValue()); + + } + + function testValidateInvalidByMonthRruleWithoutRepair2() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=bla'); + $result = $property->validate(); + + $this->assertCount(1, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + // Without repair the invalid BYMONTH is still there, but the value is changed to uppercase + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=BLA', $property->getValue()); + + } + + function testValidateInvalidByMonthRruleValue14WithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=14'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(1, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24', $property->getValue()); + + } + + function testValidateInvalidByMonthRruleMultipleWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0,1,2,3,4,14'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=1,2,3,4', $property->getValue()); + + } + + function testValidateOneOfManyInvalidByMonthRruleWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=bla,3,foo'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYMONTH in RRULE must have value(s) between 1 and 12!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=3', $property->getValue()); + + } + + function testValidateValidByMonthRrule() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=2,3'); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=2,3', $property->getValue()); + + } + + /** + * test for issue #336 + */ + function testValidateRruleBySecondZero() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=DAILY;BYHOUR=10;BYMINUTE=30;BYSECOND=0;UNTIL=20150616T153000Z'); + $result = $property->validate(Node::REPAIR); + + // There should be 0 warnings and the value should be unchanged + $this->assertEmpty($result); + $this->assertEquals('FREQ=DAILY;BYHOUR=10;BYMINUTE=30;BYSECOND=0;UNTIL=20150616T153000Z', $property->getValue()); + + } + + function testValidateValidByWeekNoWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYWEEKNO=11'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(0, $result); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYWEEKNO=11', $property->getValue()); + + } + + function testValidateInvalidByWeekNoWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYWEEKNO=55;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(1, $result); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYDAY=WE', $property->getValue()); + + } + + function testValidateMultipleInvalidByWeekNoWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYWEEKNO=55,2,-80;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYWEEKNO=2;BYDAY=WE', $property->getValue()); + + } + + function testValidateAllInvalidByWeekNoWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYWEEKNO=55,-80;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYDAY=WE', $property->getValue()); + + } + + function testValidateInvalidByWeekNoWithoutRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYWEEKNO=55;BYDAY=WE'); + $result = $property->validate(); + + $this->assertCount(1, $result); + $this->assertEquals('BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYWEEKNO=55;BYDAY=WE', $property->getValue()); + + } + + function testValidateValidByYearDayWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYYEARDAY=119'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(0, $result); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYYEARDAY=119', $property->getValue()); + + } + + function testValidateInvalidByYearDayWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYYEARDAY=367;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(1, $result); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYDAY=WE', $property->getValue()); + + } + + function testValidateMultipleInvalidByYearDayWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYYEARDAY=380,2,-390;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYYEARDAY=2;BYDAY=WE', $property->getValue()); + + } + + function testValidateAllInvalidByYearDayWithRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYYEARDAY=455,-480;BYDAY=WE'); + $result = $property->validate(Node::REPAIR); + + $this->assertCount(2, $result); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[1]['message']); + $this->assertEquals(1, $result[1]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYDAY=WE', $property->getValue()); + + } + + function testValidateInvalidByYearDayWithoutRepair() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('RRULE', 'FREQ=YEARLY;COUNT=6;BYYEARDAY=380;BYDAY=WE'); + $result = $property->validate(); + + $this->assertCount(1, $result); + $this->assertEquals('BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + $this->assertEquals('FREQ=YEARLY;COUNT=6;BYYEARDAY=380;BYDAY=WE', $property->getValue()); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/TextTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/TextTest.php new file mode 100644 index 000000000000..d9c230214e6e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/TextTest.php @@ -0,0 +1,97 @@ + '2.1', + 'PROP' => $propValue + ], false); + + // Adding quoted-printable, because we're testing if it gets removed + // automatically. + $doc->PROP['ENCODING'] = 'QUOTED-PRINTABLE'; + $doc->PROP['P1'] = 'V1'; + + + $output = $doc->serialize(); + + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\n$expected\r\nEND:VCARD\r\n", $output); + + } + + function testSerializeVCard21() { + + $this->assertVCard21Serialization( + 'f;oo', + 'PROP;P1=V1:f;oo' + ); + + } + + function testSerializeVCard21Array() { + + $this->assertVCard21Serialization( + ['f;oo', 'bar'], + 'PROP;P1=V1:f\;oo;bar' + ); + + } + function testSerializeVCard21Fold() { + + $this->assertVCard21Serialization( + str_repeat('x', 80), + 'PROP;P1=V1:' . str_repeat('x', 64) . "\r\n " . str_repeat('x', 16) + ); + + } + + + + function testSerializeQuotedPrintable() { + + $this->assertVCard21Serialization( + "foo\r\nbar", + 'PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abar' + ); + } + + function testSerializeQuotedPrintableFold() { + + $this->assertVCard21Serialization( + "foo\r\nbarxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abarxxxxxxxxxxxxxxxxxxxxxxxxxx=\r\n xxx" + ); + + } + + function testValidateMinimumPropValue() { + + $vcard = <<assertEquals(1, count($vcard->validate())); + + $this->assertEquals(1, count($vcard->N->getParts())); + + $vcard->validate(\Sabre\VObject\Node::REPAIR); + + $this->assertEquals(5, count($vcard->N->getParts())); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/UriTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/UriTest.php new file mode 100644 index 000000000000..51d7ce3a8a58 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/UriTest.php @@ -0,0 +1,28 @@ +serialize(); + $this->assertContains('URL;VALUE=URI:http://example.org/', $output); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php new file mode 100644 index 000000000000..51884116c421 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php @@ -0,0 +1,270 @@ +createProperty('BDAY', $input); + + $this->assertEquals([$output], $prop->getJsonValue()); + + } + + function dates() { + + return [ + [ + "19961022T140000", + "1996-10-22T14:00:00", + ], + [ + "--1022T1400", + "--10-22T14:00", + ], + [ + "---22T14", + "---22T14", + ], + [ + "19850412", + "1985-04-12", + ], + [ + "1985-04", + "1985-04", + ], + [ + "1985", + "1985", + ], + [ + "--0412", + "--04-12", + ], + [ + "T102200", + "T10:22:00", + ], + [ + "T1022", + "T10:22", + ], + [ + "T10", + "T10", + ], + [ + "T-2200", + "T-22:00", + ], + [ + "T102200Z", + "T10:22:00Z", + ], + [ + "T102200-0800", + "T10:22:00-0800", + ], + [ + "T--00", + "T--00", + ], + ]; + + } + + function testSetParts() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts([ + new \DateTime('2014-04-02 18:37:00') + ]); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + function testSetPartsDateTimeImmutable() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts([ + new \DateTimeImmutable('2014-04-02 18:37:00') + ]); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testSetPartsTooMany() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts([ + 1, + 2 + ]); + + } + + function testSetPartsString() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts([ + "20140402T183700Z" + ]); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + function testSetValueDateTime() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00') + ); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + function testSetValueDateTimeImmutable() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTimeImmutable('2014-04-02 18:37:00') + ); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + function testSetDateTimeOffset() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')) + ); + + $this->assertEquals('20140402T183700-0400', $prop->getValue()); + + } + + function testGetDateTime() { + + $datetime = new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime); + + $dt = $prop->getDateTime(); + $this->assertEquals('2014-04-02T18:37:00-04:00', $dt->format('c'), "For some reason this one failed. Current default timezone is: " . date_default_timezone_get()); + + } + + function testGetDate() { + + $datetime = new \DateTime('2014-04-02'); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime, null, 'DATE'); + + $this->assertEquals('DATE', $prop->getValueType()); + $this->assertEquals('BDAY:20140402', rtrim($prop->serialize())); + + } + + function testGetDateIncomplete() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + function testGetDateIncompleteFromVCard() { + + $vcard = <<BDAY; + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + function testValidate() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals([], $prop->validate()); + + } + + function testValidateBroken() { + + $datetime = '123'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals([[ + 'level' => 3, + 'message' => 'The supplied value (123) is not a correct DATE-AND-OR-TIME property', + 'node' => $prop, + ]], $prop->validate()); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php new file mode 100644 index 000000000000..b5a905277b70 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php @@ -0,0 +1,49 @@ +parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + + $this->assertEquals('nl', $result->LANG->getValue()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + + function testChangeAndSerialize() { + + $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n"; + $mimeDir = new VObject\Parser\MimeDir($input); + + $result = $mimeDir->parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + // This replicates what the vcard converter does and triggered a bug in + // the past. + $result->LANG->setValue(['de']); + + $this->assertEquals('de', $result->LANG->getValue()); + + $expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:de\r\nEND:VCARD\r\n"; + $this->assertEquals( + $expected, + $result->serialize() + ); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/PropertyTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/PropertyTest.php new file mode 100644 index 000000000000..6b848cd1cccd --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/PropertyTest.php @@ -0,0 +1,416 @@ +createProperty('propname', 'propvalue'); + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('propvalue', $property->__toString()); + $this->assertEquals('propvalue', (string)$property); + $this->assertEquals('propvalue', $property->getValue()); + + } + + function testCreate() { + + $cal = new VCalendar(); + + $params = [ + 'param1' => 'value1', + 'param2' => 'value2', + ]; + + $property = $cal->createProperty('propname', 'propvalue', $params); + + $this->assertEquals('value1', $property['param1']->getValue()); + $this->assertEquals('value2', $property['param2']->getValue()); + + } + + function testSetValue() { + + $cal = new VCalendar(); + + $property = $cal->createProperty('propname', 'propvalue'); + $property->setValue('value2'); + + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('value2', $property->__toString()); + + } + + function testParameterExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertTrue(isset($property['PARAMNAME'])); + $this->assertTrue(isset($property['paramname'])); + $this->assertFalse(isset($property['foo'])); + + } + + function testParameterGet() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property['paramname']); + + } + + function testParameterNotExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInternalType('null', $property['foo']); + + } + + function testParameterMultiple() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + $property->add('paramname', 'paramvalue'); + + $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property['paramname']); + $this->assertEquals(2, count($property['paramname']->getParts())); + + } + + function testSetParameterAsString() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertEquals(1, count($property->parameters())); + $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']); + $this->assertEquals('PARAMNAME', $property->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $property->parameters['PARAMNAME']->getValue()); + + } + + function testUnsetParameter() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $property['paramname'] = 'paramvalue'; + + unset($property['PARAMNAME']); + $this->assertEquals(0, count($property->parameters())); + + } + + function testSerialize() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + + $this->assertEquals("PROPNAME:propvalue\r\n", $property->serialize()); + + } + + function testSerializeParam() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue', [ + 'paramname' => 'paramvalue', + 'paramname2' => 'paramvalue2', + ]); + + $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n", $property->serialize()); + + } + + function testSerializeNewLine() { + + $cal = new VCalendar(); + $property = $cal->createProperty('SUMMARY', "line1\nline2"); + + $this->assertEquals("SUMMARY:line1\\nline2\r\n", $property->serialize()); + + } + + function testSerializeLongLine() { + + $cal = new VCalendar(); + $value = str_repeat('!', 200); + $property = $cal->createProperty('propname', $value); + + $expected = "PROPNAME:" . str_repeat('!', 66) . "\r\n " . str_repeat('!', 74) . "\r\n " . str_repeat('!', 60) . "\r\n"; + + $this->assertEquals($expected, $property->serialize()); + + } + + function testSerializeUTF8LineFold() { + + $cal = new VCalendar(); + $value = str_repeat('!', 65) . "\xc3\xa4bla" . str_repeat('!', 142) . "\xc3\xa4foo"; // inserted umlaut-a + $property = $cal->createProperty('propname', $value); + + // PROPNAME:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ("PROPNAME:" + 65x"!" = 74 bytes) + // äbla!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! (" äbla" + 69x"!" = 75 bytes) + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! (" " + 73x"!" = 74 bytes) + // äfoo + $expected = "PROPNAME:" . str_repeat('!', 65) . "\r\n \xc3\xa4bla" . str_repeat('!', 69) . "\r\n " . str_repeat('!', 73) . "\r\n \xc3\xa4foo\r\n"; + $this->assertEquals($expected, $property->serialize()); + + } + + function testGetIterator() { + + $cal = new VCalendar(); + $it = new ElementList([]); + $property = $cal->createProperty('propname', 'propvalue'); + $property->setIterator($it); + $this->assertEquals($it, $property->getIterator()); + + } + + + function testGetIteratorDefault() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname', 'propvalue'); + $it = $property->getIterator(); + $this->assertTrue($it instanceof ElementList); + $this->assertEquals(1, count($it)); + + } + + function testAddScalar() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL'); + + $property->add('myparam', 'value'); + + $this->assertEquals(1, count($property->parameters())); + + $this->assertTrue($property->parameters['MYPARAM'] instanceof Parameter); + $this->assertEquals('MYPARAM', $property->parameters['MYPARAM']->name); + $this->assertEquals('value', $property->parameters['MYPARAM']->getValue()); + + } + + function testAddParameter() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM', 'value'); + + $this->assertEquals(1, count($prop->parameters())); + $this->assertEquals('MYPARAM', $prop['myparam']->name); + + } + + function testAddParameterTwice() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM', 'value1'); + $prop->add('MYPARAM', 'value2'); + + $this->assertEquals(1, count($prop->parameters)); + $this->assertEquals(2, count($prop->parameters['MYPARAM']->getParts())); + + $this->assertEquals('MYPARAM', $prop['MYPARAM']->name); + + } + + + function testClone() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL', 'value'); + $property['FOO'] = 'BAR'; + + $property2 = clone $property; + + $property['FOO'] = 'BAZ'; + $this->assertEquals('BAR', (string)$property2['FOO']); + + } + + function testCreateParams() { + + $cal = new VCalendar(); + $property = $cal->createProperty('X-PROP', 'value', [ + 'param1' => 'value1', + 'param2' => ['value2', 'value3'] + ]); + + $this->assertEquals(1, count($property['PARAM1']->getParts())); + $this->assertEquals(2, count($property['PARAM2']->getParts())); + + } + + function testValidateNonUTF8() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', "Bla\x00"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x00)', $result[0]['message']); + $this->assertEquals('Bla', $property->getValue()); + + } + + function testValidateControlChars() { + + $s = "chars["; + foreach ([ + 0x7F, 0x5E, 0x5C, 0x3B, 0x3A, 0x2C, 0x22, 0x20, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + ] as $c) { + $s .= sprintf('%02X(%c)', $c, $c); + } + $s .= "]end"; + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', $s); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x7f)', $result[0]['message']); + $this->assertEquals("chars[7F()5E(^)5C(\\\\)3B(\\;)3A(:)2C(\\,)22(\")20( )1F()1E()1D()1C()1B()1A()19()18()17()16()15()14()13()12()11()10()0F()0E()0D()0C()0B()0A(\\n)09(\t)08()07()06()05()04()03()02()01()00()]end", $property->getRawMimeDirValue()); + + } + + function testValidateBadPropertyName() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X_*&PROP*", "Bla"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals($result[0]['message'], 'The propertyname: X_*&PROP* contains invalid characters. Only A-Z, 0-9 and - are allowed'); + $this->assertEquals('X-PROP', $property->name); + + } + + function testGetValue() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("SUMMARY", null); + $this->assertEquals([], $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue([]); + $this->assertEquals([], $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue([1]); + $this->assertEquals([1], $property->getParts()); + $this->assertEquals(1, $property->getValue()); + + $property->setValue([1, 2]); + $this->assertEquals([1, 2], $property->getParts()); + $this->assertEquals('1,2', $property->getValue()); + + $property->setValue('str'); + $this->assertEquals(['str'], $property->getParts()); + $this->assertEquals('str', $property->getValue()); + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessSetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + $calendar->{'X-PROP'}[0] = 'Something!'; + + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessUnsetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + unset($calendar->{'X-PROP'}[0]); + + } + + function testValidateBadEncoding() { + + $document = new VCalendar(); + $property = $document->add('X-FOO', 'value'); + $property['ENCODING'] = 'invalid'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=INVALID is not valid for this document type.', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + + } + + function testValidateBadEncodingVCard4() { + + $document = new VCard(['VERSION' => '4.0']); + $property = $document->add('X-FOO', 'value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING parameter is not valid in vCard 4.', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + + } + + function testValidateBadEncodingVCard3() { + + $document = new VCard(['VERSION' => '3.0']); + $property = $document->add('X-FOO', 'value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=BASE64 is not valid for this document type.', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + + } + + function testValidateBadEncodingVCard21() { + + $document = new VCard(['VERSION' => '2.1']); + $property = $document->add('X-FOO', 'value'); + $property['ENCODING'] = 'B'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']); + $this->assertEquals(3, $result[0]['level']); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/ReaderTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/ReaderTest.php new file mode 100644 index 000000000000..8eb349d61de5 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/ReaderTest.php @@ -0,0 +1,493 @@ +assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + function testReadStream() { + + $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR"; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + + $result = Reader::read($stream); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + function testReadComponentUnixNewLine() { + + $data = "BEGIN:VCALENDAR\nEND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + function testReadComponentLineFold() { + + $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptComponent() { + + $data = "BEGIN:VCALENDAR\r\nEND:FOO"; + + $result = Reader::read($data); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptSubComponent() { + + $data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR"; + + $result = Reader::read($data); + + } + + function testReadProperty() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + } + + function testReadPropertyWithNewLine() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:Line1\\nLine2\\NLine3\\\\Not the 4th line!\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue()); + + } + + function testReadMappedProperty() { + + $data = "BEGIN:VCALENDAR\r\nDTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + function testReadMappedPropertyGrouped() { + + $data = "BEGIN:VCALENDAR\r\nfoo.DTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadBrokenLine() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue"; + $result = Reader::read($data); + + } + + function testReadPropertyInComponent() { + + $data = [ + "BEGIN:VCALENDAR", + "PROPNAME:propValue", + "END:VCALENDAR" + ]; + + $result = Reader::read(implode("\r\n", $data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children()[0]); + $this->assertEquals('PROPNAME', $result->children()[0]->name); + $this->assertEquals('propValue', $result->children()[0]->getValue()); + + } + + function testReadNestedComponent() { + + $data = [ + "BEGIN:VCALENDAR", + "BEGIN:VTIMEZONE", + "BEGIN:DAYLIGHT", + "END:DAYLIGHT", + "END:VTIMEZONE", + "END:VCALENDAR" + ]; + + $result = Reader::read(implode("\r\n", $data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children()[0]); + $this->assertEquals('VTIMEZONE', $result->children()[0]->name); + $this->assertEquals(1, count($result->children()[0]->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children()[0]->children()[0]); + $this->assertEquals('DAYLIGHT', $result->children()[0]->children()[0]->name); + + + } + + function testReadPropertyParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyRepeatingParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;N=1;N=2;N=3,4;N=\"5\",6;N=\"7,8\";N=9,10;N=^'11^':propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('N', $result->parameters['N']->name); + $this->assertEquals('1,2,3,4,5,6,7,8,9,10,"11"', $result->parameters['N']->getValue()); + $this->assertEquals([1, 2, 3, 4, 5, 6, "7,8", 9, 10, '"11"'], $result->parameters['N']->getParts()); + + } + + function testReadPropertyRepeatingNamelessGuessedParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;WORK;VOICE;PREF:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertEquals('WORK,VOICE,PREF', $result->parameters['TYPE']->getValue()); + $this->assertEquals(['WORK', 'VOICE', 'PREF'], $result->parameters['TYPE']->getParts()); + + } + + function testReadPropertyNoName() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PRODIGY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertTrue($result->parameters['TYPE']->noName); + $this->assertEquals('PRODIGY', $result->parameters['TYPE']); + + } + + function testReadPropertyParameterExtraColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue:anotherrandomstring', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadProperty2Parameters() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(2, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + $this->assertEquals('PARAMNAME2', $result->parameters['PARAMNAME2']->name); + $this->assertEquals('paramvalue2', $result->parameters['PARAMNAME2']->getValue()); + + } + + function testReadPropertyParameterQuoted() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"paramvalue\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterNewLines() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue1^nvalue2^^nvalue3:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals("paramvalue1\nvalue2^nvalue3", $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterQuotedColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"param:value\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('param:value', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadForgiving() { + + $data = [ + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR" + ]; + + $caught = false; + try { + $result = Reader::read(implode("\r\n", $data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n", $data), Reader::OPTION_FORGIVING); + + $expected = implode("\r\n", [ + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR", + "" + ]); + + $this->assertEquals($expected, $result->serialize()); + + } + + function testReadWithInvalidLine() { + + $data = [ + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "Yes, we've actually seen a file with non-idented property values on multiple lines", + "END:VCALENDAR" + ]; + + $caught = false; + try { + $result = Reader::read(implode("\r\n", $data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n", $data), Reader::OPTION_IGNORE_INVALID_LINES); + + $expected = implode("\r\n", [ + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "END:VCALENDAR", + "" + ]); + + $this->assertEquals($expected, $result->serialize()); + + } + + /** + * Reported as Issue 32. + * + * @expectedException \Sabre\VObject\ParseException + */ + function testReadIncompleteFile() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + function testReadXMLComponent() { + + $data = << + + + + +XML; + + $result = Reader::readXML($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + + function testReadXMLStream() { + + $data = << + + + + +XML; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + + $result = Reader::readXML($stream); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children())); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php new file mode 100644 index 000000000000..27f7b3fcfb1c --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = [ + "20130929T160000Z", + "20131006T160000Z", + "20131013T160000Z", + "20131020T160000Z", + "20131027T160000Z", + "20140907T160000Z" + ]; + + $this->assertEquals($expectedDates, $dates, 'Recursed dates are restricted by month'); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php new file mode 100644 index 000000000000..00f9c993ad22 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php @@ -0,0 +1,61 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = [ + "20150101T160000Z", + "20150122T160000Z", + "20150219T160000Z", + "20150319T160000Z", + "20150423T150000Z", + "20150521T150000Z", + "20150618T150000Z", + "20150723T150000Z", + "20150820T150000Z", + "20150917T150000Z", + "20151022T150000Z", + "20151119T160000Z", + "20151224T160000Z", + ]; + + $this->assertEquals($expectedDates, $dates); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php new file mode 100644 index 000000000000..12fd545e61a2 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php @@ -0,0 +1,123 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31')); + $output = <<assertVObjectEqualsVObject($output, $vcal); + + } + + function testExpandWithReferenceTimezone() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand( + new DateTime('2015-01-01'), + new DateTime('2015-01-31'), + new DateTimeZone('Europe/Berlin') + ); + + $output = <<assertVObjectEqualsVObject($output, $vcal); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php new file mode 100644 index 000000000000..de5e7a05089b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php @@ -0,0 +1,55 @@ +VEVENT->UID); + + while ($it->valid()) { + $it->next(); + } + + // If we got here, it means we were successful. The bug that was in the + // system before would fail on the 5th tuesday of the month, if the 5th + // tuesday did not exist. + $this->assertTrue(true); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php new file mode 100644 index 000000000000..b8b956227d20 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/HandleRDateExpandTest.php @@ -0,0 +1,61 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-12-01')); + + $result = iterator_to_array($vcal->VEVENT); + + $this->assertEquals(5, count($result)); + + $utc = new DateTimeZone('UTC'); + $expected = [ + new DateTimeImmutable("2015-10-12", $utc), + new DateTimeImmutable("2015-10-15", $utc), + new DateTimeImmutable("2015-10-17", $utc), + new DateTimeImmutable("2015-10-18", $utc), + new DateTimeImmutable("2015-10-20", $utc), + ]; + + $result = array_map(function($ev) {return $ev->DTSTART->getDateTime();}, $result); + $this->assertEquals($expected, $result); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php new file mode 100644 index 000000000000..5fd1b14a12c4 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php @@ -0,0 +1,63 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01')); + + $output = <<assertVObjectEqualsVObject($output, $vcal); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php new file mode 100644 index 000000000000..0ba7296a51b5 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php @@ -0,0 +1,99 @@ +vcal = new VCalendar(); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastForwardTooFar() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'foobar'; + $ev->DTSTART = '20090420T180000Z'; + $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1'; + + $this->assertFalse($ev->isInTimeRange(new DateTimeImmutable('2012-01-01 12:00:00'), new DateTimeImmutable('3000-01-01 00:00:00'))); + + } + + /** + * Different bug, also likely an infinite loop. + */ + function testYearlyByMonthLoop() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120101T154500'; + $ev->DTSTART['TZID'] = 'Europe/Berlin'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA'; + $ev->DTEND = '20120101T164500'; + $ev->DTEND['TZID'] = 'Europe/Berlin'; + + // This recurrence rule by itself is a yearly rule that should happen + // every february. + // + // The BYDAY part expands this to every day of the month, but the + // BYSETPOS limits this to only the 1st day of the month. Very crazy + // way to specify this, and could have certainly been a lot easier. + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal, 'uuid'); + $it->fastForward(new DateTimeImmutable('2012-01-29 23:00:00', new DateTimeZone('UTC'))); + + $collect = []; + + while ($it->valid()) { + $collect[] = $it->getDtStart(); + if ($it->getDtStart() > new DateTimeImmutable('2013-02-05 22:59:59', new DateTimeZone('UTC'))) { + break; + } + $it->next(); + + } + + $this->assertEquals( + [new DateTimeImmutable('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))], + $collect + ); + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException \Sabre\VObject\InvalidDataException + * @return void + */ + function testZeroInterval() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120824T145700Z'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=0'; + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal, 'uuid'); + $it->fastForward(new DateTimeImmutable('2013-01-01 23:00:00', new DateTimeZone('UTC'))); + + // if we got this far.. it means we are no longer infinitely looping + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue26Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue26Test.php new file mode 100644 index 000000000000..c9e508a4f060 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue26Test.php @@ -0,0 +1,35 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new EventIterator($vcal, 'bae5d57a98'); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php new file mode 100644 index 000000000000..8170f8bf1f00 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php @@ -0,0 +1,49 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, 'foo'); + + $result = iterator_to_array($it); + + $tz = new DateTimeZone('Europe/Moscow'); + + $expected = [ + new DateTimeImmutable('2013-07-10 11:00:00', $tz), + new DateTimeImmutable('2013-07-12 11:00:00', $tz), + new DateTimeImmutable('2013-07-13 11:00:00', $tz), + ]; + + $this->assertEquals($expected, $result); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php new file mode 100644 index 000000000000..af7a00bc0960 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php @@ -0,0 +1,128 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724'); + + $result = []; + foreach ($it as $instance) { + + $result[] = $instance; + + } + + $tz = new DateTimeZone('Europe/Brussels'); + + $this->assertEquals([ + new DateTimeImmutable('2013-07-15 09:00:00', $tz), + new DateTimeImmutable('2013-07-16 07:00:00', $tz), + new DateTimeImmutable('2013-07-17 07:00:00', $tz), + new DateTimeImmutable('2013-07-18 09:00:00', $tz), + new DateTimeImmutable('2013-07-19 07:00:00', $tz), + ], $result); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php new file mode 100644 index 000000000000..25298e90d7a9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php @@ -0,0 +1,1453 @@ +createComponent('VEVENT'); + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07')); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $this->assertTrue($it->isInfinite()); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + * @depends testValues + */ + function testInvalidFreq() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $ev->UID = 'foo'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarNoUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarInvalidUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal, 'foo'); + + } + + /** + * @depends testValues + */ + function testHourly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal, $ev->UID); + + // Max is to prevent overflow + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07 12:00:00', $tz), + new DateTimeImmutable('2011-10-07 15:00:00', $tz), + new DateTimeImmutable('2011-10-07 18:00:00', $tz), + new DateTimeImmutable('2011-10-07 21:00:00', $tz), + new DateTimeImmutable('2011-10-08 00:00:00', $tz), + new DateTimeImmutable('2011-10-08 03:00:00', $tz), + new DateTimeImmutable('2011-10-08 06:00:00', $tz), + new DateTimeImmutable('2011-10-08 09:00:00', $tz), + new DateTimeImmutable('2011-10-08 12:00:00', $tz), + new DateTimeImmutable('2011-10-08 15:00:00', $tz), + new DateTimeImmutable('2011-10-08 18:00:00', $tz), + new DateTimeImmutable('2011-10-08 21:00:00', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testDaily() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, $ev->UID); + + // Max is to prevent overflow + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2011-10-10', $tz), + new DateTimeImmutable('2011-10-13', $tz), + new DateTimeImmutable('2011-10-16', $tz), + new DateTimeImmutable('2011-10-19', $tz), + new DateTimeImmutable('2011-10-22', $tz), + new DateTimeImmutable('2011-10-25', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testNoRRULE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, $ev->UID); + + // Max is to prevent overflow + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-08 06:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-08 06:00:00', $tz), + new DateTimeImmutable('2011-10-08 07:00:00', $tz), + new DateTimeImmutable('2011-10-09 06:00:00', $tz), + new DateTimeImmutable('2011-10-09 07:00:00', $tz), + new DateTimeImmutable('2011-10-15 06:00:00', $tz), + new DateTimeImmutable('2011-10-15 07:00:00', $tz), + new DateTimeImmutable('2011-10-16 06:00:00', $tz), + new DateTimeImmutable('2011-10-16 07:00:00', $tz), + new DateTimeImmutable('2011-10-22 06:00:00', $tz), + new DateTimeImmutable('2011-10-22 07:00:00', $tz), + new DateTimeImmutable('2011-10-23 06:00:00', $tz), + new DateTimeImmutable('2011-10-23 07:00:00', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2012-10-11 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2012-10-11 12:00:00', $tz), + new DateTimeImmutable('2012-10-11 13:00:00', $tz), + new DateTimeImmutable('2012-10-11 14:00:00', $tz), + new DateTimeImmutable('2012-10-11 15:00:00', $tz), + new DateTimeImmutable('2012-10-13 10:00:00', $tz), + new DateTimeImmutable('2012-10-13 11:00:00', $tz), + new DateTimeImmutable('2012-10-13 12:00:00', $tz), + new DateTimeImmutable('2012-10-13 13:00:00', $tz), + new DateTimeImmutable('2012-10-13 14:00:00', $tz), + new DateTimeImmutable('2012-10-13 15:00:00', $tz), + new DateTimeImmutable('2012-10-15 10:00:00', $tz), + new DateTimeImmutable('2012-10-15 11:00:00', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2011-10-11', $tz), + new DateTimeImmutable('2011-10-19', $tz), + new DateTimeImmutable('2011-10-21', $tz), + new DateTimeImmutable('2011-10-25', $tz), + new DateTimeImmutable('2011-11-02', $tz), + new DateTimeImmutable('2011-11-04', $tz), + new DateTimeImmutable('2011-11-08', $tz), + new DateTimeImmutable('2011-11-16', $tz), + new DateTimeImmutable('2011-11-18', $tz), + new DateTimeImmutable('2011-11-22', $tz), + new DateTimeImmutable('2011-11-30', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testWeekly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Max is to prevent overflow + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2011-10-21', $tz), + new DateTimeImmutable('2011-11-04', $tz), + new DateTimeImmutable('2011-11-18', $tz), + new DateTimeImmutable('2011-12-02', $tz), + new DateTimeImmutable('2011-12-16', $tz), + new DateTimeImmutable('2011-12-30', $tz), + new DateTimeImmutable('2012-01-13', $tz), + new DateTimeImmutable('2012-01-27', $tz), + new DateTimeImmutable('2012-02-10', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 08:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 15; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07 08:00:00', $tz), + new DateTimeImmutable('2011-10-07 09:00:00', $tz), + new DateTimeImmutable('2011-10-07 10:00:00', $tz), + new DateTimeImmutable('2011-10-18 08:00:00', $tz), + new DateTimeImmutable('2011-10-18 09:00:00', $tz), + new DateTimeImmutable('2011-10-18 10:00:00', $tz), + new DateTimeImmutable('2011-10-19 08:00:00', $tz), + new DateTimeImmutable('2011-10-19 09:00:00', $tz), + new DateTimeImmutable('2011-10-19 10:00:00', $tz), + new DateTimeImmutable('2011-10-21 08:00:00', $tz), + new DateTimeImmutable('2011-10-21 09:00:00', $tz), + new DateTimeImmutable('2011-10-21 10:00:00', $tz), + new DateTimeImmutable('2011-11-01 08:00:00', $tz), + new DateTimeImmutable('2011-11-01 09:00:00', $tz), + new DateTimeImmutable('2011-11-01 10:00:00', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDaySpecificHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07 18:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07 18:00:00', $tz), + new DateTimeImmutable('2011-10-18 18:00:00', $tz), + new DateTimeImmutable('2011-10-19 18:00:00', $tz), + new DateTimeImmutable('2011-10-21 18:00:00', $tz), + new DateTimeImmutable('2011-11-01 18:00:00', $tz), + new DateTimeImmutable('2011-11-02 18:00:00', $tz), + new DateTimeImmutable('2011-11-04 18:00:00', $tz), + new DateTimeImmutable('2011-11-15 18:00:00', $tz), + new DateTimeImmutable('2011-11-16 18:00:00', $tz), + new DateTimeImmutable('2011-11-18 18:00:00', $tz), + new DateTimeImmutable('2011-11-29 18:00:00', $tz), + new DateTimeImmutable('2011-11-30 18:00:00', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // Grabbing the next 12 items + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2011-10-18', $tz), + new DateTimeImmutable('2011-10-19', $tz), + new DateTimeImmutable('2011-10-21', $tz), + new DateTimeImmutable('2011-11-01', $tz), + new DateTimeImmutable('2011-11-02', $tz), + new DateTimeImmutable('2011-11-04', $tz), + new DateTimeImmutable('2011-11-15', $tz), + new DateTimeImmutable('2011-11-16', $tz), + new DateTimeImmutable('2011-11-18', $tz), + new DateTimeImmutable('2011-11-29', $tz), + new DateTimeImmutable('2011-11-30', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-12-05', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 14; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-12-05', $tz), + new DateTimeImmutable('2012-03-05', $tz), + new DateTimeImmutable('2012-06-05', $tz), + new DateTimeImmutable('2012-09-05', $tz), + new DateTimeImmutable('2012-12-05', $tz), + ], + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyEndOfMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-12-31', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 14; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-12-31', $tz), + new DateTimeImmutable('2012-08-31', $tz), + new DateTimeImmutable('2012-10-31', $tz), + new DateTimeImmutable('2012-12-31', $tz), + new DateTimeImmutable('2013-08-31', $tz), + new DateTimeImmutable('2013-10-31', $tz), + new DateTimeImmutable('2013-12-31', $tz), + new DateTimeImmutable('2014-08-31', $tz), + new DateTimeImmutable('2014-10-31', $tz), + new DateTimeImmutable('2014-12-31', $tz), + new DateTimeImmutable('2015-08-31', $tz), + new DateTimeImmutable('2015-10-31', $tz), + ], + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 14; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-01-01', $tz), + new DateTimeImmutable('2011-01-25', $tz), + new DateTimeImmutable('2011-01-31', $tz), + new DateTimeImmutable('2011-06-01', $tz), + new DateTimeImmutable('2011-06-24', $tz), + new DateTimeImmutable('2011-11-01', $tz), + new DateTimeImmutable('2011-11-24', $tz), + new DateTimeImmutable('2012-04-01', $tz), + new DateTimeImmutable('2012-04-24', $tz), + ], + $result + ); + + } + + /** + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @depends testValues + * @medium + */ + function testMonthlyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-01-03', $tz), + new DateTimeImmutable('2011-01-05', $tz), + new DateTimeImmutable('2011-01-10', $tz), + new DateTimeImmutable('2011-01-17', $tz), + new DateTimeImmutable('2011-01-18', $tz), + new DateTimeImmutable('2011-01-20', $tz), + new DateTimeImmutable('2011-01-24', $tz), + new DateTimeImmutable('2011-01-31', $tz), + new DateTimeImmutable('2011-03-02', $tz), + new DateTimeImmutable('2011-03-07', $tz), + new DateTimeImmutable('2011-03-14', $tz), + new DateTimeImmutable('2011-03-17', $tz), + new DateTimeImmutable('2011-03-21', $tz), + new DateTimeImmutable('2011-03-22', $tz), + new DateTimeImmutable('2011-03-28', $tz), + new DateTimeImmutable('2011-05-02', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-08-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-08-01', $tz), + new DateTimeImmutable('2012-10-01', $tz), + new DateTimeImmutable('2013-04-01', $tz), + new DateTimeImmutable('2013-07-01', $tz), + new DateTimeImmutable('2014-09-01', $tz), + new DateTimeImmutable('2014-12-01', $tz), + new DateTimeImmutable('2015-06-01', $tz), + new DateTimeImmutable('2016-02-01', $tz), + new DateTimeImmutable('2016-08-01', $tz), + new DateTimeImmutable('2017-05-01', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayBySetPos() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-01-03', $tz), + new DateTimeImmutable('2011-01-31', $tz), + new DateTimeImmutable('2011-02-01', $tz), + new DateTimeImmutable('2011-02-28', $tz), + new DateTimeImmutable('2011-03-01', $tz), + new DateTimeImmutable('2011-03-31', $tz), + new DateTimeImmutable('2011-04-01', $tz), + new DateTimeImmutable('2011-04-29', $tz), + new DateTimeImmutable('2011-05-02', $tz), + new DateTimeImmutable('2011-05-31', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testYearly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-01-01', $tz), + new DateTimeImmutable('2014-01-01', $tz), + new DateTimeImmutable('2017-01-01', $tz), + new DateTimeImmutable('2020-01-01', $tz), + new DateTimeImmutable('2023-01-01', $tz), + new DateTimeImmutable('2026-01-01', $tz), + new DateTimeImmutable('2029-01-01', $tz), + new DateTimeImmutable('2032-01-01', $tz), + new DateTimeImmutable('2035-01-01', $tz), + new DateTimeImmutable('2038-01-01', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyLeapYear() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2012-02-29', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2012-02-29', $tz), + new DateTimeImmutable('2016-02-29', $tz), + new DateTimeImmutable('2020-02-29', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-04-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-04-07', $tz), + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2015-04-07', $tz), + new DateTimeImmutable('2015-10-07', $tz), + new DateTimeImmutable('2019-04-07', $tz), + new DateTimeImmutable('2019-10-07', $tz), + new DateTimeImmutable('2023-04-07', $tz), + new DateTimeImmutable('2023-10-07', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonthByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-04-04', $tz), + new DateTimeImmutable('2011-04-24', $tz), + new DateTimeImmutable('2011-10-03', $tz), + new DateTimeImmutable('2011-10-30', $tz), + new DateTimeImmutable('2016-04-04', $tz), + new DateTimeImmutable('2016-04-24', $tz), + new DateTimeImmutable('2016-10-03', $tz), + new DateTimeImmutable('2016-10-30', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testFastForward() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $it->fastForward(new DateTimeImmutable('2020-05-05', new DateTimeZone('UTC'))); + + $max = 20; + $result = []; + while ($item = $it->current()) { + + $result[] = $item; + $max--; + + if (!$max) break; + $it->next(); + + } + + $this->assertEquals([], $result); + + } + + /** + * @depends testValues + */ + function testFastForwardAllDayEventThatStopAtTheStartTime() { + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY'; + + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-04-04', new DateTimeZone('UTC'))); + $ev->add($dtStart); + + $dtEnd = $vcal->createProperty('DTSTART'); + $dtEnd->setDateTime(new DateTimeImmutable('2011-04-05', new DateTimeZone('UTC'))); + $ev->add($dtEnd); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $it->fastForward(new DateTimeImmutable('2011-04-05T000000', new DateTimeZone('UTC'))); + + $this->assertEquals(new DateTimeImmutable('2011-04-06'), $it->getDTStart()); + } + + /** + * @depends testValues + */ + function testComplexExclusions() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + + $tz = new DateTimeZone('Canada/Eastern'); + $dtStart->setDateTime(new DateTimeImmutable('2011-01-01 13:50:20', $tz)); + + $exDate1 = $vcal->createProperty('EXDATE'); + $exDate1->setDateTimes([new DateTimeImmutable('2012-01-01 13:50:20', $tz), new DateTimeImmutable('2014-01-01 13:50:20', $tz)]); + $exDate2 = $vcal->createProperty('EXDATE'); + $exDate2->setDateTimes([new DateTimeImmutable('2016-01-01 13:50:20', $tz)]); + + $ev->add($dtStart); + $ev->add($exDate1); + $ev->add($exDate2); + + $vcal->add($ev); + + $it = new EventIterator($vcal, (string)$ev->UID); + + $max = 20; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $this->assertEquals( + [ + new DateTimeImmutable('2011-01-01 13:50:20', $tz), + new DateTimeImmutable('2013-01-01 13:50:20', $tz), + new DateTimeImmutable('2015-01-01 13:50:20', $tz), + new DateTimeImmutable('2017-01-01 13:50:20', $tz), + new DateTimeImmutable('2018-01-01 13:50:20', $tz), + new DateTimeImmutable('2019-01-01 13:50:20', $tz), + new DateTimeImmutable('2020-01-01 13:50:20', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + */ + function testOverridenEvent() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=DAILY;COUNT=10'; + $ev1->DTSTART = '20120107T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal, 'overridden'); + + $dates = []; + $summaries = []; + while ($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals([ + new DateTimeImmutable('2012-01-07 12:00:00', $tz), + new DateTimeImmutable('2012-01-08 12:00:00', $tz), + new DateTimeImmutable('2012-01-09 12:00:00', $tz), + new DateTimeImmutable('2012-01-10 14:00:00', $tz), + new DateTimeImmutable('2012-01-11 12:00:00', $tz), + new DateTimeImmutable('2012-01-12 12:00:00', $tz), + new DateTimeImmutable('2012-01-14 12:00:00', $tz), + new DateTimeImmutable('2012-01-15 12:00:00', $tz), + new DateTimeImmutable('2012-01-15 14:00:00', $tz), + new DateTimeImmutable('2012-01-16 12:00:00', $tz), + ], $dates); + + $this->assertEquals([ + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 2', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 3', + 'baseEvent', + ], $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEvent2() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120112T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120119T120000Z'; + $ev2->DTSTART = '20120113T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal, 'overridden'); + + $dates = []; + $summaries = []; + while ($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals([ + new DateTimeImmutable('2012-01-12 12:00:00', $tz), + new DateTimeImmutable('2012-01-13 12:00:00', $tz), + new DateTimeImmutable('2012-01-26 12:00:00', $tz), + + ], $dates); + + $this->assertEquals([ + 'baseEvent', + 'Override!', + 'baseEvent', + ], $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEventNoValuesExpected() { + + $vcal = new VCalendar(); + $ev1 = $vcal->createComponent('VEVENT'); + + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120124T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120131T120000Z'; + $ev2->DTSTART = '20120125T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal, 'overridden'); + + $dates = []; + $summaries = []; + + // The reported problem was specifically related to the VCALENDAR + // expansion. In this parcitular case, we had to forward to the 28th of + // january. + $it->fastForward(new DateTimeImmutable('2012-01-28 23:00:00')); + + // We stop the loop when it hits the 6th of februari. Normally this + // iterator would hit 24, 25 (overriden from 31) and 7 feb but because + // we 'filter' from the 28th till the 6th, we should get 0 results. + while ($it->valid() && $it->getDTStart() < new DateTimeImmutable('2012-02-06 23:00:00')) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $this->assertEquals([], $dates); + $this->assertEquals([], $summaries); + + } + + /** + * @depends testValues + */ + function testRDATE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RDATE = [ + new DateTimeImmutable('2014-08-07', new DateTimeZone('UTC')), + new DateTimeImmutable('2014-08-08', new DateTimeZone('UTC')), + ]; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTimeImmutable('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal, $ev->UID); + + // Max is to prevent overflow + $max = 12; + $result = []; + foreach ($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + [ + new DateTimeImmutable('2011-10-07', $tz), + new DateTimeImmutable('2014-08-07', $tz), + new DateTimeImmutable('2014-08-08', $tz), + ], + $result + ); + + } + + /** + * @depends testValues + * @expectedException \InvalidArgumentException + */ + function testNoMasterBadUID() { + + $vcal = new VCalendar(); + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal, 'broken'); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MaxInstancesTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MaxInstancesTest.php new file mode 100644 index 000000000000..f7a4d7fe6742 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MaxInstancesTest.php @@ -0,0 +1,42 @@ +expand(new DateTime('2014-08-01'), new DateTime('2014-09-01')); + + } finally { + Settings::$maxRecurrences = $temp; + } + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php new file mode 100644 index 000000000000..61cecd023cf0 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php @@ -0,0 +1,63 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal = $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01')); + + $output = <<assertVObjectEqualsVObject($output, $vcal); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php new file mode 100644 index 000000000000..77a58e2e1d8e --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php @@ -0,0 +1,40 @@ +assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new EventIterator($vcal, 'foo'); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php new file mode 100644 index 000000000000..58b27ba26969 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php @@ -0,0 +1,122 @@ +expand(new DateTime('2014-08-01'), new DateTime('2014-09-01')); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $vcal + ); + + + } + + function testRemoveFirstEvent() { + + $input = <<expand(new DateTime('2014-08-01'), new DateTime('2014-08-19')); + + $expected = <<assertVObjectEqualsVObject( + $expected, + $vcal + ); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/SameDateForRecurringEventsTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/SameDateForRecurringEventsTest.php new file mode 100644 index 000000000000..b4809c9f4514 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/SameDateForRecurringEventsTest.php @@ -0,0 +1,56 @@ +getComponents()); + + $this->assertEquals(4, iterator_count($eventIterator), 'in ICS 4 events'); + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php new file mode 100644 index 000000000000..d79fc931f263 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php @@ -0,0 +1,79 @@ +assertEquals( + $expected, + iterator_to_array($it) + ); + + $this->assertFalse($it->isInfinite()); + + } + + function testTimezone() { + + $tz = new DateTimeZone('Europe/Berlin'); + $it = new RDateIterator('20140901T000000,20141001T000000', new DateTimeImmutable('2014-08-01 00:00:00', $tz)); + + $expected = [ + new DateTimeImmutable('2014-08-01 00:00:00', $tz), + new DateTimeImmutable('2014-09-01 00:00:00', $tz), + new DateTimeImmutable('2014-10-01 00:00:00', $tz), + ]; + + $this->assertEquals( + $expected, + iterator_to_array($it) + ); + + + $this->assertFalse($it->isInfinite()); + + } + + + function testFastForward() { + + $utc = new DateTimeZone('UTC'); + $it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTimeImmutable('2014-08-01 00:00:00', $utc)); + + $it->fastForward(new DateTimeImmutable('2014-08-15 00:00:00')); + + $result = []; + while ($it->valid()) { + $result[] = $it->current(); + $it->next(); + } + + $expected = [ + new DateTimeImmutable('2014-09-01 00:00:00', $utc), + new DateTimeImmutable('2014-10-01 00:00:00', $utc), + ]; + + $this->assertEquals( + $expected, + $result + ); + + $this->assertFalse($it->isInfinite()); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php new file mode 100644 index 000000000000..20a12de8c34b --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php @@ -0,0 +1,1015 @@ +parse( + 'FREQ=HOURLY;INTERVAL=3;COUNT=12', + '2011-10-07 12:00:00', + [ + '2011-10-07 12:00:00', + '2011-10-07 15:00:00', + '2011-10-07 18:00:00', + '2011-10-07 21:00:00', + '2011-10-08 00:00:00', + '2011-10-08 03:00:00', + '2011-10-08 06:00:00', + '2011-10-08 09:00:00', + '2011-10-08 12:00:00', + '2011-10-08 15:00:00', + '2011-10-08 18:00:00', + '2011-10-08 21:00:00', + ] + ); + + } + + function testDaily() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + [ + '2011-10-07 00:00:00', + '2011-10-10 00:00:00', + '2011-10-13 00:00:00', + '2011-10-16 00:00:00', + '2011-10-19 00:00:00', + '2011-10-22 00:00:00', + '2011-10-25 00:00:00', + ] + ); + + } + + function testDailyByDayByHour() { + + $this->parse( + 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7', + '2011-10-08 06:00:00', + [ + '2011-10-08 06:00:00', + '2011-10-08 07:00:00', + '2011-10-09 06:00:00', + '2011-10-09 07:00:00', + '2011-10-15 06:00:00', + '2011-10-15 07:00:00', + '2011-10-16 06:00:00', + '2011-10-16 07:00:00', + '2011-10-22 06:00:00', + '2011-10-22 07:00:00', + '2011-10-23 06:00:00', + '2011-10-23 07:00:00', + ] + ); + + } + + function testDailyByHour() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15', + '2012-10-11 12:00:00', + [ + '2012-10-11 12:00:00', + '2012-10-11 13:00:00', + '2012-10-11 14:00:00', + '2012-10-11 15:00:00', + '2012-10-13 10:00:00', + '2012-10-13 11:00:00', + '2012-10-13 12:00:00', + '2012-10-13 13:00:00', + '2012-10-13 14:00:00', + '2012-10-13 15:00:00', + '2012-10-15 10:00:00', + '2012-10-15 11:00:00', + ] + ); + + } + + function testDailyByDay() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR', + '2011-10-07 12:00:00', + [ + '2011-10-07 12:00:00', + '2011-10-11 12:00:00', + '2011-10-19 12:00:00', + '2011-10-21 12:00:00', + '2011-10-25 12:00:00', + '2011-11-02 12:00:00', + '2011-11-04 12:00:00', + '2011-11-08 12:00:00', + '2011-11-16 12:00:00', + '2011-11-18 12:00:00', + '2011-11-22 12:00:00', + '2011-11-30 12:00:00', + ] + ); + + } + + function testDailyCount() { + + $this->parse( + 'FREQ=DAILY;COUNT=5', + '2014-08-01 18:03:00', + [ + '2014-08-01 18:03:00', + '2014-08-02 18:03:00', + '2014-08-03 18:03:00', + '2014-08-04 18:03:00', + '2014-08-05 18:03:00', + ] + ); + + } + + function testDailyByMonth() { + + $this->parse( + 'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU', + '2007-10-04 16:00:00', + [ + '2013-09-29 16:00:00', + '2013-10-06 16:00:00', + '2013-10-13 16:00:00', + '2013-10-20 16:00:00', + '2013-10-27 16:00:00', + '2014-09-07 16:00:00' + ], + '2013-09-28' + ); + + } + + function testWeekly() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;COUNT=10', + '2011-10-07 00:00:00', + [ + '2011-10-07 00:00:00', + '2011-10-21 00:00:00', + '2011-11-04 00:00:00', + '2011-11-18 00:00:00', + '2011-12-02 00:00:00', + '2011-12-16 00:00:00', + '2011-12-30 00:00:00', + '2012-01-13 00:00:00', + '2012-01-27 00:00:00', + '2012-02-10 00:00:00', + ] + ); + + } + + function testWeeklyByDay() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA', + '2014-08-01 00:00:00', + [ + '2014-08-01 00:00:00', + '2014-08-04 00:00:00', + '2014-08-11 00:00:00', + '2014-08-18 00:00:00', + ] + ); + + } + + function testWeeklyByDay2() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 00:00:00', + [ + '2011-10-07 00:00:00', + '2011-10-18 00:00:00', + '2011-10-19 00:00:00', + '2011-10-21 00:00:00', + '2011-11-01 00:00:00', + '2011-11-02 00:00:00', + '2011-11-04 00:00:00', + '2011-11-15 00:00:00', + '2011-11-16 00:00:00', + '2011-11-18 00:00:00', + '2011-11-29 00:00:00', + '2011-11-30 00:00:00', + ] + ); + + } + + function testWeeklyByDayByHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10', + '2011-10-07 08:00:00', + [ + '2011-10-07 08:00:00', + '2011-10-07 09:00:00', + '2011-10-07 10:00:00', + '2011-10-18 08:00:00', + '2011-10-18 09:00:00', + '2011-10-18 10:00:00', + '2011-10-19 08:00:00', + '2011-10-19 09:00:00', + '2011-10-19 10:00:00', + '2011-10-21 08:00:00', + '2011-10-21 09:00:00', + '2011-10-21 10:00:00', + '2011-11-01 08:00:00', + '2011-11-01 09:00:00', + '2011-11-01 10:00:00', + ] + ); + + } + + function testWeeklyByDaySpecificHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 18:00:00', + [ + '2011-10-07 18:00:00', + '2011-10-18 18:00:00', + '2011-10-19 18:00:00', + '2011-10-21 18:00:00', + '2011-11-01 18:00:00', + '2011-11-02 18:00:00', + '2011-11-04 18:00:00', + '2011-11-15 18:00:00', + '2011-11-16 18:00:00', + '2011-11-18 18:00:00', + '2011-11-29 18:00:00', + '2011-11-30 18:00:00', + ] + ); + + } + + function testMonthly() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=3;COUNT=5', + '2011-12-05 00:00:00', + [ + '2011-12-05 00:00:00', + '2012-03-05 00:00:00', + '2012-06-05 00:00:00', + '2012-09-05 00:00:00', + '2012-12-05 00:00:00', + ] + ); + + } + + function testMonlthyEndOfMonth() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=12', + '2011-12-31 00:00:00', + [ + '2011-12-31 00:00:00', + '2012-08-31 00:00:00', + '2012-10-31 00:00:00', + '2012-12-31 00:00:00', + '2013-08-31 00:00:00', + '2013-10-31 00:00:00', + '2013-12-31 00:00:00', + '2014-08-31 00:00:00', + '2014-10-31 00:00:00', + '2014-12-31 00:00:00', + '2015-08-31 00:00:00', + '2015-10-31 00:00:00', + ] + ); + + } + + function testMonthlyByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7', + '2011-01-01 00:00:00', + [ + '2011-01-01 00:00:00', + '2011-01-25 00:00:00', + '2011-01-31 00:00:00', + '2011-06-01 00:00:00', + '2011-06-24 00:00:00', + '2011-11-01 00:00:00', + '2011-11-24 00:00:00', + '2012-04-01 00:00:00', + '2012-04-24 00:00:00', + ] + ); + + } + + function testMonthlyByDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH', + '2011-01-03 00:00:00', + [ + '2011-01-03 00:00:00', + '2011-01-05 00:00:00', + '2011-01-10 00:00:00', + '2011-01-17 00:00:00', + '2011-01-18 00:00:00', + '2011-01-20 00:00:00', + '2011-01-24 00:00:00', + '2011-01-31 00:00:00', + '2011-03-02 00:00:00', + '2011-03-07 00:00:00', + '2011-03-14 00:00:00', + '2011-03-17 00:00:00', + '2011-03-21 00:00:00', + '2011-03-22 00:00:00', + '2011-03-28 00:00:00', + '2011-05-02 00:00:00', + ] + ); + + } + + function testMonthlyByDayByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1', + '2011-08-01 00:00:00', + [ + '2011-08-01 00:00:00', + '2012-10-01 00:00:00', + '2013-04-01 00:00:00', + '2013-07-01 00:00:00', + '2014-09-01 00:00:00', + '2014-12-01 00:00:00', + '2015-06-01 00:00:00', + '2016-02-01 00:00:00', + '2016-08-01 00:00:00', + '2017-05-01 00:00:00', + ] + ); + + } + + function testMonthlyByDayBySetPos() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1', + '2011-01-03 00:00:00', + [ + '2011-01-03 00:00:00', + '2011-01-31 00:00:00', + '2011-02-01 00:00:00', + '2011-02-28 00:00:00', + '2011-03-01 00:00:00', + '2011-03-31 00:00:00', + '2011-04-01 00:00:00', + '2011-04-29 00:00:00', + '2011-05-02 00:00:00', + '2011-05-31 00:00:00', + ] + ); + + } + + function testYearly() { + + $this->parse( + 'FREQ=YEARLY;COUNT=10;INTERVAL=3', + '2011-01-01 00:00:00', + [ + '2011-01-01 00:00:00', + '2014-01-01 00:00:00', + '2017-01-01 00:00:00', + '2020-01-01 00:00:00', + '2023-01-01 00:00:00', + '2026-01-01 00:00:00', + '2029-01-01 00:00:00', + '2032-01-01 00:00:00', + '2035-01-01 00:00:00', + '2038-01-01 00:00:00', + ] + ); + } + + function testYearlyLeapYear() { + + $this->parse( + 'FREQ=YEARLY;COUNT=3', + '2012-02-29 00:00:00', + [ + '2012-02-29 00:00:00', + '2016-02-29 00:00:00', + '2020-02-29 00:00:00', + ] + ); + } + + function testYearlyByMonth() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10', + '2011-04-07 00:00:00', + [ + '2011-04-07 00:00:00', + '2011-10-07 00:00:00', + '2015-04-07 00:00:00', + '2015-10-07 00:00:00', + '2019-04-07 00:00:00', + '2019-10-07 00:00:00', + '2023-04-07 00:00:00', + '2023-10-07 00:00:00', + ] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByMonthInvalidValue1() { + + $this->parse( + 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0', + '2011-04-07 00:00:00', + [] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByMonthInvalidValue2() { + + $this->parse( + 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=bla', + '2011-04-07 00:00:00', + [] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByMonthManyInvalidValues() { + + $this->parse( + 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=0,bla', + '2011-04-07 00:00:00', + [] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByMonthEmptyValue() { + + $this->parse( + 'FREQ=YEARLY;COUNT=6;BYMONTHDAY=24;BYMONTH=', + '2011-04-07 00:00:00', + [] + ); + + } + + function testYearlyByMonthByDay() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + [ + '2011-04-04 00:00:00', + '2011-04-24 00:00:00', + '2011-10-03 00:00:00', + '2011-10-30 00:00:00', + '2016-04-04 00:00:00', + '2016-04-24 00:00:00', + '2016-10-03 00:00:00', + '2016-10-30 00:00:00', + ] + ); + + } + + function testYearlyByYearDay() { + + $this->parse( + 'FREQ=YEARLY;COUNT=7;INTERVAL=2;BYYEARDAY=190', + '2011-07-10 03:07:00', + [ + '2011-07-10 03:07:00', + '2013-07-10 03:07:00', + '2015-07-10 03:07:00', + '2017-07-10 03:07:00', + '2019-07-10 03:07:00', + '2021-07-10 03:07:00', + '2023-07-10 03:07:00', + ] + ); + + } + + /* + * Regression test for #383 + * $parser->next() used to cause an infinite loop. + */ + function testYearlyByYearDayImmutable() { + $start = '2011-07-10 03:07:00'; + $rule = 'FREQ=YEARLY;COUNT=7;INTERVAL=2;BYYEARDAY=190'; + $tz = "UTC"; + + $dt = new DateTimeImmutable($start, new DateTimeZone($tz)); + $parser = new RRuleIterator($rule, $dt); + + $parser->next(); + + $item = $parser->current(); + $this->assertEquals($item->format('Y-m-d H:i:s'), '2013-07-10 03:07:00'); + } + + function testYearlyByYearDayMultiple() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=3;BYYEARDAY=190,301', + '2011-07-10 14:53:11', + [ + '2011-07-10 14:53:11', + '2011-10-29 14:53:11', + '2014-07-10 14:53:11', + '2014-10-29 14:53:11', + '2017-07-10 14:53:11', + '2017-10-29 14:53:11', + '2020-07-09 14:53:11', + '2020-10-28 14:53:11', + ] + ); + + } + + function testYearlyByYearDayByDay() { + + $this->parse( + 'FREQ=YEARLY;COUNT=6;BYYEARDAY=97;BYDAY=SA', + '2001-04-07 14:53:11', + [ + '2001-04-07 14:53:11', + '2006-04-08 14:53:11', + '2012-04-07 14:53:11', + '2017-04-08 14:53:11', + '2023-04-08 14:53:11', + '2034-04-08 14:53:11', + ] + ); + + } + + function testYearlyByYearDayNegative() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;BYYEARDAY=-97,-5', + '2001-09-26 14:53:11', + [ + '2001-09-26 14:53:11', + '2001-12-27 14:53:11', + '2002-09-26 14:53:11', + '2002-12-27 14:53:11', + '2003-09-26 14:53:11', + '2003-12-27 14:53:11', + '2004-09-26 14:53:11', + '2004-12-27 14:53:11', + ] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByYearDayInvalid390() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYYEARDAY=390', + '2011-04-07 00:00:00', + [ + ] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testYearlyByYearDayInvalid0() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYYEARDAY=0', + '2011-04-07 00:00:00', + [ + ] + ); + + } + + function testFastForward() { + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + [], + '2020-05-05 00:00:00' + ); + + } + + /** + * The bug that was in the + * system before would fail on the 5th tuesday of the month, if the 5th + * tuesday did not exist. + * + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @medium + */ + function testFifthTuesdayProblem() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU', + '2007-10-04 14:46:42', + [ + '2007-10-04 14:46:42', + ] + ); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastFowardTooFar() { + + $this->parse( + 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1', + '2009-04-20 18:00:00', + [ + '2009-04-20 18:00:00', + '2009-04-27 18:00:00', + '2009-05-04 18:00:00', + '2009-05-11 18:00:00', + '2009-05-18 18:00:00', + '2009-05-25 18:00:00', + '2009-06-01 18:00:00', + '2009-06-08 18:00:00', + '2009-06-15 18:00:00', + '2009-06-22 18:00:00', + '2009-06-29 18:00:00', + ] + ); + + } + + function testValidByWeekNo() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=20;BYDAY=TU', + '2011-02-07 00:00:00', + [ + '2011-02-07 00:00:00', + '2011-05-17 00:00:00', + '2012-05-15 00:00:00', + '2013-05-14 00:00:00', + '2014-05-13 00:00:00', + '2015-05-12 00:00:00', + '2016-05-17 00:00:00', + '2017-05-16 00:00:00', + '2018-05-15 00:00:00', + '2019-05-14 00:00:00', + '2020-05-12 00:00:00', + '2021-05-18 00:00:00', + ] + ); + + } + + function testNegativeValidByWeekNo() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=-20;BYDAY=TU,FR', + '2011-09-02 00:00:00', + [ + '2011-09-02 00:00:00', + '2012-08-07 00:00:00', + '2012-08-10 00:00:00', + '2013-08-06 00:00:00', + '2013-08-09 00:00:00', + '2014-08-05 00:00:00', + '2014-08-08 00:00:00', + '2015-08-11 00:00:00', + '2015-08-14 00:00:00', + '2016-08-09 00:00:00', + '2016-08-12 00:00:00', + '2017-08-08 00:00:00', + ] + ); + + } + + function testTwoValidByWeekNo() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=20;BYDAY=TU,FR', + '2011-09-07 09:00:00', + [ + '2011-09-07 09:00:00', + '2012-05-15 09:00:00', + '2012-05-18 09:00:00', + '2013-05-14 09:00:00', + '2013-05-17 09:00:00', + '2014-05-13 09:00:00', + '2014-05-16 09:00:00', + '2015-05-12 09:00:00', + '2015-05-15 09:00:00', + '2016-05-17 09:00:00', + '2016-05-20 09:00:00', + '2017-05-16 09:00:00', + ] + ); + + } + + function testValidByWeekNoByDayDefault() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=20', + '2011-05-16 00:00:00', + [ + '2011-05-16 00:00:00', + '2012-05-14 00:00:00', + '2013-05-13 00:00:00', + '2014-05-12 00:00:00', + '2015-05-11 00:00:00', + '2016-05-16 00:00:00', + '2017-05-15 00:00:00', + '2018-05-14 00:00:00', + '2019-05-13 00:00:00', + '2020-05-11 00:00:00', + '2021-05-17 00:00:00', + '2022-05-16 00:00:00', + ] + ); + + } + + function testMultipleValidByWeekNo() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=20,50;BYDAY=TU,FR', + '2011-01-16 00:00:00', + [ + '2011-01-16 00:00:00', + '2011-05-17 00:00:00', + '2011-05-20 00:00:00', + '2011-12-13 00:00:00', + '2011-12-16 00:00:00', + '2012-05-15 00:00:00', + '2012-05-18 00:00:00', + '2012-12-11 00:00:00', + '2012-12-14 00:00:00', + '2013-05-14 00:00:00', + '2013-05-17 00:00:00', + '2013-12-10 00:00:00', + ] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testInvalidByWeekNo() { + + $this->parse( + 'FREQ=YEARLY;BYWEEKNO=54', + '2011-05-16 00:00:00', + [ + ] + ); + + } + + /** + * This also at one point caused an infinite loop. We're keeping the test. + */ + function testYearlyByMonthLoop() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA', + '2012-01-01 15:45:00', + [ + '2012-02-01 15:45:00', + ], + '2012-01-29 23:00:00' + ); + + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testZeroInterval() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=0', + '2012-08-24 14:57:00', + [], + '2013-01-01 23:00:00' + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testInvalidFreq() { + + $this->parse( + 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + [] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testByDayBadOffset() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA', + '2014-08-01 00:00:00', + [] + ); + + } + + function testUntilBeginHasTimezone() { + + $this->parse( + 'FREQ=WEEKLY;UNTIL=20131118T183000', + '2013-09-23 18:30:00', + [ + '2013-09-23 18:30:00', + '2013-09-30 18:30:00', + '2013-10-07 18:30:00', + '2013-10-14 18:30:00', + '2013-10-21 18:30:00', + '2013-10-28 18:30:00', + '2013-11-04 18:30:00', + '2013-11-11 18:30:00', + '2013-11-18 18:30:00', + ], + null, + 'America/New_York' + ); + + } + + function testUntilBeforeDtStart() { + + $this->parse( + 'FREQ=DAILY;UNTIL=20140101T000000Z', + '2014-08-02 00:15:00', + [ + '2014-08-02 00:15:00', + ] + ); + + } + + function testIgnoredStuff() { + + $this->parse( + 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2', + '2014-08-02 00:15:00', + [ + '2014-08-02 00:15:00', + '2014-08-03 00:15:00', + ] + ); + + } + + function testMinusFifthThursday() { + + $this->parse( + 'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4', + '2015-01-01 00:15:00', + [ + '2015-01-01 00:15:00', + '2015-01-08 00:15:00', + '2015-02-05 00:15:00', + '2015-03-05 00:15:00' + ] + ); + + } + + /** + * @expectedException \Sabre\VObject\InvalidDataException + */ + function testUnsupportedPart() { + + $this->parse( + 'FREQ=DAILY;BYWODAN=1', + '2014-08-02 00:15:00', + [] + ); + + } + + function testIteratorFunctions() { + + $parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13')); + $parser->next(); + $this->assertEquals( + new DateTime('2014-08-03 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 1, + $parser->key() + ); + + $parser->rewind(); + + $this->assertEquals( + new DateTime('2014-08-02 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 0, + $parser->key() + ); + + } + + function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') { + + $dt = new DateTime($start, new DateTimeZone($tz)); + $parser = new RRuleIterator($rule, $dt); + + if ($fastForward) { + $parser->fastForward(new DateTime($fastForward)); + } + + $result = []; + while ($parser->valid()) { + + $item = $parser->current(); + $result[] = $item->format('Y-m-d H:i:s'); + + if ($parser->isInfinite() && count($result) >= count($expected)) { + break; + } + $parser->next(); + + } + + $this->assertEquals( + $expected, + $result + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics b/libs/composer/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics new file mode 100644 index 000000000000..6b2cccbc49a6 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-TIMEZONE:America/New_York +PRODID:-//www.churchcommunitybuilder.com//Church Community Builder//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Test Event +BEGIN:VTIMEZONE +TZID:America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:10621-1440@ccbchurch.com +DTSTART;TZID=America/New_York:20130923T183000 +DTEND;TZID=America/New_York:20130923T203000 +DTSTAMP:20131216T170211 +RRULE:FREQ=WEEKLY;UNTIL=20131118T183000 +CREATED:20130423T161111 +DESCRIPTION:Test Event ending November 11, 2013 +LAST-MODIFIED:20131126T163428 +SEQUENCE:1387231331 +SUMMARY:Test +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/SlashRTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/SlashRTest.php new file mode 100644 index 000000000000..104504dac8e8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/SlashRTest.php @@ -0,0 +1,22 @@ +add('test', "abc\r\ndef"); + $this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize()); + + } + + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php new file mode 100644 index 000000000000..050cc721e254 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php @@ -0,0 +1,326 @@ +version = VObject\Version::VERSION; + } + + function createStream($data) { + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + return $stream; + + } + + function testICalendarImportValidEvent() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while ($object = $objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertEquals([], VObject\Reader::read($return)->validate()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportWrongType() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportEndOfData() { + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while ($object = $objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertNull($object = $objects->getNext()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportInvalidEvent() { + $data = <<createStream($data); + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportMultipleValidEvents() { + + $event[] = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while ($object = $objects->getNext()) { + + $expected = <<version//EN +CALSCALE:GREGORIAN +$event[$i] +END:VCALENDAR + +EOT; + + $return .= $object->serialize(); + $expected = str_replace("\n", "\r\n", $expected); + $this->assertEquals($expected, $object->serialize()); + $i++; + } + $this->assertEquals([], VObject\Reader::read($return)->validate()); + } + + function testICalendarImportEventWithoutUID() { + + $data = <<version//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART:20140101T040000Z +DTSTAMP:20140122T233226Z +END:VEVENT +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while ($object = $objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + + if ($messages) { + $messages = array_map( + function($item) { return $item['message']; }, + $messages + ); + $this->fail('Validation errors: ' . implode("\n", $messages)); + } else { + $this->assertEquals([], $messages); + } + } + + function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents() { + + $timezones = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while ($object = $objects->getNext()) { + + $expected = <<version//EN +CALSCALE:GREGORIAN +$timezones +$event[$i] +END:VCALENDAR + +EOT; + $expected = str_replace("\n", "\r\n", $expected); + + $this->assertEquals($expected, $object->serialize()); + $return .= $object->serialize(); + $i++; + + } + + $this->assertEquals([], VObject\Reader::read($return)->validate()); + } + + function testICalendarImportWithOutVTIMEZONES() { + + $data = <<createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while ($object = $objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + $this->assertEquals([], $messages); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php new file mode 100644 index 000000000000..67261bcf68e8 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php @@ -0,0 +1,195 @@ +createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + $this->assertEquals(1, $count); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testVCardImportWrongType() { + $event[] = <<createStream($data); + + $splitter = new VCard($tempFile); + + while ($object = $splitter->getNext()) { + } + + } + + function testVCardImportValidVCardsWithCategories() { + $data = <<createStream($data); + + $splitter = new VCard($tempFile); + + $count = 0; + while ($object = $splitter->getNext()) { + $count++; + } + $this->assertEquals(4, $count); + + } + + function testVCardImportEndOfData() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + $object = $objects->getNext(); + + $this->assertNull($objects->getNext()); + + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testVCardImportCheckInvalidArgumentException() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + while ($objects->getNext()) { } + + } + + function testVCardImportMultipleValidVCards() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + + } + + function testImportMultipleSeparatedWithNewLines() { + $data = <<createStream($data); + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + } + + function testVCardImportVCardWithoutUID() { + $data = <<createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + + $this->assertEquals(1, $count); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/StringUtilTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/StringUtilTest.php new file mode 100644 index 000000000000..c2323dd70c84 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/StringUtilTest.php @@ -0,0 +1,57 @@ +assertEquals(false, $string); + + } + + function testIsUTF8() { + + $string = StringUtil::isUTF8('I 💚 SabreDAV'); + + $this->assertEquals(true, $string); + + } + + function testUTF8ControlChar() { + + $string = StringUtil::isUTF8(chr(0x00)); + + $this->assertEquals(false, $string); + + } + + function testConvertToUTF8nonUTF8() { + + $string = StringUtil::convertToUTF8(chr(0xbf)); + + $this->assertEquals(utf8_encode(chr(0xbf)), $string); + + } + + function testConvertToUTF8IsUTF8() { + + $string = StringUtil::convertToUTF8('I 💚 SabreDAV'); + + $this->assertEquals('I 💚 SabreDAV', $string); + + } + + function testConvertToUTF8ControlChar() { + + $string = StringUtil::convertToUTF8(chr(0x00)); + + $this->assertEquals('', $string); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php new file mode 100644 index 000000000000..809d8307fdda --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php @@ -0,0 +1,379 @@ +assertInstanceOf('DateTimeZone', $tz); + } catch (\Exception $e) { + if (strpos($e->getMessage(), "Unknown or bad timezone") !== false) { + $this->markTestSkipped($timezoneName . ' is not (yet) supported in this PHP version. Update pecl/timezonedb'); + } else { + throw $e; + } + + } + + } + + function getMapping() { + + TimeZoneUtil::loadTzMaps(); + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return [$value]; + }, + TimeZoneUtil::$map + ); + + } + + function testExchangeMap() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWetherMicrosoftIsStillInsane() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testUnknownExchangeId() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWindowsTimeZone() { + + $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time'); + $ex = new \DateTimeZone('America/New_York'); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneIdentifiers + */ + function testTimeZoneIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneBCIdentifiers + */ + function testTimeZoneBCIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function getPHPTimeZoneIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return [$value]; + }, + \DateTimeZone::listIdentifiers() + ); + + } + + function getPHPTimeZoneBCIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return [$value]; + }, + TimeZoneUtil::getIdentifiersBC() + ); + + } + + function testTimezoneOffset() { + + $tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true); + + if (version_compare(PHP_VERSION, '5.5.10', '>=') && !defined('HHVM_VERSION')) { + $ex = new \DateTimeZone('-04:00'); + } else { + $ex = new \DateTimeZone('Etc/GMT-4'); + } + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testTimezoneFail() { + + $tz = TimeZoneUtil::getTimeZone('FooBar', null, true); + + } + + function testFallBack() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testLjubljanaBug() { + + $vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + function testWeirdSystemVLICs() { + +$vobj = <<assertEquals($ex->getName(), $tz->getName()); + + } + + + function testPrefixedOffsetExchangeIdentifier() + { + $tz = TimeZoneUtil::getTimeZone('(UTC-05:00) Eastern Time (US & Canada)'); + $ex = new \DateTimeZone('America/New_York'); + $this->assertEquals($ex->getName(), $tz->getName()); + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php new file mode 100644 index 000000000000..382b4d299752 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php @@ -0,0 +1,39 @@ +assertTrue( + UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555') + ); + $this->assertTrue( + UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555') + ); + + } + + /** + * @depends testValidateUUID + */ + function testGetUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID( + UUIDUtil::getUUID() + ) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/VCard21Test.php b/libs/composer/vendor/sabre/vobject/tests/VObject/VCard21Test.php new file mode 100644 index 000000000000..c4ac25780556 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/VCard21Test.php @@ -0,0 +1,54 @@ +serialize(); + + $this->assertEquals($input, $output); + + } + + function testPropertyPadValueCount() { + + $input = <<serialize(); + + $expected = <<assertEquals($expected, $output); + + } +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php new file mode 100644 index 000000000000..f53433786ce9 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php @@ -0,0 +1,535 @@ +convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testConvert40to40() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testConvert21to40() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testConvert30to30() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testConvert40to30() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testConvertGroupCard() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testBDAYConversion() { + + $input = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownSourceVCardVersion() { + + $input = <<convert(Document::VCARD40); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownTargetVCardVersion() { + + $input = <<convert(Document::VCARD21); + + } + + function testConvertIndividualCard() { + + $input = <<convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + $input = $output; + $output = <<convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testAnniversary() { + + $input = <<!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = [ + $output, + $input + ]; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testMultipleAnniversaries() { + + $input = <<!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210 +ITEM2.X-ABLABEL:_$!!$_ +ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210 +ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210 +ITEM3.X-ABLABEL:_$!!$_ +ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = [ + $output, + $input + ]; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjectEqualsVObject( + $output, + $vcard + ); + + } + + function testNoLabel() { + + $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(Document::VCARD40); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $version = Version::VERSION; + + $expected = <<assertEquals($expected, str_replace("\r", "", $vcard)); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/VersionTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/VersionTest.php new file mode 100644 index 000000000000..40ca5faee624 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/VersionTest.php @@ -0,0 +1,16 @@ +assertEquals(-1, version_compare('2.0.0', $v)); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/WriterTest.php b/libs/composer/vendor/sabre/vobject/tests/VObject/WriterTest.php new file mode 100644 index 000000000000..5383071aeed1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/WriterTest.php @@ -0,0 +1,43 @@ +getComponent()); + $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $result); + + } + + function testWriteToJson() { + + $result = Writer::writeJson($this->getComponent()); + $this->assertEquals('["vcalendar",[],[]]', $result); + + } + + function testWriteToXml() { + + $result = Writer::writeXml($this->getComponent()); + $this->assertEquals( + '' . "\n" . + '' . "\n" . + ' ' . "\n" . + '' . "\n", + $result + ); + + } + +} diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/issue153.vcf b/libs/composer/vendor/sabre/vobject/tests/VObject/issue153.vcf new file mode 100644 index 000000000000..5fb0fa297c56 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/issue153.vcf @@ -0,0 +1,352 @@ +BEGIN:VCARD +VERSION:3.0 +N:Benutzer;Test;;; +FN:Test Benutzer +PHOTO;BASE64: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau + m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H + a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii + KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ + BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW + u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn + bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4 + g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci + QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh + UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9 + CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc + u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku + Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP + j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP + OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro + /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU + LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy + 9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl + G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW + QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb + 94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD + 5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+ + dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV + 4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0 + sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW + rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K + rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk + HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD + xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC + yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY + itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN + AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh + dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V + DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A + RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun + 8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg + QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt + pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS + nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu + lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V + 5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF + tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3 + Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs + uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+ + 1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx + sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r + VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP + X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY + 2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm + P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi + yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N + t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk + OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4 + V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish + yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46 + ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW + KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX + e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO + lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY + MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21 + MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy + WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d + 6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ + HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs + HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw + ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa + KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9 + iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8 + Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5 + z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33 + yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4 + NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/ + BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3 + evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP + 4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8 + nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+ + RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi + JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0 + xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA + GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS + P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw + WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+ + 6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6 + 1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf + rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c + VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z + nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m + PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3 + En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4 + wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7 + 3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP + 7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3 + wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G + 00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE + rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg + B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA + 6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw + cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb + juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r + PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t + 7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr + nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD + aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq + /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg + C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA + iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F + h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb + d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC + UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk + XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR + 79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF + jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA + MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA + Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA + +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W + qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE + DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM + jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR + jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI + do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze + MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S + KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn + cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ + JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz + R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR + kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd + 0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb + zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/ + Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf + Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa + AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht + X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp + UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO + 3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK + QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH + HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/ + McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka + 6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi + Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy + MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u + 1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up + YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH + 0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB + 159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA + 7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG + 0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm + gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS + 24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l + GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd + g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34 + x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9 + 8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I + NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ + GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe + DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey + jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN + VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP + uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU + 6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9 + jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt + XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0 + /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr + qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM + 4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM + XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw + NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx + 2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X + 2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU + 65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn + h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+ + OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd + xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh + aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw + o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH + 1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP + O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb + lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ + dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy + 7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi + anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2 + Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y + ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ + LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8 + g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld + x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar + u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV + RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe + 3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz + xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg + eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ + fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6 + XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2 + ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF + c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K + iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU + CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c + 54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc + ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c + OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4 + AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8 + zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn + Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4 + eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9 + cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW + KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21 + 1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi + qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ + q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N + ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG + CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e + lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt + MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6 + qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh + h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv + S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL + KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w + dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z + mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb + AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww + eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC + L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm + xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C + KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG + OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY + gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7 + qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP + mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA + zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR + mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg + pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF + +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu + mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND + bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V + 2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE + 9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9 + QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4 + QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki + RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP + xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW + ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA + bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml + jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk + 1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub + c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr + co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI + gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI + iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG + WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw + tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG + 7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC + SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1 + R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b + AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG + 31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx + obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy + Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA + GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr + csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg + 0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx + bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1 + oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71 + LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j + TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP + HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX + bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x + 0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl + PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC + s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT + LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc + FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09 + 9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW + 56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw + 2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH + wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj + pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I + /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW + UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5 + vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ + bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm + AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5 + 7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW + DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX + TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p + wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws + HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6 + VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt + 6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH + X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ + 7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8 + QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P + BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG + R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6 + zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe + poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD + 4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D + N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG + XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t + yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK + yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb + qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44 + 5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX + +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA + 5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC + CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye + 3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w + EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg + CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68 + d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE + bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC + UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH + qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF + pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H + G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX + cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/ + AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw + aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG + W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa + fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw + vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p + V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma + IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw + EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G + 9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2 + Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6 + ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+ + U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH + 14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr + bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt + 0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw + zbVbk4/OrNpefLsnyyg5UUAf/9k= +END:VCARD diff --git a/libs/composer/vendor/sabre/vobject/tests/VObject/issue64.vcf b/libs/composer/vendor/sabre/vobject/tests/VObject/issue64.vcf new file mode 100644 index 000000000000..611052907a25 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/VObject/issue64.vcf @@ -0,0 +1,351 @@ +BEGIN:VCARD +VERSION:2.1 +PHOTO;ENCODING=BASE64;JPEG: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau + m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H + a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii + KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ + BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW + u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn + bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4 + g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci + QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh + UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9 + CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc + u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku + Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP + j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP + OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro + /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU + LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy + 9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl + G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW + QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb + 94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD + 5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+ + dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV + 4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0 + sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW + rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K + rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk + HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD + xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC + yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY + itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN + AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh + dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V + DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A + RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun + 8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg + QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt + pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS + nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu + lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V + 5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF + tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3 + Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs + uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+ + 1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx + sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r + VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP + X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY + 2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm + P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi + yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N + t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk + OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4 + V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish + yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46 + ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW + KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX + e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO + lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY + MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21 + MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy + WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d + 6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ + HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs + HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw + ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa + KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9 + iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8 + Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5 + z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33 + yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4 + NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/ + BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3 + evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP + 4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8 + nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+ + RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi + JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0 + xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA + GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS + P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw + WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+ + 6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6 + 1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf + rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c + VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z + nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m + PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3 + En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4 + wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7 + 3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP + 7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3 + wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G + 00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE + rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg + B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA + 6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw + cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb + juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r + PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t + 7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr + nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD + aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq + /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg + C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA + iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F + h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb + d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC + UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk + XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR + 79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF + jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA + MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA + Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA + +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W + qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE + DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM + jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR + jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI + do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze + MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S + KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn + cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ + JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz + R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR + kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd + 0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb + zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/ + Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf + Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa + AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht + X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp + UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO + 3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK + QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH + HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/ + McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka + 6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi + Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy + MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u + 1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up + YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH + 0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB + 159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA + 7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG + 0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm + gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS + 24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l + GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd + g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34 + x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9 + 8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I + NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ + GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe + DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey + jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN + VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP + uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU + 6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9 + jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt + XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0 + /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr + qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM + 4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM + XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw + NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx + 2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X + 2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU + 65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn + h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+ + OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd + xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh + aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw + o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH + 1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP + O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb + lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ + dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy + 7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi + anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2 + Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y + ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ + LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8 + g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld + x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar + u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV + RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe + 3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz + xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg + eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ + fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6 + XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2 + ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF + c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K + iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU + CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c + 54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc + ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c + OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4 + AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8 + zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn + Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4 + eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9 + cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW + KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21 + 1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi + qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ + q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N + ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG + CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e + lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt + MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6 + qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh + h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv + S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL + KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w + dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z + mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb + AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww + eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC + L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm + xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C + KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG + OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY + gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7 + qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP + mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA + zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR + mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg + pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF + +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu + mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND + bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V + 2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE + 9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9 + QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4 + QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki + RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP + xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW + ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA + bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml + jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk + 1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub + c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr + co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI + gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI + iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG + WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw + tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG + 7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC + SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1 + R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b + AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG + 31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx + obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy + Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA + GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr + csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg + 0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx + bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1 + oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71 + LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j + TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP + HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX + bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x + 0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl + PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC + s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT + LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc + FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09 + 9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW + 56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw + 2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH + wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj + pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I + /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW + UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5 + vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ + bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm + AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5 + 7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW + DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX + TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p + wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws + HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6 + VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt + 6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH + X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ + 7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8 + QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P + BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG + R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6 + zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe + poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD + 4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D + N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG + XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t + yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK + yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb + qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44 + 5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX + +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA + 5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC + CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye + 3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w + EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg + CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68 + d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE + bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC + UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH + qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF + pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H + G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX + cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/ + AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw + aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG + W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa + fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw + vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p + V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma + IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw + EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G + 9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2 + Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6 + ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+ + U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH + 14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr + bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt + 0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw + zbVbk4/OrNpefLsnyyg5UUAf/9k= + +END:VCARD diff --git a/libs/composer/vendor/sabre/vobject/tests/bootstrap.php b/libs/composer/vendor/sabre/vobject/tests/bootstrap.php new file mode 100644 index 000000000000..14281e2182e1 --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/bootstrap.php @@ -0,0 +1,25 @@ +addPsr4('Sabre\\VObject\\', __DIR__ . '/VObject'); + +if (!defined('SABRE_TEMPDIR')) { + define('SABRE_TEMPDIR', __DIR__ . '/temp/'); +} + +if (!file_exists(SABRE_TEMPDIR)) { + mkdir(SABRE_TEMPDIR); +} diff --git a/libs/composer/vendor/sabre/vobject/tests/phpunit.xml b/libs/composer/vendor/sabre/vobject/tests/phpunit.xml new file mode 100644 index 000000000000..46dad6a3d22f --- /dev/null +++ b/libs/composer/vendor/sabre/vobject/tests/phpunit.xml @@ -0,0 +1,23 @@ + + + VObject/ + + + + + ../lib/ + + ../lib/Sabre/VObject/includes.php + + + + diff --git a/libs/composer/vendor/sabre/xml/.gitignore b/libs/composer/vendor/sabre/xml/.gitignore new file mode 100644 index 000000000000..accb586c7712 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/.gitignore @@ -0,0 +1,9 @@ +vendor +composer.lock +tests/cov +.*.swp + +# Composer binaries +bin/phpunit +bin/php-cs-fixer +bin/sabre-cs-fixer diff --git a/libs/composer/vendor/sabre/xml/.travis.yml b/libs/composer/vendor/sabre/xml/.travis.yml new file mode 100644 index 000000000000..96396564e762 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/.travis.yml @@ -0,0 +1,26 @@ +language: php +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + +matrix: + fast_finish: true + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - phpenv config-rm xdebug.ini; true + +install: + - composer install + +script: + - ./bin/phpunit --configuration tests/phpunit.xml.dist + - ./bin/sabre-cs-fixer fix . --dry-run --diff + diff --git a/libs/composer/vendor/sabre/xml/CHANGELOG.md b/libs/composer/vendor/sabre/xml/CHANGELOG.md new file mode 100644 index 000000000000..39a39bffe9f0 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/CHANGELOG.md @@ -0,0 +1,228 @@ +ChangeLog +========= + +1.5.0 (2016-10-09) +------------------ + +* Now requires PHP 5.5. +* Using `finally` to always roll back the context stack when serializing. +* #94: Fixed an infinite loop condition when reading some invalid XML + documents. + + +1.4.2 (2016-05-19) +------------------ + +* The `contextStack` in the Reader object is now correctly rolled back in + error conditions (@staabm). +* repeatingElements deserializer now still parses if a bare element name + without clark notation was given. +* `$elementMap` in the Reader now also supports bare element names. +* `Service::expect()` can now also work with bare element names. + + +1.4.1 (2016-03-12) +----------------- + +* Parsing clark-notation is now cached. This can speed up parsing large + documents with lots of repeating elements a fair bit. (@icewind1991). + + +1.4.0 (2016-02-14) +------------------ + +* Any array thrown into the serializer with numeric keys is now simply + traversed and each individual item is serialized. This fixes an issue + related to serializing value objects with array children. +* When serializing value objects, properties that have a null value or an + empty array are now skipped. We believe this to be the saner default, but + does constitute a BC break for those depending on this. +* Serializing array properties in value objects was broken. + + +1.3.0 (2015-12-29) +------------------ + +* The `Service` class adds a new `mapValueObject` method which provides basic + capabilities to map between ValueObjects and XML. +* #61: You can now specify serializers for specific classes, allowing you + separate the object you want to serialize from the serializer. This uses the + `$classMap` property which is defined on both the `Service` and `Writer`. +* It's now possible to pass an array of possible root elements to + `Sabre\Xml\Service::expect()`. +* Moved some parsing logic to `Reader::getDeserializerForElementName()`, + so people with more advanced use-cases can implement their own logic there. +* #63: When serializing elements using arrays, the `value` key in the array is + now optional. +* #62: Added a `keyValue` deserializer function. This can be used instead of + the `Element\KeyValue` class and is a lot more flexible. (@staabm) +* Also added an `enum` deserializer function to replace + `Element\Elements`. +* Using an empty string for a namespace prefix now has the same effect as + `null`. + + +1.2.0 (2015-08-30) +------------------ + +* #53: Added `parseGetElements`, a function like `parseInnerTree`, except + that it always returns an array of elements, or an empty array. + + +1.1.0 (2015-06-29) +------------------ + +* #44, #45: Catching broken and invalid XML better and throwing + `Sabre\Xml\LibXMLException` whenever we encounter errors. (@stefanmajoor, + @DaanBiesterbos) + + +1.0.0 (2015-05-25) +------------------ + +* No functional changes since 0.4.3. Marking it as 1.0.0 as a promise for + API stability. +* Using php-cs-fixer for automated CS enforcement. + + +0.4.3 (2015-04-01) +----------------- + +* Minor tweaks for the public release. + + +0.4.2 (2015-03-20) +------------------ + +* Removed `constants.php` again. They messed with PHPUnit and don't really + provide a great benefit. +* #41: Correctly handle self-closing xml elements. + + +0.4.1 (2015-03-19) +------------------ + +* #40: An element with an empty namespace (xmlns="") is not allowed to have a + prefix. This is now fixed. + + +0.4.0 (2015-03-18) +------------------ + +* Added `Sabre\Xml\Service`. This is intended as a simple way to centrally + configure xml applications and easily parse/write things from there. #35, #38. +* Renamed 'baseUri' to 'contextUri' everywhere. +* #36: Added a few convenience constants to `lib/constants.php`. +* `Sabre\Xml\Util::parseClarkNotation` is now in the `Sabre\Xml\Service` class. + + +0.3.1 (2015-02-08) +------------------ + +* Added `XmlDeserializable` to match `XmlSerializable`. + + +0.3.0 (2015-02-06) +------------------ + +* Added `$elementMap` argument to parseInnerTree, for quickly overriding + parsing rules within an element. + + +0.2.2 (2015-02-05) +------------------ + +* Now depends on sabre/uri 1.0. + + +0.2.1 (2014-12-17) +------------------ + +* LibXMLException now inherits from ParseException, so it's easy for users to + catch any exception thrown by the parser. + + +0.2.0 (2014-12-05) +------------------ + +* Major BC Break: method names for the Element interface have been renamed + from `serializeXml` and `deserializeXml` to `xmlSerialize` and + `xmlDeserialize`. This is so that it matches PHP's `JsonSerializable` + interface. +* #25: Added `XmlSerializable` to allow people to write serializers without + having to implement a deserializer in the same class. +* #26: Renamed the `Sabre\XML` namespace to `Sabre\Xml`. Due to composer magic + and the fact that PHP namespace are case-insensitive, this should not affect + anyone, unless you are doing exact string matches on class names. +* #23: It's not possible to automatically extract or serialize Xml fragments + from documents using `Sabre\Xml\Element\XmlFragment`. + + +0.1.0 (2014-11-24) +------------------ + +* #16: Added ability to override `elementMap`, `namespaceMap` and `baseUri` for + a fragment of a document during reading an writing using `pushContext` and + `popContext`. +* Removed: `Writer::$context` and `Reader::$context`. +* #15: Added `Reader::$baseUri` to match `Writer::$baseUri`. +* #20: Allow callbacks to be used instead of `Element` classes in the `Reader`. +* #25: Added `readText` to quickly grab all text from a node and advance the + reader to the next node. +* #15: Added `Sabre\XML\Element\Uri`. + + +0.0.6 (2014-09-26) +------------------ + +* Added: `CData` element. +* #13: Better support for xml with no namespaces. (@kalmas) +* Switched to PSR-4 directory structure. + + +0.0.5 (2013-03-27) +------------------ + +* Added: baseUri property to the Writer class. +* Added: The writeElement method can now write complex elements. +* Added: Throwing exception when invalid objects are written. + + +0.0.4 (2013-03-14) +------------------ + +* Fixed: The KeyValue parser was skipping over elements when there was no + whitespace between them. +* Fixed: Clearing libxml errors after parsing. +* Added: Support for CDATA. +* Added: Context properties. + + +0.0.3 (2013-02-22) +------------------ + +* Changed: Reader::parse returns an array with 1 level less depth. +* Added: A LibXMLException is now thrown if the XMLReader comes across an error. +* Fixed: Both the Elements and KeyValue parsers had severe issues with + nesting. +* Fixed: The reader now detects when the end of the document is hit before it + should (because we're still parsing an element). + + +0.0.2 (2013-02-17) +------------------ + +* Added: Elements parser. +* Added: KeyValue parser. +* Change: Reader::parseSubTree is now named parseInnerTree, and returns either + a string (in case of a text-node), or an array (in case there were child + elements). +* Added: Reader::parseCurrentElement is now public. + + +0.0.1 (2013-02-07) +------------------ + +* First alpha release + +Project started: 2012-11-13. First experiments in June 2009. diff --git a/libs/composer/vendor/sabre/xml/LICENSE b/libs/composer/vendor/sabre/xml/LICENSE new file mode 100644 index 000000000000..c9faf409b95d --- /dev/null +++ b/libs/composer/vendor/sabre/xml/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/composer/vendor/sabre/xml/README.md b/libs/composer/vendor/sabre/xml/README.md new file mode 100644 index 000000000000..e6fc4db5f3dd --- /dev/null +++ b/libs/composer/vendor/sabre/xml/README.md @@ -0,0 +1,25 @@ +sabre/xml +========= + +[![Build Status](https://secure.travis-ci.org/fruux/sabre-xml.svg?branch=master)](http://travis-ci.org/fruux/sabre-xml) + +The sabre/xml library is a specialized XML reader and writer. + +Documentation +------------- + +* [Introduction](http://sabre.io/xml/). +* [Installation](http://sabre.io/xml/install/). +* [Reading XML](http://sabre.io/xml/reading/). +* [Writing XML](http://sabre.io/xml/writing/). + + +Support +------- + +Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/libs/composer/vendor/sabre/xml/bin/.empty b/libs/composer/vendor/sabre/xml/bin/.empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/composer/vendor/sabre/xml/composer.json b/libs/composer/vendor/sabre/xml/composer.json new file mode 100644 index 000000000000..386f8213f5ee --- /dev/null +++ b/libs/composer/vendor/sabre/xml/composer.json @@ -0,0 +1,53 @@ +{ + "name": "sabre/xml", + "description" : "sabre/xml is an XML library that you may not hate.", + "keywords" : [ "XML", "XMLReader", "XMLWriter", "DOM" ], + "homepage" : "https://sabre.io/xml/", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.5.5", + "ext-xmlwriter" : "*", + "ext-xmlreader" : "*", + "ext-dom" : "*", + "lib-libxml" : ">=2.6.20", + "sabre/uri" : ">=1.0,<3.0.0" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-xml" + }, + "autoload" : { + "psr-4" : { + "Sabre\\Xml\\" : "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "autoload-dev" : { + "psr-4" : { + "Sabre\\Xml\\" : "tests/Sabre/Xml/" + } + }, + "require-dev": { + "sabre/cs": "~1.0.0", + "phpunit/phpunit" : "*" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/libs/composer/vendor/sabre/xml/lib/ContextStackTrait.php b/libs/composer/vendor/sabre/xml/lib/ContextStackTrait.php new file mode 100644 index 000000000000..ee3a3baca5c0 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/ContextStackTrait.php @@ -0,0 +1,123 @@ +contextStack[] = [ + $this->elementMap, + $this->contextUri, + $this->namespaceMap, + $this->classMap + ]; + + } + + /** + * Restore the previous "context". + * + * @return null + */ + function popContext() { + + list( + $this->elementMap, + $this->contextUri, + $this->namespaceMap, + $this->classMap + ) = array_pop($this->contextStack); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Deserializer/functions.php b/libs/composer/vendor/sabre/xml/lib/Deserializer/functions.php new file mode 100644 index 000000000000..2e5d877e9121 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Deserializer/functions.php @@ -0,0 +1,258 @@ +value" array. + * + * For example, keyvalue will parse: + * + * + * + * value1 + * value2 + * + * + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1" => "value1", + * "{http://sabredav.org/ns}elem2" => "value2", + * "{http://sabredav.org/ns}elem3" => null, + * ]; + * + * If you specify the 'namespace' argument, the deserializer will remove + * the namespaces of the keys that match that namespace. + * + * For example, if you call keyValue like this: + * + * keyValue($reader, 'http://sabredav.org/ns') + * + * it's output will instead be: + * + * [ + * "elem1" => "value1", + * "elem2" => "value2", + * "elem3" => null, + * ]; + * + * Attributes will be removed from the top-level elements. If elements with + * the same name appear twice in the list, only the last one will be kept. + * + * + * @param Reader $reader + * @param string $namespace + * @return array + */ +function keyValue(Reader $reader, $namespace = null) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT) { + if ($namespace !== null && $reader->namespaceURI === $namespace) { + $values[$reader->localName] = $reader->parseCurrentElement()['value']; + } else { + $clark = $reader->getClark(); + $values[$clark] = $reader->parseCurrentElement()['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $values; + +} + +/** + * The 'enum' deserializer parses elements into a simple list + * without values or attributes. + * + * For example, Elements will parse: + * + * + * + * + * + * + * content + * + * + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1", + * "{http://sabredav.org/ns}elem2", + * "{http://sabredav.org/ns}elem3", + * "{http://sabredav.org/ns}elem4", + * "{http://sabredav.org/ns}elem5", + * ]; + * + * This is useful for 'enum'-like structures. + * + * If the $namespace argument is specified, it will strip the namespace + * for all elements that match that. + * + * For example, + * + * enum($reader, 'http://sabredav.org/ns') + * + * would return: + * + * [ + * "elem1", + * "elem2", + * "elem3", + * "elem4", + * "elem5", + * ]; + * + * @param Reader $reader + * @param string $namespace + * @return string[] + */ +function enum(Reader $reader, $namespace = null) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + $reader->read(); + $currentDepth = $reader->depth; + + $values = []; + do { + + if ($reader->nodeType !== Reader::ELEMENT) { + continue; + } + if (!is_null($namespace) && $namespace === $reader->namespaceURI) { + $values[] = $reader->localName; + } else { + $values[] = $reader->getClark(); + } + + } while ($reader->depth >= $currentDepth && $reader->next()); + + $reader->next(); + return $values; + +} + +/** + * The valueObject deserializer turns an xml element into a PHP object of + * a specific class. + * + * This is primarily used by the mapValueObject function from the Service + * class, but it can also easily be used for more specific situations. + * + * @param Reader $reader + * @param string $className + * @param string $namespace + * @return object + */ +function valueObject(Reader $reader, $className, $namespace) { + + $valueObject = new $className(); + if ($reader->isEmptyElement) { + $reader->next(); + return $valueObject; + } + + $defaultProperties = get_class_vars($className); + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) { + + if (property_exists($valueObject, $reader->localName)) { + if (is_array($defaultProperties[$reader->localName])) { + $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value']; + } else { + $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value']; + } + } else { + // Ignore property + $reader->next(); + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + return $valueObject; + +} + +/** + * This deserializer helps you deserialize xml structures that look like + * this: + * + * + * ... + * ... + * ... + * + * + * Many XML documents use patterns like that, and this deserializer + * allow you to get all the 'items' as an array. + * + * In that previous example, you would register the deserializer as such: + * + * $reader->elementMap['{}collection'] = function($reader) { + * return repeatingElements($reader, '{}item'); + * } + * + * The repeatingElements deserializer simply returns everything as an array. + * + * @param Reader $reader + * @param string $childElementName Element name in clark-notation + * @return array + */ +function repeatingElements(Reader $reader, $childElementName) { + + if ($childElementName[0] !== '{') { + $childElementName = '{}' . $childElementName; + } + $result = []; + + foreach ($reader->parseGetElements() as $element) { + + if ($element['name'] === $childElementName) { + $result[] = $element['value']; + } + + } + + return $result; + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element.php b/libs/composer/vendor/sabre/xml/lib/Element.php new file mode 100644 index 000000000000..dd89c58882c2 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element.php @@ -0,0 +1,20 @@ +value = $value; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializable should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->write($this->value); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $subTree = $reader->parseInnerTree(); + return $subTree; + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element/Cdata.php b/libs/composer/vendor/sabre/xml/lib/Element/Cdata.php new file mode 100644 index 000000000000..5f42c4c6e8e8 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element/Cdata.php @@ -0,0 +1,64 @@ +value = $value; + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->writeCData($this->value); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element/Elements.php b/libs/composer/vendor/sabre/xml/lib/Element/Elements.php new file mode 100644 index 000000000000..9eefd1bf884f --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element/Elements.php @@ -0,0 +1,108 @@ + + * + * + * + * + * content + * + * + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1", + * "{http://sabredav.org/ns}elem2", + * "{http://sabredav.org/ns}elem3", + * "{http://sabredav.org/ns}elem4", + * "{http://sabredav.org/ns}elem5", + * ]; + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Elements implements Xml\Element { + + /** + * Value to serialize + * + * @var array + */ + protected $value; + + /** + * Constructor + * + * @param array $value + */ + function __construct(array $value = []) { + + $this->value = $value; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + Serializer\enum($writer, $this->value); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return Deserializer\enum($reader); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element/KeyValue.php b/libs/composer/vendor/sabre/xml/lib/Element/KeyValue.php new file mode 100644 index 000000000000..7ce53bf4c6da --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element/KeyValue.php @@ -0,0 +1,108 @@ +value struct. + * + * Attributes will be removed, and duplicate child elements are discarded. + * Complex values within the elements will be parsed by the 'standard' parser. + * + * For example, KeyValue will parse: + * + * + * + * value1 + * value2 + * + * + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1" => "value1", + * "{http://sabredav.org/ns}elem2" => "value2", + * "{http://sabredav.org/ns}elem3" => null, + * ]; + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class KeyValue implements Xml\Element { + + /** + * Value to serialize + * + * @var array + */ + protected $value; + + /** + * Constructor + * + * @param array $value + */ + function __construct(array $value = []) { + + $this->value = $value; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->write($this->value); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called staticly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return Deserializer\keyValue($reader); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element/Uri.php b/libs/composer/vendor/sabre/xml/lib/Element/Uri.php new file mode 100644 index 000000000000..8f45c0027ebd --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element/Uri.php @@ -0,0 +1,104 @@ +/foo/bar + * http://example.org/hi + * + * If the uri is relative, it will be automatically expanded to an absolute + * url during writing and reading, if the contextUri property is set on the + * reader and/or writer. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Uri implements Xml\Element { + + /** + * Uri element value. + * + * @var string + */ + protected $value; + + /** + * Constructor + * + * @param string $value + */ + function __construct($value) + { + $this->value = $value; + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->text( + \Sabre\Uri\resolve( + $writer->contextUri, + $this->value + ) + ); + + } + + /** + * This method is called during xml parsing. + * + * This method is called statically, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return new self( + \Sabre\Uri\resolve( + $reader->contextUri, + $reader->readText() + ) + ); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Element/XmlFragment.php b/libs/composer/vendor/sabre/xml/lib/Element/XmlFragment.php new file mode 100644 index 000000000000..642241ca485c --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Element/XmlFragment.php @@ -0,0 +1,147 @@ +xml = $xml; + + } + + function getXml() { + + return $this->xml; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $reader = new Reader(); + + // Wrapping the xml in a container, so root-less values can still be + // parsed. + $xml = << +{$this->getXml()} +XML; + + $reader->xml($xml); + + while ($reader->read()) { + + if ($reader->depth < 1) { + // Skipping the root node. + continue; + } + + switch ($reader->nodeType) { + + case Reader::ELEMENT : + $writer->startElement( + $reader->getClark() + ); + $empty = $reader->isEmptyElement; + while ($reader->moveToNextAttribute()) { + switch ($reader->namespaceURI) { + case '' : + $writer->writeAttribute($reader->localName, $reader->value); + break; + case 'http://www.w3.org/2000/xmlns/' : + // Skip namespace declarations + break; + default : + $writer->writeAttribute($reader->getClark(), $reader->value); + break; + } + } + if ($empty) { + $writer->endElement(); + } + break; + case Reader::CDATA : + case Reader::TEXT : + $writer->text( + $reader->value + ); + break; + case Reader::END_ELEMENT : + $writer->endElement(); + break; + + } + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = new self($reader->readInnerXml()); + $reader->next(); + return $result; + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/LibXMLException.php b/libs/composer/vendor/sabre/xml/lib/LibXMLException.php new file mode 100644 index 000000000000..f0190eb51eaf --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/LibXMLException.php @@ -0,0 +1,53 @@ +errors = $errors; + parent::__construct($errors[0]->message . ' on line ' . $errors[0]->line . ', column ' . $errors[0]->column, $code, $previousException); + + } + + /** + * Returns the LibXML errors + * + * @return void + */ + function getErrors() { + + return $this->errors; + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/ParseException.php b/libs/composer/vendor/sabre/xml/lib/ParseException.php new file mode 100644 index 000000000000..3a6883b2ff80 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/ParseException.php @@ -0,0 +1,17 @@ +localName) { + return null; + } + + return '{' . $this->namespaceURI . '}' . $this->localName; + + } + + /** + * Reads the entire document. + * + * This function returns an array with the following three elements: + * * name - The root element name. + * * value - The value for the root element. + * * attributes - An array of attributes. + * + * This function will also disable the standard libxml error handler (which + * usually just results in PHP errors), and throw exceptions instead. + * + * @return array + */ + function parse() { + + $previousEntityState = libxml_disable_entity_loader(true); + $previousSetting = libxml_use_internal_errors(true); + + try { + + // Really sorry about the silence operator, seems like I have no + // choice. See: + // + // https://bugs.php.net/bug.php?id=64230 + while ($this->nodeType !== self::ELEMENT && @$this->read()) { + // noop + } + $result = $this->parseCurrentElement(); + + $errors = libxml_get_errors(); + libxml_clear_errors(); + if ($errors) { + throw new LibXMLException($errors); + } + + } finally { + libxml_use_internal_errors($previousSetting); + libxml_disable_entity_loader($previousEntityState); + } + + return $result; + } + + + + /** + * parseGetElements parses everything in the current sub-tree, + * and returns a an array of elements. + * + * Each element has a 'name', 'value' and 'attributes' key. + * + * If the the element didn't contain sub-elements, an empty array is always + * returned. If there was any text inside the element, it will be + * discarded. + * + * If the $elementMap argument is specified, the existing elementMap will + * be overridden while parsing the tree, and restored after this process. + * + * @param array $elementMap + * @return array + */ + function parseGetElements(array $elementMap = null) { + + $result = $this->parseInnerTree($elementMap); + if (!is_array($result)) { + return []; + } + return $result; + + } + + /** + * Parses all elements below the current element. + * + * This method will return a string if this was a text-node, or an array if + * there were sub-elements. + * + * If there's both text and sub-elements, the text will be discarded. + * + * If the $elementMap argument is specified, the existing elementMap will + * be overridden while parsing the tree, and restored after this process. + * + * @param array $elementMap + * @return array|string + */ + function parseInnerTree(array $elementMap = null) { + + $text = null; + $elements = []; + + if ($this->nodeType === self::ELEMENT && $this->isEmptyElement) { + // Easy! + $this->next(); + return null; + } + + if (!is_null($elementMap)) { + $this->pushContext(); + $this->elementMap = $elementMap; + } + + try { + + // Really sorry about the silence operator, seems like I have no + // choice. See: + // + // https://bugs.php.net/bug.php?id=64230 + if (!@$this->read()) { + $errors = libxml_get_errors(); + libxml_clear_errors(); + if ($errors) { + throw new LibXMLException($errors); + } + throw new ParseException('This should never happen (famous last words)'); + } + + while (true) { + + if (!$this->isValid()) { + + $errors = libxml_get_errors(); + + if ($errors) { + libxml_clear_errors(); + throw new LibXMLException($errors); + } + } + + switch ($this->nodeType) { + case self::ELEMENT : + $elements[] = $this->parseCurrentElement(); + break; + case self::TEXT : + case self::CDATA : + $text .= $this->value; + $this->read(); + break; + case self::END_ELEMENT : + // Ensuring we are moving the cursor after the end element. + $this->read(); + break 2; + case self::NONE : + throw new ParseException('We hit the end of the document prematurely. This likely means that some parser "eats" too many elements. Do not attempt to continue parsing.'); + default : + // Advance to the next element + $this->read(); + break; + } + + } + + } finally { + + if (!is_null($elementMap)) { + $this->popContext(); + } + + } + return ($elements ? $elements : $text); + + } + + /** + * Reads all text below the current element, and returns this as a string. + * + * @return string + */ + function readText() { + + $result = ''; + $previousDepth = $this->depth; + + while ($this->read() && $this->depth != $previousDepth) { + if (in_array($this->nodeType, [XMLReader::TEXT, XMLReader::CDATA, XMLReader::WHITESPACE])) { + $result .= $this->value; + } + } + return $result; + + } + + /** + * Parses the current XML element. + * + * This method returns arn array with 3 properties: + * * name - A clark-notation XML element name. + * * value - The parsed value. + * * attributes - A key-value list of attributes. + * + * @return array + */ + function parseCurrentElement() { + + $name = $this->getClark(); + + $attributes = []; + + if ($this->hasAttributes) { + $attributes = $this->parseAttributes(); + } + + $value = call_user_func( + $this->getDeserializerForElementName($name), + $this + ); + + return [ + 'name' => $name, + 'value' => $value, + 'attributes' => $attributes, + ]; + } + + + /** + * Grabs all the attributes from the current element, and returns them as a + * key-value array. + * + * If the attributes are part of the same namespace, they will simply be + * short keys. If they are defined on a different namespace, the attribute + * name will be retured in clark-notation. + * + * @return array + */ + function parseAttributes() { + + $attributes = []; + + while ($this->moveToNextAttribute()) { + if ($this->namespaceURI) { + + // Ignoring 'xmlns', it doesn't make any sense. + if ($this->namespaceURI === 'http://www.w3.org/2000/xmlns/') { + continue; + } + + $name = $this->getClark(); + $attributes[$name] = $this->value; + + } else { + $attributes[$this->localName] = $this->value; + } + } + $this->moveToElement(); + + return $attributes; + + } + + /** + * Returns the function that should be used to parse the element identified + * by it's clark-notation name. + * + * @param string $name + * @return callable + */ + function getDeserializerForElementName($name) { + + + if (!array_key_exists($name, $this->elementMap)) { + if (substr($name, 0, 2) == '{}' && array_key_exists(substr($name, 2), $this->elementMap)) { + $name = substr($name, 2); + } else { + return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize']; + } + } + + $deserializer = $this->elementMap[$name]; + if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) { + return [$deserializer, 'xmlDeserialize']; + } + + if (is_callable($deserializer)) { + return $deserializer; + } + + $type = gettype($deserializer); + if ($type === 'string') { + $type .= ' (' . $deserializer . ')'; + } elseif ($type === 'object') { + $type .= ' (' . get_class($deserializer) . ')'; + } + throw new \LogicException('Could not use this type as a deserializer: ' . $type . ' for element: ' . $name); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Serializer/functions.php b/libs/composer/vendor/sabre/xml/lib/Serializer/functions.php new file mode 100644 index 000000000000..21448017d949 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Serializer/functions.php @@ -0,0 +1,249 @@ + + * + * + * content + * + * + * @param Writer $writer + * @param string[] $values + * @return void + */ +function enum(Writer $writer, array $values) { + + foreach ($values as $value) { + $writer->writeElement($value); + } +} + +/** + * The valueObject serializer turns a simple PHP object into a classname. + * + * Every public property will be encoded as an xml element with the same + * name, in the XML namespace as specified. + * + * Values that are set to null or an empty array are not serialized. To + * serialize empty properties, you must specify them as an empty string. + * + * @param Writer $writer + * @param object $valueObject + * @param string $namespace + */ +function valueObject(Writer $writer, $valueObject, $namespace) { + foreach (get_object_vars($valueObject) as $key => $val) { + if (is_array($val)) { + // If $val is an array, it has a special meaning. We need to + // generate one child element for each item in $val + foreach ($val as $child) { + $writer->writeElement('{' . $namespace . '}' . $key, $child); + } + + } elseif ($val !== null) { + $writer->writeElement('{' . $namespace . '}' . $key, $val); + } + } +} + + +/** + * This serializer helps you serialize xml structures that look like + * this: + * + * + * ... + * ... + * ... + * + * + * In that previous example, this serializer just serializes the item element, + * and this could be called like this: + * + * repeatingElements($writer, $items, '{}item'); + * + * @param Writer $writer + * @param array $items A list of items sabre/xml can serialize. + * @param string $childElementName Element name in clark-notation + * @return void + */ +function repeatingElements(Writer $writer, array $items, $childElementName) { + + foreach ($items as $item) { + $writer->writeElement($childElementName, $item); + } + +} + +/** + * This function is the 'default' serializer that is able to serialize most + * things, and delegates to other serializers if needed. + * + * The standardSerializer supports a wide-array of values. + * + * $value may be a string or integer, it will just write out the string as text. + * $value may be an instance of XmlSerializable or Element, in which case it + * calls it's xmlSerialize() method. + * $value may be a PHP callback/function/closure, in case we call the callback + * and give it the Writer as an argument. + * $value may be a an object, and if it's in the classMap we automatically call + * the correct serializer for it. + * $value may be null, in which case we do nothing. + * + * If $value is an array, the array must look like this: + * + * [ + * [ + * 'name' => '{namespaceUri}element-name', + * 'value' => '...', + * 'attributes' => [ 'attName' => 'attValue' ] + * ] + * [, + * 'name' => '{namespaceUri}element-name2', + * 'value' => '...', + * ] + * ] + * + * This would result in xml like: + * + * + * ... + * + * + * ... + * + * + * The value property may be any value standardSerializer supports, so you can + * nest data-structures this way. Both value and attributes are optional. + * + * Alternatively, you can also specify the array using this syntax: + * + * [ + * [ + * '{namespaceUri}element-name' => '...', + * '{namespaceUri}element-name2' => '...', + * ] + * ] + * + * This is excellent for simple key->value structures, and here you can also + * specify anything for the value. + * + * You can even mix the two array syntaxes. + * + * @param Writer $writer + * @param string|int|float|bool|array|object + * @return void + */ +function standardSerializer(Writer $writer, $value) { + + if (is_scalar($value)) { + + // String, integer, float, boolean + $writer->text($value); + + } elseif ($value instanceof XmlSerializable) { + + // XmlSerializable classes or Element classes. + $value->xmlSerialize($writer); + + } elseif (is_object($value) && isset($writer->classMap[get_class($value)])) { + + // It's an object which class appears in the classmap. + $writer->classMap[get_class($value)]($writer, $value); + + } elseif (is_callable($value)) { + + // A callback + $value($writer); + + } elseif (is_null($value)) { + + // nothing! + + } elseif (is_array($value) && array_key_exists('name', $value)) { + + // if the array had a 'name' element, we assume that this array + // describes a 'name' and optionally 'attributes' and 'value'. + + $name = $value['name']; + $attributes = isset($value['attributes']) ? $value['attributes'] : []; + $value = isset($value['value']) ? $value['value'] : null; + + $writer->startElement($name); + $writer->writeAttributes($attributes); + $writer->write($value); + $writer->endElement(); + + } elseif (is_array($value)) { + + foreach ($value as $name => $item) { + + if (is_int($name)) { + + // This item has a numeric index. We just loop through the + // array and throw it back in the writer. + standardSerializer($writer, $item); + + } elseif (is_string($name) && is_array($item) && isset($item['attributes'])) { + + // The key is used for a name, but $item has 'attributes' and + // possibly 'value' + $writer->startElement($name); + $writer->writeAttributes($item['attributes']); + if (isset($item['value'])) { + $writer->write($item['value']); + } + $writer->endElement(); + + } elseif (is_string($name)) { + + // This was a plain key-value array. + $writer->startElement($name); + $writer->write($item); + $writer->endElement(); + + } else { + + throw new InvalidArgumentException('The writer does not know how to serialize arrays with keys of type: ' . gettype($name)); + + } + } + + } elseif (is_object($value)) { + + throw new InvalidArgumentException('The writer cannot serialize objects of class: ' . get_class($value)); + + } else { + + throw new InvalidArgumentException('The writer cannot serialize values of type: ' . gettype($value)); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Service.php b/libs/composer/vendor/sabre/xml/lib/Service.php new file mode 100644 index 000000000000..09ee341cf8fb --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Service.php @@ -0,0 +1,297 @@ +elementMap = $this->elementMap; + return $r; + + } + + /** + * Returns a fresh xml writer + * + * @return Writer + */ + function getWriter() { + + $w = new Writer(); + $w->namespaceMap = $this->namespaceMap; + $w->classMap = $this->classMap; + return $w; + + } + + /** + * Parses a document in full. + * + * Input may be specified as a string or readable stream resource. + * The returned value is the value of the root document. + * + * Specifying the $contextUri allows the parser to figure out what the URI + * of the document was. This allows relative URIs within the document to be + * expanded easily. + * + * The $rootElementName is specified by reference and will be populated + * with the root element name of the document. + * + * @param string|resource $input + * @param string|null $contextUri + * @param string|null $rootElementName + * @throws ParseException + * @return array|object|string + */ + function parse($input, $contextUri = null, &$rootElementName = null) { + + if (is_resource($input)) { + // Unfortunately the XMLReader doesn't support streams. When it + // does, we can optimize this. + $input = stream_get_contents($input); + } + $r = $this->getReader(); + $r->contextUri = $contextUri; + $r->xml($input); + + $result = $r->parse(); + $rootElementName = $result['name']; + return $result['value']; + + } + + /** + * Parses a document in full, and specify what the expected root element + * name is. + * + * This function works similar to parse, but the difference is that the + * user can specify what the expected name of the root element should be, + * in clark notation. + * + * This is useful in cases where you expected a specific document to be + * passed, and reduces the amount of if statements. + * + * It's also possible to pass an array of expected rootElements if your + * code may expect more than one document type. + * + * @param string|string[] $rootElementName + * @param string|resource $input + * @param string|null $contextUri + * @return void + */ + function expect($rootElementName, $input, $contextUri = null) { + + if (is_resource($input)) { + // Unfortunately the XMLReader doesn't support streams. When it + // does, we can optimize this. + $input = stream_get_contents($input); + } + $r = $this->getReader(); + $r->contextUri = $contextUri; + $r->xml($input); + + $rootElementName = (array)$rootElementName; + + foreach ($rootElementName as &$rEl) { + if ($rEl[0] !== '{') $rEl = '{}' . $rEl; + } + + $result = $r->parse(); + if (!in_array($result['name'], $rootElementName, true)) { + throw new ParseException('Expected ' . implode(' or ', (array)$rootElementName) . ' but received ' . $result['name'] . ' as the root element'); + } + return $result['value']; + + } + + /** + * Generates an XML document in one go. + * + * The $rootElement must be specified in clark notation. + * The value must be a string, an array or an object implementing + * XmlSerializable. Basically, anything that's supported by the Writer + * object. + * + * $contextUri can be used to specify a sort of 'root' of the PHP application, + * in case the xml document is used as a http response. + * + * This allows an implementor to easily create URI's relative to the root + * of the domain. + * + * @param string $rootElementName + * @param string|array|XmlSerializable $value + * @param string|null $contextUri + */ + function write($rootElementName, $value, $contextUri = null) { + + $w = $this->getWriter(); + $w->openMemory(); + $w->contextUri = $contextUri; + $w->setIndent(true); + $w->startDocument(); + $w->writeElement($rootElementName, $value); + return $w->outputMemory(); + + } + + /** + * Map an xml element to a PHP class. + * + * Calling this function will automatically setup the Reader and Writer + * classes to turn a specific XML element to a PHP class. + * + * For example, given a class such as : + * + * class Author { + * public $firstName; + * public $lastName; + * } + * + * and an XML element such as: + * + * + * ... + * ... + * + * + * These can easily be mapped by calling: + * + * $service->mapValueObject('{http://example.org}author', 'Author'); + * + * @param string $elementName + * @param object $className + * @return void + */ + function mapValueObject($elementName, $className) { + list($namespace) = self::parseClarkNotation($elementName); + + $this->elementMap[$elementName] = function(Reader $reader) use ($className, $namespace) { + return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace); + }; + $this->classMap[$className] = function(Writer $writer, $valueObject) use ($namespace) { + return \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace); + }; + $this->valueObjectMap[$className] = $elementName; + } + + /** + * Writes a value object. + * + * This function largely behaves similar to write(), except that it's + * intended specifically to serialize a Value Object into an XML document. + * + * The ValueObject must have been previously registered using + * mapValueObject(). + * + * @param object $object + * @param string $contextUri + * @return void + */ + function writeValueObject($object, $contextUri = null) { + + if (!isset($this->valueObjectMap[get_class($object)])) { + throw new \InvalidArgumentException('"' . get_class($object) . '" is not a registered value object class. Register your class with mapValueObject.'); + } + return $this->write( + $this->valueObjectMap[get_class($object)], + $object, + $contextUri + ); + + } + + /** + * Parses a clark-notation string, and returns the namespace and element + * name components. + * + * If the string was invalid, it will throw an InvalidArgumentException. + * + * @param string $str + * @throws InvalidArgumentException + * @return array + */ + static function parseClarkNotation($str) { + static $cache = []; + + if (!isset($cache[$str])) { + + if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) { + throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string'); + } + + $cache[$str] = [ + $matches[1], + $matches[2] + ]; + } + + return $cache[$str]; + } + + /** + * A list of classes and which XML elements they map to. + */ + protected $valueObjectMap = []; + +} diff --git a/libs/composer/vendor/sabre/xml/lib/Version.php b/libs/composer/vendor/sabre/xml/lib/Version.php new file mode 100644 index 000000000000..7edb40d67acf --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/Version.php @@ -0,0 +1,19 @@ + "..", + * "{namespace}name2" => "..", + * ] + * + * One element will be created for each key in this array. The values of + * this array support any format this method supports (this method is + * called recursively). + * + * Array format 2: + * + * [ + * [ + * "name" => "{namespace}name1" + * "value" => "..", + * "attributes" => [ + * "attr" => "attribute value", + * ] + * ], + * [ + * "name" => "{namespace}name1" + * "value" => "..", + * "attributes" => [ + * "attr" => "attribute value", + * ] + * ] + * ] + * + * @param mixed $value + * @return void + */ + function write($value) { + + Serializer\standardSerializer($this, $value); + + } + + /** + * Opens a new element. + * + * You can either just use a local elementname, or you can use clark- + * notation to start a new element. + * + * Example: + * + * $writer->startElement('{http://www.w3.org/2005/Atom}entry'); + * + * Would result in something like: + * + * + * + * @param string $name + * @return bool + */ + function startElement($name) { + + if ($name[0] === '{') { + + list($namespace, $localName) = + Service::parseClarkNotation($name); + + if (array_key_exists($namespace, $this->namespaceMap)) { + $result = $this->startElementNS( + $this->namespaceMap[$namespace] === '' ? null : $this->namespaceMap[$namespace], + $localName, + null + ); + } else { + + // An empty namespace means it's the global namespace. This is + // allowed, but it mustn't get a prefix. + if ($namespace === "" || $namespace === null) { + $result = $this->startElement($localName); + $this->writeAttribute('xmlns', ''); + } else { + if (!isset($this->adhocNamespaces[$namespace])) { + $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1); + } + $result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace); + } + } + + } else { + $result = parent::startElement($name); + } + + if (!$this->namespacesWritten) { + + foreach ($this->namespaceMap as $namespace => $prefix) { + $this->writeAttribute(($prefix ? 'xmlns:' . $prefix : 'xmlns'), $namespace); + } + $this->namespacesWritten = true; + + } + + return $result; + + } + + /** + * Write a full element tag and it's contents. + * + * This method automatically closes the element as well. + * + * The element name may be specified in clark-notation. + * + * Examples: + * + * $writer->writeElement('{http://www.w3.org/2005/Atom}author',null); + * becomes: + * + * + * $writer->writeElement('{http://www.w3.org/2005/Atom}author', [ + * '{http://www.w3.org/2005/Atom}name' => 'Evert Pot', + * ]); + * becomes: + * Evert Pot + * + * @param string $name + * @param string $content + * @return bool + */ + function writeElement($name, $content = null) { + + $this->startElement($name); + if (!is_null($content)) { + $this->write($content); + } + $this->endElement(); + + } + + /** + * Writes a list of attributes. + * + * Attributes are specified as a key->value array. + * + * The key is an attribute name. If the key is a 'localName', the current + * xml namespace is assumed. If it's a 'clark notation key', this namespace + * will be used instead. + * + * @param array $attributes + * @return void + */ + function writeAttributes(array $attributes) { + + foreach ($attributes as $name => $value) { + $this->writeAttribute($name, $value); + } + + } + + /** + * Writes a new attribute. + * + * The name may be specified in clark-notation. + * + * Returns true when successful. + * + * @param string $name + * @param string $value + * @return bool + */ + function writeAttribute($name, $value) { + + if ($name[0] === '{') { + + list( + $namespace, + $localName + ) = Service::parseClarkNotation($name); + + if (array_key_exists($namespace, $this->namespaceMap)) { + // It's an attribute with a namespace we know + $this->writeAttribute( + $this->namespaceMap[$namespace] . ':' . $localName, + $value + ); + } else { + + // We don't know the namespace, we must add it in-line + if (!isset($this->adhocNamespaces[$namespace])) { + $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1); + } + $this->writeAttributeNS( + $this->adhocNamespaces[$namespace], + $localName, + $namespace, + $value + ); + + } + + } else { + return parent::writeAttribute($name, $value); + } + + } + +} diff --git a/libs/composer/vendor/sabre/xml/lib/XmlDeserializable.php b/libs/composer/vendor/sabre/xml/lib/XmlDeserializable.php new file mode 100644 index 000000000000..fa857e82c79d --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/XmlDeserializable.php @@ -0,0 +1,38 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader); + +} diff --git a/libs/composer/vendor/sabre/xml/lib/XmlSerializable.php b/libs/composer/vendor/sabre/xml/lib/XmlSerializable.php new file mode 100644 index 000000000000..3e2c528b9454 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/lib/XmlSerializable.php @@ -0,0 +1,36 @@ +stack = $this->getMockForTrait('Sabre\\Xml\\ContextStackTrait'); + + } + + function testPushAndPull() { + + $this->stack->contextUri = '/foo/bar'; + $this->stack->elementMap['{DAV:}foo'] = 'Bar'; + $this->stack->namespaceMap['DAV:'] = 'd'; + + $this->stack->pushContext(); + + $this->assertEquals('/foo/bar', $this->stack->contextUri); + $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']); + $this->assertEquals('d', $this->stack->namespaceMap['DAV:']); + + $this->stack->contextUri = '/gir/zim'; + $this->stack->elementMap['{DAV:}foo'] = 'newBar'; + $this->stack->namespaceMap['DAV:'] = 'dd'; + + $this->stack->popContext(); + + $this->assertEquals('/foo/bar', $this->stack->contextUri); + $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']); + $this->assertEquals('d', $this->stack->namespaceMap['DAV:']); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php new file mode 100644 index 000000000000..2eea9bb5aaf6 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php @@ -0,0 +1,62 @@ +elementMap['{urn:test}root'] = 'Sabre\Xml\Deserializer\enum'; + + $xml = << + + + + +XML; + + $result = $service->parse($xml); + + $expected = [ + '{urn:test}foo1', + '{urn:test}foo2', + ]; + + + $this->assertEquals($expected, $result); + + + } + + function testDeserializeDefaultNamespace() { + + $service = new Service(); + $service->elementMap['{urn:test}root'] = function($reader) { + return enum($reader, 'urn:test'); + }; + + $xml = << + + + + +XML; + + $result = $service->parse($xml); + + $expected = [ + 'foo1', + 'foo2', + ]; + + + $this->assertEquals($expected, $result); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php new file mode 100644 index 000000000000..a94ff4e01a1b --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php @@ -0,0 +1,112 @@ + + + + + hi + + foo + foo & bar + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}struct' => function(Reader $reader) { + return keyValue($reader, 'http://sabredav.org/ns'); + } + ]; + $reader->xml($input); + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [ + 'elem1' => null, + 'elem2' => 'hi', + '{http://sabredav.org/another-ns}elem3' => [ + [ + 'name' => '{http://sabredav.org/another-ns}elem4', + 'value' => 'foo', + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/another-ns}elem5', + 'value' => 'foo & bar', + 'attributes' => [], + ], + ] + ], + 'attributes' => [], + ] + ], + 'attributes' => [], + ], $output); + } + + /** + * @expectedException \Sabre\Xml\LibXMLException + */ + function testKeyValueLoop() { + + /** + * This bug is a weird one, because it triggers an infinite loop, but + * only if the XML document is a certain size (in bytes). Removing one + * or two characters from the xml body here cause the infinite loop to + * *not* get triggered, so to properly test this bug (Issue #94), don't + * change the XML body. + */ + $invalid_xml = ' + + + NONE + ENVELOPE + 1 + DC + + NONE + ENVELOPE + 1 + DC/FleetType> + + '; + $reader = new Reader(); + + $reader->xml($invalid_xml); + $reader->elementMap = [ + + '{}Package' => function($reader) { + $recipient = []; + // Borrowing a parser from the KeyValue class. + $keyValue = keyValue($reader); + + if (isset($keyValue['{}WeightOz'])){ + $recipient['referenceId'] = $keyValue['{}WeightOz']; + } + + return $recipient; + }, + ]; + + $reader->parse(); + + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php new file mode 100644 index 000000000000..025d997feb2e --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php @@ -0,0 +1,35 @@ +elementMap['{urn:test}collection'] = function($reader) { + return repeatingElements($reader, '{urn:test}item'); + }; + + $xml = << + + foo + bar + +XML; + + $result = $service->parse($xml); + + $expected = [ + 'foo', + 'bar', + ]; + + $this->assertEquals($expected, $result); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php new file mode 100644 index 000000000000..2d6ce98ce4ff --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php @@ -0,0 +1,169 @@ + + + Harry + Turtle + +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + + function testDeserializeValueObjectIgnoredElement() { + + $input = << + + Harry + Turtle + harry@example.org + +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + + function testDeserializeValueObjectAutoArray() { + + $input = << + + Harry + Turtle + http://example.org/ + http://example.net/ + +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + $vo->link = [ + 'http://example.org/', + 'http://example.net/', + ]; + + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + function testDeserializeValueObjectEmpty() { + + $input = << + +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + +} + +class TestVo { + + public $firstName; + public $lastName; + + public $link = []; + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php new file mode 100644 index 000000000000..2d12d7b21965 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php @@ -0,0 +1,58 @@ + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}blabla' => 'Sabre\\Xml\\Element\\Cdata', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + } + + function testSerialize() { + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new Cdata(''), + ]); + + $output = $writer->outputMemory(); + + $expected = << +]]> + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php new file mode 100644 index 000000000000..aaba2a01f2bb --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php @@ -0,0 +1,78 @@ +startElement('{http://sabredav.org/ns}elem1'); + $writer->write('hiiii!'); + $writer->endElement(); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $reader->next(); + + $count = 1; + while ($count) { + + $reader->read(); + if ($reader->nodeType === $reader::END_ELEMENT) { + $count--; + } + + } + $reader->read(); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php new file mode 100644 index 000000000000..f17f2094b2e5 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php @@ -0,0 +1,129 @@ + + + + + + + + content + + + + + + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}listThingy' => 'Sabre\\Xml\\Element\\Elements', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}listThingy', + 'value' => [ + '{http://sabredav.org/ns}elem1', + '{http://sabredav.org/ns}elem2', + '{http://sabredav.org/ns}elem3', + '{http://sabredav.org/ns}elem4', + '{http://sabredav.org/ns}elem5', + '{http://sabredav.org/ns}elem6', + ], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}listThingy', + 'value' => [], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}otherThing', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem2', + 'value' => null, + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem3', + 'value' => null, + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + function testSerialize() { + + $value = [ + '{http://sabredav.org/ns}elem1', + '{http://sabredav.org/ns}elem2', + '{http://sabredav.org/ns}elem3', + '{http://sabredav.org/ns}elem4', + '{http://sabredav.org/ns}elem5', + '{http://sabredav.org/ns}elem6', + ]; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new Elements($value), + ]); + + $output = $writer->outputMemory(); + + $expected = << + + + + + + + + + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php new file mode 100644 index 000000000000..51c87b5203d4 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php @@ -0,0 +1,210 @@ + + + + + hi + + foo + foo & bar + + Hithere + + + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}struct' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [ + '{http://sabredav.org/ns}elem1' => null, + '{http://sabredav.org/ns}elem2' => 'hi', + '{http://sabredav.org/ns}elem3' => [ + [ + 'name' => '{http://sabredav.org/ns}elem4', + 'value' => 'foo', + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem5', + 'value' => 'foo & bar', + 'attributes' => [], + ], + ], + '{http://sabredav.org/ns}elem6' => 'Hithere', + ], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}otherThing', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + /** + * This test was added to find out why an element gets eaten by the + * SabreDAV MKCOL parser. + */ + function testElementEater() { + + $input = << + + + + + bla + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{DAV:}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}resourcetype' => 'Sabre\\Xml\\Element\\Elements', + ]; + $reader->xml($input); + + $expected = [ + 'name' => '{DAV:}mkcol', + 'value' => [ + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => [ + '{DAV:}resourcetype' => [ + '{DAV:}collection', + ], + '{DAV:}displayname' => 'bla', + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ]; + + $this->assertEquals($expected, $reader->parse()); + + } + + + function testSerialize() { + + $value = [ + '{http://sabredav.org/ns}elem1' => null, + '{http://sabredav.org/ns}elem2' => 'textValue', + '{http://sabredav.org/ns}elem3' => [ + '{http://sabredav.org/ns}elem4' => 'text2', + '{http://sabredav.org/ns}elem5' => null, + ], + '{http://sabredav.org/ns}elem6' => 'text3', + ]; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new KeyValue($value), + ]); + + $output = $writer->outputMemory(); + + $expected = << + + + textValue + + text2 + + + text3 + + +XML; + + $this->assertEquals($expected, $output); + + } + + /** + * I discovered that when there's no whitespace between elements, elements + * can get skipped. + */ + function testElementSkipProblem() { + + $input = << + +val3val4val5 +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}root' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + '{http://sabredav.org/ns}elem3' => 'val3', + '{http://sabredav.org/ns}elem4' => 'val4', + '{http://sabredav.org/ns}elem5' => 'val5', + ], + 'attributes' => [], + ], $output); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php new file mode 100644 index 000000000000..f96684cb55ab --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php @@ -0,0 +1,60 @@ +startElement('{http://sabredav.org/ns}elem1'); + $writer->write('hiiii!'); + $writer->endElement(); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $reader->next(); + return 'foobar'; + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php new file mode 100644 index 000000000000..53f89ed7aaeb --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php @@ -0,0 +1,76 @@ + + + /foo/bar + +BLA; + + $reader = new Reader(); + $reader->contextUri = 'http://example.org/'; + $reader->elementMap = [ + '{http://sabredav.org/ns}uri' => 'Sabre\\Xml\\Element\\Uri', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals( + [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}uri', + 'value' => new Uri('http://example.org/foo/bar'), + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + $output + ); + + } + + function testSerialize() { + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->contextUri = 'http://example.org/'; + $writer->write([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}uri' => new Uri('/foo/bar'), + ] + ]); + + $output = $writer->outputMemory(); + + $expected = << + + http://example.org/foo/bar + + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php new file mode 100644 index 000000000000..461cc155ca6d --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php @@ -0,0 +1,143 @@ + + + $input + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}fragment' => 'Sabre\\Xml\\Element\\XmlFragment', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}fragment', + 'value' => new XmlFragment($expected), + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + /** + * Data provider for serialize and deserialize tests. + * + * Returns three items per test: + * + * 1. Input data for the reader. + * 2. Expected output for XmlFragment deserializer + * 3. Expected output after serializing that value again. + * + * If 3 is not set, use 1 for 3. + * + * @return void + */ + function xmlProvider() { + + return [ + [ + 'hello', + 'hello', + ], + [ + 'hello', + 'hello' + ], + [ + 'hello', + 'hello' + ], + [ + 'hello', + 'hello' + ], + [ + 'hello', + 'hello', + 'hello', + ], + [ + 'hello', + 'hello', + 'hello', + ], + [ + 'hello', + 'hello', + 'hello', + ], + [ + 'hello', + 'hello', + 'hello', + ], + [ + '', + '', + '', + ], + [ + '', + '', + '', + ], + ]; + + } + + /** + * @dataProvider xmlProvider + */ + function testSerialize($expectedFallback, $input, $expected = null) { + + if (is_null($expected)) { + $expected = $expectedFallback; + } + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + //$writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}fragment' => new XmlFragment($input), + ], + ]); + + $output = $writer->outputMemory(); + + $expected = << +$expected +XML; + + $this->assertEquals($expected, $output); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php new file mode 100644 index 000000000000..ec8a136d092b --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php @@ -0,0 +1,50 @@ + + + + +'; + + $reader = new Reader(); + $reader->elementMap = [ + '{DAV:}set' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($body); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{DAV:}propertyupdate', + 'value' => [ + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => null, + ], + 'attributes' => [], + ], + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => null, + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php new file mode 100644 index 000000000000..8da81d1202aa --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php @@ -0,0 +1,585 @@ + + +BLA; + $reader = new Reader(); + $reader->xml($input); + + $reader->next(); + + $this->assertEquals('{http://sabredav.org/ns}root', $reader->getClark()); + + } + + function testGetClarkNoNS() { + + $input = << + +BLA; + $reader = new Reader(); + $reader->xml($input); + + $reader->next(); + + $this->assertEquals('{}root', $reader->getClark()); + + } + + function testGetClarkNotOnAnElement() { + + $input = << + +BLA; + $reader = new Reader(); + $reader->xml($input); + + $this->assertNull($reader->getClark()); + } + + function testSimple() { + + $input = << + + + + Hi! + + +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [ + 'attr' => 'val', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}elem2', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem3', + 'value' => 'Hi!', + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testCDATA() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'bar', + 'attributes' => [], + ], + + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testSimpleNamespacedAttribute() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [ + '{urn:foo}attr' => 'val', + ], + ], + ], + 'attributes' => [], + ]; + + $this->assertEquals($expected, $output); + + } + + function testMappedElement() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock' + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @expectedException \LogicException + */ + function testMappedElementBadClass() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => new \StdClass() + ]; + $reader->xml($input); + + $reader->parse(); + } + + /** + * @depends testMappedElement + */ + function testMappedElementCallBack() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return 'foobar'; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testMappedElementCallBack + */ + function testMappedElementCallBackNoNamespace() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + 'elem1' => function(Reader $reader) { + $reader->next(); + return 'foobar'; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{}root', + 'value' => [ + [ + 'name' => '{}elem1', + 'value' => 'foobar', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testMappedElementCallBack + */ + function testReadText() { + + $input = << + + + hello + world + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + return $reader->readText(); + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'hello world', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testParseProblem() { + + $input = << + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock' + ]; + $reader->xml($input); + + try { + $output = $reader->parse(); + $this->fail('We expected a ParseException to be thrown'); + } catch (LibXMLException $e) { + + $this->assertInternalType('array', $e->getErrors()); + + } + + } + + /** + * @expectedException \Sabre\Xml\ParseException + */ + function testBrokenParserClass() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Eater' + ]; + $reader->xml($input); + $reader->parse(); + + + } + + /** + * Test was added for Issue #10. + * + * @expectedException Sabre\Xml\LibXMLException + */ + function testBrokenXml() { + + $input = << + + + +BLA; + + $reader = new Reader(); + $reader->xml($input); + $reader->parse(); + + } + + /** + * Test was added for Issue #45. + * + * @expectedException Sabre\Xml\LibXMLException + */ + function testBrokenXml2() { + + $input = << + + + + + + ""Administrative w"> + + + + xml($input); + $reader->parse(); + + } + + + /** + * @depends testMappedElement + */ + function testParseInnerTree() { + + $input = << + + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseInnerTree(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testParseInnerTree + */ + function testParseGetElements() { + + $input = << + + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testParseInnerTree + */ + function testParseGetElementsNoElements() { + + $input = << + + + hi + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php new file mode 100644 index 000000000000..2d26e665a2ac --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php @@ -0,0 +1,36 @@ +namespaceMap['urn:test'] = null; + + $xml = $service->write('{urn:test}root', function($writer) { + enum($writer, [ + '{urn:test}foo1', + '{urn:test}foo2', + ]); + }); + + $expected = << + + + + +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php new file mode 100644 index 000000000000..dbca65a572ae --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php @@ -0,0 +1,35 @@ +namespaceMap['urn:test'] = null; + $xml = $service->write('{urn:test}collection', function($writer) { + repeatingElements($writer, [ + 'foo', + 'bar', + ], '{urn:test}item'); + }); + + $expected = << + + foo + bar + +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php new file mode 100644 index 000000000000..e6fcf1499269 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php @@ -0,0 +1,328 @@ + 'Test!', + ]; + + $util = new Service(); + $util->elementMap = $elems; + + $reader = $util->getReader(); + $this->assertInstanceOf('Sabre\\Xml\\Reader', $reader); + $this->assertEquals($elems, $reader->elementMap); + + } + + function testGetWriter() { + + $ns = [ + 'http://sabre.io/ns' => 's', + ]; + + $util = new Service(); + $util->namespaceMap = $ns; + + $writer = $util->getWriter(); + $this->assertInstanceOf('Sabre\\Xml\\Writer', $writer); + $this->assertEquals($ns, $writer->namespaceMap); + + } + + /** + * @depends testGetReader + */ + function testParse() { + + $xml = << + value + +XML; + $util = new Service(); + $result = $util->parse($xml, null, $rootElement); + $this->assertEquals('{http://sabre.io/ns}root', $rootElement); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + + } + + /** + * @depends testGetReader + */ + function testParseStream() { + + $xml = << + value + +XML; + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $xml); + rewind($stream); + + $util = new Service(); + $result = $util->parse($stream, null, $rootElement); + $this->assertEquals('{http://sabre.io/ns}root', $rootElement); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + + } + + /** + * @depends testGetReader + */ + function testExpect() { + + $xml = << + value + +XML; + $util = new Service(); + $result = $util->expect('{http://sabre.io/ns}root', $xml); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + } + + /** + * @depends testGetReader + */ + function testExpectStream() { + + $xml = << + value + +XML; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $xml); + rewind($stream); + + $util = new Service(); + $result = $util->expect('{http://sabre.io/ns}root', $stream); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + } + + /** + * @depends testGetReader + * @expectedException \Sabre\Xml\ParseException + */ + function testExpectWrong() { + + $xml = << + value + +XML; + $util = new Service(); + $util->expect('{http://sabre.io/ns}error', $xml); + + } + + /** + * @depends testGetWriter + */ + function testWrite() { + + $util = new Service(); + $util->namespaceMap = [ + 'http://sabre.io/ns' => 's', + ]; + $result = $util->write('{http://sabre.io/ns}root', [ + '{http://sabre.io/ns}child' => 'value', + ]); + + $expected = << + + value + + +XML; + $this->assertEquals( + $expected, + $result + ); + + } + + function testMapValueObject() { + + $input = << + + 1234 + 99.99 + black friday deal + + 5 + + + + +XML; + + $ns = 'http://sabredav.org/ns'; + $orderService = new \Sabre\Xml\Service(); + $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order'); + $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus'); + $orderService->namespaceMap[$ns] = null; + + $order = $orderService->parse($input); + $expected = new Order(); + $expected->id = 1234; + $expected->amount = 99.99; + $expected->description = 'black friday deal'; + $expected->status = new OrderStatus(); + $expected->status->id = 5; + $expected->status->label = 'processed'; + + $this->assertEquals($expected, $order); + + $writtenXml = $orderService->writeValueObject($order); + $this->assertEquals($input, $writtenXml); + } + + function testMapValueObjectArrayProperty() { + + $input = << + + 1234 + 99.99 + black friday deal + + 5 + + + http://example.org/ + http://example.com/ + + +XML; + + $ns = 'http://sabredav.org/ns'; + $orderService = new \Sabre\Xml\Service(); + $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order'); + $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus'); + $orderService->namespaceMap[$ns] = null; + + $order = $orderService->parse($input); + $expected = new Order(); + $expected->id = 1234; + $expected->amount = 99.99; + $expected->description = 'black friday deal'; + $expected->status = new OrderStatus(); + $expected->status->id = 5; + $expected->status->label = 'processed'; + $expected->link = ['http://example.org/', 'http://example.com/']; + + $this->assertEquals($expected, $order); + + $writtenXml = $orderService->writeValueObject($order); + $this->assertEquals($input, $writtenXml); + } + + /** + * @expectedException \InvalidArgumentException + */ + function testWriteVoNotFound() { + + $service = new Service(); + $service->writeValueObject(new \StdClass()); + + } + + function testParseClarkNotation() { + + $this->assertEquals([ + 'http://sabredav.org/ns', + 'elem', + ], Service::parseClarkNotation('{http://sabredav.org/ns}elem')); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testParseClarkNotationFail() { + + Service::parseClarkNotation('http://sabredav.org/ns}elem'); + + } + +} + +/** + * asset for testMapValueObject() + * @internal + */ +class Order { + public $id; + public $amount; + public $description; + public $status; + public $empty; + public $link = []; +} + +/** + * asset for testMapValueObject() + * @internal + */ +class OrderStatus { + public $id; + public $label; +} diff --git a/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php new file mode 100644 index 000000000000..574d8023700b --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php @@ -0,0 +1,439 @@ +writer = new Writer(); + $this->writer->namespaceMap = [ + 'http://sabredav.org/ns' => 's', + ]; + $this->writer->openMemory(); + $this->writer->setIndent(true); + $this->writer->startDocument(); + + } + + function compare($input, $output) { + + $this->writer->write($input); + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + + function testSimple() { + + $this->compare([ + '{http://sabredav.org/ns}root' => 'text', + ], << +text + +HI + ); + + } + + /** + * @depends testSimple + */ + function testSimpleQuotes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => '"text"', + ], << +"text" + +HI + ); + + } + + function testSimpleAttributes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], << +text + +HI + ); + + } + function testMixedSyntax() { + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}single' => 'value', + '{http://sabredav.org/ns}multiple' => [ + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'bar', + ], + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'foobar', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}attributes', + 'value' => null, + 'attributes' => [ + 'foo' => 'bar', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}verbose', + 'value' => 'syntax', + 'attributes' => [ + 'foo' => 'bar', + ], + ], + ], + ], << + + value + + bar + foobar + + + syntax + + +HI + ); + } + + function testNull() { + + $this->compare([ + '{http://sabredav.org/ns}root' => null, + ], << + + +HI + ); + + } + + function testArrayFormat2() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], + ], << + + text + + +HI + ); + + } + + function testArrayOfValues() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + 'foo', + 'bar', + 'baz', + ], + ], + ], + ], << + + foobarbaz + + +HI + ); + + } + + /** + * @depends testArrayFormat2 + */ + function testArrayFormat2NoValue() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], + ], << + + + + +HI + ); + + } + + function testCustomNamespace() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{urn:foo}elem1' => 'bar', + ], + ], << + + bar + + +HI + ); + + } + + function testEmptyNamespace() { + + // Empty namespaces are allowed, so we should support this. + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{}elem1' => 'bar', + ], + ], << + + bar + + +HI + ); + + } + + function testAttributes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'val1', + '{http://sabredav.org/ns}attr2' => 'val2', + '{urn:foo}attr3' => 'val3', + ], + ], + ], + ], << + + text + + +HI + ); + + } + + function testBaseElement() { + + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Base('hello') + ], << +hello + +HI + ); + + } + + function testElementObj() { + + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], << + + hiiii! + + +HI + ); + + } + + function testEmptyNamespacePrefix() { + + $this->writer->namespaceMap['http://sabredav.org/ns'] = null; + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], << + + hiiii! + + +HI + ); + + } + + function testEmptyNamespacePrefixEmptyString() { + + $this->writer->namespaceMap['http://sabredav.org/ns'] = ''; + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], << + + hiiii! + + +HI + ); + + } + + function testWriteElement() { + + $this->writer->writeElement("{http://sabredav.org/ns}foo", 'content'); + + $output = << +content + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + + } + + function testWriteElementComplex() { + + $this->writer->writeElement("{http://sabredav.org/ns}foo", new Element\KeyValue(['{http://sabredav.org/ns}bar' => 'test'])); + + $output = << + + test + + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testWriteBadObject() { + + $this->writer->write(new \StdClass()); + + } + + function testStartElementSimple() { + + $this->writer->startElement("foo"); + $this->writer->endElement(); + + $output = << + + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + function testCallback() { + + $this->compare([ + '{http://sabredav.org/ns}root' => function(Writer $writer) { + $writer->text('deferred writer'); + }, + ], << +deferred writer + +HI + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testResource() { + + $this->compare([ + '{http://sabredav.org/ns}root' => fopen('php://memory', 'r'), + ], << +deferred writer + +HI + ); + + } + + function testClassMap() { + + $obj = (object)[ + 'key1' => 'value1', + 'key2' => 'value2', + ]; + + $this->writer->classMap['stdClass'] = function(Writer $writer, $value) { + + foreach (get_object_vars($value) as $key => $val) { + $writer->writeElement('{http://sabredav.org/ns}' . $key, $val); + } + + }; + + $this->compare([ + '{http://sabredav.org/ns}root' => $obj + ], << + + value1 + value2 + + +HI + ); + + } +} diff --git a/libs/composer/vendor/sabre/xml/tests/phpcs/ruleset.xml b/libs/composer/vendor/sabre/xml/tests/phpcs/ruleset.xml new file mode 100644 index 000000000000..07acb89eea04 --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/phpcs/ruleset.xml @@ -0,0 +1,56 @@ + + + sabre.io codesniffer ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/composer/vendor/sabre/xml/tests/phpunit.xml.dist b/libs/composer/vendor/sabre/xml/tests/phpunit.xml.dist new file mode 100644 index 000000000000..fe8b2359aa6f --- /dev/null +++ b/libs/composer/vendor/sabre/xml/tests/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + Sabre/ + + + + + ../lib/ + + + diff --git a/webdav.php b/webdav.php index 97334bbb8014..62e103ecc239 100644 --- a/webdav.php +++ b/webdav.php @@ -1,8 +1,5 @@ ServeRequest(); -// END WebDAV +if(!ilDAVActivationChecker::_isActive()) +{ + header("HTTP/1.1 403 Forbidden"); + header("X-WebDAV-Status: 403 Forbidden", true); + echo '

    Sorry

    '. + '

    Please enable the WebDAV plugin in the ILIAS Administration panel.

    '. + '

    You can only access this page, if WebDAV is enabled on this server.

    '. + ''; + exit; +} + +if(!$show_mount_instr) +{ + // Launch the WebDAV Server + include_once "Services/WebDAV/classes/class.ilWebDAVRequestHandler.php"; + $server = ilWebDAVRequestHandler::getInstance(); + $server->handleRequest(); +} +else +{ + // Show mount isntructions page for WebDAV + include_once "Services/WebDAV/classes/class.ilWebDAVMountInstructionsGUI.php"; + $mount_gui = new ilWebDAVMountInstructionsGUI(); + $mount_gui->showMountInstructionPage(); +} ?> From 2d498fcd70ae67e6a2a7f22d31506f1de95197dd Mon Sep 17 00:00:00 2001 From: nmatuschek Date: Tue, 24 Jul 2018 09:46:43 +0200 Subject: [PATCH 075/166] Fixed typo --- .../utils/class.ilWorkflowDbHelper.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php b/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php index 85d3ef0421a9..35c40d3ba316 100644 --- a/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php +++ b/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php @@ -59,8 +59,8 @@ public static function writeWorkflow(ilWorkflow $workflow) array( 'workflow_type' => array ('text', $wf_data['type'] ), 'workflow_content' => array ('text', $wf_data['content']), - 'workflow_class' => array ('test', $workflow->getWorkflowClass()), - 'workflow_location' => array ('test', $workflow->getWorkflowLocation()), + 'workflow_class' => array ('text', $workflow->getWorkflowClass()), + 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), 'subject_type' => array ('text', $wf_subject['type']), 'subject_id' => array ('integer', $wf_subject['identifier']), 'context_type' => array ('text', $wf_context['type']), @@ -80,8 +80,8 @@ public static function writeWorkflow(ilWorkflow $workflow) array( 'workflow_id' => array ('integer', $wf_id), 'workflow_type' => array ('text', $wf_data['type'] ), - 'workflow_class' => array ('test', $workflow->getWorkflowClass()), - 'workflow_location' => array ('test', $workflow->getWorkflowLocation()), + 'workflow_class' => array ('text', $workflow->getWorkflowClass()), + 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), 'workflow_content' => array ('text', $wf_data['content']), 'subject_type' => array ('text', $wf_subject['type']), 'subject_id' => array ('integer', $wf_subject['identifier']), @@ -401,13 +401,13 @@ public static function writeStartEventData($event, $process_id) $ilDB->insert('wfe_startup_events', array( - 'event_id' => array ('integer', $event_id), - 'workflow_id' => array ('text', $process_id), - 'type' => array ('text', $event['type'] ), + 'event_id' => array ('integer', $event_id), + 'workflow_id' => array ('text', $process_id), + 'type' => array ('text', $event['type'] ), 'content' => array ('text', $event['content']), - 'subject_type' => array ('text', $event['subject_type']), + 'subject_type' => array ('text', $event['subject_type']), 'subject_id' => array ('integer', $event['subject_id']), - 'context_type' => array ('text', $event['context_type']), + 'context_type' => array ('text', $event['context_type']), 'context_id' => array ('integer', $event['context_id']) ) ); @@ -432,7 +432,7 @@ public static function writeStaticInput($key, $value, $start_event) 'input_id' => array ('integer', $ilDB->nextId('wfe_static_inputs')), 'event_id' => array ('integer', $start_event), 'name' => array ('text', $key), - 'value' => array ('text', $value) + 'value' => array ('text', $value) ) ); } From c22b4238330cbbbf8d6e5bad8cb73b8aa0eff8e3 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 10:55:03 +0200 Subject: [PATCH 076/166] Minor table style fix --- Services/Calendar/templates/default/tpl.ch_upcoming_row.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Calendar/templates/default/tpl.ch_upcoming_row.html b/Services/Calendar/templates/default/tpl.ch_upcoming_row.html index 6571010aa5ac..5d22fc772ff7 100644 --- a/Services/Calendar/templates/default/tpl.ch_upcoming_row.html +++ b/Services/Calendar/templates/default/tpl.ch_upcoming_row.html @@ -1,4 +1,4 @@ - + From 9fbf3fdafa3436f742e0003f44e68e8472aa4879 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 10:56:54 +0200 Subject: [PATCH 077/166] Removed unused method --- .../classes/class.ilObjRoleGUI.php | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/Services/AccessControl/classes/class.ilObjRoleGUI.php b/Services/AccessControl/classes/class.ilObjRoleGUI.php index 8a1420d9c8d3..3e4cde94ef92 100755 --- a/Services/AccessControl/classes/class.ilObjRoleGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleGUI.php @@ -1489,9 +1489,6 @@ function __prepareOutput() $this->tpl->addBlockFile("CONTENT", "content", "tpl.adm_content.html"); $this->tpl->addBlockFile("STATUSLINE", "statusline", "tpl.statusline.html"); - // output locator - //$this->__setLocator(); - // output message if ($this->message) { @@ -1514,56 +1511,7 @@ function __setHeader() $this->getTabs($this->tabs_gui); } - function __setLocator() - { - global $DIC; - - $tree = $DIC['tree']; - $ilCtrl = $DIC['ilCtrl']; - - return; - - $this->tpl->addBlockFile("LOCATOR", "locator", "tpl.locator.html", "Services/Locator"); - - $counter = 0; - - foreach ($tree->getPathFull($this->obj_ref_id) as $key => $row) - { - if ($counter++) - { - $this->tpl->touchBlock('locator_separator_prefix'); - } - $this->tpl->setCurrentBlock("locator_item"); - - if ($row["type"] == 'rolf') - { - $this->tpl->setVariable("ITEM",$this->object->getTitle()); - $this->tpl->setVariable("LINK_ITEM",$this->ctrl->getLinkTarget($this)); - } - elseif ($row["child"] != $tree->getRootId()) - { - $ilCtrl->setParameterByClass("ilrepositorygui", "ref_id", $row["child"]); - $this->tpl->setVariable("ITEM", $row["title"]); - $this->tpl->setVariable("LINK_ITEM", - $ilCtrl->getLinkTargetByClass("ilrepositorygui", "")); - } - else - { - $ilCtrl->setParameterByClass("ilrepositorygui", "ref_id", $row["child"]); - $this->tpl->setVariable("ITEM", $this->lng->txt("repository")); - $this->tpl->setVariable("LINK_ITEM", - $ilCtrl->getLinkTargetByClass("ilrepositorygui", "")); - } - $ilCtrl->setParameterByClass("ilrepositorygui", "ref_id", $_GET["ref_id"]); - - $this->tpl->parseCurrentBlock(); - } - - $this->tpl->setVariable("TXT_LOCATOR",$this->lng->txt("locator")); - $this->tpl->parseCurrentBlock(); - } - /** * should be overwritten to add object specific items * (repository items are preloaded) From 742bc877fbb14b1d1bc15d6f68951791e2c3ce83 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 11:20:33 +0200 Subject: [PATCH 078/166] Fixed locator in administration section --- .../classes/class.ilObjRoleGUI.php | 27 ++++++++++--------- .../classes/class.ilObjRoleTemplateGUI.php | 11 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Services/AccessControl/classes/class.ilObjRoleGUI.php b/Services/AccessControl/classes/class.ilObjRoleGUI.php index 3e4cde94ef92..01ebbf647511 100755 --- a/Services/AccessControl/classes/class.ilObjRoleGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleGUI.php @@ -1513,33 +1513,34 @@ function __setHeader() /** - * should be overwritten to add object specific items - * (repository items are preloaded) - */ - function addAdminLocatorItems($a_do_not_add_object = false) + * @inheritdoc + */ + protected function addAdminLocatorItems($a_do_not_add_object = false) { global $DIC; $ilLocator = $DIC['ilLocator']; - if ($_GET["admin_mode"] == "settings" + if( + $_GET["admin_mode"] == "settings" && $_GET["ref_id"] == ROLE_FOLDER_ID) // system settings { parent::addAdminLocatorItems(true); - $ilLocator->addItem($this->lng->txt("obj_".ilObject::_lookupType( - ilObject::_lookupObjId($_GET["ref_id"]))), - $this->ctrl->getLinkTargetByClass("ilobjrolefoldergui", "view")); + $ilLocator->addItem( + $this->lng->txt("obj_".ilObject::_lookupType(ilObject::_lookupObjId($_GET["ref_id"]))), + $this->ctrl->getLinkTargetByClass("ilobjrolefoldergui", 'view') + ); if ($_GET["obj_id"] > 0) { - $ilLocator->addItem($this->object->getTitle(), - $this->ctrl->getLinkTarget($this, "view")); + $ilLocator->addItem( + $this->object->getTitle(), + $this->ctrl->getLinkTarget($this, 'perm')); } } - else // repository administration - { - // ? + else { + parent::addAdminLocatorItems($a_do_not_add_object); } } diff --git a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php index ede78d3061c0..a8df129334ae 100755 --- a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php @@ -675,12 +675,10 @@ function cancelObject() - /** - * should be overwritten to add object specific items - * (repository items are preloaded) - */ - function addAdminLocatorItems($a_do_not_add_object = false) + * @inheritdoc + */ + protected function addAdminLocatorItems($a_do_not_add_object = false) { global $DIC; @@ -692,6 +690,7 @@ function addAdminLocatorItems($a_do_not_add_object = false) ilObject::_lookupObjId($_GET["ref_id"])), $this->ctrl->getLinkTargetByClass("ilobjrolefoldergui", "view")); } - + + } // END class.ilObjRoleTemplateGUI ?> From f126d179988ab685862d344ac47f19f1c23718b1 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 12:07:30 +0200 Subject: [PATCH 079/166] Fixed locator for repository role location --- .../classes/class.ilObjRoleGUI.php | 32 +++++++++++++++++-- Services/Object/classes/class.ilObjectGUI.php | 13 -------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/Services/AccessControl/classes/class.ilObjRoleGUI.php b/Services/AccessControl/classes/class.ilObjRoleGUI.php index 01ebbf647511..5fda1c59a91b 100755 --- a/Services/AccessControl/classes/class.ilObjRoleGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleGUI.php @@ -1842,8 +1842,34 @@ protected function addToClipboardObject() ilUtil::sendSuccess($this->lng->txt('clipboard_user_added'),true); $ilCtrl->redirect($this, 'userassignment'); } - - - + + /** + * @inheritdoc + */ + protected function addLocatorItems() + { + global $DIC; + + $ilLocator = $DIC['ilLocator']; + + if($_GET["admin_mode"] == "") + { + $this->ctrl->setParameterByClass( + "ilobjrolegui", + "obj_id", + (int) $_GET["obj_id"] + ); + $ilLocator->addItem( + ilObjRole::_getTranslation($this->object->getTitle()), + $this->ctrl->getLinkTargetByClass( + array( + "ilpermissiongui", + "ilobjrolegui"), + "perm") + ); + } + + } + } // END class.ilObjRoleGUI ?> diff --git a/Services/Object/classes/class.ilObjectGUI.php b/Services/Object/classes/class.ilObjectGUI.php index dec747901bb0..de222c0c74dc 100755 --- a/Services/Object/classes/class.ilObjectGUI.php +++ b/Services/Object/classes/class.ilObjectGUI.php @@ -653,19 +653,6 @@ protected function setLocator() { $this->addLocatorItems(); } - - // not so nice workaround: todo: handle $ilLocator as tabs in ilTemplate - if ($_GET["admin_mode"] == "" && - strtolower($this->ctrl->getCmdClass()) == "ilobjrolegui") - { - $this->ctrl->setParameterByClass("ilobjrolegui", - "rolf_ref_id", $_GET["rolf_ref_id"]); - $this->ctrl->setParameterByClass("ilobjrolegui", - "obj_id", $_GET["obj_id"]); - $ilLocator->addItem($this->lng->txt("role"), - $this->ctrl->getLinkTargetByClass(array("ilpermissiongui", - "ilobjrolegui"), "perm")); - } $tpl->setLocator(); } From bcd81e08fdb6212475bcc417a82dc49d1d055302 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 12:33:17 +0200 Subject: [PATCH 080/166] Fixed tab activation for role templates --- .../classes/class.ilObjRoleTemplateGUI.php | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php index a8df129334ae..81283130b79a 100755 --- a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php @@ -17,7 +17,6 @@ */ class ilObjRoleTemplateGUI extends ilObjectGUI { - const FORM_MODE_EDIT = 1; const FORM_MODE_CREATE = 2; @@ -184,6 +183,8 @@ public function editObject(ilPropertyFormGUI $form = null) global $DIC; $rbacsystem = $DIC['rbacsystem']; + + $this->tabs_gui->activateTab('settings'); if (!$rbacsystem->checkAccess("write", $this->rolf_ref_id)) { @@ -299,6 +300,8 @@ function permObject() exit(); } + $this->tabs_gui->activateTab('perm'); + $to_filter = $objDefinition->getSubobjectsToFilter(); $tpl_filter = array(); @@ -644,26 +647,35 @@ function getAdminTabs() { $this->getTabs(); } - - function getTabs() + + /** + * @inheritdoc + */ + protected function getTabs() { global $DIC; - $rbacsystem = $DIC['rbacsystem']; + $rbacsystem = $DIC->rbac()->system(); - if ($rbacsystem->checkAccess('write',$this->rolf_ref_id)) + if($rbacsystem->checkAccess('write',$this->ref_id)) { - $this->tabs_gui->addTarget("settings", - $this->ctrl->getLinkTarget($this, "edit"), - array("edit","update"), get_class($this)); - - $this->tabs_gui->addTarget("default_perm_settings", - $this->ctrl->getLinkTarget($this, "perm"), - array("perm"), get_class($this)); + $this->tabs_gui->addTab( + 'settings', + $this->lng->txt('settings'), + $this->ctrl->getLinkTarget($this,'edit') + ); + } + if($rbacsystem->checkAccess('edit_permission',$this->ref_id)) + { + $this->tabs_gui->addTab( + 'perm', + $this->lng->txt('default_perm_settings'), + $this->ctrl->getLinkTarget($this,'perm') + ); + } } - /** * cancelObject is called when an operation is canceled, method links back * @access public From 621dd85680c7b4bd9df54eddf3fce086daceebed Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 14:27:02 +0200 Subject: [PATCH 081/166] fixed 0017789: DigiLib Book is back! --- .../classes/class.ilObjRoleTemplateGUI.php | 376 ++++++------------ ...ss.ilObjectRoleTemplateOptionsTableGUI.php | 28 +- ...ilObjectRoleTemplatePermissionTableGUI.php | 27 +- 3 files changed, 160 insertions(+), 271 deletions(-) diff --git a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php index 81283130b79a..3591677eb3ad 100755 --- a/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php +++ b/Services/AccessControl/classes/class.ilObjRoleTemplateGUI.php @@ -279,285 +279,124 @@ public function saveObject() $this->createObject($form); } + /** - * display permissions - * - * @access public - */ - function permObject() + * Show role template permissions + */ + protected function permObject() { global $DIC; - $rbacadmin = $DIC['rbacadmin']; - $rbacreview = $DIC['rbacreview']; - $rbacsystem = $DIC['rbacsystem']; + /** + * @var ilRbacSystem + */ + $rbacsystem = $DIC->rbac()->system(); + + /** + * @var ilErrorHandling + */ + $ilErr = $DIC['ilErr']; + + /** + * @var ilObjectDefinition + */ $objDefinition = $DIC['objDefinition']; - $ilSetting = $DIC['ilSetting']; - if (!$rbacsystem->checkAccess('write',$this->rolf_ref_id)) + if(!$rbacsystem->checkAccess('edit_permission', $this->ref_id)) { - $this->ilias->raiseError($this->lng->txt("msg_no_perm_perm"),$this->ilias->error_obj->WARNING); - exit(); + $ilErr->raiseError($this->lng->txt('msg_no_perm_perm'),$ilErr->MESSAGE); + return true; } - $this->tabs_gui->activateTab('perm'); - $to_filter = $objDefinition->getSubobjectsToFilter(); - - $tpl_filter = array(); - $internal_tpl = false; + $this->tpl->addBlockFile( + 'ADM_CONTENT', + 'adm_content', + 'tpl.rbac_template_permissions.html', + 'Services/AccessControl' + ); - if (($internal_tpl = $this->object->isInternalTemplate())) - { - $tpl_filter = $this->object->getFilterOfInternalTemplate(); - } - $op_order = array(); + $this->tpl->setVariable('PERM_ACTION',$this->ctrl->getFormAction($this)); - foreach(ilRbacReview::_getOperationList() as $op) - { - $op_order[$op["ops_id"]] = $op["order"]; - } + include_once './Services/Accordion/classes/class.ilAccordionGUI.php'; + $acc = new ilAccordionGUI(); + $acc->setBehaviour(ilAccordionGUI::FORCE_ALL_OPEN); + $acc->setId('template_perm_'.$this->ref_id); - $operation_info = $rbacreview->getOperationAssignment(); + $subs = $objDefinition->getSubObjectsRecursively('root','true',false); - foreach($operation_info as $info) + $sorted = array(); + foreach($subs as $subtype => $def) { - if($objDefinition->getDevMode($info['type'])) - { - continue; - } - // FILTER SUBOJECTS OF adm OBJECT - if(in_array($info['type'],$to_filter)) - { - continue; - } - if ($internal_tpl and $tpl_filter and !in_array($info['type'],$tpl_filter)) - { - continue; - } - $rbac_objects[$info['typ_id']] = array("obj_id" => $info['typ_id'], - "type" => $info['type']); - - $txt = $objDefinition->isPlugin($info['type']) - ? ilObjectPlugin::lookupTxtById($info['type'], $info['type']."_".$info['operation']) - : $this->lng->txt($info['type']."_".$info['operation']); - if (substr($info['operation'], 0, 7) == "create_" && - $objDefinition->isPlugin(substr($info['operation'], 7))) - { - $txt = ilObjectPlugin::lookupTxtById(substr($info['operation'], 7), $info['type']."_".$info['operation']); - } - elseif(substr($info['operation'],0,6) == 'create') - { - $txt = $this->lng->txt('rbac_'.$info['operation']); - } - - $order = $op_order[$info['ops_id']]; - if(substr($info['operation'],0,6) == 'create') + if($objDefinition->isPlugin($subtype)) { - $order = $objDefinition->getPositionByType($info['type']); + $translation = ilObjectPlugin::lookupTxtById($subtype,"obj_".$subtype); } - - $rbac_operations[$info['typ_id']][$info['ops_id']] = array( - "ops_id" => $info['ops_id'], - "title" => $info['operation'], - "name" => $txt, - "order" => $order); - } - - foreach ($rbac_objects as $key => $obj_data) - { - if ($objDefinition->isPlugin($obj_data["type"])) + elseif($objDefinition->isSystemObject($subtype)) { - $rbac_objects[$key]["name"] = ilObjectPlugin::lookupTxtById($obj_data["type"], - "obj_".$obj_data["type"]); + $translation = $this->lng->txt("obj_".$subtype); } else { - $rbac_objects[$key]["name"] = $this->lng->txt("obj_".$obj_data["type"]); + $translation = $this->lng->txt('objs_'.$subtype); } - $rbac_objects[$key]["ops"] = $rbac_operations[$key]; - } - - sort($rbac_objects); - - foreach ($rbac_objects as $key => $obj_data) - { - sort($rbac_objects[$key]["ops"]); + $sorted[$subtype] = $def; + $sorted[$subtype]['translation'] = $translation; } - - // sort by (translated) name of object type - $rbac_objects = ilUtil::sortArray($rbac_objects,"name","asc"); - // BEGIN CHECK_PERM - foreach ($rbac_objects as $key => $obj_data) + $sorted = ilUtil::sortArray($sorted, 'translation','asc',true,true); + foreach($sorted as $subtype => $def) { - $arr_selected = $rbacreview->getOperationsOfRole($this->object->getId(), $obj_data["type"], $this->rolf_ref_id); - $arr_checked = array_intersect($arr_selected,array_keys($rbac_operations[$obj_data["obj_id"]])); - - foreach ($rbac_operations[$obj_data["obj_id"]] as $operation) + if($objDefinition->isPlugin($subtype)) { - $checked = in_array($operation["ops_id"],$arr_checked); - $disabled = false; - - // Es wird eine 2-dim Post Variable �bergeben: perm[rol_id][ops_id] - $box = ilUtil::formCheckBox($checked,"template_perm[".$obj_data["type"]."][]",$operation["ops_id"],$disabled); - $output["perm"][$obj_data["obj_id"]][$operation["ops_id"]] = $box; + $translation = ilObjectPlugin::lookupTxtById($subtype,"obj_".$subtype); } - } - // END CHECK_PERM - - $output["col_anz"] = count($rbac_objects); - $output["txt_save"] = $this->lng->txt("save"); - $output["check_protected"] = ilUtil::formCheckBox($rbacreview->isProtected($this->rolf_ref_id,$this->object->getId()),"protected",1); - $output["text_protected"] = $this->lng->txt("role_protect_permissions"); - -/************************************/ -/* adopt permissions form */ -/************************************/ - - $output["message_middle"] = $this->lng->txt("adopt_perm_from_template"); - - // send message for system role - if ($this->object->getId() == SYSTEM_ROLE_ID) - { - $output["adopt"] = array(); - ilUtil::sendFailure($this->lng->txt("msg_sysrole_not_editable")); - } - else - { - // BEGIN ADOPT_PERMISSIONS - $parent_role_ids = $rbacreview->getParentRoleIds($this->rolf_ref_id,true); - - // sort output for correct color changing - ksort($parent_role_ids); - - foreach ($parent_role_ids as $key => $par) + elseif($objDefinition->isSystemObject($subtype)) { - if ($par["obj_id"] != SYSTEM_ROLE_ID) - { - $radio = ilUtil::formRadioButton(0,"adopt",$par["obj_id"]); - $output["adopt"][$key]["css_row_adopt"] = ilUtil::switchColor($key, "tblrow1", "tblrow2"); - $output["adopt"][$key]["check_adopt"] = $radio; - $output["adopt"][$key]["type"] = ($par["type"] == 'role' ? 'Role' : 'Template'); - $output["adopt"][$key]["role_name"] = $par["title"]; - } + $translation = $this->lng->txt("obj_".$subtype); } - - $output["formaction_adopt"] = $this->ctrl->getFormAction($this); - // END ADOPT_PERMISSIONS - } - - $output["formaction"] = - $this->ctrl->getFormAction($this); - - $this->data = $output; - - -/************************************/ -/* generate output */ -/************************************/ - - $this->tpl->addBlockFile("ADM_CONTENT", "adm_content", "tpl.adm_perm_role.html", - "Services/AccessControl"); - - foreach ($rbac_objects as $obj_data) - { - // BEGIN object_operations - $this->tpl->setCurrentBlock("object_operations"); - - $obj_data["ops"] = ilUtil::sortArray($obj_data["ops"], 'order','asc',true,true); - - foreach ($obj_data["ops"] as $operation) - { - $ops_ids[] = $operation["ops_id"]; - - $css_row = ilUtil::switchColor($key, "tblrow1", "tblrow2"); - $this->tpl->setVariable("CSS_ROW",$css_row); - $this->tpl->setVariable("PERMISSION",$operation["name"]); - $this->tpl->setVariable("CHECK_PERMISSION",$this->data["perm"][$obj_data["obj_id"]][$operation["ops_id"]]); - $this->tpl->parseCurrentBlock(); - } // END object_operations - - // BEGIN object_type - $this->tpl->setCurrentBlock("object_type"); - $this->tpl->setVariable("TXT_OBJ_TYPE",$obj_data["name"]); - -// TODO: move this if in a function and query all objects that may be disabled or inactive - if ($this->objDefinition->getDevMode($obj_data["type"])) + else { - $this->tpl->setVariable("TXT_NOT_IMPL", "(".$this->lng->txt("not_implemented_yet").")"); + $translation = $this->lng->txt('objs_'.$subtype); } - - // js checkbox toggles - $this->tpl->setVariable("JS_VARNAME","template_perm_".$obj_data["type"]); - $this->tpl->setVariable("JS_ONCLICK",ilUtil::array_php2js($ops_ids)); - $this->tpl->setVariable("TXT_CHECKALL", $this->lng->txt("check_all")); - $this->tpl->setVariable("TXT_UNCHECKALL", $this->lng->txt("uncheck_all")); - - - $this->tpl->parseCurrentBlock(); - // END object_type - } - - /* - // BEGIN ADOPT PERMISSIONS - foreach ($this->data["adopt"] as $key => $value) - { - $this->tpl->setCurrentBlock("ADOPT_PERM_ROW"); - $this->tpl->setVariable("CSS_ROW_ADOPT",$value["css_row_adopt"]); - $this->tpl->setVariable("CHECK_ADOPT",$value["check_adopt"]); - $this->tpl->setVariable("TYPE",$value["type"]); - $this->tpl->setVariable("ROLE_NAME",$value["role_name"]); - $this->tpl->parseCurrentBlock(); - } - - $this->tpl->setCurrentBlock("ADOPT_PERM_FORM"); - $this->tpl->setVariable("MESSAGE_MIDDLE",$this->data["message_middle"]); - $this->tpl->setVariable("FORMACTION_ADOPT",$this->data["formaction_adopt"]); - $this->tpl->setVariable("ADOPT",$this->lng->txt('copy')); - $this->tpl->parseCurrentBlock(); - // END ADOPT PERMISSIONS - */ - - $this->tpl->setCurrentBlock("tblfooter_protected"); - $this->tpl->setVariable("COL_ANZ",3); - $this->tpl->setVariable("CHECK_BOTTOM",$this->data["check_protected"]); - $this->tpl->setVariable("MESSAGE_TABLE",$this->data["text_protected"]); - $this->tpl->parseCurrentBlock(); - - $this->tpl->setVariable("COL_ANZ_PLUS",4); - $this->tpl->setVariable("TXT_SAVE",$this->data["txt_save"]); - $this->tpl->setCurrentBlock("adm_content"); - $this->tpl->setVariable("TBL_TITLE_IMG",ilUtil::getImagePath("icon_".$this->object->getType().".svg")); - $this->tpl->setVariable("TBL_TITLE_IMG_ALT",$this->lng->txt($this->object->getType())); - - // compute additional information in title - if (substr($this->object->getTitle(),0,3) == "il_") - { - $desc = $this->lng->txt("predefined_template");//$this->lng->txt("obj_".$parent_node['type'])." (".$parent_node['obj_id'].") : ".$parent_node['title']; - } - - $description = "
     ".$desc.""; - // translation for autogenerated roles - if (substr($this->object->getTitle(),0,3) == "il_") - { - include_once('./Services/AccessControl/classes/class.ilObjRole.php'); + $tbl = new ilObjectRoleTemplatePermissionTableGUI( + $this, + 'perm', + $this->ref_id, + $this->obj_id, + $subtype, + false + ); + $tbl->setShowChangeExistingObjects(false); + $tbl->parse(); + + $acc->addItem($translation, $tbl->getHTML()); + } + + $this->tpl->setVariable('ACCORDION',$acc->getHTML()); + + // Add options table + include_once './Services/AccessControl/classes/class.ilObjectRoleTemplateOptionsTableGUI.php'; + $options = new ilObjectRoleTemplateOptionsTableGUI( + $this, + 'perm', + $this->ref_id, + $this->obj_id, + false + ); + $options->setShowOptions(false); + $options->addMultiCommand( + 'permSave', + $this->lng->txt('save') + ); + + $options->parse(); + $this->tpl->setVariable('OPTIONS_TABLE',$options->getHTML()); + } - $title = ilObjRole::_getTranslation($this->object->getTitle())." (".$this->object->getTitle().")"; - } - else - { - $title = $this->object->getTitle(); - } - $this->tpl->setVariable("TBL_TITLE",$title.$description); - - $this->tpl->setVariable("TXT_PERMISSION",$this->data["txt_permission"]); - $this->tpl->setVariable("FORMACTION",$this->data["formaction"]); - $this->tpl->parseCurrentBlock(); - } /** @@ -565,40 +404,48 @@ function permObject() * * @access public */ - function permSaveObject() + protected function permSaveObject() { global $DIC; - $rbacadmin = $DIC['rbacadmin']; - $rbacsystem = $DIC['rbacsystem']; - $rbacreview = $DIC['rbacreview']; + /** + * @var ilRbacSystem + */ + $rbacsystem = $DIC->rbac()->system(); + + /** + * @var ilRbacAdmin + */ + $rbacadmin = $DIC->rbac()->admin(); + + /** + * @var ilErrorHandling + */ + $ilErr = $DIC['ilErr']; + + /** + * @var ilObjectDefinition + */ $objDefinition = $DIC['objDefinition']; + if (!$rbacsystem->checkAccess('write',$this->rolf_ref_id)) { - $this->ilias->raiseError($this->lng->txt("msg_no_perm_perm"),$this->ilias->error_obj->WARNING); + $ilErr->raiseError($this->lng->txt('msg_no_perm_perm'),$ilErr->MESSAGE); + return true; } - else - { - // Alle Template Eintraege loeschen - $rbacadmin->deleteRolePermission($this->object->getId(), $this->rolf_ref_id); + // delete all existing template entries + $rbacadmin->deleteRolePermission($this->object->getId(), $this->ref_id); - foreach ($_POST["template_perm"] as $key => $ops_array) - { - // Setzen der neuen template permissions - $rbacadmin->setRolePermission($this->object->getId(), $key,$ops_array,$this->rolf_ref_id); - } + foreach ($_POST["template_perm"] as $key => $ops_array) + { + $rbacadmin->setRolePermission($this->object->getId(), $key,$ops_array,$this->rolf_ref_id); } - + // update object data entry (to update last modification date) $this->object->update(); - // set protected flag - // not applicable for role templates - #$rbacadmin->setProtected($this->rolf_ref_id,$this->object->getId(),ilUtil::tf2yn($_POST['protected'])); - ilUtil::sendSuccess($this->lng->txt("saved_successfully"),true); - $this->ctrl->redirect($this, "perm"); } @@ -672,7 +519,6 @@ protected function getTabs() $this->lng->txt('default_perm_settings'), $this->ctrl->getLinkTarget($this,'perm') ); - } } diff --git a/Services/AccessControl/classes/class.ilObjectRoleTemplateOptionsTableGUI.php b/Services/AccessControl/classes/class.ilObjectRoleTemplateOptionsTableGUI.php index 24f0d8213720..f41d779f530c 100644 --- a/Services/AccessControl/classes/class.ilObjectRoleTemplateOptionsTableGUI.php +++ b/Services/AccessControl/classes/class.ilObjectRoleTemplateOptionsTableGUI.php @@ -19,6 +19,7 @@ class ilObjectRoleTemplateOptionsTableGUI extends ilTable2GUI private $obj_ref_id = null; private $show_admin_permissions = true; + private $show_options = true; /** * Constructor @@ -60,6 +61,24 @@ public function __construct($a_parent_obj,$a_parent_cmd, $a_obj_ref_id,$a_role_i $this->setTopCommands(false); } + + /** + * Set show options + * @param bool show/hide options + * + */ + public function setShowOptions($a_status) + { + $this->show_options = $a_status; + } + + /** + * @return bool + */ + public function getShowOptions() + { + return $this->show_options; + } /** @@ -91,6 +110,12 @@ public function fillRow($row) $rbacreview = $DIC['rbacreview']; + + if(!$this->getShowOptions()) + { + return true; + } + if(isset($row['recursive']) and !$this->show_admin_permissions) { $this->tpl->setCurrentBlock('recursive'); @@ -134,7 +159,6 @@ public function parse() $row[1]['protected'] = 1; $this->setData($row); - } } -?> \ No newline at end of file +?> diff --git a/Services/AccessControl/classes/class.ilObjectRoleTemplatePermissionTableGUI.php b/Services/AccessControl/classes/class.ilObjectRoleTemplatePermissionTableGUI.php index 58e5e6f4c79d..a22148f5009d 100644 --- a/Services/AccessControl/classes/class.ilObjectRoleTemplatePermissionTableGUI.php +++ b/Services/AccessControl/classes/class.ilObjectRoleTemplatePermissionTableGUI.php @@ -23,6 +23,7 @@ class ilObjectRoleTemplatePermissionTableGUI extends ilTable2GUI private $tpl_type = ''; private $show_admin_permissions = false; + private $show_change_existing_objects = true; private static $template_permissions = NULL; @@ -70,7 +71,23 @@ public function __construct($a_parent_obj,$a_parent_cmd, $a_ref_id,$a_role_id,$a $this->initTemplatePermissions(); } - + + /** + * @param bool $a_status + */ + public function setShowChangeExistingObjects($a_status) + { + $this->show_change_existing_objects = $a_status; + } + + /** + * @return bool + */ + public function getShowChangeExistingObjects() + { + return $this->show_change_existing_objects; + } + /** * * @return @@ -174,7 +191,7 @@ public function fillRow($row) $this->tpl->setCurrentBlock('ce_desc_td'); $this->tpl->setVariable('CE_DESC_TYPE',$this->getTemplateType()); $this->tpl->setVariable('CE_LONG',$this->lng->txt('change_existing_object_type_desc')); - + if($objDefinition->isSystemObject($this->getTemplateType())) { $this->tpl->setVariable("TXT_CE", @@ -189,7 +206,6 @@ public function fillRow($row) ? ilObjectPlugin::lookupTxtById($this->getTemplateType(), "objs_".$this->getTemplateType()) : $this->lng->txt('objs_'.$this->getTemplateType()); - $this->tpl->setVariable('TXT_CE', $this->lng->txt('change_existing_prefix').' '. $pl_txt.' '. @@ -304,7 +320,10 @@ public function parse() $rows[] = $perm; } - if(!$this->show_admin_permissions) + if( + !$this->show_admin_permissions && + $this->getShowChangeExistingObjects() + ) { $rows[] = array('show_ce' => 1); } From 6cf08b259c863b5e45a3a99261670b39aaf7f40f Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Tue, 24 Jul 2018 15:11:31 +0200 Subject: [PATCH 082/166] Tweak style fix --- .../templates/default/tpl.role_row.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Services/AccessControl/templates/default/tpl.role_row.html b/Services/AccessControl/templates/default/tpl.role_row.html index 7e857233ac18..0db0c36b909e 100644 --- a/Services/AccessControl/templates/default/tpl.role_row.html +++ b/Services/AccessControl/templates/default/tpl.role_row.html @@ -1,10 +1,10 @@ - - + + - + {VAL_TITLE} @@ -17,13 +17,13 @@
    {VAL_PRE}
    - + {ROLE_TYPE} - + {CONTEXT} - + {COPY_TEXT} From 45b745c58e5764a0af809f986b3a21731c2a67fc Mon Sep 17 00:00:00 2001 From: mjansen Date: Tue, 24 Jul 2018 16:36:31 +0200 Subject: [PATCH 083/166] Mail: Adds type hints and return types / Removed require_once directives --- .../Mail/classes/Address/Parser/RFC822.php | 6 +- .../class.ilBaseMailRfc822AddressParser.php | 24 ++-- .../class.ilMailImapRfc822AddressParser.php | 26 ++--- ...s.ilMailPearRfc822WrapperAddressParser.php | 26 ++--- .../class.ilMailRfc822AddressParser.php | 15 +-- ...class.ilMailRfc822AddressParserFactory.php | 8 +- .../Type/class.ilBaseMailAddressType.php | 17 ++- .../Type/class.ilMailAddressTypeFactory.php | 27 ++--- .../Type/class.ilMailGroupAddressType.php | 41 +++---- ...s.ilMailLoginOrEmailAddressAddressType.php | 68 +++++------ .../class.ilMailMailingListAddressType.php | 54 ++++----- .../Type/class.ilMailRoleAddressType.php | 110 ++++++++---------- .../class.ilGroupNameAsMailValidator.php | 10 +- .../classes/Address/class.ilMailAddress.php | 10 +- .../interface.ilMailAddressType.php | 4 +- .../interface.ilMailRecipientParser.php | 2 +- 16 files changed, 196 insertions(+), 252 deletions(-) diff --git a/Services/Mail/classes/Address/Parser/RFC822.php b/Services/Mail/classes/Address/Parser/RFC822.php index 9c32734ca66b..44f2e362e5cf 100755 --- a/Services/Mail/classes/Address/Parser/RFC822.php +++ b/Services/Mail/classes/Address/Parser/RFC822.php @@ -197,8 +197,7 @@ function parseAddressList($address = null, $default_domain = null, $nest_groups if ($this->address === false || isset($this->error)) { // mjansen patch 14 Ap 2016 start - require_once 'Services/Mail/exceptions/class.ilMailException.php'; - throw new ilMailException($this->error); + throw new \ilMailException($this->error); // mjansen patch 14 Ap 2016 end } @@ -209,8 +208,7 @@ function parseAddressList($address = null, $default_domain = null, $nest_groups if ($valid === false || isset($this->error)) { // mjansen patch 14 Ap 2016 start - require_once 'Services/Mail/exceptions/class.ilMailException.php'; - throw new ilMailException($this->error); + throw new \ilMailException($this->error); // mjansen patch 14 Ap 2016 end } diff --git a/Services/Mail/classes/Address/Parser/class.ilBaseMailRfc822AddressParser.php b/Services/Mail/classes/Address/Parser/class.ilBaseMailRfc822AddressParser.php index 8b0169e0ea40..857b8081e77e 100644 --- a/Services/Mail/classes/Address/Parser/class.ilBaseMailRfc822AddressParser.php +++ b/Services/Mail/classes/Address/Parser/class.ilBaseMailRfc822AddressParser.php @@ -1,13 +1,11 @@ */ -abstract class ilBaseMailRfc822AddressParser implements ilMailRecipientParser +abstract class ilBaseMailRfc822AddressParser implements \ilMailRecipientParser { /** * @var string @@ -15,9 +13,9 @@ abstract class ilBaseMailRfc822AddressParser implements ilMailRecipientParser protected $addresses = ''; /** - * @param string $a_addresses + * @param string $a_addresses A comma separated list of email addresses */ - public function __construct($a_addresses) + public function __construct(string $a_addresses) { $this->addresses = $a_addresses; } @@ -25,31 +23,33 @@ public function __construct($a_addresses) /** * @return string */ - public function getAddresses() + public function getAddresses(): string { return $this->addresses; } /** + * A comma separated list of email addresses * @param string $addresses */ - public function setAddresses($addresses) + public function setAddresses(string $addresses) { $this->addresses = $addresses; } /** - * @param string $a_addresses - * @return ilMailAddress[] + * @param string $a_addresses A comma separated list of email addresses + * @return \ilMailAddress[] */ - protected abstract function parseAddressString($a_addresses); + protected abstract function parseAddressString(string $a_addresses): array; /** - * {@inheritdoc} + * @inheritdoc */ - public function parse() + public function parse(): array { $addresses = preg_replace('/;/', ',', trim($this->addresses)); + return $this->parseAddressString($addresses); } } \ No newline at end of file diff --git a/Services/Mail/classes/Address/Parser/class.ilMailImapRfc822AddressParser.php b/Services/Mail/classes/Address/Parser/class.ilMailImapRfc822AddressParser.php index ec070baa2968..c9d6b84a138e 100644 --- a/Services/Mail/classes/Address/Parser/class.ilMailImapRfc822AddressParser.php +++ b/Services/Mail/classes/Address/Parser/class.ilMailImapRfc822AddressParser.php @@ -1,34 +1,30 @@ */ -class ilMailImapRfc822AddressParser extends ilBaseMailRfc822AddressParser +class ilMailImapRfc822AddressParser extends \ilBaseMailRfc822AddressParser { /** - * {@inheritdoc} + * @inheritdoc */ - protected function parseAddressString($a_addresses) + protected function parseAddressString(string $a_addresses): array { - require_once 'Services/Mail/classes/class.ilMail.php'; - $parsed_addresses = imap_rfc822_parse_adrlist($a_addresses, ilMail::ILIAS_HOST); + $parsed_addresses = imap_rfc822_parse_adrlist($a_addresses, \ilMail::ILIAS_HOST); // #18992 - $valid_parsed_addresses = array_filter($parsed_addresses, function($address) { + $valid_parsed_addresses = array_filter($parsed_addresses, function ($address) { return '.SYNTAX-ERROR.' != $address->host; }); - if($parsed_addresses != $valid_parsed_addresses) - { - throw new ilMailException($a_addresses); + + if ($parsed_addresses != $valid_parsed_addresses) { + throw new \ilMailException($a_addresses); } - require_once 'Services/Mail/classes/Address/class.ilMailAddress.php'; - return array_map(function($address) { - return new ilMailAddress($address->mailbox, $address->host); + return array_map(function ($address) { + return new \ilMailAddress($address->mailbox, $address->host); }, $valid_parsed_addresses); } } \ No newline at end of file diff --git a/Services/Mail/classes/Address/Parser/class.ilMailPearRfc822WrapperAddressParser.php b/Services/Mail/classes/Address/Parser/class.ilMailPearRfc822WrapperAddressParser.php index 6b3c2fd44f4b..414dd6befef9 100644 --- a/Services/Mail/classes/Address/Parser/class.ilMailPearRfc822WrapperAddressParser.php +++ b/Services/Mail/classes/Address/Parser/class.ilMailPearRfc822WrapperAddressParser.php @@ -1,32 +1,26 @@ */ -class ilMailPearRfc822WrapperAddressParser extends ilBaseMailRfc822AddressParser +class ilMailPearRfc822WrapperAddressParser extends \ilBaseMailRfc822AddressParser { /** - * {@inheritdoc} + * @inheritdoc */ - public function parseAddressString($a_addresses) + public function parseAddressString(string $a_addresses): array { - if(strlen($a_addresses) == 0) - { - return array(); + if (strlen($a_addresses) == 0) { + return []; } - require_once 'Services/Mail/classes/class.ilMail.php'; - require_once 'Services/Mail/classes/Address/Parser/RFC822.php'; - $parser = new Mail_RFC822(); - $parsed_addresses = $parser->parseAddressList($a_addresses, ilMail::ILIAS_HOST, false, true); + $parser = new \Mail_RFC822(); + $parsed_addresses = $parser->parseAddressList($a_addresses, \ilMail::ILIAS_HOST, false, true); - require_once 'Services/Mail/classes/Address/class.ilMailAddress.php'; - return array_map(function($address) { - return new ilMailAddress($address->mailbox, $address->host); + return array_map(function ($address) { + return new \ilMailAddress($address->mailbox, $address->host); }, $parsed_addresses); } } \ No newline at end of file diff --git a/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParser.php b/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParser.php index 09f682a27449..5e43d7591561 100644 --- a/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParser.php +++ b/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParser.php @@ -1,34 +1,31 @@ */ -class ilMailRfc822AddressParser extends ilBaseMailRfc822AddressParser +class ilMailRfc822AddressParser extends \ilBaseMailRfc822AddressParser { /** - * @var ilBaseMailRfc822AddressParser + * @var \ilBaseMailRfc822AddressParser */ protected $aggregated_parser; /** - * @param ilBaseMailRfc822AddressParser $a_addresses + * @param \ilBaseMailRfc822AddressParser $a_addresses */ - public function __construct(ilBaseMailRfc822AddressParser $a_addresses) + public function __construct(\ilBaseMailRfc822AddressParser $a_addresses) { parent::__construct($a_addresses->getAddresses()); $this->aggregated_parser = $a_addresses; } /** - * {@inheritdoc} + * @inheritdoc */ - protected function parseAddressString($a_addresses) + protected function parseAddressString(string $a_addresses): array { - // @todo: Use an own ILIAS parser instead return $this->aggregated_parser->parse(); } } \ No newline at end of file diff --git a/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParserFactory.php b/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParserFactory.php index 6ee488cb395e..10d526f72f31 100644 --- a/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParserFactory.php +++ b/Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParserFactory.php @@ -9,12 +9,10 @@ class ilMailRfc822AddressParserFactory { /** * @param string $a_address - * @return ilMailRecipientParser + * @return \ilMailRecipientParser */ - public function getParser($a_address) + public function getParser(string $a_address): \ilMailRecipientParser { - require_once 'Services/Mail/classes/Address/Parser/class.ilMailPearRfc822WrapperAddressParser.php'; - require_once 'Services/Mail/classes/Address/Parser/class.ilMailRfc822AddressParser.php'; - return new ilMailRfc822AddressParser(new ilMailPearRfc822WrapperAddressParser($a_address)); + return new \ilMailRfc822AddressParser(new \ilMailPearRfc822WrapperAddressParser($a_address)); } } \ No newline at end of file diff --git a/Services/Mail/classes/Address/Type/class.ilBaseMailAddressType.php b/Services/Mail/classes/Address/Type/class.ilBaseMailAddressType.php index c963d2e192e7..70673ed5f457 100644 --- a/Services/Mail/classes/Address/Type/class.ilBaseMailAddressType.php +++ b/Services/Mail/classes/Address/Type/class.ilBaseMailAddressType.php @@ -1,13 +1,11 @@ */ -abstract class ilBaseMailAddressType implements ilMailAddressType +abstract class ilBaseMailAddressType implements \ilMailAddressType { /** * @var \ilMailAddress @@ -45,7 +43,7 @@ public function __construct(\ilMailAddress $a_address) } /** - * + * */ protected function init() { @@ -55,19 +53,20 @@ protected function init() * @param $a_sender_id integer * @return boolean */ - abstract protected function isValid($a_sender_id); + abstract protected function isValid(int $a_sender_id): bool; /** - * {@inheritdoc} + * @inheritdoc */ - public function validate($a_sender_id) + public function validate(int $a_sender_id): bool { $this->resetErrors(); + return $this->isValid($a_sender_id); } /** - * + * */ private function resetErrors() { @@ -77,7 +76,7 @@ private function resetErrors() /** * @return array */ - public function getErrors() + public function getErrors(): array { return $this->errors; } diff --git a/Services/Mail/classes/Address/Type/class.ilMailAddressTypeFactory.php b/Services/Mail/classes/Address/Type/class.ilMailAddressTypeFactory.php index 9a17a6eaeea9..3bf87786f929 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailAddressTypeFactory.php +++ b/Services/Mail/classes/Address/Type/class.ilMailAddressTypeFactory.php @@ -7,43 +7,40 @@ */ class ilMailAddressTypeFactory { - /** @var ilGroupNameAsMailValidator */ + /** @var \ilGroupNameAsMailValidator */ private $groupNameValidator; /** - * @param ilGroupNameAsMailValidator|null $groupNameValidator + * @param \ilGroupNameAsMailValidator|null $groupNameValidator */ - public function __construct(ilGroupNameAsMailValidator $groupNameValidator = null) + public function __construct(\ilGroupNameAsMailValidator $groupNameValidator = null) { if ($groupNameValidator === null) { - $groupNameValidator = new ilGroupNameAsMailValidator(ilMail::ILIAS_HOST); + $groupNameValidator = new \ilGroupNameAsMailValidator(\ilMail::ILIAS_HOST); } + $this->groupNameValidator = $groupNameValidator; } /** - * @param ilMailAddress $a_address - * @return ilMailAddressType + * @param \ilMailAddress $a_address + * @return \ilMailAddressType */ - public function getByPrefix(ilMailAddress $a_address) + public function getByPrefix(\ilMailAddress $a_address): \ilMailAddressType { switch(true) { case substr($a_address->getMailbox(), 0, 1) != '#' && substr($a_address->getMailbox(), 0, 2) != '"#': - require_once 'Services/Mail/classes/Address/Type/class.ilMailLoginOrEmailAddressAddressType.php'; - return new ilMailLoginOrEmailAddressAddressType($a_address); + return new \ilMailLoginOrEmailAddressAddressType($a_address); case substr($a_address->getMailbox(), 0, 7) == '#il_ml_': - require_once 'Services/Mail/classes/Address/Type/class.ilMailMailingListAddressType.php'; - return new ilMailMailingListAddressType($a_address); + return new \ilMailMailingListAddressType($a_address); case ($this->groupNameValidator->validate($a_address)): - require_once 'Services/Mail/classes/Address/Type/class.ilMailGroupAddressType.php'; - return new ilMailGroupAddressType($a_address); + return new \ilMailGroupAddressType($a_address); default: - require_once 'Services/Mail/classes/Address/Type/class.ilMailRoleAddressType.php'; - return new ilMailRoleAddressType($a_address); + return new \ilMailRoleAddressType($a_address); } } } diff --git a/Services/Mail/classes/Address/Type/class.ilMailGroupAddressType.php b/Services/Mail/classes/Address/Type/class.ilMailGroupAddressType.php index dd3cdece9f78..1122583d2ef1 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailGroupAddressType.php +++ b/Services/Mail/classes/Address/Type/class.ilMailGroupAddressType.php @@ -1,54 +1,47 @@ */ -class ilMailGroupAddressType extends ilBaseMailAddressType +class ilMailGroupAddressType extends \ilBaseMailAddressType { /** - * {@inheritdoc} + * @inheritdoc */ - public function isValid($a_sender_id) + public function isValid(int $a_sender_id): bool { - return ilUtil::groupNameExists(substr($this->address->getMailbox(), 1)); + return \ilUtil::groupNameExists(substr($this->address->getMailbox(), 1)); } /** - * {@inheritdoc} + * @inheritdoc */ - public function resolve() + public function resolve(): array { - $usr_ids = array(); + $usr_ids = []; $possibleGroupTitle = substr($this->address->getMailbox(), 1); - $possibleGroupObjId = ilObjGroup::_lookupIdByTitle($possibleGroupTitle); + $possibleGroupObjId = \ilObjGroup::_lookupIdByTitle($possibleGroupTitle); $grp_object = null; - foreach(ilObject::_getAllReferences($possibleGroupObjId) as $ref_id) - { - $grp_object = ilObjectFactory::getInstanceByRefId($ref_id); + foreach (\ilObject::_getAllReferences($possibleGroupObjId) as $ref_id) { + $grp_object = \ilObjectFactory::getInstanceByRefId($ref_id); break; } - if($grp_object instanceof ilObjGroup) - { - foreach($grp_object->getGroupMemberIds() as $usr_id) - { + if ($grp_object instanceof \ilObjGroup) { + foreach ($grp_object->getGroupMemberIds() as $usr_id) { $usr_ids[] = $usr_id; } - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Found the following group member user ids for address (object title) '%s' and obj_id %s: %s", $possibleGroupTitle, $possibleGroupObjId, implode(', ', array_unique($usr_ids)) + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Found the following group member user ids for address (object title) '%s' and obj_id %s: %s", + $possibleGroupTitle, $possibleGroupObjId, implode(', ', array_unique($usr_ids)) )); - } - else - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( + } else { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( "Did not find any group object for address (object title) '%s'", $possibleGroupTitle )); } diff --git a/Services/Mail/classes/Address/Type/class.ilMailLoginOrEmailAddressAddressType.php b/Services/Mail/classes/Address/Type/class.ilMailLoginOrEmailAddressAddressType.php index ca3448a826b9..062c71e7a87f 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailLoginOrEmailAddressAddressType.php +++ b/Services/Mail/classes/Address/Type/class.ilMailLoginOrEmailAddressAddressType.php @@ -1,39 +1,32 @@ */ -class ilMailLoginOrEmailAddressAddressType extends ilBaseMailAddressType +class ilMailLoginOrEmailAddressAddressType extends \ilBaseMailAddressType { /** - * {@inheritdoc} + * @inheritdoc */ - protected function isValid($a_sender_id) + protected function isValid(int $a_sender_id): bool { - - if($this->address->getHost() == ilMail::ILIAS_HOST) - { + if ($this->address->getHost() == ilMail::ILIAS_HOST) { $usr_id = ilObjUser::getUserIdByLogin($this->address->getMailbox()); - } - else - { + } else { $usr_id = false; } - if(!$usr_id && $this->address->getHost() == ilMail::ILIAS_HOST) - { - $this->errors[] = array('mail_recipient_not_found', $this->address->getMailbox()); + if (!$usr_id && $this->address->getHost() == ilMail::ILIAS_HOST) { + $this->errors[] = ['mail_recipient_not_found', $this->address->getMailbox()]; return false; } - require_once 'Services/Mail/classes/class.ilMailGlobalServices.php'; - if($usr_id && !$this->rbacsystem->checkAccessOfUser($usr_id, 'internal_mail', ilMailGlobalServices::getMailObjectRefId())) - { - $this->errors[] = array('user_cant_receive_mail', $this->address->getMailbox()); + if ( + $usr_id && + !$this->rbacsystem->checkAccessOfUser($usr_id, 'internal_mail', \ilMailGlobalServices::getMailObjectRefId())) { + $this->errors[] = ['user_cant_receive_mail', $this->address->getMailbox()]; return false; } @@ -41,33 +34,32 @@ protected function isValid($a_sender_id) } /** - * {@inheritdoc} + * @inheritdoc */ - public function resolve() + public function resolve(): array { - if($this->address->getHost() == ilMail::ILIAS_HOST) - { + if ($this->address->getHost() == \ilMail::ILIAS_HOST) { $address = $this->address->getMailbox(); - - } - else - { - $address = $this->address->getMailbox() . '@'. $this->address->getHost(); + + } else { + $address = $this->address->getMailbox() . '@' . $this->address->getHost(); } - $usr_ids = array_filter(array(ilObjUser::getUserIdByLogin($address))); + $usr_ids = array_filter([ + \ilObjUser::getUserIdByLogin($address) + ]); - if(count($usr_ids) > 0) - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Found the following user ids for address (login) '%s': %s", $address, implode(', ', array_unique($usr_ids)) - )); - } - else if(strlen($address) > 0) - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Did not find any user account for address (login) '%s'", $address + if (count($usr_ids) > 0) { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Found the following user ids for address (login) '%s': %s", $address, + implode(', ', array_unique($usr_ids)) )); + } else { + if (strlen($address) > 0) { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Did not find any user account for address (login) '%s'", $address + )); + } } return $usr_ids; diff --git a/Services/Mail/classes/Address/Type/class.ilMailMailingListAddressType.php b/Services/Mail/classes/Address/Type/class.ilMailMailingListAddressType.php index b306a245eb12..360cb756d912 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailMailingListAddressType.php +++ b/Services/Mail/classes/Address/Type/class.ilMailMailingListAddressType.php @@ -1,18 +1,16 @@ */ -class ilMailMailingListAddressType extends ilBaseMailAddressType +class ilMailMailingListAddressType extends \ilBaseMailAddressType { /** - * @var ilMailingLists|null + * @var \ilMailingLists|null */ - protected static $maling_lists; + protected static $mailingLists; /** * @@ -24,57 +22,51 @@ protected function init() } /** - * + * */ protected static function initMailingLists() { global $DIC; - if(self::$maling_lists === null) - { - require_once 'Services/Contact/classes/class.ilMailingLists.php'; - self::$maling_lists = new ilMailingLists($DIC->user()); + if (self::$mailingLists === null) { + self::$mailingLists = new \ilMailingLists($DIC->user()); } } /** - * {@inheritdoc} + * @inheritdoc */ - protected function isValid($a_sender_id) + protected function isValid(int $a_sender_id): bool { - $valid = self::$maling_lists->mailingListExists($this->address->getMailbox()); + $valid = self::$mailingLists->mailingListExists($this->address->getMailbox()); - if(!$valid) - { - $this->errors = array( - array('mail_no_valid_mailing_list', $this->address->getMailbox()) - ); + if (!$valid) { + $this->errors = [ + ['mail_no_valid_mailing_list', $this->address->getMailbox()] + ]; } return $valid; } /** - * {@inheritdoc} + * @inheritdoc */ - public function resolve() + public function resolve(): array { - $usr_ids = array(); + $usr_ids = []; - if(self::$maling_lists->mailingListExists($this->address->getMailbox())) - { - foreach(self::$maling_lists->getCurrentMailingList()->getAssignedEntries() as $entry) - { + if (self::$mailingLists->mailingListExists($this->address->getMailbox())) { + foreach (self::$mailingLists->getCurrentMailingList()->getAssignedEntries() as $entry) { $usr_ids[] = $entry['usr_id']; } - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Found the following user ids for address (mailing list title) '%s': %s", $this->address->getMailbox(), implode(', ', array_unique($usr_ids)) + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Found the following user ids for address (mailing list title) '%s': %s", + $this->address->getMailbox(), implode(', ', array_unique($usr_ids)) )); - } - else - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( + } else { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( "Did not find any user ids for address (mailing list title) '%s'", $this->address->getMailbox() )); } diff --git a/Services/Mail/classes/Address/Type/class.ilMailRoleAddressType.php b/Services/Mail/classes/Address/Type/class.ilMailRoleAddressType.php index 288b4bc022de..5a91d5f4173f 100644 --- a/Services/Mail/classes/Address/Type/class.ilMailRoleAddressType.php +++ b/Services/Mail/classes/Address/Type/class.ilMailRoleAddressType.php @@ -1,37 +1,33 @@ * @author Stefan Meyer * @author Michael Jansen */ -class ilMailRoleAddressType extends ilBaseMailAddressType +class ilMailRoleAddressType extends \ilBaseMailAddressType { /** * @var array */ - protected static $role_ids_by_address = array(); + protected static $role_ids_by_address = []; /** * @var array */ - protected static $may_send_to_global_roles = array(); + protected static $may_send_to_global_roles = []; /** - * @param ilMailAddress $a_address + * @param \ilMailAddress $a_address * @return array */ - protected static function getRoleIdsByAddress(ilMailAddress $a_address) + protected static function getRoleIdsByAddress(\ilMailAddress $a_address): array { $address = $a_address->getMailbox() . '@' . $a_address->getHost(); - if(!isset(self::$role_ids_by_address[$address])) - { + if (!isset(self::$role_ids_by_address[$address])) { self::$role_ids_by_address[$address] = self::searchRolesByMailboxAddressList($address); } @@ -42,19 +38,14 @@ protected static function getRoleIdsByAddress(ilMailAddress $a_address) * @param int $a_sender_id * @return bool */ - protected function maySendToGlobalRole($a_sender_id) + protected function maySendToGlobalRole(int $a_sender_id): bool { - if(!isset(self::$may_send_to_global_roles[$a_sender_id])) - { - if($a_sender_id == ANONYMOUS_USER_ID) - { + if (!isset(self::$may_send_to_global_roles[$a_sender_id])) { + if ($a_sender_id == ANONYMOUS_USER_ID) { self::$may_send_to_global_roles[$a_sender_id] = true; - } - else - { - require_once 'Services/Mail/classes/class.ilMailGlobalServices.php'; + } else { self::$may_send_to_global_roles[$a_sender_id] = $this->rbacsystem->checkAccessOfUser( - $a_sender_id, 'mail_to_global_roles', ilMailGlobalServices::getMailObjectRefId() + $a_sender_id, 'mail_to_global_roles', \ilMailGlobalServices::getMailObjectRefId() ); } } @@ -63,77 +54,74 @@ protected function maySendToGlobalRole($a_sender_id) } /** - * {@inheritdoc} + * @inheritdoc */ - public function isValid($a_sender_id) + public function isValid(int $a_sender_id): bool { $role_ids = self::getRoleIdsByAddress($this->address); - if(!self::maySendToGlobalRole($a_sender_id)) - { - foreach($role_ids as $role_id) - { - if($this->rbacreview->isGlobalRole($role_id)) - { + if (!self::maySendToGlobalRole($a_sender_id)) { + foreach ($role_ids as $role_id) { + if ($this->rbacreview->isGlobalRole($role_id)) { $this->errors[] = array('mail_to_global_roles_not_allowed', $this->address->getMailbox()); return false; } } } - if(count($role_ids) == 0) - { - $this->errors[] = array('mail_recipient_not_found', $this->address->getMailbox()); - return false; - } - else if(count($role_ids) > 1) - { - $this->errors[] = array('mail_multiple_role_recipients_found', $this->address->getMailbox(), implode(',', $role_ids)); + if (count($role_ids) == 0) { + $this->errors[] = ['mail_recipient_not_found', $this->address->getMailbox()]; return false; + } else { + if (count($role_ids) > 1) { + $this->errors[] = [ + 'mail_multiple_role_recipients_found', + $this->address->getMailbox(), + implode(',', $role_ids) + ]; + return false; + } } return true; } /** - * {@inheritdoc} + * @inheritdoc */ - public function resolve() + public function resolve(): array { - $usr_ids = array(); + $usr_ids = []; $role_ids = self::getRoleIdsByAddress($this->address); - if(count($role_ids) > 0) - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Found the following role ids for address '%s': %s", $this->address->getMailbox() . '@' . $this->address->getHost(), implode(', ', array_unique($role_ids)) + if (count($role_ids) > 0) { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Found the following role ids for address '%s': %s", + $this->address->getMailbox() . '@' . $this->address->getHost(), implode(', ', array_unique($role_ids)) )); - foreach($role_ids as $role_id) - { - foreach($this->rbacreview->assignedUsers($role_id) as $usr_id) - { + foreach ($role_ids as $role_id) { + foreach ($this->rbacreview->assignedUsers($role_id) as $usr_id) { $usr_ids[] = $usr_id; } } - if(count($usr_ids) > 0) - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Found the following user ids for roles determined by address '%s': %s", $this->address->getMailbox() . '@' . $this->address->getHost(), implode(', ', array_unique($usr_ids)) + if (count($usr_ids) > 0) { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Found the following user ids for roles determined by address '%s': %s", + $this->address->getMailbox() . '@' . $this->address->getHost(), + implode(', ', array_unique($usr_ids)) )); - } - else - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Did not find any assigned users for roles determined by '%s'", $this->address->getMailbox() . '@' . $this->address->getHost() + } else { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Did not find any assigned users for roles determined by '%s'", + $this->address->getMailbox() . '@' . $this->address->getHost() )); } - } - else - { - ilLoggerFactory::getLogger('mail')->debug(sprintf( - "Did not find any role (and user ids) for address '%s'", $this->address->getMailbox() . '@' . $this->address->getHost() + } else { + \ilLoggerFactory::getLogger('mail')->debug(sprintf( + "Did not find any role (and user ids) for address '%s'", + $this->address->getMailbox() . '@' . $this->address->getHost() )); } diff --git a/Services/Mail/classes/Address/Validator/class.ilGroupNameAsMailValidator.php b/Services/Mail/classes/Address/Validator/class.ilGroupNameAsMailValidator.php index 58b5adc5d833..5c0788149c3b 100644 --- a/Services/Mail/classes/Address/Validator/class.ilGroupNameAsMailValidator.php +++ b/Services/Mail/classes/Address/Validator/class.ilGroupNameAsMailValidator.php @@ -9,21 +9,21 @@ class ilGroupNameAsMailValidator /** * @param string $host */ - public function __construct($host) + public function __construct(string $host) { $this->host = $host; } /** * Validates if the given address contains a valid group name to send an email - * @param ilMailAddress $address + * @param \ilMailAddress $address * @return bool */ - public function validate(\ilMailAddress $address) + public function validate(\ilMailAddress $address): bool { $groupName = substr($address->getMailbox(), 1); - if (ilUtil::groupNameExists($groupName) && $this->isHostValid($address->getHost())) { + if (\ilUtil::groupNameExists($groupName) && $this->isHostValid($address->getHost())) { return true; } @@ -35,7 +35,7 @@ public function validate(\ilMailAddress $address) * @param string $host * @return bool */ - private function isHostValid($host) + private function isHostValid(string $host): bool { return ($host == $this->host || 0 === strlen($host)); } diff --git a/Services/Mail/classes/Address/class.ilMailAddress.php b/Services/Mail/classes/Address/class.ilMailAddress.php index f4c19764aeb5..a07d51c59aca 100644 --- a/Services/Mail/classes/Address/class.ilMailAddress.php +++ b/Services/Mail/classes/Address/class.ilMailAddress.php @@ -22,7 +22,7 @@ class ilMailAddress * @param string $mailbox * @param string $host */ - public function __construct($mailbox, $host) + public function __construct(string $mailbox, string $host) { $this->mailbox = $mailbox; $this->host = $host; @@ -31,7 +31,7 @@ public function __construct($mailbox, $host) /** * @param string $host */ - public function setHost($host) + public function setHost(string $host) { $this->host = $host; } @@ -39,7 +39,7 @@ public function setHost($host) /** * @param string $mailbox */ - public function setMailbox($mailbox) + public function setMailbox(string $mailbox) { $this->mailbox = $mailbox; } @@ -47,7 +47,7 @@ public function setMailbox($mailbox) /** * @return string */ - public function getHost() + public function getHost(): string { return $this->host; } @@ -55,7 +55,7 @@ public function getHost() /** * @return string */ - public function getMailbox() + public function getMailbox(): string { return $this->mailbox; } diff --git a/Services/Mail/interfaces/interface.ilMailAddressType.php b/Services/Mail/interfaces/interface.ilMailAddressType.php index 3424ddf7233f..97ffa8070969 100644 --- a/Services/Mail/interfaces/interface.ilMailAddressType.php +++ b/Services/Mail/interfaces/interface.ilMailAddressType.php @@ -11,11 +11,11 @@ interface ilMailAddressType * Returns an array of resolved user ids * @return int[] */ - public function resolve(); + public function resolve(): array; /** * @param $a_sender_id integer * @return bool */ - public function validate($a_sender_id); + public function validate(int $a_sender_id): bool; } \ No newline at end of file diff --git a/Services/Mail/interfaces/interface.ilMailRecipientParser.php b/Services/Mail/interfaces/interface.ilMailRecipientParser.php index 3c3dfd642f86..c0e993eccff9 100644 --- a/Services/Mail/interfaces/interface.ilMailRecipientParser.php +++ b/Services/Mail/interfaces/interface.ilMailRecipientParser.php @@ -10,5 +10,5 @@ interface ilMailRecipientParser /** * @return ilMailAddress[] */ - public function parse(); + public function parse(): array; } \ No newline at end of file From 5680d6edec80a539f3165a3dc0f6c95d5757ff7a Mon Sep 17 00:00:00 2001 From: Matthias Kunkel Date: Wed, 25 Jul 2018 10:06:31 +0200 Subject: [PATCH 084/166] Lang files changes cherry-picked from release_53 --- lang/ilias_de.lang | 4 ++-- lang/ilias_en.lang | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index dc0d577b126e..0bf3502321bf 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -14149,7 +14149,7 @@ cont#:#cont_assign_skills#:#Kompetenzen zuweisen dateplaner#:#cal_batch_file_downloads#:#Datei-Downloads aus Kalender stapelweise verarbeiten dateplaner#:#cal_batch_file_downloads_info#:#Erlaubt Herunterladen aller Dateien zu Einträgen in der gewählten Kalenderansicht dateplaner#:#cal_download_files#:#Dateien herunterladen -dateplaner#:#cal_calendar_download#:#Kalender herunterladen +dateplaner#:#cal_calendar_download#:#Kalender-Dateien pdfgen#:#cleanup#:#Bereinigen skmg#:#skmg_order_nr_info#:#Platziert die Kompetenz in der Liste der Kompetenzen. common#:#position_permission_settings#:#Rechte nach Positionen @@ -14198,7 +14198,7 @@ content#:#cont_sc_name_setting_fullname#:#Titel, Vorname und Nachname content#:#cont_sc_name_setting_salutation_lastname#:#Anrede und Nachname content#:#cont_sc_name_setting_first_name#:#Vorname content#:#cont_sc_name_setting_no_name#:#Kein Name -dateplaner#:#cal_download_files_started#:#Download der Dateien wurde gestartet. +dateplaner#:#cal_download_files_started#:#ILIAS erzeugt ein ZIP-Archiv mit allen zugehörigen Dateien. Sie können diese anschließend per Klick auf das Koffer-Icon in der Kopfzeile herunterladen. common#:#usr_name_undisclosed#:#Nicht freigegeben common#:#obj_exc_duplicate#:#Übung kopieren meta#:#adt_error_max_length#:#Der eingegebene Text ist zu lang. Bitte geben Sie einen kürzeren Text ein. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 33fc99ad0376..fc54a2d18e21 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -14197,7 +14197,7 @@ content#:#cont_sc_name_setting_fullname#:#Title, First name and Last name content#:#cont_sc_name_setting_salutation_lastname#:#Salutation and Last name content#:#cont_sc_name_setting_first_name#:#First name content#:#cont_sc_name_setting_no_name#:#no name -dateplaner#:#cal_download_files_started#:#Download of calendar files has started. +dateplaner#:#cal_download_files_started#:#ILIAS is generating a ZIP archive of all available files. You can download them by clicking on the bag icon in the top bar. poll#:#poll_voting_period_full_info#:#Voting period from %s to %s common#:#usr_name_undisclosed#:#Undisclosed common#:#obj_exc_duplicate#:#Copy Exercise From 29b1ac2a9b6806be5fd9acb766272a960619148f Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Wed, 25 Jul 2018 14:13:11 +0200 Subject: [PATCH 085/166] 0007383: Closed, temporarily available but visible groups: learner can access ("broken") content tab --- Modules/Group/classes/class.ilObjGroupGUI.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/Group/classes/class.ilObjGroupGUI.php b/Modules/Group/classes/class.ilObjGroupGUI.php index 36fbf45cbd84..39e9ca814d69 100755 --- a/Modules/Group/classes/class.ilObjGroupGUI.php +++ b/Modules/Group/classes/class.ilObjGroupGUI.php @@ -1046,7 +1046,7 @@ function getTabs() $ilHelp->setScreenIdComponent("grp"); - if ($rbacsystem->checkAccess('read',$this->ref_id)) + if ($ilAccess->checkAccess('read','',$this->ref_id)) { if ($this->object->getNewsTimeline()) { @@ -1054,8 +1054,11 @@ function getTabs() { $this->addContentTab(); } - $this->tabs_gui->addTab("news_timeline", $lng->txt("cont_news_timeline_tab"), - $this->ctrl->getLinkTargetByClass("ilnewstimelinegui", "show")); + $this->tabs_gui->addTab( + "news_timeline", + $lng->txt("cont_news_timeline_tab"), + $this->ctrl->getLinkTargetByClass("ilnewstimelinegui", "show") + ); if ($this->object->getNewsTimelineLandingPage()) { $this->addContentTab(); From a9140db23faec2d79f0e67fe1991ddfd1f432e92 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Wed, 25 Jul 2018 14:55:37 +0200 Subject: [PATCH 086/166] 0008873: Membership limitations regarding groups are called "Course groupings" --- Modules/Course/classes/class.ilObjCourseGrouping.php | 2 +- lang/ilias_de.lang | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/Course/classes/class.ilObjCourseGrouping.php b/Modules/Course/classes/class.ilObjCourseGrouping.php index 194f6a92e7d8..d5690be03964 100644 --- a/Modules/Course/classes/class.ilObjCourseGrouping.php +++ b/Modules/Course/classes/class.ilObjCourseGrouping.php @@ -602,7 +602,7 @@ public static function _checkGroupingDependencies(&$container_obj, $a_user_id = if(!$assigned_message) { self::$assignedObjects[] = $condition['target_obj_id']; - $assigned_message = $lng->txt('crs_grp_already_assigned'); + $assigned_message = $lng->txt('grp_grp_already_assigned'); } } diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 0bf3502321bf..32169ef64096 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -5180,6 +5180,7 @@ crs#:#crs_groupings#:#Kursgruppierungen crs#:#crs_groups_nr#:#Anzahl Gruppen crs#:#crs_grp_added_grouping#:#Eine neue Mitgliedschaftsbeschränkung wurde angelegt. crs#:#crs_grp_already_assigned#:#Da Sie bereits Mitglied in einem der Kurse sind, können Sie nicht einem weiteren Kurs aus der Liste beitreten! +grp#:#grp_grp_already_assigned#:#Da Sie bereits Mitglied in einer der Gruppen sind, können Sie nicht einer weiteren Gruppe aus der Liste beitreten! crs#:#crs_grp_assign_crs#:#Kurs zuordnen crs#:#crs_grp_assignments#:#Zuordnung von Kursmitgliedern zu Gruppen crs#:#crs_grp_enter_title#:#Bitte geben Sie einen Titel ein. From 84d962b85794da0ec78bd18b2d1ca8b05a0da180 Mon Sep 17 00:00:00 2001 From: smeyer-ilias Date: Wed, 25 Jul 2018 15:01:28 +0200 Subject: [PATCH 087/166] Fixed 0009229: Different messages in group and course --- Modules/Course/classes/class.ilObjCourseGUI.php | 1 + Modules/Group/classes/class.ilObjGroupGUI.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Modules/Course/classes/class.ilObjCourseGUI.php b/Modules/Course/classes/class.ilObjCourseGUI.php index 2f7596ffea1b..13a131d38e32 100755 --- a/Modules/Course/classes/class.ilObjCourseGUI.php +++ b/Modules/Course/classes/class.ilObjCourseGUI.php @@ -1189,6 +1189,7 @@ protected function initEditForm() $opt = new ilRadioOption($this->lng->txt('crs_subscription_options_password'),IL_CRS_SUBSCRIPTION_PASSWORD); $pass = new ilTextInputGUI($this->lng->txt("password"),'subscription_password'); + $pass->setRequired(true); $pass->setInfo($this->lng->txt('crs_reg_password_info')); $pass->setSubmitFormOnEnter(true); $pass->setSize(32); diff --git a/Modules/Group/classes/class.ilObjGroupGUI.php b/Modules/Group/classes/class.ilObjGroupGUI.php index 39e9ca814d69..33e066ac6aed 100755 --- a/Modules/Group/classes/class.ilObjGroupGUI.php +++ b/Modules/Group/classes/class.ilObjGroupGUI.php @@ -1517,6 +1517,7 @@ public function initForm($a_mode = 'edit', $a_omit_form_action = false) $opt_pass = new ilRadioOption($this->lng->txt('grp_pass_request'),GRP_REGISTRATION_PASSWORD); $pass = new ilTextInputGUI($this->lng->txt("password"),'password'); + $pass->setRequired(true); $pass->setInfo($this->lng->txt('grp_reg_password_info')); $pass->setValue($this->object->getPassword()); $pass->setSize(32); From 704478e73dac73ad604f0ece5e503172bead6e2f Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 25 Jul 2018 16:04:12 +0200 Subject: [PATCH 088/166] OnScreenChat: Fixed #23343 --- templates/default/less/Services/OnScreenChat/delos.less | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/default/less/Services/OnScreenChat/delos.less b/templates/default/less/Services/OnScreenChat/delos.less index 1a383b7a9da8..acac9669bf1f 100644 --- a/templates/default/less/Services/OnScreenChat/delos.less +++ b/templates/default/less/Services/OnScreenChat/delos.less @@ -342,6 +342,7 @@ .media-heading { text-overflow: ellipsis; overflow: hidden; + max-width: 225px; a { color: @link-color !important; From 5ec87a81e1ea411ddb3a5265277f69aa87e825af Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 25 Jul 2018 16:07:21 +0200 Subject: [PATCH 089/166] Mail: Adds type hints and return types / Removed require_once directives --- templates/default/delos.css | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/default/delos.css b/templates/default/delos.css index 62e6f9036dc7..a481fd48f4aa 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -15402,6 +15402,7 @@ body.ilPrtfPdfBody .ilPCMyCoursesToggle img { .ilOnScreenChatMenuItem .media-body .media-heading { text-overflow: ellipsis; overflow: hidden; + max-width: 225px; } .ilOnScreenChatMenuItem .media-body .media-heading a { color: #557196 !important; From 41c669f4d91fd693aed5216a44d79e7b0dc6ead7 Mon Sep 17 00:00:00 2001 From: mjansen Date: Wed, 25 Jul 2018 17:19:16 +0200 Subject: [PATCH 090/166] OnScreenChat: Fixed #23344 (jQuery include has to be before bootstrap include, otherwise the bootstrap utilities like Tooltip etc. don't work) --- Services/OnScreenChat/js/onscreenchat.js | 10 ++++++++++ .../templates/default/tpl.chat-window.html | 2 +- Services/UICore/classes/class.ilTemplate.php | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Services/OnScreenChat/js/onscreenchat.js b/Services/OnScreenChat/js/onscreenchat.js index c43680f9bd4a..3f60c1963a7b 100644 --- a/Services/OnScreenChat/js/onscreenchat.js +++ b/Services/OnScreenChat/js/onscreenchat.js @@ -1,6 +1,9 @@ (function($, $scope, $chat, $menu){ 'use strict'; + var bootstrapTooltip = $.fn.tooltip.noConflict(); + $.fn.bootstrapTt = bootstrapTooltip; + var TYPE_CONSTANT = 'osc'; var PREFIX_CONSTANT = TYPE_CONSTANT + '_'; @@ -254,6 +257,11 @@ getModule().container.append(conversationWindow); getModule().addMessagesOnOpen(conversation); + conversationWindow.find('[data-toggle="tooltip"]').bootstrapTt({ + container: 'body', + viewport: { selector: 'body', padding: 10 } + }); + var emoticonPanel = conversationWindow.find('[data-onscreenchat-emoticons-panel]'), messageField = conversationWindow.find('[data-onscreenchat-message]'); @@ -335,11 +343,13 @@ $template.find('[href="addUser"]').attr({ "title": il.Language.txt('chat_osc_add_user'), "data-onscreenchat-add": conversation.id, + "data-toggle": "tooltip", "data-placement": "auto" }); $template.find('.close').attr({ "title": il.Language.txt('close'), "data-onscreenchat-close": conversation.id, + "data-toggle": "tooltip", "data-placement": "auto" }); diff --git a/Services/OnScreenChat/templates/default/tpl.chat-window.html b/Services/OnScreenChat/templates/default/tpl.chat-window.html index e8d35ab826da..8e5f65212a55 100644 --- a/Services/OnScreenChat/templates/default/tpl.chat-window.html +++ b/Services/OnScreenChat/templates/default/tpl.chat-window.html @@ -1,7 +1,7 @@
    - {CONVERSATION_ICON} [[participants]] + {CONVERSATION_ICON} [[participants]]
    {ADD_ACTION} {CLOSE_ACTION} diff --git a/Services/UICore/classes/class.ilTemplate.php b/Services/UICore/classes/class.ilTemplate.php index 2656319bdff2..590aea332ef7 100755 --- a/Services/UICore/classes/class.ilTemplate.php +++ b/Services/UICore/classes/class.ilTemplate.php @@ -1691,6 +1691,7 @@ function getStandardTemplate() // always load jQuery include_once("./Services/jQuery/classes/class.iljQueryUtil.php"); iljQueryUtil::initjQuery(); + iljQueryUtil::initjQueryUI(); // always load ui framework include_once("./Services/UICore/classes/class.ilUIFramework.php"); From 567ecc601721ceea3e1aa3a5f2cc455795c3949d Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 26 Jul 2018 09:33:53 +0200 Subject: [PATCH 091/166] OnScreenChat: Added help message according to #23347 --- .../classes/class.ilOnScreenChatGUI.php | 6 +++--- Services/OnScreenChat/js/onscreenchat.js | 1 + .../templates/default/tpl.chat-add-user.html | 16 ++++++++++------ lang/ilias_de.lang | 3 ++- lang/ilias_en.lang | 1 + 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php b/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php index 96532a776615..0463dfd91531 100644 --- a/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php +++ b/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php @@ -257,7 +257,7 @@ public static function initializeFrontend() $DIC->language()->toJS(array( 'chat_osc_no_usr_found', 'chat_osc_emoticons', 'chat_osc_write_a_msg', 'autocomplete_more', 'close', 'chat_osc_invite_to_conversation', 'chat_osc_user', 'chat_osc_add_user', 'chat_osc_subs_rej_msgs', - 'chat_osc_subs_rej_msgs_p', 'chat_osc_self_rej_msgs' + 'chat_osc_subs_rej_msgs_p', 'chat_osc_self_rej_msgs', 'chat_osc_search_modal_info' )); require_once 'Services/jQuery/classes/class.iljQueryUtil.php'; @@ -275,8 +275,8 @@ public static function initializeFrontend() $DIC['tpl']->addJavascript('./Modules/Chatroom/chat/node_modules/socket.io/node_modules/socket.io-client/socket.io.js'); $DIC['tpl']->addJavascript('./Services/OnScreenChat/js/chat.js'); $DIC['tpl']->addJavascript('./Services/OnScreenChat/js/onscreenchat.js'); - $DIC['tpl']->addOnLoadCode("il.Chat.setConfig(".ilJsonUtil::encode($chatConfig).");"); - $DIC['tpl']->addOnLoadCode("il.OnScreenChat.setConfig(".ilJsonUtil::encode($guiConfig).");"); + $DIC['tpl']->addOnLoadCode("il.Chat.setConfig(".json_encode($chatConfig).");"); + $DIC['tpl']->addOnLoadCode("il.OnScreenChat.setConfig(".json_encode($guiConfig).");"); $DIC['tpl']->addOnLoadCode("il.OnScreenChat.init();"); self::$frontend_initialized = true; diff --git a/Services/OnScreenChat/js/onscreenchat.js b/Services/OnScreenChat/js/onscreenchat.js index 3f60c1963a7b..746ec951d0d4 100644 --- a/Services/OnScreenChat/js/onscreenchat.js +++ b/Services/OnScreenChat/js/onscreenchat.js @@ -656,6 +656,7 @@ show: true, body: getModule().config.modalTemplate .replace(/\[\[conversationId\]\]/g, $(this).attr('data-onscreenchat-add')) + .replace('#:#chat_osc_search_modal_info#:#', il.Language.txt('chat_osc_search_modal_info')) .replace('#:#chat_osc_user#:#', il.Language.txt('chat_osc_user')) .replace('#:#chat_osc_no_usr_found#:#', il.Language.txt('chat_osc_no_usr_found')), onShown: function (e, modal) { diff --git a/Services/OnScreenChat/templates/default/tpl.chat-add-user.html b/Services/OnScreenChat/templates/default/tpl.chat-add-user.html index 9e4975c7d880..60365ff66941 100644 --- a/Services/OnScreenChat/templates/default/tpl.chat-add-user.html +++ b/Services/OnScreenChat/templates/default/tpl.chat-add-user.html @@ -1,8 +1,12 @@
    -
    -

    - -

    - -
    +
    +
    +
    + + + +
    #:#chat_osc_search_modal_info#:#
    +
    +
    +
    \ No newline at end of file diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 32169ef64096..2e6a20169957 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -13736,7 +13736,8 @@ chatroom#:#chat_osc_dont_accept_msg#:#Sie können aktuell von anderen Personen n chatroom#:#chat_osc_doesnt_accept_msg#:#Konversationen nicht möglich chatroom#:#chat_osc_subs_rej_msgs#:#Ihr Chat-Partner möchte zur Zeit keine Nachrichten empfangen. Er kann den Empfang von Chat-Nachrichten in seinen Chat-Einstellungen wieder aktivieren. chatroom#:#chat_osc_subs_rej_msgs_p#:#Die folgenden Konversationspartner möchten aktuell keine Nachrichten mehr empfangen: %s -chatroom#:#chat_osc_self_rej_msgs#:#Sie können aktuell nicht an der Conversation teilnehmen, da Sie den Empfang von Chat-Nachrichten in Ihren Einstellungen aktuell unterdrückt haben. +chatroom#:#chat_osc_self_rej_msgs#:#Sie können aktuell nicht an der Konversation teilnehmen, da Sie den Empfang von Chat-Nachrichten in Ihren Einstellungen aktuell unterdrückt haben. +chatroom#:#chat_osc_search_modal_info#:#Suchen Sie hier nach einem Benutzer, den Sie zur Konversation hinzufügen möchten. Wenn Sie den Suchdialog in einem Gruppen-Chat geöffnet haben, wird der Benutzer zu diesem hinzugefügt. Wenn Sie den Suchdialog aus aus einem Einzel-Chat heraus geöffnet haben, öffnet sich nach dem Hinzufügen ein neues Fenster für den Gruppen-Chat. usr#:#user_actions#:#Benutzeraktionen usr#:#user_action#:#Benuzteraktion chatroom#:#chtr_activation_online_info#:#Wählen Sie diese Einstellung, um den Chatraum für Benutzer verfügbar zumachen. diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index fc54a2d18e21..1c7c30acde06 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -13756,6 +13756,7 @@ chatroom#:#chtr_activation_limited_visibility_info#:#If checked, the title of th chatroom#:#chat_osc_subs_rej_msgs#:#Currently it is not possible to continue this conversation. Your chat-partner must activate chats in the chat-settings. chatroom#:#chat_osc_subs_rej_msgs_p#:#The following chat partners of your conversation do not want to receive messages anymore: %s chatroom#:#chat_osc_self_rej_msgs#:#You currently cannot take part in this conversation because you suppressed receiving messages in your settings. +chatroom#:#chat_osc_search_modal_info#:#Here you can search for users you would like to add to a conversation. If the search dialogue was opened within a group chat, the new user will be added to this chat. If you started the search within a 1:1 chat, a new group chat window will be opened. chatroom#:#chtr_server_status#:#Server Status chatroom#:#chtr_ban_actor_tbl_head#:#Akteur chatroom#:#chtr_ban_ts_tbl_head#:#Timestamp From 183c16b66028f1151116d180269d65ffe7e0987f Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 26 Jul 2018 09:55:37 +0200 Subject: [PATCH 092/166] OnScreenChat: Added tooltip for chat participants in menu overlay --- Services/OnScreenChat/js/onscreenchat-menu.js | 7 +++++++ Services/OnScreenChat/js/onscreenchat.js | 5 +---- .../OnScreenChat/templates/default/tpl.chat-menu-item.html | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Services/OnScreenChat/js/onscreenchat-menu.js b/Services/OnScreenChat/js/onscreenchat-menu.js index a93fc1f8be53..dfd0d1be34c7 100644 --- a/Services/OnScreenChat/js/onscreenchat-menu.js +++ b/Services/OnScreenChat/js/onscreenchat-menu.js @@ -144,7 +144,9 @@ } getModule().content.find('#onscreenchatmenu-content').html(templates); + il.ExtLink.autolink($('#onscreenchatmenu-content').find('[data-onscreenchat-body-last-msg]')); + getModule().rendered = true; } @@ -217,6 +219,11 @@ afterListUpdate: function() { $('.ilOnScreenChatMenuLoader').remove(); + + $('#onscreenchatmenu-content').find('[data-toggle="tooltip"]').tooltip({ + container: 'body', + viewport: { selector: 'body', padding: 10 } + }); }, hasConversation: function(conversation) { diff --git a/Services/OnScreenChat/js/onscreenchat.js b/Services/OnScreenChat/js/onscreenchat.js index 746ec951d0d4..59ac6d4ba372 100644 --- a/Services/OnScreenChat/js/onscreenchat.js +++ b/Services/OnScreenChat/js/onscreenchat.js @@ -1,9 +1,6 @@ (function($, $scope, $chat, $menu){ 'use strict'; - var bootstrapTooltip = $.fn.tooltip.noConflict(); - $.fn.bootstrapTt = bootstrapTooltip; - var TYPE_CONSTANT = 'osc'; var PREFIX_CONSTANT = TYPE_CONSTANT + '_'; @@ -257,7 +254,7 @@ getModule().container.append(conversationWindow); getModule().addMessagesOnOpen(conversation); - conversationWindow.find('[data-toggle="tooltip"]').bootstrapTt({ + conversationWindow.find('[data-toggle="tooltip"]').tooltip({ container: 'body', viewport: { selector: 'body', padding: 10 } }); diff --git a/Services/OnScreenChat/templates/default/tpl.chat-menu-item.html b/Services/OnScreenChat/templates/default/tpl.chat-menu-item.html index f27461a39bf1..51047e4ccc4e 100644 --- a/Services/OnScreenChat/templates/default/tpl.chat-menu-item.html +++ b/Services/OnScreenChat/templates/default/tpl.chat-menu-item.html @@ -7,7 +7,7 @@
    -

    [[participants]]

    +

    [[participants]]

    [[last_message]]

    @@ -15,4 +15,4 @@

    [[participants]]

    -
    +
    \ No newline at end of file From a35c583d55787a82663b9bb5e436f43a2bbebc95 Mon Sep 17 00:00:00 2001 From: nmatuschek Date: Thu, 26 Jul 2018 11:10:44 +0200 Subject: [PATCH 093/166] Fixed typo --- .../utils/class.ilWorkflowDbHelper.php | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php b/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php index 35c40d3ba316..21acc8f2298a 100644 --- a/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php +++ b/Services/WorkflowEngine/classes/utils/class.ilWorkflowDbHelper.php @@ -57,16 +57,16 @@ public static function writeWorkflow(ilWorkflow $workflow) { $ilDB->update('wfe_workflows', array( - 'workflow_type' => array ('text', $wf_data['type'] ), + 'workflow_type' => array ('text', $wf_data['type'] ), 'workflow_content' => array ('text', $wf_data['content']), - 'workflow_class' => array ('text', $workflow->getWorkflowClass()), - 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), - 'subject_type' => array ('text', $wf_subject['type']), - 'subject_id' => array ('integer', $wf_subject['identifier']), - 'context_type' => array ('text', $wf_context['type']), - 'context_id' => array ('integer', $wf_context['identifier']), + 'workflow_class' => array ('text', $workflow->getWorkflowClass()), + 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), + 'subject_type' => array ('text', $wf_subject['type']), + 'subject_id' => array ('integer', $wf_subject['identifier']), + 'context_type' => array ('text', $wf_context['type']), + 'context_id' => array ('integer', $wf_context['identifier']), 'workflow_instance' => array ('clob', $instance), - 'active' => array ('integer', (int)$active) + 'active' => array ('integer', (int)$active) ), array( 'workflow_id' => array ('integer', $wf_id) @@ -78,16 +78,16 @@ public static function writeWorkflow(ilWorkflow $workflow) { $ilDB->insert('wfe_workflows', array( - 'workflow_id' => array ('integer', $wf_id), - 'workflow_type' => array ('text', $wf_data['type'] ), - 'workflow_class' => array ('text', $workflow->getWorkflowClass()), - 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), + 'workflow_id' => array ('integer', $wf_id), + 'workflow_type' => array ('text', $wf_data['type'] ), + 'workflow_class' => array ('text', $workflow->getWorkflowClass()), + 'workflow_location' => array ('text', $workflow->getWorkflowLocation()), 'workflow_content' => array ('text', $wf_data['content']), - 'subject_type' => array ('text', $wf_subject['type']), - 'subject_id' => array ('integer', $wf_subject['identifier']), - 'context_type' => array ('text', $wf_context['type']), - 'context_id' => array ('integer', $wf_context['identifier']), - 'workflow_instance' => array ('clob', $instance), + 'subject_type' => array ('text', $wf_subject['type']), + 'subject_id' => array ('integer', $wf_subject['identifier']), + 'context_type' => array ('text', $wf_context['type']), + 'context_id' => array ('integer', $wf_context['identifier']), + 'workflow_instance' => array ('clob', $instance), 'active' => array ('integer', (int)$active) ) ); @@ -233,14 +233,14 @@ public static function writeDetector(ilDetector $a_detector) $ilDB->update('wfe_det_listening', array( 'workflow_id' => array ('integer', $wf_id), - 'type' => array ('text', $det_data['type'] ), - 'content' => array ('text', $det_data['content']), - 'subject_type' => array ('text', $det_subject['type']), - 'subject_id' => array ('integer', $det_subject['identifier']), - 'context_type' => array ('text', $det_context['type']), + 'type' => array ('text', $det_data['type'] ), + 'content' => array ('text', $det_data['content']), + 'subject_type' => array ('text', $det_subject['type']), + 'subject_id' => array ('integer', $det_subject['identifier']), + 'context_type' => array ('text', $det_context['type']), 'context_id' => array ('integer', $det_context['identifier']), 'listening_start' => array ('integer', $det_listen['listening_start']), - 'listening_end' => array ('integer', $det_listen['listening_end']) + 'listening_end' => array ('integer', $det_listen['listening_end']) ), array( 'detector_id' => array ('integer', $det_id) @@ -254,14 +254,14 @@ public static function writeDetector(ilDetector $a_detector) array( 'detector_id' => array ('integer', $det_id), 'workflow_id' => array ('integer', $wf_id), - 'type' => array ('text', $det_data['type'] ), - 'content' => array ('text', $det_data['content']), - 'subject_type' => array ('text', $det_subject['type']), - 'subject_id' => array ('integer', $det_subject['identifier']), - 'context_type' => array ('text', $det_context['type']), + 'type' => array ('text', $det_data['type'] ), + 'content' => array ('text', $det_data['content']), + 'subject_type' => array ('text', $det_subject['type']), + 'subject_id' => array ('integer', $det_subject['identifier']), + 'context_type' => array ('text', $det_context['type']), 'context_id' => array ('integer', $det_context['identifier']), 'listening_start' => array ('integer', $det_listen['listening_start']), - 'listening_end' => array ('integer', $det_listen['listening_end']) + 'listening_end' => array ('integer', $det_listen['listening_end']) ) ); } @@ -494,7 +494,7 @@ public static function deleteStartEventData($event_id) $result = $ilDB->query( 'SELECT event_id FROM wfe_startup_events - WHERE workflow_id = ' . $ilDB->quote($event_id, 'text') + WHERE workflow_id = ' . $ilDB->quote($event_id, 'integer') ); $events = array(); @@ -506,7 +506,7 @@ public static function deleteStartEventData($event_id) $ilDB->manipulate( 'DELETE FROM wfe_startup_events - WHERE workflow_id = ' . $ilDB->quote($event_id, 'text') + WHERE workflow_id = ' . $ilDB->quote($event_id, 'integer') ); if(count($events)) From 579c9a0dc53808408ff1c5a0f3b8bbde84b7a360 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 26 Jul 2018 14:48:52 +0200 Subject: [PATCH 094/166] OnScreenChat: Fixed #23351 --- .../classes/class.ilOnScreenChatGUI.php | 68 ++++------- .../class.ilOnScreenChatUserDataProvider.php | 112 ++++++++++++++++++ Services/OnScreenChat/js/onscreenchat-menu.js | 21 +++- Services/OnScreenChat/js/onscreenchat.js | 104 +++++++++++----- 4 files changed, 229 insertions(+), 76 deletions(-) create mode 100644 Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php diff --git a/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php b/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php index 0463dfd91531..29e61b6fdf61 100644 --- a/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php +++ b/Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php @@ -8,8 +8,6 @@ */ class ilOnScreenChatGUI { - const WAC_TTL_TIME = 60; - /** * Boolean to track whether this service has already been initialized. * @@ -41,7 +39,8 @@ protected static function getEmoticons(ilChatroomServerSettings $chatSettings) require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php'; require_once 'Modules/Chatroom/classes/class.ilChatroomSmilies.php'; - ilWACSignedPath::setTokenMaxLifetimeInSeconds(self::WAC_TTL_TIME); + $oldWacTokenValue = \ilWACSignedPath::getTokenMaxLifetimeInSeconds(); + \ilWACSignedPath::setTokenMaxLifetimeInSeconds(60); $smileys_array = ilChatroomSmilies::_getSmilies(); foreach($smileys_array as $smiley_array) @@ -71,6 +70,8 @@ protected static function getEmoticons(ilChatroomServerSettings $chatSettings) $smileys[$new_key] = $new_val; } } + + \ilWACSignedPath::setTokenMaxLifetimeInSeconds($oldWacTokenValue); } return $smileys; @@ -84,8 +85,8 @@ public function executeCommand() switch($cmd) { - case 'getUserProfileImages': - $this->getUserProfileImages(); + case 'getUserProfileData': + $this->getUserProfileData(); break; case 'verifyLogin': $this->verifyLogin(); @@ -140,58 +141,29 @@ public function getUserList() exit; } - public function getUserProfileImages() + public function getUserProfileData() { global $DIC; - $response = array(); - - if(!$DIC->user() || $DIC->user()->isAnonymous()) - { - echo json_encode($response); + if (!$DIC->user() || $DIC->user()->isAnonymous()) { + echo json_encode([]); exit(); } - if(!isset($_GET['usr_ids']) || strlen($_GET['usr_ids']) == 0) - { - echo json_encode($response); + if (!isset($_GET['usr_ids']) || strlen($_GET['usr_ids']) == 0) { + echo json_encode([]); exit(); } $DIC['lng']->loadLanguageModule('user'); - - require_once 'Services/WebAccessChecker/classes/class.ilWACSignedPath.php'; - ilWACSignedPath::setTokenMaxLifetimeInSeconds(self::WAC_TTL_TIME); - - $user_ids = array_filter(array_map('intval', array_map('trim', explode(',', $_GET['usr_ids'])))); - require_once 'Services/User/classes/class.ilUserUtil.php'; - $public_data = ilUserUtil::getNamePresentation($user_ids, true, false, '', false, true, false, true); - $public_names = ilUserUtil::getNamePresentation($user_ids, false, false, '', false, true, false, false); - - foreach($user_ids as $usr_id) - { - $public_image = isset($public_data[$usr_id]) && isset($public_data[$usr_id]['img']) ? $public_data[$usr_id]['img'] : ''; - - $public_name = ''; - if(isset($public_names[$usr_id])) - { - $public_name = $public_names[$usr_id]; - if('unknown' == $public_name && isset($public_data[$usr_id]) && isset($public_data[$usr_id]['login'])) - { - $public_name = $public_data[$usr_id]['login']; - } - } - $response[$usr_id] = array( - 'public_name' => $public_name, - 'profile_image' => $public_image - ); - } + $userProvider = new \ilOnScreenChatUserDataProvider($DIC->database(), $DIC->user()); + $data = $userProvider->getDataByUserIds(explode(',', $_GET['usr_ids'])); require_once 'Services/Authentication/classes/class.ilSession.php'; ilSession::enableWebAccessWithoutSession(true); - echo json_encode($response); + echo json_encode($data); exit(); } @@ -217,7 +189,8 @@ public static function initializeFrontend() $settings = self::loadServerSettings(); $DIC->language()->loadLanguageModule('chatroom'); - + $DIC->language()->loadLanguageModule('user'); + $renderer = $DIC->ui()->renderer(); $factory = $DIC->ui()->factory(); @@ -233,6 +206,8 @@ public static function initializeFrontend() )); $chatWindowTemplate->setVariable('CONVERSATION_ICON', ilUtil::img(ilUtil::getImagePath('icon_chta.svg'))); + $userProvider = new \ilOnScreenChatUserDataProvider($DIC->database(), $DIC->user()); + $guiConfig = array( 'chatWindowTemplate' => $chatWindowTemplate->get(), 'messageTemplate' => (new ilTemplate('tpl.chat-message.html', false, false, 'Services/OnScreenChat'))->get(), @@ -240,18 +215,19 @@ public static function initializeFrontend() 'userId' => $DIC->user()->getId(), 'username' => $DIC->user()->getLogin(), 'userListURL' => $DIC->ctrl()->getLinkTargetByClass('ilonscreenchatgui', 'getUserList', '', true, false), - 'userProfileDataURL' => $DIC->ctrl()->getLinkTargetByClass('ilonscreenchatgui', 'getUserProfileImages', '', true, false), + 'userProfileDataURL' => $DIC->ctrl()->getLinkTargetByClass('ilonscreenchatgui', 'getUserProfileData', '', true, false), 'verifyLoginURL' => $DIC->ctrl()->getLinkTargetByClass('ilonscreenchatgui', 'verifyLogin', '', true, false), 'loaderImg' => ilUtil::getImagePath('loader.svg'), 'emoticons' => self::getEmoticons($settings), - 'locale' => $DIC->language()->getLangKey() + 'locale' => $DIC->language()->getLangKey(), + 'initialUserData' => $userProvider->getInitialUserProfileData(), ); $chatConfig = array( 'url' => $settings->generateClientUrl() . '/' . $settings->getInstance() . '-im', 'subDirectory' => $settings->getSubDirectory() . '/socket.io', 'userId' => $DIC->user()->getId(), - 'username' => $DIC->user()->getLogin() + 'username' => $DIC->user()->getLogin(), ); $DIC->language()->toJS(array( diff --git a/Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php b/Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php new file mode 100644 index 000000000000..e54cf0f1a977 --- /dev/null +++ b/Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php @@ -0,0 +1,112 @@ +db = $db; + $this->user = $user; + } + + /** + * @return int[] + * @throws \ilWACException + */ + public function getInitialUserProfileData() + { + $res = $this->db->queryF( + ' + SELECT DISTINCT(participants) FROM osc_conversation WHERE id IN ( + SELECT conversation_id FROM osc_activity WHERE user_id = %s + UNION + SELECT conversation_id FROM osc_messages WHERE user_id = %s + ) + ', + ['integer', 'integer'], + [$this->user->getId(), $this->user->getId()] + ); + + $usrIds = []; + while ($row = $this->db->fetchAssoc($res)) { + $participants = json_decode($row['participants'], true); + + if (is_array($participants)) { + $usrIds = array_unique(array_merge($usrIds, array_map(function ($user) { + return $user['id']; + }, $participants))); + } + } + + return $this->getDataByUserIds($usrIds); + } + + /** + * @param int[] $usrIds + * @throws ilWACException + * @return $data + */ + public function getDataByUserIds(array $usrIds) + { + $usrIds = array_filter(array_map('intval', array_map('trim', $usrIds))); + + $oldWacTokenValue = \ilWACSignedPath::getTokenMaxLifetimeInSeconds(); + \ilWACSignedPath::setTokenMaxLifetimeInSeconds(60); + + $publicData = \ilUserUtil::getNamePresentation($usrIds, true, false, '', false, true, false, true); + + $data = []; + + foreach ($usrIds as $usr_id) { + $publicImage = isset($publicData[$usr_id]) && isset($publicData[$usr_id]['img']) ? $publicData[$usr_id]['img'] : ''; + + $publicName = ''; + if (isset($publicData[$usr_id])) { + $login = ''; + if (isset($publicData[$usr_id]['login'])) { + $publicName = $login = $publicData[$usr_id]['login']; + } + + if (isset($publicData[$usr_id]['public_profile']) && $publicData[$usr_id]['public_profile']) { + $publicName = implode(', ', [ + $publicData[$usr_id]['lastname'], + $publicData[$usr_id]['firstname'], + ]); + + if ($publicName !== '') { + $publicName .= ' [' . $login . ']'; + } else { + $publicName = $login; + } + } + } + + $data[$usr_id] = array( + 'public_name' => $publicName, + 'profile_image' => $publicImage + ); + } + + \ilWACSignedPath::setTokenMaxLifetimeInSeconds($oldWacTokenValue); + + return $data; + } +} \ No newline at end of file diff --git a/Services/OnScreenChat/js/onscreenchat-menu.js b/Services/OnScreenChat/js/onscreenchat-menu.js index dfd0d1be34c7..4857d1825b9f 100644 --- a/Services/OnScreenChat/js/onscreenchat-menu.js +++ b/Services/OnScreenChat/js/onscreenchat-menu.js @@ -7,6 +7,7 @@ conversations: [], messageFormatter: {}, participantsImages: {}, + participantsNames: {}, setConfig: function(config) { $scope.il.OnScreenChatMenu.config = config; @@ -79,7 +80,13 @@ for (var key in participants) { if(participants.hasOwnProperty(key) && participants[key].id != getModule().config.userId) { - participantNames.push(participants[key].name); + var publicName = getPublicName(participants[key].id); + if (publicName !== "") { + participantNames.push(publicName); + } else { + participantNames.push(participants[key].name); + } + participantUserIds.push(participants[key].id); } } @@ -204,6 +211,10 @@ getModule().participantsImages = images; }, + syncPublicNames: function(names) { + getModule().participantsNames = names; + }, + countUnreadMessages: function() { var conversations = getModule().conversations; @@ -244,6 +255,14 @@ return ""; }; + var getPublicName = function(userId) { + if (getModule().participantsNames.hasOwnProperty(userId)) { + return getModule().participantsNames[userId]; + } + + return ""; + }; + var getModule = function() { return $scope.il.OnScreenChatMenu; }; diff --git a/Services/OnScreenChat/js/onscreenchat.js b/Services/OnScreenChat/js/onscreenchat.js index 59ac6d4ba372..5e58b1a3c0d2 100644 --- a/Services/OnScreenChat/js/onscreenchat.js +++ b/Services/OnScreenChat/js/onscreenchat.js @@ -121,6 +121,7 @@ emoticons: {}, messageFormatter: {}, participantsImages: {}, + participantsNames: {}, chatWindowWidth: 278, numWindows: Infinity, @@ -143,6 +144,16 @@ $menu.setMessageFormatter(getModule().getMessageFormatter()); + $.each(getModule().config.initialUserData, function(usrId, item) { + getModule().participantsNames[usrId] = item.public_name; + + var img = new Image(); + img.src = item.profile_image; + getModule().participantsImages[usrId] = img; + }); + $menu.syncPublicNames(getModule().participantsNames); + $menu.syncProfileImages(getModule().participantsImages); + $(window).on('storage', function(e){ var conversation = e.originalEvent.newValue; if(typeof conversation == "string") { @@ -446,34 +457,57 @@ getModule().receiveMessage(messageObject); }, - requestUserImages: function(conversation) { - var participantsIds = getParticipantsIds(conversation); + /** + * + * @param conversation + * @returns {jQuery.Deferred} + */ + requestUserProfileData: function(conversation) { + var dfd = new $.Deferred(), + participantsIds = getParticipantsIds(conversation); + participantsIds = participantsIds.filter(function(id){ return !getModule().participantsImages.hasOwnProperty(id); }); - $.get( - getModule().config.userProfileDataURL + '&usr_ids=' + participantsIds.join(','), - function (response){ - $.each(response, function(id, item){ - var img = new Image(); - img.src = item.profile_image; - getModule().participantsImages[id] = img; + if (participantsIds.length === 0) { + dfd.resolve(); - $('[data-onscreenchat-avatar='+id+']').attr('src', img.src); - $menu.syncProfileImages(getModule().participantsImages); - }); - }, - 'json' - ); + return dfd; + } + + $.ajax({ + url: getModule().config.userProfileDataURL + '&usr_ids=' + participantsIds.join(','), + dataType: 'json', + method: 'GET' + }).done(function(response) { + $.each(response, function(id, item){ + getModule().participantsNames[id] = item.public_name; + $menu.syncPublicNames(getModule().participantsNames); + + var img = new Image(); + img.src = item.profile_image; + getModule().participantsImages[id] = img; + + $('[data-onscreenchat-avatar='+id+']').attr('src', img.src); + $menu.syncProfileImages(getModule().participantsImages); + }); + + dfd.resolve(); + }); + + return dfd; }, onConversationInit: function(conversation){ - getModule().requestUserImages(conversation); - conversation.lastActivity = (new Date).getTime(); - conversation.open = true; - $menu.add(conversation); - getModule().storage.save(conversation); + $ + .when(getModule().requestUserProfileData(conversation)) + .then(function() { + conversation.lastActivity = (new Date).getTime(); + conversation.open = true; + $menu.add(conversation); + getModule().storage.save(conversation); + }); }, onMenuItemRemovalRequest: function(e) { @@ -585,16 +619,19 @@ onConversation: function(conversation) { var chatWindow = $('[data-onscreenchat-window='+conversation.id+']'); - getModule().requestUserImages(conversation); - if(chatWindow.length !== 0) { - chatWindow.find('[data-onscreenchat-window-participants]').html( - getParticipantsNames(conversation).join(', ') - ); - } + $ + .when(getModule().requestUserProfileData(conversation)) + .then(function() { + if(chatWindow.length !== 0) { + chatWindow.find('[data-onscreenchat-window-participants]').html( + getParticipantsNames(conversation).join(', ') + ); + } - $menu.add(conversation); - getModule().storage.save(conversation); + $menu.add(conversation); + getModule().storage.save(conversation); + }); }, onHistory: function (conversation) { @@ -930,10 +967,14 @@ } }; })(); - + var findUsernameByIdByConversation = function(conversation, usrId) { for(var index in conversation.participants) { if(conversation.participants.hasOwnProperty(index) && conversation.participants[index].id == usrId) { + if (getModule().participantsNames.hasOwnProperty(conversation.participants[index].id)) { + return getModule().participantsNames[conversation.participants[index].id]; + } + return conversation.participants[index].name; } } @@ -971,6 +1012,11 @@ for(var key in conversation.participants) { if(getModule().user.id != conversation.participants[key].id) { + if (getModule().participantsNames.hasOwnProperty(conversation.participants[key].id)) { + names.push(getModule().participantsNames[conversation.participants[key].id]); + continue; + } + names.push(conversation.participants[key].name); } } From 12bde7f821a189a82af7619573766d04342f7690 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 26 Jul 2018 14:50:28 +0200 Subject: [PATCH 095/166] Updated composer classmap --- libs/composer/vendor/composer/autoload_classmap.php | 9 ++++++++- libs/composer/vendor/composer/autoload_static.php | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libs/composer/vendor/composer/autoload_classmap.php b/libs/composer/vendor/composer/autoload_classmap.php index a20a523733b0..25d9c2ab1c11 100644 --- a/libs/composer/vendor/composer/autoload_classmap.php +++ b/libs/composer/vendor/composer/autoload_classmap.php @@ -833,6 +833,12 @@ 'ILIAS\\Validation\\Constraints\\LessThan' => $baseDir . '/../../src/Validation/Constraints/LessThan.php', 'ILIAS\\Validation\\Constraints\\Not' => $baseDir . '/../../src/Validation/Constraints/Not.php', 'ILIAS\\Validation\\Constraints\\Parallel' => $baseDir . '/../../src/Validation/Constraints/Parallel.php', + 'ILIAS\\Validation\\Constraints\\Password\\Factory' => $baseDir . '/../../src/Validation/Constraints/Password/Factory.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasLowerChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasLowerChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasMinLength' => $baseDir . '/../../src/Validation/Constraints/Password/HasMinLength.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasNumbers' => $baseDir . '/../../src/Validation/Constraints/Password/HasNumbers.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasSpecialChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasSpecialChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasUpperChars' => $baseDir . '/../../src/Validation/Constraints/Password/HasUpperChars.php', 'ILIAS\\Validation\\Constraints\\Sequential' => $baseDir . '/../../src/Validation/Constraints/Sequential.php', 'ILIAS\\Validation\\Factory' => $baseDir . '/../../src/Validation/Factory.php', 'ILIAS\\WebAccessChecker\\HttpServiceAware' => $baseDir . '/../../Services/WebAccessChecker/classes/HttpServiceAware.php', @@ -1518,6 +1524,7 @@ 'SurveyCategories' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyCategories.php', 'SurveyConstraintsTableGUI' => $baseDir . '/../../Modules/Survey/classes/tables/class.SurveyConstraintsTableGUI.php', 'SurveyImportParser' => $baseDir . '/../../Services/Survey/classes/class.SurveyImportParser.php', + 'SurveyImportParserPre38' => $baseDir . '/../../Services/Survey/classes/class.SurveyImportParserPre38.php', 'SurveyMaterialsSourceTableGUI' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/tables/class.SurveyMaterialsSourceTableGUI.php', 'SurveyMatrixQuestion' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyMatrixQuestion.php', 'SurveyMatrixQuestionEvaluation' => $baseDir . '/../../Modules/SurveyQuestionPool/classes/class.SurveyMatrixQuestionEvaluation.php', @@ -3191,7 +3198,6 @@ 'ilContentStylesTableGUI' => $baseDir . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => $baseDir . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => $baseDir . '/../../Services/Context/classes/class.ilContextApacheSSO.php', - 'ilContextBehat' => $baseDir . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => $baseDir . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => $baseDir . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => $baseDir . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -5075,6 +5081,7 @@ 'ilOnScreenChatAppEventListener' => $baseDir . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatAppEventListener.php', 'ilOnScreenChatGUI' => $baseDir . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php', 'ilOnScreenChatMenuGUI' => $baseDir . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatMenuGUI.php', + 'ilOnScreenChatUserDataProvider' => $baseDir . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php', 'ilOnScreenChatUserUserAutoComplete' => $baseDir . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatUserUserAutoComplete.php', 'ilOnlineTracking' => $baseDir . '/../../Services/Tracking/classes/class.ilOnlineTracking.php', 'ilOpenLayersMapGUI' => $baseDir . '/../../Services/Maps/classes/class.ilOpenLayersMapGUI.php', diff --git a/libs/composer/vendor/composer/autoload_static.php b/libs/composer/vendor/composer/autoload_static.php index bf874fd27731..0eee809b0ed4 100644 --- a/libs/composer/vendor/composer/autoload_static.php +++ b/libs/composer/vendor/composer/autoload_static.php @@ -1025,6 +1025,12 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ILIAS\\Validation\\Constraints\\LessThan' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/LessThan.php', 'ILIAS\\Validation\\Constraints\\Not' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Not.php', 'ILIAS\\Validation\\Constraints\\Parallel' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Parallel.php', + 'ILIAS\\Validation\\Constraints\\Password\\Factory' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/Factory.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasLowerChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasLowerChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasMinLength' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasMinLength.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasNumbers' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasNumbers.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasSpecialChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasSpecialChars.php', + 'ILIAS\\Validation\\Constraints\\Password\\HasUpperChars' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Password/HasUpperChars.php', 'ILIAS\\Validation\\Constraints\\Sequential' => __DIR__ . '/../..' . '/../../src/Validation/Constraints/Sequential.php', 'ILIAS\\Validation\\Factory' => __DIR__ . '/../..' . '/../../src/Validation/Factory.php', 'ILIAS\\WebAccessChecker\\HttpServiceAware' => __DIR__ . '/../..' . '/../../Services/WebAccessChecker/classes/HttpServiceAware.php', @@ -1710,6 +1716,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'SurveyCategories' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyCategories.php', 'SurveyConstraintsTableGUI' => __DIR__ . '/../..' . '/../../Modules/Survey/classes/tables/class.SurveyConstraintsTableGUI.php', 'SurveyImportParser' => __DIR__ . '/../..' . '/../../Services/Survey/classes/class.SurveyImportParser.php', + 'SurveyImportParserPre38' => __DIR__ . '/../..' . '/../../Services/Survey/classes/class.SurveyImportParserPre38.php', 'SurveyMaterialsSourceTableGUI' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/tables/class.SurveyMaterialsSourceTableGUI.php', 'SurveyMatrixQuestion' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyMatrixQuestion.php', 'SurveyMatrixQuestionEvaluation' => __DIR__ . '/../..' . '/../../Modules/SurveyQuestionPool/classes/class.SurveyMatrixQuestionEvaluation.php', @@ -3383,7 +3390,6 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilContentStylesTableGUI' => __DIR__ . '/../..' . '/../../Services/Style/Content/classes/class.ilContentStylesTableGUI.php', 'ilContext' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContext.php', 'ilContextApacheSSO' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextApacheSSO.php', - 'ilContextBehat' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextBehat.php', 'ilContextCron' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextCron.php', 'ilContextIcal' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextIcal.php', 'ilContextLTIProvider' => __DIR__ . '/../..' . '/../../Services/Context/classes/class.ilContextLTIProvider.php', @@ -5267,6 +5273,7 @@ class ComposerStaticInit2fffdf922cf8fdbf1f62eec345993c83 'ilOnScreenChatAppEventListener' => __DIR__ . '/../..' . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatAppEventListener.php', 'ilOnScreenChatGUI' => __DIR__ . '/../..' . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatGUI.php', 'ilOnScreenChatMenuGUI' => __DIR__ . '/../..' . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatMenuGUI.php', + 'ilOnScreenChatUserDataProvider' => __DIR__ . '/../..' . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatUserDataProvider.php', 'ilOnScreenChatUserUserAutoComplete' => __DIR__ . '/../..' . '/../../Services/OnScreenChat/classes/class.ilOnScreenChatUserUserAutoComplete.php', 'ilOnlineTracking' => __DIR__ . '/../..' . '/../../Services/Tracking/classes/class.ilOnlineTracking.php', 'ilOpenLayersMapGUI' => __DIR__ . '/../..' . '/../../Services/Maps/classes/class.ilOpenLayersMapGUI.php', From 0ad96868b37decae08c496ccaa9784b792b69472 Mon Sep 17 00:00:00 2001 From: mjansen Date: Thu, 26 Jul 2018 16:21:21 +0200 Subject: [PATCH 096/166] SAML: Changes regarding single logout --- Services/Saml/classes/class.ilSimpleSAMLphpWrapper.php | 2 -- Services/Saml/lib/authsources.php.dist | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Services/Saml/classes/class.ilSimpleSAMLphpWrapper.php b/Services/Saml/classes/class.ilSimpleSAMLphpWrapper.php index 2f9f12c14255..4e00642073e8 100644 --- a/Services/Saml/classes/class.ilSimpleSAMLphpWrapper.php +++ b/Services/Saml/classes/class.ilSimpleSAMLphpWrapper.php @@ -139,8 +139,6 @@ public function getAttributes() public function logout($returnUrl = '') { ilSession::set('used_external_auth', false); - ilUtil::setCookie("SAMLSESSID",""); - ilUtil::setCookie("SimpleSAMLAuthToken",""); $params = array( 'ReturnStateParam' => 'LogoutState', diff --git a/Services/Saml/lib/authsources.php.dist b/Services/Saml/lib/authsources.php.dist index 06c759317dbe..661017ebd7af 100644 --- a/Services/Saml/lib/authsources.php.dist +++ b/Services/Saml/lib/authsources.php.dist @@ -35,7 +35,9 @@ $config = array( // The URL to the discovery service. // Can be NULL/unset, in which case a builtin discovery service will be used. - 'discoURL' => NULL + 'discoURL' => NULL, + //'sign.logout' => TRUE, + //'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', ), ); \ No newline at end of file From 19c8dd882c06d39484e3bfc2051d9f2462088224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 27 Jul 2018 12:55:39 +0200 Subject: [PATCH 097/166] dbupdates for cloze question's specific feedback extension, migration of existing data for assClozeTest as well as for all other qtpes --- .../DBupdate_ClozeGapFeedback.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Modules/TestQuestionPool/DBupdate_ClozeGapFeedback.php diff --git a/Modules/TestQuestionPool/DBupdate_ClozeGapFeedback.php b/Modules/TestQuestionPool/DBupdate_ClozeGapFeedback.php new file mode 100644 index 000000000000..225ff736ced9 --- /dev/null +++ b/Modules/TestQuestionPool/DBupdate_ClozeGapFeedback.php @@ -0,0 +1,79 @@ +checkAccess('read', '', SYSTEM_FOLDER_ID) ) +{ + die('administrative privileges only!'); +} + +/* @var \ILIAS\DI\Container $DIC */ +try +{ + if( !$DIC->database()->tableColumnExists('qpl_fb_specific', 'question') ) + { + // add new table column for indexing different question gaps in assClozeTest + $DIC->database()->addTableColumn('qpl_fb_specific', 'question', array( + 'type' => 'integer', 'length' => 4, 'notnull' => false, 'default' => null + )); + + // give all other qtypes having a single subquestion the question index 0 + $DIC->database()->manipulateF( + "UPDATE qpl_fb_specific SET question = %s WHERE question_fi NOT IN( + SELECT question_id FROM qpl_questions + INNER JOIN qpl_qst_type ON question_type_id = question_type_fi + WHERE type_tag = %s + )", array('integer', 'text'), array(0, 'assClozeTest') + ); + + // for all assClozeTest entries - migrate the gap feedback indexes from answer field to questin field + $DIC->database()->manipulateF( + "UPDATE qpl_fb_specific SET question = answer WHERE question_fi IN( + SELECT question_id FROM qpl_questions + INNER JOIN qpl_qst_type ON question_type_id = question_type_fi + WHERE type_tag = %s + )", array('text'), array('assClozeTest') + ); + + // for all assClozeTest entries - initialize the answer field with 0 for the formaly stored gap feedback + $DIC->database()->manipulateF( + "UPDATE qpl_fb_specific SET answer = %s WHERE question_fi IN( + SELECT question_id FROM qpl_questions + INNER JOIN qpl_qst_type ON question_type_id = question_type_fi + WHERE type_tag = %s + )", array('integer', 'text'), array(0, 'assClozeTest') + ); + + // finaly set the question index field to notnull = true (not nullable) as it is now initialized + $DIC->database()->modifyTableColumn('qpl_fb_specific', 'question', array( + 'notnull' => true, 'default' => 0 + )); + + // add unique constraint on qid and the two specific feedback indentification index fields + $DIC->database()->addUniqueConstraint('qpl_fb_specific', array( + 'question_fi', 'question', 'answer' + )); + } + + if( !$DIC->database()->tableColumnExists('qpl_qst_cloze', 'feedback_mode') ) + { + $DIC->database()->addTableColumn('qpl_qst_cloze', 'feedback_mode', array( + 'type' => 'text', 'length' => 16, 'notnull' => false, 'default' => null + )); + + $DIC->database()->manipulateF("UPDATE qpl_qst_cloze SET feedback_mode = %s", + array('text'), array('gapQuestion') + ); + + $DIC->database()->modifyTableColumn('qpl_qst_cloze', 'feedback_mode', array( + 'notnull' => true, 'default' => 'gapQuestion' + )); + } + +} +catch(ilException $e) +{ + +} From 008c044c0ed551ff68ca6d09c854831f9b2c6a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 27 Jul 2018 12:57:12 +0200 Subject: [PATCH 098/166] extension of feedback object graph for handling sub questions per question and therefor real answer specific feedback for all gaps of a cloze test question. changed access to feedback api for all qtypes, extended behavior of cloze question --- .../classes/class.assClozeGap.php | 7 +- .../classes/class.assClozeTest.php | 70 +- .../classes/class.assClozeTestGUI.php | 18 +- .../classes/class.assErrorTextGUI.php | 4 +- .../classes/class.assImagemapQuestion.php | 4 +- .../classes/class.assImagemapQuestionGUI.php | 8 +- .../classes/class.assKprimChoice.php | 2 +- .../classes/class.assKprimChoiceGUI.php | 6 +- .../classes/class.assLongMenuGUI.php | 4 +- .../classes/class.assMatchingQuestionGUI.php | 4 +- .../classes/class.assMultipleChoice.php | 4 +- .../classes/class.assMultipleChoiceGUI.php | 12 +- .../class.assOrderingHorizontalGUI.php | 4 +- .../classes/class.assOrderingQuestionGUI.php | 4 +- .../classes/class.assSingleChoice.php | 4 +- .../classes/class.assSingleChoiceGUI.php | 4 +- .../classes/class.assTextQuestionGUI.php | 2 +- .../export/qti12/class.assClozeTestExport.php | 44 +- .../qti12/class.assImagemapQuestionExport.php | 2 +- .../qti12/class.assKprimChoiceExport.php | 2 +- .../export/qti12/class.assLongMenuExport.php | 2 +- .../qti12/class.assMultipleChoiceExport.php | 2 +- .../export/qti12/class.assQuestionExport.php | 2 +- .../qti12/class.assSingleChoiceExport.php | 2 +- .../feedback/class.ilAssClozeTestFeedback.php | 799 +++++++++++++++++- ...onfigurableMultiOptionQuestionFeedback.php | 57 +- ...class.ilAssMultiOptionQuestionFeedback.php | 112 ++- .../feedback/class.ilAssQuestionFeedback.php | 35 +- ...lass.ilAssSingleOptionQuestionFeedback.php | 15 +- .../class.ilAssSpecificFeedbackIdentifier.php | 98 +++ ...ss.ilAssSpecificFeedbackIdentifierList.php | 104 +++ .../import/qti12/class.assClozeTestImport.php | 31 +- .../import/qti12/class.assErrorTextImport.php | 2 +- .../qti12/class.assImagemapQuestionImport.php | 2 +- .../qti12/class.assKprimChoiceImport.php | 2 +- .../qti12/class.assMultipleChoiceImport.php | 2 +- .../qti12/class.assSingleChoiceImport.php | 2 +- lang/ilias_de.lang | 16 + lang/ilias_en.lang | 16 + 39 files changed, 1281 insertions(+), 229 deletions(-) create mode 100644 Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifier.php create mode 100644 Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php diff --git a/Modules/TestQuestionPool/classes/class.assClozeGap.php b/Modules/TestQuestionPool/classes/class.assClozeGap.php index afcdc44ba03b..faee7a634c5c 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeGap.php +++ b/Modules/TestQuestionPool/classes/class.assClozeGap.php @@ -17,7 +17,10 @@ */ class assClozeGap { - + const TYPE_TEXT = 0; + const TYPE_SELECT = 1; + const TYPE_NUMERIC = 2; + /** * Type of gap * @@ -89,7 +92,7 @@ public function setType($a_type = 0) * Gets the items of a cloze gap * * @param ilArrayElementShuffler $shuffler - * @return array The list of items + * @return assAnswerCloze[] The list of items */ public function getItems(ilArrayElementShuffler $shuffler) { diff --git a/Modules/TestQuestionPool/classes/class.assClozeTest.php b/Modules/TestQuestionPool/classes/class.assClozeTest.php index 0970f266d8f2..d3f0dc2816af 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTest.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTest.php @@ -8,6 +8,7 @@ require_once './Modules/TestQuestionPool/interfaces/interface.ilObjAnswerScoringAdjustable.php'; require_once './Modules/TestQuestionPool/interfaces/interface.iQuestionCondition.php'; require_once './Modules/TestQuestionPool/classes/class.ilUserQuestionResult.php'; +require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php'; /** * Class for cloze tests @@ -92,6 +93,8 @@ class assClozeTest extends assQuestion implements ilObjQuestionScoringAdjustable var $fixedTextLength; public $cloze_text; + + protected $feedbackMode = ilAssClozeTestFeedback::FB_MODE_GAP_QUESTION; /** * assClozeTest constructor @@ -187,6 +190,7 @@ public function loadFromDb($question_id) $this->setClozeText($data['cloze_text']); $this->setFixedTextLength($data["fixed_textlen"]); $this->setIdenticalScoring(($data['tstamp'] == 0) ? true : $data["identical_scoring"]); + $this->setFeedbackMode($data['feedback_mode']); // replacement of old syntax with new syntax include_once("./Services/RTE/classes/class.ilRTE.php"); $this->question = ilRTE::_replaceMediaObjectImageSrc($this->question, 1); @@ -317,30 +321,22 @@ public function saveAnswerSpecificDataToDb() */ public function saveAdditionalQuestionDataToDb() { - global $ilDB; - - $ilDB->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s", + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + + $DIC->database()->manipulateF( "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s", array( "integer" ), array( $this->getId() ) ); - - $ilDB->manipulateF( "INSERT INTO " . $this->getAdditionalTableName() - . " (question_fi, textgap_rating, identical_scoring, fixed_textlen, cloze_text) VALUES (%s, %s, %s, %s, %s)", - array( - "integer", - "text", - "text", - "integer", - "text" - ), - array( - $this->getId(), - $this->getTextgapRating(), - $this->getIdenticalScoring(), - $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL, - ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0) - ) - ); + + $DIC->database()->insert($this->getAdditionalTableName(), array( + 'question_fi' => array('integer', $this->getId()), + 'textgap_rating' => array('text', $this->getTextgapRating()), + 'identical_scoring' => array('text', $this->getIdenticalScoring()), + 'fixed_textlen' => array('integer', $this->getFixedTextLength() ? $this->getFixedTextLength() : NULL), + 'cloze_text' => array('text', ilRTE::_replaceMediaObjectImageSrc($this->getClozeText(), 0)), + 'feedback_mode' => array('text', $this->getFeedbackMode()) + )); } /** @@ -594,6 +590,22 @@ function setEndTag($end_tag = "[/gap]") { $this->end_tag = $end_tag; } + + /** + * @return string + */ + public function getFeedbackMode() + { + return $this->feedbackMode; + } + + /** + * @param string $feedbackMode + */ + public function setFeedbackMode($feedbackMode) + { + $this->feedbackMode = $feedbackMode; + } /** * Create gap entries by parsing the question text @@ -2022,4 +2034,20 @@ public function calculateReachedPointsFromPreviewSession(ilAssQuestionPreviewSes return $this->calculateReachedPointsForSolution($userSolution); } + + public function fetchAnswerValueForGap($userSolution, $gapIndex) + { + $answerValue = ''; + + foreach($userSolution as $valueRow) + { + if ($valueRow['value1'] == $gapIndex) + { + $answerValue = $valueRow['value2']; + break; + } + } + + return $answerValue; + } } \ No newline at end of file diff --git a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php index da0ba9f60632..aab481fb1f77 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -1469,25 +1469,27 @@ public function setQuestionTabs() function getSpecificFeedbackOutput($active_id, $pass) { - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists(array_values($this->object->gaps)) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } + $userSolution = $this->object->getTestOutputSolutions($active_id, $pass); + global $lng; $feedback = ''; - foreach ($this->object->gaps as $index => $answer) + foreach ($this->object->gaps as $gapIndex => $gap) { - $caption = $lng->txt('gap').' '.($index+1) .': '; - + $answerValue = $this->object->fetchAnswerValueForGap($userSolution, $gapIndex); + $answerIndex = $this->object->feedbackOBJ->determineAnswerIndexForAnswerValue($gap, $answerValue); + $fb = $this->object->feedbackOBJ->determineTestOutputGapFeedback($gapIndex, $answerIndex); + + $caption = $lng->txt('gap').' '.($gapIndex+1) .': '; $feedback .= ''; + $feedback .= $fb . ''; } $feedback .= '
    '; - $feedback .= $caption .''; - $feedback .= $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $index - ) . '
    '; diff --git a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php index 5ce2822cf7e8..af7b5cb1b496 100644 --- a/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php +++ b/Modules/TestQuestionPool/classes/class.assErrorTextGUI.php @@ -459,7 +459,7 @@ function getSpecificFeedbackOutput($active_id, $pass) { $selection = $this->object->getBestSelection(false); - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists(array_keys($selection)) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } @@ -502,7 +502,7 @@ function getSpecificFeedbackOutput($active_id, $pass) if ( preg_match('/'.preg_quote($ans->text_wrong, '/').'/', $element) ) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $idx + $this->object->getId(), 0, $idx ); $feedback .= ''. $fb . ''; diff --git a/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php b/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php index 7d860679b571..298d3e7b7562 100755 --- a/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assImagemapQuestion.php @@ -883,7 +883,7 @@ function getRTETextWithMediaObjects() $text = parent::getRTETextWithMediaObjects(); foreach ($this->answers as $index => $answer) { - $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), $index); + $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(),0, $index); } return $text; } @@ -960,7 +960,7 @@ public function toJSON() "state" => $answer_obj->getState(), "area" => $answer_obj->getArea(), "feedback" => $this->formatSAQuestion( - $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), $key) + $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(),0, $key) ) )); $order++; diff --git a/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php index 6e4c38287667..5939e3229c9f 100755 --- a/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assImagemapQuestionGUI.php @@ -583,7 +583,7 @@ function getSolutionOutput( if ($show_feedback) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $solution_id + $this->object->getId(),0, $solution_id ); if (strlen($fb)) @@ -755,7 +755,7 @@ function getTestOutput($active_id, $pass, $is_postponed = FALSE, $use_post_solut if(!$this->object->getIsMultipleChoice() && count($userSelection) && current($userSelection) == $answer_id) { $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $answer_id + $this->object->getId(),0, $answer_id ); if (strlen($feedback)) { @@ -922,7 +922,7 @@ public function setQuestionTabs() function getSpecificFeedbackOutput($active_id, $pass) { - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists(array_values($this->object->getAnswers())) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } @@ -932,7 +932,7 @@ function getSpecificFeedbackOutput($active_id, $pass) foreach($this->object->getAnswers() as $idx => $answer) { $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $idx + $this->object->getId(),0, $idx ); $output .= "{$answer->getAnswerText()}{$feedback}"; diff --git a/Modules/TestQuestionPool/classes/class.assKprimChoice.php b/Modules/TestQuestionPool/classes/class.assKprimChoice.php index b65802cfcff6..4610797b1899 100644 --- a/Modules/TestQuestionPool/classes/class.assKprimChoice.php +++ b/Modules/TestQuestionPool/classes/class.assKprimChoice.php @@ -1011,7 +1011,7 @@ public function toJSON() 'order' => (int)$answer->getPosition(), 'image' => (string)$answer->getImageFile(), 'feedback' => $this->formatSAQuestion( - $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), $key) + $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(),0, $key) ) ); } diff --git a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php index 728ac0e91ba9..2e0f7e9dfcf6 100644 --- a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php @@ -792,7 +792,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem { if($user_solution[$answer_id]) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); @@ -804,7 +804,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem if($this->object->getSpecificFeedbackSetting() == ilAssConfigurableMultiOptionQuestionFeedback::FEEDBACK_SETTING_ALL) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); @@ -819,7 +819,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem if($answer->getCorrectness()) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); diff --git a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php index 7d2ccb43cf02..15dc4687a6c8 100644 --- a/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php +++ b/Modules/TestQuestionPool/classes/class.assLongMenuGUI.php @@ -483,7 +483,7 @@ function setQuestionTabs() function getSpecificFeedbackOutput($active_id, $pass) { - if( !$this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), 0) ) + if( !$this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, 0) ) { return ''; } @@ -499,7 +499,7 @@ function getSpecificFeedbackOutput($active_id, $pass) $feedback .= $caption .''; $feedback .= $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $index + $this->object->getId(),0, $index ) . ' '; } $feedback .= ''; diff --git a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php index 1bfdf1aada9f..dc81dde09f08 100755 --- a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php @@ -1054,7 +1054,7 @@ function getSpecificFeedbackOutput($active_id, $pass) { $matches = array_values($this->object->getMaximumScoringMatchingPairs()); - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists($matches) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } @@ -1064,7 +1064,7 @@ function getSpecificFeedbackOutput($active_id, $pass) foreach ($matches as $idx => $ans) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $idx + $this->object->getId(),0, $idx ); $feedback .= '"' . $ans->definition->text . '" ' . $this->lng->txt("matches") . ' "'; $feedback .= $ans->term->text . '"'; diff --git a/Modules/TestQuestionPool/classes/class.assMultipleChoice.php b/Modules/TestQuestionPool/classes/class.assMultipleChoice.php index cc9b918812bc..c1692f444bc2 100755 --- a/Modules/TestQuestionPool/classes/class.assMultipleChoice.php +++ b/Modules/TestQuestionPool/classes/class.assMultipleChoice.php @@ -1024,7 +1024,7 @@ function getRTETextWithMediaObjects() $text = parent::getRTETextWithMediaObjects(); foreach ($this->answers as $index => $answer) { - $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), $index); + $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(),0, $index); $answer_obj = $this->answers[$index]; $text .= $answer_obj->getAnswertext(); } @@ -1131,7 +1131,7 @@ public function toJSON() "order" => (int) $answer_obj->getOrder(), "image" => (string) $answer_obj->getImage(), "feedback" => $this->formatSAQuestion( - $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), $key) + $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(),0, $key) ) )); } diff --git a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php index 541075c764e3..b6c0609ecd72 100755 --- a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php @@ -337,7 +337,7 @@ function getSolutionOutput( if (strcmp($mc_solution, $answer_id) == 0) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $answer_id + $this->object->getId(),0, $answer_id ); if (strlen($fb)) { @@ -352,7 +352,7 @@ function getSolutionOutput( if ($this->object->getSpecificFeedbackSetting() == 1) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $answer_id + $this->object->getId(),0, $answer_id ); if (strlen($fb)) { @@ -369,7 +369,7 @@ function getSolutionOutput( if ($answer->getPoints() > 0) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $answer_id + $this->object->getId(),0, $answer_id ); if (strlen($fb)) { @@ -1082,7 +1082,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem { if(strcmp($mc_solution, $answer_id) == 0) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); @@ -1095,7 +1095,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem if($this->object->getSpecificFeedbackSetting() == 1) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); @@ -1110,7 +1110,7 @@ private function populateSpecificFeedbackInline($user_solution, $answer_id, $tem if($answer->getPoints() > 0) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php index 537b7d12c71d..a3c56ce40cde 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontalGUI.php @@ -456,7 +456,7 @@ function getSpecificFeedbackOutput($active_id, $pass) $answers = explode(' ', $this->object->getOrderText()); } - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists(array_values($answers)) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } @@ -466,7 +466,7 @@ function getSpecificFeedbackOutput($active_id, $pass) foreach($answers as $idx => $answer) { $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $idx + $this->object->getId(),0, $idx ); $output .= "{$answer}{$feedback}"; diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php index 030d0c57c7ac..38f8e2e8e361 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestionGUI.php @@ -705,7 +705,7 @@ function setQuestionTabs() function getSpecificFeedbackOutput($active_id, $pass) { - if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists($this->object->getOrderingElementList()) ) + if( !$this->object->feedbackOBJ->specificAnswerFeedbackExists() ) { return ''; } @@ -715,7 +715,7 @@ function getSpecificFeedbackOutput($active_id, $pass) foreach( $this->object->getOrderingElementList() as $element) { $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $element->getPosition() + $this->object->getId(),0, $element->getPosition() ); if( $this->object->isImageOrderingType() ) diff --git a/Modules/TestQuestionPool/classes/class.assSingleChoice.php b/Modules/TestQuestionPool/classes/class.assSingleChoice.php index 5f4e542961d0..ed5dd0deaade 100755 --- a/Modules/TestQuestionPool/classes/class.assSingleChoice.php +++ b/Modules/TestQuestionPool/classes/class.assSingleChoice.php @@ -991,7 +991,7 @@ function getRTETextWithMediaObjects() $text = parent::getRTETextWithMediaObjects(); foreach ($this->answers as $index => $answer) { - $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(), $index); + $text .= $this->feedbackOBJ->getSpecificAnswerFeedbackContent($this->getId(),0, $index); $answer_obj = $this->answers[$index]; $text .= $answer_obj->getAnswertext(); } @@ -1094,7 +1094,7 @@ public function toJSON() "order" => (int)$answer_obj->getOrder(), "image" => (string) $answer_obj->getImage(), "feedback" => $this->formatSAQuestion( - $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(), $key) + $this->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation($this->getId(),0, $key) ) )); } diff --git a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php index a6d863d045e7..52ebabeb43f7 100755 --- a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php @@ -562,7 +562,7 @@ function getTestOutput($active_id, $pass, $is_postponed = FALSE, $use_post_solut if($feedbackOutputRequired) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $answer_id + $this->object->getId(),0, $answer_id ); if (strlen($fb)) { @@ -923,7 +923,7 @@ private function populateInlineFeedback($template, $answer_id, $user_solution) if($feedbackOutputRequired) { - $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(), $answer_id); + $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation($this->object->getId(),0, $answer_id); if(strlen($fb)) { $template->setCurrentBlock("feedback"); diff --git a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php index 64806a09b98d..b5065ccf27e2 100755 --- a/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assTextQuestionGUI.php @@ -561,7 +561,7 @@ function getSpecificFeedbackOutput($active_id, $pass) if ($this->object->isKeywordMatching($user_answer, $ans->getAnswertext() )) { $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackTestPresentation( - $this->object->getId(), $idx + $this->object->getId(),0, $idx ); $feedback .= '' . $ans->getAnswertext() . ''; $feedback .= $fb . ' '; diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php index e4bb6b7b50ec..5946c2e94457 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php @@ -584,7 +584,7 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = $a_xml_writer->xmlEndTag("flow_mat"); $a_xml_writer->xmlEndTag("itemfeedback"); }*/ - + /* $attrs = array( "ident" => $i, "view" => "All" @@ -596,12 +596,14 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = // $a_xml_writer->xmlElement("mattext"); // $a_xml_writer->xmlEndTag("material"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $i + $this->object->getId(), $i, 0 ); $this->object->addQTIMaterial($a_xml_writer, $fb); $a_xml_writer->xmlEndTag("flow_mat"); $a_xml_writer->xmlEndTag("itemfeedback"); + */ } + $this->exportAnswerSpecificFeedbacks($a_xml_writer); if (strlen($feedback_allcorrect)) { @@ -641,6 +643,40 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = } return $xml; } + + /** + * @param ilXmlWriter $xmlWriter + */ + protected function exportAnswerSpecificFeedbacks(ilXmlWriter $xmlWriter) + { + require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php'; + $feedbackIdentifierList = new ilAssSpecificFeedbackIdentifierList(); + $feedbackIdentifierList->load($this->object->getId()); + + foreach($feedbackIdentifierList as $fbIdentifier) + { + $feedback = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( + $this->object->getId(), $fbIdentifier->getQuestionIndex(), $fbIdentifier->getAnswerIndex() + ); + + $xmlWriter->xmlStartTag("itemfeedback", array( + "ident" => $this->buildQtiExportIdent($fbIdentifier), "view" => "All" + )); + + $xmlWriter->xmlStartTag("flow_mat"); + $this->object->addQTIMaterial($a_xml_writer, $feedback); + $xmlWriter->xmlEndTag("flow_mat"); + + $xmlWriter->xmlEndTag("itemfeedback"); + } + } + + /** + * @param ilAssSpecificFeedbackIdentifier $fbIdentifier + * @return string + */ + public function buildQtiExportIdent(ilAssSpecificFeedbackIdentifier $fbIdentifier) + { + return "{$fbIdentifier->getQuestionIndex()}:{$fbIdentifier->getAnswerIndex()}"; + } } - -?> diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php index 88f19edf59c2..d93004daa22a 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assImagemapQuestionExport.php @@ -456,7 +456,7 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = // qti flow_mat $a_xml_writer->xmlStartTag("flow_mat"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $index + $this->object->getId(),0, $index ); $this->object->addQTIMaterial($a_xml_writer, $fb); $a_xml_writer->xmlEndTag("flow_mat"); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php index 8a783f1e2a08..e469e2307a95 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assKprimChoiceExport.php @@ -278,7 +278,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $xml->xmlStartTag('flow_mat'); $this->object->addQTIMaterial($xml, $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $answer->getPosition() + $this->object->getId(),0, $answer->getPosition() )); $xml->xmlEndTag('flow_mat'); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php index a9cdfa8f9b89..01fc7c9bf1d2 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assLongMenuExport.php @@ -227,7 +227,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu // qti flow_mat $xml->xmlStartTag("flow_mat"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $i + $this->object->getId(),0, $i ); $this->object->addQTIMaterial($xml, $fb); $xml->xmlEndTag("flow_mat"); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php index f83d3e21f23d..863fcac0d0da 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assMultipleChoiceExport.php @@ -329,7 +329,7 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = // qti flow_mat $a_xml_writer->xmlStartTag("flow_mat"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $index + $this->object->getId(),0, $index ); $this->object->addQTIMaterial($a_xml_writer, $fb); $a_xml_writer->xmlEndTag("flow_mat"); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php index 70e40fae17b0..fa5fb135e24c 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assQuestionExport.php @@ -50,7 +50,7 @@ protected function addAnswerSpecificFeedback(ilXmlWriter $a_xml_writer, $answers // qti flow_mat $a_xml_writer->xmlStartTag("flow_mat"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $index + $this->object->getId(),0, $index ); $this->object->addQTIMaterial($a_xml_writer, $fb); $a_xml_writer->xmlEndTag("flow_mat"); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php index 0efe85236b8b..14e25fcb617c 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assSingleChoiceExport.php @@ -310,7 +310,7 @@ function toXML($a_include_header = true, $a_include_binary = true, $a_shuffle = // qti flow_mat $a_xml_writer->xmlStartTag("flow_mat"); $fb = $this->object->feedbackOBJ->getSpecificAnswerFeedbackExportPresentation( - $this->object->getId(), $index + $this->object->getId(),0, $index ); $this->object->addQTIMaterial($a_xml_writer, $fb); $a_xml_writer->xmlEndTag("flow_mat"); diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php index 6df89d3d1dfb..d98a5ca8a313 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php @@ -2,6 +2,7 @@ /* Copyright (c) 1998-2013 ILIAS open source, Extended GPL, see docs/LICENSE */ require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php'; +require_once 'Services/Randomization/classes/class.ilArrayElementOrderKeeper.php'; /** * feedback class for assClozeTest questions @@ -13,38 +14,814 @@ */ class ilAssClozeTestFeedback extends ilAssMultiOptionQuestionFeedback { + /** + * constants for different feedback modes (per gap or per gap-answers/options) + */ + const FB_MODE_GAP_QUESTION = 'gapQuestion'; + const FB_MODE_GAP_ANSWERS = 'gapAnswers'; + + /** + * constants for answer indexes in case of FB_MODE_GAP_ANSWERS + */ + const FB_TEXT_GAP_EMPTY_INDEX = -1; + const FB_TEXT_GAP_NOMATCH_INDEX = -2; // indexes for preset answers: 0 - n + const FB_SELECT_GAP_EMPTY_INDEX = -1; // indexes for given select options: 0 - n + const FB_NUMERIC_GAP_EMPTY_INDEX = -1; + const FB_NUMERIC_GAP_VALUE_HIT_INDEX = 0; + const FB_NUMERIC_GAP_RANGE_HIT_INDEX = 1; + const FB_NUMERIC_GAP_TOO_LOW_INDEX = 2; + const FB_NUMERIC_GAP_TOO_HIGH_INDEX = 3; + + /** + * object instance of current question + * + * @access protected + * @var assClozeTest + */ + protected $questionOBJ = null; + /** * returns the answer options mapped by answer index * (overwrites parent method from ilAssMultiOptionQuestionFeedback) * - * @return array $answerOptionsByAnswerIndex + * @return assClozeGap[] */ - protected function getAnswerOptionsByAnswerIndex() + protected function getGapsByIndex() { return $this->questionOBJ->gaps; } + /** + * @return boolean $isSaveableInPageObjectEditingMode + */ + public function isSaveableInPageObjectEditingMode() + { + return true; + } + /** * builds an answer option label from given (mixed type) index and answer * (overwrites parent method from ilAssMultiOptionQuestionFeedback) * * @access protected - * @param integer $index - * @param mixed $answer + * @param integer $indexgapIndex + * @param assClozeGap $gap * @return string $answerOptionLabel */ - protected function buildAnswerOptionLabel($index, $answer) + protected function buildGapFeedbackLabel($gapIndex, $gap) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + $answers = array(); + + foreach($gap->getItems(new ilArrayElementOrderKeeper()) as $item ) + { + $answers[] = '"' . $item->getAnswertext().'"'; + } + + $answers = implode(' / ', $answers); + + $label = sprintf($DIC->language()->txt('ass_cloze_gap_fb_gap_label'), + $gapIndex + 1, $answers + ); + + return $label; + } + + /** + * @param integer $gapIndex + * @param assAnswerCloze $item + * @return string + */ + protected function buildTextGapGivenAnswerFeedbackLabel($gapIndex, $item) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_txt_match_label'), + $gapIndex + 1, $item->getAnswertext() + ); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildTextGapWrongAnswerFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_txt_nomatch_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildTextGapEmptyFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_txt_empty_label'), $gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @param assAnswerCloze $item + * @return string + */ + protected function buildSelectGapOptionFeedbackLabel($gapIndex, $item) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_sel_opt_label'), + $gapIndex + 1, $item->getAnswertext() + ); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildSelectGapEmptyFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_sel_empty_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildNumericGapValueHitFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_num_valuehit_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildNumericGapRangeHitFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_num_rangehit_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildNumericGapTooLowFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_num_toolow_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildNumericGapTooHighFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_num_toohigh_label'),$gapIndex + 1); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildNumericGapEmptyFeedbackLabel($gapIndex) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + return sprintf($DIC->language()->txt('ass_cloze_gap_fb_num_empty_label'),$gapIndex + 1); + } + + public function completeSpecificFormProperties(ilPropertyFormGUI $form) { - $caption = 'Gap '.$ordinal = $index+1 .': '; + global $DIC; /* @var ILIAS\DI\Container $DIC */ - foreach( $answer->items as $item ) + if( !$this->questionOBJ->getSelfAssessmentEditingMode() ) + { + $header = new ilFormSectionHeaderGUI(); + $header->setTitle($this->lng->txt('feedback_answers')); + $form->addItem($header); + + $feedbackMode = new ilRadioGroupInputGUI( + $DIC->language()->txt('ass_cloze_fb_mode'), 'feedback_mode' + ); + $feedbackMode->setRequired(true); + $form->addItem($feedbackMode); + + $fbModeGapQuestion = new ilRadioOption($DIC->language()->txt('ass_cloze_fb_mode_gap_qst'), + self::FB_MODE_GAP_QUESTION, $DIC->language()->txt('ass_cloze_fb_mode_gap_qst_info') + ); + $this->completeFormPropsForFeedbackModeGapQuestion($fbModeGapQuestion); + $feedbackMode->addOption($fbModeGapQuestion); + + $fbModeGapAnswers = new ilRadioOption($DIC->language()->txt('ass_cloze_fb_mode_gap_answ'), + self::FB_MODE_GAP_ANSWERS, $DIC->language()->txt('ass_cloze_fb_mode_gap_answ_info') + ); + $this->completeFormPropsForFeedbackModeGapAnswers($fbModeGapAnswers); + $feedbackMode->addOption($fbModeGapAnswers); + } + } + + protected function completeFormPropsForFeedbackModeGapQuestion(ilRadioOption $fbModeOpt) + { + foreach($this->getGapsByIndex() as $gapIndex => $gap ) + { + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildGapFeedbackLabel($gapIndex, $gap), true + ); + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel , $this->buildPostVarForFbFieldPerGapQuestion($gapIndex), + $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + } + + protected function completeFormPropsForFeedbackModeGapAnswers(ilRadioOption $fbModeOpt) + { + foreach($this->getGapsByIndex() as $gapIndex => $gap ) { - $caption .= '"' . $item->getAnswertext().'" / '; + switch( $gap->getType() ) + { + case assClozeGap::TYPE_TEXT: + + $this->completeFbPropsForTextGap($fbModeOpt, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_SELECT: + + $this->completeFbPropsForSelectGap($fbModeOpt, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_NUMERIC: + + $this->completeFbPropsForNumericGap($fbModeOpt, $gapIndex); + break; + } } + } + + /** + * @param ilRadioOption $fbModeOpt + * @param assClozeGap $gap + * @param integer $gapIndex + */ + protected function completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeGap $gap, $gapIndex) + { + foreach($gap->getItems(new ilArrayElementOrderKeeper()) as $answerIndex => $item) + { + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildTextGapGivenAnswerFeedbackLabel($gapIndex, $item), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_{$answerIndex}"; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildTextGapWrongAnswerFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_TEXT_GAP_NOMATCH_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildTextGapEmptyFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_TEXT_GAP_EMPTY_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + + /** + * @param ilRadioOption $fbModeOpt + * @param assClozeGap $gap + * @param integer $gapIndex + */ + protected function completeFbPropsForSelectGap(ilRadioOption $fbModeOpt, assClozeGap $gap, $gapIndex) + { + foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $optIndex => $item) + { + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildSelectGapOptionFeedbackLabel($gapIndex, $item), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_{$optIndex}"; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildSelectGapEmptyFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_" . self::FB_SELECT_GAP_EMPTY_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + + /** + * @param ilRadioOption $fbModeOpt + * @param assClozeGap $gap + * @param integer $gapIndex + */ + protected function completeFbPropsForNumericGap(ilRadioOption $fbModeOpt, $gapIndex) + { + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildNumericGapValueHitFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_NUMERIC_GAP_VALUE_HIT_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildNumericGapRangeHitFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_NUMERIC_GAP_RANGE_HIT_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildNumericGapTooLowFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_NUMERIC_GAP_TOO_LOW_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildNumericGapTooHighFeedbackLabel($gapIndex), true + ); + + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_NUMERIC_GAP_TOO_HIGH_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); - $caption = substr($caption, 0, strlen($caption)-3); - $caption .= ''; + $propertyLabel = $this->questionOBJ->prepareTextareaOutput( + $this->buildNumericGapEmptyFeedbackLabel($gapIndex), true + ); - return $caption; + $propertyPostVar = "feedback_answer_{$gapIndex}_".self::FB_NUMERIC_GAP_EMPTY_INDEX; + + $fbModeOpt->addSubItem($this->buildFeedbackContentFormProperty( + $propertyLabel, $propertyPostVar, $this->questionOBJ->isAdditionalContentEditingModePageObject() + )); + } + + public function initSpecificFormProperties(ilPropertyFormGUI $form) + { + if( !$this->questionOBJ->getSelfAssessmentEditingMode() ) + { + /* @var ilRadioGroupInputGUI $fbMode */ + $fbMode = $form->getItemByPostVar('feedback_mode'); + $fbMode->setValue($this->questionOBJ->getFeedbackMode()); + + switch( $this->questionOBJ->getFeedbackMode() ) + { + case self::FB_MODE_GAP_QUESTION: + + $this->initFeedbackFieldsPerGapQuestion($form); + break; + + case self::FB_MODE_GAP_ANSWERS: + + $this->initFeedbackFieldsPerGapAnswers($form); + break; + } + } + } + + protected function initFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form) + { + foreach( $this->getGapsByIndex() as $gapIndex => $gap ) + { + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, 0); + $form->getItemByPostVar($this->buildPostVarForFbFieldPerGapQuestion($gapIndex))->setValue($value); + } + } + + protected function initFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form) + { + foreach( $this->getGapsByIndex() as $gapIndex => $gap ) + { + switch( $gap->getType() ) + { + case assClozeGap::TYPE_TEXT: + + $this->initFbPropsForTextGap($form, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_SELECT: + + $this->initFbPropsForSelectGap($form, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_NUMERIC: + + $this->initFbPropsForNumericGap($form, $gapIndex); + break; + } + } + } + + protected function initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) + { + foreach($gap->getItems(new ilArrayElementOrderKeeper()) as $answerIndex => $item) + { + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $answerIndex); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex); + $form->getItemByPostVar($postVar)->setValue($value); + } + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + } + + protected function initFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) + { + foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $optIndex => $item) + { + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $optIndex); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex); + $form->getItemByPostVar($postVar)->setValue($value); + } + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + } + + protected function initFbPropsForNumericGap(ilPropertyFormGUI $form, $gapIndex) + { + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + + $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX); + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX); + $form->getItemByPostVar($postVar)->setValue($value); + } + + public function saveSpecificFormProperties(ilPropertyFormGUI $form) + { + if( !$this->questionOBJ->getSelfAssessmentEditingMode() ) + { + $this->saveSpecificFeedbackMode( $this->questionOBJ->getId(), + $form->getItemByPostVar('feedback_mode')->getValue() + ); + + $this->deleteSpecificAnswerFeedbacks($this->questionOBJ->getId(), + $this->questionOBJ->isAdditionalContentEditingModePageObject() + ); + + switch( $this->questionOBJ->getFeedbackMode() ) + { + case self::FB_MODE_GAP_QUESTION: + + $this->saveFeedbackFieldsPerGapQuestion($form); + break; + + case self::FB_MODE_GAP_ANSWERS: + + $this->saveFeedbackFieldsPerGapAnswers($form); + break; + } + } + } + + protected function saveFeedbackFieldsPerGapQuestion(ilPropertyFormGUI $form) + { + foreach( $this->getGapsByIndex() as $gapIndex => $gap ) + { + $postVar = $this->buildPostVarForFbFieldPerGapQuestion($gapIndex); + $value = $form->getItemByPostVar($postVar)->getValue(); + + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, 0, $value + ); + } + + } + + protected function saveFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form) + { + foreach( $this->getGapsByIndex() as $gapIndex => $gap ) + { + switch( $gap->getType() ) + { + case assClozeGap::TYPE_TEXT: + + $this->saveFbPropsForTextGap($form, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_SELECT: + + $this->saveFbPropsForSelectGap($form, $gap, $gapIndex); + break; + + case assClozeGap::TYPE_NUMERIC: + + $this->saveFbPropsForNumericGap($form, $gapIndex); + break; + } + } + } + + protected function saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) + { + foreach($gap->getItems(new ilArrayElementOrderKeeper()) as $answerIndex => $item) + { + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, $answerIndex, $value + ); + } + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_TEXT_GAP_NOMATCH_INDEX, $value + ); + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_TEXT_GAP_EMPTY_INDEX, $value + ); + } + + protected function saveFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, $gapIndex) + { + foreach ($gap->getItems(new ilArrayElementOrderKeeper()) as $optIndex => $item) + { + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, $optIndex, $value + ); + } + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_SELECT_GAP_EMPTY_INDEX, $value + ); + } + + protected function saveFbPropsForNumericGap(ilPropertyFormGUI $form, $gapIndex) + { + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_NUMERIC_GAP_VALUE_HIT_INDEX, $value + ); + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_NUMERIC_GAP_RANGE_HIT_INDEX, $value + ); + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_NUMERIC_GAP_TOO_LOW_INDEX, $value + ); + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_NUMERIC_GAP_TOO_HIGH_INDEX, $value + ); + + $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX); + $value = $form->getItemByPostVar($postVar)->getValue(); + $this->saveSpecificAnswerFeedbackContent( + $this->questionOBJ->getId(), $gapIndex, self::FB_NUMERIC_GAP_EMPTY_INDEX, $value + ); + } + + /** + * duplicates the SPECIFIC feedback relating to the given original question id + * and saves it for the given duplicate question id + * + * (overwrites the method from parent class, because of individual setting) + * + * @access protected + * @param integer $originalQuestionId + * @param integer $duplicateQuestionId + */ + protected function duplicateSpecificFeedback($originalQuestionId, $duplicateQuestionId) + { + // sync specific feedback setting to duplicated question + + $this->syncSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId); + + parent::duplicateSpecificFeedback($originalQuestionId, $duplicateQuestionId); + } + + private function syncSpecificFeedbackSetting($sourceQuestionId, $targetQuestionId) + { + $res = $this->db->queryF( + "SELECT feedback_mode FROM {$this->questionOBJ->getAdditionalTableName()} WHERE question_fi = %s", + array('integer'), array($sourceQuestionId) + ); + + $row = $this->db->fetchAssoc($res); + + $this->db->update( $this->questionOBJ->getAdditionalTableName(), + array( 'feedback_mode' => array('integer', $row['feedback_mode']) ), + array( 'question_fi' => array('integer', $targetQuestionId) ) + ); + } + + protected function syncSpecificFeedback($originalQuestionId, $duplicateQuestionId) + { + $this->syncSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId); + parent::syncSpecificFeedback($originalQuestionId, $duplicateQuestionId); + } + + /** + * saves the given specific feedback mode for the given question id to the db. + * (It's stored to dataset of question itself) + * @param integer $questionId + * @param string $feedbackMode + */ + protected function saveSpecificFeedbackMode($questionId, $feedbackMode) + { + $this->questionOBJ->setFeedbackMode($feedbackMode); + + $this->db->update($this->questionOBJ->getAdditionalTableName(), + array('feedback_mode' => array('text', $feedbackMode)), + array('question_fi' => array('integer', $questionId)) + ); + } + + /** + * @param integer $gapIndex + * @return string + */ + protected function buildPostVarForFbFieldPerGapQuestion($gapIndex) + { + return "feedback_answer_{$gapIndex}"; + } + + /** + * @param integer $gapIndex + * @param integer $answerIndex + * @return string + */ + protected function buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex) + { + return "feedback_answer_{$gapIndex}_{$answerIndex}"; + } + + /** + * @param $gapIndex + * @param $answerIndex + * @return mixed|string + */ + protected function getSpecificAnswerFeedbackFormValue($gapIndex, $answerIndex) + { + if ($this->questionOBJ->isAdditionalContentEditingModePageObject()) + { + $pageObjectId = $this->getSpecificAnswerFeedbackPageObjectId( + $this->questionOBJ->getId(), $gapIndex, $answerIndex + ); + + $value = $this->getPageObjectNonEditableValueHTML( + $this->getSpecificAnswerFeedbackPageObjectType(), $pageObjectId + ); + } + else + { + $value = $this->questionOBJ->prepareTextareaOutput( + $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), $gapIndex, $answerIndex) + ); + } + + return $value; + } + + public function determineTestOutputGapFeedback($gapIndex, $answerIndex) + { + if( $this->questionOBJ->getFeedbackMode() == self::FB_MODE_GAP_QUESTION ) + { + return $this->getSpecificAnswerFeedbackTestPresentation($this->questionOBJ->getId(), $gapIndex, 0); + } + + return $this->getSpecificAnswerFeedbackTestPresentation($this->questionOBJ->getId(), $gapIndex, $answerIndex); + } + + public function determineAnswerIndexForAnswerValue(assClozeGap $gap, $answerValue) + { + switch( $gap->getType() ) + { + case CLOZE_TEXT: + + if( !strlen($answerValue) ) + { + return self::FB_TEXT_GAP_EMPTY_INDEX; + } + + $items = $gap->getItems(new ilArrayElementOrderKeeper()); + + foreach($items as $answerIndex => $answer) + { + /* @var assAnswerCloze $answer */ + + if( $answer->getAnswertext() == $answerValue ) + { + return $answerIndex; + } + } + + return self::FB_TEXT_GAP_NOMATCH_INDEX; + + case CLOZE_SELECT: + + if( strlen($answerValue) ) + { + return $answerValue; + } + + return self::FB_SELECT_GAP_EMPTY_INDEX; + + case CLOZE_NUMERIC: + + if( !strlen($answerValue) ) + { + return self::FB_NUMERIC_GAP_EMPTY_INDEX; + } + + /* @var assAnswerCloze $item */ + + $item = current($gap->getItems(new ilArrayElementOrderKeeper())); + + if( $answerValue == $item->getAnswertext() ) + { + return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX; + } + + if( $answerValue >= $item->getLowerBound() && $answerValue <= $item->getUpperBound() ) + { + return self::FB_NUMERIC_GAP_RANGE_HIT_INDEX; + } + + if( $answerValue < $item->getLowerBound() ) + { + return self::FB_NUMERIC_GAP_TOO_LOW_INDEX; + } + + if( $answerValue > $item->getUpperBound() ) + { + return self::FB_NUMERIC_GAP_TOO_HIGH_INDEX; + } + } } } diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php index 8a9913d05411..c189640dbfa3 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssConfigurableMultiOptionQuestionFeedback.php @@ -100,13 +100,13 @@ public function initSpecificFormProperties(ilPropertyFormGUI $form) { $value = $this->getPageObjectNonEditableValueHTML( $this->getSpecificAnswerFeedbackPageObjectType(), - $this->getSpecificAnswerFeedbackPageObjectId($this->questionOBJ->getId(), $index) + $this->getSpecificAnswerFeedbackPageObjectId($this->questionOBJ->getId(), 0, $index) ); } else { $value = $this->questionOBJ->prepareTextareaOutput( - $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), $index) + $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), 0, $index) ); } @@ -133,7 +133,7 @@ public function saveSpecificFormProperties(ilPropertyFormGUI $form) foreach( $this->getAnswerOptionsByAnswerIndex() as $index => $answer ) { $this->saveSpecificAnswerFeedbackContent( - $this->questionOBJ->getId(), $index, $form->getInput("feedback_answer_$index") + $this->questionOBJ->getId(), 0, $index, $form->getInput("feedback_answer_$index") ); } } @@ -182,30 +182,7 @@ protected function duplicateSpecificFeedback($originalQuestionId, $duplicateQues $this->syncSpecificFeedbackSetting($originalQuestionId, $duplicateQuestionId); - // sync specific answer feedback to duplicated question - - $res = $this->db->queryF( - "SELECT * FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", array('integer'), array($originalQuestionId) - ); - - while( $row = $this->db->fetchAssoc($res) ) - { - $nextId = $this->db->nextId($this->getSpecificFeedbackTableName()); - - $this->db->insert($this->getSpecificFeedbackTableName(), array( - 'feedback_id' => array('integer', $nextId), - 'question_fi' => array('integer', $duplicateQuestionId), - 'answer' => array('integer', $row['answer']), - 'feedback' => array('text', $row['feedback']), - 'tstamp' => array('integer', time()) - )); - - if( $this->questionOBJ->isAdditionalContentEditingModePageObject() ) - { - $pageObjectType = $this->getSpecificAnswerFeedbackPageObjectType(); - $this->duplicatePageObject($pageObjectType, $row['feedback_id'], $nextId, $duplicateQuestionId); - } - } + parent::duplicateSpecificFeedback($originalQuestionId, $duplicateQuestionId); } /** @@ -222,31 +199,7 @@ protected function syncSpecificFeedback($originalQuestionId, $duplicateQuestionI // sync specific feedback setting to the original $this->syncSpecificFeedbackSetting($duplicateQuestionId, $originalQuestionId); - // delete specific feedback of the original - $this->db->manipulateF( - "DELETE FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", - array('integer'), array($originalQuestionId) - ); - - // get specific feedback of the actual question - $res = $this->db->queryF( - "SELECT * FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", - array('integer'), array($duplicateQuestionId) - ); - - // save specific feedback to the original - while( $row = $this->db->fetchAssoc($res) ) - { - $nextId = $this->db->nextId($this->getSpecificFeedbackTableName()); - - $this->db->insert($this->getSpecificFeedbackTableName(), array( - 'feedback_id' => array('integer', $nextId), - 'question_fi' => array('integer', $originalQuestionId), - 'answer' => array('integer',$row['answer']), - 'feedback' => array('text',$row['feedback']), - 'tstamp' => array('integer',time()) - )); - } + parent::syncSpecificFeedback($originalQuestionId, $duplicateQuestionId); } private function syncSpecificFeedbackSetting($sourceQuestionId, $targetQuestionId) diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php index 350bec9c6eec..e1d9f2257c82 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssMultiOptionQuestionFeedback.php @@ -27,22 +27,23 @@ abstract class ilAssMultiOptionQuestionFeedback extends ilAssQuestionFeedback * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificAnswerFeedbackTestPresentationHTML */ - public function getSpecificAnswerFeedbackTestPresentation($questionId, $answerIndex) + public function getSpecificAnswerFeedbackTestPresentation($questionId, $questionIndex, $answerIndex) { if( $this->questionOBJ->isAdditionalContentEditingModePageObject() ) { $specificAnswerFeedbackTestPresentationHTML = $this->getPageObjectContent( $this->getSpecificAnswerFeedbackPageObjectType(), - $this->getSpecificAnswerFeedbackPageObjectId($questionId, $answerIndex) + $this->getSpecificAnswerFeedbackPageObjectId($questionId, $questionIndex, $answerIndex) ); } else { $specificAnswerFeedbackTestPresentationHTML = $this->getSpecificAnswerFeedbackContent( - $questionId, $answerIndex + $questionId, $questionIndex, $answerIndex ); } @@ -96,13 +97,13 @@ public function initSpecificFormProperties(ilPropertyFormGUI $form) { $value = $this->getPageObjectNonEditableValueHTML( $this->getSpecificAnswerFeedbackPageObjectType(), - $this->getSpecificAnswerFeedbackPageObjectId($this->questionOBJ->getId(), $index) + $this->getSpecificAnswerFeedbackPageObjectId($this->questionOBJ->getId(), 0, $index) ); } else { $value = $this->questionOBJ->prepareTextareaOutput( - $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), $index) + $this->getSpecificAnswerFeedbackContent($this->questionOBJ->getId(), 0, $index) ); } @@ -125,7 +126,7 @@ public function saveSpecificFormProperties(ilPropertyFormGUI $form) foreach( $this->getAnswerOptionsByAnswerIndex() as $index => $answer ) { $this->saveSpecificAnswerFeedbackContent( - $this->questionOBJ->getId(), $index, $form->getInput("feedback_answer_$index") + $this->questionOBJ->getId(), 0, $index, $form->getInput("feedback_answer_$index") ); } } @@ -136,16 +137,18 @@ public function saveSpecificFormProperties(ilPropertyFormGUI $form) * * @access public * @param integer $questionId - * @param boolean $answerIndex + * @param integer $questionIndex + * @param integer $answerIndex * @return string $feedbackContent */ - public function getSpecificAnswerFeedbackContent($questionId, $answerIndex) + public function getSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex) { require_once 'Services/RTE/classes/class.ilRTE.php'; $res = $this->db->queryF( - "SELECT * FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s AND answer = %s", - array('integer','integer'), array($questionId, $answerIndex) + "SELECT * FROM {$this->getSpecificFeedbackTableName()} + WHERE question_fi = %s AND question = %s AND answer = %s", + array('integer','integer','integer'), array($questionId, $questionIndex, $answerIndex) ); while( $row = $this->db->fetchAssoc($res) ) @@ -189,11 +192,12 @@ public function getAllSpecificAnswerFeedbackContents($questionId) * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent * @return integer $feedbackId */ - public function saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $feedbackContent) + public function saveSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex, $feedbackContent) { require_once 'Services/RTE/classes/class.ilRTE.php'; @@ -202,7 +206,7 @@ public function saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $fe $feedbackContent = ilRTE::_replaceMediaObjectImageSrc($feedbackContent, 0); } - $feedbackId = $this->getSpecificAnswerFeedbackId($questionId, $answerIndex); + $feedbackId = $this->getSpecificAnswerFeedbackId($questionId, $questionIndex, $answerIndex); if( $feedbackId ) { @@ -223,6 +227,7 @@ public function saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $fe $this->db->insert($this->getSpecificFeedbackTableName(), array( 'feedback_id' => array('integer', $feedbackId), 'question_fi' => array('integer', $questionId), + 'question' => array('integer', $questionIndex), 'answer' => array('integer', $answerIndex), 'feedback' => array('text', $feedbackContent), 'tstamp' => array('integer', time()) @@ -244,9 +249,15 @@ public function deleteSpecificAnswerFeedbacks($questionId, $isAdditionalContentE { if( $isAdditionalContentEditingModePageObject ) { - foreach( $this->getSpecificAnswerFeedbackIdByAnswerIndexMap($questionId) as $answerIndex => $pageObjectId ) + require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php'; + $feedbackIdentifiers = new ilAssSpecificFeedbackIdentifierList(); + $feedbackIdentifiers->load($questionId); + + foreach( $feedbackIdentifiers as $identifier ) { - $this->ensurePageObjectDeleted($this->getSpecificAnswerFeedbackPageObjectType(), $pageObjectId); + $this->ensurePageObjectDeleted( + $this->getSpecificAnswerFeedbackPageObjectType(), $identifier->getFeedbackId() + ); } } @@ -255,6 +266,7 @@ public function deleteSpecificAnswerFeedbacks($questionId, $isAdditionalContentE array('integer'), array($questionId) ); } + /** * duplicates the SPECIFIC feedback relating to the given original question id * and saves it for the given duplicate question id @@ -277,6 +289,7 @@ protected function duplicateSpecificFeedback($originalQuestionId, $duplicateQues $this->db->insert($this->getSpecificFeedbackTableName(), array( 'feedback_id' => array('integer', $nextId), 'question_fi' => array('integer', $duplicateQuestionId), + 'question' => array('integer', $row['question']), 'answer' => array('integer', $row['answer']), 'feedback' => array('text', $row['feedback']), 'tstamp' => array('integer', time()) @@ -317,6 +330,7 @@ protected function syncSpecificFeedback($originalQuestionId, $duplicateQuestionI $this->db->insert($this->getSpecificFeedbackTableName(), array( 'feedback_id' => array('integer', $nextId), 'question_fi' => array('integer', $originalQuestionId), + 'question' => array('integer',$row['question']), 'answer' => array('integer',$row['answer']), 'feedback' => array('text',$row['feedback']), 'tstamp' => array('integer',time()) @@ -333,11 +347,12 @@ protected function syncSpecificFeedback($originalQuestionId, $duplicateQuestionI * @param boolean $answerIndex * @return string $feedbackId */ - final protected function getSpecificAnswerFeedbackId($questionId, $answerIndex) + final protected function getSpecificAnswerFeedbackId($questionId, $questionIndex, $answerIndex) { $res = $this->db->queryF( - "SELECT feedback_id FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s AND answer = %s", - array('integer','integer'), array($questionId, $answerIndex) + "SELECT feedback_id FROM {$this->getSpecificFeedbackTableName()} + WHERE question_fi = %s AND question = %s AND answer = %s", + array('integer','integer','integer'), array($questionId, $questionIndex, $answerIndex) ); $feedbackId = null; @@ -350,32 +365,6 @@ final protected function getSpecificAnswerFeedbackId($questionId, $answerIndex) return $feedbackId; } - - /** - * returns an array mapping feedback ids to answer indexes - * for all answer options of question - * - * @final - * @access protected - * @param integer $questionId - * @return array $feedbackIdByAnswerIndexMap - */ - final protected function getSpecificAnswerFeedbackIdByAnswerIndexMap($questionId) - { - $res = $this->db->queryF( - "SELECT feedback_id, answer FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", - array('integer'), array($questionId) - ); - - $feedbackIdByAnswerIndexMap = array(); - - while( $row = $this->db->fetchAssoc($res) ) - { - $feedbackIdByAnswerIndexMap[ $row['answer'] ] = $row['feedback_id']; - } - - return $feedbackIdByAnswerIndexMap; - } /** * returns the table name for specific feedback @@ -421,16 +410,17 @@ protected function buildAnswerOptionLabel($index, $answer) * @final * @access protected * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return integer $pageObjectId */ - final protected function getSpecificAnswerFeedbackPageObjectId($questionId, $answerIndex) + final protected function getSpecificAnswerFeedbackPageObjectId($questionId, $questionIndex, $answerIndex) { - $pageObjectId = $this->getSpecificAnswerFeedbackId($questionId, $answerIndex); + $pageObjectId = $this->getSpecificAnswerFeedbackId($questionId, $questionIndex, $answerIndex); if( !$pageObjectId ) { - $pageObjectId = $this->saveSpecificAnswerFeedbackContent($questionId, $answerIndex, null); + $pageObjectId = $this->saveSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex, null); } return $pageObjectId; @@ -442,22 +432,23 @@ final protected function getSpecificAnswerFeedbackPageObjectId($questionId, $ans * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificAnswerFeedbackExportPresentation */ - public function getSpecificAnswerFeedbackExportPresentation($questionId, $answerIndex) + public function getSpecificAnswerFeedbackExportPresentation($questionId, $questionIndex, $answerIndex) { if( $this->questionOBJ->isAdditionalContentEditingModePageObject() ) { $specificAnswerFeedbackExportPresentation = $this->getPageObjectXML( $this->getSpecificAnswerFeedbackPageObjectType(), - $this->getSpecificAnswerFeedbackPageObjectId($questionId, $answerIndex) + $this->getSpecificAnswerFeedbackPageObjectId($questionId, $questionIndex, $answerIndex) ); } else { $specificAnswerFeedbackExportPresentation = $this->getSpecificAnswerFeedbackContent( - $questionId, $answerIndex + $questionId, $questionIndex, $answerIndex ); } @@ -470,36 +461,29 @@ public function getSpecificAnswerFeedbackExportPresentation($questionId, $answer * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent */ - public function importSpecificAnswerFeedback($questionId, $answerIndex, $feedbackContent) + public function importSpecificAnswerFeedback($questionId, $questionIndex, $answerIndex, $feedbackContent) { if( $this->questionOBJ->isAdditionalContentEditingModePageObject() ) { - $pageObjectId = $this->getSpecificAnswerFeedbackPageObjectId($questionId, $answerIndex); + $pageObjectId = $this->getSpecificAnswerFeedbackPageObjectId($questionId, $questionIndex, $answerIndex); $pageObjectType = $this->getSpecificAnswerFeedbackPageObjectType(); $this->createPageObject($pageObjectType, $pageObjectId, $feedbackContent); } else { - $this->saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $feedbackContent); + $this->saveSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex, $feedbackContent); } } - public function specificAnswerFeedbackExists($answerIndexes) + public function specificAnswerFeedbackExists() { - foreach($answerIndexes as $answerIndex) - { - $fb = $this->getSpecificAnswerFeedbackExportPresentation($this->questionOBJ->getId(), $answerIndex); - - if( strlen($fb) ) - { - return true; - } - } - - return false; + return (bool)strlen( + $this->getAllSpecificAnswerFeedbackContents($this->questionOBJ->getId()) + ); } } diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssQuestionFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssQuestionFeedback.php index b6771690a3dc..e9da7f2c4459 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssQuestionFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssQuestionFeedback.php @@ -133,10 +133,11 @@ public function getGenericFeedbackTestPresentation($questionId, $solutionComplet * @abstract * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificAnswerFeedbackTestPresentationHTML */ - abstract public function getSpecificAnswerFeedbackTestPresentation($questionId, $answerIndex); + abstract public function getSpecificAnswerFeedbackTestPresentation($questionId, $questionIndex, $answerIndex); /** * completes a given form object with the GENERIC form properties @@ -351,10 +352,11 @@ final public function getGenericFeedbackContent($questionId, $solutionCompleted) * @abstract * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $feedbackContent */ - abstract public function getSpecificAnswerFeedbackContent($questionId, $answerIndex); + abstract public function getSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex); /** * returns the SPECIFIC feedback content for a given question id and answer index. @@ -437,11 +439,12 @@ final public function saveGenericFeedbackContent($questionId, $solutionCompleted * @abstract * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent * @return integer $feedbackId */ - abstract public function saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $feedbackContent); + abstract public function saveSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex, $feedbackContent); /** * deletes all GENERIC feedback contents (and page objects if required) @@ -688,8 +691,8 @@ function getClassNameByType($a_type, $a_gui = false) * * @final * @access private - * @param type $pageObjectType - * @param type $pageObjectId + * @param string $pageObjectType + * @param integer $pageObjectId * @return string $pageObjectEditingLink */ final private function getPageObjectEditingLink($pageObjectType, $pageObjectId) @@ -729,8 +732,8 @@ final public function getPageObjectOutputMode() * * @final * @access protected - * @param type $pageObjectType - * @param type $pageObjectId + * @param string $pageObjectType + * @param integer $pageObjectId * @return string $pageObjectContent */ final protected function getPageObjectContent($pageObjectType, $pageObjectId) @@ -757,8 +760,8 @@ final protected function getPageObjectContent($pageObjectType, $pageObjectId) * * @final * @access protected - * @param type $pageObjectType - * @param type $pageObjectId + * @param string $pageObjectType + * @param integer $pageObjectId * @return string $pageObjectXML */ final protected function getPageObjectXML($pageObjectType, $pageObjectId) @@ -777,8 +780,8 @@ final protected function getPageObjectXML($pageObjectType, $pageObjectId) * * @final * @access private - * @param type $pageObjectType - * @param type $pageObjectId + * @param string $pageObjectType + * @param integer $pageObjectId */ final private function ensurePageObjectExists($pageObjectType, $pageObjectId) { @@ -855,8 +858,8 @@ final protected function duplicatePageObject($pageObjectType, $originalPageObjec * * @final * @access protected - * @param type $pageObjectType - * @param type $pageObjectId + * @param string $pageObjectType + * @param integer $pageObjectId */ final protected function ensurePageObjectDeleted($pageObjectType, $pageObjectId) { @@ -985,10 +988,11 @@ public function getGenericFeedbackExportPresentation($questionId, $solutionCompl * @abstract * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificFeedbackExportPresentation */ - abstract public function getSpecificAnswerFeedbackExportPresentation($questionId, $answerIndex); + abstract public function getSpecificAnswerFeedbackExportPresentation($questionId, $questionIndex, $answerIndex); /** * imports the given feedback content as generic feedback for the given question id @@ -1021,10 +1025,11 @@ public function importGenericFeedback($questionId, $solutionCompleted, $feedback * @abstract * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent */ - abstract public function importSpecificAnswerFeedback($questionId, $answerIndex, $feedbackContent); + abstract public function importSpecificAnswerFeedback($questionId, $questionIndex, $answerIndex, $feedbackContent); /** * @param ilAssSelfAssessmentMigrator $migrator diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssSingleOptionQuestionFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssSingleOptionQuestionFeedback.php index ceff1133b4a0..bdf212c85d8d 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssSingleOptionQuestionFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssSingleOptionQuestionFeedback.php @@ -22,10 +22,11 @@ abstract class ilAssSingleOptionQuestionFeedback extends ilAssQuestionFeedback * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificAnswerFeedbackTestPresentationHTML */ - public function getSpecificAnswerFeedbackTestPresentation($questionId, $answerIndex) + public function getSpecificAnswerFeedbackTestPresentation($questionId, $questionIndex, $answerIndex) { return null; } @@ -68,10 +69,11 @@ public function saveSpecificFormProperties(ilPropertyFormGUI $form) * * @access public * @param integer $questionId + * @param integer $questionIndex * @param boolean $answerIndex * @return string $feedbackContent */ - public function getSpecificAnswerFeedbackContent($questionId, $answerIndex) + public function getSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex) { return ''; } @@ -94,11 +96,12 @@ public function getAllSpecificAnswerFeedbackContents($questionId) * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent * @return integer $feedbackId */ - public function saveSpecificAnswerFeedbackContent($questionId, $answerIndex, $feedbackContent) + public function saveSpecificAnswerFeedbackContent($questionId, $questionIndex, $answerIndex, $feedbackContent) { return null; } @@ -144,10 +147,11 @@ protected function syncSpecificFeedback($originalQuestionId, $duplicateQuestionI * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @return string $specificAnswerFeedbackExportPresentation */ - public function getSpecificAnswerFeedbackExportPresentation($questionId, $answerIndex) + public function getSpecificAnswerFeedbackExportPresentation($questionId, $questionIndex, $answerIndex) { return null; } @@ -158,10 +162,11 @@ public function getSpecificAnswerFeedbackExportPresentation($questionId, $answer * * @access public * @param integer $questionId + * @param integer $questionIndex * @param integer $answerIndex * @param string $feedbackContent */ - public function importSpecificAnswerFeedback($questionId, $answerIndex, $feedbackContent) + public function importSpecificAnswerFeedback($questionId, $questionIndex, $answerIndex, $feedbackContent) { } } diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifier.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifier.php new file mode 100644 index 000000000000..752a905c4b67 --- /dev/null +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifier.php @@ -0,0 +1,98 @@ + + * @version $Id$ + * + * @package Modules/TestQuestionPool + */ +class ilAssSpecificFeedbackIdentifier +{ + /** + * @var integer + */ + protected $feedbackId; + + /** + * @var integer + */ + protected $questionId; + + /** + * @var integer + */ + protected $questionIndex; + + /** + * @var integer + */ + protected $answerIndex; + + /** + * @return int + */ + public function getFeedbackId() + { + return $this->feedbackId; + } + + /** + * @param int $feedbackId + */ + public function setFeedbackId($feedbackId) + { + $this->feedbackId = $feedbackId; + } + + /** + * @return int + */ + public function getQuestionId() + { + return $this->questionId; + } + + /** + * @param int $questionId + */ + public function setQuestionId($questionId) + { + $this->questionId = $questionId; + } + + /** + * @return int + */ + public function getQuestionIndex() + { + return $this->questionIndex; + } + + /** + * @param int $questionIndex + */ + public function setQuestionIndex($questionIndex) + { + $this->questionIndex = $questionIndex; + } + + /** + * @return int + */ + public function getAnswerIndex() + { + return $this->answerIndex; + } + + /** + * @param int $answerIndex + */ + public function setAnswerIndex($answerIndex) + { + $this->answerIndex = $answerIndex; + } +} \ No newline at end of file diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php new file mode 100644 index 000000000000..3fc6157091c3 --- /dev/null +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifierList.php @@ -0,0 +1,104 @@ + + * @version $Id$ + * + * @package Modules/TestQuestionPool + */ +class ilAssSpecificFeedbackIdentifierList implements Iterator +{ + /** + * @var ilAssSpecificFeedbackIdentifier[] + */ + protected $map; + + /** + * @param ilAssSpecificFeedbackIdentifier $identifier + */ + protected function add(ilAssSpecificFeedbackIdentifier $identifier) + { + $this->map[] = $identifier; + } + + /** + * @param integer $questionId + */ + public function load($questionId) + { + global $DIC; /* @var ILIAS\DI\Container $DIC */ + + $res = $DIC->database()->queryF( + "SELECT feedback_id, question, answer FROM {$this->getSpecificFeedbackTableName()} WHERE question_fi = %s", + array('integer'), array($questionId) + ); + + $feedbackIdByAnswerIndexMap = array(); + + while( $row = $DIC->database()->fetchAssoc($res) ) + { + $identifier = new ilAssSpecificFeedbackIdentifier(); + + $identifier->setQuestionId($questionId); + + $identifier->setQuestionIndex($row['question']); + $identifier->setAnswerIndex($row['answer']); + + $identifier->setFeedbackId($row['feedback_id']); + + $this->add($identifier); + } + } + + /** + * @return ilAssSpecificFeedbackIdentifier + */ + public function current() + { + return current($this->map); + } + + /** + * @return ilAssSpecificFeedbackIdentifier + */ + public function next() + { + return next($this->map); + } + + /** + * @return integer|null + */ + public function key() + { + return key($this->map); + } + + /** + * @return bool + */ + public function valid() + { + return key($this->map) !== null; + } + + /** + * @return ilAssSpecificFeedbackIdentifier + */ + public function rewind() + { + return reset($this->map); + } + + protected function getSpecificFeedbackTableName() + { + require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php'; + return ilAssClozeTestFeedback::TABLE_NAME_SPECIFIC_FEEDBACK; + } +} \ No newline at end of file diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php index 718694c867b8..b07152b6a0d3 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assClozeTestImport.php @@ -393,8 +393,10 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co $this->object->setClozeTextValue(ilRTE::_replaceMediaObjectImageSrc($clozetext, 1)); foreach ($feedbacks as $ident => $material) { + $fbIdentifier = $this->buildFeedbackIdentifier($ident); $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(), $fbIdentifier->getQuestionIndex(), $fbIdentifier->getAnswerIndex(), + ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) @@ -432,6 +434,29 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co } $this->object->saveToDb(); } + + /** + * @param string $ident + * @return ilAssSpecificFeedbackIdentifier + */ + protected function buildFeedbackIdentifier($ident) + { + require_once 'Modules/TestQuestionPool/classes/feedback/class.ilAssSpecificFeedbackIdentifier.php'; + $fbIdentifier = new ilAssSpecificFeedbackIdentifier(); + + $ident = explode(':', $ident); + + if( count($ident) > 1 ) + { + $fbIdentifier->setQuestionIndex($ident[0]); + $fbIdentifier->setAnswerIndex($ident[1]); + } + else + { + $fbIdentifier->setQuestionIndex($ident[0]); + $fbIdentifier->setAnswerIndex(0); + } + + return $fbIdentifier; + } } - -?> diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php index 435b210d76dc..82e2b7096fc1 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assErrorTextImport.php @@ -104,7 +104,7 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co foreach ($feedbacks as $ident => $material) { $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(),0, $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php index 89d0dbd9ee0f..3750ae2f8f44 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assImagemapQuestionImport.php @@ -314,7 +314,7 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co foreach ($feedbacks as $ident => $material) { $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(),0, $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php index bbb7db57f172..b9ccaf13ae8f 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assKprimChoiceImport.php @@ -354,7 +354,7 @@ public function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$ques foreach ($feedbacks as $ident => $material) { $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(),0, $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php index 9004241fea50..95edf7b09d4c 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assMultipleChoiceImport.php @@ -354,7 +354,7 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co foreach ($feedbacks as $ident => $material) { $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(),0, $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) diff --git a/Modules/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php b/Modules/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php index b26cda878621..100a5c74eb22 100644 --- a/Modules/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php +++ b/Modules/TestQuestionPool/classes/import/qti12/class.assSingleChoiceImport.php @@ -348,7 +348,7 @@ function fromXML(&$item, $questionpool_id, &$tst_id, &$tst_object, &$question_co foreach ($feedbacks as $ident => $material) { $this->object->feedbackOBJ->importSpecificAnswerFeedback( - $this->object->getId(), $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) + $this->object->getId(),0, $ident, ilRTE::_replaceMediaObjectImageSrc($material, 1) ); } foreach ($feedbacksgeneric as $correctness => $material) diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index d890ed47c335..3f111ac83eb2 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -516,6 +516,22 @@ assessment#:#cloze_textgap_whitespace_before#:#Leerzeichen vor dem Begriff assessment#:#cloze_textgap_whitespace_after#:#Leerzeichen nach dem Begriff assessment#:#cloze_textgap_multiple_whitespace#:#Mehrere Leerzeichen hinereinander assessment#:#out_of_range#:#Ausserhalb des Bereichs +assessment#:#ass_cloze_fb_mode#:#Modus der Rückmeldung +assessment#:#ass_cloze_fb_mode_gap_qst#:#Rückmeldungen je Lücke +assessment#:#ass_cloze_fb_mode_gap_qst_info#:#Für jede Lücke kann eine Rückmeldung hinterlegt werden. +assessment#:#ass_cloze_fb_mode_gap_answ#:#Antwortspezifische Rückmeldungen +assessment#:#ass_cloze_fb_mode_gap_answ_info#:#Für jede Lücke können unterschiedliche Rückmeldung je Antwort hinterlegt werden. +assessment#:#ass_cloze_gap_fb_gap_label#:#Lücke %s: %s +assessment#:#ass_cloze_gap_fb_txt_empty_label#:#Lücke %s - Keine Eingabe +assessment#:#ass_cloze_gap_fb_txt_nomatch_label#:#Lücke %s - Falsche Eingabe +assessment#:#ass_cloze_gap_fb_txt_match_label#:#Lücke %s - Angegebene Antwort: %s +assessment#:#ass_cloze_gap_fb_sel_empty_label#:#Lücke %s - Keine Auswahl +assessment#:#ass_cloze_gap_fb_sel_opt_label#:#Lücke %s - Auswahloption: %s +assessment#:#ass_cloze_gap_fb_num_empty_label#:#Lücke %s - Keine Eingabe +assessment#:#ass_cloze_gap_fb_num_valuehit_label#:#Lücke %s - Genauer Wert +assessment#:#ass_cloze_gap_fb_num_rangehit_label#:#Lücke %s - Wertebereich +assessment#:#ass_cloze_gap_fb_num_toolow_label#:#Lücke %s - Wert zu niedrig +assessment#:#ass_cloze_gap_fb_num_toohigh_label#:#Lücke %s - Wert zu hoch assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Verknüpfung diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 8bc0bec9000d..66f61ed67161 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -420,6 +420,22 @@ assessment#:#cloze_textgap_whitespace_before#:#Whitespace before the value assessment#:#cloze_textgap_whitespace_after#:#Whitespace behind the value assessment#:#cloze_textgap_multiple_whitespace#:#Multiple Whitespaces assessment#:#out_of_range#:#Out of range +assessment#:#ass_cloze_fb_mode#:#Feedback Mode +assessment#:#ass_cloze_fb_mode_gap_qst#:#Feedback per Gap +assessment#:#ass_cloze_fb_mode_gap_qst_info#:#For every gap a single feedback can be configured. +assessment#:#ass_cloze_fb_mode_gap_answ#:#Answer specific Feedback +assessment#:#ass_cloze_fb_mode_gap_answ_info#:#For every gap separate feedbacks can be configured for different answers. +assessment#:#ass_cloze_gap_fb_gap_label#:#Gap %s: %s +assessment#:#ass_cloze_gap_fb_txt_empty_label#:#Gap %s - No Input +assessment#:#ass_cloze_gap_fb_txt_nomatch_label#:#Gap %s - Wrong Answer +assessment#:#ass_cloze_gap_fb_txt_match_label#:#Gap %s - Given Answer: %s +assessment#:#ass_cloze_gap_fb_sel_empty_label#:#Gap %s - No Selection +assessment#:#ass_cloze_gap_fb_sel_opt_label#:#Gap %s - Select Option: %s +assessment#:#ass_cloze_gap_fb_num_empty_label#:#Gap %s - No Input +assessment#:#ass_cloze_gap_fb_num_valuehit_label#:#Gap %s - Value Hit +assessment#:#ass_cloze_gap_fb_num_rangehit_label#:#Gap %s - Range Hit +assessment#:#ass_cloze_gap_fb_num_toolow_label#:#Gap %s - Value too Low +assessment#:#ass_cloze_gap_fb_num_toohigh_label#:#Gap %s - Value too High assessment#:#code#:#Code assessment#:#codebase#:#Codebase assessment#:#concatenation#:#Concatenation From 1a2f78859a21e69412daaf620d54879f3b4fe4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Heyser?= Date: Fri, 27 Jul 2018 13:29:36 +0200 Subject: [PATCH 099/166] fixed bug in results tab --- .../class.ilParticipantsTestResultsGUI.php | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php b/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php index 4b5a87b0a0cd..c002cb1fec67 100644 --- a/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php +++ b/Modules/Test/classes/class.ilParticipantsTestResultsGUI.php @@ -308,14 +308,7 @@ protected function deleteSingleUserResultsCmd() $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); $participantData->setParticipantAccessFilter($accessFilter); - if( $this->getTestObj()->getFixedParticipants() ) - { - $participantData->setUserIdsFilter((array)$_POST["chbUser"]); - } - else - { - $participantData->setActiveIdsFilter((array)$_POST["chbUser"]); - } + $participantData->setActiveIdsFilter((array)$_POST["chbUser"]); $participantData->load($this->getTestObj()->getTestId()); @@ -337,19 +330,23 @@ protected function confirmDeleteSelectedUserDataCmd() { global $DIC; /* @var ILIAS\DI\Container $DIC */ - require_once 'Modules/Test/classes/class.ilTestParticipantAccessFilter.php'; - $accessFilter = ilTestParticipantAccessFilter::getManageParticipantsUserFilter($this->getTestObj()->getRefId()); - - require_once 'Modules/Test/classes/class.ilTestParticipantData.php'; - $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); - $participantData->setParticipantAccessFilter($accessFilter); - $participantData->setActiveIdsFilter($_POST["chbUser"]); - - $participantData->load($this->getTestObj()->getTestId()); - - $this->getTestObj()->removeTestResults($participantData); + if( isset($_POST["chbUser"]) && is_array($_POST["chbUser"]) && count($_POST["chbUser"]) ) + { + require_once 'Modules/Test/classes/class.ilTestParticipantAccessFilter.php'; + $accessFilter = ilTestParticipantAccessFilter::getManageParticipantsUserFilter($this->getTestObj()->getRefId()); + + require_once 'Modules/Test/classes/class.ilTestParticipantData.php'; + $participantData = new ilTestParticipantData($DIC->database(), $DIC->language()); + $participantData->setParticipantAccessFilter($accessFilter); + $participantData->setActiveIdsFilter($_POST["chbUser"]); + + $participantData->load($this->getTestObj()->getTestId()); + + $this->getTestObj()->removeTestResults($participantData); + + ilUtil::sendSuccess($DIC->language()->txt("tst_selected_user_data_deleted"), true); + } - ilUtil::sendSuccess($DIC->language()->txt("tst_selected_user_data_deleted"), true); $DIC->ctrl()->redirect($this, self::CMD_SHOW_PARTICIPANTS); } From d9c8cb6567e1b2b7c5a55a57580d3cf3b8b21691 Mon Sep 17 00:00:00 2001 From: Alexander Killing Date: Fri, 27 Jul 2018 14:27:06 +0200 Subject: [PATCH 100/166] removed old media element dirs --- .../classes/class.ilPlayerUtil.php | 2 - .../media_element_2_11_3/background.png | Bin 166 -> 0 bytes .../media_element_2_11_3/bigplay.png | Bin 3001 -> 0 bytes .../media_element_2_11_3/bigplay.svg | 1 - .../media_element_2_11_3/controls-ted.png | Bin 1559 -> 0 bytes .../media_element_2_11_3/controls-wmp-bg.png | Bin 1960 -> 0 bytes .../media_element_2_11_3/controls-wmp.png | Bin 5511 -> 0 bytes .../media_element_2_11_3/controls.png | Bin 1892 -> 0 bytes .../media_element_2_11_3/controls.svg | 1 - .../flashmediaelement.swf | Bin 28644 -> 0 bytes .../media_element_2_11_3/jquery.js | 9597 ----------------- .../media_element_2_11_3/loading.gif | Bin 6224 -> 0 bytes .../mediaelement-and-player.js | 4982 --------- .../mediaelement-and-player.min.js | 167 - .../media_element_2_11_3/mediaelement.js | 1868 ---- .../media_element_2_11_3/mediaelement.min.js | 65 - .../mediaelementplayer.css | 850 -- .../mediaelementplayer.js | 3112 ------ .../mediaelementplayer.min.css | 1 - .../mediaelementplayer.min.js | 100 - .../media_element_2_11_3/mejs-skins.css | 283 - .../silverlightmediaelement.xap | Bin 12461 -> 0 bytes ...T CHANGE THESE FILES. USE -src- FOLDER.txt | 0 .../media_element_2_14_2/background.png | Bin 166 -> 0 bytes .../media_element_2_14_2/bigplay.png | Bin 3001 -> 0 bytes .../media_element_2_14_2/bigplay.svg | 1 - .../media_element_2_14_2/controls-ted.png | Bin 1559 -> 0 bytes .../media_element_2_14_2/controls-wmp-bg.png | Bin 1960 -> 0 bytes .../media_element_2_14_2/controls-wmp.png | Bin 5511 -> 0 bytes .../media_element_2_14_2/controls.png | Bin 1892 -> 0 bytes .../media_element_2_14_2/controls.svg | 1 - .../flashmediaelement-cdn.swf | Bin 29229 -> 0 bytes .../flashmediaelement.swf | Bin 29194 -> 0 bytes .../media_element_2_14_2/jquery.js | 9597 ----------------- .../media_element_2_14_2/loading.gif | Bin 6224 -> 0 bytes .../mediaelement-and-player.js | 5158 --------- .../mediaelement-and-player.min.js | 176 - .../media_element_2_14_2/mediaelement.js | 1930 ---- .../media_element_2_14_2/mediaelement.min.js | 70 - .../mediaelementplayer.css | 870 -- .../mediaelementplayer.js | 3198 ------ .../mediaelementplayer.min.css | 1 - .../mediaelementplayer.min.js | 104 - .../media_element_2_14_2/mejs-skins.css | 289 - .../silverlightmediaelement.xap | Bin 12461 -> 0 bytes 45 files changed, 42424 deletions(-) delete mode 100755 Services/MediaObjects/media_element_2_11_3/background.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/bigplay.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/bigplay.svg delete mode 100755 Services/MediaObjects/media_element_2_11_3/controls-ted.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/controls-wmp-bg.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/controls-wmp.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/controls.png delete mode 100755 Services/MediaObjects/media_element_2_11_3/controls.svg delete mode 100755 Services/MediaObjects/media_element_2_11_3/flashmediaelement.swf delete mode 100755 Services/MediaObjects/media_element_2_11_3/jquery.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/loading.gif delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelement-and-player.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelement-and-player.min.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelement.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelement.min.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelementplayer.css delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelementplayer.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelementplayer.min.css delete mode 100755 Services/MediaObjects/media_element_2_11_3/mediaelementplayer.min.js delete mode 100755 Services/MediaObjects/media_element_2_11_3/mejs-skins.css delete mode 100755 Services/MediaObjects/media_element_2_11_3/silverlightmediaelement.xap delete mode 100644 Services/MediaObjects/media_element_2_14_2/DO NOT CHANGE THESE FILES. USE -src- FOLDER.txt delete mode 100644 Services/MediaObjects/media_element_2_14_2/background.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/bigplay.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/bigplay.svg delete mode 100644 Services/MediaObjects/media_element_2_14_2/controls-ted.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/controls-wmp-bg.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/controls-wmp.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/controls.png delete mode 100644 Services/MediaObjects/media_element_2_14_2/controls.svg delete mode 100755 Services/MediaObjects/media_element_2_14_2/flashmediaelement-cdn.swf delete mode 100644 Services/MediaObjects/media_element_2_14_2/flashmediaelement.swf delete mode 100644 Services/MediaObjects/media_element_2_14_2/jquery.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/loading.gif delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelement-and-player.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelement-and-player.min.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelement.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelement.min.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelementplayer.css delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelementplayer.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelementplayer.min.css delete mode 100644 Services/MediaObjects/media_element_2_14_2/mediaelementplayer.min.js delete mode 100644 Services/MediaObjects/media_element_2_14_2/mejs-skins.css delete mode 100644 Services/MediaObjects/media_element_2_14_2/silverlightmediaelement.xap diff --git a/Services/MediaObjects/classes/class.ilPlayerUtil.php b/Services/MediaObjects/classes/class.ilPlayerUtil.php index 2c696acc950d..c8aa286bdff2 100644 --- a/Services/MediaObjects/classes/class.ilPlayerUtil.php +++ b/Services/MediaObjects/classes/class.ilPlayerUtil.php @@ -11,8 +11,6 @@ */ class ilPlayerUtil { - private static $mejs_ver = "2_14_2"; - /** * Get local path of jQuery file */ diff --git a/Services/MediaObjects/media_element_2_11_3/background.png b/Services/MediaObjects/media_element_2_11_3/background.png deleted file mode 100755 index fd428412ae26af13dab448ec833b1cb603e37ee9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G9-c0aAr-gYoHrC?P~c(M$o@YsOF*1w?-55oQ{94SCl?~X dlYaFB3@*Kl;yd0~_5jUf@O1TaS?83{1OV-(FBAX( diff --git a/Services/MediaObjects/media_element_2_11_3/bigplay.png b/Services/MediaObjects/media_element_2_11_3/bigplay.png deleted file mode 100755 index 694553e31c387188b6bde397a5200c212aff2dc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3001 zcmY*bc{G%78y@@E$G#25HuhyKF`}BmWY5?NW#6Vk%!n-cHO9Ub5>Xh0LXv%?$k?+c zNmQ00Tal8G?=9y$-|xH5dG6<4u5&-vKks$kBpd6~oNS_OAP|Vt%+%NxxV?eX3uXYK z;#~bV;9|g|&FsNo@bsrMAAxer#?s#8@bGYOa1f~e9pAip1C*koq7Vp#g@px}&d$yP zO=o9kEEa2SZjM5skVvGKmX@NTqLh>rz@*V=04FLc3XjKMx^&6c*Y}A0<;xdfl$V#6 zot>SUno1&(0M1dfp`oF=y1KNq6qxSp>;O&RQ7V<%-rf$3fXumtV7=o>}9f*fVT3SoX_%9s(2SDCmgdHLAF8~<;9s;0HD1el~82}p_ zhlk=O%KE@Y)+?q?co2x4_s^jlo4LLZG@%5Oa|F8}UqYBihz|(m9pvc)!{9=E!h%9B z;bBI0+RSz1MF3Cm56>vX$Af?i3WV9?0(?LSggOGLj?hrWQxL%RIWuFFeR#q8-AEVF z7SR_0COM0mr%?~9?p$cBE?DVL1}EAG`gMOfylt%(l&Py_1{Yy2d^+s)`yvkhylrQ} ziks_1ch8md;g%;c=m}`MtE#Tj0t_wA*;UDS>w=s7dg>@-eXxR=0W5=B%I=0Sy3|JM zx&-m*kI7Ee__^(6+KHCPT+a{4($>mc-#72c7L2V5F_`kD)0kUd$n`P@$MQ^CsJs+& zm=w$LyOxHy!I8df*5y)nf5uhAXj?rgT2?MDn4`8$@iFk5HU%#x&#-so>Ed^A5dn|)!cq2@uj zLiB5IjSGnWUxNwgllH0WN!pkbQ|27y1sbRKr#$(aI#1oCY2gP>gjC#BEJ8f!Q8-hL z#**sbmg(&q_1*QGPE3J?*g$rH?h`OXE-YRtnFg&;kl%hv^463$2BVv@3lghX%$puK z7uQ|gBig#M8`C|~@|oFefsXx-yt>l$!aDGF=Vb_lWcKVb?k18uDj!E;bJkYMYPaBr zkRlw%GCKyUw97HSe1$MJ>7SVkq2w3Ff{tOC#W2~R^DYSDR&PL%025;|E;y1|O`3@3 zQjDp|!|dgLiASbg;D0+1O1x*tU{;3XVErcr9Z*OJAj7D#<(-pwNM@nhpzzOj3*BA+ z6oGT;LwOa9*$I$w;TfkjKg>kHs8C|05!2_&v|L!1Hav_OYwf8y-(RJki{5#HL0lq1 zQakPiw})%2vO7*o2Z+Cw@|M3AhQYg~{X&oa{&D4T9Ww#;ubVHg%@w{54@)F>oqRA{~oCvahigz^a2`g;5|_faxl-EI}z(7I)DDYIpNPJ|*Z>+G0i zS`t;RTqG@dRo1iEbk4Ay08Lt1ccY##hi+VqOE@0IU_P{f=@++)(Mzw?3Et!rXzsd^ zAF%cM`bWpJ2{9_=5jzt%tPRQ{3#RJjOp2*|AH+M=P}~INo*@ee9;L+A;*tG)2+4Sh zJhIZqd*1M;=PzZe+ESPk)2O<|V$ItO^NE_swu^%2NxJOk(Y%+17C$m`1z`@H-ly=u!p0DxAH_TYh)IT35y4xnCY5g2y4sZB3R-Oy-N2syQA>1g5ZR| zju{&VXK=XlV}}azq8EYkC>poj1wKtFGTu}2`<^y$E{M%D)uZyKPt^TTRZsaFA}n_Q zpmZ8|BG>%`l@ASaJK@LvI3*Q?I938SiG%Jpf#K8=NV2EO>q)jN6+Vbmr=NDRGR~W4 zWK?56N+Iq!z)bwEAnIl1;ul6lWZ;8Jy!Da1)i~e&*Q|M9ca~n_K_W~eI<}c@A&+*C*E}4b@7x8^Hoh-w|6iYILnz)@YDn8 zfLtpX@j`tcXp1o&s=|+|6rOkcfjzeoMT6LROfNK%o-c2rECUAS338{pKbLK6N+vRT z+-H&+x?Vu*x5M~(>JH-8$mbYGVVWgNaXjSI0!LJGQXGs0X?nN4ARqBH+E^FaNM%!z zr=6njH-hagX@s8?OKCs#z8Ib;g0}Pu@q@%6bE(^=F6s)tX%Hf!S!+mW$2Yp;qF8A) z!*SA@?3baqU!9}rb_M@6I)+Cv9VDKNA2O1O1_6~1uPH?P`Hu-{_OYd`5CMF@S-8-DDNuIi-p%3D7a%L4)<`t`k9FP4-b!uzGHACSKa@B1vL}*#w z%ga|D2Jx2x{xv6_oI_CiR=jN3uYMoff$H?<#qrbTP2D_BOIKFodu}aPY_gviV3ye6 zV&@L~$xIq_+{o1rf!Zz7*)T>YG3>nEaA8?dp}Z*OC4@p6x_$zHUy}{3$H~f#vnqPrL4vi9K0d5<`f6qspVM_~ zErP$%klg%YrB$I!RIg3s`{`hHwk78G8=6hz`t-9sO#(jt;X0RF`?V*+R|7vyNM_#} zaJflc?~oZ1Uf~#q%i46;xvgF%q%)N?BP*~)s%=~TQ(Seb)6&f3uOxvC0r>Io3^C`= zJ9Hec=Oj=G`>xM+y;;+SqHEGNIz}WIU13!9c+Pp@d$Qw+ly7YU!wSR$Q>~~DMH((iW<7KMWpkoWBN4M( zj)YZVYD7P(b8yFfwn*oD+)Xj0Gxy-S-NaR%!Wr`_RX5*6E+bE$*1Oi3uFsc>z+Yii z$k4NqGnzOZV1Hu-xfi*8C->m@4pUcO#tQhD^#tjU&A9a(f|6s!%zo#WnFIfWATtwd J;~KO(@jp;uhBg2I diff --git a/Services/MediaObjects/media_element_2_11_3/bigplay.svg b/Services/MediaObjects/media_element_2_11_3/bigplay.svg deleted file mode 100755 index c2f62bbc0d42..000000000000 --- a/Services/MediaObjects/media_element_2_11_3/bigplay.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Services/MediaObjects/media_element_2_11_3/controls-ted.png b/Services/MediaObjects/media_element_2_11_3/controls-ted.png deleted file mode 100755 index 3aac05aa83cb7fed54831a19d85a8c267e939720..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1559 zcmV+y2I%>TP)b($m${ z*ulZUq@<+Ga(0rEl9ZH`pP!%1jF6a^n9k17S65e|p`o6hp2^9{eSLkbtgPMK;L9^M z+S%N$uCCP7)cpMX_4W2?X=#LngolTRmX?;bwzgkiUyO{5%M1^>xw(;%k+-+E)6&+J zm6fuxvf|?6#l^+Q$jId6%EZLP(b3VXtEe}8|OnVHSZ(SU$}$;r#*<>hvEcKiGL`uh5i zkdUgXsxvb)^78WY^YifV@a*jD@$vER@9*vH?d$97=H}-4`T6JP=k@jV_V)IQii+y$ z>iGEh=;-KXW@hg0?)3EZ%K!uE>FM|P_y7O@%*@Qh?k|P_0004WQchCKUO+1^xvT^Vf+4-v z?sc7Z-MiLHLK3gJBmxDokhc7(Po9K~ylSxWt2dNo@;Sl?lo7!uO(A{9KKhcXlIi|* z)y1Qa5h#7)A$5HCA#2rYb!%&D^wFbHwOX})PFczr-dbY>A5ReB;9pG+3Mf@97FAW< z`^_$@#bPp{9Hl1lR*DgxJ|!m+1Vd;O67twE40X&%lVL0z8dpaq6uf@iEEu|V$;8rlYmO1Fx9fWQbs~s{ba@$F zUPy;++u3Y(V|{%iXWP49#CDZ$@Ky&SP&x!de!Sv^TxlU8J&w%E1a)EdAq-cS`Z;F>v|iz|9xRCPd0Ub675YM~q&DLV{d z7{(zX9XP}RNIKR_5iBPO;7B-77M5R&vkX}Y2U>&LnK(6AlW?FxuwRK|!=Qu%0UU<` zK*E77)i3}^ICz%7F?0%r%)R)pkRl7YIh_mue8*g-7=Rz5v5=e7$p8@Kh<~DS@k0z2 zVxf}(fbZ~PzA`xYAte@KppyZB@9`$se=vLUO}IZaubxa3 zHs_?lA)@Ti%^if~n@~=e6=#!aA{BDtfhTuUxQTp7)I=iW-Tv*8e@-s{iI;zOUB+AZ z^8cR5Bi=-ws7cf$Y7#YxnnX>aCQ*~9Nz^225;YN_?$H>b_y?Un1O5gTSFQj6002ov JPDHLkV1l~`FgySN diff --git a/Services/MediaObjects/media_element_2_11_3/controls-wmp-bg.png b/Services/MediaObjects/media_element_2_11_3/controls-wmp-bg.png deleted file mode 100755 index 89bb9b95602ecfca6290f6006f817da365da7b90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1960 zcmYLJ3se$l7KXGe6VuYMbnUV2H0EK-rZuw~cg#9Qf|=4d)s!)##yeTU#4AaGIBw2M!o?G&wajHQ+#IeSQ6{TekqJwY3!@CMa-vdV1if zv$GR?IK6i5T5)ml{}KQPh(S5r;>wjP&CShl)Y8&o;&$W44Jc76l~4{wrNH(1uz_GnvVUcBQ4I5YcEfmoHxiz^hlU0s$y2Dk=g^fGP+G zoGU9UK{TKQ$w05XyxhbMgn~j-JIIturJ%fAE(gVOb8{gXw$;_uVzD?iHI+)Ga=Bcg zPzYO?2&{2&acnkQCX*Eu6hJbpz!ut>CI$Bu2m~f=p}Ps0$KwH8=nF@{%A^U*2n-N1 zGc&WYvXYaN6$(XSVj_)31Mr-joQQ}BkQ$}~Wa8uFRVo#1K?KtSfCSjW#Nl4~`T2kY z1SAp(lgXsh>EYqwAZ%G#SyWUM(BN=585tST(b3>1BqV_B3UV!>TfQc^%e00E%@4Iu<8s08mk zEG%rTm1rEih4?`Kpx_v`q?C>Iw6UbA1yNHXB0U?P%RLoy1_#Pc6Bjn4GBN{^9`~*3Y-$uL9F5rr6?+VGm zyG|p@Hc1geS-FPDzwo)}_n(iN5d|Q?;S?T3)&poALSKB2gQgbb;^G|g24*I0dc~VFJw&h{e z%oaEA&>dqUL&qu4CBH}Ot(@2tp9^&>ZPmfaUFUQA&)RQk#;Ddy)fat>n>=k^=h0$c z4kJd3q@jGma&GnTgOtwguluea`gB{HeS;mre@Hj)b3eWbqAvl%mm(I@>Vq*WZfQ%Dfu z>&6W;-+6dT1`ghQL_jjsM#s1yy&sbTk z#{^QXzoYS0jf&kA8hOB-^aGLe!Haq(vFqtZKhlOf5k+HuBwg7*M*XQN4}3UxoQ>D! z@$cS-QuEFq<1EsvRWnP+7xsSPdSiK`|HCw;QztB#D1sAQdstr-~oiDZrX zexfB#!?X)Lp>KSQ#s2$U1=INOuh!uKyf^m7srS)l7?3o*oE$X1Uaz>-Mh+wyB1Wq6 z6_b9x;sDYep{JGd!|M&2FumqK_euAsdOaRg7`0vPLXLI^T`hiaTu86#U;9p#WWlZ zR`3<_7fYS)vx=OUvZu^L1rf~)Q`S^z%=qB;nZadq)v?Jly1@}_*7CkE+UHlPpRN}i z#~&ik%1TG!)#9C7es^yZ>7VMIQ%myj);|1-xp?A7m)7xp&w?giMXluyGYlc^Rc0@3 zrPNWv>?tq#$gc~h27U4DTJ!##3r}pUoR(KK(f(|6TmM)I|3?_YQU4>2hcB@I3!4Eg A)c^nh diff --git a/Services/MediaObjects/media_element_2_11_3/controls-wmp.png b/Services/MediaObjects/media_element_2_11_3/controls-wmp.png deleted file mode 100755 index 4775ef5b02faf2b826d35dfc72511b6b27fea87b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5511 zcmV;26?p22P)9hLyBQ4hN&GAyCPy3!zfCIF;tR_%QPgZWEhGVlEmz8l0-uy zA|i<-A{&Xwilx`S&-eM9+Szt@>YeGk?YsMSKRnL8=iGBneV+R{zh<7vdaTtrS~QEp zgQ{0P)HL^@zEy+W6^5g2SY2CR_rv1!!{YF2HI7y->j}(itIesgT4AfJ$7AZ_|FEpz z#VYL{&DuPmdHpd&r#G>>vXAZU6CCWH;b{LHhs43&8FscmU~TmPGqG*>`sY#iMni?Y zUHf@t*j~)e&SHOmA41Ca<#qM;Y#~b0{~{qI!mBm9#uN$LboTMd750xlVsqyds~g8y zTsuO1Je7fkFde#_6{#`e0+ty-801Iw$aiaB{7}a{~H;hPzawte^&7O19W`7 z;Y(Sg&6ekLIuUyP8j{MrJDi6iWgoCDU6&F{e8I;vUfT^SQ2Z@jaq2_HXxOcD~0$H9mZ zybuzu{C*%1NJ8$JugdSX^;pNU-sgXXjEZ`X=IWY3V)IxzGtMXw5+>>B3Bl|Q#$tD| zrPA@+XC~Ei>Ru5ww|s!D{R9JYw`%JuC3v(zy3;MXJ-eNsw!yB z%}El@2L|wAd>p62U@}k2??wvAyq6G`{YBtA{Pm^m*OO7#q4kHBvCXJh-ui&(!fw*( zqjdVPU}g*9nN184A;I)5Ztqzp&!}fdlORbXHNJkVh#8G;pwb@r$2XNki1Sn8!-o&3 zvsB@#yBqNN6s`sbljR4onB+;h8otsYVU%fEtn@@8O!BaVhf>La@dx_)Z#ui_Ab=WR_O@fp9s0W zzD@}tB+}9Xa40+XcyQ%%sp~7ZJ4woEd%MaD*43f5?qTCmix6ij#|8;;v?74$k0j6+Stk)7I7&SgxYaN8|OWK+0A6E6nr)RkA@Dmz;_eY>|eS$0c`K$SOIEJa`1q_ck@#9%OF&&|yV5vpB> z!(qSk{7Eu>YO`>|7m93X@3+qt87eAfgow>sBGwTg{I^7Sg0pZ1CeY>|hr53Uox{t- zQW9Mw%L?5@k{pSi$SpB4+_7zvEu^bH=QlVP%a@H(naA>PR#_a9e&}a#aBx8KS65e2T3T9?%2zBuesV(Uxi5H2hm0=l z(MekZahN#nmWTv=eABz>*ltEZt8W|^msj}o=^E1u3AA*N!bM`*!;1bG z|6v~@ZAkiE-cf{RG#Yc0nJJ!m;^=T`er*+VbjE-XLG(qxlHns6-Q#QMA`#-)4x+hp z_?w;UhqDW4lM86^O`@$o22UssZ|E(&L+@`0QjtSq*aq8DZTcnZ*heq>>Q^to7G@GB z7@FMVT+)GucIpG%XPlm%Ds=mNsJ**W=dk22D@T|lNGkWKcIt8 z&S`J0oc8`#tcpkDuUcminm^zhy2W8+lSFKwlaA^bTO(EpMT!hnBxZ?DeGexigwB5u zmmjY%9$Q9J*C<*!zj$SSVH>I~l99w%f_=)+D5OY~9H7lRFH2w&^2|(*)0PxM3JY_7Do|gUGKVY6oGmc;wh3Bg9v>U~vXeU=849!w}mZSA95p<0O8K zeQ&_edBhjpLW?_&Qa+d72UI`K6SmsA9IMp^S(W`+gw*>qn_~v`-Z)(0H8^~6lr%?> zXA8q<)sRP+Z5r}zK@`>oQB*fbVg`|4<>8X|Q^vTw{0MDg0cJ}(Owzua~-fov@!TZHUtR)iaekiP#=PDH<{{TXKn*8%dMo3PDMyK33 z$7ClQ!^n4pQ9u+nkeFAuaE6d??T}q^Mwoq5>>?9e4oc1arTyD3D3Py_RZ*Ol;Mat&pmYTy_T3SfbZ$76~I@+}= zTLh+xv6~2?Q;c=P$a9Rq)Hnk3s}bZoM^NY-Mq$$pikj$1Tc_-j(?Vi%2hMhqYY(8X zQA0r!ui2j2dDBN;!zl9X!yLB4@=~amqskfuqZfA$7w=ugNwl=NT}@|94^gb9YcX7LTB*pw9+y5Q5c;Q$ZH;liO5$NLt*nM3SW;A zqcGR|z(ckNvA2JSj^Gpugoq|h>M0bk9cjPG#~5;LBPv47mE*9~OrX-y zp`}9zp+3fKYHpTmbxO-&O|Iq4D|sz1M|Dk2-tRwbSv8bdV=z<>Bd2y0xs8)B%1Wyx z3KL;&i6EaSXpNxo7G^q`|7=Pe9vvYRmDO55@`Z@jNg`QSwPjX_BJWL9^_A=73sFA` z0~Zw~H8E7y48zjk($e|FP;hy9DZ`UoOT?tS7OImc?9otHpw;Tj7{)FSgs{J$VOV*Lp?4Fgjh?};J?s@h#iyXcHhK2~r+E!5CJcm+#8|6Df?wzCfBB=d_&+k`5 zrHk`PAkd@AMn*>DG+13v&&>Q8%io%YR;{eIPiyjTqsaXp1`_hZy#S;4EsP!Sk=OoK z!PF6lxqSiogsEj1>sve6-rYm@=se7CLX4y+3fkvWBL7Rh_PBuUy@RoRk$8t(&jJiB z5{>hC71-f&H-v}22E@tv?p!4wCaF~xzmy=v$j)6NL!w6IzkB~)4W-SmUnf^tzrB^# zd9=E=IW!EPAeUqqh}_O)@p)j2sR_-S1Iw2l>85<`KL$ zck_9HZ<%eZ5Ypb7w8=hJVDP+y!L6&(+KK*ixFIcH5xO(pjsOjRVz z&Cknb5qY09~f7Fg$tk~MEwT1U#8#|$s7Gdb3!vY(~?caoPU<*b``!QA%^ho*JUU7~vO2nOFe4E;OE4ecQ>vD+d6WoO%u!+*}9y%62qPitQLfmP8lW}}}0!vFv zDbKP72ZNAuMw3BB#-C~>Hjh@r)lu)-Csfa#BU{^targj*$FxK98 z+ABo-7p%C(t<`#_plx5HdF=}3$T9MX(&=NA&)lFwFnx4`=%GUSltS`8Sw15;zQqZO zqQ|Pux}^&Y?tI4Uz9kgac#);sL-UDSv$joOZ08a^N1tI`zCcOr3}v&Yuq>RSYT*pk zlFonl2Gt7cn&pC1N!9Ze^JgfIvHsh0bRPVLiGyo2`R8;p(zQ~XTdQasgtokosZW6K z_%rGgA5ro40+#m|s3A$V<#W`n+@MyjiTc}n8zDqm7SB-;=k>}Zyoc9_e)=0@TPLt` zg;reS(aFfv3adM{mVr4$_fIkR85sWf8J_)X*f&1Gnz%v@Vf_}#Jb9*V?;6_0XUy_s zcJ}lV?W0R5vii6i=h296kBk-$VI}nev;}7o*`!K}q`U(VBPRLAhv{(b79nE%>N93Y zKpeo*6)?SXLL%ZQYv@C9wMQr7=Mm%4ifmn|X$`@p%^^6qfyl-IV!IzOe{`-8+dD;+ zq=n*}@D4Aa&OMCcx^9%PuEp+hWa;)4-&S!u$|zkZ=9eT&>^`mB;nymf0t)2~ewA0u z^Qf|VFZj)t(k$I{`%hXyLFLQx3QKMZow|WKb&Wc8jXHIW+H{RNb&Wc8bxobRMxDBL zpH7`^8nSfz|4oJ;>eTT_oqA=KZa+<@UQV5Q?N89DJE>E*Wa;*3nOh!p>eeUFsn=4c zUi4MdXUWde?OQXMDoquY7Q-C@XxS6%)KO9YHCm)^{td;?ngol*k!`UwWT()nTYjNV zz54t9U8J$J{f==CeOFl>85n*ChF9Yj!xYxhl*6( z9vz=S?2zf;_&nSLGon$KRPI$iO`enIrCz!^b$#ffPQ4^uoqDT(0vFT|-Zjyzzv&(o zm9C`f{;u(P)|<>kr=IoRV`h74=+vJfA(`vcMWZfKu^%9Wlr#)TnzB5f^^$GXvmN3ebn4G(4tcyzJ-?=1 zG<`py(8>sTHOSPPvYho;KS{2%{ZBge=SWEAI(2H)Q~ekv^V{*#Nk=nHRc>azqDIa| zL_xz)dOG#zV~^LVQ=^_zc$v@qqUJEmJ5rT1zf0<8rc-}D_IRB-HR`Fvm(1@*(VG!T z?Q52XB|0J9^2B9=2vS(rm4QKznDqEk1$>Pz{90cPeG zxu%$=E;lvyXQWfNHoS2>&j_)5jhX1wO-+L-e>6by3sw2RJ>|hnbm~=E&)KL`FG^RZ zZh8~O=JxL09}bXv<`0P`UDA8Xhtt-nTO4iXXH5uo>Uq?uYw76J^W?are@2`vzv!mY zCf=KwPmQ`to4zgQ^D*RkiS%^p)mgeJoq8d4>RMVl^)AjiZcfUsNtK#A=f2UYn>y#e zmV20X#naZQGdgm!bd$Nno%eP#=Q-7I(PQ8RW z^_SGC8?$tKI-^cKhdT8_YSN3SQ!k`OU4c4vl}DX=A$97xKMaG_^?HQ-Zf3LDa~d0* z`ow*onXH{~_!r?B-G-O&e2ZkBozIx8UHUKhJf+oiFJg6%3fZ$y*g3v>5c?lKVR`2Q ztfQ0i6jto$!4k<>J-otr;vC^sV}uzYdNZu z+G51)MQX6jGk=Z&60>r`eMtYBPRP%;iuN5?g2ym+A0b=dJ4UwuBqdlzoFKRR7*(MY zRCqRYLVmWH>`Tb*JS29IJ$Qy3?fgL)2G2>{E}uVu!4lO8`PpV^7((UbB^>LYVOzaM z-P*&bB^ozAt2(T!6Y>=H!WM;Z<650l%>$TA4Jngn)U&xZJvyG$a!NW-(zXm+&kn4D z!1lLb9?R4-Ii(#s8M^7Fn{K-4rkifM>86`*y6L8yZo2(y`yb9PTcN(KP|W}U002ov JPDHLkV1kZU;|2f# diff --git a/Services/MediaObjects/media_element_2_11_3/controls.png b/Services/MediaObjects/media_element_2_11_3/controls.png deleted file mode 100755 index f6a857d800b64264443af4609e0ebf7175593d8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1892 zcmdUv_d6R37snIh7SSuz2t|V4+9fes_1q$f5VaGdb}NdiM3cByUnLZ^M^$TvYH6+3 zZtmo)edTp*6v8gC|(^b&SOo{wj536 z1dNZHlfT#k*&N}fAhCEpKEA0rt62a5cx-KsH)gZhr>CdK$Hy!d>*(m{=g*&qhld9T z2mAZ`dwY93J3HIk+gn>(o12?VCUawBV|{&nZEbCJb@j)OA1f;>%gf7)i;D{j3-j~y zb8~aEv$HcZGt<-4-@kvKnwpxNoSc}L7#|-W8yg!L85tfP9vT{AFc<>^1O5H|eSLl1 z-Q8VXU7eks9UUE?K7DF$Z*OaBYi(_9X=!O}Y;0&~sIRZDt*x!DuCA)8s;sQ6sHi9} zFE1-AD=lp(DJgmN>Q!-ZaZynboleisFE1!4$ji&i&CSip$;r;ne(~Z(R#sMKX6Dl~ z3Ntb?($mvZQc{wWlM@pY6A}{Q}azH=;kx2CS5AyY;`1<;KdwUZI1W!*-4-XG_cXu~8H&<6z7Z(>NCnrZo zM|*pFJ3BiZ4rgm?Yhz<$Wo2b)X=!0$VQy}I^X5%6Gczm}Yhq%8!C=s6Gzx`6B9VrM zh6V-(H*Va}*Vot6)4O{0s;;iC&KY`IT3QGM0uG03YHF&fsi~@}s+@5}Sy@?0Nl8&r zQBF<{c7{9@3YC?Wm64H=mX?;1l9H5^gg_u-ViICvVj?0U!otE}Fjz=P2m}HF0DxcW zN>Glxq1syFIA`#G0pOSvCe9g}Kfv@B1pokvoCR?BpYUItra0Buk%}UF-KP4Ih%mgb zzZU=w*MMtjz!Bwb*F!iC=`#n8be9@*o9qQJB9QKQ!7#pLuOJfHj{-yCbXS_I69E7o zAQo+ecYOTSp5OU`gXsCo*>52)lh6vn8Kw*N@0iYb)3|u0uPUYjPIZ@+Fb|Aw;YTAb z5*xWH@ll7~EM`~`)BMw-YTd3_=zldfr+26G^c6|9ubO%Xt;J`06(dSSt}I(|Gi z-t*F1?mb=zt7(_@kXPIV9`QVZ_h_=)jyh07AvR3;X^zG5ym@b#-hWSSIn=C%#4L8k zRk&*G8xRFvgjcG~)1kFGlmk}OLyo{W4j`6Fzxc*HS|(D|o~1m@SscZF+L z#~vvMstrBA_Ut3ef#^Ibr;l=euG~%w2lKfw#MAo4XLW1*>dM6xyEb>7=|IK#WHB8` zjjHs6@{iuC^4}_Hl6MiQbsB1S)$V7}MEUE=$bbJUu(f>*Q_KVT`;GYz_5$C1(R%6r zb#HJh?!lJ*kOI<%MLcD~mNhR`P@4Y99ItYSw=Vh1M^^5ka9jwFIa@j(%2fcPT#Oa; z$P5qkI;b^~7@g$4e8@-<40LSip>WT}^x%Zd7+l@7|JWO% z&1A^bwBo;5TXcQa-8&$H=Tb!$vr3zbzQ8pu%r5jL`%DPuszpuvua(L60t1TbgZCko z$rV?gHL%rBURyr;Y=dm7eHvM=&>StyOr>#g`B7x!UbcIrVl{)GdZ$T&(wg@_sC2dr z`|qxGJHtOJe;$JFDyUfJ1H_VOq`r4y*%!^gAc|g&r7g!IlJsjXZlAOaE|G^qD{dtaisv}19gi`!-Cwlo2@;UsMHwBU z>>3{ZsS_za)C)>Ab3+%Ak?h9s5*Wfjlxi?aW~MA$@F`^apbn~d)8K{l cc \ No newline at end of file diff --git a/Services/MediaObjects/media_element_2_11_3/flashmediaelement.swf b/Services/MediaObjects/media_element_2_11_3/flashmediaelement.swf deleted file mode 100755 index 9832d7b98b4f75e16c09b86706ef0e861174abb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28644 zcmV(|K+(TLS5pck$N&I%oXov)hP}H^&_ujK=akCV)uA!*eE3`T-(&h+d^I>hy^^ou*eX zp^}Ab6owpGNJ3{>oL-luG8kY8g{Fu1+tdx(t3m<2{Cs?UI{5^4^6ir7>njfkkOy?_ z>Mn<(!qcXD+tTCEX8Z{YSu2E>Sd%BM5q*P@o@-;@+ zUhERZsFZ4jER1D=otH*kJ43b;?*?5%GLv0CbMFbNPwK|$6{XA3%0dfO`nNk`sk|Gt zhK%wZ3I03VG3s1(ra@-RQOP0_W1<7t8JH&^3yzDF8Fez1R*|7m$=Yd*-3!!8m9Aa3 zvAay6RWc=te5IN#QEO#7jZ&qTW$1My4Jy6i{Q_vr>VI2*P@|Zb5AUelT<=hbeJOfJUBGWH#DSkSYQ`c(Hl3d?JZJkFeK}96h)3sJqL`;l)?z@^m$`K=+015xINZSW!LZv@lO}ogHlK{WWGIA!^Rl%BAva&QNWEM9s1$8} zXJ37cDmpBnU!N{YLw-z%58CHo0c02@WU90(nJ+@Ug)*if`n@QpHbCjH_xVLvL&~EfAv61k4ZCs}&j)i59>?y$?M_N*ql^apc@Y?lSImZdYC! zZw7BAuab9&H0_p{Ei zo?^Ysy3*QDQYASl@vy0|d1`am*4^%!U5(v3d%lCvp}oWB4s#vWI-GT&q(0K=(qE)O zj@gc@9e;L|IE`<%s@XTq20E{DmLQ4c##yua9t)P7EjZPE>;2S88!sJt{Mz=Dk;*6Cr_YlI!H_3~l27l?idr%K{=8*J%c4fKLc*et7Y+W7eVY_E0NEkAiI-eUqk5%}kbzxYQ%T$U7SR0)#himhue>9j|8Q0sLk^VC)MEP zip3`-X#vqN&qI=+q>`Vk3!_ccx*mwL$6tbB%W=uAMHiYp4-fG>bz%?cHqKc75fVWWq5TH zsQ;WBxp&&#yyku#N3BEx+LgLfNws)B_Q#f8!rF~Fkn(iID&x4(XJ#Kg-+bH?_c4Es%+eM)pTDwIpNA4Rs`^FX0Bad{?z@#gVdz*eti-8W_u7$yR>ko zLk7R|02xZ^z8`0TxE9fpvhNJA)&5TobZ`;t~@YKeSW4Wu9WM!XZ*p9x-8{Y0uj2W1~FW4asXt&UOf*D%PT;@#m+N z_HZiAMWSRM)I(4-zarm6Jzk62=cW{SJa856xViOiSJxV^yt6Y!${+q%wx23{h%DuY zMCx-v$kurdj-36hMak#eB6Iyl?C#e3S!~-w)+;Jqu|rF?o^tnO-{cj- z3aI(+y359|!aBwwwC%@YH1)=&=^hhDPo<}A=qT>uGBy7*t7*?G9q7}yZjLIy`LA{Lksi)>LxWF!$3(McJ8ms#M=@wM^YS{NBRT7r!eA zE?>8{d+Sa={uMiEEl`y^A^w+#Zta}=U_)Umiy6JX{xY1`FROY|pACtA&Qp+Mrz@X7 z{_agdvBlhfh@G%aU3Guf6VcMDq5TdFA~L89M}i%1t}g!PQIKN>GD$3_C$>&ky16Qi z34*;3PoI`cOMt56?@P&BQymY`2$jw=VcKurTIb`dZC6AcYP-A`4cxjJrT%{53M$`N zensc!{6%4Fw?L0_mt94d5mmY$S^N@kqB8Gdm#=4*KXI9k&}u-?l)oazgf0TUd|3es})}BTj$}4L6^&KW`QHD5we>dT?%Eesc_5lw( zzC85sRObHmSv@RUyePdDIZV`{GIV#U-={ZP$B{EaayxMC^S*bdAyM@%MC}Rb?`VVj@oqk+`2z9LtgiE?|rr!g_Z?zo%Wt_{^GbxaRF7%*?IFE$hI@G z(Ds_Pao3Bg%ZI9V1r;YYbJ#p^+2sLgBQh)zFCqf*dJIS`yyzpWCnW< zm7q^{eslVpyu%ABXXSlv;zb@p{6tk!vjKAurAb60_xa&Bk|UZ=oQQY>*COX`6-haZ zDhiS~`Lj(mC7oI<4YGL&Q@Ga+p)tyZ)h|}~xBNl4a>v(lcZ+MOx7V!jymS4?(Gj^- zEo6??SJRFz7_;8%X?DMZH)M<=-M+B)VYr|giOxz8bwKI2cFMY8Xnfgz{AsRcguLuh z#XS=em8PJM!+z;LW3b&yi(KQ0z7z7UjW*_&m&fq$wCi)Y0me0clCiFZ?0ibO>}oSuK#i7;NeuI=!u ziop5T(WYw%o%dbqd3V$(*bK`mkkutm#g`pD70c%?K%xau7LrjGY&os%bY;o>jXB6e zzZs?bXDqu^cCcLD3sL2NB5`6XQO2t4o4y~tS(IAbbI9P5(HnEl?TBCKFva;gDzz}7 z9Wz9$cC|t^ePTDhe!Q^Itu%FgbL!TzwWgADTZ4UBE%N9hno)AugvY|6;Cb@fCFTBB z8MhF%^d54*5V3wm=EG+lOHbSGLSYX;K`i^VgN=Ik*@jjl&ihUZoH1+K^HDP&?`{|M z&F*&n{tiEQ&ro)t(?}Pq%syL24(wX7XmCY&^5Bj~rdJJbbLQg6eu`0Di5R{WM+cNg z&PGT&xEPJ%`Nfr;P{wh4B5GStgw_v1rO_1#xvnlo<(8obPi*_u!sFL@9-&@YE~jkQ z(Qzh>;gs6H~aN-XyJLK@lr$=#G z&v^xR!Uk-dB1ZQ6_M?i0VIHgcBf;JXRB(PvR8mEl!)FL5-ADW_BUFPAZ#3c0>`lVz zvW~;Nx=_a9Gyd|jyM#Oj1fjsp_>zm35y(Ei7{!Tq`9Iit{rmDu$bCr=wIsVtJt^<3 z{lOJ2qY?L?666_o#_u}WZ=XGAx3*9BtX)X7G!EJR+ODjhcv(C52^GN$ayW2u0?!unulZaVlTvflaMj}_CaL-?J%tJIzPmHcUolj3gycV z9+pQAhPK(y`<@r}EO|upfZ2$b9EX~HGCxUG02?+m5!qGiQU(p!tH?+}yhsV+kNxC) zvsFhCN05lbEtm7jd~SX39WqAu`ts+G&*1`@P`alEwcPm+IVI6<36~HYt!zY4F$~%N z)4t`N*-71x)eKcUJNc)N$B@7Yeh%_gbKn^MT1zgye{DxYXV*aM2lIC?8m+WA*>maP z!chtGSxc!GIT5Dz{UXNn`zh!WA(-3o=BJ&UjN2x!PPJN^$9pih^Ze0+eeSERL#tXc z5`Qp%v;U_*+W!3N%+J@-M826PdmZU+-1%bL-YdUVsmL|?A@uVzf`ZukA{pH@Q2U~-? zyQTcp@z~~{I!-~Nhd&{)o&IJ?ixa>8G%K|fp4lbJ+IoyO-l_B1u3x~tYQx^PoOI)-xVo7BlT73=q&lJtr z-4%Iztsc}XEv$HP$JIeq&oTuGpRE4l!O?T4E7a{wyn#+gIz#C&*mq9WSF)s~Gc-t? z8;3d!pj{4!N+$E?1lw<$Ah~AZd6po*ickB=j!g5I8gFeUDQX?`^=|hEW%d?-x^(hc zM<;OeH@k;BUk*Z|v^bPxQMG!d7hH=)L7czOTK~4f=kHBL3)bIq@7E#8ed|Pzdq@O& zvFV)a#)4`SDjRO%xbvugz|d`SLxLme-}86Lm46+Kw{AYSM{BG5^PlwVcx}@Fc`I_7 zJ~V7QJSHhA`GieCiFHzPaSv$+#KkRek%?nQMyqsb|c+_HLq zS5evXpQoNcqQ|$9c$bxJXyBIb zmKt)`|E?u|_G0hBc74M-j&bSy@YbpvD7@XLP1+dQYw!oBjD9x8^71I{!ZQ!YI-c6G znN)6Ee|7Bq=&HpbQwxtxv(QdU{(bIO_fxI%Z!Z#c1Tp?PNbvjU5nr8|-X&_0& zMs#ZvIHpsltFjX{_XcnJv#Nw#sZJ07q-S~TG>;eHM_oeVliv=1we;IzC~Y2`e8S;F z153_r-jax@#X@9HwJ2FJC9l_<-Q^=jE=*k*cu_q+DRq5BWDQD6+>doUhoqhM{97H8 zhRj$Mgf2RmaP&k!&+V!Xo*6C(CuAWiCH;J4%x6?s^Hk-KBV~WI4L`ej8eySaAH06G zY)UBdsJM=fY0}DFwv4&!R({}#l`JtBt_Jz#8#@jJr0qfuSBsJJa<8z_%e{h-{nHd= z-y>;tb#fB*om&oa=vIx|2KGu^aOq_iyRZx1J%(?}>KhX(Go^JC{c%2g?vlvO$ibr; zIo}i}hVyz@iwbXqZx7lYAHHr0S%ZppNN^$GZs^$2Ux(hyBf_bO1yx))%|ua_Z6eG3 zAGPay9j(hj+^+GZeZr91b@=#*$q3u^7}Vol-t1;ymLZE)uTG0MZ0`T?hSB}BW1qa? zC$e^|o`R^|m57sj!?h*ywaaH15r215mh|x*q~u)0s21=hd zVg_;m_q2t}KZ&}&w~@6f1xb>QDgH^CbM#e;2`8l@e#o%=L6#;s$Q=-u+x&un%n!Vr zBWq!Px42(P+?z=|{5sqtcE^&){nWw^$bQwN2Ql-WMJMi@dkiIHn0PNcIq34XX;udq zPwfo7FuHUsE2P=Sw=O%D7jVb|t~NIj=h_{QwC53h63Q0XbVb~;Yf<=&bt?B0$?;P) zqHS$9uI^~6Iz7;NfJ4rpwtK@PjmW`4Mm=)zJT-te_-SBcc1GMO-4?7_v!qqC4awJ^ ziuMlvXV+iZrAIHsd#I;v>A2o=6npero>2Ro`EJ6GUlc0?sf-On+M3XoQY7{{)}bug z&onIT#OjyQ$$MHyEqXb0;;|m?eZoeeb{;`g$#%h?8%tXE>wrWR$524#CuQdz9eG}U z=2w>@-?&Ac&$u{~)9n6cggzb7$JqDiw4+II5*0knN!Tt8kau2ic1=KliRYh!QY&u6 z{CM)};1x-~4fRC!uW$8z5ju9%SL!&_rlyNzq3gKm&A2gpazgpE5FZ!T~7W{Xa=9~V|yd=kG&e5icet*BIKuP)Nx zR&Bbnf7@P<{a4RjM+0q((WV25_a014SS1NUUG20>uidV)KM{miaoTnXic=TmhWRYY zuzuFV>CNb@xj(<0vZVa=vgVgWCsyvS*n;P5uy9iU&65ur1xz2=>$36HwukptJXx?M zc9JXcs*gwC-(Zn#bs*W}xn=B|oR{UV*W9}4U$*kKeCwT;8@$q|uey?k_4vN$v=bnX35F(y%vlHxOm9D=BM8Zc)$rFhEz6((xY9Ap!4q-1gzC&4EF)w|ta zM6@ZlhziIk+mGDO1yPrCqbmCq2F4-xIs2(w?N02d`2x9f<@U$OjmBZ8)oAsQGs_*L z&X4x|3`wj+sO7gSmnFa4jP4Jvgc*~UmM$fpYR;LQqRa;o?K+44G-72$$%3P80v0#p zrJDL7v;myD>ZhqYFWjC+cKA(~_8ruJNB{L%E78cg7xs)dEZvoIZr8(;Bi3*4le;4b zrx2IW>S~F{?R-LBGJ(8Ne82O?^$mqBM;zN6)nB;(T|WM_b=xr&LFR2M&XUbHCfKo3Y2h z+-G0AlE;o4hc-F>Gq7}FED`h|ZqLl`Cb@z9^zcKEM+&L(@ee_Xd z$j;%SuUig-u4lO+=Z~cJiUu#_<9j~2r#by>)qqQX#V+0duqazY%iG-A{@02*!|yAt zj(#_~GKICeC%Kbn!9`J^iA%2r{T@5O>%f345mEt`17VbheCkMg@H?c{?xZh5}a z%e^4jf8D&@t<^u?jh!qcp5T(CS0!It%;kc?c1G0WA5Ki4A8Mjr!SVIW52sp`hi=oq zGEwC<$g+i3)V7GP5$2a6uCv$8$I$p#1;P#P&}o~UkRbQXZtvGWn!FBS4tGQl`=EZ7 zJgAFFgLB+&&r=V!>=O3Lm>2^P&R8t0_TF z-4{&FSYgWBI0voYjl|E!l-;#_j6{+W)c#oBjt-8+k6+UR=Cn9yLcD%Ch*vlv$>A3p z#Pv)>lFKk)@`%iZP?d~OvwNSM&mM3Pac(6eE*6`NL3=%pl%0r5?i^#H7A!}iRToO~ zrfx=#4~o&S*!XrH1^D2OAZpn|12gXyv$rmxIzCS->qP$RI+*AM?dy?C~)+}%Cphd?BX z-;X>d4_>va-F}o_X2RpTMJy@3BoJLjq7j?X_XDdod2Dot z-KPs89u@j_YoVQZ=J$;ON7JGP?^*)2+KeTm9s3<0E3GN4F1lwm&Fi=LGhJ5ZukSmD zxN##&oJP->kd~wvw9(Oo69W**5nrjAe6XgKUzMd@XF^=l}nzv9)nw55Zcu#wz6xBpx^F%yT`HD zgEkIRZv-Cg{m8lNxP-~$!>@+QxAbT`bgf_XAw`SQ&@;h_8KSvo1C|9Lk73tQbjWW+ z-veUWKAk^*OZ1AsnK!)eMx_?GhaBE=dSV>!!S`Q9jyit)_LN_a%I&%z=g(U^YWpYE zU$pXZF!njR(rT&p_kC;6q)HSw|Jm8+6C^_}$Q89gZm2D4huWhK$Q^m0j>r>rLSD!l z`5<59hx}1z6o3Lz7t|GXLvqv|^*}vQFVq_ap>Wg}4M9UuDoR7?Xc)>wDx^VLq(gdS zK>2748jC(fpP^!Og!+lHliAChWX)vGvKF$IvQ{#g%thuZYbW!Ob(6_u6*5;BO~fCP z5-L<0D$Q###dC_MXWyX#an@^{*E)N)-sKf4UF%HhNX^^-k@8($0Qpp>~}e{J+5`0;)&v_ZODHlNAgoV#Vt@AimR;^qW{QN0j4*!@~vZR3=* zF4eQH#h3Eh6x&Pt-W=-eTWxcDai}{@^tT;&`LkE;xNq0irxw@MruP02T~n!hOv)Zo zKi8$+rsFg!TUa-@rT(?4;=0;T>&lupF`7u;xa>h)s*Suhbpy(l`PK50QWqbXDp`n5pQqC^--z;(1#2`i_L~8+c$Gn=FHkAH&>PB^tUVgW zDes%Q2^v4IEOM?4#K3?_g(!Q$` zAIePogqF|?F(AlV0PF*&y@f`8Z)Xh6yW@u4{bCQP*+e;p*<2AL?r z5N_+)=B^?L?cbB%*A=W6MzAr&)sbgP>4 zJSV>u@Z(7)fd-!Za!g4aM*jZ(&_U$D-PgCdZASJtf|WQ7U5|E)L!LLjxlMgXlz|v4 z38M@a%-VCrAcEt~wf!#gf0@G-Sc2{#E+vLKD2%!h{>y$Y*NTyn(AumlaAqQ8g9aqe z8h50omN_ppGV0!mnNERSD;>UjHuKzQb$Gw=j+Px$-5&;NZvPV7@1N72gNIke)h;D_#@mqJY}bs_Cr0cv3@Plcd$RqwZqxaSKQeD1W)ondvCB%YmvF4xZD3qe~OG&MVjW<1NL&0WeG=|;E9T4h~CsSzD z+1j42%$Jc|d-cfDX^pZhMW(8!D`Rk6Wd`+V$oTrW$})8toxZ1Qn=Iy^t4yOZ8dds) zJVmCOd>_%r%ZK^rDjT8FYndWn*Iqt8%(q2JzrA{R=k+q%6lnvS!ZPak|0bi34UHeO zv7Jy3YMw&Afn2Mt8!PDStx?MZH)M|J3U_nXay6^9Tu!XzdSee)VdRXGT2$qP`y}~n zS5Lf-S>3?)tOC5T36SybR*JnuKTkS+d+<>A#XMDIo9iH;c~INDRzNfTiGT<_)r5dD zRa%(rM@pzx5dVvWnm3Tp8@dyOEo!Ik3^L<8lEZ)kGKXQ0ujl9rRQg19t}5L)%E-!v z{sJc7k_b~ozIB$T$Tz6cGxFi6*vEHZ4-!5?nJ}ter!k~wWT)q5LX{n<;(!sio!r)B zZp5@9+q@kgOtS3^H~5m{**cuh+vm)d|8}?Fd=}+L4&A&# zeIZldN1=zx7EtNc5-OdxVP`c80`+8S`@>p*e1L-3kan$M;hg^|o{8!fiCLh1d?~5r z!Wy+elx6)&5V_b7UxHZWm4Le>VD;IW;yu2)TY$LBmXd$Rd$aNV-mH>{gZ}0rZh=m% zl;;?8HML*$mPa=H*0T5etK?)+yZ^_gL{a|Zuo@8de{5hkyrMm=n$9{;fil`%n| z$&by?%~0uk{`Wq>1cd(Q)*5U1B=>s1CrhY$49DAzIq2-$)HQrS4`(x{tY@;3psy_j{U*?TEm z-`}=YT5aD?(m*2a|4LOmYa8azH5yK@-TT`%%!rx474cG$IU6i%yJJKU`2HRm)#RBQ zMJTjNjY`jB8vA}^W3|%X+)hMhtY;c`{m90~x)$a>x_@LtgDzjI4AJH0!D+8DxAptT zwmA%Wm=;bO*@u-xpV{jD1jK}Xpqd!=)*q-Q#`^=!iIwf}LGBtDun(+No?cy`FskHA zwKBn|&>Q1`EwJ2eKG-#DmOemJXSzPHQ)`Lb?rv> z{g}2oZ5-1&UZqeL{-0We>Ge8&m;Xx(=I=^EVDD$EfuK%`0 z^qH~`k&s>eK?0&QE*~Ty=JA7F5Ci!^U2be5+x`nFYtBAMRcG`*xOZ#KUZ;`hLP%OZtIP-?F1^|CN%c(+3u+VaDtIWKEd~ zt5{e)uv7W${mt*+TWf0@n0v5IPyv@$~y)_ihw* zEzEsrx!{MB}-!l*E}k$qUV-@cMHA|Q6h``^a|uU`wBjaBInbocHRu5tCdSGdOP zzH^0pdo~U1(+9TBdoE;+Xx?)nd%GXTGIjhIg)rNtkDz7#{5C!@Chj97pItX+IAz=# z-R0zs?s6eItx=)Yf>RNr11U4Sw0Xxx@_whpF_Q^onGXqGDk4g z$uK8zIM5}izjk@e?jmoT`AFd`( ziw4&wB&jo~iIFxhlc9-G3`Rw^ieW*eZ2fLE7;ZT9%od~x9Z->X4;8ddt>S<63LglpdO`R8vX-&4^VY7aWiG zt5N+jv3s}OyQDeL&D&&6td?F?+b1(_lQS_J$|l5+pQj`)hgqqc*c)){>a7g%pO6Pdmg7Ev;pvM=|I3zn*c4hVYo><`RYAGB3I83Jc8lMytka zN7tpErC_*G8a(7m^9nDJ5=+yZ@E%)b**{}Thj3I5*X zYXaUG`D%?(t>vdPZ$G>Q5?ZsFM7=_5AitDimCz__$ySp+qg83d2@NuKY!&Obk~LC| zGTJ7*QzS?)U?y#qo~KulS({~$t~M|-pdk@QB&sAu%$pJ^Vn53er#BD`on9+0G3(p0 zG?wWZ`B~&zuOvh^<{aF!%^TgKwhuTk>2yUtEE%k#kg!A6P?&CBYkoR&ku=}Q^Nnha zfyW#Xmg!k)=siTi_`%k1z4eLepYLqmd5p7f>Xpr+!Sk6-I$rUWB-zNAJ+&!+>2APQSR}jjh>x?{c<{{2Q_u zzGT92tUkh$gT}J=YfyEXRY&krEMu8#n7Le#ZeT9oG~@7)6rWuzQMyi}WcQdwxH!w$~ZBUe>Fff{^Qkr#90v9^6lj_`P$Mn3$ zzHJ!uvqH+&ZnOMQknhZ6W-EEyeY0=M6SH~8ud;c^ zYqD(Q)6jKya?3_HZW``|YI2RrvuNF=thzy$N!NmfEl?%bZBW~WB`i~dbX~qNFW)F) zb(2_mIuU|!wMqjPE$gjofh8*ev%*_P>oVb{U#G~{4U|0=^wJI{I8sq)NXXHRtUdWH zYA4Z0p|@<1?voAbZ2n~1BE9}2a?A9FpJlI z6NnyI)+%(Q!H};KE8!^0GzMp8f?>3N%Q3TR?0#7`@~dp#@tthn^_1v3zlg5$g6KL~ z3Tr(aQN2GSZs?xayyL4#-u6ty4gCYTRw(WV~@hJk^f-VgtuV*mTw?!`sPp9 z-Sg?XTfM0M4o}xz%*73_%TDjR9NRZ_6}Eos;%n9TDr@uZ#kKx!Y7yN(I5aFYJv2Bm zSfpVzi2Np>FfP7--}tbE1YzwBM3fjA6P6SQbYUW5O4u>@31MOV!b17P_N$cKxai

    hzkozOpgyvjO;HOsb+q44hS6wBn3xDCJstZ z?4M4?4U12QMu`y??B`I-WGvaVSr}q-j))>O-aCo>x}EoMU5-{sKIW?ZIWU8Fw%XX} zN4LBU`b=-e@-_N&r+Xt$0gm;)|EbIFjc@zVkNrC(ZULPK{O|m}B;3)U~V9+y*Ufa7iYTu^lEV4@Q6;vOB71>vKp;67*$%89;+-GT-inSAI%6N zL;WMQSvn3}qdB^a;au_^W}ZTmk)O+lPNBH~d>Fz2FI%P6gYTr%W|MP*XC$_lTc}d# zdCbYqQR(%<+D*sTsI=KI1+^ho5zEQfYdA2DC@DTVUiC@7%3!og=${lDnjX?WCN4TG zG0aY{$^}!`h-F@;MrTlQphv#hgK6w3@tE&}(ahHd2t)h=g$BB&Yn>FirDp@@MK17U z=;cCB#?&aGfZ1D6z;-X^d%{Y|U9qd!vtB-OYtLHurVlBUTX{BgcjP=nE?fxsWWn=u zc^ZX2TgA&!cJcE`_}?|;9k;>0@p~idd9-d^7)gvcd(g2DkuV_7e+kwvZ z^l4{+ONAObf~2}2ooI1-o(_(R!k)f7MxA*KD~xWbtO|`Xwo4iDOax|jgrM6YIckkk zpwlv*2gHm5f(O#T+i+w(karvnOEM~RCiE$cREjh6RdpF5(_E%DaESr7fQx6SS(l{d z8>o!A^vXVAJCqYA#N4`WZ`n2~V|2(WyYZ7moogn{*Mj*}lKB#9a|MgsBlkQfW0kX35}6#Yub>XVPL-4W&y%uA4WR3 z+rPV71>RknS|^`>Und$mCIubbnRA@INi$oDNixS?qK->88@SHovc^wmcAmie;9%id z=lIISp7ow6?AHb^s@(c*ZG1EujIO0Y@@3rUJ6r)gMxeE)d5&dK7EMd4<`eMOqY z{(!7e2dj}u)RqyRw9Xy%>cf~RFXmCf;hkb~VQSgPqxCW;y%O@7+ilDGHe@4tvFTWy zF`SrT`*&(#<_(5P05$LeVs%zw1D=?NCUddfJ3Z*~q)mB`av5ExG5mN{cG##qX`0@c zo7cfG);Lx*)~IstJybr_d#E=rm*|R+)Dd&VXa3+u&u1krF#9>M<>7{z8^Q{mHRfbn zTGl6H6r(JH69&bGu<{qxJFC_TqfwEWQ^z`2OBN6=gXDZ-JBTwMfn2f^oH}@UxDWO2 z&>>4xFg7=@Gr-?n4rTOE?;&YJy;D273E--#0dof@U7mfN+bioNW4VXf%h(CXWl{g_ zO7qS`EaVMvIyOEz>RfNTciMtl*M4k^JuDaxne~j>`!jQR(8Nz= zF4#QAFA&wc+!hTyZaOz#V^nKkCksdF^vZ#HMIJu`5-PnURIkfR$WbVDBg54iFb3wY zQe`n2gTNe1$P<+s4D@K&kX~M1A};d`TA((lVZ-vTpgjH9Aq3#hVyNhQ~)&sXu7mj$qJ%qHYzDKrKZdp{#e zD`Zv`?3b|C(9(w5_XPIt#@;|g3|%;g43yMNNnQ<@s~_=ltQo(Lxp*;uGa&RC<to8H74c{0n8o2wgr-=(lhPDKAL#zm#z6PD%s`2}R zu=WoMMD>4LAguip1-kB!7{m?#big{Z@!twqHu@6^QA~euX3}H&Cx@Ankcy|x$)rRJ zHWeKfoE#>JjEzi;433tuzMWjgm&GI{hJ^}cNwG{qz-$V+42y_w3U^iVUcx8JCzpw8 zodLN_XwLk9?E4Fb$SNrI(?MOtjv3wx$wwk;)YqP9EG9(NNpT!HkIC3 z*fMMs>?*B7!<@h5FIc=31q!u>oIY}y%{#SL<}0PSoUS8o@oq;t=H!kHg;~@lxms8w zKBGG+_Q6W>94mv2hTP)}WH(!o>z}#6C08}v;Kdjs_BpPPUaibld6DZPTfil4EzBnx zY3>{>YxD&ZnJ$xAQV^RU7id7_cEMWN+jV?!{38d4SrAt_o%Mt*8}-9ek&w?^&I~vY z8-!~1?Z@EIUgj$6Aaj?Ek$o!jkad&|mGzMI5}2#8jL$p+rLfT;9-JjAqcu4@K*wv( z5+S2zkmHlXUX#K%5=XFjE$M&@i=^vXI-#BNZOMA^XLK`!9Zk!K=y3&z8G!-ZUlv|Z_);wAF;6o*vTa3t#^Q*C?5$lc8v`TZDI}K=4Gd;&KIwwB%HRx88_QasTo#^SW`*U>wzh)I zfc@-(J(J^!xwBxOotbodxCY5(kuoJ1<@T~1MS+U>zZgF27fAoNf6r!M&I>4TbX?H$9}P(J*T~Ad#b%Bc9%4M z%yaW$MW+-niz1hC@-mGA^9!HQXnx=mz%d7+Gw?uhb$aZB%{b9v0UD9B}>{%B*} z=det9{l~H)jk4}iENvn6l_p8Q#KKkBsS-O$d0Vg}z^4nd2;VzKor}c6Q#vmG0w8 z=kTNld98Su=7}u$))+#HZ_7lOhcOp7bFvVMnOdPUU7N71OHp+xZe7aWO2T$z)x1T^ zR;^{$jkBD$^7e1#Ed^|rV`axOV&lSg(>Tk0D{uc+-crD3Id)Q4>}0{Axb}{19NgM6 zm6&heL}|OG3VBTxS~gYKzNx|vqWZG0_ySA`ut0!m0k#rgX93kxK(!W7E&`690LKZq z$pR`}Kq&-NrhqaEs2NzmE5-u;YQP%6TEIHMdcX!i37`~E1}Fzq05$?PVF9j$wwp0s zjIluc6&7%}LA)K}9T4w?co)Q9LtF*%ZonSEUcfhieSrOd1Av2oZvi0!@gax~1C9WW z0*(QW15RLptpzEK7cdF0Vs;18@^?3x>N5xC8hFa2Ie7%I^an0Dc8L1Uv#f z20Q`O0GW#We|S>SPobLSPA$NPz+cFSPcN)q-y}ci*zku9bi2ra4dni6i@~r;wgu? z0<1hG9E7@WAwC2+ObMKh0FDBV0geMs0KNm91e^k#2Kw(It_J)7I0HBfI0rZn zxB$2axCFQi_z?h#?sNrWP;4hqY9~-=r=K8y6>tskGxP~!ce)Pg8-SaTzXiAhxC^)s z_!aOF@EGs}@D%Vn;2GdK;3eQsz+ZsZusPlU#&HCaBEWdS1V8{NC6}(mJUWg~gV1OY z8aqBd7)d=bP2QoD?^uZENHMSGzf+U zq0k^48mtlxmWBpPLze*^SRuL`;>l14)`o^HM}u(b3Md0>LxW&x5GD=6r$P8M2%p{r zb(H|vk~9dP-b2O$z`mq6gJ|P0eFKZ>4S>6Vdw^?ztAK-m5HUzw4AK^ZsKp?CF^F3X z;ueFr#UO4meH~lUKa(=3Zw*BdJ%H?di0?yO%CUlpiD6Z&VO7NR6ToB&@=$*o@IC1s z^1nj)L%<`zW57+oEx>KSIiNWYh!fKnA-V+E2gTn2DgjTS`2~o7h4>-hG2kVj2a^L5Vrd#ju>vA>bk42-MV&fgt@G;CDbN z8WrIp77UgI*6o`4zxkz(vdzP=9d*oWB9DIrJqgp(ju_^xxQqp31SIrvWBH zeiGmslwAcJ1pEn@4s{CwivTkKMO+*DA1FT#I11PeI0i65+hki1r49WG+tDweXcCa7 z+t8CCngO-*p-N#xPl4=A$ezUZuoyP57&ZdYOpZV_3osin2QU{ffeUMHLxfE~P;OD~1GH&8Yn$|_*2xe$K=Wy=980M8-+7ff^qwuLPX`~l6EP+knk zw4ql)Gz&=LZ0Ok#t%mFxKoO+pkvz0m3s?tb&qz;z4UpaoWxqrEHN=~s$5Mz70XAZr z<~DR0P~3*na)_@({5x!(OW1)fqomNIBh0~({)#K5w*z(nZgQmbA*d?>tOx9bvR#0$ z0aKx_3gT%H?}m5}U@zbst`of<;)8(iq3!^*`xerN0Y|vf=2H3?qy$oWF;pDIQhEu* zO97{$cp1bS0V@D!AYTmeF`!vR7zBI`r~>Q;tN}~{mfIE;jO6gx9x&Ybv(EKi>?~&$^zKEN_o|S@rLmA*M;CCpS4z$Tqx*Eu) zKyoTz8elV2&4+jspc1f!lmWH^z5>(${Zqmbq;~+$6Hx+o0Zsw714eL@u!0o<)=Gh-4Dcls?E`!R zD2M!hhyk`QNzm6p7n>km0bN%@ycuwVXb9v>0NWwI1F#FQ8*l;We}?)4ycU8s-}2}~ zPQu+mC zoq3=&E$3n8Cp|C+zy`#MW=0}Wb)1(InlT?a0jzH%GXoh%*$E7%&<|f4cuo%CUNeX_73_>JyH9;#u8>qu> zgp0N&DZ=d_*B;OT;0~o64^rBZq&y+T=|nj4B4yrWOdnG{FSI|yLuXS!0OAC~_+0>9 z0o?%dT3))>@*?PA3V^UD6ZT@l-b@$-Arb}ycNEpf6arx=6NW*Esc=&O!hNBG2tXtt zityHtwB$rX9Vdox7)$z~`qy)Zv1kA*<#1*hQOjUF zqJdxFqER4-<=C_WmP$me1ja@&GU3~R{#aSsLQ6Xs)gFd%fH6Rgd5*A5PN2xmU~!x= zqMKtRY5}X-5+iXdj4WG=XaTZ8G7&ArE>51Vi1|@c=2uCXpCx5}mlSa@rz3P}FXP7|BOt)O`#_JwO^gK^nb4 z4!uDRK_G`2t0m0)Fy3!Ah`sBfu#_nltC~Eq&gU+I;0#jLqW8uAlft# zZ90f{7>HH@qRjx&W>x@!aw7z)O%P=9DlzSVMhxBz%v55u1){AOeMMM9+n8U++s>qS zFzKC4xQm<(=xautRZO^>3HLCydl~v~*g5WFD)uu)2Uy7;WaaZMLwty-Im`<62pb)R z=om)FSyoP9@}q~}L3k3QQw;fOrs?-A$JK1~0~?)TqqA&uj*ZT<(FL~Oi)_D_*yu7_ z_#+!#VGDm^qpNImjT!W3CcMssHz2%;(Jf}#Z?l;@3{$_b)4t0__t@w@2wL+1g4|yr z&_0Af_XvW##}It-1Oj~x1cs*&7=MEx|91!q{(xZQGYCdKhoJBU1fyR~9D@e+_{N{{!JT{02feAt;Q-<8cs9z(o|LYCAk3V%T%D>BY<3RzQsQ+Nf1B-B3?wnH8HD=9vvOdR|r#iw9DQG7VkIjbl> z7xwaMiqGS4Icq3BpTpxsfLY^PV;i`h+2Yn*ggvl~i>SUhfr~iV_){+8&c&F=MZ5{j zO^!pZBG#avlx?-o(}|oKUg}y(mV$C@YAC3Yz=ebRJFfP5f^We*(`Hegw<*Oe>IuF> zVSd_d%JUaWF&naXDN2wwhw{8fDds@-KE)BH&80jaP>Q*b{gvX}nlwuKNf2U}x zw8fO?ACzJ-WS>zY>$D}5=Q^xd0@>%3g>BkW%JT)KSPI#fl-Mq98RhwkQY?e)pOmFT z+84|ezJTmsl$BH3a?0~>O0gWWuPN*1X)7qte<;NYGItv|<9Qa)3-Wep1%*A&XBqH6lXB%4tbm)DW76i--Mz(MgfiPb-3?WS_d zsG1~5?V)nYshT0x?Q-@~HAA6^vxLVLtYoHS%)ak<(!(VGg*fz&lQ}S z>@!(MD9@Fgnw&FPM=8%QIW_7tS;r{PVouHQGg-$e&sCh75ofYaP)Wj~i#53tYlNzO z1|yG5u7^*@T-;K~t;y?)T_@og_&e$(g_^^ikav;cS8tx9{t1Q?r=Z4AU7dA`$~sN8 zy(d_cQ$p1gFe?Xae$PgGsMSooo2=s+vW{+|HANR{3W+wHDPQDaD}tL{YBhBxyc&#% zE%-?_V_>y5P&H#4O2UMzv-VQ9Qm8J%OvMju#ROd4o(OgdTY82qosLQWZm813))M+B zN=6F2@v6(NlOaUGmwxh0Fs)8 znCMFXb5OkqSGyrH^Tile_&im!lx5@s%g8d)x)srpHl%eiGr$H8To!6pVNlMCRLyEK zgG*G+8baEd?1STstk;u~N?@dmlqa?aOIK5h`_`1>>eS0r)Me1XgH+8%Or$&PMHh*MYswwcKxIxxT@2y^YKTSlYps-egO6 zvbkGqZWp1sP1RIE$9E`OaC~calbPJ1YW9HWegWBhLuMr7!HniGGdf6SbO`7#Q8r|u z50fr{{s_=t2l}HhzZ3PMJ&CIc&t&mHy;i%S>a1N<_%9SE=M?q-74{|YQ65LW-`B@C zAQERRylW?p_u=edY-N(L$?kgBo5V-*gw1m@$;M9XY=k9jCX=j5HhBR%ew#!H1c>{T zxfu{Qi2Ja)F<^5Tn30ggecy*@?pyQfU;WLD1X{;GTVHo|b#--hb@#uzTe8{a!t>C{ zlf-dKL{3vDx8F-d&l6VS8Io8<4V|TLH57Xsrv54Ps)1g0*@kzqm^Q5+BOQ0U_qzBV zE@j63=)gH)4p_^ax0X2w>0g1z{0F2Tf%Ky+y-n*@B=T6cr4xQj@avOVxudO?^)Y~u}z#q6MwJCt@^TM>)Ed=rBm;k3zqYu<-eT^DA!ZR>_@YXsVO0=v}XXrr9kFz-{%>mt`9L8GYFi)IY(3bZG+ z!`$u;#)+)VeCbT2IW3ai^T7TT>UGb1kW_doS)L`8=VC%vhz@3|NalW-($k>Q2O@e^ z#BK^RUC_jg+!Ai^bC~!jyD5-2bI?yxx)N$W35$Ojt$~vTL#hoPKzaSa_qJUFOp_CFPQQ0Vrf>0IJ_brULws(q_0zY zsWhvQK1bShfwMf@ddbx~;S4guC`Pr0TNy`D!vnahvn)OKE zrnGA|hxI`*I_Fgtn@C(GQaslw zPU9PkQ?93!&v%+6LT+pl<$6i=LZ`_hBw~{(_fPtJohn2~$12cl*mYaxJaF+NNkF4L zp^Rt)JmYIpw#<1J)Q&zl>d4@zBalV|xh0m&myx7iri2K>O@ByxU$Hb18si?){FsD) zJtO=lB>a^`w1m*F^NkGkXGDE15$y?T`waCLL@h`}dx827lBiz?wUDSqiD;<|IS*jT z>0H=bCZc^LNQ|@kO7}(c7CE@%d@~~}MzwyGh?YUtx1`rHXS)mXyFLVamm%1@48h)I z2=*>Ruy<*bCkXZ)b)}!=O})RQO`Rav0BQb25Nx0{f5!Gh{rwBR)ZreqyA6^Z2T3P3 zSYoG-4wlg&vT&%34kImx%g6{xs{qkQ%G|%99`t6Gf^r$&EIXDaU^v( z)#)c$qwz8_fhZ%G5}8O+M!J4MI6*9N+e&$cm?YvPOME{=tRmuMOB|IURui$p5=TR` ze}ZP?ShKNd&BnTB9Cn^5(oDdr%{5~U0`MYQEE)E+;Dx3KGP$kEP=q$f(?9_W{EYxd zB05!u-PZp?y01A6AqCJ*Q(gJT`1r9~QZr1=p0 zNu>nLD4Z*!^JHW`>9WcVo&ncdloHoEpKHD4T41?0*xW*!+vodEPmde+-g-d;c8~nNh zgj2Z8Cx{^6#SXXd1D_zV)CZALL4ltw#G_+UDWy`Wyi}@FD%C5MqP(evS^q_m#lH%x z?n+yAmrGy;R^5G{RrjLGzY40-gFR_~8t3i@T1iBw$lNzcnM1zuKP2P#k>;?UT~66j z#xZb${1ucflZ;|2B{nv5+!|=5H4t!&T~a=W!c(?zl`T9AO`Z|t6TgKb=hJxjynDgj zFWoCHp_78ur2T5?me$a2aK)v~;F_BZAC&oPq)Q7Iu)+<3GWlyI%5J+=1doBM?tr)s z#2~V%OqRez6|G(WCIT&U*02G3(?X7BrUP8fT!x zS!T`OE*;{g0sG&W*{`6uK8-=^g}Fe$5^J}yYVME@Pm9Yon;@;PicZ*=NK}<{ijvY? z6FgU|?OeT%rt^Vfj`F0rl}3-ZEPAAU`!3LK_`@+5QHTf9-6Z;7`v7)_56$Tb zWM*kMYhQK+<1ZfJBju`%X>3G(t9fJoi1*&tJ}q`>MFXp)m>#5x3lW1=%?8ID# zV;uVqj@1L*QcA`y z0N+x|iy1)Mf{U`_McOp?OH=F?T#^`r$R(O!gl`Kj%Z`_+>?^t4Al69B5_O|MHmlk8HTL#2uQE$rf^d~EM zH~2R7DQ;jGLb-&h}I zhEZdE6{7*d$%SPq+D}FLE0>XNZ$-0HF&Y?c3kTT3u^6_WVujhP(AtDFmYLw1iEhC_ z6)scB=pYpxtRe%I2o|^m$EEK&D6=77XS&bDtj>Ohda!9k*s&j)M1JJJHD8V;(ZFr!f03i)#ds2`)NSPUK8qEHl z2!oZG@20_Q5fR9oi`+Dr{SgsJl@EM^0w4|#Q-rlGK?4wnhbw~Fmf!&phes$=aSH;(KToAvh=x?SAr);%b#F-ZZ%CbPNTnK5kEK#nX7<3NeufS9D{!d&X@?@9 zdw|Dqq#^)=QRGv)*KFX@z+l&M9(K)96umBN;6-I8xU3)U0r_a|7)4c#I8PJAjE+~a zV-n|c$>;l!^iE2U1Pt!WJO3O|?GMngcN(ho7N!qv@sm~N0P48x&UmFvWEvU(bWs7oN^_z@ z6;4s%QR)RZf2wlJ%kv&=$@L7yhO^6K(-aQR+QlX-!mae3PIDh^{|5}Tn+-Hf;2tBV z#*DbX<;SVzk=d5ZZE^-Baj161CP(`Xj1jiuerCxTNN0j->e8_!H zMCYrb1El)WIsQ`GDXIA5X4bK5%r&erF)StUbG{z^q*R$gS4 zl8m7JflGjohNcc*8mip_GBQIwoy6fli$F5fbbyoF#a1Ybj0-R!4ZF&rw5tFYQ>AqM z$;w{-9EEtQcZ}q$COOAN2g1N$&3y?oPGFzOeTI@}tZr+Fi2<=)KFW#grP2%32V&AiAn z5zP#5)XWHKrd(#{oU~ew%E}#W<*l{y#z0;nw&ZO*UgM=7%8Zv6+-b_Kpk2C=_=zOG zBBOEz*2c-*YRy#1nC;)O8C&t|toRuOxGU4AQ)g)%oy}e|lSL*YACaZMNcl?6Q(&1k z^jyls&dMNyz=cgDF3MIlOC=xyqmX5?@nZQQdQ}83EJUYPNI&kPIAW#k4PW-Gl9;;5 z^t@dy8_(NyP=P?+dYQoF#@)esWj5M&HrRGPMmwDWm>)+wn=l%iNy2)?cL!S}?hZbc zs89IyO(^s`FeaoQwdqrwZ=wjx!cpBdQt}+4y8cR->*#D3rCR1J=FZ-f z{w8^oIy+jqwt$_tEN#HDO?aF#cV(MbMR87@DOOc{qR~TD zSB2c@7KJ^g1Q~(MY!%GI)K=9jZ6-;>8Ov5Bz}K6|$bPEa{B6p?o>Zo=mCRAqv@6BK zl`39M>y5=H{Z%!&NEG4XZ=m}i%94h~Hw){4i-{JCVD!+^J|76L1DmtXhZ;^eX=&&;XWm89#`5jX2ijFGtd!!1EsSxdr zp|8>r~MRWx5rdRLDk7k~-ayN<>eoqSJ~(NJ{Xb%paM5 z1M|Pv!Th<4TX2Th&Z=mw%0HuA>P>M@SC^tk#GBzyRP>xGJg)$63hGssmS3;jQlR6o z8@r(JOoGb>NuU3VS$?L0KfGDZ4!VxhE@l*NT)1?Ly1&L2bzhB(3Q;SUZcW$tO18$< zOpQ;~Lz*g}))V|Ac1a<`WwveUs*h0B%PLC?<>X&hqFa#GzP7jAZm}y0AuyNkXhy*u z_bYf+AwK5PL^CxK_t&_laErvH$;M{BuClcJ>&opGl#cTa(JgjEnLd7n!D&0wm}Uv3 z;3TjWyQvVDb3t`8BUpX!2;NeN+PQ3JGyFT#{N1fZZ!27NaZyb(>@`{O(5F_#?kL=a zabaCE73!K+*rj536|Ty-aCb8mb~mlCN5vWxjtsbPZ!_lKd%yWp6khl>_BGSQzNSs| z5wT*8lL#){-%N%5_Zqc_##;7Y^3ZA=LIz-7YdrOI&w>$}*9P9#E5thHJAVLqBNghloBx;|eT#N#%ac z<;OPB3o3kC<&V^~*3Zj(@Cyv8h;}AT%YB_Mp%MkeNbuPE1t+3CbjPvUgYeD1 z@UbL(91F+$o>7_w(-OSZ&yi4Cfj{M3;dnH2O%+Yh{L05xPedkammm1}`DjW-;zZif zh*Yd2lZcdbc?BM&u>uEinpjJ!PHx5qldTQ*+R+U52(HKx{QsWY^_Wh3f^EOn;F?OJq(exhiW=C?vf&ephDqcDXG zbccdgEpQS+p%CTo+Wbl6XHvew=1=>wFR6?lR%)C=L@P7CH`n()5A^30Jcc7Ha!u$mf0TSHvi=vETA}uVr}`X>*%+7o~ZB zo#t&|YCFu}lwa+-t+reBi7nQ6OT^S0S?UsvXGKhXFJtYc8n25en%3c)S=MD5uZ)Pu zE%f3f{-dcE5)~mgj`>&0WSY@5NBiNe6l?hbF@C6_C%-)lJdA06iyv9qaFJ-cp>=G%#^FoB1}a#uJ8sZ! zY@_CLmyNpcV;%j3=$mYXawbMLYlmMkT~)ciqUOd5Oo)*4d7KH169rpzxPj+LJe(p# zx`n3Ic)WQczdX(7GBonAR4`2Z@h1Gm+VW3gem3W1=I5%@aDk@LE^;#{%-^c*Nels^ z9{ySw51&+VyZpt&B_!`tk~iD(^wL=5vgA3IT&gj<^S5ayTU2R_`e;o0rbTmYQD2P( zwQ12jTU4g83Nf@kA<%Q4vMg$WR<{SVD3R`Z8LbnyQp4`j?|JzZD9fqgz79|D--q* zVV5O*$^_hx_~7=e zrBdV+#A)hzTY{6$`Yiv243oK>UOJAGpPsO@{#ttJz`AtO(rz;Cl#ZSz+8Il`B|3=n zf7|d#=@-xHa~i27U)(CrY4^N#+#Kg^;pVjZXO7lz6Wt&h$2uvSo5K69DVCfYXmce9 z#k&eGr&T`=_Igv0*rv4Zf?J`T-V$#u+&=V~qD5ca`-YruwWcq9-c-qwH+3-NI4$uK z;_plAOqonn=hqW-!%OadI2AiZFjZC!cOg1s)cSrz*1I)oH=<9+9SqOsykO1 zj4Btjz4QGIRQhjdG(4@*6zvQrRiv4KYcY zDH5AO_q^PPF%K`%=6t?(=xKCDW^I_p}+V=*tv0ctH&a7T);?Lsjct~EC`X(9X_Z@n;hm%1ZR7)$T5OkQn@mP; zX#g?8=M|V{`M0$*nF%{J&O5iusx-MGL$1TI9+^9rqFj??`N`-V9lfigmoy=W;OF^- z5A;GNm>OwO$8;SX(FlAn^sl9|Wjaxn-=JNb8tg%b{;wj#y@>woOB-&VH2ePtF+PYn zz{R{7AC$ClCwaF%1bKgeyzfBXk+i%cd?47Iytps#n9RVYlMvEP>6})dvXxJ#rJR;* z)3fwhpv8HdHELOpGs3@S+VhgBDeW}1@Bsx2<3Z6(u-qq!yl9CB zGX~q?#bAj_w2SN~qxMw6H$W&=i6@xQTLpiJ%U0ZF6f3`CEn#reL!~cUaSe)t50$=b zO<#iwz6KR7#G`9dDKR0HnwLsZ%JfyF;5(S3KY)Jylz+P2kHUSGBus}>^8qS@-Ug}+ zdc!6%SY>e95S77c!yxosk9xf*2U16{)MeTkK~XZX-BS5IJtzF3o}IqJp@k*)cfv8F z6^u^U7j891H4CK2sw|KmN9n1$?HqxKa6D&11&9YHDIk8t4d0M?wS-)%S_t5m$xZ3K zJkWc2W=e(*8RQ`$ylG$PrzV90apE4%QmEOfO}P&#W~D$o^uQ$I8^L^}>U7@H{F6gb z(YD;>5Pe35e2`OI^DUmLl|)#a)&WU`^`RWDHaA;#$d;jAfa}dC!V(?grC%GTj8{R|Ezhg-`Ix6(RXk~6Dp zrmA`Gt8V0dJK5pv@WKIWtONcX9I)1=9n`5yr>V}~jO&El#*VqKDLvzu2eR}g199+w zbV1R?1&{j9u+SQWJvt5n$3MdpPFPQ90Rg8nBhE|^GB53wjTJhb^^Bax3Y_)5c3$JU znUT?~%Q>H?Ij?OjtNyQgSRx=J~F7a%)esH;&H2_Uq z$J+TvfSRu}R95~YsynoKC}04p@(_0aluCqG>#!Rs^@Qo6sn&Zg)p``QdTPH`Pi-hn zjr8^$QwEm*r-`MEx1s^evqn2Zv>Bv1Zdq&NmLa;Jj~8;3Fs@BPD|L<_sB%iU|McvYDODVKe>~W=?wxu2(xAZ=FVu{xQf6N3eJq_+&9$G3< z4s8ylwo(m6mrbnQ+q10!g7!iEK3xAf)GzaVh*##Beztz?z4iP1^+!?t0bU{*8BK|S z5Y(3ib%3D3p8xP*uxEx?L3Q^E8tMxgL-mJwe4w8{)^k|iaL6meAH`}_&NF$W=Ra#6 z>G8Yfk)A2Ha(AWW?$+_DrY(#j^>C#!+Dla7Hf5}b)B859zl*a-&n|uAJ?qyKt+YKF zEVNE%WODw{J*RRN(665>DegY3?Q@V>N~C)K@b&iATw zpG36lVICv_CTJ5_$hoTYHI0QB46GMhKEBYgOpLNP$$Ji8_{1&tcmPr2a%tkEmy6qnDkcAA@0li{VAi z@I7KYV)bTb6x!5e@K!`7&{}kq#alTX6O3vUSqI<7?__&b)hz3{XOB|H`OQw|)@+(( zoyf>4%gD0v1w#5Z_(g7#_0YpO&19Uu;I=jC|ovHE_^axU}W_ko>+aIJ@$~7;29e00U@M!ru`hwq3y3t4%TQ`_b zsO-vodQ>S)Fu(PBU# z`;ecXEDZ=;aF1$tK)?(R00>|gn&@TX(MuG|Z^BJVfL-sHb-fXRtdrvvOdxwh8TVr2 zXt`=cndHUBduCJsVSS=~b)&zMVMYfCGXOB~*?0ylC^If#FPq2l?uliD=SY*4WMTkw ze2^F3t{=j~vpCwc9uPUGg_#tvyE(&|ihx}Krw91fn~~U@z-OP+55_VDqlh;Aj&LET zspu)QIKX$vUnS}iqJnPAT2o`Rke2X8yIVrLc^s%E9xv@G%m>68J`TJk;^7lIPNWUc zA$(GYPx;5^@$hLKhl=s=8BP%ESsf2Kkt14~PXZm>pO(2p+^u4=Cn^X=31^Du2;mHJ zfduV{ZwaK4AYrX7)aaA=tqq2QG~!zVCJ_iek0X_x0R%z}f@%Vpo59*Za4Jf7b19Sd za49qH4Fvn(^reLQo?w@$3s+Fc7^wD-UhruBJOM19;PRvOOTtw91i_EiC%dN3CrCc6 zIZw#SC#cM6jpoxD-9kP(B$d*yq*C_Z-}ApjG6ynFJC&T4ukX#l3~gG5b|^y|;?2%| z507L69BQS{uF^+>#{(RFrO(UK}Jcp->A6R`0WF;4fKG+*eg z3KX6T1fRGUQlAeX;;z@}$-uXNfXx?a#6y$iObNW{kN6B4ar|yIcBOH=BVP$%M>g8I zLeu%8cCXUsk`~tkVb@#@;7MPgaHf~cpXIsTYRq-cguY#oKb!J(=0>18l=Fog$Eils zrNJB=o@vg+OR4frpMMJT*O*&@s_<3XySYx%Uar|XcH1wB;~CuT0G`1OVchT%pZT$p z*R<0>+FjQ!J~y-|ZefaBB%^b@Xr)&?*Q+x$0zUmAA&j05%vEA HB8{V)G-+>{ diff --git a/Services/MediaObjects/media_element_2_11_3/jquery.js b/Services/MediaObjects/media_element_2_11_3/jquery.js deleted file mode 100755 index 86a330515eed..000000000000 --- a/Services/MediaObjects/media_element_2_11_3/jquery.js +++ /dev/null @@ -1,9597 +0,0 @@ -/*! - * jQuery JavaScript Library v1.9.1 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-2-4 - */ -(function( window, undefined ) { - -// Can't do this because several apps including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ -//"use strict"; -var - // The deferred used on DOM ready - readyList, - - // A central reference to the root jQuery(document) - rootjQuery, - - // Support: IE<9 - // For `typeof node.method` instead of `node.method !== undefined` - core_strundefined = typeof undefined, - - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - location = window.location, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // [[Class]] -> type pairs - class2type = {}, - - // List of deleted data cache ids, so we can reuse them - core_deletedIds = [], - - core_version = "1.9.1", - - // Save a reference to some core methods - core_concat = core_deletedIds.concat, - core_push = core_deletedIds.push, - core_slice = core_deletedIds.slice, - core_indexOf = core_deletedIds.indexOf, - core_toString = class2type.toString, - core_hasOwn = class2type.hasOwnProperty, - core_trim = core_version.trim, - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Used for matching numbers - core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, - - // Used for splitting on whitespace - core_rnotwhite = /\S+/g, - - // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, - rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }, - - // The ready event handler - completed = function( event ) { - - // readyState === "complete" is good enough for us to call the dom ready in oldIE - if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { - detach(); - jQuery.ready(); - } - }, - // Clean-up method for dom ready events - detach = function() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } - }; - -jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: core_version, - - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return core_slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; - }, - - slice: function() { - return this.pushStack( core_slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: core_push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var src, copyIsArray, copy, name, options, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger("ready").off("ready"); - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - isWindow: function( obj ) { - return obj != null && obj == obj.window; - }, - - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); - }, - - type: function( obj ) { - if ( obj == null ) { - return String( obj ); - } - return typeof obj === "object" || typeof obj === "function" ? - class2type[ core_toString.call(obj) ] || "object" : - typeof obj; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !core_hasOwn.call(obj, "constructor") && - !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || core_hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw new Error( msg ); - }, - - // data: string of html - // context (optional): If specified, the fragment will be created in this context, defaults to document - // keepScripts (optional): If true, will include scripts passed in the html string - parseHTML: function( data, context, keepScripts ) { - if ( !data || typeof data !== "string" ) { - return null; - } - if ( typeof context === "boolean" ) { - keepScripts = context; - context = false; - } - context = context || document; - - var parsed = rsingleTag.exec( data ), - scripts = !keepScripts && []; - - // Single tag - if ( parsed ) { - return [ context.createElement( parsed[1] ) ]; - } - - parsed = jQuery.buildFragment( [ data ], context, scripts ); - if ( scripts ) { - jQuery( scripts ).remove(); - } - return jQuery.merge( [], parsed.childNodes ); - }, - - parseJSON: function( data ) { - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( data ); - } - - if ( data === null ) { - return data; - } - - if ( typeof data === "string" ) { - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - if ( data ) { - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return ( new Function( "return " + data ) )(); - } - } - } - - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - if ( !data || typeof data !== "string" ) { - return null; - } - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && jQuery.trim( data ) ) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - // args is for internal usage only - each: function( obj, callback, args ) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike( obj ); - - if ( args ) { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } - } - - return obj; - }, - - // Use native String.trim function wherever possible - trim: core_trim && !core_trim.call("\uFEFF\xA0") ? - function( text ) { - return text == null ? - "" : - core_trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArraylike( Object(arr) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - core_push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - var len; - - if ( arr ) { - if ( core_indexOf ) { - return core_indexOf.call( arr, elem, i ); - } - - len = arr.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in arr && arr[ i ] === elem ) { - return i; - } - } - } - - return -1; - }, - - merge: function( first, second ) { - var l = second.length, - i = first.length, - j = 0; - - if ( typeof l === "number" ) { - for ( ; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var retVal, - ret = [], - i = 0, - length = elems.length; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike( elems ), - ret = []; - - // Go through the array, translating each of the items to their - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - } - - // Flatten any nested arrays - return core_concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var args, proxy, tmp; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = core_slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - // Multifunctional method to get and set values of a collection - // The value/s can optionally be executed if it's a function - access: function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < length; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; - }, - - now: function() { - return ( new Date() ).getTime(); - } -}); - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - - // If IE event model is used - } else { - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch(e) {} - - if ( top && top.doScroll ) { - (function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch(e) { - return setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise( obj ); -}; - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -function isArraylike( obj ) { - var length = obj.length, - type = jQuery.type( obj ); - - if ( jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === "array" || type !== "function" && - ( length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj ); -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // First callback to fire (used internally by add and fireWith) - firingStart, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( list && ( !fired || stack ) ) { - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var action = tuple[ 0 ], - fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = core_slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; - if( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); -jQuery.support = (function() { - - var support, all, a, - input, select, fragment, - opt, eventName, isSupported, i, - div = document.createElement("div"); - - // Setup - div.setAttribute( "className", "t" ); - div.innerHTML = "
    a"; - - // Support tests won't run in some limited or non-browser environments - all = div.getElementsByTagName("*"); - a = div.getElementsByTagName("a")[ 0 ]; - if ( !all || !a || !all.length ) { - return {}; - } - - // First batch of tests - select = document.createElement("select"); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName("input")[ 0 ]; - - a.style.cssText = "top:1px;float:left;opacity:.5"; - support = { - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", - - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: div.firstChild.nodeType === 3, - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: a.getAttribute("href") === "/a", - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.5/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) - checkOn: !!input.value, - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Tests for enctype support on a form (#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", - - // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode - boxModel: document.compatMode === "CSS1Compat", - - // Will be defined later - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true, - boxSizingReliable: true, - pixelPosition: false - }; - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Support: IE<9 - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - // Check if we can trust getAttribute("value") - input = document.createElement("input"); - input.setAttribute( "value", "" ); - support.input = input.getAttribute( "value" ) === ""; - - // Check if an input maintains its value after becoming a radio - input.value = "t"; - input.setAttribute( "type", "radio" ); - support.radioValue = input.value === "t"; - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "checked", "t" ); - input.setAttribute( "name", "t" ); - - fragment = document.createDocumentFragment(); - fragment.appendChild( input ); - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<9 - // Opera does not clone events (and typeof div.attachEvent === undefined). - // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() - if ( div.attachEvent ) { - div.attachEvent( "onclick", function() { - support.noCloneEvent = false; - }); - - div.cloneNode( true ).click(); - } - - // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php - for ( i in { submit: true, change: true, focusin: true }) { - div.setAttribute( eventName = "on" + i, "t" ); - - support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; - } - - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, marginDiv, tds, - divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - container = document.createElement("div"); - container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - - body.appendChild( container ).appendChild( div ); - - // Support: IE8 - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - div.innerHTML = "
    t
    "; - tds = div.getElementsByTagName("td"); - tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Support: IE8 - // Check if empty table cells still have offsetWidth/Height - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - - // Check box-sizing and margin behavior - div.innerHTML = ""; - div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; - support.boxSizing = ( div.offsetWidth === 4 ); - support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); - - // Use window.getComputedStyle because jsdom on node.js will break without it. - if ( window.getComputedStyle ) { - support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; - support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. (#3333) - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = div.appendChild( document.createElement("div") ); - marginDiv.style.cssText = div.style.cssText = divReset; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; - - support.reliableMarginRight = - !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); - } - - if ( typeof div.style.zoom !== core_strundefined ) { - // Support: IE<8 - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - div.innerHTML = ""; - div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); - - // Support: IE6 - // Check if elements with layout shrink-wrap their children - div.style.display = "block"; - div.innerHTML = "

    "; - div.firstChild.style.width = "5px"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - - if ( support.inlineBlockNeedsLayout ) { - // Prevent IE 6 from affecting layout for positioned elements #11048 - // Prevent IE from shrinking the body in IE 7 mode #12869 - // Support: IE<8 - body.style.zoom = 1; - } - } - - body.removeChild( container ); - - // Null elements to avoid leaks in IE - container = div = tds = marginDiv = null; - }); - - // Null elements to avoid leaks in IE - all = select = fragment = opt = a = input = null; - - return support; -})(); - -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, - rmultiDash = /([A-Z])/g; - -function internalData( elem, name, data, pvt /* Internal Use Only */ ){ - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; -} - -function internalRemoveData( elem, name, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var i, l, thisCache, - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } else { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } -} - -jQuery.extend({ - cache: {}, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data ) { - return internalData( elem, name, data ); - }, - - removeData: function( elem, name ) { - return internalRemoveData( elem, name ); - }, - - // For internal use only. - _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); - }, - - _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - // Do not set data on non-element because it will not be cleared (#8335). - if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { - return false; - } - - var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - - // nodes accept data unless otherwise specified; rejection can be conditional - return !noData || noData !== true && elem.getAttribute("classid") === noData; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var attrs, name, - elem = this[0], - i = 0, - data = null; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attrs = elem.attributes; - for ( ; i < attrs.length; i++ ) { - name = attrs[i].name; - - if ( !name.indexOf( "data-" ) ) { - name = jQuery.camelCase( name.slice(5) ); - - dataAttr( elem, name, data[ name ] ); - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - return jQuery.access( this, function( value ) { - - if ( value === undefined ) { - // Try to fetch any internally stored data first - return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; - } - - this.each(function() { - jQuery.data( this, key, value ); - }); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray(data) ) { - queue = jQuery._data( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - hooks.cur = fn; - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return jQuery._data( elem, key ) || jQuery._data( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - jQuery._removeData( elem, type + "queue" ); - jQuery._removeData( elem, key ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while( i-- ) { - tmp = jQuery._data( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var nodeHook, boolHook, - rclass = /[\t\r\n]/g, - rreturn = /\r/g, - rfocusable = /^(?:input|select|textarea|button|object)$/i, - rclickable = /^(?:a|area)$/i, - rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, - ruseDefault = /^(?:checked|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - getSetInput = jQuery.support.input; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, - - prop: function( name, value ) { - return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - - addClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call( this, j, this.className ) ); - }); - } - - if ( proceed ) { - // The disjunction here is for better compressibility (see removeClass) - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - " " - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - elem.className = jQuery.trim( cur ); - - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = arguments.length === 0 || typeof value === "string" && value; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call( this, j, this.className ) ); - }); - } - if ( proceed ) { - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - "" - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - elem.className = value ? jQuery.trim( cur ) : ""; - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( i ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.match( core_rnotwhite ) || []; - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space separated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - // Toggle whole class name - } else if ( type === core_strundefined || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // If the element has a class name or if we're passed "false", - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var ret, hooks, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var val, - self = jQuery(this); - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, self.val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - }, - select: { - get: function( elem ) { - var value, option, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one" || index < 0, - values = one ? null : [], - max = one ? index + 1 : options.length, - i = index < 0 ? - max : - one ? index : 0; - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // oldIE doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - // Don't return options that are disabled or in a disabled optgroup - ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && - ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var values = jQuery.makeArray( value ); - - jQuery(elem).find("option").each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attr: function( elem, name, value ) { - var hooks, notxml, ret, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === core_strundefined ) { - return jQuery.prop( elem, name, value ); - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( notxml ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - - } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, value + "" ); - return value; - } - - } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - - // In IE9+, Flash objects don't have .getAttribute (#12945) - // Support: IE9+ - if ( typeof elem.getAttribute !== core_strundefined ) { - ret = elem.getAttribute( name ); - } - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var name, propName, - i = 0, - attrNames = value && value.match( core_rnotwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( (name = attrNames[i++]) ) { - propName = jQuery.propFix[ name ] || name; - - // Boolean attributes get special treatment (#10870) - if ( rboolean.test( name ) ) { - // Set corresponding property to false for boolean attributes - // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 - if ( !getSetAttribute && ruseDefault.test( name ) ) { - elem[ jQuery.camelCase( "default-" + name ) ] = - elem[ propName ] = false; - } else { - elem[ propName ] = false; - } - - // See #9699 for explanation of this approach (setting first, then removal) - } else { - jQuery.attr( elem, name, "" ); - } - - elem.removeAttribute( getSetAttribute ? name : propName ); - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to default in case type is set after value during creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - propFix: { - tabindex: "tabIndex", - readonly: "readOnly", - "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" - }, - - prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - return ( elem[ name ] = value ); - } - - } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } - } - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - var attributeNode = elem.getAttributeNode("tabindex"); - - return attributeNode && attributeNode.specified ? - parseInt( attributeNode.value, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - } - } -}); - -// Hook for boolean attributes -boolHook = { - get: function( elem, name ) { - var - // Use .prop to determine if this attribute is understood as boolean - prop = jQuery.prop( elem, name ), - - // Fetch it accordingly - attr = typeof prop === "boolean" && elem.getAttribute( name ), - detail = typeof prop === "boolean" ? - - getSetInput && getSetAttribute ? - attr != null : - // oldIE fabricates an empty string for missing boolean attributes - // and conflates checked/selected into attroperties - ruseDefault.test( name ) ? - elem[ jQuery.camelCase( "default-" + name ) ] : - !!attr : - - // fetch an attribute node for properties not recognized as boolean - elem.getAttributeNode( name ); - - return detail && detail.value !== false ? - name.toLowerCase() : - undefined; - }, - set: function( elem, value, name ) { - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { - // IE<8 needs the *property* name - elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); - - // Use defaultChecked and defaultSelected for oldIE - } else { - elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; - } - - return name; - } -}; - -// fix oldIE value attroperty -if ( !getSetInput || !getSetAttribute ) { - jQuery.attrHooks.value = { - get: function( elem, name ) { - var ret = elem.getAttributeNode( name ); - return jQuery.nodeName( elem, "input" ) ? - - // Ignore the value *property* by using defaultValue - elem.defaultValue : - - ret && ret.specified ? ret.value : undefined; - }, - set: function( elem, value, name ) { - if ( jQuery.nodeName( elem, "input" ) ) { - // Does not return so that setAttribute is also used - elem.defaultValue = value; - } else { - // Use nodeHook if defined (#1954); otherwise setAttribute is fine - return nodeHook && nodeHook.set( elem, value, name ); - } - } - }; -} - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret = elem.getAttributeNode( name ); - return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? - ret.value : - undefined; - }, - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - elem.setAttributeNode( - (ret = elem.ownerDocument.createAttribute( name )) - ); - } - - ret.value = value += ""; - - // Break association with cloned elements by also using setAttribute (#9646) - return name === "value" || value === elem.getAttribute( name ) ? - value : - undefined; - } - }; - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - nodeHook.set( elem, value === "" ? false : value, name ); - } - }; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }); - }); -} - - -// Some attributes require a special call on IE -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !jQuery.support.hrefNormalized ) { - jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - get: function( elem ) { - var ret = elem.getAttribute( name, 2 ); - return ret == null ? undefined : ret; - } - }); - }); - - // href/src property should get the full normalized URL (#10299/#12915) - jQuery.each([ "href", "src" ], function( i, name ) { - jQuery.propHooks[ name ] = { - get: function( elem ) { - return elem.getAttribute( name, 4 ); - } - }; - }); -} - -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // Note: IE uppercases css property names, but if we were to .toLowerCase() - // .cssText, that would destroy case senstitivity in URL's, like in "background" - return elem.style.cssText || undefined; - }, - set: function( elem, value ) { - return ( elem.style.cssText = value + "" ); - } - }; -} - -// Safari mis-reports the default selected property of an option -// Accessing the parent's selectedIndex property fixes it -if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { - get: function( elem ) { - var parent = elem.parentNode; - - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }); -} - -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - -// Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); - } - } - }); -}); -var rformElems = /^(?:input|select|textarea)$/i, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - var tmp, events, t, handleObjIn, - special, eventHandle, handleObj, - handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - var j, handleObj, tmp, - origCount, t, events, - special, handlers, type, - namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( core_rnotwhite ) || [""]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery._removeData( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - var handle, ontype, cur, - bubbleType, special, tmp, i, - eventPath = [ elem || document ], - type = core_hasOwn.call( event, "type" ) ? event.type : event, - namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - event.isTrigger = true; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && - !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - try { - elem[ type ](); - } catch ( e ) { - // IE<9 dies on focus/blur to hidden element (#1486,#12518) - // only reproducible on winXP IE8 native, not IE9 in IE8 mode - } - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, ret, handleObj, matched, j, - handlerQueue = [], - args = core_slice.call( arguments ), - handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var sel, handleObj, matches, i, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - for ( ; cur != this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: IE<9 - // Fix target property (#1925) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Support: Chrome 23+, Safari? - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Support: IE<9 - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) - event.metaKey = !!event.metaKey; - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var body, eventDoc, doc, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { - this.click(); - return false; - } - } - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== document.activeElement && this.focus ) { - try { - this.focus(); - return false; - } catch ( e ) { - // Support: IE<9 - // If we error on focus to hidden element (#1486, #12518), - // let .trigger() run the handlers - } - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === document.activeElement && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - - beforeunload: { - postDispatch: function( event ) { - - // Even when returnValue equals to undefined Firefox will still show alert - if ( event.result !== undefined ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - var name = "on" + type; - - if ( elem.detachEvent ) { - - // #8545, #7054, preventing memory leaks for custom events in IE6-8 - // detachEvent needed property on element, by name of that event, to properly expose it to GC - if ( typeof elem[ name ] === core_strundefined ) { - elem[ name ] = null; - } - - elem.detachEvent( name, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - if ( !e ) { - return; - } - - // If preventDefault exists, run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // Support: IE - // Otherwise set the returnValue property of the original event to false - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - if ( !e ) { - return; - } - // If stopPropagation exists, run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - - // Support: IE - // Set the cancelBubble property of the original event to true - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !jQuery._data( form, "submitBubbles" ) ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submit_bubble = true; - }); - jQuery._data( form, "submitBubbles", true ); - } - }); - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( event._submit_bubble ) { - delete event._submit_bubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - } - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - } - // Allow triggered, simulated change events (#11500) - jQuery.event.simulate( "change", this, event, true ); - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - jQuery._data( elem, "changeBubbles", true ); - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return !rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var type, origFn; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, - - delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); -/*! - * Sizzle CSS Selector Engine - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license - * http://sizzlejs.com/ - */ -(function( window, undefined ) { - -var i, - cachedruns, - Expr, - getText, - isXML, - compile, - hasDuplicate, - outermostContext, - - // Local document vars - setDocument, - document, - docElem, - documentIsXML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - sortOrder, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - support = {}, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Array methods - arr = [], - pop = arr.pop, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - operators = "([*^$|!~]?=)", - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments quoted, - // then not containing pseudos/brackets, - // then attribute selectors/non-parenthetical expressions, - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rsibling = /[\x20\t\r\n\f]*[+~]/, - - rnative = /^[^{]+\{\s*\[native code/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rescape = /'|\\/g, - rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, - funescape = function( _, escaped ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - return high !== high ? - escaped : - // BMP codepoint - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }; - -// Use a stripped-down slice if we can't use a native one -try { - slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; -} catch ( e ) { - slice = function( i ) { - var elem, - results = []; - while ( (elem = this[i++]) ) { - results.push( elem ); - } - return results; - }; -} - -/** - * For feature detection - * @param {Function} fn The function to test for native support - */ -function isNative( fn ) { - return rnative.test( fn + "" ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var cache, - keys = []; - - return (cache = function( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key += " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key ] = value); - }); -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return fn( div ); - } catch (e) { - return false; - } finally { - // release memory in IE - div = null; - } -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( !documentIsXML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { - push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); - return results; - } - } - - // QSA path - if ( support.qsa && !rbuggyQSA.test(selector) ) { - old = true; - nid = expando; - newContext = context; - newSelector = nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && context.parentNode || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, slice.call( newContext.querySelectorAll( - newSelector - ), 0 ) ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Detect xml - * @param {Element|Object} elem An element or a document - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var doc = node ? node.ownerDocument || node : preferredDoc; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - - // Support tests - documentIsXML = isXML( doc ); - - // Check if getElementsByTagName("*") returns only elements - support.tagNameNoComments = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Check if attributes should be retrieved by attribute nodes - support.attributes = assert(function( div ) { - div.innerHTML = ""; - var type = typeof div.lastChild.getAttribute("multiple"); - // IE8 returns a string for some attributes even when not present - return type !== "boolean" && type !== "string"; - }); - - // Check if getElementsByClassName can be trusted - support.getByClassName = assert(function( div ) { - // Opera can't find a second classname (in 9.6) - div.innerHTML = ""; - if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { - return false; - } - - // Safari 3.2 caches class attributes and doesn't catch changes - div.lastChild.className = "e"; - return div.getElementsByClassName("e").length === 2; - }); - - // Check if getElementById returns elements by name - // Check if getElementsByName privileges form controls or returns elements by ID - support.getByName = assert(function( div ) { - // Inject content - div.id = expando + 0; - div.innerHTML = "
    "; - docElem.insertBefore( div, docElem.firstChild ); - - // Test - var pass = doc.getElementsByName && - // buggy browsers will return fewer than the correct 2 - doc.getElementsByName( expando ).length === 2 + - // buggy browsers will return more than the correct 0 - doc.getElementsByName( expando + 0 ).length; - support.getIdNotName = !doc.getElementById( expando ); - - // Cleanup - docElem.removeChild( div ); - - return pass; - }); - - // IE6/7 return modified attributes - Expr.attrHandle = assert(function( div ) { - div.innerHTML = ""; - return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && - div.firstChild.getAttribute("href") === "#"; - }) ? - {} : - { - "href": function( elem ) { - return elem.getAttribute( "href", 2 ); - }, - "type": function( elem ) { - return elem.getAttribute("type"); - } - }; - - // ID find and filter - if ( support.getIdNotName ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && !documentIsXML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && !documentIsXML ) { - var m = context.getElementById( id ); - - return m ? - m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? - [m] : - undefined : - []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.tagNameNoComments ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Name - Expr.find["NAME"] = support.getByName && function( tag, context ) { - if ( typeof context.getElementsByName !== strundefined ) { - return context.getElementsByName( name ); - } - }; - - // Class - Expr.find["CLASS"] = support.getByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { - return context.getElementsByClassName( className ); - } - }; - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21), - // no need to also add to buggyMatches since matches checks buggyQSA - // A support test would require too much code (would include document ready) - rbuggyQSA = [ ":focus" ]; - - if ( (support.qsa = isNative(doc.querySelectorAll)) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explictly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // IE8 - Some boolean attributes are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - - // Opera 10-12/IE8 - ^= $= *= and empty values - // Should not select anything - div.innerHTML = ""; - if ( div.querySelectorAll("[i^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || - docElem.mozMatchesSelector || - docElem.webkitMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - // Document order sorting - sortOrder = docElem.compareDocumentPosition ? - function( a, b ) { - var compare; - - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { - if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { - if ( a === doc || contains( preferredDoc, a ) ) { - return -1; - } - if ( b === doc || contains( preferredDoc, b ) ) { - return 1; - } - return 0; - } - return compare & 4 ? -1 : 1; - } - - return a.compareDocumentPosition ? -1 : 1; - } : - function( a, b ) { - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Parentless nodes are either documents or disconnected - } else if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - // Always assume the presence of duplicates if sort doesn't - // pass them to our comparison function (as in Google Chrome). - hasDuplicate = false; - [0, 0].sort( sortOrder ); - support.detectDuplicates = hasDuplicate; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - // rbuggyQSA always contains :focus, so no need for an existence check - if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [elem] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - var val; - - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - if ( !documentIsXML ) { - name = name.toLowerCase(); - } - if ( (val = Expr.attrHandle[ name ]) ) { - return val( elem ); - } - if ( documentIsXML || support.attributes ) { - return elem.getAttribute( name ); - } - return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? - name : - val && val.specified ? val.value : null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -// Document sorting and removing duplicates -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - i = 1, - j = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( ; (elem = results[i]); i++ ) { - if ( elem === results[ i - 1 ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - return results; -}; - -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -// Returns a function to use in pseudos for input types -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -// Returns a function to use in pseudos for buttons -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -// Returns a function to use in pseudos for positionals -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[5] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[4] ) { - match[2] = match[4]; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeName ) { - if ( nodeName === "*" ) { - return function() { return true; }; - } - - nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifider - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsXML ? - elem.getAttribute("xml:lang") || elem.getAttribute("lang") : - elem.lang) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( tokens = [] ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push( { - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { - if ( (data = cache[1]) === true || data === cachedruns ) { - return data === true; - } - } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher( elem, context, xml ) || cachedruns; - if ( cache[1] === true ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); - - if ( outermost ) { - outermostContext = context !== document && context; - cachedruns = matcherCachedRuns; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - for ( ; (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !group ) { - group = tokenize( selector ); - } - i = group.length; - while ( i-- ) { - cached = matcherFromTokens( group[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - } - return cached; -}; - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - match = tokenize( selector ); - - if ( !seed ) { - // Try to minimize operations if there is only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && !documentIsXML && - Expr.relative[ tokens[1].type ] ) { - - context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; - if ( !context ) { - return results; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && context.parentNode || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, slice.call( seed, 0 ) ); - return results; - } - - break; - } - } - } - } - } - - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile( selector, match )( - seed, - context, - documentIsXML, - results, - rsibling.test( selector ) - ); - return results; -} - -// Deprecated -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Easy API for creating new setFilters -function setFilters() {} -Expr.filters = setFilters.prototype = Expr.pseudos; -Expr.setFilters = new setFilters(); - -// Initialize with the default document -setDocument(); - -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})( window ); -var runtil = /Until$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - isSimple = /^.[^:#\[\.,]*$/, - rneedsContext = jQuery.expr.match.needsContext, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var i, ret, self, - len = this.length; - - if ( typeof selector !== "string" ) { - self = this; - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - ret = []; - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, this[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; - return ret; - }, - - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false) ); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true) ); - }, - - is: function( selector ) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - rneedsContext.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - ret = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - cur = this[i]; - - while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - } - cur = cur.parentNode; - } - } - - return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( jQuery.unique(all) ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -jQuery.fn.andSelf = jQuery.fn.addBack; - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( this.length > 1 && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem ) { - return ( elem === qualifier ) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; - }); -} -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rtbody = /\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - option: [ 1, "" ], - legend: [ 1, "
    ", "
    " ], - area: [ 1, "", "" ], - param: [ 1, "", "" ], - thead: [ 1, "", "
    " ], - tr: [ 2, "", "
    " ], - col: [ 2, "", "
    " ], - td: [ 3, "", "
    " ], - - // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
    ", "
    " ] - }, - safeFragment = createSafeFragment( document ), - fragmentDiv = safeFragment.appendChild( document.createElement("div") ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -jQuery.fn.extend({ - text: function( value ) { - return jQuery.access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); - }, null, value, arguments.length ); - }, - - wrapAll: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); - - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); - } - - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; - } - - return elem; - }).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); - } - - return this.each(function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - }); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each(function(i) { - jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - }, - - append: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.insertBefore( elem, this.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, false, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, false, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - - // If this is a select, ensure that it displays empty (#12336) - // Support: IE<9 - if ( elem.options && jQuery.nodeName( elem, "select" ) ) { - elem.options.length = 0; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return jQuery.access( this, function( value ) { - var elem = this[0] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for (; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - elem = this[i] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch(e) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function( value ) { - var isFunc = jQuery.isFunction( value ); - - // Make sure that the elements are removed from the DOM before they are inserted - // this can help fix replacing a parent with child elements - if ( !isFunc && typeof value !== "string" ) { - value = jQuery( value ).not( this ).detach(); - } - - return this.domManip( [ value ], true, function( elem ) { - var next = this.nextSibling, - parent = this.parentNode; - - if ( parent ) { - jQuery( this ).remove(); - parent.insertBefore( elem, next ); - } - }); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, table, callback ) { - - // Flatten any nested arrays - args = core_concat.apply( [], args ); - - var first, node, hasScripts, - scripts, doc, fragment, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[0], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[0] = value.call( this, index, table ? self.html() : undefined ); - } - self.domManip( args, table, callback ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - table = table && jQuery.nodeName( first, "tr" ); - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( - table && jQuery.nodeName( this[i], "table" ) ? - findOrAppend( this[i], "tbody" ) : - this[i], - node, - i - ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Hope ajax is available... - jQuery.ajax({ - url: node.src, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } else { - jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); - } - } - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - } - } - - return this; - } -}); - -function findOrAppend( elem, tag ) { - return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - var attr = elem.getAttributeNode("type"); - elem.type = ( attr && attr.specified ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - if ( match ) { - elem.type = match[1]; - } else { - elem.removeAttribute("type"); - } - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var elem, - i = 0; - for ( ; (elem = elems[i]) != null; i++ ) { - jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); - } -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function fixCloneNodeIssues( src, dest ) { - var nodeName, e, data; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { - data = jQuery._data( dest ); - - for ( e in data.events ) { - jQuery.removeEvent( dest, e, data.handle ); - } - - // Event data gets referenced instead of copied if the expando gets copied too - dest.removeAttribute( jQuery.expando ); - } - - // IE blanks contents when cloning scripts, and tries to evaluate newly-set text - if ( nodeName === "script" && dest.text !== src.text ) { - disableScript( dest ).text = src.text; - restoreScript( dest ); - - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - } else if ( nodeName === "object" ) { - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - - dest.defaultChecked = dest.checked = src.checked; - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.defaultSelected = dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone(true); - jQuery( insert[i] )[ original ]( elems ); - - // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() - core_push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - -function getAll( context, tag ) { - var elems, elem, - i = 0, - found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : - undefined; - - if ( !found ) { - for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { - if ( !tag || jQuery.nodeName( elem, tag ) ) { - found.push( elem ); - } else { - jQuery.merge( found, getAll( elem, tag ) ); - } - } - } - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], found ) : - found; -} - -// Used in buildFragment, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( manipulation_rcheckableType.test( elem.type ) ) { - elem.defaultChecked = elem.checked; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var destElements, node, clone, i, srcElements, - inPage = jQuery.contains( elem.ownerDocument, elem ); - - if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - // Fix all IE cloning issues - for ( i = 0; (node = srcElements[i]) != null; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - fixCloneNodeIssues( node, destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0; (node = srcElements[i]) != null; i++ ) { - cloneCopyEvent( node, destElements[i] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - destElements = srcElements = node = null; - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var j, elem, contains, - tmp, tag, tbody, wrap, - l = elems.length, - - // Ensure a safe fragment - safe = createSafeFragment( context ), - - nodes = [], - i = 0; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || safe.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - - tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; - - // Descend through wrappers to the right content - j = wrap[0]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Manually add leading whitespace removed by IE - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); - } - - // Remove IE's autoinserted from table fragments - if ( !jQuery.support.tbody ) { - - // String was a , *may* have spurious - elem = tag === "table" && !rtbody.test( elem ) ? - tmp.firstChild : - - // String was a bare or - wrap[1] === "
    " && !rtbody.test( elem ) ? - tmp : - 0; - - j = elem && elem.childNodes.length; - while ( j-- ) { - if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { - elem.removeChild( tbody ); - } - } - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !jQuery.support.appendChecked ) { - jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); - } - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( safe.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - tmp = null; - - return safe; - }, - - cleanData: function( elems, /* internal */ acceptData ) { - var elem, type, id, data, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - deleteExpando = jQuery.support.deleteExpando, - special = jQuery.event.special; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( acceptData || jQuery.acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( deleteExpando ) { - delete elem[ internalKey ]; - - } else if ( typeof elem.removeAttribute !== core_strundefined ) { - elem.removeAttribute( internalKey ); - - } else { - elem[ internalKey ] = null; - } - - core_deletedIds.push( id ); - } - } - } - } - } -}); -var iframe, getStyles, curCSS, - ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity\s*=\s*([^)]*)/, - rposition = /^(top|right|bottom|left)$/, - // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" - // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rmargin = /^margin/, - rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), - rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), - rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), - elemdisplay = { BODY: "block" }, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: 0, - fontWeight: 400 - }, - - cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; - -// return a css property mapped to a potentially vendor prefixed property -function vendorPropName( style, name ) { - - // shortcut for names that are not vendor prefixed - if ( name in style ) { - return name; - } - - // check for vendor prefixed names - var capName = name.charAt(0).toUpperCase() + name.slice(1), - origName = name, - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in style ) { - return name; - } - } - - return origName; -} - -function isHidden( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); -} - -function showHide( elements, show ) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; - - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - values[ index ] = jQuery._data( elem, "olddisplay" ); - display = elem.style.display; - if ( show ) { - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !values[ index ] && display === "none" ) { - elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); - } - } else { - - if ( !values[ index ] ) { - hidden = isHidden( elem ); - - if ( display && display !== "none" || !hidden ) { - jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); - } - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } - - return elements; -} - -jQuery.fn.extend({ - css: function( name, value ) { - return jQuery.access( this, function( elem, name, value ) { - var len, styles, - map = {}, - i = 0; - - if ( jQuery.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - }, - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - var bool = typeof state === "boolean"; - - return this.each(function() { - if ( bool ? state : isHidden( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - }); - } -}); - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Exclude the following css properties to add px - cssNumber: { - "columnCount": true, - "fillOpacity": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); - // Fixes bug #9237 - type = "number"; - } - - // Make sure that NaN and null values aren't set. See: #7116 - if ( value == null || type === "number" && isNaN( value ) ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; - } - - // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, - // but it would mean to define eight (for every problematic property) identical functions - if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { - - // Wrapped to prevent IE from throwing errors when 'invalid' values are provided - // Fixes bug #5509 - try { - style[ name ] = value; - } catch(e) {} - } - - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var num, val, hooks, - origName = jQuery.camelCase( name ); - - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - //convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; - } - return val; - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; - } -}); - -// NOTE: we've included the "window" in window.getComputedStyle -// because jsdom on node.js will break without it. -if ( window.getComputedStyle ) { - getStyles = function( elem ) { - return window.getComputedStyle( elem, null ); - }; - - curCSS = function( elem, name, _computed ) { - var width, minWidth, maxWidth, - computed = _computed || getStyles( elem ), - - // getPropertyValue is only needed for .css('filter') in IE9, see #12537 - ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, - style = elem.style; - - if ( computed ) { - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right - // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels - // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values - if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret; - }; -} else if ( document.documentElement.currentStyle ) { - getStyles = function( elem ) { - return elem.currentStyle; - }; - - curCSS = function( elem, name, _computed ) { - var left, rs, rsLeft, - computed = _computed || getStyles( elem ), - ret = computed ? computed[ name ] : undefined, - style = elem.style; - - // Avoid setting ret to empty string here - // so we don't default to auto - if ( ret == null && style && style[ name ] ) { - ret = style[ name ]; - } - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - // but not position css attributes, as those are proportional to the parent element instead - // and we can't measure the parent instead because it might trigger a "stacking dolls" problem - if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { - - // Remember the original values - left = style.left; - rs = elem.runtimeStyle; - rsLeft = rs && rs.left; - - // Put in the new values to get a computed value out - if ( rsLeft ) { - rs.left = elem.currentStyle.left; - } - style.left = name === "fontSize" ? "1em" : ret; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if ( rsLeft ) { - rs.left = rsLeft; - } - } - - return ret === "" ? "auto" : ret; - }; -} - -function setPositiveNumber( elem, value, subtract ) { - var matches = rnumsplit.exec( value ); - return matches ? - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i = extra === ( isBorderBox ? "border" : "content" ) ? - // If we already have the right measurement, avoid augmentation - 4 : - // Otherwise initialize for horizontal or vertical properties - name === "width" ? 1 : 0, - - val = 0; - - for ( ; i < 4; i += 2 ) { - // both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // at this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - // at this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // at this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with offset property, which is equivalent to the border-box value - var valueIsBorderBox = true, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - styles = getStyles( elem ), - isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if ( val <= 0 || val == null ) { - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, styles ); - if ( val < 0 || val == null ) { - val = elem.style[ name ]; - } - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test(val) ) { - return val; - } - - // we need the check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - } - - // use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -// Try to determine the default display value of an element -function css_defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - // Use the already-created iframe if possible - iframe = ( iframe || - jQuery("'; - - /* - container.innerHTML = - '' + - '' + - '' + - '' + - '' + - '' + - ''; - */ - - break; - } - // hide original element - htmlMediaElement.style.display = 'none'; - - // FYI: options.success will be fired by the MediaPluginBridge - - return pluginMediaElement; - }, - - updateNative: function(playback, options, autoplay, preload) { - - var htmlMediaElement = playback.htmlMediaElement, - m; - - - // add methods to video object to bring it into parity with Flash Object - for (m in mejs.HtmlMediaElement) { - htmlMediaElement[m] = mejs.HtmlMediaElement[m]; - } - - /* - Chrome now supports preload="none" - if (mejs.MediaFeatures.isChrome) { - - // special case to enforce preload attribute (Chrome doesn't respect this) - if (preload === 'none' && !autoplay) { - - // forces the browser to stop loading (note: fails in IE9) - htmlMediaElement.src = ''; - htmlMediaElement.load(); - htmlMediaElement.canceledPreload = true; - - htmlMediaElement.addEventListener('play',function() { - if (htmlMediaElement.canceledPreload) { - htmlMediaElement.src = playback.url; - htmlMediaElement.load(); - htmlMediaElement.play(); - htmlMediaElement.canceledPreload = false; - } - }, false); - // for some reason Chrome forgets how to autoplay sometimes. - } else if (autoplay) { - htmlMediaElement.load(); - htmlMediaElement.play(); - } - } - */ - - // fire success code - options.success(htmlMediaElement, htmlMediaElement); - - return htmlMediaElement; - } -}; - -/* - - test on IE (object vs. embed) - - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) - - fullscreen? -*/ - -// YouTube Flash and Iframe API -mejs.YouTubeApi = { - isIframeStarted: false, - isIframeLoaded: false, - loadIframeApi: function() { - if (!this.isIframeStarted) { - var tag = document.createElement('script'); - tag.src = "//www.youtube.com/player_api"; - var firstScriptTag = document.getElementsByTagName('script')[0]; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - this.isIframeStarted = true; - } - }, - iframeQueue: [], - enqueueIframe: function(yt) { - - if (this.isLoaded) { - this.createIframe(yt); - } else { - this.loadIframeApi(); - this.iframeQueue.push(yt); - } - }, - createIframe: function(settings) { - - var - pluginMediaElement = settings.pluginMediaElement, - player = new YT.Player(settings.containerId, { - height: settings.height, - width: settings.width, - videoId: settings.videoId, - playerVars: {controls:0}, - events: { - 'onReady': function() { - - // hook up iframe object to MEjs - settings.pluginMediaElement.pluginApi = player; - - // init mejs - mejs.MediaPluginBridge.initPlugin(settings.pluginId); - - // create timer - setInterval(function() { - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); - }, 250); - }, - 'onStateChange': function(e) { - - mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); - - } - } - }); - }, - - createEvent: function (player, pluginMediaElement, eventName) { - var obj = { - type: eventName, - target: pluginMediaElement - }; - - if (player && player.getDuration) { - - // time - pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); - pluginMediaElement.duration = obj.duration = player.getDuration(); - - // state - obj.paused = pluginMediaElement.paused; - obj.ended = pluginMediaElement.ended; - - // sound - obj.muted = player.isMuted(); - obj.volume = player.getVolume() / 100; - - // progress - obj.bytesTotal = player.getVideoBytesTotal(); - obj.bufferedBytes = player.getVideoBytesLoaded(); - - // fake the W3C buffered TimeRange - var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; - - obj.target.buffered = obj.buffered = { - start: function(index) { - return 0; - }, - end: function (index) { - return bufferedTime; - }, - length: 1 - }; - - } - - // send event up the chain - pluginMediaElement.dispatchEvent(obj.type, obj); - }, - - iFrameReady: function() { - - this.isLoaded = true; - this.isIframeLoaded = true; - - while (this.iframeQueue.length > 0) { - var settings = this.iframeQueue.pop(); - this.createIframe(settings); - } - }, - - // FLASH! - flashPlayers: {}, - createFlash: function(settings) { - - this.flashPlayers[settings.pluginId] = settings; - - /* - settings.container.innerHTML = - '' + - '' + - '' + - ''; - */ - - var specialIEContainer, - youtubeUrl = '//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; - - if (mejs.MediaFeatures.isIE) { - - specialIEContainer = document.createElement('div'); - settings.container.appendChild(specialIEContainer); - specialIEContainer.outerHTML = '' + - '' + - '' + - '' + - '' + -''; - } else { - settings.container.innerHTML = - '' + - '' + - '' + - ''; - } - - }, - - flashReady: function(id) { - var - settings = this.flashPlayers[id], - player = document.getElementById(id), - pluginMediaElement = settings.pluginMediaElement; - - // hook up and return to MediaELementPlayer.success - pluginMediaElement.pluginApi = - pluginMediaElement.pluginElement = player; - mejs.MediaPluginBridge.initPlugin(id); - - // load the youtube video - player.cueVideoById(settings.videoId); - - var callbackName = settings.containerId + '_callback'; - - window[callbackName] = function(e) { - mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); - } - - player.addEventListener('onStateChange', callbackName); - - setInterval(function() { - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); - }, 250); - }, - - handleStateChange: function(youTubeState, player, pluginMediaElement) { - switch (youTubeState) { - case -1: // not started - pluginMediaElement.paused = true; - pluginMediaElement.ended = true; - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); - //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); - break; - case 0: - pluginMediaElement.paused = false; - pluginMediaElement.ended = true; - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); - break; - case 1: - pluginMediaElement.paused = false; - pluginMediaElement.ended = false; - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); - break; - case 2: - pluginMediaElement.paused = true; - pluginMediaElement.ended = false; - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); - break; - case 3: // buffering - mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); - break; - case 5: - // cued? - break; - - } - - } -} -// IFRAME -function onYouTubePlayerAPIReady() { - mejs.YouTubeApi.iFrameReady(); -} -// FLASH -function onYouTubePlayerReady(id) { - mejs.YouTubeApi.flashReady(id); -} - -window.mejs = mejs; -window.MediaElement = mejs.MediaElement; - -/*! - * Adds Internationalization and localization to objects. - * - * What is the concept beyond i18n? - * http://en.wikipedia.org/wiki/Internationalization_and_localization - * - * - * This file both i18n methods and locale which is used to translate - * strings into other languages. - * - * Default translations are not available, you have to add them - * through locale objects which are named exactly as the langcode - * they stand for. The default language is always english (en). - * - * - * Wrapper built to be able to attach the i18n object to - * other objects without changing more than one line. - * - * - * LICENSE: - * - * The i18n file uses methods from the Drupal project (drupal.js): - * - i18n.methods.t() (modified) - * - i18n.methods.checkPlain() (full copy) - * - i18n.methods.formatString() (full copy) - * - * The Drupal project is (like mediaelementjs) licensed under GPLv2. - * - http://drupal.org/licensing/faq/#q1 - * - https://github.com/johndyer/mediaelement - * - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * - * - * @author - * Tim Latz (latz.tim@gmail.com) - * - * @see - * me-i18n-locale.js - * - * @params - * - context - document, iframe .. - * - exports - CommonJS, window .. - * - */ -;(function(context, exports, undefined) { - "use strict"; - var i18n = { - "locale": { - "strings" : {} - }, - "methods" : {} - }; -// start i18n - - - /** - * Get the current browser's language - * - * @see: i18n.methods.t() - */ - i18n.locale.getLanguage = function () { - return { - "language" : navigator.language - }; - }; - - /** - * Store the language the locale object was initialized with - */ - i18n.locale.INIT_LANGUAGE = i18n.locale.getLanguage(); - - - /** - * Encode special characters in a plain-text string for display as HTML. - */ - i18n.methods.checkPlain = function (str) { - var character, regex, - replace = { - '&': '&', - '"': '"', - '<': '<', - '>': '>' - }; - str = String(str); - for (character in replace) { - if (replace.hasOwnProperty(character)) { - regex = new RegExp(character, 'g'); - str = str.replace(regex, replace[character]); - } - } - return str; - }; - - /** - * Replace placeholders with sanitized values in a string. - * - * @param str - * A string with placeholders. - * @param args - * An object of replacements pairs to make. Incidences of any key in this - * array are replaced with the corresponding value. Based on the first - * character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (i18n.methods.checkPlain) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (checkPlain + ) - * - * @see i18n.methods.t() - */ - i18n.methods.formatString = function(str, args) { - // Transform arguments before inserting them. - for (var key in args) { - switch (key.charAt(0)) { - // Escaped only. - case '@': - args[key] = i18n.methods.checkPlain(args[key]); - break; - // Pass-through. - case '!': - break; - // Escaped and placeholder. - case '%': - default: - args[key] = '' + i18n.methods.checkPlain(args[key]) + ''; - break; - } - str = str.replace(key, args[key]); - } - return str; - }; - - /** - * Translate strings to the page language or a given language. - * - * See the documentation of the server-side t() function for further details. - * - * @param str - * A string containing the English string to translate. - * @param args - * An object of replacements pairs to make after translation. Incidences - * of any key in this array are replaced with the corresponding value. - * See i18n.methods.formatString(). - * - * @param options - * - 'context' (defaults to the default context): The context the source string - * belongs to. - * - * @return - * The translated string. - */ - i18n.methods.t = function (str, args, options) { - - // Fetch the localized version of the string. - if (i18n.locale.strings && i18n.locale.strings[options.context] && i18n.locale.strings[options.context][str]) { - str = i18n.locale.strings[options.context][str]; - } - - if (args) { - str = i18n.methods.formatString(str, args); - } - return str; - }; - - - /** - * Wrapper for i18n.methods.t() - * - * @see i18n.methods.t() - * @throws InvalidArgumentException - */ - i18n.t = function(str, args, options) { - - if (typeof str === 'string' && str.length > 0) { - - // check every time due languge can change for - // different reasons (translation, lang switcher ..) - var lang = i18n.locale.getLanguage(); - - options = options || { - "context" : lang.language - }; - - return i18n.methods.t(str, args, options); - } - else { - throw { - "name" : 'InvalidArgumentException', - "message" : 'First argument is either not a string or empty.' - } - } - }; - -// end i18n - exports.i18n = i18n; -}(document, mejs)); - -/*! - * This is a i18n.locale language object. - * - * German translation by Tim Latz, latz.tim@gmail.com - * - * @author - * Tim Latz (latz.tim@gmail.com) - * - * @see - * me-i18n.js - * - * @params - * - exports - CommonJS, window .. - */ -;(function(exports, undefined) { - - "use strict"; - - exports.de = { - "Fullscreen" : "Vollbild", - "Go Fullscreen" : "Vollbild an", - "Turn off Fullscreen" : "Vollbild aus", - "Close" : "Schließen" - }; - -}(mejs.i18n.locale.strings)); -/*! - * This is a i18n.locale language object. - * - * Traditional chinese translation by Tim Latz, latz.tim@gmail.com - * - * @author - * Tim Latz (latz.tim@gmail.com) - * - * @see - * me-i18n.js - * - * @params - * - exports - CommonJS, window .. - */ -;(function(exports, undefined) { - - "use strict"; - - exports.zh = { - "Fullscreen" : "全螢幕", - "Go Fullscreen" : "全屏模式", - "Turn off Fullscreen" : "退出全屏模式", - "Close" : "關閉" - }; - -}(mejs.i18n.locale.strings)); - - -/*! - * MediaElementPlayer - * http://mediaelementjs.com/ - * - * Creates a controller bar for HTML5