XCloner – Backup and Restore - Version 4.0.2

Version Description

  • added WebDAV remote storage support
  • added Google Drive Suppor through XCloner-Google-Drive plugin
  • added depedency injection code refactoring
  • added TAR PAX support on restore
  • improving code quality scrutinizer
  • fixing phpversion requirement
  • adding BackBlaze remote storage support
  • added Remote Storage Manage Backups dropdown selection
  • fixed windows opendir error
  • added total archived files to notifications email
  • timezone scheduler fix
  • added default error sending to admin when no notification email is set
Download this release

Release Info

Developer xcloner
Plugin Icon 128x128 XCloner – Backup and Restore
Version 4.0.2
Comparing to
See all releases

Code changes from version 4.0.1 to 4.0.2

Files changed (172) hide show
  1. README.md +4 -0
  2. README.txt +19 -4
  3. admin/class-xcloner-admin.php +41 -17
  4. admin/css/xcloner-admin.css +18 -1
  5. admin/js/dataTables.responsive.js +2 -0
  6. admin/js/materialize.clockpicker.js +2 -1
  7. admin/js/xcloner-admin.js +31 -36
  8. admin/js/xcloner-backup-class.js +19 -0
  9. admin/js/xcloner-manage-backups-class.js +76 -18
  10. admin/js/xcloner-remote-storage-class.js +6 -1
  11. admin/js/xcloner-restore-class.js +22 -16
  12. admin/js/xcloner-scheduler-class.js +32 -37
  13. admin/partials/xcloner_console_page.php +3 -21
  14. admin/partials/xcloner_generate_backups_page.php +8 -8
  15. admin/partials/xcloner_init_page.php +8 -8
  16. admin/partials/xcloner_manage_backups_page.php +96 -17
  17. admin/partials/xcloner_remote_storage_page.php +252 -12
  18. admin/partials/xcloner_restore_page.php +4 -4
  19. admin/partials/xcloner_scheduled_backups_page.php +7 -7
  20. composer.json +2 -1
  21. composer.lock +455 -1
  22. includes/class-xcloner-activator.php +8 -7
  23. includes/class-xcloner-api.php +127 -102
  24. includes/class-xcloner-archive.php +38 -16
  25. includes/class-xcloner-database.php +11 -10
  26. includes/class-xcloner-deactivator.php +5 -2
  27. includes/class-xcloner-file-system.php +101 -43
  28. includes/class-xcloner-file-transfer.php +4 -5
  29. includes/class-xcloner-loader.php +13 -9
  30. includes/class-xcloner-logger.php +30 -13
  31. includes/class-xcloner-remote-storage.php +327 -24
  32. includes/class-xcloner-requirements.php +12 -3
  33. includes/class-xcloner-sanitization.php +2 -0
  34. includes/class-xcloner-scheduler.php +35 -22
  35. includes/class-xcloner-settings.php +11 -3
  36. includes/class-xcloner.php +133 -43
  37. languages/xcloner-backup-and-restore-ro_RO.mo +0 -0
  38. languages/xcloner-backup-and-restore-ro_RO.po +433 -251
  39. public/class-xcloner-public.php +3 -3
  40. restore/{vendor.phar → vendor.build.txt} +0 -0
  41. restore/xcloner_restore.php.txt +21 -9
  42. vendor/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php +298 -0
  43. vendor/aws/aws-sdk-php/src/Multipart/AbstractUploader.php +129 -0
  44. vendor/aws/aws-sdk-php/src/Multipart/UploadState.php +145 -0
  45. vendor/composer/autoload_files.php +7 -0
  46. vendor/composer/autoload_psr4.php +10 -0
  47. vendor/composer/autoload_static.php +60 -0
  48. vendor/composer/installed.json +468 -0
  49. vendor/league/flysystem-webdav/LICENCE +19 -0
  50. vendor/league/flysystem-webdav/changelog.md +37 -0
  51. vendor/league/flysystem-webdav/composer.json +33 -0
  52. vendor/league/flysystem-webdav/src/WebDAVAdapter.php +392 -0
  53. vendor/sabre/dav/.gitignore +43 -0
  54. vendor/sabre/dav/.travis.yml +36 -0
  55. vendor/sabre/dav/CHANGELOG.md +2378 -0
  56. vendor/sabre/dav/CONTRIBUTING.md +87 -0
  57. vendor/sabre/dav/LICENSE +27 -0
  58. vendor/sabre/dav/README.md +38 -0
  59. vendor/sabre/dav/bin/build.php +177 -0
  60. vendor/sabre/dav/bin/googlecode_upload.py +248 -0
  61. vendor/sabre/dav/bin/migrateto20.php +453 -0
  62. vendor/sabre/dav/bin/migrateto21.php +176 -0
  63. vendor/sabre/dav/bin/migrateto30.php +171 -0
  64. vendor/sabre/dav/bin/migrateto32.php +268 -0
  65. vendor/sabre/dav/bin/naturalselection +140 -0
  66. vendor/sabre/dav/bin/sabredav +2 -0
  67. vendor/sabre/dav/bin/sabredav.php +53 -0
  68. vendor/sabre/dav/composer.json +68 -0
  69. vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php +226 -0
  70. vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php +270 -0
  71. vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php +61 -0
  72. vendor/sabre/dav/lib/CalDAV/Backend/PDO.php +1511 -0
  73. vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php +65 -0
  74. vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php +60 -0
  75. vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php +296 -0
  76. vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php +89 -0
  77. vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php +81 -0
  78. vendor/sabre/dav/lib/CalDAV/Calendar.php +472 -0
  79. vendor/sabre/dav/lib/CalDAV/CalendarHome.php +378 -0
  80. vendor/sabre/dav/lib/CalDAV/CalendarObject.php +237 -0
  81. vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php +375 -0
  82. vendor/sabre/dav/lib/CalDAV/CalendarRoot.php +80 -0
  83. vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php +35 -0
  84. vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php +378 -0
  85. vendor/sabre/dav/lib/CalDAV/ICalendar.php +18 -0
  86. vendor/sabre/dav/lib/CalDAV/ICalendarObject.php +21 -0
  87. vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php +39 -0
  88. vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php +26 -0
  89. vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php +101 -0
  90. vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php +23 -0
  91. vendor/sabre/dav/lib/CalDAV/Notifications/INode.php +40 -0
  92. vendor/sabre/dav/lib/CalDAV/Notifications/Node.php +121 -0
  93. vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php +180 -0
  94. vendor/sabre/dav/lib/CalDAV/Plugin.php +1068 -0
  95. vendor/sabre/dav/lib/CalDAV/Principal/Collection.php +33 -0
  96. vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php +19 -0
  97. vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php +19 -0
  98. vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php +181 -0
  99. vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php +181 -0
  100. vendor/sabre/dav/lib/CalDAV/Principal/User.php +135 -0
  101. vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php +15 -0
  102. vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php +190 -0
  103. vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php +15 -0
  104. vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php +13 -0
  105. vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php +203 -0
  106. vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php +123 -0
  107. vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php +1066 -0
  108. vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php +155 -0
  109. vendor/sabre/dav/lib/CalDAV/SharedCalendar.php +229 -0
  110. vendor/sabre/dav/lib/CalDAV/SharingPlugin.php +401 -0
  111. vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php +40 -0
  112. vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php +120 -0
  113. vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php +221 -0
  114. vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php +84 -0
  115. vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php +97 -0
  116. vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php +82 -0
  117. vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php +98 -0
  118. vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php +302 -0
  119. vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php +213 -0
  120. vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php +45 -0
  121. vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php +182 -0
  122. vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php +87 -0
  123. vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php +80 -0
  124. vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php +128 -0
  125. vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php +130 -0
  126. vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php +129 -0
  127. vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php +60 -0
  128. vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php +57 -0
  129. vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php +124 -0
  130. vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php +139 -0
  131. vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php +91 -0
  132. vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php +150 -0
  133. vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php +79 -0
  134. vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php +111 -0
  135. vendor/sabre/dav/lib/CardDAV/AddressBook.php +357 -0
  136. vendor/sabre/dav/lib/CardDAV/AddressBookHome.php +191 -0
  137. vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php +80 -0
  138. vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php +38 -0
  139. vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php +190 -0
  140. vendor/sabre/dav/lib/CardDAV/Backend/PDO.php +550 -0
  141. vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php +81 -0
  142. vendor/sabre/dav/lib/CardDAV/Card.php +216 -0
  143. vendor/sabre/dav/lib/CardDAV/IAddressBook.php +18 -0
  144. vendor/sabre/dav/lib/CardDAV/ICard.php +19 -0
  145. vendor/sabre/dav/lib/CardDAV/IDirectory.php +20 -0
  146. vendor/sabre/dav/lib/CardDAV/Plugin.php +940 -0
  147. vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php +172 -0
  148. vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php +63 -0
  149. vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php +89 -0
  150. vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php +98 -0
  151. vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php +83 -0
  152. vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php +47 -0
  153. vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php +113 -0
  154. vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php +199 -0
  155. vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php +144 -0
  156. vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php +138 -0
  157. vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php +168 -0
  158. vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php +96 -0
  159. vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php +70 -0
  160. vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php +58 -0
  161. vendor/sabre/dav/lib/DAV/Auth/Backend/File.php +77 -0
  162. vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php +57 -0
  163. vendor/sabre/dav/lib/DAV/Auth/Plugin.php +285 -0
  164. vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php +101 -0
  165. vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php +34 -0
  166. vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php +117 -0
  167. vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php +60 -0
  168. vendor/sabre/dav/lib/DAV/Browser/Plugin.php +802 -0
  169. vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php +132 -0
  170. vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico +0 -0
  171. vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE +21 -0
  172. vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css +164 -0
README.md CHANGED
@@ -1,5 +1,9 @@
1
  # XCloner Wordpress Plugin - Backup and Restore
2
 
 
 
 
 
3
  Backup your Wordpress site, restore to any web location, send your backups to Dropbox, Amazon S3, Azure, FTP, SFTP and many others with XCloner backup plugin.
4
 
5
  XCloner is a Backup and Restore plugin that is perfectly integrated with Wordpress.
1
  # XCloner Wordpress Plugin - Backup and Restore
2
 
3
+ [![Author](http://img.shields.io/badge/author-@thinkovi-blue.svg?style=flat-square)](https://twitter.com/thinkovi)
4
+ [![Software License](https://img.shields.io/badge/license-GPL-brightgreen.svg?style=flat-square)](LICENSE.txt)
5
+ [![Build Status](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/badges/build.png?b=master)](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/build-status/master)
6
+
7
  Backup your Wordpress site, restore to any web location, send your backups to Dropbox, Amazon S3, Azure, FTP, SFTP and many others with XCloner backup plugin.
8
 
9
  XCloner is a Backup and Restore plugin that is perfectly integrated with Wordpress.
README.txt CHANGED
@@ -1,12 +1,12 @@
1
  === XCloner - Backup and Restore===
2
  Contributors: xcloner
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AAPE8PLAE554S
4
- Tags: backup plugin, restore plugin, database backup, full site backup, xcloner, website cloner, wordpress backup, database restore, blog transfer
5
  Requires at least: 3.0.1
6
  Tested up to: 4.7
7
- Stable tag: 4.0.1
8
 
9
- Backup your site, restore to any web location, send your backups to Dropbox, Amazon S3, Azure, FTP, SFTP and many others with XCloner backup plugin.
10
 
11
  == Description ==
12
 
@@ -32,7 +32,7 @@ PHP 5.4+ with mod CURL installed
32
  * Received email notifications of created backups
33
  * Generate automatic backups based on cronjobs, it can run daily, weekly, monthly or even hourly
34
  * Restore your backups on any other location, XCloner will attempt to extract the backup archive files for you, as well as import the mysql dump and update the Wordpress config details
35
- * Upload your backups to Remote Storage locations supporting FTP, SFTP, Dropbox, AWS, Azure Blog, BackBlaze and many more to come
36
  * Watch every step of XCloner through it's built in debugger
37
  * Althrough we have optimized XCloner to run properly on most hosts, we give Developers options to customize it's running speed and avoid backup timeouts, all from the XCloner Config-> System Options
38
  * Ability to split backups into multiple smaller parts if a certain size limit is reached
@@ -88,6 +88,21 @@ Yes, if XCloner Logger option is enabled, it will store a log file inside the xc
88
 
89
  == Changelog ==
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  = 4.0.1 =
92
  * Code rewritten from ground up to make use of latest code standards
93
  * Added support for Dropbox, Amazon S3, Azure Blob and SFTP storage
1
  === XCloner - Backup and Restore===
2
  Contributors: xcloner
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AAPE8PLAE554S
4
+ Tags: backup plugin, restore plugin, database backup, duplicate, full site backup, website cloner, wordpress backup, database restore, webdav, azure, ftp, sftp, amazon s3, dropbox, google drive
5
  Requires at least: 3.0.1
6
  Tested up to: 4.7
7
+ Stable tag: 4.0.2
8
 
9
+ Backup your site, restore to any web location, send your backups to Dropbox, Amazon S3, Azure, FTP, SFTP, WebDAV, Google Drive with XCloner plugin.
10
 
11
  == Description ==
12
 
32
  * Received email notifications of created backups
33
  * Generate automatic backups based on cronjobs, it can run daily, weekly, monthly or even hourly
34
  * Restore your backups on any other location, XCloner will attempt to extract the backup archive files for you, as well as import the mysql dump and update the Wordpress config details
35
+ * Upload your backups to Remote Storage locations supporting FTP, SFTP, Dropbox, AWS, Azure Blog, BackBlaze, WebDAV, Google Drive and many more to come
36
  * Watch every step of XCloner through it's built in debugger
37
  * Althrough we have optimized XCloner to run properly on most hosts, we give Developers options to customize it's running speed and avoid backup timeouts, all from the XCloner Config-> System Options
38
  * Ability to split backups into multiple smaller parts if a certain size limit is reached
88
 
89
  == Changelog ==
90
 
91
+ = 4.0.2 =
92
+ * added WebDAV remote storage support
93
+ * added Google Drive Suppor through XCloner-Google-Drive plugin
94
+ * added depedency injection code refactoring
95
+ * added TAR PAX support on restore
96
+ * improving code quality scrutinizer
97
+ * fixing phpversion requirement
98
+ * adding BackBlaze remote storage support
99
+ * added Remote Storage Manage Backups dropdown selection
100
+ * fixed windows opendir error
101
+ * added total archived files to notifications email
102
+ * timezone scheduler fix
103
+ * added default error sending to admin when no notification email is set
104
+
105
+
106
  = 4.0.1 =
107
  * Code rewritten from ground up to make use of latest code standards
108
  * Added support for Dropbox, Amazon S3, Azure Blob and SFTP storage
admin/class-xcloner-admin.php CHANGED
@@ -40,6 +40,7 @@ class Xcloner_Admin {
40
  */
41
  private $version;
42
 
 
43
  /**
44
  * Initialize the class and set its properties.
45
  *
@@ -47,11 +48,16 @@ class Xcloner_Admin {
47
  * @param string $plugin_name The name of this plugin.
48
  * @param string $version The version of this plugin.
49
  */
50
- public function __construct( $plugin_name, $version ) {
51
 
52
- $this->plugin_name = $plugin_name;
53
- $this->version = $version;
54
-
 
 
 
 
 
55
  }
56
 
57
  /**
@@ -75,7 +81,7 @@ class Xcloner_Admin {
75
  * between the defined hooks and the functions defined in this
76
  * class.
77
  */
78
-
79
  wp_enqueue_style( $this->plugin_name."_materialize", plugin_dir_url( __FILE__ ) . 'css/materialize.min.css', array(), $this->version, 'all' );
80
  wp_enqueue_style( $this->plugin_name."_materialize.clockpicker", plugin_dir_url( __FILE__ ) . 'css/materialize.clockpicker.css', array(), $this->version, 'all' );
81
  wp_enqueue_style( $this->plugin_name."_materialize.icons", '//fonts.googleapis.com/icon?family=Material+Icons', array(), $this->version, 'all' );
@@ -107,7 +113,10 @@ class Xcloner_Admin {
107
  * between the defined hooks and the functions defined in this
108
  * class.
109
  */
110
-
 
 
 
111
  wp_enqueue_script( $this->plugin_name."_materialize", plugin_dir_url( __FILE__ ) . 'js/materialize.min.js', array( 'jquery' ), $this->version, false );
112
  wp_enqueue_script( $this->plugin_name."_materialize.clockpicker", plugin_dir_url( __FILE__ ) . 'js/materialize.clockpicker.js', array( 'jquery' ), $this->version, false );
113
  wp_enqueue_script( $this->plugin_name."_jquery.datatables", plugin_dir_url( __FILE__ ) . 'js/jquery.dataTables.min.js', array( 'jquery' ), $this->version, false );
@@ -131,18 +140,36 @@ class Xcloner_Admin {
131
 
132
  public function xcloner_remote_storage_page()
133
  {
 
 
 
 
 
134
  if(isset($_POST['action']))
135
  {
136
- $remote_storage = new Xcloner_Remote_Storage();
137
  $remote_storage->save($_POST['action']);
138
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  require_once("partials/xcloner_remote_storage_page.php");
140
 
141
  }
142
 
143
  public function xcloner_scheduled_backups_page()
144
  {
145
- $requirements = new XCloner_Requirements();
 
146
  if(!$requirements->check_backup_ready_status())
147
  {
148
  require_once("partials/xcloner_init_page.php");
@@ -173,7 +200,8 @@ class Xcloner_Admin {
173
 
174
  public function xcloner_generate_backups_page()
175
  {
176
- $requirements = new XCloner_Requirements();
 
177
  if(!$requirements->check_backup_ready_status())
178
  {
179
  require_once("partials/xcloner_init_page.php");
@@ -205,8 +233,10 @@ class Xcloner_Admin {
205
  ?>
206
 
207
  <?php
 
 
208
  if( isset( $_GET[ 'tab' ] ) ) {
209
- $active_tab = $_GET[ 'tab' ];
210
  } // end if
211
  else{
212
  $active_tab = "general_options";
@@ -220,8 +250,6 @@ class Xcloner_Admin {
220
  <li><a href="?page=xcloner_settings_page&tab=mysql_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'mysql_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('Mysql Options', 'xcloner-backup-and-restore')?></a></li>
221
  <li><a href="?page=xcloner_settings_page&tab=system_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'system_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('System Options', 'xcloner-backup-and-restore')?></a></li>
222
  <li><a href="?page=xcloner_settings_page&tab=cleanup_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'cleanup_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('Cleanup Options', 'xcloner-backup-and-restore')?></a></li>
223
- <!--<li><a href="?page=xcloner_settings_page&tab=cron_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'cron_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('Cron Options', 'xcloner-backup-and-restore')?></a></li>
224
- -->
225
  </ul>
226
 
227
  <div class="wrap">
@@ -246,11 +274,7 @@ class Xcloner_Admin {
246
 
247
  settings_fields('xcloner_cleanup_settings_group');
248
  do_settings_sections('xcloner_cleanup_settings_page');
249
- }/*else{
250
-
251
- settings_fields('xcloner_cron_settings_group');
252
- do_settings_sections('xcloner_cron_settings_page');
253
- }*/
254
 
255
  // output save settings button
256
  submit_button('Save Settings');
40
  */
41
  private $version;
42
 
43
+ private $xcloner_container;
44
  /**
45
  * Initialize the class and set its properties.
46
  *
48
  * @param string $plugin_name The name of this plugin.
49
  * @param string $version The version of this plugin.
50
  */
51
+ public function __construct( Xcloner $xcloner_container) {
52
 
53
+ $this->plugin_name = $xcloner_container->get_plugin_name();
54
+ $this->version = $xcloner_container->get_version();
55
+ $this->xcloner_container = $xcloner_container;
56
+ }
57
+
58
+ public function get_xcloner_container()
59
+ {
60
+ return $this->xcloner_container;
61
  }
62
 
63
  /**
81
  * between the defined hooks and the functions defined in this
82
  * class.
83
  */
84
+
85
  wp_enqueue_style( $this->plugin_name."_materialize", plugin_dir_url( __FILE__ ) . 'css/materialize.min.css', array(), $this->version, 'all' );
86
  wp_enqueue_style( $this->plugin_name."_materialize.clockpicker", plugin_dir_url( __FILE__ ) . 'css/materialize.clockpicker.css', array(), $this->version, 'all' );
87
  wp_enqueue_style( $this->plugin_name."_materialize.icons", '//fonts.googleapis.com/icon?family=Material+Icons', array(), $this->version, 'all' );
113
  * between the defined hooks and the functions defined in this
114
  * class.
115
  */
116
+
117
+ add_thickbox();
118
+ wp_enqueue_script('plugin-install');
119
+ wp_enqueue_script('updates');
120
  wp_enqueue_script( $this->plugin_name."_materialize", plugin_dir_url( __FILE__ ) . 'js/materialize.min.js', array( 'jquery' ), $this->version, false );
121
  wp_enqueue_script( $this->plugin_name."_materialize.clockpicker", plugin_dir_url( __FILE__ ) . 'js/materialize.clockpicker.js', array( 'jquery' ), $this->version, false );
122
  wp_enqueue_script( $this->plugin_name."_jquery.datatables", plugin_dir_url( __FILE__ ) . 'js/jquery.dataTables.min.js', array( 'jquery' ), $this->version, false );
140
 
141
  public function xcloner_remote_storage_page()
142
  {
143
+ $xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
144
+ $remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
145
+
146
+ $_POST['action'] = $xcloner_sanitization->sanitize_input_as_string($_POST['action']);
147
+
148
  if(isset($_POST['action']))
149
  {
 
150
  $remote_storage->save($_POST['action']);
151
  }
152
+
153
+ if(isset($_POST['authentification_code']) && $_POST['authentification_code'] != "")
154
+ {
155
+ $_POST['authentification_code'] = $xcloner_sanitization->sanitize_input_as_string($_POST['authentification_code']);
156
+
157
+ $remote_storage->set_access_token($_POST['authentification_code']);
158
+ }
159
+
160
+ if(isset($_POST['connection_check']) && $_POST['connection_check'])
161
+ {
162
+ $remote_storage->check($_POST['action']);
163
+ }
164
+
165
  require_once("partials/xcloner_remote_storage_page.php");
166
 
167
  }
168
 
169
  public function xcloner_scheduled_backups_page()
170
  {
171
+ $requirements = $this->xcloner_container->get_xcloner_requirements();
172
+
173
  if(!$requirements->check_backup_ready_status())
174
  {
175
  require_once("partials/xcloner_init_page.php");
200
 
201
  public function xcloner_generate_backups_page()
202
  {
203
+ $requirements = $this->xcloner_container->get_xcloner_requirements();
204
+
205
  if(!$requirements->check_backup_ready_status())
206
  {
207
  require_once("partials/xcloner_init_page.php");
233
  ?>
234
 
235
  <?php
236
+ $xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
237
+
238
  if( isset( $_GET[ 'tab' ] ) ) {
239
+ $active_tab = $xcloner_sanitization->sanitize_input_as_string($_GET[ 'tab' ]);
240
  } // end if
241
  else{
242
  $active_tab = "general_options";
250
  <li><a href="?page=xcloner_settings_page&tab=mysql_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'mysql_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('Mysql Options', 'xcloner-backup-and-restore')?></a></li>
251
  <li><a href="?page=xcloner_settings_page&tab=system_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'system_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('System Options', 'xcloner-backup-and-restore')?></a></li>
252
  <li><a href="?page=xcloner_settings_page&tab=cleanup_options" class="nav-tab col s12 m3 l2 <?php echo $active_tab == 'cleanup_options' ? 'nav-tab-active' : ''; ?>"><?php echo __('Cleanup Options', 'xcloner-backup-and-restore')?></a></li>
 
 
253
  </ul>
254
 
255
  <div class="wrap">
274
 
275
  settings_fields('xcloner_cleanup_settings_group');
276
  do_settings_sections('xcloner_cleanup_settings_page');
277
+ }
 
 
 
 
278
 
279
  // output save settings button
280
  submit_button('Save Settings');
admin/css/xcloner-admin.css CHANGED
@@ -168,7 +168,8 @@ textarea.materialize-textarea{
168
 
169
  #scheduled_backups .edit, #scheduled_backups .status.active, #scheduled_backups .delete,
170
  #manage_backups .download, #manage_backups .cloud-upload, #manage_backups .delete, #manage_backups .list-backup-content,
171
- .backup-done .download, .backup-done .cloud-upload, .backup-done .delete, .backup-done .list-backup-content{
 
172
  color: #4db6ac;
173
  }
174
 
@@ -362,6 +363,22 @@ ul.files-list li{
362
  min-width: 250px;
363
  }
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  @media only screen and (min-width: 993px){
367
  .dashboard .backup-ready{
168
 
169
  #scheduled_backups .edit, #scheduled_backups .status.active, #scheduled_backups .delete,
170
  #manage_backups .download, #manage_backups .cloud-upload, #manage_backups .delete, #manage_backups .list-backup-content,
171
+ .backup-done .download, .backup-done .cloud-upload, .backup-done .delete, .backup-done .list-backup-content,
172
+ #manage_backups .copy-remote-to-local,#manage_backups .expand-multipart{
173
  color: #4db6ac;
174
  }
175
 
363
  min-width: 250px;
364
  }
365
 
366
+ #generate_backup_form .progress, .xcloner-restore .progress{
367
+ width: 100% !important;
368
+ height: 4px !important;
369
+ }
370
+
371
+ i.backup_warning{
372
+ color: red;
373
+ }
374
+
375
+ .col.remote-storage-selection{
376
+ padding-top: 20px;
377
+ }
378
+
379
+ .remote-storage #authentification_code {
380
+ display: none;
381
+ }
382
 
383
  @media only screen and (min-width: 993px){
384
  .dashboard .backup-ready{
admin/js/dataTables.responsive.js CHANGED
@@ -20,6 +20,8 @@
20
  *
21
  * For details please refer to: http://www.datatables.net
22
  */
 
 
23
  (function( factory ){
24
  if ( typeof define === 'function' && define.amd ) {
25
  // AMD
20
  *
21
  * For details please refer to: http://www.datatables.net
22
  */
23
+ /** global: define */
24
+
25
  (function( factory ){
26
  if ( typeof define === 'function' && define.amd ) {
27
  // AMD
admin/js/materialize.clockpicker.js CHANGED
@@ -1 +1,2 @@
1
- !function(){function t(t){return document.createElementNS(r,t)}function i(t){return(t<10?"0":"")+t}function e(t){var i=++v+"";return t?t+i:i}function s(s,n){function r(t,i){var e=h.offset(),s=/^touch/.test(t.type),o=e.left+f,c=e.top+f,r=(s?t.originalEvent.touches[0]:t).pageX-o,l=(s?t.originalEvent.touches[0]:t).pageY-c,u=Math.sqrt(r*r+l*l),m=!1;if(!i||!(u<b-w||u>b+w)){t.preventDefault();var v=setTimeout(function(){V.popover.addClass("clockpicker-moving")},200);p&&h.append(V.canvas),V.setHand(r,l,!i,!0),a.off(d).on(d,function(t){t.preventDefault();var i=/^touch/.test(t.type),e=(i?t.originalEvent.touches[0]:t).pageX-o,s=(i?t.originalEvent.touches[0]:t).pageY-c;(m||e!==r||s!==l)&&(m=!0,V.setHand(e,s,!1,!0))}),a.off(k).on(k,function(t){a.off(k),t.preventDefault();var e=/^touch/.test(t.type),s=(e?t.originalEvent.changedTouches[0]:t).pageX-o,p=(e?t.originalEvent.changedTouches[0]:t).pageY-c;(i||m)&&s===r&&p===l&&V.setHand(s,p),"hours"===V.currentView?V.toggleView("minutes",A/2):n.autoclose&&(V.minutesView.addClass("clockpicker-dial-out"),setTimeout(function(){V.done()},A/2)),h.prepend(z),clearTimeout(v),V.popover.removeClass("clockpicker-moving"),a.off(d)})}}var l=c(M),h=l.find(".clockpicker-plate"),m=l.find(".picker__holder"),v=l.find(".clockpicker-hours"),P=l.find(".clockpicker-minutes"),C=l.find(".clockpicker-am-pm-block"),x="INPUT"===s.prop("tagName"),T=x?s:s.find("input"),_=c("label[for="+T.attr("id")+"]"),V=this;if(this.id=e("cp"),this.element=s,this.holder=m,this.options=n,this.isAppended=!1,this.isShown=!1,this.currentView="hours",this.isInput=x,this.input=T,this.label=_,this.popover=l,this.plate=h,this.hoursView=v,this.minutesView=P,this.amPmBlock=C,this.spanHours=l.find(".clockpicker-span-hours"),this.spanMinutes=l.find(".clockpicker-span-minutes"),this.spanAmPm=l.find(".clockpicker-span-am-pm"),this.footer=l.find(".picker__footer"),this.amOrPm="PM",n.twelvehour){var H=['<div class="clockpicker-am-pm-block">','<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-am-button">',"AM","</button>",'<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-pm-button">',"PM","</button>","</div>"].join("");c(H);n.ampmclickable?(this.spanAmPm.empty(),c('<div id="click-am">AM</div>').on("click",function(){V.spanAmPm.children("#click-am").addClass("text-primary"),V.spanAmPm.children("#click-pm").removeClass("text-primary"),V.amOrPm="AM"}).appendTo(this.spanAmPm),c('<div id="click-pm">PM</div>').on("click",function(){V.spanAmPm.children("#click-pm").addClass("text-primary"),V.spanAmPm.children("#click-am").removeClass("text-primary"),V.amOrPm="PM"}).appendTo(this.spanAmPm)):(c('<button type="button" class="btn-floating btn-flat clockpicker-button am-button" tabindex="1">AM</button>').on("click",function(){V.amOrPm="AM",V.amPmBlock.children(".pm-button").removeClass("active"),V.amPmBlock.children(".am-button").addClass("active"),V.spanAmPm.empty().append("AM")}).appendTo(this.amPmBlock),c('<button type="button" class="btn-floating btn-flat clockpicker-button pm-button" tabindex="2">PM</button>').on("click",function(){V.amOrPm="PM",V.amPmBlock.children(".am-button").removeClass("active"),V.amPmBlock.children(".pm-button").addClass("active"),V.spanAmPm.empty().append("PM")}).appendTo(this.amPmBlock))}T.attr("type","text"),n.darktheme&&l.addClass("darktheme"),c('<button type="button" class="btn-flat clockpicker-button" tabindex="'+(n.twelvehour?"3":"1")+'">'+n.donetext+"</button>").click(c.proxy(this.done,this)).appendTo(this.footer),this.spanHours.click(c.proxy(this.toggleView,this,"hours")),this.spanMinutes.click(c.proxy(this.toggleView,this,"minutes")),T.on("focus.clockpicker click.clockpicker",c.proxy(this.show,this));var S,B,D,E,I=c('<div class="clockpicker-tick"></div>');if(n.twelvehour)for(S=1;S<13;S+=1)B=I.clone(),D=S/6*Math.PI,E=b,B.css("font-size","140%"),B.css({left:f+Math.sin(D)*E-w,top:f-Math.cos(D)*E-w}),B.html(0===S?"00":S),v.append(B),B.on(u,r);else for(S=0;S<24;S+=1){B=I.clone(),D=S/6*Math.PI;var O=S>0&&S<13;E=O?g:b,B.css({left:f+Math.sin(D)*E-w,top:f-Math.cos(D)*E-w}),O&&B.css("font-size","120%"),B.html(0===S?"00":S),v.append(B),B.on(u,r)}for(S=0;S<60;S+=5)B=I.clone(),D=S/30*Math.PI,B.css({left:f+Math.sin(D)*b-w,top:f-Math.cos(D)*b-w}),B.css("font-size","140%"),B.html(i(S)),P.append(B),B.on(u,r);if(h.on(u,function(t){0===c(t.target).closest(".clockpicker-tick").length&&r(t,!0)}),p){var z=l.find(".clockpicker-canvas"),U=t("svg");U.setAttribute("class","clockpicker-svg"),U.setAttribute("width",y),U.setAttribute("height",y);var j=t("g");j.setAttribute("transform","translate("+f+","+f+")");var L=t("circle");L.setAttribute("class","clockpicker-canvas-bearing"),L.setAttribute("cx",0),L.setAttribute("cy",0),L.setAttribute("r",2);var N=t("line");N.setAttribute("x1",0),N.setAttribute("y1",0);var X=t("circle");X.setAttribute("class","clockpicker-canvas-bg"),X.setAttribute("r",w);var Y=t("circle");Y.setAttribute("class","clockpicker-canvas-fg"),Y.setAttribute("r",5),j.appendChild(N),j.appendChild(X),j.appendChild(Y),j.appendChild(L),U.appendChild(j),z.append(U),this.hand=N,this.bg=X,this.fg=Y,this.bearing=L,this.g=j,this.canvas=z}o(this.options.init)}function o(t){t&&"function"==typeof t&&t()}var c=window.jQuery,n=c(window),a=c(document),r="http://www.w3.org/2000/svg",p="SVGAngle"in window&&function(){var t,i=document.createElement("div");return i.innerHTML="<svg/>",t=(i.firstChild&&i.firstChild.namespaceURI)==r,i.innerHTML="",t}(),l=function(){var t=document.createElement("div").style;return"transition"in t||"WebkitTransition"in t||"MozTransition"in t||"msTransition"in t||"OTransition"in t}(),h="ontouchstart"in window,u="mousedown"+(h?" touchstart":""),d="mousemove.clockpicker"+(h?" touchmove.clockpicker":""),k="mouseup.clockpicker"+(h?" touchend.clockpicker":""),m=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,v=0,f=135,b=110,g=80,w=20,y=2*f,A=l?350:1,M=['<div class="clockpicker picker">','<div class="picker__holder">','<div class="picker__frame">','<div class="picker__wrap">','<div class="picker__box">','<div class="picker__date-display">','<div class="clockpicker-display">','<div class="clockpicker-display-column">','<span class="clockpicker-span-hours text-primary"></span>',":",'<span class="clockpicker-span-minutes"></span>',"</div>",'<div class="clockpicker-display-column clockpicker-display-am-pm">','<div class="clockpicker-span-am-pm"></div>',"</div>","</div>","</div>",'<div class="picker__calendar-container">','<div class="clockpicker-plate">','<div class="clockpicker-canvas"></div>','<div class="clockpicker-dial clockpicker-hours"></div>','<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',"</div>",'<div class="clockpicker-am-pm-block">',"</div>","</div>",'<div class="picker__footer">',"</div>","</div>","</div>","</div>","</div>","</div>"].join("");s.DEFAULTS={"default":"",fromnow:0,donetext:"Done",autoclose:!1,ampmclickable:!1,darktheme:!1,twelvehour:!0,vibrate:!0},s.prototype.toggle=function(){this[this.isShown?"hide":"show"]()},s.prototype.locate=function(){var t=this.element,i=this.popover;t.offset(),t.outerWidth(),t.outerHeight(),this.options.align;i.show()},s.prototype.show=function(t){if(this.setAMorPM=function(t){var i=t,e="pm"==t?"am":"pm";this.options.twelvehour&&(this.amOrPm=i.toUpperCase(),this.options.ampmclickable?(this.spanAmPm.children("#click-"+i).addClass("text-primary"),this.spanAmPm.children("#click-"+e).removeClass("text-primary")):(this.amPmBlock.children("."+e+"-button").removeClass("active"),this.amPmBlock.children("."+i+"-button").addClass("active"),this.spanAmPm.empty().append(this.amOrPm)))},!this.isShown){o(this.options.beforeShow),c(":input").each(function(){c(this).attr("tabindex",-1)});var e=this;this.input.blur(),this.popover.addClass("picker--opened"),this.input.addClass("picker__input picker__input--active"),c(document.body).css("overflow","hidden"),this.isAppended||(this.options.hasOwnProperty("container")?this.popover.appendTo(this.options.container):this.popover.insertAfter(this.input),this.setAMorPM("pm"),n.on("resize.clockpicker"+this.id,function(){e.isShown&&e.locate()}),this.isAppended=!0);var s=((this.input.prop("value")||this.options["default"]||"")+"").split(":");if(this.options.twelvehour&&"undefined"!=typeof s[1]&&(s[1].includes("AM")?this.setAMorPM("am"):this.setAMorPM("pm"),s[1]=s[1].replace("AM","").replace("PM","")),"now"===s[0]){var r=new Date(+new Date+this.options.fromnow);r.getHours()>=12?this.setAMorPM("pm"):this.setAMorPM("am"),s=[r.getHours(),r.getMinutes()]}this.hours=+s[0]||0,this.minutes=+s[1]||0,this.spanHours.html(i(this.hours)),this.spanMinutes.html(i(this.minutes)),this.toggleView("hours"),this.locate(),this.isShown=!0,a.on("click.clockpicker."+this.id+" focusin.clockpicker."+this.id,function(t){var i=c(t.target);0===i.closest(e.popover.find(".picker__wrap")).length&&0===i.closest(e.input).length&&e.hide()}),a.on("keyup.clockpicker."+this.id,function(t){27===t.keyCode&&e.hide()}),o(this.options.afterShow)}},s.prototype.hide=function(){o(this.options.beforeHide),this.input.removeClass("picker__input picker__input--active"),this.popover.removeClass("picker--opened"),c(document.body).css("overflow","visible"),this.isShown=!1,c(":input").each(function(t){c(this).attr("tabindex",t+1)}),a.off("click.clockpicker."+this.id+" focusin.clockpicker."+this.id),a.off("keyup.clockpicker."+this.id),this.popover.hide(),o(this.options.afterHide)},s.prototype.toggleView=function(t,i){var e=!1;"minutes"===t&&"visible"===c(this.hoursView).css("visibility")&&(o(this.options.beforeHourSelect),e=!0);var s="hours"===t,n=s?this.hoursView:this.minutesView,a=s?this.minutesView:this.hoursView;this.currentView=t,this.spanHours.toggleClass("text-primary",s),this.spanMinutes.toggleClass("text-primary",!s),a.addClass("clockpicker-dial-out"),n.css("visibility","visible").removeClass("clockpicker-dial-out"),this.resetClock(i),clearTimeout(this.toggleViewTimer),this.toggleViewTimer=setTimeout(function(){a.css("visibility","hidden")},A),e&&o(this.options.afterHourSelect)},s.prototype.resetClock=function(t){var i=this.currentView,e=this[i],s="hours"===i,o=Math.PI/(s?6:30),c=e*o,n=s&&e>0&&e<13?g:b,a=Math.sin(c)*n,r=-Math.cos(c)*n,l=this;p&&t?(l.canvas.addClass("clockpicker-canvas-out"),setTimeout(function(){l.canvas.removeClass("clockpicker-canvas-out"),l.setHand(a,r)},t)):this.setHand(a,r)},s.prototype.setHand=function(t,e,s,o){var n,a=Math.atan2(t,-e),r="hours"===this.currentView,l=Math.PI/(r||s?6:30),h=Math.sqrt(t*t+e*e),u=this.options,d=r&&h<(b+g)/2,k=d?g:b;if(u.twelvehour&&(k=b),a<0&&(a=2*Math.PI+a),n=Math.round(a/l),a=n*l,u.twelvehour?r?0===n&&(n=12):(s&&(n*=5),60===n&&(n=0)):r?(12===n&&(n=0),n=d?0===n?12:n:0===n?0:n+12):(s&&(n*=5),60===n&&(n=0)),r?this.fg.setAttribute("class","clockpicker-canvas-fg"):n%5==0?this.fg.setAttribute("class","clockpicker-canvas-fg"):this.fg.setAttribute("class","clockpicker-canvas-fg active"),this[this.currentView]!==n&&m&&this.options.vibrate&&(this.vibrateTimer||(navigator[m](10),this.vibrateTimer=setTimeout(c.proxy(function(){this.vibrateTimer=null},this),100))),this[this.currentView]=n,this[r?"spanHours":"spanMinutes"].html(i(n)),!p)return void this[r?"hoursView":"minutesView"].find(".clockpicker-tick").each(function(){var t=c(this);t.toggleClass("active",n===+t.html())});o||!r&&n%5?(this.g.insertBefore(this.hand,this.bearing),this.g.insertBefore(this.bg,this.fg),this.bg.setAttribute("class","clockpicker-canvas-bg clockpicker-canvas-bg-trans")):(this.g.insertBefore(this.hand,this.bg),this.g.insertBefore(this.fg,this.bg),this.bg.setAttribute("class","clockpicker-canvas-bg"));var v=Math.sin(a)*(k-w),f=-Math.cos(a)*(k-w),y=Math.sin(a)*k,A=-Math.cos(a)*k;this.hand.setAttribute("x2",v),this.hand.setAttribute("y2",f),this.bg.setAttribute("cx",y),this.bg.setAttribute("cy",A),this.fg.setAttribute("cx",y),this.fg.setAttribute("cy",A)},s.prototype.done=function(){o(this.options.beforeDone),this.hide(),this.label.addClass("active");var t=this.input.prop("value"),e=i(this.hours)+":"+i(this.minutes);this.options.twelvehour&&(e+=this.amOrPm),this.input.prop("value",e),e!==t&&(this.input.triggerHandler("change"),this.isInput||this.element.trigger("change")),this.options.autoclose&&this.input.trigger("blur"),o(this.options.afterDone)},s.prototype.remove=function(){this.element.removeData("clockpicker"),this.input.off("focus.clockpicker click.clockpicker"),this.isShown&&this.hide(),this.isAppended&&(n.off("resize.clockpicker"+this.id),this.popover.remove())},c.fn.pickatime=function(t){var i=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=c(this),o=e.data("clockpicker");if(o)"function"==typeof o[t]&&o[t].apply(o,i);else{var n=c.extend({},s.DEFAULTS,e.data(),"object"==typeof t&&t);e.data("clockpicker",new s(e,n))}})}}();
 
1
+ /** global: navigator */
2
+ !function(){function t(t){return document.createElementNS(r,t)}function i(t){return(t<10?"0":"")+t}function e(t){var i=++v+"";return t?t+i:i}function s(s,n){function r(t,i){var e=h.offset(),s=/^touch/.test(t.type),o=e.left+f,c=e.top+f,r=(s?t.originalEvent.touches[0]:t).pageX-o,l=(s?t.originalEvent.touches[0]:t).pageY-c,u=Math.sqrt(r*r+l*l),m=!1;if(!i||!(u<b-w||u>b+w)){t.preventDefault();var v=setTimeout(function(){V.popover.addClass("clockpicker-moving")},200);p&&h.append(V.canvas),V.setHand(r,l,!i,!0),a.off(d).on(d,function(t){t.preventDefault();var i=/^touch/.test(t.type),e=(i?t.originalEvent.touches[0]:t).pageX-o,s=(i?t.originalEvent.touches[0]:t).pageY-c;(m||e!==r||s!==l)&&(m=!0,V.setHand(e,s,!1,!0))}),a.off(k).on(k,function(t){a.off(k),t.preventDefault();var e=/^touch/.test(t.type),s=(e?t.originalEvent.changedTouches[0]:t).pageX-o,p=(e?t.originalEvent.changedTouches[0]:t).pageY-c;(i||m)&&s===r&&p===l&&V.setHand(s,p),"hours"===V.currentView?V.toggleView("minutes",A/2):n.autoclose&&(V.minutesView.addClass("clockpicker-dial-out"),setTimeout(function(){V.done()},A/2)),h.prepend(z),clearTimeout(v),V.popover.removeClass("clockpicker-moving"),a.off(d)})}}var l=c(M),h=l.find(".clockpicker-plate"),m=l.find(".picker__holder"),v=l.find(".clockpicker-hours"),P=l.find(".clockpicker-minutes"),C=l.find(".clockpicker-am-pm-block"),x="INPUT"===s.prop("tagName"),T=x?s:s.find("input"),_=c("label[for="+T.attr("id")+"]"),V=this;if(this.id=e("cp"),this.element=s,this.holder=m,this.options=n,this.isAppended=!1,this.isShown=!1,this.currentView="hours",this.isInput=x,this.input=T,this.label=_,this.popover=l,this.plate=h,this.hoursView=v,this.minutesView=P,this.amPmBlock=C,this.spanHours=l.find(".clockpicker-span-hours"),this.spanMinutes=l.find(".clockpicker-span-minutes"),this.spanAmPm=l.find(".clockpicker-span-am-pm"),this.footer=l.find(".picker__footer"),this.amOrPm="PM",n.twelvehour){var H=['<div class="clockpicker-am-pm-block">','<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-am-button">',"AM","</button>",'<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-pm-button">',"PM","</button>","</div>"].join("");c(H);n.ampmclickable?(this.spanAmPm.empty(),c('<div id="click-am">AM</div>').on("click",function(){V.spanAmPm.children("#click-am").addClass("text-primary"),V.spanAmPm.children("#click-pm").removeClass("text-primary"),V.amOrPm="AM"}).appendTo(this.spanAmPm),c('<div id="click-pm">PM</div>').on("click",function(){V.spanAmPm.children("#click-pm").addClass("text-primary"),V.spanAmPm.children("#click-am").removeClass("text-primary"),V.amOrPm="PM"}).appendTo(this.spanAmPm)):(c('<button type="button" class="btn-floating btn-flat clockpicker-button am-button" tabindex="1">AM</button>').on("click",function(){V.amOrPm="AM",V.amPmBlock.children(".pm-button").removeClass("active"),V.amPmBlock.children(".am-button").addClass("active"),V.spanAmPm.empty().append("AM")}).appendTo(this.amPmBlock),c('<button type="button" class="btn-floating btn-flat clockpicker-button pm-button" tabindex="2">PM</button>').on("click",function(){V.amOrPm="PM",V.amPmBlock.children(".am-button").removeClass("active"),V.amPmBlock.children(".pm-button").addClass("active"),V.spanAmPm.empty().append("PM")}).appendTo(this.amPmBlock))}T.attr("type","text"),n.darktheme&&l.addClass("darktheme"),c('<button type="button" class="btn-flat clockpicker-button" tabindex="'+(n.twelvehour?"3":"1")+'">'+n.donetext+"</button>").click(c.proxy(this.done,this)).appendTo(this.footer),this.spanHours.click(c.proxy(this.toggleView,this,"hours")),this.spanMinutes.click(c.proxy(this.toggleView,this,"minutes")),T.on("focus.clockpicker click.clockpicker",c.proxy(this.show,this));var S,B,D,E,I=c('<div class="clockpicker-tick"></div>');if(n.twelvehour)for(S=1;S<13;S+=1)B=I.clone(),D=S/6*Math.PI,E=b,B.css("font-size","140%"),B.css({left:f+Math.sin(D)*E-w,top:f-Math.cos(D)*E-w}),B.html(0===S?"00":S),v.append(B),B.on(u,r);else for(S=0;S<24;S+=1){B=I.clone(),D=S/6*Math.PI;var O=S>0&&S<13;E=O?g:b,B.css({left:f+Math.sin(D)*E-w,top:f-Math.cos(D)*E-w}),O&&B.css("font-size","120%"),B.html(0===S?"00":S),v.append(B),B.on(u,r)}for(S=0;S<60;S+=5)B=I.clone(),D=S/30*Math.PI,B.css({left:f+Math.sin(D)*b-w,top:f-Math.cos(D)*b-w}),B.css("font-size","140%"),B.html(i(S)),P.append(B),B.on(u,r);if(h.on(u,function(t){0===c(t.target).closest(".clockpicker-tick").length&&r(t,!0)}),p){var z=l.find(".clockpicker-canvas"),U=t("svg");U.setAttribute("class","clockpicker-svg"),U.setAttribute("width",y),U.setAttribute("height",y);var j=t("g");j.setAttribute("transform","translate("+f+","+f+")");var L=t("circle");L.setAttribute("class","clockpicker-canvas-bearing"),L.setAttribute("cx",0),L.setAttribute("cy",0),L.setAttribute("r",2);var N=t("line");N.setAttribute("x1",0),N.setAttribute("y1",0);var X=t("circle");X.setAttribute("class","clockpicker-canvas-bg"),X.setAttribute("r",w);var Y=t("circle");Y.setAttribute("class","clockpicker-canvas-fg"),Y.setAttribute("r",5),j.appendChild(N),j.appendChild(X),j.appendChild(Y),j.appendChild(L),U.appendChild(j),z.append(U),this.hand=N,this.bg=X,this.fg=Y,this.bearing=L,this.g=j,this.canvas=z}o(this.options.init)}function o(t){t&&"function"==typeof t&&t()}var c=window.jQuery,n=c(window),a=c(document),r="http://www.w3.org/2000/svg",p="SVGAngle"in window&&function(){var t,i=document.createElement("div");return i.innerHTML="<svg/>",t=(i.firstChild&&i.firstChild.namespaceURI)==r,i.innerHTML="",t}(),l=function(){var t=document.createElement("div").style;return"transition"in t||"WebkitTransition"in t||"MozTransition"in t||"msTransition"in t||"OTransition"in t}(),h="ontouchstart"in window,u="mousedown"+(h?" touchstart":""),d="mousemove.clockpicker"+(h?" touchmove.clockpicker":""),k="mouseup.clockpicker"+(h?" touchend.clockpicker":""),m=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,v=0,f=135,b=110,g=80,w=20,y=2*f,A=l?350:1,M=['<div class="clockpicker picker">','<div class="picker__holder">','<div class="picker__frame">','<div class="picker__wrap">','<div class="picker__box">','<div class="picker__date-display">','<div class="clockpicker-display">','<div class="clockpicker-display-column">','<span class="clockpicker-span-hours text-primary"></span>',":",'<span class="clockpicker-span-minutes"></span>',"</div>",'<div class="clockpicker-display-column clockpicker-display-am-pm">','<div class="clockpicker-span-am-pm"></div>',"</div>","</div>","</div>",'<div class="picker__calendar-container">','<div class="clockpicker-plate">','<div class="clockpicker-canvas"></div>','<div class="clockpicker-dial clockpicker-hours"></div>','<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',"</div>",'<div class="clockpicker-am-pm-block">',"</div>","</div>",'<div class="picker__footer">',"</div>","</div>","</div>","</div>","</div>","</div>"].join("");s.DEFAULTS={"default":"",fromnow:0,donetext:"Done",autoclose:!1,ampmclickable:!1,darktheme:!1,twelvehour:!0,vibrate:!0},s.prototype.toggle=function(){this[this.isShown?"hide":"show"]()},s.prototype.locate=function(){var t=this.element,i=this.popover;t.offset(),t.outerWidth(),t.outerHeight(),this.options.align;i.show()},s.prototype.show=function(t){if(this.setAMorPM=function(t){var i=t,e="pm"==t?"am":"pm";this.options.twelvehour&&(this.amOrPm=i.toUpperCase(),this.options.ampmclickable?(this.spanAmPm.children("#click-"+i).addClass("text-primary"),this.spanAmPm.children("#click-"+e).removeClass("text-primary")):(this.amPmBlock.children("."+e+"-button").removeClass("active"),this.amPmBlock.children("."+i+"-button").addClass("active"),this.spanAmPm.empty().append(this.amOrPm)))},!this.isShown){o(this.options.beforeShow),c(":input").each(function(){c(this).attr("tabindex",-1)});var e=this;this.input.blur(),this.popover.addClass("picker--opened"),this.input.addClass("picker__input picker__input--active"),c(document.body).css("overflow","hidden"),this.isAppended||(this.options.hasOwnProperty("container")?this.popover.appendTo(this.options.container):this.popover.insertAfter(this.input),this.setAMorPM("pm"),n.on("resize.clockpicker"+this.id,function(){e.isShown&&e.locate()}),this.isAppended=!0);var s=((this.input.prop("value")||this.options["default"]||"")+"").split(":");if(this.options.twelvehour&&"undefined"!=typeof s[1]&&(s[1].includes("AM")?this.setAMorPM("am"):this.setAMorPM("pm"),s[1]=s[1].replace("AM","").replace("PM","")),"now"===s[0]){var r=new Date(+new Date+this.options.fromnow);r.getHours()>=12?this.setAMorPM("pm"):this.setAMorPM("am"),s=[r.getHours(),r.getMinutes()]}this.hours=+s[0]||0,this.minutes=+s[1]||0,this.spanHours.html(i(this.hours)),this.spanMinutes.html(i(this.minutes)),this.toggleView("hours"),this.locate(),this.isShown=!0,a.on("click.clockpicker."+this.id+" focusin.clockpicker."+this.id,function(t){var i=c(t.target);0===i.closest(e.popover.find(".picker__wrap")).length&&0===i.closest(e.input).length&&e.hide()}),a.on("keyup.clockpicker."+this.id,function(t){27===t.keyCode&&e.hide()}),o(this.options.afterShow)}},s.prototype.hide=function(){o(this.options.beforeHide),this.input.removeClass("picker__input picker__input--active"),this.popover.removeClass("picker--opened"),c(document.body).css("overflow","visible"),this.isShown=!1,c(":input").each(function(t){c(this).attr("tabindex",t+1)}),a.off("click.clockpicker."+this.id+" focusin.clockpicker."+this.id),a.off("keyup.clockpicker."+this.id),this.popover.hide(),o(this.options.afterHide)},s.prototype.toggleView=function(t,i){var e=!1;"minutes"===t&&"visible"===c(this.hoursView).css("visibility")&&(o(this.options.beforeHourSelect),e=!0);var s="hours"===t,n=s?this.hoursView:this.minutesView,a=s?this.minutesView:this.hoursView;this.currentView=t,this.spanHours.toggleClass("text-primary",s),this.spanMinutes.toggleClass("text-primary",!s),a.addClass("clockpicker-dial-out"),n.css("visibility","visible").removeClass("clockpicker-dial-out"),this.resetClock(i),clearTimeout(this.toggleViewTimer),this.toggleViewTimer=setTimeout(function(){a.css("visibility","hidden")},A),e&&o(this.options.afterHourSelect)},s.prototype.resetClock=function(t){var i=this.currentView,e=this[i],s="hours"===i,o=Math.PI/(s?6:30),c=e*o,n=s&&e>0&&e<13?g:b,a=Math.sin(c)*n,r=-Math.cos(c)*n,l=this;p&&t?(l.canvas.addClass("clockpicker-canvas-out"),setTimeout(function(){l.canvas.removeClass("clockpicker-canvas-out"),l.setHand(a,r)},t)):this.setHand(a,r)},s.prototype.setHand=function(t,e,s,o){var n,a=Math.atan2(t,-e),r="hours"===this.currentView,l=Math.PI/(r||s?6:30),h=Math.sqrt(t*t+e*e),u=this.options,d=r&&h<(b+g)/2,k=d?g:b;if(u.twelvehour&&(k=b),a<0&&(a=2*Math.PI+a),n=Math.round(a/l),a=n*l,u.twelvehour?r?0===n&&(n=12):(s&&(n*=5),60===n&&(n=0)):r?(12===n&&(n=0),n=d?0===n?12:n:0===n?0:n+12):(s&&(n*=5),60===n&&(n=0)),r?this.fg.setAttribute("class","clockpicker-canvas-fg"):n%5==0?this.fg.setAttribute("class","clockpicker-canvas-fg"):this.fg.setAttribute("class","clockpicker-canvas-fg active"),this[this.currentView]!==n&&m&&this.options.vibrate&&(this.vibrateTimer||(navigator[m](10),this.vibrateTimer=setTimeout(c.proxy(function(){this.vibrateTimer=null},this),100))),this[this.currentView]=n,this[r?"spanHours":"spanMinutes"].html(i(n)),!p)return void this[r?"hoursView":"minutesView"].find(".clockpicker-tick").each(function(){var t=c(this);t.toggleClass("active",n===+t.html())});o||!r&&n%5?(this.g.insertBefore(this.hand,this.bearing),this.g.insertBefore(this.bg,this.fg),this.bg.setAttribute("class","clockpicker-canvas-bg clockpicker-canvas-bg-trans")):(this.g.insertBefore(this.hand,this.bg),this.g.insertBefore(this.fg,this.bg),this.bg.setAttribute("class","clockpicker-canvas-bg"));var v=Math.sin(a)*(k-w),f=-Math.cos(a)*(k-w),y=Math.sin(a)*k,A=-Math.cos(a)*k;this.hand.setAttribute("x2",v),this.hand.setAttribute("y2",f),this.bg.setAttribute("cx",y),this.bg.setAttribute("cy",A),this.fg.setAttribute("cx",y),this.fg.setAttribute("cy",A)},s.prototype.done=function(){o(this.options.beforeDone),this.hide(),this.label.addClass("active");var t=this.input.prop("value"),e=i(this.hours)+":"+i(this.minutes);this.options.twelvehour&&(e+=this.amOrPm),this.input.prop("value",e),e!==t&&(this.input.triggerHandler("change"),this.isInput||this.element.trigger("change")),this.options.autoclose&&this.input.trigger("blur"),o(this.options.afterDone)},s.prototype.remove=function(){this.element.removeData("clockpicker"),this.input.off("focus.clockpicker click.clockpicker"),this.isShown&&this.hide(),this.isAppended&&(n.off("resize.clockpicker"+this.id),this.popover.remove())},c.fn.pickatime=function(t){var i=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=c(this),o=e.data("clockpicker");if(o)"function"==typeof o[t]&&o[t].apply(o,i);else{var n=c.extend({},s.DEFAULTS,e.data(),"object"==typeof t&&t);e.data("clockpicker",new s(e,n))}})}}();
admin/js/xcloner-admin.js CHANGED
@@ -16,15 +16,29 @@
16
  jQuery("ul.xcloner_regex_exclude_limit li").fadeIn();
17
  })
18
 
19
- jQuery(".regex_pattern").click(function(){
20
  jQuery(this).select();
21
  })
22
 
23
- jQuery(".btn.system_info_toggle").click(function(){
24
  jQuery(".additional_system_info").toggle();
25
  })
26
 
27
- jQuery(".nav-tab-wrapper.content li").click(function(e){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  jQuery(".nav-tab-wrapper li a").removeClass("nav-tab-active");
29
  jQuery(this).find('a').addClass("nav-tab-active");
30
  jQuery(".nav-tab-wrapper-content .tab-content").removeClass('active');
@@ -42,44 +56,14 @@
42
  }
43
  })
44
 
45
- /**
46
- * All of the code for your admin-facing JavaScript source
47
- * should reside in this file.
48
- *
49
- * Note: It has been assumed you will write jQuery code here, so the
50
- * $ function reference has been prepared for usage within the scope
51
- * of this function.
52
- *
53
- * This enables you to define handlers, for when the DOM is ready:
54
- *
55
- * $(function() {
56
- *
57
- * });
58
- *
59
- * When the window is loaded:
60
- *
61
- * $( window ).load(function() {
62
- *
63
- * });
64
- *
65
- * ...and/or other possibilities.
66
- *
67
- * Ideally, it is not considered best practise to attach more than a
68
- * single DOM-ready or window-load handler for a particular page.
69
- * Although scripts in the WordPress core, Plugins and Themes may be
70
- * practising this, we should strive to set a better example in our own work.
71
- */
72
-
73
  })( jQuery );
74
 
75
 
76
 
77
 
78
- jQuery( document ).ajaxError(function(err, request) {
79
- //console.log( err );
80
- //console.log( request );
81
  //show_ajax_error("dd", "dd12", request)
82
- });
83
 
84
  function next_tab(hash){
85
  jQuery(".nav-tab-wrapper").find("li a[href='"+hash+"']").trigger('click');
@@ -100,21 +84,28 @@ function doShortText(elem)
100
  elem.attr("data-text", text).text(first+"..."+last);
101
  }
102
 
 
103
  function show_ajax_error(title, msg, json){
104
 
105
  //var json = jQuery.parseJSON( body )
106
 
107
- if(xcloner_backup !== undefined)
 
108
  xcloner_backup.cancel_backup();
 
109
 
110
  if(json.responseText)
 
111
  msg = msg+": "+json.responseText;
 
112
 
113
  jQuery("#error_modal .title").text(title);
114
  jQuery("#error_modal .msg").text(msg);
115
 
116
  if(json.status)
 
117
  jQuery("#error_modal .status").text(json.status+" "+json.statusText);
 
118
 
119
  jQuery("#error_modal .body").text(JSON.stringify(json));
120
  var error_modal = jQuery("#error_modal").modal();
@@ -129,4 +120,8 @@ var ID = function () {
129
  };
130
 
131
 
 
 
 
 
132
 
16
  jQuery("ul.xcloner_regex_exclude_limit li").fadeIn();
17
  })
18
 
19
+ jQuery(".regex_pattern").on("click", function(){
20
  jQuery(this).select();
21
  })
22
 
23
+ jQuery(".btn.system_info_toggle").on("click", function(){
24
  jQuery(".additional_system_info").toggle();
25
  })
26
 
27
+ jQuery("a.download-logger").on("click", function(e){
28
+ var xcloner_manage_backups = new Xcloner_Manage_Backups();
29
+
30
+ var hash = jQuery(this).attr('href');
31
+ var id = hash.substr(1)
32
+
33
+ if(id)
34
+ {
35
+ xcloner_manage_backups.download_backup_by_name(id);
36
+ }
37
+
38
+ e.preventDefault();
39
+ })
40
+
41
+ jQuery(".nav-tab-wrapper.content li").on("click", function(e){
42
  jQuery(".nav-tab-wrapper li a").removeClass("nav-tab-active");
43
  jQuery(this).find('a').addClass("nav-tab-active");
44
  jQuery(".nav-tab-wrapper-content .tab-content").removeClass('active');
56
  }
57
  })
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  })( jQuery );
60
 
61
 
62
 
63
 
64
+ //jQuery( document ).ajaxError(function(err, request) {
 
 
65
  //show_ajax_error("dd", "dd12", request)
66
+ //});
67
 
68
  function next_tab(hash){
69
  jQuery(".nav-tab-wrapper").find("li a[href='"+hash+"']").trigger('click');
84
  elem.attr("data-text", text).text(first+"..."+last);
85
  }
86
 
87
+ /** global: xcloner_backup */
88
  function show_ajax_error(title, msg, json){
89
 
90
  //var json = jQuery.parseJSON( body )
91
 
92
+ if(typeof xcloner_backup !== 'undefined')
93
+ {
94
  xcloner_backup.cancel_backup();
95
+ }
96
 
97
  if(json.responseText)
98
+ {
99
  msg = msg+": "+json.responseText;
100
+ }
101
 
102
  jQuery("#error_modal .title").text(title);
103
  jQuery("#error_modal .msg").text(msg);
104
 
105
  if(json.status)
106
+ {
107
  jQuery("#error_modal .status").text(json.status+" "+json.statusText);
108
+ }
109
 
110
  jQuery("#error_modal .body").text(JSON.stringify(json));
111
  var error_modal = jQuery("#error_modal").modal();
120
  };
121
 
122
 
123
+ var getUrlParam = function(name) {
124
+ return (location.search.split(name + '=')[1] || '').split('&')[0];
125
+ }
126
+
127
 
admin/js/xcloner-backup-class.js CHANGED
@@ -55,13 +55,19 @@ class Xcloner_Backup{
55
  if(json.extra.stats)
56
  {
57
  if(json.extra.stats.tables_count !== undefined)
 
58
  jQuery(elem).find(".table-counter").text(parseInt(json.extra.stats.tables_count));
 
59
 
60
  if(json.extra.stats.database_count !== undefined)
 
61
  jQuery(elem).find(".database-counter").text(parseInt(json.extra.stats.database_count));
 
62
 
63
  if(json.extra.stats.total_records !== undefined)
 
64
  jQuery(elem).find(".total-records").text(parseInt(json.extra.stats.total_records));
 
65
  }
66
 
67
  if(json.extra.tableName)
@@ -139,7 +145,9 @@ class Xcloner_Backup{
139
  {
140
 
141
  if(json.total_files_num)
 
142
  jQuery(".file-system .file-counter").text(parseInt(json.total_files_num) + parseInt(jQuery(".file-system .file-counter").text()));
 
143
 
144
  if(json.total_files_size) {
145
  var size = parseFloat(json.total_files_size) + parseFloat(jQuery(".file-system .file-size-total").text())
@@ -147,7 +155,9 @@ class Xcloner_Backup{
147
  }
148
 
149
  if(json.last_logged_file)
 
150
  jQuery(".file-system .last-logged-file").text(json.last_logged_file);
 
151
 
152
  if(!json.finished /*&& !this.cancel*/){
153
 
@@ -189,12 +199,16 @@ class Xcloner_Backup{
189
  return false;*/
190
 
191
  if(json.extra)
 
192
  this.params.extra = json.extra;
 
193
 
194
  if(json.extra)
195
  {
196
  if(json.extra.start_at_line !== undefined)
 
197
  jQuery(elem).find(".file-counter").text(parseInt(json.extra.start_at_line));
 
198
 
199
  if(json.extra.start_at_line !== undefined){
200
  //var prev_backup_size = parseInt(jQuery(elem).find(".file-size-total").attr('data-processed'));
@@ -207,9 +221,13 @@ class Xcloner_Backup{
207
  if(json.extra.processed_file)
208
  {
209
  if(json.extra.start_at_byte !== undefined && json.extra.start_at_byte)
 
210
  var processed_size = json.extra.start_at_byte;
 
211
  else
 
212
  var processed_size = json.extra.processed_file_size;
 
213
 
214
  jQuery(elem).find(".last-logged-file").text(json.extra.processed_file+" ("+this.getSize(processed_size, 1024)+" KB)");
215
  }
@@ -368,6 +386,7 @@ class Xcloner_Backup{
368
  this.resume.init = init
369
  }
370
 
 
371
  do_ajax(elem, action, init = 0)
372
  {
373
  var hash = '';
55
  if(json.extra.stats)
56
  {
57
  if(json.extra.stats.tables_count !== undefined)
58
+ {
59
  jQuery(elem).find(".table-counter").text(parseInt(json.extra.stats.tables_count));
60
+ }
61
 
62
  if(json.extra.stats.database_count !== undefined)
63
+ {
64
  jQuery(elem).find(".database-counter").text(parseInt(json.extra.stats.database_count));
65
+ }
66
 
67
  if(json.extra.stats.total_records !== undefined)
68
+ {
69
  jQuery(elem).find(".total-records").text(parseInt(json.extra.stats.total_records));
70
+ }
71
  }
72
 
73
  if(json.extra.tableName)
145
  {
146
 
147
  if(json.total_files_num)
148
+ {
149
  jQuery(".file-system .file-counter").text(parseInt(json.total_files_num) + parseInt(jQuery(".file-system .file-counter").text()));
150
+ }
151
 
152
  if(json.total_files_size) {
153
  var size = parseFloat(json.total_files_size) + parseFloat(jQuery(".file-system .file-size-total").text())
155
  }
156
 
157
  if(json.last_logged_file)
158
+ {
159
  jQuery(".file-system .last-logged-file").text(json.last_logged_file);
160
+ }
161
 
162
  if(!json.finished /*&& !this.cancel*/){
163
 
199
  return false;*/
200
 
201
  if(json.extra)
202
+ {
203
  this.params.extra = json.extra;
204
+ }
205
 
206
  if(json.extra)
207
  {
208
  if(json.extra.start_at_line !== undefined)
209
+ {
210
  jQuery(elem).find(".file-counter").text(parseInt(json.extra.start_at_line));
211
+ }
212
 
213
  if(json.extra.start_at_line !== undefined){
214
  //var prev_backup_size = parseInt(jQuery(elem).find(".file-size-total").attr('data-processed'));
221
  if(json.extra.processed_file)
222
  {
223
  if(json.extra.start_at_byte !== undefined && json.extra.start_at_byte)
224
+ {
225
  var processed_size = json.extra.start_at_byte;
226
+ }
227
  else
228
+ {
229
  var processed_size = json.extra.processed_file_size;
230
+ }
231
 
232
  jQuery(elem).find(".last-logged-file").text(json.extra.processed_file+" ("+this.getSize(processed_size, 1024)+" KB)");
233
  }
386
  this.resume.init = init
387
  }
388
 
389
+ /** global: ajaxurl */
390
  do_ajax(elem, action, init = 0)
391
  {
392
  var hash = '';
admin/js/xcloner-manage-backups-class.js CHANGED
@@ -1,9 +1,13 @@
 
 
1
 
2
- class Xcloner_Manage_Backups{
3
 
4
  constructor()
5
  {
6
  this.file_counter = 0
 
 
7
  //this.edit_modal = jQuery('.modal').modal();
8
  }
9
 
@@ -21,7 +25,7 @@
21
  jQuery.ajax({
22
  url: ajaxurl,
23
  method: 'post',
24
- data: { action : 'delete_backup_by_name', name: id},
25
  success: function(response){
26
  if(response.finished)
27
  {
@@ -77,9 +81,13 @@
77
  jQuery("#backup_cotent_modal .modal-content .files-list").prepend(files_text.reverse().join("\n"));
78
 
79
  if(!response.finished && jQuery('#backup_cotent_modal').is(':visible'))
 
80
  $this.list_backup_content_callback(backup_file, response.start, response.part)
 
81
  else
 
82
  jQuery("#backup_cotent_modal .progress > div").addClass('determinate').removeClass(".indeterminate").css('width', "100%")
 
83
 
84
  },
85
  error: function(xhr, textStatus, error){
@@ -146,6 +154,38 @@
146
  })
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  //end class
150
  }
151
 
@@ -153,6 +193,8 @@ jQuery(document).ready(function(){
153
 
154
  var xcloner_manage_backups = new Xcloner_Manage_Backups();
155
 
 
 
156
  jQuery("a.expand-multipart").on("click", function(){
157
  jQuery(this).parent().find("ul.multipart").toggle();
158
  jQuery(this).parent().find("a.expand-multipart.remove").toggle();
@@ -161,7 +203,7 @@ jQuery(document).ready(function(){
161
  var dataTable = jQuery('#manage_backups').DataTable( {
162
  'responsive': true,
163
  'bFilter': true,
164
- "order": [[ 2, "desc" ]],
165
  buttons: [
166
  'selectAll',
167
  'selectNone'
@@ -187,37 +229,54 @@ jQuery(document).ready(function(){
187
  "sSearch": "",
188
  "sSearchPlaceholder" : 'Search Backups',
189
  } ,
 
190
  "fnDrawCallback": function( oSettings ) {
191
 
192
  jQuery(this).off("click", ".delete").on("click", ".delete", function(e){
193
-
194
  var hash = jQuery(this).attr('href');
195
  var id = hash.substr(1)
196
- if(show_delete_alert && confirm('Are you sure you want to delete it?'))
197
- var data = xcloner_manage_backups.delete_backup_by_name(id, (this), dataTable);
198
- else
199
- var data = xcloner_manage_backups.delete_backup_by_name(id, (this), dataTable);
 
 
 
 
 
 
 
 
 
200
  e.preventDefault();
201
  })
202
 
203
  jQuery(this).off("click", ".download").on("click", ".download", function(e){
204
  var hash = jQuery(this).attr('href');
205
  var id = hash.substr(1)
206
- var data = xcloner_manage_backups.download_backup_by_name(id);
207
  e.preventDefault();
208
  })
209
 
210
  jQuery(this).off("click", ".cloud-upload").on("click", ".cloud-upload", function(e){
211
  var hash = jQuery(this).attr('href');
212
  var id = hash.substr(1)
213
- var data = xcloner_manage_backups.cloud_upload(id);
 
 
 
 
 
 
 
214
  e.preventDefault();
215
  })
216
 
217
  jQuery(this).off("click", ".list-backup-content").on("click", ".list-backup-content", function(e){
218
  var hash = jQuery(this).attr('href');
219
  var id = hash.substr(1)
220
- var data = xcloner_manage_backups.list_backup_content(id);
221
  e.preventDefault();
222
  })
223
  }
@@ -241,14 +300,13 @@ jQuery(document).ready(function(){
241
  }
242
  })
243
 
244
- /*jQuery("#save_schedule").on("submit", function(){
245
-
246
- xcloner_scheduler.save_schedule(jQuery(this), dataTable)
247
-
248
- return false;
249
- })*/
250
-
251
  jQuery("#remote_storage_modal").modal();
 
 
 
 
 
 
252
 
253
 
254
  var show_delete_alert=1;
1
+ /** global: ajaxurl */
2
+ /** global: Materialize */
3
 
4
+ class Xcloner_Manage_Backups{
5
 
6
  constructor()
7
  {
8
  this.file_counter = 0
9
+ this.storage_selection = "";
10
+
11
  //this.edit_modal = jQuery('.modal').modal();
12
  }
13
 
25
  jQuery.ajax({
26
  url: ajaxurl,
27
  method: 'post',
28
+ data: { action : 'delete_backup_by_name', name: id, storage_selection: this.storage_selection},
29
  success: function(response){
30
  if(response.finished)
31
  {
81
  jQuery("#backup_cotent_modal .modal-content .files-list").prepend(files_text.reverse().join("\n"));
82
 
83
  if(!response.finished && jQuery('#backup_cotent_modal').is(':visible'))
84
+ {
85
  $this.list_backup_content_callback(backup_file, response.start, response.part)
86
+ }
87
  else
88
+ {
89
  jQuery("#backup_cotent_modal .progress > div").addClass('determinate').removeClass(".indeterminate").css('width', "100%")
90
+ }
91
 
92
  },
93
  error: function(xhr, textStatus, error){
154
  })
155
  }
156
 
157
+ copy_remote_to_local(backup_file)
158
+ {
159
+ jQuery("#local_storage_upload_modal").modal('open');
160
+ jQuery("#local_storage_upload_modal .modal-content .backup-name").text(backup_file);
161
+ jQuery("#local_storage_upload_modal .status-text").removeClass("error").text("");
162
+ jQuery("#local_storage_upload_modal .status .progress .indeterminate").removeClass("determinate").css("width", "0%");
163
+
164
+ if(backup_file)
165
+ {
166
+ jQuery.ajax({
167
+ url: ajaxurl,
168
+ method: 'post',
169
+ data: { action : 'copy_backup_remote_to_local', file: backup_file, storage_type: this.storage_selection},
170
+ success: function(response){
171
+ if(response.error)
172
+ {
173
+ jQuery("#local_storage_upload_modal .status-text").addClass("error").text(response.message)
174
+ }else{
175
+ jQuery("#local_storage_upload_modal .status-text").removeClass("error").text("done")
176
+ }
177
+
178
+ jQuery("#local_storage_upload_modal .status .progress .indeterminate").addClass("determinate").css("width", "100%");
179
+ },
180
+ error: function(xhr, textStatus, error){
181
+ jQuery("#local_storage_upload_modal .status-text").addClass("error").text(textStatus+error)
182
+ },
183
+ dataType: 'json'
184
+ });
185
+ }
186
+
187
+ }
188
+
189
  //end class
190
  }
191
 
193
 
194
  var xcloner_manage_backups = new Xcloner_Manage_Backups();
195
 
196
+ xcloner_manage_backups.storage_selection = getUrlParam('storage_selection');
197
+
198
  jQuery("a.expand-multipart").on("click", function(){
199
  jQuery(this).parent().find("ul.multipart").toggle();
200
  jQuery(this).parent().find("a.expand-multipart.remove").toggle();
203
  var dataTable = jQuery('#manage_backups').DataTable( {
204
  'responsive': true,
205
  'bFilter': true,
206
+ "order": [[ 1, "desc" ]],
207
  buttons: [
208
  'selectAll',
209
  'selectNone'
229
  "sSearch": "",
230
  "sSearchPlaceholder" : 'Search Backups',
231
  } ,
232
+ //"ajax": ajaxurl+"?action=get_backup_list",
233
  "fnDrawCallback": function( oSettings ) {
234
 
235
  jQuery(this).off("click", ".delete").on("click", ".delete", function(e){
236
+
237
  var hash = jQuery(this).attr('href');
238
  var id = hash.substr(1)
239
+ var data = "";
240
+
241
+ if(show_delete_alert)
242
+ {
243
+ if(confirm('Are you sure you want to delete it?'))
244
+ {
245
+ xcloner_manage_backups.delete_backup_by_name(id, (this), dataTable);
246
+ }
247
+ }else{
248
+ xcloner_manage_backups.delete_backup_by_name(id, (this), dataTable);
249
+ }
250
+
251
+
252
  e.preventDefault();
253
  })
254
 
255
  jQuery(this).off("click", ".download").on("click", ".download", function(e){
256
  var hash = jQuery(this).attr('href');
257
  var id = hash.substr(1)
258
+ xcloner_manage_backups.download_backup_by_name(id);
259
  e.preventDefault();
260
  })
261
 
262
  jQuery(this).off("click", ".cloud-upload").on("click", ".cloud-upload", function(e){
263
  var hash = jQuery(this).attr('href');
264
  var id = hash.substr(1)
265
+ xcloner_manage_backups.cloud_upload(id);
266
+ e.preventDefault();
267
+ })
268
+
269
+ jQuery(this).off("click", ".copy-remote-to-local").on("click", ".copy-remote-to-local", function(e){
270
+ var hash = jQuery(this).attr('href');
271
+ var id = hash.substr(1)
272
+ xcloner_manage_backups.copy_remote_to_local(id);
273
  e.preventDefault();
274
  })
275
 
276
  jQuery(this).off("click", ".list-backup-content").on("click", ".list-backup-content", function(e){
277
  var hash = jQuery(this).attr('href');
278
  var id = hash.substr(1)
279
+ xcloner_manage_backups.list_backup_content(id);
280
  e.preventDefault();
281
  })
282
  }
300
  }
301
  })
302
 
 
 
 
 
 
 
 
303
  jQuery("#remote_storage_modal").modal();
304
+ jQuery("#local_storage_upload_modal").modal();
305
+
306
+ jQuery("#storage_selection").on("change", function(){
307
+ console.log(jQuery(this).val());
308
+ window.location = window.location.href.split('&storage_selection')[0]+"&storage_selection="+jQuery(this).val();
309
+ })
310
 
311
 
312
  var show_delete_alert=1;
admin/js/xcloner-remote-storage-class.js CHANGED
@@ -6,12 +6,15 @@ class Xcloner_Remote_Storage
6
 
7
  }
8
 
 
9
  toggle_status(elem)
10
  {
11
  var field = jQuery(elem).attr("name")
12
  var value = 0
13
  if(jQuery(elem).is(":checked"))
 
14
  value = 1;
 
15
 
16
  if(field){
17
  jQuery.ajax({
@@ -20,7 +23,9 @@ class Xcloner_Remote_Storage
20
  data: { action : 'remote_storage_save_status', id: field, value: value},
21
  success: function(response){
22
  if(!response.finished)
23
- alert('Error changing status')
 
 
24
  },
25
  dataType: 'json'
26
 
6
 
7
  }
8
 
9
+ /** global: ajaxurl */
10
  toggle_status(elem)
11
  {
12
  var field = jQuery(elem).attr("name")
13
  var value = 0
14
  if(jQuery(elem).is(":checked"))
15
+ {
16
  value = 1;
17
+ }
18
 
19
  if(field){
20
  jQuery.ajax({
23
  data: { action : 'remote_storage_save_status', id: field, value: value},
24
  success: function(response){
25
  if(!response.finished)
26
+ {
27
+ alert('Error changing status')
28
+ }
29
  },
30
  dataType: 'json'
31
 
admin/js/xcloner-restore-class.js CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  class Xcloner_Restore{
2
 
3
  constructor(hash)
@@ -50,10 +53,10 @@ class Xcloner_Restore{
50
 
51
  for( var key in files)
52
  {
 
 
53
  if(files[key].selected)
54
- var selected = "selected";
55
- else
56
- var selected = "not-selected";
57
 
58
  jQuery('.xcloner-restore #remote_backup_file').append("<option value='"+files[key].path+"' "+selected+">"+files[key].path+"("+e.detail.$this.getSize(files[key].size)+" MB)"+"</option>").addClass("file");
59
  }
@@ -95,14 +98,23 @@ class Xcloner_Restore{
95
  jQuery(".xcloner-restore .steps.active .progress").show();
96
 
97
  if(e.detail.class == "indeterminate")
 
98
  jQuery(".xcloner-restore .steps.active .progress > div").addClass(e.detail.class).removeClass('determinate')
 
 
99
  if(e.detail.class == "determinate")
 
100
  jQuery(".xcloner-restore .steps.active .progress > div").addClass(e.detail.class).removeClass('indeterminate')
 
101
 
102
  if(e.detail.percent == 100)
 
103
  jQuery(".xcloner-restore .steps.active .progress > div").removeClass('indeterminate').addClass('determinate').css("width", e.detail.percent+"%")
104
- else
 
 
105
  jQuery(".xcloner-restore .steps.active .progress .determinate").css("width", e.detail.percent+"%")
 
106
  }
107
 
108
  }, false);
@@ -218,7 +230,7 @@ class Xcloner_Restore{
218
 
219
  if(this.resume.callback == "get_remote_mysqldump_files_callback")
220
  {
221
- console.log("do resume");
222
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
223
  this.resume = new Object();
224
  return;
@@ -280,7 +292,6 @@ class Xcloner_Restore{
280
 
281
  if(response.statusText.extracted_files)
282
  {
283
- //console.log(response.statusText.extracted_files);
284
  document.dispatchEvent(new CustomEvent("remote_restore_update_files_list", {detail: {files: response.statusText.extracted_files}}));
285
  }
286
 
@@ -313,7 +324,7 @@ class Xcloner_Restore{
313
 
314
  if(this.resume.callback == "remote_restore_backup_file_callback")
315
  {
316
- console.log("do resume");
317
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
318
  this.resume = new Object();
319
  return;
@@ -326,8 +337,6 @@ class Xcloner_Restore{
326
 
327
  remote_restore_mysql_backup_file_callback(response, status, params = new Object())
328
  {
329
- //var processed = parseInt(response.statusText.start)+parseInt(response.statusText.processed)
330
-
331
  if(!status)
332
  {
333
  this.start = response.statusText.start;
@@ -343,12 +352,6 @@ class Xcloner_Restore{
343
  params.query = "";
344
 
345
  var processed = parseInt(response.statusText.start)+parseInt(response.statusText.processed)
346
-
347
- if(response.statusText.extracted_files)
348
- {
349
- //console.log(response.statusText.extracted_files);
350
- //document.dispatchEvent(new CustomEvent("remote_restore_update_files_list", {detail: {files: response.statusText.extracted_files}}));
351
- }
352
 
353
  if(!response.statusText.finished)
354
  {
@@ -412,7 +415,7 @@ class Xcloner_Restore{
412
 
413
  if(this.resume.callback == "remote_restore_mysql_backup_file_callback")
414
  {
415
- console.log("do resume mysql backup restore");
416
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
417
  this.resume = new Object();
418
  return;
@@ -576,6 +579,7 @@ class Xcloner_Restore{
576
  document.dispatchEvent(new CustomEvent("remote_restore_update_files_list", {detail: {files: ""}}));
577
  }
578
 
 
579
  do_ajax(callback, action="", params= new Object())
580
  {
581
  params.action = action
@@ -594,7 +598,9 @@ class Xcloner_Restore{
594
  }
595
 
596
  if(!this.restore_script_url)
 
597
  return false;
 
598
 
599
  var $this = this;
600
 
1
+ /** global: CustomEvent */
2
+ /** global: Event */
3
+
4
  class Xcloner_Restore{
5
 
6
  constructor(hash)
53
 
54
  for( var key in files)
55
  {
56
+ var selected = "not-selected";
57
+
58
  if(files[key].selected)
59
+ selected = "selected";
 
 
60
 
61
  jQuery('.xcloner-restore #remote_backup_file').append("<option value='"+files[key].path+"' "+selected+">"+files[key].path+"("+e.detail.$this.getSize(files[key].size)+" MB)"+"</option>").addClass("file");
62
  }
98
  jQuery(".xcloner-restore .steps.active .progress").show();
99
 
100
  if(e.detail.class == "indeterminate")
101
+ {
102
  jQuery(".xcloner-restore .steps.active .progress > div").addClass(e.detail.class).removeClass('determinate')
103
+ }
104
+
105
  if(e.detail.class == "determinate")
106
+ {
107
  jQuery(".xcloner-restore .steps.active .progress > div").addClass(e.detail.class).removeClass('indeterminate')
108
+ }
109
 
110
  if(e.detail.percent == 100)
111
+ {
112
  jQuery(".xcloner-restore .steps.active .progress > div").removeClass('indeterminate').addClass('determinate').css("width", e.detail.percent+"%")
113
+ }
114
+ else
115
+ {
116
  jQuery(".xcloner-restore .steps.active .progress .determinate").css("width", e.detail.percent+"%")
117
+ }
118
  }
119
 
120
  }, false);
230
 
231
  if(this.resume.callback == "get_remote_mysqldump_files_callback")
232
  {
233
+ //console.log("do resume");
234
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
235
  this.resume = new Object();
236
  return;
292
 
293
  if(response.statusText.extracted_files)
294
  {
 
295
  document.dispatchEvent(new CustomEvent("remote_restore_update_files_list", {detail: {files: response.statusText.extracted_files}}));
296
  }
297
 
324
 
325
  if(this.resume.callback == "remote_restore_backup_file_callback")
326
  {
327
+ //console.log("do resume");
328
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
329
  this.resume = new Object();
330
  return;
337
 
338
  remote_restore_mysql_backup_file_callback(response, status, params = new Object())
339
  {
 
 
340
  if(!status)
341
  {
342
  this.start = response.statusText.start;
352
  params.query = "";
353
 
354
  var processed = parseInt(response.statusText.start)+parseInt(response.statusText.processed)
 
 
 
 
 
 
355
 
356
  if(!response.statusText.finished)
357
  {
415
 
416
  if(this.resume.callback == "remote_restore_mysql_backup_file_callback")
417
  {
418
+ //console.log("do resume mysql backup restore");
419
  this.do_ajax(this.resume.callback, this.resume.action, this.resume.params);
420
  this.resume = new Object();
421
  return;
579
  document.dispatchEvent(new CustomEvent("remote_restore_update_files_list", {detail: {files: ""}}));
580
  }
581
 
582
+ /** global: ajaxurl */
583
  do_ajax(callback, action="", params= new Object())
584
  {
585
  params.action = action
598
  }
599
 
600
  if(!this.restore_script_url)
601
+ {
602
  return false;
603
+ }
604
 
605
  var $this = this;
606
 
admin/js/xcloner-scheduler-class.js CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  jQuery(document).ready(function(){
2
 
3
  class Xcloner_Scheduler{
@@ -22,7 +26,9 @@ jQuery(document).ready(function(){
22
  data: { action : 'get_schedule_by_id', id: id},
23
  success: function(response){
24
  if(response.id == id)
25
- $this.create_modal(response)
 
 
26
  },
27
  dataType: 'json'
28
  });
@@ -55,9 +61,13 @@ jQuery(document).ready(function(){
55
  this.edit_modal.find("#schedule_id").text(response.id)
56
 
57
  if(response.status == 1)
 
58
  this.edit_modal.find("#status").attr("checked", "checked");
59
- else
 
 
60
  this.edit_modal.find("#status").removeAttr("checked");
 
61
 
62
  this.edit_modal.find("#schedule_id").text(response.id)
63
  this.edit_modal.find("#schedule_id_hidden").val(response.id)
@@ -100,18 +110,6 @@ jQuery(document).ready(function(){
100
 
101
  save_schedule(form, dataTable)
102
  {
103
- /*if(!this.IsJsonString(jQuery("#table_params").val()) )
104
- {
105
- //alert("Database field is not a valid json data!");
106
- //return false;
107
- }
108
-
109
- if(!this.IsJsonString(jQuery("#excluded_files").val()) )
110
- {
111
- alert("Exclude files field is not a valid json data!");
112
- return false;
113
- }*/
114
-
115
  var data = jQuery(form).serialize();
116
  var $this = this
117
 
@@ -166,36 +164,33 @@ jQuery(document).ready(function(){
166
  'selectNone'
167
  ],
168
  "language": {
169
- "emptyTable": "No schedules available"
 
 
 
 
170
  },
171
  columnDefs: [
172
  { targets: 'no-sort', orderable: false },
173
  { className: "hide-on-med-and-down", "targets": [ 3, 5 ] }
174
  ],
175
- language: {
176
- buttons: {
177
- selectAll: "Select all items",
178
- selectNone: "Select none"
179
- }
180
- },
181
  "ajax": ajaxurl+"?action=get_scheduler_list",
182
  "fnDrawCallback": function( oSettings ) {
183
- //jQuery("#scheduled_backups").find(".edit").each(function(){
184
- jQuery(this).off("click", ".edit").on("click", ".edit",function(){
185
- var hash = jQuery(this).attr('href');
186
- var id = hash.substr(1)
187
- var data = xcloner_scheduler.get_schedule_by_id(id);
188
- })
189
- //})
190
-
191
- //jQuery("#scheduled_backups").find(".delete").each(function(){
192
- jQuery(this).off("click", ".delete").on("click", ".delete", function(){
193
- var hash = jQuery(this).attr('href');
194
- var id = hash.substr(1)
195
- if(confirm('Are you sure you want to delete it?'))
196
- var data = xcloner_scheduler.delete_schedule_by_id(id, (this), dataTable);
197
- })
198
- //})
199
 
200
  jQuery("span.shorten_string").each(function(){
201
  doShortText(jQuery(this));
1
+ /** global: ajaxurl */
2
+ /** global: Materialize */
3
+ /** global: dataTable */
4
+
5
  jQuery(document).ready(function(){
6
 
7
  class Xcloner_Scheduler{
26
  data: { action : 'get_schedule_by_id', id: id},
27
  success: function(response){
28
  if(response.id == id)
29
+ {
30
+ $this.create_modal(response)
31
+ }
32
  },
33
  dataType: 'json'
34
  });
61
  this.edit_modal.find("#schedule_id").text(response.id)
62
 
63
  if(response.status == 1)
64
+ {
65
  this.edit_modal.find("#status").attr("checked", "checked");
66
+ }
67
+ else
68
+ {
69
  this.edit_modal.find("#status").removeAttr("checked");
70
+ }
71
 
72
  this.edit_modal.find("#schedule_id").text(response.id)
73
  this.edit_modal.find("#schedule_id_hidden").val(response.id)
110
 
111
  save_schedule(form, dataTable)
112
  {
 
 
 
 
 
 
 
 
 
 
 
 
113
  var data = jQuery(form).serialize();
114
  var $this = this
115
 
164
  'selectNone'
165
  ],
166
  "language": {
167
+ "emptyTable": "No schedules available",
168
+ "buttons": {
169
+ selectAll: "Select all items",
170
+ selectNone: "Select none"
171
+ }
172
  },
173
  columnDefs: [
174
  { targets: 'no-sort', orderable: false },
175
  { className: "hide-on-med-and-down", "targets": [ 3, 5 ] }
176
  ],
 
 
 
 
 
 
177
  "ajax": ajaxurl+"?action=get_scheduler_list",
178
  "fnDrawCallback": function( oSettings ) {
179
+
180
+ jQuery(this).off("click", ".edit").on("click", ".edit",function(){
181
+ var hash = jQuery(this).attr('href');
182
+ var id = hash.substr(1)
183
+ var data = xcloner_scheduler.get_schedule_by_id(id);
184
+ })
185
+
186
+ jQuery(this).off("click", ".delete").on("click", ".delete", function(){
187
+ var hash = jQuery(this).attr('href');
188
+ var id = hash.substr(1)
189
+ if(confirm('Are you sure you want to delete it?'))
190
+ {
191
+ var data = xcloner_scheduler.delete_schedule_by_id(id, (this), dataTable);
192
+ }
193
+ })
 
194
 
195
  jQuery("span.shorten_string").each(function(){
196
  doShortText(jQuery(this));
admin/partials/xcloner_console_page.php CHANGED
@@ -1,26 +1,8 @@
1
  <?php
2
 
3
- $xcloner_settings = new Xcloner_Settings();
4
- $logger = new Xcloner_Logger();
5
-
6
-
7
- $xcloner_scheduler = new Xcloner_Scheduler();
8
- //$xcloner_scheduler->xcloner_scheduler_callback(90);
9
-
10
- //$logger_content = $logger->getLastDebugLines();
11
-
12
- $xcloner_file_transfer = new Xcloner_File_Transfer();
13
-
14
- $xcloner_file_transfer->set_target("http://thinkovi.com/xcloner/xcloner_restore.php");
15
- //$xcloner_file_transfer->set_target("http://localhost/xcloner/xcloner_restore.php");
16
-
17
- $start = 0 ;
18
- while( $start = $xcloner_file_transfer->transfer_file("backup_localhost-2017-02-07_13-29-sql-ac9b0.tgz", $start))
19
- {
20
- //echo $start."--";
21
- }
22
-
23
- echo "done";
24
  ?>
25
  <div class="col s12 ">
26
  <div>
1
  <?php
2
 
3
+ $xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
4
+ $logger = $this->get_xcloner_container()->get_xcloner_logger();
5
+ $logger_content = $logger->getLastDebugLines();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  ?>
7
  <div class="col s12 ">
8
  <div>
admin/partials/xcloner_generate_backups_page.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
- $xcloner_settings = new Xcloner_Settings();
3
- $xcloner_remote_storage = new Xcloner_Remote_Storage();
4
  $available_storages = $xcloner_remote_storage->get_available_storages();
5
  $tab = 1;
6
  ?>
@@ -229,25 +229,25 @@ $tab = 1;
229
  </div>
230
  -->
231
  <div class="row">
232
- <div class="input-field inline col s12 m10 l6">
233
  <input type="text" id="schedule_name" class="" name="schedule_name" required>
234
  <label for="schedule_name"><?php echo __('Schedule Name', 'xcloner-backup-and-restore') ?></label>
235
  </div>
236
  </div>
237
 
238
  <div class="row">
239
- <div class="input-field inline col s12 m6 l4">
240
  <input type="datetime-local" id="datepicker" class="datepicker" name="schedule_start_date" >
241
  <label for="datepicker"><?php echo __('Schedule Backup To Start On:','xcloner-backup-and-restore')?></label>
242
  </div>
243
- <div class="input-field inline col s12 m4 l2">
244
  <input id="timepicker_ampm_dark" class="timepicker" type="time" name="schedule_start_time">
245
  <label for="timepicker_ampm_dark"><?php echo __('At:','xcloner-backup-and-restore')?></label>
246
  </div>
247
  </div>
248
 
249
  <div class="row">
250
- <div class="input-field col s12 m10 l6">
251
  <select name="schedule_frequency" id="schedule_frequency" class="validate" required>
252
  <option value="" disabled selected><?php echo __('please select', 'xcloner-backup-and-restore') ?></option>
253
  <option value="single"><?php echo __("Don't Repeat",'xcloner-backup-and-restore')?></option>
@@ -262,7 +262,7 @@ $tab = 1;
262
 
263
  <?php if(sizeof($available_storages)):?>
264
  <div class="row">
265
- <div class="input-field col s12 m10 l6">
266
  <select name="schedule_storage" id="schedule_storage" class="validate">
267
  <option value="" selected><?php echo __('none', 'xcloner-backup-and-restore') ?></option>
268
  <?php foreach($available_storages as $storage=>$text):?>
@@ -274,7 +274,7 @@ $tab = 1;
274
  </div>
275
  <?php endif?>
276
  <div class="row">
277
- <div class="col s12 m10 l6">
278
  <button class="right btn waves-effect waves-light submit_schedule" type="submit" name="action"><?php echo __("Submit" ,'xcloner-backup-and-restore')?>
279
  <i class="material-icons right">send</i>
280
  </button>
1
  <?php
2
+ $xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
3
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
4
  $available_storages = $xcloner_remote_storage->get_available_storages();
5
  $tab = 1;
6
  ?>
229
  </div>
230
  -->
231
  <div class="row">
232
+ <div class="input-field inline col s12 m12 l7">
233
  <input type="text" id="schedule_name" class="" name="schedule_name" required>
234
  <label for="schedule_name"><?php echo __('Schedule Name', 'xcloner-backup-and-restore') ?></label>
235
  </div>
236
  </div>
237
 
238
  <div class="row">
239
+ <div class="input-field inline col s12 m8 l4">
240
  <input type="datetime-local" id="datepicker" class="datepicker" name="schedule_start_date" >
241
  <label for="datepicker"><?php echo __('Schedule Backup To Start On:','xcloner-backup-and-restore')?></label>
242
  </div>
243
+ <div class="input-field inline col s12 m4 l3">
244
  <input id="timepicker_ampm_dark" class="timepicker" type="time" name="schedule_start_time">
245
  <label for="timepicker_ampm_dark"><?php echo __('At:','xcloner-backup-and-restore')?></label>
246
  </div>
247
  </div>
248
 
249
  <div class="row">
250
+ <div class="input-field col s12 l7">
251
  <select name="schedule_frequency" id="schedule_frequency" class="validate" required>
252
  <option value="" disabled selected><?php echo __('please select', 'xcloner-backup-and-restore') ?></option>
253
  <option value="single"><?php echo __("Don't Repeat",'xcloner-backup-and-restore')?></option>
262
 
263
  <?php if(sizeof($available_storages)):?>
264
  <div class="row">
265
+ <div class="input-field col s12 m12 l7">
266
  <select name="schedule_storage" id="schedule_storage" class="validate">
267
  <option value="" selected><?php echo __('none', 'xcloner-backup-and-restore') ?></option>
268
  <?php foreach($available_storages as $storage=>$text):?>
274
  </div>
275
  <?php endif?>
276
  <div class="row">
277
+ <div class="col s12 l7">
278
  <button class="right btn waves-effect waves-light submit_schedule" type="submit" name="action"><?php echo __("Submit" ,'xcloner-backup-and-restore')?>
279
  <i class="material-icons right">send</i>
280
  </button>
admin/partials/xcloner_init_page.php CHANGED
@@ -12,11 +12,11 @@
12
  * @subpackage Xcloner/admin/partials
13
  */
14
 
15
- $requirements = new XCloner_Requirements();
16
- $xcloner_settings = new Xcloner_Settings();
17
- $xcloner_file_system = new Xcloner_File_System();
18
- $logger = new Xcloner_Logger();
19
- $xcloner_scheduler = new Xcloner_Scheduler();
20
 
21
  $logger_content = $logger->getLastDebugLines();
22
 
@@ -97,7 +97,7 @@ if($requirements->check_backup_ready_status())
97
  <div class="item">
98
  <div class="title"><?php echo __("Backup Date", 'xcloner-backup-and-restore')?>:</div>
99
  <?php
100
- echo date($date_format." ".$time_format, $latest_backup['timestamp'])
101
  ?>
102
  </div>
103
  <?php else:?>
@@ -122,7 +122,7 @@ if($requirements->check_backup_ready_status())
122
 
123
  if(is_array($list))
124
  {
125
- $xcloner_file_system->sort_by($list, "next_run_time","desc");
126
  }
127
 
128
  if(isset($list[0]))
@@ -150,7 +150,7 @@ if($requirements->check_backup_ready_status())
150
  <li class="active">
151
  <div class="collapsible-header active">
152
  <i class="material-icons">bug_report</i><?php echo __('XCloner Debugger', 'xcloner-backup-and-restore')?>
153
- <span class="right"><?php echo basename($logger->get_main_logger_url())?></span>
154
  </div>
155
  <div class="collapsible-body">
156
  <div class="console" id="xcloner-console"><?php if($logger_content) echo implode("<br />\n", array_reverse($logger_content)); ?></div>
12
  * @subpackage Xcloner/admin/partials
13
  */
14
 
15
+ $requirements = $this->get_xcloner_container()->get_xcloner_requirements();
16
+ $xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
17
+ $xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
18
+ $logger = $this->get_xcloner_container()->get_xcloner_logger();
19
+ $xcloner_scheduler = $this->get_xcloner_container()->get_xcloner_scheduler();
20
 
21
  $logger_content = $logger->getLastDebugLines();
22
 
97
  <div class="item">
98
  <div class="title"><?php echo __("Backup Date", 'xcloner-backup-and-restore')?>:</div>
99
  <?php
100
+ echo date($date_format." ".$time_format, $latest_backup['timestamp']+(get_option( 'gmt_offset' ) * HOUR_IN_SECONDS))
101
  ?>
102
  </div>
103
  <?php else:?>
122
 
123
  if(is_array($list))
124
  {
125
+ $xcloner_file_system->sort_by($list, "next_run_time","asc");
126
  }
127
 
128
  if(isset($list[0]))
150
  <li class="active">
151
  <div class="collapsible-header active">
152
  <i class="material-icons">bug_report</i><?php echo __('XCloner Debugger', 'xcloner-backup-and-restore')?>
153
+ <span class="right"><a href="#<?php echo $logger_basename = basename($logger->get_main_logger_url())?>" class="download-logger"><?php echo $logger_basename?></a></span>
154
  </div>
155
  <div class="collapsible-body">
156
  <div class="console" id="xcloner-console"><?php if($logger_content) echo implode("<br />\n", array_reverse($logger_content)); ?></div>
admin/partials/xcloner_manage_backups_page.php CHANGED
@@ -1,14 +1,42 @@
1
  <?php
2
 
3
- $xcloner_file_system = new Xcloner_File_System();
 
 
 
4
 
5
- $backup_list = $xcloner_file_system->get_backup_archives_list();
 
 
 
 
 
6
 
7
- $xcloner_remote_storage = new Xcloner_Remote_Storage();
8
  $available_storages = $xcloner_remote_storage->get_available_storages();
 
 
9
  ?>
10
 
11
- <h1><?= esc_html(get_admin_page_title()); ?></h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  <table id="manage_backups">
14
  <thead>
@@ -33,17 +61,32 @@ $available_storages = $xcloner_remote_storage->get_available_storages();
33
  <?php
34
  $i = 0;
35
  foreach($backup_list as $file_info):?>
 
 
 
 
 
 
 
 
 
 
 
 
36
  <?php if(!isset($file_info['parent'])):?>
37
 
38
  <tr>
39
  <td class="checkbox">
40
  <p>
41
- <input name="backup[]" value="<?php echo $file_info['path']?>" type="checkbox" id="checkbox_<?php echo ++$i?>">
42
  <label for="checkbox_<?php echo $i?>">&nbsp;</label>
43
  </p>
44
  </td>
45
  <td>
46
- <?php echo $file_info['path']?>
 
 
 
47
  <?php
48
  if(isset($file_info['childs']) and is_array($file_info['childs'])):
49
  ?>
@@ -51,25 +94,47 @@ foreach($backup_list as $file_info):?>
51
  <a href="#" title="collapse" class="expand-multipart remove"><i class="material-icons">remove</i></a>
52
  <ul class="multipart">
53
  <?php foreach($file_info['childs'] as $child):?>
54
- <?php #$download_file .= "|".$child[0];?>
55
  <li>
56
  <?php echo $child[0]?> (<?php echo size_format($child[2])?>)
57
- <a href="#<?php echo $child[0];?>" class="download" title="Download Backup"><i class="material-icons">file_download</i></a>
58
- <a href="#<?php echo $child[0]?>" class="list-backup-content" title="<?php echo __('List Backup Content','xcloner-backup-and-restore')?>"><i class="material-icons">folder_open</i></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </li>
60
  <?php endforeach;?>
61
  </ul>
62
  <?php endif;?>
63
  </td>
64
- <td><?php echo date("d M, Y H:i", $file_info['timestamp'])?></td>
65
  <td><?php echo size_format($file_info['size'])?></td>
66
  <td>
67
- <a href="#<?php echo $file_info['path'];?>" class="download" title="<?php echo __('Download Backup','xcloner-backup-and-restore')?>"><i class="material-icons">file_download</i></a>
68
- <?php if(sizeof($available_storages)):?>
69
- <a href="#<?php echo $file_info['path']?>" class="cloud-upload" title="<?php echo __('Send Backup To Remote Storage','xcloner-backup-and-restore')?>"><i class="material-icons">cloud_upload</i></a>
70
- <?php endif?>
71
- <a href="#<?php echo $file_info['path']?>" class="list-backup-content" title="<?php echo __('List Backup Content','xcloner-backup-and-restore')?>"><i class="material-icons">folder_open</i></a>
72
- <a href="#<?php echo $file_info['path']?>" class="delete" title="<?php echo __('Delete Backup','xcloner-backup-and-restore')?>"><i class="material-icons">delete</i></a>
 
 
 
 
 
 
 
 
73
  </td>
74
 
75
  </tr>
@@ -83,7 +148,6 @@ foreach($backup_list as $file_info):?>
83
  <a class="waves-effect waves-light btn delete-all"><i class="material-icons left">delete</i><?php echo __("Delete",'xcloner-backup-and-restore')?></a>
84
 
85
  <!-- List Backup Content Modal-->
86
-
87
  <div id="backup_cotent_modal" class="modal">
88
  <div class="modal-content">
89
  <h4><?php echo sprintf(__("Listing Backup Content ",'xcloner-backup-and-restore'), "")?></h4>
@@ -96,6 +160,21 @@ foreach($backup_list as $file_info):?>
96
  </div>
97
  </div>
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  <!-- Remote Storage Modal Structure -->
100
  <div id="remote_storage_modal" class="modal">
101
  <form method="POST" class="remote-storage-form">
1
  <?php
2
 
3
+ $xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
4
+ $xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
5
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
6
+ $storage_selection = "";
7
 
8
+ if(isset($_GET['storage_selection']) and $_GET['storage_selection'])
9
+ {
10
+ $storage_selection = $xcloner_sanitization->sanitize_input_as_string($_GET['storage_selection']);
11
+ }
12
+
13
+ $backup_list = $xcloner_file_system->get_backup_archives_list($storage_selection);
14
 
 
15
  $available_storages = $xcloner_remote_storage->get_available_storages();
16
+
17
+
18
  ?>
19
 
20
+ <div class="row">
21
+ <div class="col s12 m6 l9">
22
+ <h1><?= esc_html(get_admin_page_title()); ?></h1>
23
+ </div>
24
+ <?php if(sizeof($available_storages)):?>
25
+ <div class="col s12 m6 l3 remote-storage-selection">
26
+ <select name="storage_selection" id="storage_selection" class="validate" required >
27
+
28
+ <?php if($storage_selection):?>
29
+ <option value="" selected><?php echo __('Change To Local Storage...', 'xcloner-backup-and-restore') ?></option>
30
+ <?php else: ?>
31
+ <option value="" selected><?php echo __('Change To Remote Storage...', 'xcloner-backup-and-restore') ?></option>
32
+ <?php endif;?>
33
+
34
+ <?php foreach($available_storages as $storage=>$text):?>
35
+ <option value="<?php echo $storage?>"<?php if($storage == $storage_selection) echo "selected"?>><?php echo $text?></option>
36
+ <?php endforeach?>
37
+ </select>
38
+ <?php endif?>
39
+ </div>
40
 
41
  <table id="manage_backups">
42
  <thead>
61
  <?php
62
  $i = 0;
63
  foreach($backup_list as $file_info):?>
64
+ <?php
65
+ if($storage_selection == "gdrive")
66
+ $file_info['path'] = $file_info['filename'].".".$file_info['extension'];
67
+ $file_exists_on_local_storage = true;
68
+
69
+ if($storage_selection)
70
+ {
71
+ if(!$xcloner_file_system->get_storage_filesystem()->has($file_info['path']))
72
+ $file_exists_on_local_storage = false;
73
+ }
74
+
75
+ ?>
76
  <?php if(!isset($file_info['parent'])):?>
77
 
78
  <tr>
79
  <td class="checkbox">
80
  <p>
81
+ <input name="backup[]" value="<?php echo $file_info['basename']?>" type="checkbox" id="checkbox_<?php echo ++$i?>">
82
  <label for="checkbox_<?php echo $i?>">&nbsp;</label>
83
  </p>
84
  </td>
85
  <td>
86
+ <span class=""><?php echo $file_info['path']?></span>
87
+ <?php if(!$file_exists_on_local_storage): ?>
88
+ <a href="#" title="<?php echo __("File does not exists on local storage","xcloner-backup-and-restore")?>"><i class="material-icons backup_warning">warning</i></a>
89
+ <?php endif?>
90
  <?php
91
  if(isset($file_info['childs']) and is_array($file_info['childs'])):
92
  ?>
94
  <a href="#" title="collapse" class="expand-multipart remove"><i class="material-icons">remove</i></a>
95
  <ul class="multipart">
96
  <?php foreach($file_info['childs'] as $child):?>
 
97
  <li>
98
  <?php echo $child[0]?> (<?php echo size_format($child[2])?>)
99
+ <?php
100
+ $child_exists_on_local_storage = true;
101
+ if($storage_selection)
102
+ {
103
+ if(!$xcloner_file_system->get_storage_filesystem()->has($child[0]))
104
+ $child_exists_on_local_storage = false;
105
+ }
106
+ ?>
107
+ <?php if(!$child_exists_on_local_storage): ?>
108
+ <a href="#" title="<?php echo __("File does not exists on local storage","xcloner-backup-and-restore")?>"><i class="material-icons backup_warning">warning</i></a>
109
+ <?php endif?>
110
+ <?php if(!$storage_selection) :?>
111
+ <a href="#<?php echo $child[0];?>" class="download" title="Download Backup"><i class="material-icons">file_download</i></a>
112
+ <a href="#<?php echo $child[0]?>" class="list-backup-content" title="<?php echo __('List Backup Content','xcloner-backup-and-restore')?>"><i class="material-icons">folder_open</i></a>
113
+ <?php elseif($storage_selection != "gdrive" && !$xcloner_file_system->get_storage_filesystem()->has($child[0])): ?>
114
+ <a href="#<?php echo $child[0]?>" class="copy-remote-to-local" title="<?php echo __('Push Backup To Local Storage','xcloner-backup-and-restore')?>"><i class="material-icons">file_upload</i></a>
115
+ <?php endif?>
116
  </li>
117
  <?php endforeach;?>
118
  </ul>
119
  <?php endif;?>
120
  </td>
121
+ <td><?php if(isset($file_info['timestamp'])) echo date("d M, Y H:i", $file_info['timestamp'])?></td>
122
  <td><?php echo size_format($file_info['size'])?></td>
123
  <td>
124
+ <?php if(!$storage_selection):?>
125
+ <a href="#<?php echo $file_info['basename'];?>" class="download" title="<?php echo __('Download Backup','xcloner-backup-and-restore')?>"><i class="material-icons">file_download</i></a>
126
+
127
+ <?php if(sizeof($available_storages)):?>
128
+ <a href="#<?php echo $file_info['basename']?>" class="cloud-upload" title="<?php echo __('Send Backup To Remote Storage','xcloner-backup-and-restore')?>"><i class="material-icons">cloud_upload</i></a>
129
+ <?php endif?>
130
+ <a href="#<?php echo $file_info['basename']?>" class="list-backup-content" title="<?php echo __('List Backup Content','xcloner-backup-and-restore')?>"><i class="material-icons">folder_open</i></a>
131
+ <?php endif;?>
132
+
133
+ <a href="#<?php echo $file_info['basename']?>" class="delete" title="<?php echo __('Delete Backup','xcloner-backup-and-restore')?>"><i class="material-icons">delete</i></a>
134
+ <?php if($storage_selection and !$file_exists_on_local_storage):?>
135
+ <a href="#<?php echo $file_info['basename'];?>" class="copy-remote-to-local" title="<?php echo __('Push Backup To Local Storage','xcloner-backup-and-restore')?>"><i class="material-icons">file_upload</i></a>
136
+ <?php endif?>
137
+
138
  </td>
139
 
140
  </tr>
148
  <a class="waves-effect waves-light btn delete-all"><i class="material-icons left">delete</i><?php echo __("Delete",'xcloner-backup-and-restore')?></a>
149
 
150
  <!-- List Backup Content Modal-->
 
151
  <div id="backup_cotent_modal" class="modal">
152
  <div class="modal-content">
153
  <h4><?php echo sprintf(__("Listing Backup Content ",'xcloner-backup-and-restore'), "")?></h4>
160
  </div>
161
  </div>
162
 
163
+ <!-- Local Transfer Modal-->
164
+ <div id="local_storage_upload_modal" class="modal">
165
+ <div class="modal-content">
166
+ <h4><?php echo sprintf(__("Transfer Remote Backup To Local Storage",'xcloner-backup-and-restore'), "")?></h4>
167
+ <h5 class="backup-name"></h5>
168
+
169
+ <div class="row status">
170
+ <div class="progress">
171
+ <div class="indeterminate"></div>
172
+ </div>
173
+ <?php echo __("Uploading backup to the local storage filesystem...",'xcloner-backup-and-restore')?> <span class="status-text"></span>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
  <!-- Remote Storage Modal Structure -->
179
  <div id="remote_storage_modal" class="modal">
180
  <form method="POST" class="remote-storage-form">
admin/partials/xcloner_remote_storage_page.php CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  <h1><?= esc_html(get_admin_page_title()); ?></h1>
2
 
3
  <form class="remote-storage-form" method="POST">
@@ -10,7 +20,7 @@
10
  <!-- FTP STORAGE-->
11
  <li id="ftp">
12
  <div class="collapsible-header">
13
- <i class="material-icons">computer</i><?php echo __("Ftp Storage",'xcloner-backup-and-restore')?>
14
  <div class="right">
15
  <div class="switch">
16
  <label>
@@ -171,11 +181,11 @@
171
 
172
  <div class="row">
173
  <div class="col s12 m3 label">
174
- <label for="sftp_private_key"><?php echo __("SFTP Private Key",'xcloner-backup-and-restore')?></label>
175
  </div>
176
  <div class=" col s12 m6">
177
- <input placeholder="<?php echo __("SFTP Private Key",'xcloner-backup-and-restore')?>" id="sftp_private_key" type="text" name="xcloner_sftp_private_key" class="validate" value="<?php echo get_option("xcloner_sftp_private_key")?>">
178
- </div>
179
  </div>
180
 
181
  <div class="row">
@@ -272,7 +282,18 @@
272
  <label for="aws_region"><?php echo __("AWS Region",'xcloner-backup-and-restore')?></label>
273
  </div>
274
  <div class=" col s12 m6">
275
- <input placeholder="<?php echo __("AWS Region",'xcloner-backup-and-restore')?>" id="aws_region" type="text" name="xcloner_aws_region" class="validate" value="<?php echo get_option("xcloner_aws_region")?>" autocomplete="off" >
 
 
 
 
 
 
 
 
 
 
 
276
  </div>
277
  </div>
278
 
@@ -553,15 +574,224 @@
553
  </div>
554
  </li>
555
 
556
- <!--<li>
557
- <div class="collapsible-header"><i class="material-icons">cloud</i>Amazon S3 Storage</div>
558
- <div class="collapsible-body"><span>Lorem ipsum dolor sit amet.</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  </li>
560
- <li>
561
- <div class="collapsible-header"><i class="material-icons">cloud</i>Dropbox Storage</div>
562
- <div class="collapsible-body"><span>Lorem ipsum dolor sit amet.</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  </li>
564
- -->
 
 
565
  </ul>
566
  </div>
567
  </div>
@@ -582,6 +812,16 @@ jQuery(document).ready(function(){
582
  window.location.hash = "#"+tag;
583
  })
584
 
 
 
 
 
 
 
 
 
 
 
585
  if(location.hash)
586
  jQuery(location.hash+" div.collapsible-header").addClass("active");
587
 
1
+ <?php
2
+ $remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
3
+
4
+ $gdrive_auth_url = "";
5
+
6
+ if(method_exists($remote_storage, "get_gdrive_auth_url"))
7
+ $gdrive_auth_url = $remote_storage->get_gdrive_auth_url();
8
+
9
+ $gdrive_construct = $remote_storage->gdrive_construct();
10
+ ?>
11
  <h1><?= esc_html(get_admin_page_title()); ?></h1>
12
 
13
  <form class="remote-storage-form" method="POST">
20
  <!-- FTP STORAGE-->
21
  <li id="ftp">
22
  <div class="collapsible-header">
23
+ <i class="material-icons">computer</i><?php echo __("FTP Storage",'xcloner-backup-and-restore')?>
24
  <div class="right">
25
  <div class="switch">
26
  <label>
181
 
182
  <div class="row">
183
  <div class="col s12 m3 label">
184
+ <label for="sftp_private_key"><?php echo __("SFTP Private Key(RSA)",'xcloner-backup-and-restore')?></label>
185
  </div>
186
  <div class=" col s12 m6">
187
+ <textarea rows="5" placeholder="<?php echo __("Local Server Path or Contents of the SFTP Private Key RSA File",'xcloner-backup-and-restore')?>" id="sftp_private_key" type="text" name="xcloner_sftp_private_key" class="validate" value=""><?php echo get_option("xcloner_sftp_private_key")?></textarea>
188
+ </div>
189
  </div>
190
 
191
  <div class="row">
282
  <label for="aws_region"><?php echo __("AWS Region",'xcloner-backup-and-restore')?></label>
283
  </div>
284
  <div class=" col s12 m6">
285
+ <select placeholder="<?php echo __("example: us-east-1",'xcloner-backup-and-restore')?>" id="aws_region" type="text" name="xcloner_aws_region" class="validate" value="<?php echo get_option("xcloner_aws_region")?>" autocomplete="off" >
286
+ <option readonly value=""><?php echo __("Please Select AWS Region")?></option>
287
+ <?php
288
+ $aws_regions = $remote_storage->get_aws_regions();
289
+
290
+ foreach($aws_regions as $key=>$region){
291
+ ?>
292
+ <option value="<?php echo $key?>" <?php echo ($key == get_option('xcloner_aws_region')?"selected":"")?>><?php echo $region?> = <?php echo $key?></option>
293
+ <?php
294
+ }
295
+ ?>
296
+ </select>
297
  </div>
298
  </div>
299
 
574
  </div>
575
  </li>
576
 
577
+ <!-- WEBDAV STORAGE-->
578
+ <li id="webdav">
579
+ <div class="collapsible-header">
580
+ <i class="material-icons">computer</i><?php echo __("WebDAV Storage",'xcloner-backup-and-restore')?>
581
+ <div class="right">
582
+ <div class="switch">
583
+ <label>
584
+ Off
585
+ <input type="checkbox" name="xcloner_webdav_enable" class="status" value="1" <?php if(get_option("xcloner_webdav_enable")) echo "checked"?> \>
586
+ <span class="lever"></span>
587
+ On
588
+ </label>
589
+ </div>
590
+ </div>
591
+ </div>
592
+ <div class="collapsible-body">
593
+
594
+ <div class="row">
595
+ <div class="col s12 m3 label">
596
+ &nbsp;
597
+ </div>
598
+ <div class=" col s12 m6">
599
+ <p>
600
+ <?php //echo sprintf(__('Visit %s and get your Account Id and Application Key.','xcloner-backup-and-restore'), '<a href="https://secure.backblaze.com/b2_buckets.htm" target="_blank">https://secure.backblaze.com/b2_buckets.htm</a>')?>
601
+ </p>
602
+ </div>
603
+ </div>
604
+
605
+ <div class="row">
606
+ <div class="col s12 m3 label">
607
+ <label for="webdav_url"><?php echo __("WebDAV Base Url",'xcloner-backup-and-restore')?></label>
608
+ </div>
609
+ <div class=" col s12 m6">
610
+ <input placeholder="<?php echo __("WebDAV Base Url",'xcloner-backup-and-restore')?>" id="webdav_url" type="text" name="xcloner_webdav_url" class="validate" value="<?php echo get_option("xcloner_webdav_url")?>" autocomplete="off" >
611
+ </div>
612
+ </div>
613
+
614
+ <div class="row">
615
+ <div class="col s12 m3 label">
616
+ <label for="webdav_username"><?php echo __("WebDAV Username",'xcloner-backup-and-restore')?></label>
617
+ </div>
618
+ <div class=" col s12 m6">
619
+ <input placeholder="<?php echo __("WebDAV Username",'xcloner-backup-and-restore')?>" id="webdav_username" type="text" name="xcloner_webdav_username" class="validate" value="<?php echo get_option("xcloner_webdav_username")?>" autocomplete="off" >
620
+ </div>
621
+ </div>
622
+
623
+ <div class="row">
624
+ <div class="col s12 m3 label">
625
+ <label for="webdav_password"><?php echo __("WebDAV Password",'xcloner-backup-and-restore')?></label>
626
+ </div>
627
+ <div class=" col s12 m6">
628
+ <input placeholder="<?php echo __("WebDAV Password",'xcloner-backup-and-restore')?>" id="webdav_password" type="password" name="xcloner_webdav_password" class="validate" value="<?php echo get_option("xcloner_webdav_password")?>" autocomplete="off" >
629
+ </div>
630
+ </div>
631
+
632
+ <div class="row">
633
+ <div class="col s12 m3 label">
634
+ <label for="webdav_target_folder"><?php echo __("WebDAV Target Folder",'xcloner-backup-and-restore')?></label>
635
+ </div>
636
+ <div class=" col s12 m6">
637
+ <input placeholder="<?php echo __("WebDAV Target Folder",'xcloner-backup-and-restore')?>" id="webdav_target_folder" type="text" name="xcloner_webdav_target_folder" class="validate" value="<?php echo get_option("xcloner_webdav_target_folder")?>" autocomplete="off" >
638
+ </div>
639
+ </div>
640
+
641
+ <div class="row">
642
+ <div class="col s12 m3 label">
643
+ <label for="webdav_cleanup_days"><?php echo __("WebDAV Cleanup (days)",'xcloner-backup-and-restore')?></label>
644
+ </div>
645
+ <div class=" col s12 m6">
646
+ <input placeholder="<?php echo __("how many days to keep the backups for",'xcloner-backup-and-restore')?>" id="webdav_cleanup_days" type="text" name="xcloner_webdav_cleanup_days" class="validate" value="<?php echo get_option("xcloner_webdav_cleanup_days")?>">
647
+ </div>
648
+ </div>
649
+
650
+ <div class="row">
651
+ <div class="col s6 m4">
652
+ <button class="btn waves-effect waves-light" type="submit" name="action" id="action" value="webdav"><?php echo __("Save Settings",'xcloner-backup-and-restore')?>
653
+ <i class="material-icons right">save</i>
654
+ </button>
655
+ </div>
656
+ <div class="col s6 m4">
657
+ <button class="btn waves-effect waves-light orange" type="submit" name="action" id="action" value="webdav" onclick="jQuery('#connection_check').val('1')"><?php echo __("Verify",'xcloner-backup-and-restore')?>
658
+ <i class="material-icons right">import_export</i>
659
+ </button>
660
+ </div>
661
+ </div>
662
+
663
+ </div>
664
  </li>
665
+
666
+ <!-- Google DRIVE STORAGE-->
667
+ <li id="gdrive">
668
+ <div class="collapsible-header">
669
+ <i class="material-icons">computer</i><?php echo __("Google Drive Storage",'xcloner-backup-and-restore')?>
670
+ <?php if($gdrive_construct):?>
671
+ <div class="right">
672
+ <div class="switch">
673
+ <label>
674
+ Off
675
+ <input type="checkbox" name="xcloner_gdrive_enable" class="status" value="1" <?php if(get_option("xcloner_gdrive_enable")) echo "checked"?> \>
676
+ <span class="lever"></span>
677
+ On
678
+ </label>
679
+ </div>
680
+ </div>
681
+ <?php endif?>
682
+ </div>
683
+ <div class="collapsible-body">
684
+
685
+ <?php if($gdrive_construct) : ?>
686
+
687
+ <div class="row">
688
+ <div class="col s12 m3 label">
689
+ &nbsp;
690
+ </div>
691
+ <div class=" col s12 m9">
692
+ <p>
693
+ <?php echo sprintf(__('Visit %s to create a new application and get your Client ID and Client Secret.','xcloner-backup-and-restore'), '<a href="https://console.developers.google.com" target="_blank">https://console.developers.google.com</a>')?>
694
+ <a href="https://youtu.be/YXUVPUVgG8k" target="_blank" class="btn-floating tooltipped btn-small" data-position="right" data-delay="50" data-html="true"
695
+ data-tooltip="<?php echo sprintf(__('Click here to view a short video explaining how to create the Client ID and Client Secret as well as connecting XCloner with the Google Drive API %s','xcloner-backup-and-restore'),"<br />https://youtu.be/YXUVPUVgG8k")?>" data-tooltip-id="92c95730-94e9-7b59-bd52-14adc30d5e3e"><i class="material-icons">help_outline</i></a>
696
+ </p>
697
+ </div>
698
+ </div>
699
+
700
+ <div class="row">
701
+ <div class="col s12 m3 label">
702
+ <label for="gdrive_client_id"><?php echo __("Client ID",'xcloner-backup-and-restore')?></label>
703
+ </div>
704
+ <div class=" col s12 m6">
705
+ <input placeholder="<?php echo __("Google Client ID",'xcloner-backup-and-restore')?>" id="gdrive_client_id" type="text" name="xcloner_gdrive_client_id" class="validate" value="<?php echo get_option("xcloner_gdrive_client_id")?>">
706
+ </div>
707
+ </div>
708
+
709
+ <div class="row">
710
+ <div class="col s12 m3 label">
711
+ <label for="gdrive_client_secret"><?php echo __("Client Secret",'xcloner-backup-and-restore')?></label>
712
+ </div>
713
+ <div class=" col s12 m6">
714
+ <input placeholder="<?php echo __("Google Client Secret",'xcloner-backup-and-restore')?>" id="gdrive_client_secret" type="text" name="xcloner_gdrive_client_secret" class="validate" value="<?php echo get_option("xcloner_gdrive_client_secret")?>">
715
+ </div>
716
+ </div>
717
+
718
+
719
+ <div class="row">
720
+ <div class="col s12 m3 label">
721
+ &nbsp;
722
+ </div>
723
+ <div class=" col s12 m6">
724
+ <a class="btn" target="_blank" id="gdrive_authorization_click" onclick="jQuery('#authentification_code').show()" href="<?php echo $gdrive_auth_url?>"><?php echo sprintf(__('Authorize Google Drive','xcloner-backup-and-restore'))?></a>
725
+ <input type="text" name="authentification_code" id="authentification_code" placeholder="<?php echo __("Paste Authorization Code Here","xcloner-backup-and-restore")?>">
726
+ </div>
727
+ </div>
728
+
729
+ <div class="row">
730
+ <div class="col s12 m3 label">
731
+ <label for="gdrive_target_folder"><?php echo __("Folder ID or Root Path",'xcloner-backup-and-restore')?>
732
+ <a class="btn-floating tooltipped btn-small" data-position="right" data-delay="50" data-html="true" \
733
+ data-tooltip="<?php echo __('Folder ID can be found by right clicking on the folder name and selecting \'Get shareable link\' menu, format https://drive.google.com/open?id={FOLDER_ID}<br />
734
+ If you supply a folder name, it has to exists in the drive root and start with / , example /backups.xcloner.com/','xcloner-backup-and-restore')?>" data-tooltip-id="92c95730-94e9-7b59-bd52-14adc30d5e3e"><i class="material-icons">help_outline</i></a>
735
+ </label>
736
+ </div>
737
+ <div class=" col s12 m6">
738
+ <input placeholder="<?php echo __("Target Folder ID or Root Path",'xcloner-backup-and-restore')?>" id="gdrive_target_folder" type="text" name="xcloner_gdrive_target_folder" class="validate" value="<?php echo get_option("xcloner_gdrive_target_folder")?>" autocomplete="off" >
739
+ </div>
740
+ </div>
741
+
742
+ <div class="row">
743
+ <div class="col s12 m3 label">
744
+ <label for="gdrive_cleanup_days"><?php echo __("Google Drive Cleanup (days)",'xcloner-backup-and-restore')?></label>
745
+ </div>
746
+ <div class=" col s12 m6">
747
+ <input placeholder="<?php echo __("how many days to keep the backups for",'xcloner-backup-and-restore')?>" id="gdrive_cleanup_days" type="text" name="xcloner_gdrive_cleanup_days" class="validate" value="<?php echo get_option("xcloner_gdrive_cleanup_days")?>">
748
+ </div>
749
+ </div>
750
+
751
+ <div class="row">
752
+ <div class="col s6 m4">
753
+ <button class="btn waves-effect waves-light" type="submit" name="action" id="action" value="gdrive"><?php echo __("Save Settings",'xcloner-backup-and-restore')?>
754
+ <i class="material-icons right">save</i>
755
+ </button>
756
+ </div>
757
+ <div class="col s6 m4">
758
+ <button class="btn waves-effect waves-light orange" type="submit" name="action" id="action" value="gdrive" onclick="jQuery('#connection_check').val('1')"><?php echo __("Verify",'xcloner-backup-and-restore')?>
759
+ <i class="material-icons right">import_export</i>
760
+ </button>
761
+ </div>
762
+ </div>
763
+ <?php else:?>
764
+
765
+ <div class="row">
766
+ <div class=" col s12">
767
+ <div class="center">
768
+ <?php
769
+ $url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=xcloner-google-drive'), 'install-plugin_xcloner-google-drive');
770
+ ?>
771
+ <h6><?php echo __("This storage option requires the XCloner-Google-Drive Wordpress Plugin to be installed and activated.")?></h6>
772
+ <h6><?php echo __("PHP 5.5 minimum version is required.")?></h6>
773
+ <br />
774
+ <a class="install-now btn" data-slug="xcloner-google-drive" href="<?php echo $url;?>" aria-label="Install XCloner Google Drive 1.0.0 now" data-name="XCloner Google Drive 1.0.0">
775
+ <?php echo sprintf(__('Install Now','xcloner-backup-and-restore'))?>
776
+ </a>
777
+
778
+ <a href="<?php echo admin_url("plugin-install.php")?>?tab=plugin-information&amp;plugin=xcloner-google-drive&amp;TB_iframe=true&amp;width=772&amp;height=499" class="btn thickbox open-plugin-details-modal" aria-label="More information about Theme Check 20160523.1" data-title="Theme Check 20160523.1">
779
+ <!--
780
+ <a class="btn" href="https://github.com/ovidiul/XCloner-Google-Drive/archive/master.zip">
781
+ -->
782
+ <?php echo sprintf(__('More Details','xcloner-backup-and-restore'))?>
783
+ </a>
784
+ </div>
785
+ </div>
786
+ </div>
787
+
788
+ <?php endif; ?>
789
+
790
+ </div>
791
  </li>
792
+
793
+
794
+
795
  </ul>
796
  </div>
797
  </div>
812
  window.location.hash = "#"+tag;
813
  })
814
 
815
+ jQuery("#gdrive_authorization_click").on("click", function(e){
816
+
817
+ var href = (jQuery(this).attr("href"))
818
+
819
+ var new_href= href.replace(/(client_id=).*?(&)/,'$1' + jQuery("#gdrive_client_id").val() + '$2');
820
+
821
+ jQuery(this).attr("href", new_href)
822
+
823
+ });
824
+
825
  if(location.hash)
826
  jQuery(location.hash+" div.collapsible-header").addClass("active");
827
 
admin/partials/xcloner_restore_page.php CHANGED
@@ -1,9 +1,9 @@
1
  <?php
2
 
3
- $xcloner_settings = new Xcloner_Settings();
4
- $logger = new Xcloner_Logger();
5
- $xcloner_file_system = new Xcloner_File_System();
6
- $xcloner_file_transfer = new Xcloner_File_Transfer();
7
 
8
  $start = 0 ;
9
 
1
  <?php
2
 
3
+ $xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
4
+ $logger = $this->get_xcloner_container()->get_xcloner_logger();
5
+ $xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
6
+ $xcloner_file_transfer = $this->get_xcloner_container()->get_xcloner_file_transfer();
7
 
8
  $start = 0 ;
9
 
admin/partials/xcloner_scheduled_backups_page.php CHANGED
@@ -1,9 +1,10 @@
1
  <?php
2
- $xcloner_scheduler = new Xcloner_Scheduler();
3
- $xcloner_remote_storage = new Xcloner_Remote_Storage();
 
4
  $available_storages = $xcloner_remote_storage->get_available_storages();
5
  ?>
6
- <?php if(!defined("DISABLE_WP_CRON") or !DISABLE_WP_CRON): ?>
7
  <div id="setting-error-" class="error settings-error notice is-dismissible">
8
  <p><strong>
9
  <?php echo sprintf(__('We have noticed that DISABLE_WP_CRON is disabled, we recommend enabling that and setting up wp-cron.php to run manually through your hosting account scheduler as explained <a href="%s" target="_blank">here</a>', 'xcloner-backup-and-restore'), "http://www.inmotionhosting.com/support/website/wordpress/disabling-the-wp-cronphp-in-wordpress") ?>
@@ -47,7 +48,7 @@ $available_storages = $xcloner_remote_storage->get_available_storages();
47
 
48
  <div class="row">
49
  <div class="col s12 m6 offset-m6 teal lighten-1" id="server_time">
50
- <h2><?php echo __('Current Server Time', 'xcloner-backup-and-restore')?>: <span class="right"><?php echo date("Y/m/d H:i")?></span></h2>
51
  </div>
52
  </div>
53
 
@@ -113,7 +114,7 @@ $available_storages = $xcloner_remote_storage->get_available_storages();
113
 
114
  <?php if(sizeof($available_storages)):?>
115
  <div class="row">
116
- <div class="input-field col s12 l6">
117
  <select name="schedule_storage" id="schedule_storage" class="validate" >
118
  <option value="" selected><?php echo __('none', 'xcloner-backup-and-restore') ?></option>
119
  <?php foreach($available_storages as $storage=>$text):?>
@@ -131,7 +132,6 @@ $available_storages = $xcloner_remote_storage->get_available_storages();
131
  <label for="email_notification"><?php echo __('Email Notification Address', 'xcloner-backup-and-restore') ?></label>
132
  </div>
133
  </div>
134
-
135
  </div>
136
 
137
  <div id="advanced_scheduler_settings" class="tab-content">
@@ -160,7 +160,7 @@ $available_storages = $xcloner_remote_storage->get_available_storages();
160
 
161
  <div class="row">
162
 
163
- <div class="input-field col s12">
164
  <button class="right btn waves-effect waves-light" type="submit" name="action"><?php echo __('Save', 'xcloner-backup-and-restore') ?>
165
  <i class="material-icons right">send</i>
166
  </button>
1
  <?php
2
+ $xcloner_scheduler = $this->get_xcloner_container()->get_xcloner_scheduler();
3
+
4
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
5
  $available_storages = $xcloner_remote_storage->get_available_storages();
6
  ?>
7
+ <?php if(!defined("DISABLE_WP_CRON") || !DISABLE_WP_CRON): ?>
8
  <div id="setting-error-" class="error settings-error notice is-dismissible">
9
  <p><strong>
10
  <?php echo sprintf(__('We have noticed that DISABLE_WP_CRON is disabled, we recommend enabling that and setting up wp-cron.php to run manually through your hosting account scheduler as explained <a href="%s" target="_blank">here</a>', 'xcloner-backup-and-restore'), "http://www.inmotionhosting.com/support/website/wordpress/disabling-the-wp-cronphp-in-wordpress") ?>
48
 
49
  <div class="row">
50
  <div class="col s12 m6 offset-m6 teal lighten-1" id="server_time">
51
+ <h2><?php echo __('Current Server Time', 'xcloner-backup-and-restore')?>: <span class="right"><?php echo current_time('mysql');?></span></h2>
52
  </div>
53
  </div>
54
 
114
 
115
  <?php if(sizeof($available_storages)):?>
116
  <div class="row">
117
+ <div class="input-field col s12 l12">
118
  <select name="schedule_storage" id="schedule_storage" class="validate" >
119
  <option value="" selected><?php echo __('none', 'xcloner-backup-and-restore') ?></option>
120
  <?php foreach($available_storages as $storage=>$text):?>
132
  <label for="email_notification"><?php echo __('Email Notification Address', 'xcloner-backup-and-restore') ?></label>
133
  </div>
134
  </div>
 
135
  </div>
136
 
137
  <div id="advanced_scheduler_settings" class="tab-content">
160
 
161
  <div class="row">
162
 
163
+ <div class="input-field col s12 ">
164
  <button class="right btn waves-effect waves-light" type="submit" name="action"><?php echo __('Save', 'xcloner-backup-and-restore') ?>
165
  <i class="material-icons right">send</i>
166
  </button>
composer.json CHANGED
@@ -8,7 +8,8 @@
8
  "league/flysystem-dropbox": "^1.0",
9
  "league/flysystem-azure": "^1.0",
10
  "league/flysystem-aws-s3-v3": "^1.0",
11
- "mhetreramesh/flysystem-backblaze": "^1.0"
 
12
  },
13
  "prefer-stable": true
14
  }
8
  "league/flysystem-dropbox": "^1.0",
9
  "league/flysystem-azure": "^1.0",
10
  "league/flysystem-aws-s3-v3": "^1.0",
11
+ "mhetreramesh/flysystem-backblaze": "^1.0",
12
+ "league/flysystem-webdav": "^1.0"
13
  },
14
  "prefer-stable": true
15
  }
composer.lock CHANGED
@@ -4,7 +4,7 @@
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
  "This file is @generated automatically"
6
  ],
7
- "content-hash": "110b361da5eda4768bff2464dbf6a9d1",
8
  "packages": [
9
  {
10
  "name": "aws/aws-sdk-php",
@@ -656,6 +656,53 @@
656
  "description": "Flysystem adapter for SFTP",
657
  "time": "2016-12-08T21:28:01+00:00"
658
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  {
660
  "name": "mhetreramesh/flysystem-backblaze",
661
  "version": "1.0.5",
@@ -1089,6 +1136,413 @@
1089
  ],
1090
  "time": "2016-10-10T12:19:37+00:00"
1091
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  {
1093
  "name": "splitbrain/php-archive",
1094
  "version": "1.0.7",
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
  "This file is @generated automatically"
6
  ],
7
+ "content-hash": "26c9988e5a81c42240310246c519b4dd",
8
  "packages": [
9
  {
10
  "name": "aws/aws-sdk-php",
656
  "description": "Flysystem adapter for SFTP",
657
  "time": "2016-12-08T21:28:01+00:00"
658
  },
659
+ {
660
+ "name": "league/flysystem-webdav",
661
+ "version": "1.0.5",
662
+ "source": {
663
+ "type": "git",
664
+ "url": "https://github.com/thephpleague/flysystem-webdav.git",
665
+ "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7"
666
+ },
667
+ "dist": {
668
+ "type": "zip",
669
+ "url": "https://api.github.com/repos/thephpleague/flysystem-webdav/zipball/5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
670
+ "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
671
+ "shasum": ""
672
+ },
673
+ "require": {
674
+ "league/flysystem": "~1.0",
675
+ "php": ">=5.5.0",
676
+ "sabre/dav": "~3.1"
677
+ },
678
+ "require-dev": {
679
+ "mockery/mockery": "~0.9",
680
+ "phpunit/phpunit": "~4.0"
681
+ },
682
+ "type": "library",
683
+ "extra": {
684
+ "branch-alias": {
685
+ "dev-master": "1.0-dev"
686
+ }
687
+ },
688
+ "autoload": {
689
+ "psr-4": {
690
+ "League\\Flysystem\\WebDAV\\": "src"
691
+ }
692
+ },
693
+ "notification-url": "https://packagist.org/downloads/",
694
+ "license": [
695
+ "MIT"
696
+ ],
697
+ "authors": [
698
+ {
699
+ "name": "Frank de Jonge",
700
+ "email": "info@frenky.net"
701
+ }
702
+ ],
703
+ "description": "Flysystem adapter for WebDAV",
704
+ "time": "2016-12-14T11:28:55+00:00"
705
+ },
706
  {
707
  "name": "mhetreramesh/flysystem-backblaze",
708
  "version": "1.0.5",
1136
  ],
1137
  "time": "2016-10-10T12:19:37+00:00"
1138
  },
1139
+ {
1140
+ "name": "sabre/dav",
1141
+ "version": "3.2.2",
1142
+ "source": {
1143
+ "type": "git",
1144
+ "url": "https://github.com/fruux/sabre-dav.git",
1145
+ "reference": "e987775e619728f12205606c9cc3ee565ffb1516"
1146
+ },
1147
+ "dist": {
1148
+ "type": "zip",
1149
+ "url": "https://api.github.com/repos/fruux/sabre-dav/zipball/e987775e619728f12205606c9cc3ee565ffb1516",
1150
+ "reference": "e987775e619728f12205606c9cc3ee565ffb1516",
1151
+ "shasum": ""
1152
+ },
1153
+ "require": {
1154
+ "ext-ctype": "*",
1155
+ "ext-date": "*",
1156
+ "ext-dom": "*",
1157
+ "ext-iconv": "*",
1158
+ "ext-mbstring": "*",
1159
+ "ext-pcre": "*",
1160
+ "ext-simplexml": "*",
1161
+ "ext-spl": "*",
1162
+ "lib-libxml": ">=2.7.0",
1163
+ "php": ">=5.5.0",
1164
+ "psr/log": "^1.0",
1165
+ "sabre/event": ">=2.0.0, <4.0.0",
1166
+ "sabre/http": "^4.2.1",
1167
+ "sabre/uri": "^1.0.1",
1168
+ "sabre/vobject": "^4.1.0",
1169
+ "sabre/xml": "^1.4.0"
1170
+ },
1171
+ "require-dev": {
1172
+ "evert/phpdoc-md": "~0.1.0",
1173
+ "monolog/monolog": "^1.18",
1174
+ "phpunit/phpunit": "> 4.8, <6.0.0",
1175
+ "sabre/cs": "^1.0.0"
1176
+ },
1177
+ "suggest": {
1178
+ "ext-curl": "*",
1179
+ "ext-pdo": "*"
1180
+ },
1181
+ "bin": [
1182
+ "bin/sabredav",
1183
+ "bin/naturalselection"
1184
+ ],
1185
+ "type": "library",
1186
+ "extra": {
1187
+ "branch-alias": {
1188
+ "dev-master": "3.1.0-dev"
1189
+ }
1190
+ },
1191
+ "autoload": {
1192
+ "psr-4": {
1193
+ "Sabre\\DAV\\": "lib/DAV/",
1194
+ "Sabre\\DAVACL\\": "lib/DAVACL/",
1195
+ "Sabre\\CalDAV\\": "lib/CalDAV/",
1196
+ "Sabre\\CardDAV\\": "lib/CardDAV/"
1197
+ }
1198
+ },
1199
+ "notification-url": "https://packagist.org/downloads/",
1200
+ "license": [
1201
+ "BSD-3-Clause"
1202
+ ],
1203
+ "authors": [
1204
+ {
1205
+ "name": "Evert Pot",
1206
+ "email": "me@evertpot.com",
1207
+ "homepage": "http://evertpot.com/",
1208
+ "role": "Developer"
1209
+ }
1210
+ ],
1211
+ "description": "WebDAV Framework for PHP",
1212
+ "homepage": "http://sabre.io/",
1213
+ "keywords": [
1214
+ "CalDAV",
1215
+ "CardDAV",
1216
+ "WebDAV",
1217
+ "framework",
1218
+ "iCalendar"
1219
+ ],
1220
+ "time": "2017-02-15T03:06:08+00:00"
1221
+ },
1222
+ {
1223
+ "name": "sabre/event",
1224
+ "version": "3.0.0",
1225
+ "source": {
1226
+ "type": "git",
1227
+ "url": "https://github.com/fruux/sabre-event.git",
1228
+ "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
1229
+ },
1230
+ "dist": {
1231
+ "type": "zip",
1232
+ "url": "https://api.github.com/repos/fruux/sabre-event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534",
1233
+ "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534",
1234
+ "shasum": ""
1235
+ },
1236
+ "require": {
1237
+ "php": ">=5.5"
1238
+ },
1239
+ "require-dev": {
1240
+ "phpunit/phpunit": "*",
1241
+ "sabre/cs": "~0.0.4"
1242
+ },
1243
+ "type": "library",
1244
+ "autoload": {
1245
+ "psr-4": {
1246
+ "Sabre\\Event\\": "lib/"
1247
+ },
1248
+ "files": [
1249
+ "lib/coroutine.php",
1250
+ "lib/Loop/functions.php",
1251
+ "lib/Promise/functions.php"
1252
+ ]
1253
+ },
1254
+ "notification-url": "https://packagist.org/downloads/",
1255
+ "license": [
1256
+ "BSD-3-Clause"
1257
+ ],
1258
+ "authors": [
1259
+ {
1260
+ "name": "Evert Pot",
1261
+ "email": "me@evertpot.com",
1262
+ "homepage": "http://evertpot.com/",
1263
+ "role": "Developer"
1264
+ }
1265
+ ],
1266
+ "description": "sabre/event is a library for lightweight event-based programming",
1267
+ "homepage": "http://sabre.io/event/",
1268
+ "keywords": [
1269
+ "EventEmitter",
1270
+ "async",
1271
+ "events",
1272
+ "hooks",
1273
+ "plugin",
1274
+ "promise",
1275
+ "signal"
1276
+ ],
1277
+ "time": "2015-11-05T20:14:39+00:00"
1278
+ },
1279
+ {
1280
+ "name": "sabre/http",
1281
+ "version": "4.2.2",
1282
+ "source": {
1283
+ "type": "git",
1284
+ "url": "https://github.com/fruux/sabre-http.git",
1285
+ "reference": "dd50e7260356f4599d40270826f9548b23efa204"
1286
+ },
1287
+ "dist": {
1288
+ "type": "zip",
1289
+ "url": "https://api.github.com/repos/fruux/sabre-http/zipball/dd50e7260356f4599d40270826f9548b23efa204",
1290
+ "reference": "dd50e7260356f4599d40270826f9548b23efa204",
1291
+ "shasum": ""
1292
+ },
1293
+ "require": {
1294
+ "ext-ctype": "*",
1295
+ "ext-mbstring": "*",
1296
+ "php": ">=5.4",
1297
+ "sabre/event": ">=1.0.0,<4.0.0",
1298
+ "sabre/uri": "~1.0"
1299
+ },
1300
+ "require-dev": {
1301
+ "phpunit/phpunit": "~4.3",
1302
+ "sabre/cs": "~0.0.1"
1303
+ },
1304
+ "suggest": {
1305
+ "ext-curl": " to make http requests with the Client class"
1306
+ },
1307
+ "type": "library",
1308
+ "autoload": {
1309
+ "files": [
1310
+ "lib/functions.php"
1311
+ ],
1312
+ "psr-4": {
1313
+ "Sabre\\HTTP\\": "lib/"
1314
+ }
1315
+ },
1316
+ "notification-url": "https://packagist.org/downloads/",
1317
+ "license": [
1318
+ "BSD-3-Clause"
1319
+ ],
1320
+ "authors": [
1321
+ {
1322
+ "name": "Evert Pot",
1323
+ "email": "me@evertpot.com",
1324
+ "homepage": "http://evertpot.com/",
1325
+ "role": "Developer"
1326
+ }
1327
+ ],
1328
+ "description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
1329
+ "homepage": "https://github.com/fruux/sabre-http",
1330
+ "keywords": [
1331
+ "http"
1332
+ ],
1333
+ "time": "2017-01-02T19:38:42+00:00"
1334
+ },
1335
+ {
1336
+ "name": "sabre/uri",
1337
+ "version": "1.2.1",
1338
+ "source": {
1339
+ "type": "git",
1340
+ "url": "https://github.com/fruux/sabre-uri.git",
1341
+ "reference": "ada354d83579565949d80b2e15593c2371225e61"
1342
+ },
1343
+ "dist": {
1344
+ "type": "zip",
1345
+ "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/ada354d83579565949d80b2e15593c2371225e61",
1346
+ "reference": "ada354d83579565949d80b2e15593c2371225e61",
1347
+ "shasum": ""
1348
+ },
1349
+ "require": {
1350
+ "php": ">=5.4.7"
1351
+ },
1352
+ "require-dev": {
1353
+ "phpunit/phpunit": ">=4.0,<6.0",
1354
+ "sabre/cs": "~1.0.0"
1355
+ },
1356
+ "type": "library",
1357
+ "autoload": {
1358
+ "files": [
1359
+ "lib/functions.php"
1360
+ ],
1361
+ "psr-4": {
1362
+ "Sabre\\Uri\\": "lib/"
1363
+ }
1364
+ },
1365
+ "notification-url": "https://packagist.org/downloads/",
1366
+ "license": [
1367
+ "BSD-3-Clause"
1368
+ ],
1369
+ "authors": [
1370
+ {
1371
+ "name": "Evert Pot",
1372
+ "email": "me@evertpot.com",
1373
+ "homepage": "http://evertpot.com/",
1374
+ "role": "Developer"
1375
+ }
1376
+ ],
1377
+ "description": "Functions for making sense out of URIs.",
1378
+ "homepage": "http://sabre.io/uri/",
1379
+ "keywords": [
1380
+ "rfc3986",
1381
+ "uri",
1382
+ "url"
1383
+ ],
1384
+ "time": "2017-02-20T19:59:28+00:00"
1385
+ },
1386
+ {
1387
+ "name": "sabre/vobject",
1388
+ "version": "4.1.2",
1389
+ "source": {
1390
+ "type": "git",
1391
+ "url": "https://github.com/fruux/sabre-vobject.git",
1392
+ "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c"
1393
+ },
1394
+ "dist": {
1395
+ "type": "zip",
1396
+ "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
1397
+ "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
1398
+ "shasum": ""
1399
+ },
1400
+ "require": {
1401
+ "ext-mbstring": "*",
1402
+ "php": ">=5.5",
1403
+ "sabre/xml": ">=1.5 <3.0"
1404
+ },
1405
+ "require-dev": {
1406
+ "phpunit/phpunit": "*",
1407
+ "sabre/cs": "^1.0.0"
1408
+ },
1409
+ "suggest": {
1410
+ "hoa/bench": "If you would like to run the benchmark scripts"
1411
+ },
1412
+ "bin": [
1413
+ "bin/vobject",
1414
+ "bin/generate_vcards"
1415
+ ],
1416
+ "type": "library",
1417
+ "extra": {
1418
+ "branch-alias": {
1419
+ "dev-master": "4.0.x-dev"
1420
+ }
1421
+ },
1422
+ "autoload": {
1423
+ "psr-4": {
1424
+ "Sabre\\VObject\\": "lib/"
1425
+ }
1426
+ },
1427
+ "notification-url": "https://packagist.org/downloads/",
1428
+ "license": [
1429
+ "BSD-3-Clause"
1430
+ ],
1431
+ "authors": [
1432
+ {
1433
+ "name": "Evert Pot",
1434
+ "email": "me@evertpot.com",
1435
+ "homepage": "http://evertpot.com/",
1436
+ "role": "Developer"
1437
+ },
1438
+ {
1439
+ "name": "Dominik Tobschall",
1440
+ "email": "dominik@fruux.com",
1441
+ "homepage": "http://tobschall.de/",
1442
+ "role": "Developer"
1443
+ },
1444
+ {
1445
+ "name": "Ivan Enderlin",
1446
+ "email": "ivan.enderlin@hoa-project.net",
1447
+ "homepage": "http://mnt.io/",
1448
+ "role": "Developer"
1449
+ }
1450
+ ],
1451
+ "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
1452
+ "homepage": "http://sabre.io/vobject/",
1453
+ "keywords": [
1454
+ "availability",
1455
+ "freebusy",
1456
+ "iCalendar",
1457
+ "ical",
1458
+ "ics",
1459
+ "jCal",
1460
+ "jCard",
1461
+ "recurrence",
1462
+ "rfc2425",
1463
+ "rfc2426",
1464
+ "rfc2739",
1465
+ "rfc4770",
1466
+ "rfc5545",
1467
+ "rfc5546",
1468
+ "rfc6321",
1469
+ "rfc6350",
1470
+ "rfc6351",
1471
+ "rfc6474",
1472
+ "rfc6638",
1473
+ "rfc6715",
1474
+ "rfc6868",
1475
+ "vCalendar",
1476
+ "vCard",
1477
+ "vcf",
1478
+ "xCal",
1479
+ "xCard"
1480
+ ],
1481
+ "time": "2016-12-06T04:14:09+00:00"
1482
+ },
1483
+ {
1484
+ "name": "sabre/xml",
1485
+ "version": "1.5.0",
1486
+ "source": {
1487
+ "type": "git",
1488
+ "url": "https://github.com/fruux/sabre-xml.git",
1489
+ "reference": "59b20e5bbace9912607481634f97d05a776ffca7"
1490
+ },
1491
+ "dist": {
1492
+ "type": "zip",
1493
+ "url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7",
1494
+ "reference": "59b20e5bbace9912607481634f97d05a776ffca7",
1495
+ "shasum": ""
1496
+ },
1497
+ "require": {
1498
+ "ext-dom": "*",
1499
+ "ext-xmlreader": "*",
1500
+ "ext-xmlwriter": "*",
1501
+ "lib-libxml": ">=2.6.20",
1502
+ "php": ">=5.5.5",
1503
+ "sabre/uri": ">=1.0,<3.0.0"
1504
+ },
1505
+ "require-dev": {
1506
+ "phpunit/phpunit": "*",
1507
+ "sabre/cs": "~1.0.0"
1508
+ },
1509
+ "type": "library",
1510
+ "autoload": {
1511
+ "psr-4": {
1512
+ "Sabre\\Xml\\": "lib/"
1513
+ },
1514
+ "files": [
1515
+ "lib/Deserializer/functions.php",
1516
+ "lib/Serializer/functions.php"
1517
+ ]
1518
+ },
1519
+ "notification-url": "https://packagist.org/downloads/",
1520
+ "license": [
1521
+ "BSD-3-Clause"
1522
+ ],
1523
+ "authors": [
1524
+ {
1525
+ "name": "Evert Pot",
1526
+ "email": "me@evertpot.com",
1527
+ "homepage": "http://evertpot.com/",
1528
+ "role": "Developer"
1529
+ },
1530
+ {
1531
+ "name": "Markus Staab",
1532
+ "email": "markus.staab@redaxo.de",
1533
+ "role": "Developer"
1534
+ }
1535
+ ],
1536
+ "description": "sabre/xml is an XML library that you may not hate.",
1537
+ "homepage": "https://sabre.io/xml/",
1538
+ "keywords": [
1539
+ "XMLReader",
1540
+ "XMLWriter",
1541
+ "dom",
1542
+ "xml"
1543
+ ],
1544
+ "time": "2016-10-09T22:57:52+00:00"
1545
+ },
1546
  {
1547
  "name": "splitbrain/php-archive",
1548
  "version": "1.0.7",
includes/class-xcloner-activator.php CHANGED
@@ -22,7 +22,7 @@
22
  */
23
  class Xcloner_Activator {
24
 
25
- const xcloner_db_version = '1.1.4';
26
  const xcloner_minimum_version = '5.4.0';
27
  /**
28
  * Short Description. (use period)
@@ -46,11 +46,11 @@ class Xcloner_Activator {
46
 
47
  $xcloner_db_version = Xcloner_Activator::xcloner_db_version;
48
 
49
- if($installed_ver != $xcloner_db_version)
 
 
50
  {
51
- $table_name = $wpdb->prefix . "xcloner_scheduler";
52
-
53
- $xcloner_schedule_sql="CREATE TABLE IF NOT EXISTS `".$table_name."` (
54
  `id` int(11) NOT NULL AUTO_INCREMENT,
55
  `name` varchar(255) NOT NULL,
56
  `recurrence` varchar(10) NOT NULL,
@@ -60,15 +60,16 @@ class Xcloner_Activator {
60
  `hash` varchar(10) DEFAULT NULL,
61
  `status` int(1) NOT NULL,
62
  `last_backup` varchar(100) DEFAULT NULL,
63
- PRIMARY KEY (`id`)
64
  ) ".$charset_collate.";
65
  ";
 
66
  require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
67
  dbDelta( $xcloner_schedule_sql );
68
 
69
  update_option( "xcloner_db_version", $xcloner_db_version );
70
  }
71
-
72
  if(!get_option('xcloner_backup_compression_level'))
73
  update_option('xcloner_backup_compression_level', 0);
74
 
22
  */
23
  class Xcloner_Activator {
24
 
25
+ const xcloner_db_version = '1.1.5';
26
  const xcloner_minimum_version = '5.4.0';
27
  /**
28
  * Short Description. (use period)
46
 
47
  $xcloner_db_version = Xcloner_Activator::xcloner_db_version;
48
 
49
+ $xcloner_scheduler_table = $wpdb->prefix . "xcloner_scheduler";
50
+
51
+ if($installed_ver != $xcloner_db_version)
52
  {
53
+ $xcloner_schedule_sql="CREATE TABLE `".$xcloner_scheduler_table."` (
 
 
54
  `id` int(11) NOT NULL AUTO_INCREMENT,
55
  `name` varchar(255) NOT NULL,
56
  `recurrence` varchar(10) NOT NULL,
60
  `hash` varchar(10) DEFAULT NULL,
61
  `status` int(1) NOT NULL,
62
  `last_backup` varchar(100) DEFAULT NULL,
63
+ PRIMARY KEY (`id`)
64
  ) ".$charset_collate.";
65
  ";
66
+
67
  require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
68
  dbDelta( $xcloner_schedule_sql );
69
 
70
  update_option( "xcloner_db_version", $xcloner_db_version );
71
  }
72
+
73
  if(!get_option('xcloner_backup_compression_level'))
74
  update_option('xcloner_backup_compression_level', 0);
75
 
includes/class-xcloner-api.php CHANGED
@@ -17,12 +17,13 @@ class Xcloner_Api{
17
  private $xcloner_settings;
18
  private $xcloner_file_system;
19
  private $xcloner_requirements;
20
- private $_file_system;
21
  private $archive_system;
22
  private $form_params;
23
  private $logger;
 
24
 
25
- public function __construct()
26
  {
27
  global $wpdb;
28
 
@@ -31,25 +32,18 @@ class Xcloner_Api{
31
  ob_end_clean();
32
  ob_start();
33
 
34
- $wpdb->show_errors = false;
35
-
36
- $this->xcloner_settings = new Xcloner_Settings();
37
-
38
- //generating the hash suffix for tmp xcloner store folder
39
- if(isset($_POST['hash'])){
40
 
41
- if($_POST['hash'] == "generate_hash")
42
- $this->xcloner_settings->generate_new_hash();
43
- else
44
- $this->xcloner_settings->set_hash($_POST['hash']);
45
- }
46
-
47
- $this->logger = new XCloner_Logger("xcloner_api", $this->xcloner_settings->get_hash());
48
- $this->xcloner_file_system = new Xcloner_File_System($this->xcloner_settings->get_hash());
49
- $this->xcloner_sanitization = new Xcloner_Sanitization();
50
- $this->xcloner_requirements = new XCloner_Requirements();
51
- $this->archive_system = new Xcloner_Archive($this->xcloner_settings->get_hash());
52
- $this->xcloner_database = new XCloner_Database($this->xcloner_settings->get_hash());
53
 
54
 
55
  if(isset($_POST['API_ID'])){
@@ -58,6 +52,18 @@ class Xcloner_Api{
58
 
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  public function init_db()
62
  {
63
  return;
@@ -75,11 +81,10 @@ class Xcloner_Api{
75
 
76
  try
77
  {
78
- //$xcloner_db = new XCloner_Database($data['dbUsername'], $data['dbPassword'], $data['dbDatabase'], $data['dbHostname']);
79
  $this->xcloner_database->init($data);
80
 
81
- }catch(Exception $e)
82
- {
83
  $this->send_response($e->getMessage());
84
  $this->logger->error($e->getMessage());
85
 
@@ -98,13 +103,12 @@ class Xcloner_Api{
98
  {
99
  global $wpdb;
100
 
101
- if (!current_user_can('manage_options')) {
102
- die("Not allowed access here!");
103
- }
104
 
105
- $scheduler = new Xcloner_Scheduler();
106
  $params = array();
107
  $schedule = array();
 
108
 
109
  if(isset($_POST['data']))
110
  $params = json_decode(stripslashes($_POST['data']));
@@ -148,10 +152,7 @@ class Xcloner_Api{
148
 
149
  $this->form_params['excluded_files'] = ($return);
150
 
151
- //print_r($this->form_params['database'] );
152
- //print_r($this->form_params['excluded_files']);
153
-
154
- $schedule['start_at'] = $this->form_params['backup_params']['start_at'] ;
155
 
156
  if(!isset($_POST['status']))
157
  $schedule['status'] = 0;
@@ -165,10 +166,12 @@ class Xcloner_Api{
165
  }
166
 
167
  if(!$schedule['start_at'])
168
- $schedule['start_at'] = time();
169
-
170
- $schedule['start_at'] = date('Y-m-d H:i:s', $schedule['start_at']);
171
-
 
 
172
  $schedule['name'] = $this->form_params['backup_params']['schedule_name'];
173
  $schedule['recurrence'] = $this->form_params['backup_params']['schedule_frequency'];
174
  $schedule['remote_storage'] = $this->form_params['backup_params']['schedule_storage'];
@@ -177,7 +180,7 @@ class Xcloner_Api{
177
 
178
  if(!isset($_POST['id']))
179
  {
180
- $insert = $wpdb->insert(
181
  $wpdb->prefix.'xcloner_scheduler',
182
  $schedule,
183
  array(
@@ -186,7 +189,7 @@ class Xcloner_Api{
186
  )
187
  );
188
  }else {
189
- $insert = $wpdb->update(
190
  $wpdb->prefix.'xcloner_scheduler',
191
  $schedule,
192
  array( 'id' => $_POST['id'] ),
@@ -218,9 +221,7 @@ class Xcloner_Api{
218
  */
219
  public function backup_files()
220
  {
221
- if (!current_user_can('manage_options')) {
222
- die("Not allowed access here!");
223
- }
224
 
225
  $params = json_decode(stripslashes($_POST['data']));
226
 
@@ -262,7 +263,8 @@ class Xcloner_Api{
262
  try{
263
  $from = "";
264
  $subject = "";
265
- $this->archive_system->send_notification($to, $from, $subject, $return['extra']['backup_parent'], $this->form_params);
 
266
  }catch(Exception $e)
267
  {
268
  $this->logger->error($e->getMessage());
@@ -281,9 +283,7 @@ class Xcloner_Api{
281
  */
282
  public function backup_database()
283
  {
284
- if (!current_user_can('manage_options')) {
285
- die("Not allowed access here!");
286
- }
287
 
288
  $params = json_decode(stripslashes($_POST['data']));
289
 
@@ -314,9 +314,7 @@ class Xcloner_Api{
314
  */
315
  public function scan_filesystem()
316
  {
317
- if (!current_user_can('manage_options')) {
318
- die("Not allowed access here!");
319
- }
320
 
321
  $params = json_decode(stripslashes($_POST['data']));
322
  $init = (int)$_POST['init'];
@@ -413,9 +411,7 @@ class Xcloner_Api{
413
  */
414
  public function get_file_system_action()
415
  {
416
- if (!current_user_can('manage_options')) {
417
- die("Not allowed access here!");
418
- }
419
 
420
  $folder = $this->xcloner_sanitization->sanitize_input_as_relative_path($_POST['id']);
421
 
@@ -424,7 +420,6 @@ class Xcloner_Api{
424
  if($folder == "#"){
425
 
426
  $folder = "/";
427
- //$list_directory = $this->xcloner_settings->get_xcloner_start_path();
428
  $data[] = array(
429
  'id' => $folder,
430
  'parent' => '#',
@@ -462,9 +457,7 @@ class Xcloner_Api{
462
  else
463
  $text .= " (". $this->xcloner_requirements->file_format_size($file['size']).")";
464
 
465
- //if(in_array($file['path'], $this->xcloner_file_system->get_excluded_files()))
466
- //echo $file['path']."--".$this->xcloner_file_system->is_excluded($file);
467
- if($excluded_pattern = $this->xcloner_file_system->is_excluded($file))
468
  $selected = true;
469
  else
470
  $selected = false;
@@ -491,9 +484,7 @@ class Xcloner_Api{
491
  */
492
  public function get_database_tables_action()
493
  {
494
- if (!current_user_can('manage_options')) {
495
- die("Not allowed access here!");
496
- }
497
 
498
  $database = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['id']);
499
 
@@ -574,14 +565,14 @@ class Xcloner_Api{
574
  */
575
  public function get_schedule_by_id()
576
  {
577
- if (!current_user_can('manage_options')) {
578
- die("Not allowed access here!");
579
- }
580
 
581
  $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
582
- $scheduler = new Xcloner_Scheduler();
583
  $data = $scheduler->get_schedule_by_id($schedule_id);
584
 
 
 
585
  return $this->send_response($data);
586
  }
587
 
@@ -592,11 +583,9 @@ class Xcloner_Api{
592
  */
593
  public function get_scheduler_list()
594
  {
595
- if (!current_user_can('manage_options')) {
596
- die("Not allowed access here!");
597
- }
598
 
599
- $scheduler = new Xcloner_Scheduler();
600
  $data = $scheduler->get_scheduler_list();
601
  $return['data'] = array();
602
 
@@ -611,7 +600,7 @@ class Xcloner_Api{
611
 
612
  $next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id));
613
 
614
- $next_run = date("d M, Y H:i", $next_run_time);
615
 
616
  $remote_storage = $res->remote_storage;
617
 
@@ -620,7 +609,7 @@ class Xcloner_Api{
620
 
621
  if(trim($next_run))
622
  {
623
- $date_text = $next_run;
624
 
625
  if($next_run_time >= time())
626
  $next_run = "in ".human_time_diff($next_run_time, time());
@@ -640,8 +629,8 @@ class Xcloner_Api{
640
  if( $this->xcloner_file_system->get_storage_filesystem()->has($res->last_backup))
641
  {
642
  $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($res->last_backup);
643
- $backup_size = size_format($metadata['size']);
644
- $backup_time = date(get_option('date_format')." ".get_option('time_format'), $metadata['timestamp']);
645
  }
646
 
647
  $backup_text = "<span title='".$backup_time."' class='shorten_string'>".$res->last_backup." (".$backup_size.")</span>";
@@ -649,7 +638,7 @@ class Xcloner_Api{
649
 
650
  $return['data'][] = array($res->id, $res->name, $res->recurrence,/*$res->start_at,*/ $next_run, $remote_storage, $backup_text, $status, $action);
651
  }
652
-
653
  return $this->send_response($return, 0);
654
  }
655
 
@@ -660,12 +649,10 @@ class Xcloner_Api{
660
  */
661
  public function delete_schedule_by_id()
662
  {
663
- if (!current_user_can('manage_options')) {
664
- die("Not allowed access here!");
665
- }
666
 
667
  $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
668
- $scheduler = new Xcloner_Scheduler();
669
  $data['finished'] = $scheduler->delete_schedule_by_id($schedule_id);
670
 
671
  return $this->send_response($data);
@@ -678,22 +665,21 @@ class Xcloner_Api{
678
  */
679
  public function delete_backup_by_name()
680
  {
681
- if (!current_user_can('manage_options')) {
682
- die("Not allowed access here!");
683
- }
684
 
685
  $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_POST['name']);
 
686
 
687
- $data['finished'] = $this->xcloner_file_system->delete_backup_by_name($backup_name);
688
 
689
  return $this->send_response($data);
690
  }
691
 
692
  public function list_backup_files()
693
  {
694
- if (!current_user_can('manage_options')) {
695
- die("Not allowed access here!");
696
- }
697
 
698
  $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
699
  $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
@@ -756,6 +742,41 @@ class Xcloner_Api{
756
  $this->send_response($return, 0);
757
  }
758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  /*
760
  *
761
  * Upload backup to remote API
@@ -763,14 +784,12 @@ class Xcloner_Api{
763
  */
764
  public function upload_backup_to_remote()
765
  {
766
- if (!current_user_can('manage_options')) {
767
- die("Not allowed access here!");
768
- }
769
 
770
  $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
771
  $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
772
 
773
- $xcloner_remote_storage = new Xcloner_Remote_Storage();
774
 
775
  $return = array();
776
 
@@ -804,11 +823,9 @@ class Xcloner_Api{
804
  */
805
  public function remote_storage_save_status()
806
  {
807
- if (!current_user_can('manage_options')) {
808
- die("Not allowed access here!");
809
- }
810
 
811
- $xcloner_remote_storage = new Xcloner_Remote_Storage();
812
 
813
  $return['finished'] = $xcloner_remote_storage->change_storage_status($_POST['id'], $_POST['value']);
814
 
@@ -818,9 +835,7 @@ class Xcloner_Api{
818
 
819
  public function download_restore_script()
820
  {
821
- if (!current_user_can('manage_options')) {
822
- die("Not allowed access here!");
823
- }
824
 
825
  @ob_end_clean();
826
 
@@ -828,13 +843,29 @@ class Xcloner_Api{
828
  $xcloner_plugin_filesystem = new Filesystem($adapter, new Config([
829
  'disable_asserts' => true,
830
  ]));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
831
 
832
  $tmp_file = $this->xcloner_settings->get_xcloner_tmp_path().DS."xcloner-restore.tgz";
833
 
834
  $tar = new Tar();
835
  $tar->create($tmp_file);
836
 
837
- $tar->addFile(dirname(__DIR__)."/restore/vendor.phar", "vendor.phar");
838
  //$tar->addFile(dirname(__DIR__)."/restore/vendor.tgz", "vendor.tgz");
839
 
840
  $files = $xcloner_plugin_filesystem->listContents("vendor/", true);
@@ -873,9 +904,7 @@ class Xcloner_Api{
873
  */
874
  public function download_backup_by_name()
875
  {
876
- if (!current_user_can('manage_options')) {
877
- die("Not allowed access here!");
878
- }
879
 
880
  @ob_end_clean();
881
 
@@ -883,7 +912,6 @@ class Xcloner_Api{
883
 
884
 
885
  $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($backup_name);
886
- //$mimetype = $this->xcloner_file_system->get_storage_filesystem()->getMimetype($backup_name);
887
  $read_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($backup_name);
888
 
889
 
@@ -893,7 +921,6 @@ class Xcloner_Api{
893
  header('Cache-Control: private', false);
894
  header('Content-Transfer-Encoding: binary');
895
  header('Content-Disposition: attachment; filename="'.$metadata['path'].'";');
896
- //header('Content-Type: ' . $mimetype);
897
  header('Content-Type: application/octet-stream');
898
  header('Content-Length: ' . $metadata['size']);
899
 
@@ -913,9 +940,7 @@ class Xcloner_Api{
913
 
914
  public function restore_upload_backup()
915
  {
916
- if (!current_user_can('manage_options')) {
917
- die("Not allowed access here!");
918
- }
919
 
920
  $return['part'] = 0;
921
  $return['total_parts'] = 0;
@@ -952,7 +977,7 @@ class Xcloner_Api{
952
 
953
  try{
954
 
955
- $xcloner_file_transfer = new Xcloner_File_Transfer();
956
  $xcloner_file_transfer->set_target($target_url);
957
  $return['start'] = $xcloner_file_transfer->transfer_file($file, $start, $hash);
958
 
17
  private $xcloner_settings;
18
  private $xcloner_file_system;
19
  private $xcloner_requirements;
20
+ private $xcloner_sanitization;
21
  private $archive_system;
22
  private $form_params;
23
  private $logger;
24
+ private $xcloner_container;
25
 
26
+ public function __construct(Xcloner $xcloner_container)
27
  {
28
  global $wpdb;
29
 
32
  ob_end_clean();
33
  ob_start();
34
 
35
+ $wpdb->show_errors = false;
 
 
 
 
 
36
 
37
+ $this->xcloner_container = $xcloner_container;
38
+
39
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
40
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_api");
41
+ $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
42
+ $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
43
+ $this->xcloner_requirements = $xcloner_container->get_xcloner_requirements();
44
+ $this->archive_system = $xcloner_container->get_archive_system();
45
+ $this->xcloner_database = $xcloner_container->get_xcloner_database();
46
+ $this->xcloner_scheduler = $xcloner_container->get_xcloner_scheduler();
 
 
47
 
48
 
49
  if(isset($_POST['API_ID'])){
52
 
53
  }
54
 
55
+ private function get_xcloner_container()
56
+ {
57
+ return $this->xcloner_container;
58
+ }
59
+
60
+ private function check_access()
61
+ {
62
+ if (function_exists('current_user_can') && !current_user_can('manage_options')) {
63
+ die("Not allowed access here!");
64
+ }
65
+ }
66
+
67
  public function init_db()
68
  {
69
  return;
81
 
82
  try
83
  {
 
84
  $this->xcloner_database->init($data);
85
 
86
+ }catch(Exception $e){
87
+
88
  $this->send_response($e->getMessage());
89
  $this->logger->error($e->getMessage());
90
 
103
  {
104
  global $wpdb;
105
 
106
+ $this->check_access();
 
 
107
 
108
+ $scheduler = $this->xcloner_scheduler;
109
  $params = array();
110
  $schedule = array();
111
+ $response = array();
112
 
113
  if(isset($_POST['data']))
114
  $params = json_decode(stripslashes($_POST['data']));
152
 
153
  $this->form_params['excluded_files'] = ($return);
154
 
155
+ $schedule['start_at'] = $this->form_params['backup_params']['start_at'];
 
 
 
156
 
157
  if(!isset($_POST['status']))
158
  $schedule['status'] = 0;
166
  }
167
 
168
  if(!$schedule['start_at'])
169
+ {
170
+ $schedule['start_at'] = date('Y-m-d H:i:s', time());
171
+ }else{
172
+ $schedule['start_at'] = date('Y-m-d H:i:s', $schedule['start_at'] - (get_option( 'gmt_offset' ) * HOUR_IN_SECONDS) );
173
+ }
174
+
175
  $schedule['name'] = $this->form_params['backup_params']['schedule_name'];
176
  $schedule['recurrence'] = $this->form_params['backup_params']['schedule_frequency'];
177
  $schedule['remote_storage'] = $this->form_params['backup_params']['schedule_storage'];
180
 
181
  if(!isset($_POST['id']))
182
  {
183
+ $wpdb->insert(
184
  $wpdb->prefix.'xcloner_scheduler',
185
  $schedule,
186
  array(
189
  )
190
  );
191
  }else {
192
+ $wpdb->update(
193
  $wpdb->prefix.'xcloner_scheduler',
194
  $schedule,
195
  array( 'id' => $_POST['id'] ),
221
  */
222
  public function backup_files()
223
  {
224
+ $this->check_access();
 
 
225
 
226
  $params = json_decode(stripslashes($_POST['data']));
227
 
263
  try{
264
  $from = "";
265
  $subject = "";
266
+ $additional['lines_total'] = $return['extra']['lines_total'];
267
+ $this->archive_system->send_notification($to, $from, $subject, $return['extra']['backup_parent'], $this->form_params,"", $additional);
268
  }catch(Exception $e)
269
  {
270
  $this->logger->error($e->getMessage());
283
  */
284
  public function backup_database()
285
  {
286
+ $this->check_access();
 
 
287
 
288
  $params = json_decode(stripslashes($_POST['data']));
289
 
314
  */
315
  public function scan_filesystem()
316
  {
317
+ $this->check_access();
 
 
318
 
319
  $params = json_decode(stripslashes($_POST['data']));
320
  $init = (int)$_POST['init'];
411
  */
412
  public function get_file_system_action()
413
  {
414
+ $this->check_access();
 
 
415
 
416
  $folder = $this->xcloner_sanitization->sanitize_input_as_relative_path($_POST['id']);
417
 
420
  if($folder == "#"){
421
 
422
  $folder = "/";
 
423
  $data[] = array(
424
  'id' => $folder,
425
  'parent' => '#',
457
  else
458
  $text .= " (". $this->xcloner_requirements->file_format_size($file['size']).")";
459
 
460
+ if($this->xcloner_file_system->is_excluded($file))
 
 
461
  $selected = true;
462
  else
463
  $selected = false;
484
  */
485
  public function get_database_tables_action()
486
  {
487
+ $this->check_access();
 
 
488
 
489
  $database = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['id']);
490
 
565
  */
566
  public function get_schedule_by_id()
567
  {
568
+ $this->check_access();
 
 
569
 
570
  $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
571
+ $scheduler = $this->xcloner_scheduler;
572
  $data = $scheduler->get_schedule_by_id($schedule_id);
573
 
574
+ $data['start_at'] = date("Y-m-d H:i", strtotime($data['start_at']) + (get_option( 'gmt_offset' ) * HOUR_IN_SECONDS));
575
+
576
  return $this->send_response($data);
577
  }
578
 
583
  */
584
  public function get_scheduler_list()
585
  {
586
+ $this->check_access();
 
 
587
 
588
+ $scheduler = $this->xcloner_scheduler;
589
  $data = $scheduler->get_scheduler_list();
590
  $return['data'] = array();
591
 
600
 
601
  $next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id));
602
 
603
+ $next_run = date(get_option('date_format')." ".get_option('time_format'), $next_run_time);
604
 
605
  $remote_storage = $res->remote_storage;
606
 
609
 
610
  if(trim($next_run))
611
  {
612
+ $date_text = date(get_option('date_format')." ".get_option('time_format'), $next_run_time + (get_option( 'gmt_offset' ) * HOUR_IN_SECONDS));
613
 
614
  if($next_run_time >= time())
615
  $next_run = "in ".human_time_diff($next_run_time, time());
629
  if( $this->xcloner_file_system->get_storage_filesystem()->has($res->last_backup))
630
  {
631
  $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($res->last_backup);
632
+ $backup_size = size_format($this->xcloner_file_system->get_backup_size($res->last_backup));
633
+ $backup_time = date(get_option('date_format')." ".get_option('time_format'), $metadata['timestamp']+(get_option( 'gmt_offset' ) * HOUR_IN_SECONDS));
634
  }
635
 
636
  $backup_text = "<span title='".$backup_time."' class='shorten_string'>".$res->last_backup." (".$backup_size.")</span>";
638
 
639
  $return['data'][] = array($res->id, $res->name, $res->recurrence,/*$res->start_at,*/ $next_run, $remote_storage, $backup_text, $status, $action);
640
  }
641
+
642
  return $this->send_response($return, 0);
643
  }
644
 
649
  */
650
  public function delete_schedule_by_id()
651
  {
652
+ $this->check_access();
 
 
653
 
654
  $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
655
+ $scheduler = $this->xcloner_scheduler;
656
  $data['finished'] = $scheduler->delete_schedule_by_id($schedule_id);
657
 
658
  return $this->send_response($data);
665
  */
666
  public function delete_backup_by_name()
667
  {
668
+ $this->check_access();
 
 
669
 
670
  $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_POST['name']);
671
+ $storage_selection = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_selection']);
672
 
673
+ $data['finished'] = $this->xcloner_file_system->delete_backup_by_name($backup_name, $storage_selection);
674
 
675
  return $this->send_response($data);
676
  }
677
 
678
  public function list_backup_files()
679
  {
680
+ $this->check_access();
681
+
682
+ $backup_parts = array();
683
 
684
  $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
685
  $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
742
  $this->send_response($return, 0);
743
  }
744
 
745
+ public function copy_backup_remote_to_local()
746
+ {
747
+
748
+ $this->check_access();
749
+
750
+ $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
751
+ $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
752
+
753
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
754
+
755
+ $return = array();
756
+
757
+ try
758
+ {
759
+ if(method_exists($xcloner_remote_storage, "copy_backup_remote_to_local"))
760
+ {
761
+ $return = call_user_func_array(array($xcloner_remote_storage, "copy_backup_remote_to_local"), array($backup_file, $storage_type));
762
+ }
763
+ }catch(Exception $e){
764
+
765
+ $return['error'] = 1;
766
+ $return['message'] = $e->getMessage();
767
+ }
768
+
769
+ if(!$return)
770
+ {
771
+ $return['error'] = 1;
772
+ $return['message'] = "Upload failed, please check the error log for more information!";
773
+ }
774
+
775
+
776
+ $this->send_response($return, 0);
777
+
778
+ }
779
+
780
  /*
781
  *
782
  * Upload backup to remote API
784
  */
785
  public function upload_backup_to_remote()
786
  {
787
+ $this->check_access();
 
 
788
 
789
  $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
790
  $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
791
 
792
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
793
 
794
  $return = array();
795
 
823
  */
824
  public function remote_storage_save_status()
825
  {
826
+ $this->check_access();
 
 
827
 
828
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
829
 
830
  $return['finished'] = $xcloner_remote_storage->change_storage_status($_POST['id'], $_POST['value']);
831
 
835
 
836
  public function download_restore_script()
837
  {
838
+ $this->check_access();
 
 
839
 
840
  @ob_end_clean();
841
 
843
  $xcloner_plugin_filesystem = new Filesystem($adapter, new Config([
844
  'disable_asserts' => true,
845
  ]));
846
+
847
+ /* Generate PHAR FILE
848
+ $file = 'restore/vendor.built';
849
+
850
+ if(file_exists($file))
851
+ unlink($file);
852
+ $phar2 = new Phar($file, 0, 'vendor.phar');
853
+
854
+ // add all files in the project, only include php files
855
+ $phar2->buildFromIterator(
856
+ new RecursiveIteratorIterator(
857
+ new RecursiveDirectoryIterator(__DIR__.'/vendor/')),
858
+ __DIR__);
859
+
860
+ $phar2->setStub($phar2->createDefaultStub('vendor/autoload.php', 'vendor/autoload.php'));
861
+ * */
862
 
863
  $tmp_file = $this->xcloner_settings->get_xcloner_tmp_path().DS."xcloner-restore.tgz";
864
 
865
  $tar = new Tar();
866
  $tar->create($tmp_file);
867
 
868
+ $tar->addFile(dirname(__DIR__)."/restore/vendor.build.txt", "vendor.phar");
869
  //$tar->addFile(dirname(__DIR__)."/restore/vendor.tgz", "vendor.tgz");
870
 
871
  $files = $xcloner_plugin_filesystem->listContents("vendor/", true);
904
  */
905
  public function download_backup_by_name()
906
  {
907
+ $this->check_access();
 
 
908
 
909
  @ob_end_clean();
910
 
912
 
913
 
914
  $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($backup_name);
 
915
  $read_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($backup_name);
916
 
917
 
921
  header('Cache-Control: private', false);
922
  header('Content-Transfer-Encoding: binary');
923
  header('Content-Disposition: attachment; filename="'.$metadata['path'].'";');
 
924
  header('Content-Type: application/octet-stream');
925
  header('Content-Length: ' . $metadata['size']);
926
 
940
 
941
  public function restore_upload_backup()
942
  {
943
+ $this->check_access();
 
 
944
 
945
  $return['part'] = 0;
946
  $return['total_parts'] = 0;
977
 
978
  try{
979
 
980
+ $xcloner_file_transfer = $this->get_xcloner_container()->get_xcloner_file_transfer();
981
  $xcloner_file_transfer->set_target($target_url);
982
  $return['start'] = $xcloner_file_transfer->transfer_file($file, $start, $hash);
983
 
includes/class-xcloner-archive.php CHANGED
@@ -12,14 +12,19 @@ class Xcloner_Archive extends Tar
12
  private $files_to_process_per_request = 250; //block of 512 bytes
13
  private $compression_level = 0; //0-9 , 0 uncompressed
14
  private $xcloner_split_backup_limit = 2048; //2048MB
 
15
 
16
  private $archive_name;
 
 
 
 
17
 
18
- public function __construct($hash = "", $archive_name = "")
19
  {
20
- $this->filesystem = new Xcloner_File_System($hash);
21
- $this->logger = new XCloner_Logger('xcloner_archive', $hash);
22
- $this->xcloner_settings = new Xcloner_Settings($hash);
23
 
24
  if($value = $this->xcloner_settings->get_xcloner_option('xcloner_size_limit_per_request'))
25
  $this->file_size_per_request_limit = $value*1024*1024; //MB
@@ -132,7 +137,7 @@ class Xcloner_Archive extends Tar
132
  * Send backup archive notfication by E-Mail
133
  *
134
  */
135
- public function send_notification($to, $from, $subject, $backup_name, $params, $error_message="")
136
  {
137
  if(!$from)
138
  $from = "XCloner Backup";
@@ -150,6 +155,12 @@ class Xcloner_Archive extends Tar
150
  $body = sprintf(__("Generated Backup Size: %s"), size_format($this->filesystem->get_backup_size($backup_name)));
151
  $body .= "<br /><br />";
152
 
 
 
 
 
 
 
153
  $backup_parts = $this->filesystem->get_multipart_files($backup_name);
154
 
155
  if(!$backups_counter = sizeof($backup_parts))
@@ -206,6 +217,8 @@ class Xcloner_Archive extends Tar
206
  */
207
  public function start_incremental_backup($backup_params, $extra_params, $init)
208
  {
 
 
209
  if(!isset($extra_params['backup_part']))
210
  $extra_params['backup_part'] = 0;
211
 
@@ -228,8 +241,6 @@ class Xcloner_Archive extends Tar
228
  {
229
  $this->logger->info(sprintf(__("Initializing the backup archive %s"), $this->get_archive_name()));
230
 
231
- $this->backup_archive = new Tar();
232
- $this->backup_archive->setCompression($this->compression_level);
233
  $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
234
 
235
  $return['extra']['backup_init'] = 1;
@@ -266,9 +277,15 @@ class Xcloner_Archive extends Tar
266
 
267
  $file->seek(PHP_INT_MAX);
268
 
269
- $return['extra']['lines_total'] = ($file->key());
270
 
271
- $file->seek($extra_params['start_at_line']);
 
 
 
 
 
 
272
 
273
  $this->processed_size_bytes = 0;
274
 
@@ -276,13 +293,13 @@ class Xcloner_Archive extends Tar
276
 
277
  $start_byte = $extra_params['start_at_byte'];
278
 
279
- //$this->files_to_process_per_request = 10;
280
- //$this->file_size_per_request_limit = 1024*1024;
281
  $byte_limit = 0;
282
 
283
  while(!$file->eof() and $counter<=$this->files_to_process_per_request)
284
  {
285
- $line = str_getcsv($file->current());
 
 
286
 
287
  $relative_path = stripslashes($line[0]);
288
 
@@ -294,8 +311,13 @@ class Xcloner_Archive extends Tar
294
 
295
  //$adapter = $this->filesystem->get_adapter($start_filesystem);
296
 
297
- if(!$this->filesystem->get_filesystem($start_filesystem)->has($relative_path))
298
  {
 
 
 
 
 
299
  $extra_params['start_at_line']++;
300
  $file->next();
301
  continue;
@@ -303,13 +325,13 @@ class Xcloner_Archive extends Tar
303
 
304
  $file_info = $this->filesystem->get_filesystem($start_filesystem)->getMetadata($relative_path);
305
 
306
- //echo "Processing ".$file_info['path']."(".$file_info['size'].")";
307
-
308
  if(!isset($file_info['size']))
309
  $file_info['size'] = 0;
310
 
311
- if($start_filesystem == "tmp_filesystem")
 
312
  $file_info['archive_prefix_path'] = $this->xcloner_settings->get_xcloner_tmp_path_suffix();
 
313
 
314
  $byte_limit = (int)$this->file_size_per_request_limit/512;
315
 
12
  private $files_to_process_per_request = 250; //block of 512 bytes
13
  private $compression_level = 0; //0-9 , 0 uncompressed
14
  private $xcloner_split_backup_limit = 2048; //2048MB
15
+ private $processed_size_bytes = 0 ;
16
 
17
  private $archive_name;
18
+ private $backup_archive;
19
+ private $filesystem;
20
+ private $logger;
21
+ private $xcloner_settings;
22
 
23
+ public function __construct(Xcloner $xcloner_container, $archive_name = "")
24
  {
25
+ $this->filesystem = $xcloner_container->get_xcloner_filesystem();
26
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_archive");
27
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
28
 
29
  if($value = $this->xcloner_settings->get_xcloner_option('xcloner_size_limit_per_request'))
30
  $this->file_size_per_request_limit = $value*1024*1024; //MB
137
  * Send backup archive notfication by E-Mail
138
  *
139
  */
140
+ public function send_notification($to, $from, $subject, $backup_name, $params, $error_message="", $additional = array())
141
  {
142
  if(!$from)
143
  $from = "XCloner Backup";
155
  $body = sprintf(__("Generated Backup Size: %s"), size_format($this->filesystem->get_backup_size($backup_name)));
156
  $body .= "<br /><br />";
157
 
158
+ if(isset($additional['lines_total']))
159
+ {
160
+ $body .= sprintf(__("Total files added: %s"), $additional['lines_total']);
161
+ $body .= "<br /><br />";
162
+ }
163
+
164
  $backup_parts = $this->filesystem->get_multipart_files($backup_name);
165
 
166
  if(!$backups_counter = sizeof($backup_parts))
217
  */
218
  public function start_incremental_backup($backup_params, $extra_params, $init)
219
  {
220
+ $return = array();
221
+
222
  if(!isset($extra_params['backup_part']))
223
  $extra_params['backup_part'] = 0;
224
 
241
  {
242
  $this->logger->info(sprintf(__("Initializing the backup archive %s"), $this->get_archive_name()));
243
 
 
 
244
  $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
245
 
246
  $return['extra']['backup_init'] = 1;
277
 
278
  $file->seek(PHP_INT_MAX);
279
 
280
+ $return['extra']['lines_total'] = ($file->key()-1);
281
 
282
+ //we skip the first CSV line with headers
283
+ if(!$extra_params['start_at_line'])
284
+ {
285
+ $file->seek(1);
286
+ }else{
287
+ $file->seek($extra_params['start_at_line']+1);
288
+ }
289
 
290
  $this->processed_size_bytes = 0;
291
 
293
 
294
  $start_byte = $extra_params['start_at_byte'];
295
 
 
 
296
  $byte_limit = 0;
297
 
298
  while(!$file->eof() and $counter<=$this->files_to_process_per_request)
299
  {
300
+ $current_line_str = $file->current();
301
+
302
+ $line = str_getcsv($current_line_str);
303
 
304
  $relative_path = stripslashes($line[0]);
305
 
311
 
312
  //$adapter = $this->filesystem->get_adapter($start_filesystem);
313
 
314
+ if(!$relative_path || !$this->filesystem->get_filesystem($start_filesystem)->has($relative_path))
315
  {
316
+ if($relative_path != "")
317
+ {
318
+ $this->logger->error(sprintf("Could not add file %b to backup archive, file not found", $relative_path));
319
+ }
320
+
321
  $extra_params['start_at_line']++;
322
  $file->next();
323
  continue;
325
 
326
  $file_info = $this->filesystem->get_filesystem($start_filesystem)->getMetadata($relative_path);
327
 
 
 
328
  if(!isset($file_info['size']))
329
  $file_info['size'] = 0;
330
 
331
+ if($start_filesystem == "tmp_filesystem")
332
+ {
333
  $file_info['archive_prefix_path'] = $this->xcloner_settings->get_xcloner_tmp_path_suffix();
334
+ }
335
 
336
  $byte_limit = (int)$this->file_size_per_request_limit/512;
337
 
includes/class-xcloner-database.php CHANGED
@@ -21,7 +21,7 @@
21
  */
22
 
23
 
24
- class XCloner_Database extends wpdb{
25
 
26
 
27
  public $debug = 0;
@@ -38,11 +38,11 @@ class XCloner_Database extends wpdb{
38
  private $TEMP_DBPROCESS_FILE = ".database";
39
  private $TEMP_DUMP_FILE = "database-backup.sql";
40
 
41
- public function __construct($hash="", $wp_user="", $wp_pass="", $wp_db="", $wp_host="")
42
  {
43
- $this->logger = new XCloner_Logger("xcloner_database", $hash);
44
- $this->xcloner_settings = new Xcloner_Settings($hash);
45
- $this->fs = new Xcloner_File_System($hash);
46
 
47
  if($this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request'))
48
  $this->recordsPerSession = $this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request');
@@ -238,8 +238,10 @@ class XCloner_Database extends wpdb{
238
  /*
239
  * Returns an array of tables from a database and mark $excluded ones
240
  *
241
- * name: lisTables
242
- * @param array $excluded array of tables to mark as excluded
 
 
243
  * @return array $tablesList
244
  */
245
  public function list_tables($database = "", $included = array(), $get_num_records = 0)
@@ -401,8 +403,7 @@ class XCloner_Database extends wpdb{
401
  $return['tableName'] = $tableName;
402
  $return['totalRecords'] = $tableInfo[1];
403
 
404
- //if(intval($return['totalRecords']) != 0)
405
- //print_r($tableInfo);
406
 
407
  if(trim($tableName) !="" and !$tableInfo[2])
408
  $processed_records = $this->export_table($databaseName, $tableName, $startAtRecord, $this->recordsPerSession, $dumpfile);
@@ -592,7 +593,7 @@ class XCloner_Database extends wpdb{
592
  $return .= "# Powered by XCloner Site Backup\n";
593
  $return .= "# http://www.xcloner.com\n";
594
  $return .= "#\n";
595
- $return .= "# Host: " . $_SERVER['HTTP_HOST'] . "\n";
596
  $return .= "# Generation Time: " . date("M j, Y \a\\t H:i") . "\n";
597
  $return .= "# PHP Version: " . phpversion() . "\n";
598
  $return .= "# Database Charset: ". $this->charset . "\n";
21
  */
22
 
23
 
24
+ class Xcloner_Database extends wpdb{
25
 
26
 
27
  public $debug = 0;
38
  private $TEMP_DBPROCESS_FILE = ".database";
39
  private $TEMP_DUMP_FILE = "database-backup.sql";
40
 
41
+ public function __construct(Xcloner $xcloner_container, $wp_user="", $wp_pass="", $wp_db="", $wp_host="")
42
  {
43
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_database");
44
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
45
+ $this->fs = $xcloner_container->get_xcloner_filesystem();
46
 
47
  if($this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request'))
48
  $this->recordsPerSession = $this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request');
238
  /*
239
  * Returns an array of tables from a database and mark $excluded ones
240
  *
241
+ * name: list_tables
242
+ * @param string $database
243
+ * @param array $include
244
+ * @param int $get_num_records
245
  * @return array $tablesList
246
  */
247
  public function list_tables($database = "", $included = array(), $get_num_records = 0)
403
  $return['tableName'] = $tableName;
404
  $return['totalRecords'] = $tableInfo[1];
405
 
406
+ $processed_records = 0;
 
407
 
408
  if(trim($tableName) !="" and !$tableInfo[2])
409
  $processed_records = $this->export_table($databaseName, $tableName, $startAtRecord, $this->recordsPerSession, $dumpfile);
593
  $return .= "# Powered by XCloner Site Backup\n";
594
  $return .= "# http://www.xcloner.com\n";
595
  $return .= "#\n";
596
+ $return .= "# Host: " . get_site_url() . "\n";
597
  $return .= "# Generation Time: " . date("M j, Y \a\\t H:i") . "\n";
598
  $return .= "# PHP Version: " . phpversion() . "\n";
599
  $return .= "# Database Charset: ". $this->charset . "\n";
includes/class-xcloner-deactivator.php CHANGED
@@ -30,9 +30,12 @@ class Xcloner_Deactivator {
30
  * @since 1.0.0
31
  */
32
  public static function deactivate() {
33
- if(class_exists('Xcloner_Scheduler'))
 
 
 
34
  {
35
- $xcloner_scheduler = new Xcloner_Scheduler();
36
  $xcloner_scheduler->deactivate_wp_cron_hooks();
37
  }
38
  }
30
  * @since 1.0.0
31
  */
32
  public static function deactivate() {
33
+
34
+ global $xcloner_plugin;
35
+
36
+ if(is_a($xcloner_plugin, 'Xcloner'))
37
  {
38
+ $xcloner_scheduler = $xcloner_plugin->get_xcloner_scheduler();
39
  $xcloner_scheduler->deactivate_wp_cron_hooks();
40
  }
41
  }
includes/class-xcloner-file-system.php CHANGED
@@ -14,11 +14,17 @@ class Xcloner_File_System{
14
  public $tmp_filesystem;
15
  public $storage_filesystem;
16
  private $xcloner_settings_append;
17
- private $logger;
18
 
 
19
  private $start_adapter;
20
  private $tmp_adapter;
21
  private $storage_adapter;
 
 
 
 
 
22
 
23
  private $files_counter;
24
  private $files_size;
@@ -27,12 +33,12 @@ class Xcloner_File_System{
27
  private $backup_archive_extensions = array("tar", "tgz", "tar.gz", "gz", "csv");
28
  private $backup_name_tags = array('[time]', '[hostname]', '[domain]');
29
 
30
- public function __construct($hash = "")
31
  {
32
- $this->logger = new XCloner_Logger('xcloner_file_system', $hash);
33
- $this->xcloner_requirements = new Xcloner_Requirements();
34
-
35
- $this->xcloner_settings = new Xcloner_Settings($hash);
36
 
37
  try{
38
 
@@ -60,7 +66,7 @@ class Xcloner_File_System{
60
  'disable_asserts' => true,
61
  ]));
62
  }catch(Exception $e){
63
- return false;
64
  }
65
 
66
 
@@ -69,6 +75,12 @@ class Xcloner_File_System{
69
 
70
  }
71
 
 
 
 
 
 
 
72
  public function set_hash($hash)
73
  {
74
  $this->xcloner_settings->set_hash($hash);
@@ -84,8 +96,21 @@ class Xcloner_File_System{
84
  return $this->tmp_filesystem;
85
  }
86
 
87
- public function get_storage_filesystem()
88
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  return $this->storage_filesystem;
90
  }
91
 
@@ -116,7 +141,7 @@ class Xcloner_File_System{
116
 
117
  public function get_start_path_file_info($file)
118
  {
119
- //$info= $this->getMetadataFull('start_adapter', $file);
120
  return $this->start_filesystem->normalizeFileInfo($info);
121
  }
122
 
@@ -161,7 +186,7 @@ class Xcloner_File_System{
161
  public function get_latest_backups()
162
  {
163
  $files = $this->get_backup_archives_list();
164
-
165
  if(is_array($files))
166
  $this->sort_by($files, "timestamp","desc");
167
 
@@ -215,13 +240,13 @@ class Xcloner_File_System{
215
  return $backup_size;
216
  }
217
 
218
- public function get_multipart_files($backup_name)
219
  {
220
  $files = array();
221
 
222
  if($this->is_multipart($backup_name))
223
  {
224
- $lines = explode(PHP_EOL, $this->get_storage_filesystem()->read($backup_name));
225
  foreach($lines as $line)
226
  {
227
  if($line)
@@ -235,22 +260,22 @@ class Xcloner_File_System{
235
  return $files;
236
  }
237
 
238
- public function delete_backup_by_name($backup_name)
239
  {
240
  if($this->is_multipart($backup_name))
241
  {
242
- $lines = explode(PHP_EOL, $this->get_storage_filesystem()->read($backup_name));
243
  foreach($lines as $line)
244
  {
245
  if($line)
246
  {
247
  $data = str_getcsv($line);
248
- $this->get_storage_filesystem()->delete($data[0]);
249
  }
250
  }
251
  }
252
 
253
- if($this->get_storage_filesystem()->delete($backup_name))
254
  $return = true;
255
  else
256
  $return = false;
@@ -267,12 +292,13 @@ class Xcloner_File_System{
267
  }
268
 
269
 
270
- public function get_backup_archives_list()
271
  {
272
  $list = array();
273
 
274
- if(method_exists($this->get_storage_filesystem(), "listContents"))
275
- $list = $this->get_storage_filesystem()->listContents();
 
276
 
277
 
278
  $backup_files = array();
@@ -280,17 +306,17 @@ class Xcloner_File_System{
280
 
281
  foreach($list as $file_info)
282
  {
283
- $data = array();
284
-
285
  if(isset($file_info['extension']) and $file_info['extension'] == "csv")
286
  {
287
- $lines = explode(PHP_EOL, $this->get_storage_filesystem()->read($file_info['path']));
 
 
288
  foreach($lines as $line)
289
  if($line)
290
  {
291
  $data = str_getcsv($line);
292
  if(is_array($data)){
293
- $parents[$data[0]] = $file_info['path'];
294
  $file_info['childs'][] = $data;
295
  $file_info['size'] += $data[2];
296
  }
@@ -304,8 +330,13 @@ class Xcloner_File_System{
304
 
305
  foreach($backup_files as $key=>$file_info)
306
  {
307
- if(isset($parents[$file_info['path']]))
308
- $backup_files[$key]['parent'] = $parents[$file_info['path']];
 
 
 
 
 
309
  }
310
 
311
  return $backup_files;
@@ -582,7 +613,7 @@ class Xcloner_File_System{
582
 
583
  $start_time = microtime();
584
 
585
- $data = $this->tmp_filesystem->read($tmp_file);
586
 
587
  $end_time = microtime() - $start_time;
588
 
@@ -616,21 +647,39 @@ class Xcloner_File_System{
616
 
617
  public function sort_by( &$array, $field, $direction = 'asc')
618
  {
619
- $direction = strtolower($direction);
620
-
621
- usort($array, create_function('$a, $b', '
622
- $a = $a["' . $field . '"];
623
- $b = $b["' . $field . '"];
624
-
625
- if ($a == $b)
626
- {
627
- return 0;
628
- }
629
-
630
- return ($a ' . ($direction == 'desc' ? '>' : '<') .' $b) ? -1 : 1;
631
- '));
632
-
633
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  }
635
 
636
  public function is_excluded($file)
@@ -639,7 +688,7 @@ class Xcloner_File_System{
639
 
640
  if($xcloner_exclude_files_larger_than_mb = $this->xcloner_settings->get_xcloner_option('xcloner_exclude_files_larger_than_mb'))
641
  {
642
- if(isset($file['size']) and $file['size'] > $this->calc_to_bytes($xcloner_exclude_files_larger_than_mb))
643
  return "> ".$xcloner_exclude_files_larger_than_mb."MB";
644
  }
645
 
@@ -741,11 +790,20 @@ class Xcloner_File_System{
741
  if(!isset($file['visibility']))
742
  $file['visibility'] = "private";
743
 
744
- $line = '"'.addslashes($file['path']).'","'.$file['timestamp'].'","'.$file['size'].'","'.$file['visibility'].'","'.$storage.'"'.PHP_EOL;
 
 
745
 
746
  $this->last_logged_file = $file['path'];
747
 
748
  try{
 
 
 
 
 
 
 
749
  $this->tmp_filesystem_append->write($this->get_included_files_handler(), $line);
750
 
751
  }catch(Exception $e){
14
  public $tmp_filesystem;
15
  public $storage_filesystem;
16
  private $xcloner_settings_append;
17
+ private $xcloner_container;
18
 
19
+ private $logger;
20
  private $start_adapter;
21
  private $tmp_adapter;
22
  private $storage_adapter;
23
+ private $xcloner_requirements;
24
+ private $xcloner_settings;
25
+ private $start_filesystem;
26
+ private $tmp_filesystem_append;
27
+ private $storage_filesystem_append;
28
 
29
  private $files_counter;
30
  private $files_size;
33
  private $backup_archive_extensions = array("tar", "tgz", "tar.gz", "gz", "csv");
34
  private $backup_name_tags = array('[time]', '[hostname]', '[domain]');
35
 
36
+ public function __construct(Xcloner $xcloner_container, $hash = "")
37
  {
38
+ $this->xcloner_container = $xcloner_container;
39
+
40
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_file_system");
41
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
42
 
43
  try{
44
 
66
  'disable_asserts' => true,
67
  ]));
68
  }catch(Exception $e){
69
+ $this->logger->error("Filesystem Initialization Error: ".$e->getMessage());
70
  }
71
 
72
 
75
 
76
  }
77
 
78
+ private function get_xcloner_container()
79
+ {
80
+ return $this->xcloner_container;
81
+ }
82
+
83
+
84
  public function set_hash($hash)
85
  {
86
  $this->xcloner_settings->set_hash($hash);
96
  return $this->tmp_filesystem;
97
  }
98
 
99
+ public function get_storage_filesystem($remote_storage_selection = "")
100
  {
101
+ if($remote_storage_selection != "")
102
+ {
103
+ $remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
104
+ $method = "get_".$remote_storage_selection."_filesystem";
105
+
106
+ if(!method_exists($remote_storage, $method))
107
+ return false;
108
+
109
+ list($adapter, $filesystem) = $remote_storage->$method();
110
+
111
+ return $filesystem;
112
+ }
113
+
114
  return $this->storage_filesystem;
115
  }
116
 
141
 
142
  public function get_start_path_file_info($file)
143
  {
144
+ $info= $this->getMetadataFull('start_adapter', $file);
145
  return $this->start_filesystem->normalizeFileInfo($info);
146
  }
147
 
186
  public function get_latest_backups()
187
  {
188
  $files = $this->get_backup_archives_list();
189
+
190
  if(is_array($files))
191
  $this->sort_by($files, "timestamp","desc");
192
 
240
  return $backup_size;
241
  }
242
 
243
+ public function get_multipart_files($backup_name, $storage_selection = "")
244
  {
245
  $files = array();
246
 
247
  if($this->is_multipart($backup_name))
248
  {
249
+ $lines = explode(PHP_EOL, $this->get_storage_filesystem($storage_selection)->read($backup_name));
250
  foreach($lines as $line)
251
  {
252
  if($line)
260
  return $files;
261
  }
262
 
263
+ public function delete_backup_by_name($backup_name, $storage_selection = "")
264
  {
265
  if($this->is_multipart($backup_name))
266
  {
267
+ $lines = explode(PHP_EOL, $this->get_storage_filesystem($storage_selection)->read($backup_name));
268
  foreach($lines as $line)
269
  {
270
  if($line)
271
  {
272
  $data = str_getcsv($line);
273
+ $this->get_storage_filesystem($storage_selection)->delete($data[0]);
274
  }
275
  }
276
  }
277
 
278
+ if($this->get_storage_filesystem($storage_selection)->delete($backup_name))
279
  $return = true;
280
  else
281
  $return = false;
292
  }
293
 
294
 
295
+ public function get_backup_archives_list($storage_selection = "")
296
  {
297
  $list = array();
298
 
299
+
300
+ if(method_exists($this->get_storage_filesystem($storage_selection), "listContents"))
301
+ $list = $this->get_storage_filesystem($storage_selection)->listContents();
302
 
303
 
304
  $backup_files = array();
306
 
307
  foreach($list as $file_info)
308
  {
 
 
309
  if(isset($file_info['extension']) and $file_info['extension'] == "csv")
310
  {
311
+ $data = array();
312
+
313
+ $lines = explode(PHP_EOL, $this->get_storage_filesystem($storage_selection)->read($file_info['path']));
314
  foreach($lines as $line)
315
  if($line)
316
  {
317
  $data = str_getcsv($line);
318
  if(is_array($data)){
319
+ $parents[$data[0]] = $file_info['basename'];
320
  $file_info['childs'][] = $data;
321
  $file_info['size'] += $data[2];
322
  }
330
 
331
  foreach($backup_files as $key=>$file_info)
332
  {
333
+ if(!isset($backup_files[$key]['timestamp']))
334
+ {
335
+ //$backup_files[$key]['timestamp'] = $this->get_storage_filesystem($storage_selection)->getTimestamp($file_info['path']);
336
+ }
337
+
338
+ if(isset($parents[$file_info['basename']]))
339
+ $backup_files[$key]['parent'] = $parents[$file_info['basename']];
340
  }
341
 
342
  return $backup_files;
613
 
614
  $start_time = microtime();
615
 
616
+ $this->tmp_filesystem->read($tmp_file);
617
 
618
  $end_time = microtime() - $start_time;
619
 
647
 
648
  public function sort_by( &$array, $field, $direction = 'asc')
649
  {
650
+ if(strtolower($direction) == "desc" || $direction == SORT_DESC)
651
+ $direction = SORT_DESC;
652
+ else
653
+ $direction = SORT_ASC;
654
+
655
+ $array = $this->array_orderby($array, $field, $direction);
656
+
657
+ return true;
658
+ }
659
+
660
+ private function array_orderby()
661
+ {
662
+ $args = func_get_args();
663
+ $data = array_shift($args);
664
+
665
+ foreach ($args as $n => $field) {
666
+ if (is_string($field)) {
667
+ $tmp = array();
668
+ foreach ($data as $key => $row)
669
+ {
670
+ if(is_array($row))
671
+ $tmp[$key] = $row[$field];
672
+ else
673
+ $tmp[$key] = $row->$field;
674
+ }
675
+ $args[$n] = $tmp;
676
+ }
677
+ }
678
+ $args[] = &$data;
679
+
680
+ call_user_func_array('array_multisort', $args);
681
+
682
+ return array_pop($args);
683
  }
684
 
685
  public function is_excluded($file)
688
 
689
  if($xcloner_exclude_files_larger_than_mb = $this->xcloner_settings->get_xcloner_option('xcloner_exclude_files_larger_than_mb'))
690
  {
691
+ if(isset($file['size']) && $file['size'] > $this->calc_to_bytes($xcloner_exclude_files_larger_than_mb))
692
  return "> ".$xcloner_exclude_files_larger_than_mb."MB";
693
  }
694
 
790
  if(!isset($file['visibility']))
791
  $file['visibility'] = "private";
792
 
793
+ $csv_filename = str_replace('"','""', $file['path']);
794
+
795
+ $line = '"'.($csv_filename).'","'.$file['timestamp'].'","'.$file['size'].'","'.$file['visibility'].'","'.$storage.'"'.PHP_EOL;
796
 
797
  $this->last_logged_file = $file['path'];
798
 
799
  try{
800
+ if(!$this->tmp_filesystem_append->has($this->get_included_files_handler()))
801
+ {
802
+ //adding fix for UTF-8 CSV preview
803
+ $start_line = "\xEF\xBB\xBF".'"Filename","Timestamp","Size","Visibility","Storage"'.PHP_EOL;
804
+ $this->tmp_filesystem_append->write($this->get_included_files_handler(), $start_line);
805
+ }
806
+
807
  $this->tmp_filesystem_append->write($this->get_included_files_handler(), $line);
808
 
809
  }catch(Exception $e){
includes/class-xcloner-file-transfer.php CHANGED
@@ -30,7 +30,8 @@ class Xcloner_File_Transfer extends Xcloner_File_System{
30
  $binary_data = fread($fp, $this->transfer_limit);
31
 
32
  $tmp_filename = "xcloner_upload_".substr(md5(time()), 0, 5);
33
- $tmp_file = $this->get_tmp_filesystem()->write($tmp_filename, $binary_data);
 
34
 
35
  $tmp_file_path = $this->get_tmp_filesystem_adapter()->applyPathPrefix($tmp_filename);
36
 
@@ -42,9 +43,8 @@ class Xcloner_File_Transfer extends Xcloner_File_System{
42
  $send_array['hash'] = $hash;
43
  #$send_array['blob'] = $binary_data;
44
  $send_array['blob'] = $this->curl_file_create($tmp_file_path,'application/x-binary',$tmp_filename);
45
- //var_dump($send_array);exit;
46
 
47
- $data = http_build_query($send_array);
48
 
49
  $this->get_logger()->info(sprintf("Sending curl request to %s with %s data of file %s starting position %s using temporary file %s", $this->target_url, $this->transfer_limit, $file, $start, $tmp_filename));
50
 
@@ -74,7 +74,6 @@ class Xcloner_File_Transfer extends Xcloner_File_System{
74
  if($result->status != 200)
75
  {
76
  throw new Exception($result->response);
77
- return false;
78
  }
79
 
80
  if(ftell($fp) >= $this->get_storage_filesystem()->getSize($file))
@@ -87,7 +86,7 @@ class Xcloner_File_Transfer extends Xcloner_File_System{
87
  return ftell($fp);
88
  }
89
 
90
- function curl_file_create($filename, $mimetype = '', $postname = '') {
91
  if (!function_exists('curl_file_create')) {
92
 
93
  return "@$filename;filename="
30
  $binary_data = fread($fp, $this->transfer_limit);
31
 
32
  $tmp_filename = "xcloner_upload_".substr(md5(time()), 0, 5);
33
+
34
+ $this->get_tmp_filesystem()->write($tmp_filename, $binary_data);
35
 
36
  $tmp_file_path = $this->get_tmp_filesystem_adapter()->applyPathPrefix($tmp_filename);
37
 
43
  $send_array['hash'] = $hash;
44
  #$send_array['blob'] = $binary_data;
45
  $send_array['blob'] = $this->curl_file_create($tmp_file_path,'application/x-binary',$tmp_filename);
 
46
 
47
+ //$data = http_build_query($send_array);
48
 
49
  $this->get_logger()->info(sprintf("Sending curl request to %s with %s data of file %s starting position %s using temporary file %s", $this->target_url, $this->transfer_limit, $file, $start, $tmp_filename));
50
 
74
  if($result->status != 200)
75
  {
76
  throw new Exception($result->response);
 
77
  }
78
 
79
  if(ftell($fp) >= $this->get_storage_filesystem()->getSize($file))
86
  return ftell($fp);
87
  }
88
 
89
+ private function curl_file_create($filename, $mimetype = '', $postname = '') {
90
  if (!function_exists('curl_file_create')) {
91
 
92
  return "@$filename;filename="
includes/class-xcloner-loader.php CHANGED
@@ -41,37 +41,41 @@ class Xcloner_Loader {
41
  */
42
  protected $filters;
43
 
 
44
  /**
45
  * Initialize the collections used to maintain the actions and filters.
46
  *
47
  * @since 1.0.0
48
  */
49
- public function __construct() {
50
 
51
  $this->actions = array();
52
  $this->filters = array();
 
 
53
 
54
  }
55
 
56
  public function xcloner_backup_add_admin_menu()
57
  {
58
  if ( function_exists('add_menu_page') )
59
- $hook_suffix = add_menu_page( __('Site Backup','xcloner-backup-and-restore'), __('Site Backup','xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page', 'xcloner_display', 'dashicons-backup');
60
 
61
  if ( function_exists('add_submenu_page') )
62
  {
63
 
64
- add_submenu_page( 'xcloner_init_page', __('XCloner Dashboard','xcloner-backup-and-restore'), __('Dashboard','xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page', 'xcloner_display');
65
- add_submenu_page( 'xcloner_init_page', __('XCloner Backup Settings','xcloner-backup-and-restore'), __('Settings','xcloner-backup-and-restore'), 'manage_options', 'xcloner_settings_page', 'xcloner_display');
66
- add_submenu_page( 'xcloner_init_page', __('Remote Storage Settings','xcloner-backup-and-restore'), __('Remote Storage','xcloner-backup-and-restore'), 'manage_options', 'xcloner_remote_storage_page', 'xcloner_display');
67
- add_submenu_page( 'xcloner_init_page', __('Manage Backups','xcloner-backup-and-restore'), __('Manage Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_manage_backups_page', 'xcloner_display');
68
- add_submenu_page( 'xcloner_init_page', __('Scheduled Backups','xcloner-backup-and-restore'), __('Scheduled Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_scheduled_backups_page', 'xcloner_display');
69
- add_submenu_page( 'xcloner_init_page', __('Generate Backups','xcloner-backup-and-restore'), __('Generate Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_generate_backups_page', 'xcloner_display');
70
- add_submenu_page( 'xcloner_init_page', __('Restore Backups','xcloner-backup-and-restore'), __('Restore Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_restore_page', 'xcloner_display');
71
  }
72
 
73
  }
74
 
 
75
  /**
76
  * Add a new action to the collection to be registered with WordPress.
77
  *
41
  */
42
  protected $filters;
43
 
44
+ private $xcloner_plugin;
45
  /**
46
  * Initialize the collections used to maintain the actions and filters.
47
  *
48
  * @since 1.0.0
49
  */
50
+ public function __construct(Xcloner $xcloner_container) {
51
 
52
  $this->actions = array();
53
  $this->filters = array();
54
+
55
+ $this->xcloner_container = $xcloner_container;
56
 
57
  }
58
 
59
  public function xcloner_backup_add_admin_menu()
60
  {
61
  if ( function_exists('add_menu_page') )
62
+ add_menu_page( __('Site Backup','xcloner-backup-and-restore'), __('Site Backup','xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page', array($this->xcloner_container, 'xcloner_display'), 'dashicons-backup');
63
 
64
  if ( function_exists('add_submenu_page') )
65
  {
66
 
67
+ add_submenu_page( 'xcloner_init_page', __('XCloner Dashboard','xcloner-backup-and-restore'), __('Dashboard','xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page', array($this->xcloner_container, 'xcloner_display'));
68
+ add_submenu_page( 'xcloner_init_page', __('XCloner Backup Settings','xcloner-backup-and-restore'), __('Settings','xcloner-backup-and-restore'), 'manage_options', 'xcloner_settings_page', array($this->xcloner_container, 'xcloner_display'));
69
+ add_submenu_page( 'xcloner_init_page', __('Remote Storage Settings','xcloner-backup-and-restore'), __('Remote Storage','xcloner-backup-and-restore'), 'manage_options', 'xcloner_remote_storage_page', array($this->xcloner_container, 'xcloner_display'));
70
+ add_submenu_page( 'xcloner_init_page', __('Manage Backups','xcloner-backup-and-restore'), __('Manage Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_manage_backups_page', array($this->xcloner_container, 'xcloner_display'));
71
+ add_submenu_page( 'xcloner_init_page', __('Scheduled Backups','xcloner-backup-and-restore'), __('Scheduled Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_scheduled_backups_page', array($this->xcloner_container, 'xcloner_display'));
72
+ add_submenu_page( 'xcloner_init_page', __('Generate Backups','xcloner-backup-and-restore'), __('Generate Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_generate_backups_page', array($this->xcloner_container, 'xcloner_display'));
73
+ add_submenu_page( 'xcloner_init_page', __('Restore Backups','xcloner-backup-and-restore'), __('Restore Backups','xcloner-backup-and-restore'), 'manage_options', 'xcloner_restore_page', array($this->xcloner_container, 'xcloner_display'));
74
  }
75
 
76
  }
77
 
78
+
79
  /**
80
  * Add a new action to the collection to be registered with WordPress.
81
  *
includes/class-xcloner-logger.php CHANGED
@@ -7,20 +7,33 @@ use Monolog\Handler\RotatingFileHandler;
7
  class Xcloner_Logger extends Logger{
8
 
9
  private $logger_path ;
10
- private $max_logger_files = 15;
11
  private $main_logger_url;
12
 
13
- public function __construct($logger_name = "xcloner_logger", $hash="")
14
  {
15
- $xcloner_settings = new Xcloner_Settings($hash);
16
- $logger_path = $xcloner_settings->get_xcloner_store_path().DS.$xcloner_settings->get_logger_filename();
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  if($hash)
 
19
  $logger_path_tmp = $xcloner_settings->get_xcloner_tmp_path().DS.$xcloner_settings->get_logger_filename(1);
20
-
21
 
22
  $this->logger_path = $logger_path;
23
- //$this->logger_path_tmp = $logger_path_tmp;
24
 
25
  if(!is_dir($xcloner_settings->get_xcloner_store_path()) or !is_writable($xcloner_settings->get_xcloner_store_path()))
26
  {
@@ -40,19 +53,25 @@ class Xcloner_Logger extends Logger{
40
  $debug_level = Logger::INFO;
41
 
42
  if(WP_DEBUG)
 
43
  $debug_level = Logger::DEBUG;
 
44
 
45
 
46
  if($logger_path)
47
- $this->pushHandler($stream = new RotatingFileHandler($logger_path, $this->max_logger_files, $debug_level));
 
 
 
 
 
48
 
49
-
50
- $this->main_logger_url = $stream->getUrl();
51
-
52
  if($hash and $logger_path_tmp)
 
53
  $this->pushHandler(new StreamHandler($logger_path_tmp, $debug_level));
 
54
 
55
- return $this;
56
  }
57
 
58
  function get_main_logger_url()
@@ -64,11 +83,9 @@ class Xcloner_Logger extends Logger{
64
  {
65
  $lines = array();
66
 
67
- //if(!file_exists($this->logger_path) or !is_readable($this->logger_path))
68
  if(!file_exists($this->main_logger_url) or !is_readable($this->main_logger_url))
69
  return false;
70
 
71
- //$fp = fopen($this->logger_path, 'r');
72
  $fp = fopen($this->main_logger_url, 'r');
73
  fseek($fp, -1, SEEK_END);
74
  $pos = ftell($fp);
7
  class Xcloner_Logger extends Logger{
8
 
9
  private $logger_path ;
10
+ private $max_logger_files = 7;
11
  private $main_logger_url;
12
 
13
+ public function __construct(Xcloner $xcloner_container, $logger_name = "xcloner_logger")
14
  {
15
+ if(!$xcloner_container->get_xcloner_settings())
16
+ {
17
+ $xcloner_settings = new Xcloner_Settings($xcloner_container);
18
+ }else{
19
+ $xcloner_settings = $xcloner_container->get_xcloner_settings();
20
+ }
21
+
22
+ $hash = $xcloner_settings->get_hash();
23
+ if($hash == "-".$xcloner_settings->get_server_unique_hash(5))
24
+ {
25
+ $hash = "";
26
+ }
27
+
28
+ $logger_path = $xcloner_settings->get_xcloner_store_path().DS.$xcloner_settings->get_logger_filename();
29
+ $logger_path_tmp = "";
30
 
31
  if($hash)
32
+ {
33
  $logger_path_tmp = $xcloner_settings->get_xcloner_tmp_path().DS.$xcloner_settings->get_logger_filename(1);
34
+ }
35
 
36
  $this->logger_path = $logger_path;
 
37
 
38
  if(!is_dir($xcloner_settings->get_xcloner_store_path()) or !is_writable($xcloner_settings->get_xcloner_store_path()))
39
  {
53
  $debug_level = Logger::INFO;
54
 
55
  if(WP_DEBUG)
56
+ {
57
  $debug_level = Logger::DEBUG;
58
+ }
59
 
60
 
61
  if($logger_path)
62
+ {
63
+ $stream = new RotatingFileHandler($logger_path, $this->max_logger_files, $debug_level);
64
+ $this->pushHandler($stream);
65
+
66
+ $this->main_logger_url = $stream->getUrl();
67
+ }
68
 
 
 
 
69
  if($hash and $logger_path_tmp)
70
+ {
71
  $this->pushHandler(new StreamHandler($logger_path_tmp, $debug_level));
72
+ }
73
 
74
+ //return $this;
75
  }
76
 
77
  function get_main_logger_url()
83
  {
84
  $lines = array();
85
 
 
86
  if(!file_exists($this->main_logger_url) or !is_readable($this->main_logger_url))
87
  return false;
88
 
 
89
  $fp = fopen($this->main_logger_url, 'r');
90
  fseek($fp, -1, SEEK_END);
91
  $pos = ftell($fp);
includes/class-xcloner-remote-storage.php CHANGED
@@ -18,12 +18,17 @@ use League\Flysystem\AwsS3v3\AwsS3Adapter;
18
  use Mhetreramesh\Flysystem\BackblazeAdapter;
19
  use ChrisWhite\B2\Client as B2Client;
20
 
 
 
 
21
  class Xcloner_Remote_Storage{
22
 
 
 
23
  private $storage_fields = array(
24
  "option_prefix" => "xcloner_",
25
  "ftp" => array(
26
- "text" => "Ftp",
27
  "ftp_enable" => "int",
28
  "ftp_hostname" => "string",
29
  "ftp_port" => "int",
@@ -43,7 +48,7 @@ class Xcloner_Remote_Storage{
43
  "sftp_username" => "string",
44
  "sftp_password" => "raw",
45
  "sftp_path" => "path",
46
- "sftp_private_key" => "path",
47
  "sftp_timeout" => "int",
48
  "sftp_cleanup_days" => "float",
49
  ),
@@ -81,14 +86,60 @@ class Xcloner_Remote_Storage{
81
  "backblaze_cleanup_days" => "float",
82
  ),
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  );
85
 
86
- public function __construct($hash = "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  {
88
- $this->xcloner_sanitization = new Xcloner_Sanitization($hash);
89
- $this->xcloner_file_system = new Xcloner_File_System($hash);
90
- $this->logger = new XCloner_Logger("xcloner_remote_storage", $hash);
91
- $this->xcloner = new Xcloner();
 
 
 
 
 
92
  }
93
 
94
  public function get_available_storages()
@@ -106,6 +157,11 @@ class Xcloner_Remote_Storage{
106
 
107
  public function save($action = "ftp")
108
  {
 
 
 
 
 
109
  $storage = $this->xcloner_sanitization->sanitize_input_as_string($action);
110
  $this->logger->debug(sprintf("Saving the remote storage %s options", strtoupper($action)));
111
 
@@ -129,17 +185,17 @@ class Xcloner_Remote_Storage{
129
  $this->xcloner->trigger_message(__("%s storage settings saved.", 'xcloner-backup-and-restore'), "success", ucfirst($action));
130
  }
131
 
132
- if(isset($_POST['connection_check']) && $_POST['connection_check'])
133
- {
134
- try{
135
- $this->verify_filesystem($action);
136
- $this->xcloner->trigger_message(__("%s connection is valid.", 'xcloner-backup-and-restore'), "success", ucfirst($action));
137
- $this->logger->debug(sprintf("Connection to remote storage %s is valid", strtoupper($action)));
138
- }catch(Exception $e){
139
- $this->xcloner->trigger_message("%s connection error: ".$e->getMessage(), "error", ucfirst($action));
140
- }
 
141
  }
142
-
143
  }
144
 
145
  public function verify_filesystem($storage_type)
@@ -154,6 +210,15 @@ class Xcloner_Remote_Storage{
154
  list($adapter, $filesystem) = $this->$method();
155
 
156
  $test_file = substr(".xcloner_".md5(time()), 0, 15);
 
 
 
 
 
 
 
 
 
157
 
158
  //testing write access
159
  if(!$filesystem->write($test_file, "data"))
@@ -169,12 +234,17 @@ class Xcloner_Remote_Storage{
169
  if(!$filesystem->delete($test_file))
170
  throw new Exception(__("Could not delete data",'xcloner-backup-and-restore'));
171
  $this->logger->debug(sprintf("I can delete data to remote storage %s", strtoupper($storage_type)));
 
 
172
  }
173
 
174
  public function upload_backup_to_storage($file, $storage)
175
  {
176
  if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
 
 
177
  return false;
 
178
 
179
  $method = "get_".$storage."_filesystem";
180
 
@@ -188,15 +258,15 @@ class Xcloner_Remote_Storage{
188
 
189
  $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $file, strtoupper($storage)), array(""));
190
 
191
- if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
192
  {
193
  $this->logger->info(sprintf("File not found %s in local storage", $file));
194
  return false;
195
- }
196
 
197
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($file);
198
 
199
- if(!$remote_storage_filesystem->updateStream($file, $backup_file_stream))
200
  {
201
  $this->logger->info(sprintf("Could not transfer file %s", $file));
202
  return false;
@@ -211,15 +281,66 @@ class Xcloner_Remote_Storage{
211
  $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $part_file, strtoupper($storage)), array(""));
212
 
213
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($part_file);
214
- if(!$remote_storage_filesystem->updateStream($part_file, $backup_file_stream))
215
  return false;
216
  }
217
  }
218
 
219
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- //$remote_storage_adapter->disconnect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
 
 
223
  return true;
224
 
225
  }
@@ -253,6 +374,16 @@ class Xcloner_Remote_Storage{
253
  {
254
  $this->logger->info(sprintf("Creating the AZURE BLOB remote storage connection"), array(""));
255
 
 
 
 
 
 
 
 
 
 
 
256
  $endpoint = sprintf(
257
  'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s',
258
  get_option("xcloner_azure_account_name"),
@@ -263,7 +394,9 @@ class Xcloner_Remote_Storage{
263
 
264
  $adapter = new AzureAdapter($blobRestProxy, get_option("xcloner_azure_container"));
265
 
266
- $filesystem = new Filesystem($adapter);
 
 
267
 
268
  return array($adapter, $filesystem);
269
  }
@@ -291,6 +424,11 @@ class Xcloner_Remote_Storage{
291
  throw new Exception("AWS S3 class requires PHP 5.5 to be installed!");
292
  }
293
 
 
 
 
 
 
294
 
295
  $client = new S3Client([
296
  'credentials' => [
@@ -322,11 +460,171 @@ class Xcloner_Remote_Storage{
322
  $client = new B2Client(get_option("xcloner_backblaze_account_id"), get_option("xcloner_backblaze_application_key"));
323
  $adapter = new BackblazeAdapter($client, get_option("xcloner_backblaze_bucket_name"));
324
 
325
- $filesystem = new Filesystem($adapter);
 
 
326
 
327
  return array($adapter, $filesystem);
328
  }
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  public function get_ftp_filesystem()
331
  {
332
  $this->logger->info(sprintf("Creating the FTP remote storage connection"), array(""));
@@ -386,4 +684,9 @@ class Xcloner_Remote_Storage{
386
  return update_option($field, $value);
387
  }
388
 
 
 
 
 
 
389
  }
18
  use Mhetreramesh\Flysystem\BackblazeAdapter;
19
  use ChrisWhite\B2\Client as B2Client;
20
 
21
+ use Sabre\DAV\Client as SabreClient;
22
+ use League\Flysystem\WebDAV\WebDAVAdapter;
23
+
24
  class Xcloner_Remote_Storage{
25
 
26
+ private $gdrive_app_name = "XCloner Backup and Restore";
27
+
28
  private $storage_fields = array(
29
  "option_prefix" => "xcloner_",
30
  "ftp" => array(
31
+ "text" => "FTP",
32
  "ftp_enable" => "int",
33
  "ftp_hostname" => "string",
34
  "ftp_port" => "int",
48
  "sftp_username" => "string",
49
  "sftp_password" => "raw",
50
  "sftp_path" => "path",
51
+ "sftp_private_key" => "raw",
52
  "sftp_timeout" => "int",
53
  "sftp_cleanup_days" => "float",
54
  ),
86
  "backblaze_cleanup_days" => "float",
87
  ),
88
 
89
+ "webdav" => array(
90
+ "text" => "WebDAV",
91
+ "webdav_enable" => "int",
92
+ "webdav_url" => "string",
93
+ "webdav_username" => "string",
94
+ "webdav_password" => "string",
95
+ "webdav_target_folder" => "string",
96
+ "webdav_cleanup_days" => "float",
97
+ ),
98
+
99
+ "gdrive" => array(
100
+ "text" => "Google Drive",
101
+ "gdrive_enable" => "int",
102
+ "gdrive_access_code" => "string",
103
+ "gdrive_client_id" => "string",
104
+ "gdrive_client_secret" => "string",
105
+ "gdrive_target_folder" => "string",
106
+ "gdrive_cleanup_days" => "float",
107
+ ),
108
  );
109
 
110
+ private $aws_regions = array(
111
+ 'us-east-1'=>'US East (N. Virginia)',
112
+ 'us-east-2'=>'US East (Ohio)',
113
+ 'us-west-1'=>'US West (N. California)',
114
+ 'us-west-2'=>'US West (Oregon)',
115
+ 'ca-central-1'=>'Canada (Central)',
116
+ 'eu-west-1'=>'EU (Ireland)',
117
+ 'eu-central-1'=>'EU (Frankfurt)',
118
+ 'eu-west-2'=>'EU (London)',
119
+ 'ap-northeast-1'=>'Asia Pacific (Tokyo)',
120
+ 'ap-northeast-2'=>'Asia Pacific (Seoul)',
121
+ 'ap-southeast-1'=>'Asia Pacific (Singapore)',
122
+ 'ap-southeast-2'=>'Asia Pacific (Sydney)',
123
+ 'ap-south-1'=>'Asia Pacific (Mumbai)',
124
+ 'sa-east-1'=>'South America (São Paulo)'
125
+ );
126
+
127
+ private $xcloner_sanitization;
128
+ private $xcloner_file_system;
129
+ private $logger;
130
+ private $xcloner;
131
+
132
+ public function __construct(Xcloner $xcloner_container)
133
  {
134
+ $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
135
+ $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
136
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_remote_storage");
137
+ $this->xcloner = $xcloner_container;
138
+ }
139
+
140
+ private function get_xcloner_container()
141
+ {
142
+ return $this->xcloner_container;
143
  }
144
 
145
  public function get_available_storages()
157
 
158
  public function save($action = "ftp")
159
  {
160
+ if(!$action)
161
+ {
162
+ return false;
163
+ }
164
+
165
  $storage = $this->xcloner_sanitization->sanitize_input_as_string($action);
166
  $this->logger->debug(sprintf("Saving the remote storage %s options", strtoupper($action)));
167
 
185
  $this->xcloner->trigger_message(__("%s storage settings saved.", 'xcloner-backup-and-restore'), "success", ucfirst($action));
186
  }
187
 
188
+ }
189
+
190
+ public function check($action = "ftp")
191
+ {
192
+ try{
193
+ $this->verify_filesystem($action);
194
+ $this->xcloner->trigger_message(__("%s connection is valid.", 'xcloner-backup-and-restore'), "success", ucfirst($action));
195
+ $this->logger->debug(sprintf("Connection to remote storage %s is valid", strtoupper($action)));
196
+ }catch(Exception $e){
197
+ $this->xcloner->trigger_message("%s connection error: ".$e->getMessage(), "error", ucfirst($action));
198
  }
 
199
  }
200
 
201
  public function verify_filesystem($storage_type)
210
  list($adapter, $filesystem) = $this->$method();
211
 
212
  $test_file = substr(".xcloner_".md5(time()), 0, 15);
213
+
214
+ if($storage_type == "gdrive")
215
+ {
216
+ if(!is_array($filesystem->listContents()))
217
+ throw new Exception(__("Could not read data",'xcloner-backup-and-restore'));
218
+ $this->logger->debug(sprintf("I can list data from remote storage %s", strtoupper($storage_type)));
219
+
220
+ return true;
221
+ }
222
 
223
  //testing write access
224
  if(!$filesystem->write($test_file, "data"))
234
  if(!$filesystem->delete($test_file))
235
  throw new Exception(__("Could not delete data",'xcloner-backup-and-restore'));
236
  $this->logger->debug(sprintf("I can delete data to remote storage %s", strtoupper($storage_type)));
237
+
238
+ return true;
239
  }
240
 
241
  public function upload_backup_to_storage($file, $storage)
242
  {
243
  if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
244
+ {
245
+ $this->logger->info(sprintf("File not found %s in local storage", $file));
246
  return false;
247
+ }
248
 
249
  $method = "get_".$storage."_filesystem";
250
 
258
 
259
  $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $file, strtoupper($storage)), array(""));
260
 
261
+ /*if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
262
  {
263
  $this->logger->info(sprintf("File not found %s in local storage", $file));
264
  return false;
265
+ }*/
266
 
267
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($file);
268
 
269
+ if(!$remote_storage_filesystem->writeStream($file, $backup_file_stream))
270
  {
271
  $this->logger->info(sprintf("Could not transfer file %s", $file));
272
  return false;
281
  $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $part_file, strtoupper($storage)), array(""));
282
 
283
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($part_file);
284
+ if(!$remote_storage_filesystem->writeStream($part_file, $backup_file_stream))
285
  return false;
286
  }
287
  }
288
 
289
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
290
+
291
+ return true;
292
+
293
+ }
294
+
295
+ public function copy_backup_remote_to_local($file, $storage)
296
+ {
297
+ $method = "get_".$storage."_filesystem";
298
+
299
+ $target_filename = $file;
300
+
301
+ if(!method_exists($this, $method))
302
+ return false;
303
+
304
+ list($remote_storage_adapter, $remote_storage_filesystem) = $this->$method();
305
+
306
+ if(!$remote_storage_filesystem->has($file))
307
+ {
308
+ $this->logger->info(sprintf("File not found %s in remote storage %s", $file, strtoupper($storage)));
309
+ return false;
310
+ }
311
 
312
+ if($storage == "gdrive")
313
+ {
314
+ $metadata = $remote_storage_filesystem->getMetadata($file);
315
+ $target_filename = $metadata['filename'].".".$metadata['extension'];
316
+ }
317
+
318
+ $this->logger->info(sprintf("Transferring backup %s to local storage from %s storage", $file, strtoupper($storage)), array(""));
319
+
320
+ $backup_file_stream = $remote_storage_filesystem->readStream($file);
321
+
322
+ if(!$this->xcloner_file_system->get_storage_filesystem()->writeStream($target_filename, $backup_file_stream))
323
+ {
324
+ $this->logger->info(sprintf("Could not transfer file %s", $file));
325
+ return false;
326
+ }
327
+
328
+ if($this->xcloner_file_system->is_multipart($target_filename))
329
+ {
330
+ $parts = $this->xcloner_file_system->get_multipart_files($file, $storage);
331
+ if(is_array($parts))
332
+ foreach($parts as $part_file)
333
+ {
334
+ $this->logger->info(sprintf("Transferring backup %s to local storage from %s storage", $part_file, strtoupper($storage)), array(""));
335
+
336
+ $backup_file_stream = $remote_storage_filesystem->readStream($part_file);
337
+ if(!$this->xcloner_file_system->get_storage_filesystem()->writeStream($part_file, $backup_file_stream))
338
+ return false;
339
+ }
340
+ }
341
 
342
+ $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
343
+
344
  return true;
345
 
346
  }
374
  {
375
  $this->logger->info(sprintf("Creating the AZURE BLOB remote storage connection"), array(""));
376
 
377
+ if (version_compare(phpversion(), '5.5.0', '<'))
378
+ {
379
+ throw new Exception("AZURE BLOB requires PHP 5.5 to be installed!");
380
+ }
381
+
382
+ if (!class_exists('XmlWriter'))
383
+ {
384
+ throw new Exception("AZURE BLOB requires libxml PHP module to be installed with XmlWriter class enabled!");
385
+ }
386
+
387
  $endpoint = sprintf(
388
  'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s',
389
  get_option("xcloner_azure_account_name"),
394
 
395
  $adapter = new AzureAdapter($blobRestProxy, get_option("xcloner_azure_container"));
396
 
397
+ $filesystem = new Filesystem($adapter, new Config([
398
+ 'disable_asserts' => true,
399
+ ]));
400
 
401
  return array($adapter, $filesystem);
402
  }
424
  throw new Exception("AWS S3 class requires PHP 5.5 to be installed!");
425
  }
426
 
427
+ if (!class_exists('XmlWriter'))
428
+ {
429
+ throw new Exception("AZURE BLOB requires libxml PHP module to be installed with XmlWriter class enabled!");
430
+ }
431
+
432
 
433
  $client = new S3Client([
434
  'credentials' => [
460
  $client = new B2Client(get_option("xcloner_backblaze_account_id"), get_option("xcloner_backblaze_application_key"));
461
  $adapter = new BackblazeAdapter($client, get_option("xcloner_backblaze_bucket_name"));
462
 
463
+ $filesystem = new Filesystem($adapter, new Config([
464
+ 'disable_asserts' => true,
465
+ ]));
466
 
467
  return array($adapter, $filesystem);
468
  }
469
 
470
+ public function get_webdav_filesystem()
471
+ {
472
+ $this->logger->info(sprintf("Creating the WEBDAV remote storage connection"), array(""));
473
+
474
+ if (version_compare(phpversion(), '5.5.0', '<'))
475
+ {
476
+ throw new Exception("WEBDAV API requires PHP 5.5 to be installed!");
477
+ }
478
+
479
+ $settings = array(
480
+ 'baseUri' => get_option("xcloner_webdav_url"),
481
+ 'userName' => get_option("xcloner_webdav_username"),
482
+ 'password' => get_option("xcloner_webdav_password"),
483
+ //'proxy' => 'locahost:8888',
484
+ );
485
+
486
+
487
+ $client = new SabreClient($settings);
488
+ $adapter = new WebDAVAdapter($client, get_option("xcloner_webdav_target_folder"));
489
+ $filesystem = new Filesystem($adapter, new Config([
490
+ 'disable_asserts' => true,
491
+ ]));
492
+
493
+ return array($adapter, $filesystem);
494
+ }
495
+
496
+
497
+ public function gdrive_construct()
498
+ {
499
+
500
+ //if((function_exists("is_plugin_active") && !is_plugin_active("xcloner-google-drive/xcloner-google-drive.php")) || !file_exists(__DIR__ . "/../../xcloner-google-drive/vendor/autoload.php"))
501
+ if(!class_exists('Google_Client'))
502
+ {
503
+ return false;
504
+ }
505
+
506
+ //require_once(__DIR__ . "/../../xcloner-google-drive/vendor/autoload.php");
507
+
508
+ $client = new \Google_Client();
509
+ $client->setApplicationName($this->gdrive_app_name);
510
+ $client->setClientId(get_option("xcloner_gdrive_client_id"));
511
+ $client->setClientSecret(get_option("xcloner_gdrive_client_secret"));
512
+
513
+ //$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']."?page=xcloner_remote_storage_page&action=set_gdrive_code";
514
+ $redirect_uri = "urn:ietf:wg:oauth:2.0:oob";
515
+
516
+ $client->setRedirectUri($redirect_uri); //urn:ietf:wg:oauth:2.0:oob
517
+ $client->addScope("https://www.googleapis.com/auth/drive");
518
+ $client->setAccessType('offline');
519
+
520
+ return $client;
521
+ }
522
+
523
+ public function get_gdrive_auth_url()
524
+ {
525
+ $client = $this->gdrive_construct();
526
+
527
+ if(!$client)
528
+ return false;
529
+
530
+ return $authUrl = $client->createAuthUrl();
531
+ }
532
+
533
+ public function set_access_token($code)
534
+ {
535
+ $client = $this->gdrive_construct();
536
+
537
+ if(!$client)
538
+ {
539
+ $error_msg = "Could not initialize the Google Drive Class, please check that the xcloner-google-drive plugin is enabled...";
540
+ $this->logger->error($error_msg);
541
+ return false;
542
+ }
543
+
544
+ $token = $client->fetchAccessTokenWithAuthCode($code);
545
+ $client->setAccessToken($token);
546
+
547
+ update_option("xcloner_gdrive_access_token", $token['access_token']);
548
+ update_option("xcloner_gdrive_refresh_token", $token['refresh_token']);
549
+
550
+ $redirect_url = ('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']."?page=xcloner_remote_storage_page#gdrive");
551
+
552
+ ?>
553
+ <script>
554
+ window.location='<?php echo $redirect_url?>';
555
+ </script>
556
+ <?php
557
+
558
+ }
559
+
560
+ /*
561
+ * php composer.phar remove nao-pon/flysystem-google-drive
562
+ *
563
+ */
564
+ public function get_gdrive_filesystem()
565
+ {
566
+ if (version_compare(phpversion(), '5.5.0', '<'))
567
+ {
568
+ throw new Exception("Google Drive API requires PHP 5.5 to be installed!");
569
+ }
570
+
571
+ $this->logger->info(sprintf("Creating the Google Drive remote storage connection"), array(""));
572
+
573
+ $client = $this->gdrive_construct();
574
+
575
+ if(!$client)
576
+ {
577
+ $error_msg = "Could not initialize the Google Drive Class, please check that the xcloner-google-drive plugin is enabled...";
578
+ $this->logger->error($error_msg);
579
+ throw new Exception($error_msg);
580
+ }
581
+
582
+ $client->refreshToken(get_option("xcloner_gdrive_refresh_token"));
583
+
584
+ $service = new \Google_Service_Drive($client);
585
+
586
+ $parent = 'root';
587
+ $dir = basename( get_option("xcloner_gdrive_target_folder"));
588
+
589
+ $folderID = get_option("xcloner_gdrive_target_folder");
590
+
591
+ $tmp = parse_url($folderID);
592
+
593
+ if(isset($tmp['query']))
594
+ {
595
+ $folderID = str_replace("id=", "", $tmp['query']);
596
+ }
597
+
598
+ if(stristr($folderID, "/"))
599
+ {
600
+ $query = sprintf('mimeType = \'application/vnd.google-apps.folder\' and \'%s\' in parents and name contains \'%s\'', $parent, $dir);
601
+ $response = $service->files->listFiles([
602
+ 'pageSize' => 1,
603
+ 'q' => $query
604
+ ]);
605
+
606
+ if(sizeof($response))
607
+ {
608
+ foreach ($response as $obj) {
609
+ $folderID = $obj->getId();
610
+ }
611
+ }else{
612
+ $this->xcloner->trigger_message(sprintf(__("Could not find folder ID by name %s", 'xcloner-backup-and-restore'), $folderID), "error");
613
+ }
614
+ }
615
+
616
+ $this->logger->info(sprintf("Using target folder with ID %s on the remote storage", $folderID));
617
+
618
+ $adapter = new \Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter($service, $folderID);
619
+
620
+ $filesystem = new \League\Flysystem\Filesystem($adapter, new Config([
621
+ 'disable_asserts' => true,
622
+ ]));
623
+
624
+
625
+ return array($adapter, $filesystem);
626
+ }
627
+
628
  public function get_ftp_filesystem()
629
  {
630
  $this->logger->info(sprintf("Creating the FTP remote storage connection"), array(""));
684
  return update_option($field, $value);
685
  }
686
 
687
+ public function get_aws_regions()
688
+ {
689
+ return $this->aws_regions;
690
+ }
691
+
692
  }
includes/class-xcloner-requirements.php CHANGED
@@ -1,14 +1,23 @@
1
  <?php
2
 
3
- class XCloner_Requirements
4
  {
5
 
6
  var $min_php_version = "5.4.0";
7
  var $safe_mode = "Off";
8
 
9
- public function __construct()
 
 
 
 
 
 
 
 
 
10
  {
11
- $this->xcloner_settings = new Xcloner_Settings();
12
  }
13
 
14
  public function check_backup_ready_status()
1
  <?php
2
 
3
+ class Xcloner_Requirements
4
  {
5
 
6
  var $min_php_version = "5.4.0";
7
  var $safe_mode = "Off";
8
 
9
+ private $xcloner_settings;
10
+ private $xcloner_container;
11
+
12
+ public function __construct(Xcloner $xcloner_container)
13
+ {
14
+ $this->xcloner_container = $xcloner_container;
15
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
16
+ }
17
+
18
+ private function get_xcloner_container()
19
  {
20
+ return $this->xcloner_container;
21
  }
22
 
23
  public function check_backup_ready_status()
includes/class-xcloner-sanitization.php CHANGED
@@ -3,6 +3,8 @@ use League\Flysystem\Util;
3
 
4
  class Xcloner_Sanitization {
5
 
 
 
6
  public function sanitize_input_as_int($option)
7
  {
8
  return filter_var($option, FILTER_SANITIZE_NUMBER_INT);
3
 
4
  class Xcloner_Sanitization {
5
 
6
+ public function __construct(){}
7
+
8
  public function sanitize_input_as_int($option)
9
  {
10
  return filter_var($option, FILTER_SANITIZE_NUMBER_INT);
includes/class-xcloner-scheduler.php CHANGED
@@ -5,23 +5,35 @@ class Xcloner_Scheduler{
5
  private $db;
6
  private $scheduler_table = "xcloner_scheduler";
7
 
 
 
 
 
 
 
8
 
9
  /*public function __call($method, $args) {
10
  echo "$method is not defined";
11
  }*/
12
 
13
- public function __construct()
14
  {
15
  global $wpdb;
16
- $this->db = $wpdb;
17
 
18
- $wpdb->show_errors = false;
 
19
 
20
- $this->xcloner_settings = new Xcloner_Settings();
 
21
 
22
- $this->scheduler_table = $this->db->prefix.$this->scheduler_table;
23
  }
24
-
 
 
 
 
 
25
  public function get_scheduler_list($return_only_enabled = 0 )
26
  {
27
  $list = $this->db->get_results("SELECT * FROM ".$this->scheduler_table);
@@ -33,7 +45,7 @@ class Xcloner_Scheduler{
33
  foreach($list as $res)
34
  if($res->status)
35
  {
36
- $res->next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id));
37
  $new_list[] = $res;
38
  }
39
  $list = $new_list;
@@ -199,16 +211,12 @@ class Xcloner_Scheduler{
199
  {
200
  set_time_limit(0);
201
 
202
- $this->xcloner_settings->generate_new_hash();
203
-
204
- $this->xcloner_file_system = new Xcloner_File_System($this->xcloner_settings->get_hash());
205
- $this->xcloner_database = new XCloner_Database($this->xcloner_settings->get_hash());
206
- $this->archive_system = new Xcloner_Archive($this->xcloner_settings->get_hash());
207
- $this->logger = new XCloner_Logger('xcloner_scheduler', $this->xcloner_settings->get_hash());
208
- $this->xcloner_remote_storage = new Xcloner_Remote_Storage($this->xcloner_settings->get_hash());
209
-
210
- //$schedule = $this->get_schedule_by_id($id);
211
-
212
  if($schedule['recurrence'] == "single")
213
  {
214
  $this->disable_single_cron($schedule['id']);
@@ -241,7 +249,6 @@ class Xcloner_Scheduler{
241
  $this->logger->info(sprintf("Starting the database backup"), array("CRON"));
242
 
243
  $init = 1;
244
- $extra = array();
245
  $return['finished'] = 0;
246
 
247
  while(!$return['finished'])
@@ -255,7 +262,6 @@ class Xcloner_Scheduler{
255
  $this->logger->info(sprintf("Starting file archive process"), array("CRON"));
256
 
257
  $init = 0;
258
- $extra = array();
259
  $return['finished'] = 0;
260
  $return['extra'] = array();
261
 
@@ -280,7 +286,7 @@ class Xcloner_Scheduler{
280
  $this->logger->info(sprintf("Transferring backup to remote storage %s", strtoupper($schedule['remote_storage'])), array("CRON"));
281
 
282
  if(method_exists($this->xcloner_remote_storage, "upload_backup_to_storage"))
283
- $return_storage = call_user_func_array(array($this->xcloner_remote_storage, "upload_backup_to_storage"), array($backup_file, $schedule['remote_storage']));
284
  }
285
 
286
 
@@ -288,7 +294,8 @@ class Xcloner_Scheduler{
288
  {
289
  try{
290
  $from = "XCloner Schedule - ".$schedule['name'];
291
- $this->archive_system->send_notification($to, $from, "", $return['extra']['backup_parent'], $schedule);
 
292
  }catch(Exception $e)
293
  {
294
  $this->logger->error($e->getMessage());
@@ -310,7 +317,13 @@ class Xcloner_Scheduler{
310
 
311
  }catch(Exception $e){
312
 
313
- if(isset($schedule['backup_params']->email_notification) and $to=$schedule['backup_params']->email_notification)
 
 
 
 
 
 
314
  {
315
  $from = "XCloner Schedule - ".$schedule['name'];
316
  $this->archive_system->send_notification($to, $from, "Scheduled backup error","", "", $e->getMessage());
5
  private $db;
6
  private $scheduler_table = "xcloner_scheduler";
7
 
8
+ private $xcloner_remote_storage;
9
+ private $archive_system;
10
+ private $xcloner_database;
11
+ private $xcloner_settings;
12
+ private $logger;
13
+ private $xcloner_file_system;
14
 
15
  /*public function __call($method, $args) {
16
  echo "$method is not defined";
17
  }*/
18
 
19
+ public function __construct(Xcloner $xcloner_container)
20
  {
21
  global $wpdb;
 
22
 
23
+ $this->db = $wpdb;
24
+ $wpdb->show_errors = false;
25
 
26
+ $this->xcloner_container = $xcloner_container;
27
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
28
 
29
+ $this->scheduler_table = $this->db->prefix.$this->scheduler_table;
30
  }
31
+
32
+ private function get_xcloner_container()
33
+ {
34
+ return $this->xcloner_container;
35
+ }
36
+
37
  public function get_scheduler_list($return_only_enabled = 0 )
38
  {
39
  $list = $this->db->get_results("SELECT * FROM ".$this->scheduler_table);
45
  foreach($list as $res)
46
  if($res->status)
47
  {
48
+ $res->next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id))+(get_option( 'gmt_offset' ) * HOUR_IN_SECONDS);
49
  $new_list[] = $res;
50
  }
51
  $list = $new_list;
211
  {
212
  set_time_limit(0);
213
 
214
+ $this->xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
215
+ $this->xcloner_database = $this->get_xcloner_container()->get_xcloner_database();
216
+ $this->archive_system = $this->get_xcloner_container()->get_archive_system();
217
+ $this->logger = $this->get_xcloner_container()->get_xcloner_logger()->withName("xcloner_scheduler");
218
+ $this->xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
219
+
 
 
 
 
220
  if($schedule['recurrence'] == "single")
221
  {
222
  $this->disable_single_cron($schedule['id']);
249
  $this->logger->info(sprintf("Starting the database backup"), array("CRON"));
250
 
251
  $init = 1;
 
252
  $return['finished'] = 0;
253
 
254
  while(!$return['finished'])
262
  $this->logger->info(sprintf("Starting file archive process"), array("CRON"));
263
 
264
  $init = 0;
 
265
  $return['finished'] = 0;
266
  $return['extra'] = array();
267
 
286
  $this->logger->info(sprintf("Transferring backup to remote storage %s", strtoupper($schedule['remote_storage'])), array("CRON"));
287
 
288
  if(method_exists($this->xcloner_remote_storage, "upload_backup_to_storage"))
289
+ call_user_func_array(array($this->xcloner_remote_storage, "upload_backup_to_storage"), array($backup_file, $schedule['remote_storage']));
290
  }
291
 
292
 
294
  {
295
  try{
296
  $from = "XCloner Schedule - ".$schedule['name'];
297
+ $additional['lines_total'] = $return['extra']['lines_total'];
298
+ $this->archive_system->send_notification($to, $from, "", $return['extra']['backup_parent'], $schedule, "", $additional);
299
  }catch(Exception $e)
300
  {
301
  $this->logger->error($e->getMessage());
317
 
318
  }catch(Exception $e){
319
 
320
+ //send email to site admin if email notification is not set in the scheduler
321
+ if(!isset($schedule['backup_params']->email_notification) || !$schedule['backup_params']->email_notification)
322
+ {
323
+ $schedule['backup_params']->email_notification = get_option('admin_email');
324
+ }
325
+
326
+ if(isset($schedule['backup_params']->email_notification) && $to=$schedule['backup_params']->email_notification)
327
  {
328
  $from = "XCloner Schedule - ".$schedule['name'];
329
  $this->archive_system->send_notification($to, $from, "Scheduled backup error","", "", $e->getMessage());
includes/class-xcloner-settings.php CHANGED
@@ -5,13 +5,21 @@ class Xcloner_Settings
5
  private $logger_file = "xcloner_main_%s.log";
6
  private $logger_file_hash = "xcloner%s.log";
7
  private $hash ;
 
 
8
 
9
- public function __construct($hash = "")
10
  {
 
11
  if(isset($hash))
12
  $this->set_hash($hash);
13
  }
14
 
 
 
 
 
 
15
  public function get_logger_filename($include_hash = 0)
16
  {
17
  if($include_hash)
@@ -195,7 +203,7 @@ class Xcloner_Settings
195
  public function settings_init()
196
  {
197
  global $wpdb;
198
- $this->xcloner_sanitization = new Xcloner_Sanitization();
199
 
200
  //ADDING MISSING OPTIONS
201
  if( false == get_option( 'xcloner_mysql_settings_page' ) ) {
@@ -459,7 +467,7 @@ class Xcloner_Settings
459
  'xcloner_system_settings_page',
460
  'xcloner_system_settings_group',
461
  array('xcloner_exclude_files_larger_than_mb',
462
- __('Use this option to automatically exclude files larger than a certain size in MB, or set to -1 to include all. Range 0-1000 MB','xcloner-backup-and-restore'),
463
  )
464
  );
465
 
5
  private $logger_file = "xcloner_main_%s.log";
6
  private $logger_file_hash = "xcloner%s.log";
7
  private $hash ;
8
+ private $xcloner_sanitization;
9
+ private $xcloner_container;
10
 
11
+ public function __construct(Xcloner $xcloner_container, $hash = "")
12
  {
13
+ $this->xcloner_container = $xcloner_container;
14
  if(isset($hash))
15
  $this->set_hash($hash);
16
  }
17
 
18
+ private function get_xcloner_container()
19
+ {
20
+ return $this->xcloner_container;
21
+ }
22
+
23
  public function get_logger_filename($include_hash = 0)
24
  {
25
  if($include_hash)
203
  public function settings_init()
204
  {
205
  global $wpdb;
206
+ $this->xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
207
 
208
  //ADDING MISSING OPTIONS
209
  if( false == get_option( 'xcloner_mysql_settings_page' ) ) {
467
  'xcloner_system_settings_page',
468
  'xcloner_system_settings_group',
469
  array('xcloner_exclude_files_larger_than_mb',
470
+ __('Use this option to automatically exclude files larger than a certain size in MB, or set to 0 to include all. Range 0-1000 MB','xcloner-backup-and-restore'),
471
  )
472
  );
473
 
includes/class-xcloner.php CHANGED
@@ -58,7 +58,17 @@ class Xcloner {
58
  * @var string $version The current version of the plugin.
59
  */
60
  protected $version;
61
-
 
 
 
 
 
 
 
 
 
 
62
  /**
63
  * Define the core functionality of the plugin.
64
  *
@@ -73,7 +83,7 @@ class Xcloner {
73
  register_shutdown_function(array($this, 'exception_handler'));
74
 
75
  $this->plugin_name = 'xcloner';
76
- $this->version = '4.0.1';
77
 
78
  $this->load_dependencies();
79
  $this->set_locale();
@@ -88,6 +98,56 @@ class Xcloner {
88
 
89
  }
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  public function check_dependencies(){
92
 
93
  $backup_storage_path = realpath(__DIR__.DS."..".DS."..".DS."..").DS."backups".DS;
@@ -227,7 +287,7 @@ class Xcloner {
227
  */
228
  require_once plugin_dir_path( dirname( __FILE__ ) ) . 'public/class-xcloner-public.php';
229
 
230
- $this->loader = new Xcloner_Loader();
231
 
232
  }
233
 
@@ -260,7 +320,7 @@ class Xcloner {
260
  */
261
  private function define_admin_hooks() {
262
 
263
- $plugin_admin = new Xcloner_Admin( $this->get_plugin_name(), $this->get_version() );
264
  $this->plugin_admin = $plugin_admin;
265
 
266
  $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
@@ -283,8 +343,25 @@ class Xcloner {
283
  /**
284
  * register wporg_settings_init to the admin_init action hook
285
  */
286
- $settings = new Xcloner_Settings();
287
- add_action('admin_init', array($settings, 'settings_init'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
289
 
290
  /**
@@ -296,7 +373,7 @@ class Xcloner {
296
  */
297
  private function define_public_hooks() {
298
 
299
- $plugin_public = new Xcloner_Public( $this->get_plugin_name(), $this->get_version() );
300
 
301
  $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
302
  $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
@@ -305,7 +382,7 @@ class Xcloner {
305
 
306
  public function exception_handler() {
307
 
308
- $logger = new XCloner_Logger("php_system");
309
  $error = error_get_last();
310
 
311
  if($error['type'] and $logger)
@@ -329,33 +406,37 @@ class Xcloner {
329
 
330
  private function define_ajax_hooks()
331
  {
332
- $plugin_public = new Xcloner_Public( $this->get_plugin_name(), $this->get_version() );
333
- //$this->loader->add_action( 'wp_ajax_get_database_tables_action', $plugin_public, array('Xcloner_Api','get_database_tables_action') );
334
-
335
- if(is_admin())
336
  {
337
- $xcloner_api = new Xcloner_Api();
 
 
 
 
 
 
 
 
338
 
339
- add_action( 'wp_ajax_get_database_tables_action' , array($xcloner_api,'get_database_tables_action') );
340
- add_action( 'wp_ajax_get_file_system_action' , array($xcloner_api,'get_file_system_action') );
341
- add_action( 'wp_ajax_scan_filesystem' , array($xcloner_api,'scan_filesystem') );
342
- add_action( 'wp_ajax_backup_database' , array($xcloner_api,'backup_database') );
343
- add_action( 'wp_ajax_backup_files' , array($xcloner_api,'backup_files') );
344
- add_action( 'wp_ajax_save_schedule' , array($xcloner_api,'save_schedule') );
345
- add_action( 'wp_ajax_get_schedule_by_id' , array($xcloner_api,'get_schedule_by_id') );
346
- add_action( 'wp_ajax_get_scheduler_list' , array($xcloner_api,'get_scheduler_list') );
347
- add_action( 'wp_ajax_delete_schedule_by_id' , array($xcloner_api,'delete_schedule_by_id') );
348
- add_action( 'wp_ajax_delete_backup_by_name' , array($xcloner_api,'delete_backup_by_name') );
349
- add_action( 'wp_ajax_download_backup_by_name' , array($xcloner_api,'download_backup_by_name') );
350
- add_action( 'wp_ajax_remote_storage_save_status' , array($xcloner_api,'remote_storage_save_status') );
351
- add_action( 'wp_ajax_upload_backup_to_remote' , array($xcloner_api,'upload_backup_to_remote') );
352
- add_action( 'wp_ajax_list_backup_files' , array($xcloner_api,'list_backup_files') );
353
- add_action( 'wp_ajax_restore_upload_backup' , array($xcloner_api,'restore_upload_backup') );
354
- add_action( 'wp_ajax_download_restore_script' , array($xcloner_api,'download_restore_script') );
355
- add_action( 'admin_notices', array($this, 'xcloner_error_admin_notices' ));
356
-
357
- //if (is_admin()) {
358
- add_filter('plugin_action_links', array($this, 'add_plugin_action_links'), 10, 2);
359
  }
360
 
361
  }
@@ -380,18 +461,11 @@ class Xcloner {
380
  add_filter( 'cron_schedules', array($this, 'add_new_intervals'));
381
 
382
 
383
- $xcloner_scheduler = new Xcloner_Scheduler();
384
  $xcloner_scheduler->update_wp_cron_hooks();
385
 
386
  }
387
 
388
- public function xcloner_scheduler_callback($schedule_id)
389
- {
390
- $cron = new Xcloner_Scheduler;
391
-
392
- $cron->run_schedule($schedule_id);
393
- }
394
-
395
  function add_new_intervals($schedules)
396
  {
397
  // add weekly and monthly intervals
@@ -449,11 +523,27 @@ class Xcloner {
449
  return $this->version;
450
  }
451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  public function display($page)
453
  {
454
- $plugin_admin = new Xcloner_Admin( $this->get_plugin_name(), $this->get_version() );
455
  $this->plugin_admin = $plugin_admin;
456
 
457
- $view = call_user_func_array(array($this->plugin_admin, $page), array());
458
  }
459
  }
58
  * @var string $version The current version of the plugin.
59
  */
60
  protected $version;
61
+
62
+ private $xcloner_settings;
63
+ private $xcloner_logger;
64
+ private $xcloner_sanitization;
65
+ private $xcloner_requirements;
66
+ private $xcloner_filesystem;
67
+ private $archive_system;
68
+ private $xcloner_database;
69
+ private $xcloner_scheduler;
70
+ private $xcloner_remote_storage;
71
+ private $xcloner_file_transfer;
72
  /**
73
  * Define the core functionality of the plugin.
74
  *
83
  register_shutdown_function(array($this, 'exception_handler'));
84
 
85
  $this->plugin_name = 'xcloner';
86
+ $this->version = '4.0.2';
87
 
88
  $this->load_dependencies();
89
  $this->set_locale();
98
 
99
  }
100
 
101
+ public function get_xcloner_settings()
102
+ {
103
+ return $this->xcloner_settings;
104
+ }
105
+
106
+ public function get_xcloner_filesystem()
107
+ {
108
+ return $this->xcloner_filesystem;
109
+ }
110
+
111
+ public function get_xcloner_logger()
112
+ {
113
+ return $this->xcloner_logger;
114
+ }
115
+
116
+ public function get_xcloner_sanitization()
117
+ {
118
+ return $this->xcloner_sanitization;
119
+ }
120
+
121
+ public function get_xcloner_requirements()
122
+ {
123
+ return $this->xcloner_requirements;
124
+ }
125
+
126
+ public function get_archive_system()
127
+ {
128
+ return $this->archive_system;
129
+ }
130
+
131
+ public function get_xcloner_database()
132
+ {
133
+ return $this->xcloner_database;
134
+ }
135
+
136
+ public function get_xcloner_scheduler()
137
+ {
138
+ return $this->xcloner_scheduler;
139
+ }
140
+
141
+ public function get_xcloner_remote_storage()
142
+ {
143
+ return $this->xcloner_remote_storage;
144
+ }
145
+
146
+ public function get_xcloner_file_transfer()
147
+ {
148
+ return $this->xcloner_file_transfer;
149
+ }
150
+
151
  public function check_dependencies(){
152
 
153
  $backup_storage_path = realpath(__DIR__.DS."..".DS."..".DS."..").DS."backups".DS;
287
  */
288
  require_once plugin_dir_path( dirname( __FILE__ ) ) . 'public/class-xcloner-public.php';
289
 
290
+ $this->loader = new Xcloner_Loader($this);
291
 
292
  }
293
 
320
  */
321
  private function define_admin_hooks() {
322
 
323
+ $plugin_admin = new Xcloner_Admin( $this );
324
  $this->plugin_admin = $plugin_admin;
325
 
326
  $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
343
  /**
344
  * register wporg_settings_init to the admin_init action hook
345
  */
346
+
347
+ $this->xcloner_settings = new XCloner_Settings($this);
348
+
349
+ if(defined('DOING_CRON') || isset($_POST['hash'])){
350
+
351
+ if(defined('DOING_CRON') || $_POST['hash'] == "generate_hash"){
352
+ $this->xcloner_settings->generate_new_hash();
353
+ }else{
354
+ $this->xcloner_settings->set_hash($_POST['hash']);
355
+ }
356
+ }
357
+
358
+ $this->xcloner_sanitization = new Xcloner_Sanitization();
359
+ $this->xcloner_requirements = new Xcloner_Requirements($this);
360
+
361
+ add_action('admin_init', array($this->xcloner_settings, 'settings_init'));
362
+
363
+ //adding links to the Manage Plugins Wordpress page for XCloner
364
+ add_filter('plugin_action_links', array($this, 'add_plugin_action_links'), 10, 2);
365
  }
366
 
367
  /**
373
  */
374
  private function define_public_hooks() {
375
 
376
+ $plugin_public = new Xcloner_Public( $this );
377
 
378
  $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
379
  $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
382
 
383
  public function exception_handler() {
384
 
385
+ $logger = new XCloner_Logger($this, "php_system");
386
  $error = error_get_last();
387
 
388
  if($error['type'] and $logger)
406
 
407
  private function define_ajax_hooks()
408
  {
409
+ if(is_admin() || defined('DOING_CRON'))
 
 
 
410
  {
411
+ $this->xcloner_logger = new XCloner_Logger($this, "xcloner_api");
412
+ $this->xcloner_filesystem = new Xcloner_File_System($this);
413
+ $this->archive_system = new Xcloner_Archive($this);
414
+ $this->xcloner_database = new Xcloner_Database($this);
415
+ $this->xcloner_scheduler = new Xcloner_Scheduler($this);
416
+ $this->xcloner_remote_storage = new Xcloner_Remote_Storage($this);
417
+ $this->xcloner_file_transfer = new Xcloner_File_Transfer($this);
418
+
419
+ $xcloner_api = new Xcloner_Api($this);
420
 
421
+ add_action( 'wp_ajax_get_database_tables_action', array($xcloner_api,'get_database_tables_action') );
422
+ add_action( 'wp_ajax_get_file_system_action', array($xcloner_api,'get_file_system_action') );
423
+ add_action( 'wp_ajax_scan_filesystem', array($xcloner_api,'scan_filesystem') );
424
+ add_action( 'wp_ajax_backup_database', array($xcloner_api,'backup_database') );
425
+ add_action( 'wp_ajax_backup_files' , array($xcloner_api,'backup_files') );
426
+ add_action( 'wp_ajax_save_schedule' , array($xcloner_api,'save_schedule') );
427
+ add_action( 'wp_ajax_get_schedule_by_id', array($xcloner_api,'get_schedule_by_id') );
428
+ add_action( 'wp_ajax_get_scheduler_list', array($xcloner_api,'get_scheduler_list') );
429
+ add_action( 'wp_ajax_delete_schedule_by_id' , array($xcloner_api,'delete_schedule_by_id') );
430
+ add_action( 'wp_ajax_delete_backup_by_name' , array($xcloner_api,'delete_backup_by_name') );
431
+ add_action( 'wp_ajax_download_backup_by_name', array($xcloner_api,'download_backup_by_name') );
432
+ add_action( 'wp_ajax_remote_storage_save_status', array($xcloner_api,'remote_storage_save_status') );
433
+ add_action( 'wp_ajax_upload_backup_to_remote', array($xcloner_api,'upload_backup_to_remote') );
434
+ add_action( 'wp_ajax_list_backup_files' , array($xcloner_api,'list_backup_files') );
435
+ add_action( 'wp_ajax_restore_upload_backup' , array($xcloner_api,'restore_upload_backup') );
436
+ add_action( 'wp_ajax_download_restore_script', array($xcloner_api,'download_restore_script') );
437
+ add_action( 'wp_ajax_copy_backup_remote_to_local', array($xcloner_api,'copy_backup_remote_to_local') );
438
+ add_action( 'admin_notices', array($this, 'xcloner_error_admin_notices' ));
439
+
 
440
  }
441
 
442
  }
461
  add_filter( 'cron_schedules', array($this, 'add_new_intervals'));
462
 
463
 
464
+ $xcloner_scheduler = $this->get_xcloner_scheduler();
465
  $xcloner_scheduler->update_wp_cron_hooks();
466
 
467
  }
468
 
 
 
 
 
 
 
 
469
  function add_new_intervals($schedules)
470
  {
471
  // add weekly and monthly intervals
523
  return $this->version;
524
  }
525
 
526
+ function xcloner_display()
527
+ {
528
+ // check user capabilities
529
+ if (!current_user_can('manage_options')) {
530
+ return;
531
+ }
532
+
533
+ $page = sanitize_key($_GET['page']);
534
+
535
+ if($page)
536
+ {
537
+ $this->display($page);
538
+ }
539
+
540
+ }
541
+
542
  public function display($page)
543
  {
544
+ $plugin_admin = new Xcloner_Admin($this);
545
  $this->plugin_admin = $plugin_admin;
546
 
547
+ call_user_func_array(array($this->plugin_admin, $page), array());
548
  }
549
  }
languages/xcloner-backup-and-restore-ro_RO.mo CHANGED
Binary file
languages/xcloner-backup-and-restore-ro_RO.po CHANGED
@@ -1,8 +1,8 @@
1
  msgid ""
2
  msgstr ""
3
  "Project-Id-Version: \n"
4
- "POT-Creation-Date: 2017-02-27 12:32+0200\n"
5
- "PO-Revision-Date: 2017-02-27 12:35+0200\n"
6
  "Last-Translator: \n"
7
  "Language-Team: \n"
8
  "Language: ro_RO\n"
@@ -20,31 +20,27 @@ msgstr ""
20
  "X-Poedit-SearchPathExcluded-0: admin/js\n"
21
  "X-Poedit-SearchPathExcluded-1: vendor\n"
22
 
23
- #: admin/class-xcloner-admin.php:200
24
  msgid "Settings Saved"
25
  msgstr "Setari Salvate"
26
 
27
- #: admin/class-xcloner-admin.php:219
28
  msgid "General Options"
29
  msgstr "Optiuni Generale"
30
 
31
- #: admin/class-xcloner-admin.php:220
32
  msgid "Mysql Options"
33
  msgstr "Optiuni Mysql"
34
 
35
- #: admin/class-xcloner-admin.php:221
36
  msgid "System Options"
37
  msgstr "Optiuni Sistem"
38
 
39
- #: admin/class-xcloner-admin.php:222
40
  msgid "Cleanup Options"
41
  msgstr "Optiuni Curatare"
42
 
43
- #: admin/class-xcloner-admin.php:223
44
- msgid "Cron Options"
45
- msgstr "Optiuni Cron"
46
-
47
- #: admin/partials/xcloner_console_page.php:28
48
  msgid "XCloner Debugger Dashboard"
49
  msgstr "Consola Debug XCloner"
50
 
@@ -61,7 +57,7 @@ msgid "Files Options"
61
  msgstr "Optiuni Fisiere"
62
 
63
  #: admin/partials/xcloner_generate_backups_page.php:18
64
- #: includes/class-xcloner.php:367
65
  msgid "Generate Backup"
66
  msgstr "Creare Backup"
67
 
@@ -71,8 +67,8 @@ msgstr "Programare Backup"
71
 
72
  #: admin/partials/xcloner_generate_backups_page.php:30
73
  #: admin/partials/xcloner_init_page.php:88
74
- #: admin/partials/xcloner_manage_backups_page.php:22
75
- #: admin/partials/xcloner_scheduled_backups_page.php:141
76
  msgid "Backup Name"
77
  msgstr "Nume Backup"
78
 
@@ -166,18 +162,18 @@ msgid "Backup Done"
166
  msgstr "Backup Terminat"
167
 
168
  #: admin/partials/xcloner_generate_backups_page.php:190
169
- #: admin/partials/xcloner_manage_backups_page.php:69
170
  msgid "Send Backup To Remote Storage"
171
  msgstr "Trimite Backup In Stocare La Distanta "
172
 
173
  #: admin/partials/xcloner_generate_backups_page.php:192
174
- #: admin/partials/xcloner_manage_backups_page.php:67
175
  msgid "Download Backup"
176
  msgstr "Descarcare Backup"
177
 
178
  #: admin/partials/xcloner_generate_backups_page.php:193
179
- #: admin/partials/xcloner_manage_backups_page.php:58
180
- #: admin/partials/xcloner_manage_backups_page.php:71
181
  msgid "List Backup Content"
182
  msgstr "Afiseaza Continutul Backup"
183
 
@@ -201,15 +197,15 @@ msgid "(Dismiss this notice."
201
  msgstr "(Anuleaza aceasta notificare."
202
 
203
  #: admin/partials/xcloner_generate_backups_page.php:227
204
- #: admin/partials/xcloner_scheduled_backups_page.php:50
205
  msgid "Current Server Time"
206
  msgstr "Timpul Curent Pe Server"
207
 
208
  #: admin/partials/xcloner_generate_backups_page.php:234
209
  #: admin/partials/xcloner_init_page.php:132
210
- #: admin/partials/xcloner_scheduled_backups_page.php:22
211
- #: admin/partials/xcloner_scheduled_backups_page.php:34
212
- #: admin/partials/xcloner_scheduled_backups_page.php:92
213
  msgid "Schedule Name"
214
  msgstr "Numele Programarii"
215
 
@@ -226,27 +222,27 @@ msgid "please select"
226
  msgstr "faceti o selectie"
227
 
228
  #: admin/partials/xcloner_generate_backups_page.php:253
229
- #: admin/partials/xcloner_scheduled_backups_page.php:105
230
  msgid "Don't Repeat"
231
  msgstr "O Singura Data"
232
 
233
  #: admin/partials/xcloner_generate_backups_page.php:254
234
- #: admin/partials/xcloner_scheduled_backups_page.php:106
235
  msgid "Hourly"
236
  msgstr "In Fiecare ora"
237
 
238
  #: admin/partials/xcloner_generate_backups_page.php:255
239
- #: admin/partials/xcloner_scheduled_backups_page.php:107
240
  msgid "Daily"
241
  msgstr "Zilnic"
242
 
243
  #: admin/partials/xcloner_generate_backups_page.php:256
244
- #: admin/partials/xcloner_scheduled_backups_page.php:108
245
  msgid "Weekly"
246
  msgstr "Saptamanal"
247
 
248
  #: admin/partials/xcloner_generate_backups_page.php:257
249
- #: admin/partials/xcloner_scheduled_backups_page.php:109
250
  msgid "Monthly"
251
  msgstr "Lunar"
252
 
@@ -255,7 +251,7 @@ msgid "Please Select Frequency to run"
255
  msgstr "Selectati Frecventa De Rulare"
256
 
257
  #: admin/partials/xcloner_generate_backups_page.php:267
258
- #: admin/partials/xcloner_scheduled_backups_page.php:118
259
  msgid "none"
260
  msgstr "nici una"
261
 
@@ -280,22 +276,22 @@ msgid "Close"
280
  msgstr "Inchide"
281
 
282
  #: admin/partials/xcloner_generate_backups_page.php:305
283
- #: admin/partials/xcloner_manage_backups_page.php:89
284
  msgid "Listing Backup Content "
285
  msgstr "Continutul Fisierului De Backup"
286
 
287
  #: admin/partials/xcloner_generate_backups_page.php:320
288
- #: admin/partials/xcloner_manage_backups_page.php:104
289
  msgid "Remote Storage Transfer"
290
  msgstr "Transfer In Stocare Externa"
291
 
292
  #: admin/partials/xcloner_generate_backups_page.php:329
293
- #: admin/partials/xcloner_manage_backups_page.php:113
294
  msgid "please select..."
295
  msgstr "faceti o selectie…"
296
 
297
  #: admin/partials/xcloner_generate_backups_page.php:341
298
- #: admin/partials/xcloner_manage_backups_page.php:125
299
  msgid "Uploading backup to the selected remote storage..."
300
  msgstr "Incarc backup-ul in spatiul de stocare selectat"
301
 
@@ -361,316 +357,483 @@ msgstr "Urmatoarea Executie"
361
  msgid "Unscheduled"
362
  msgstr "Neprogramat"
363
 
364
- #: admin/partials/xcloner_init_page.php:177
 
 
 
 
365
  msgid "System Check"
366
  msgstr "Verificare Sistem"
367
 
368
- #: admin/partials/xcloner_init_page.php:180
369
- #: includes/class-xcloner-settings.php:276
370
  msgid "Backup Start Location"
371
  msgstr "Calea Inceput Backup"
372
 
373
- #: admin/partials/xcloner_init_page.php:183
374
- #: includes/class-xcloner-settings.php:290
375
  msgid "Backup Storage Location"
376
  msgstr "Calea Stocare Backup"
377
 
378
- #: admin/partials/xcloner_init_page.php:186
379
  msgid "Temporary Location"
380
  msgstr "Locatia Temporara"
381
 
382
- #: admin/partials/xcloner_init_page.php:190
383
  msgid "PHP Version Check"
384
  msgstr "Verificare Versiune PHP"
385
 
386
- #: admin/partials/xcloner_init_page.php:194
387
  msgid "PHP Safe Mode"
388
  msgstr ""
389
 
390
- #: admin/partials/xcloner_init_page.php:198
391
  msgid "BACKUP READY"
392
  msgstr "PREGATIT DE BACKUP"
393
 
394
- #: admin/partials/xcloner_init_page.php:198
395
  msgid "Backup not ready, please check above requirements"
396
  msgstr "Sistem Backup Invalid, va rog verificati cerintele date"
397
 
398
- #: admin/partials/xcloner_init_page.php:204
399
  msgid "PHP max_execution_time"
400
  msgstr ""
401
 
402
- #: admin/partials/xcloner_init_page.php:207
403
  msgid "PHP memory_limit"
404
  msgstr ""
405
 
406
- #: admin/partials/xcloner_init_page.php:210
407
  msgid "PHP open_basedir"
408
  msgstr ""
409
 
410
- #: admin/partials/xcloner_init_page.php:218
411
  msgid "Reading Time 1MB Block"
412
  msgstr "Timp citire bloc de 1MB "
413
 
414
- #: admin/partials/xcloner_init_page.php:218
415
  #: admin/partials/xcloner_init_page.php:221
 
416
  msgid "unknown"
417
  msgstr "necunoscut"
418
 
419
- #: admin/partials/xcloner_init_page.php:221
420
  msgid "Writing Time 1MB Block"
421
  msgstr "Timp scriere bloc de 1M"
422
 
423
- #: admin/partials/xcloner_init_page.php:224
424
  msgid "Free Disk Space"
425
  msgstr "Spatiul Disc Liber"
426
 
427
- #: admin/partials/xcloner_init_page.php:229
428
  msgid "Toggle Additional System Info"
429
  msgstr "Arata Informatii Sistem Aditionale"
430
 
431
- #: admin/partials/xcloner_manage_backups_page.php:23
 
 
 
 
 
 
 
 
432
  msgid "Created Time"
433
  msgstr "Timpul Creat"
434
 
435
- #: admin/partials/xcloner_manage_backups_page.php:24
436
  msgid "Size"
437
  msgstr "Marime"
438
 
439
- #: admin/partials/xcloner_manage_backups_page.php:25
440
- #: admin/partials/xcloner_scheduled_backups_page.php:28
441
- #: admin/partials/xcloner_scheduled_backups_page.php:40
442
  msgid "Action"
443
  msgstr "Actiune"
444
 
445
- #: admin/partials/xcloner_manage_backups_page.php:72
 
 
 
 
 
 
 
 
 
 
446
  msgid "Delete Backup"
447
  msgstr "Stergere Backup"
448
 
449
- #: admin/partials/xcloner_manage_backups_page.php:83
450
  msgid "Delete"
451
  msgstr "Sterge"
452
 
453
- #: admin/partials/xcloner_manage_backups_page.php:109
 
 
 
 
 
 
 
 
454
  #, php-format
455
  msgid "Send %s to remote storage"
456
  msgstr "Trimite %s in spatiul de stocare extern"
457
 
458
- #: admin/partials/xcloner_remote_storage_page.php:13
459
- msgid "Ftp Storage"
460
- msgstr "Stocare Ftp"
461
 
462
- #: admin/partials/xcloner_remote_storage_page.php:28
463
- #: admin/partials/xcloner_remote_storage_page.php:31
464
  msgid "Ftp Hostname"
465
  msgstr ""
466
 
467
- #: admin/partials/xcloner_remote_storage_page.php:34
468
  msgid "Ftp Port"
469
  msgstr ""
470
 
471
- #: admin/partials/xcloner_remote_storage_page.php:40
472
- #: admin/partials/xcloner_remote_storage_page.php:43
473
  msgid "Ftp Username"
474
  msgstr ""
475
 
476
- #: admin/partials/xcloner_remote_storage_page.php:50
477
- #: admin/partials/xcloner_remote_storage_page.php:53
478
  msgid "Ftp Password"
479
  msgstr ""
480
 
481
- #: admin/partials/xcloner_remote_storage_page.php:59
482
- #: admin/partials/xcloner_remote_storage_page.php:62
483
  msgid "Ftp Storage Folder"
484
  msgstr ""
485
 
486
- #: admin/partials/xcloner_remote_storage_page.php:68
487
  msgid "Ftp Transfer Mode"
488
  msgstr ""
489
 
490
- #: admin/partials/xcloner_remote_storage_page.php:72
491
  msgid "Passive"
492
  msgstr "Pasiv"
493
 
494
- #: admin/partials/xcloner_remote_storage_page.php:75
495
  msgid "Active"
496
  msgstr "Activ"
497
 
498
- #: admin/partials/xcloner_remote_storage_page.php:81
499
  msgid "Ftp Secure Connection"
500
  msgstr "Conexiune Securizata Ftp"
501
 
502
- #: admin/partials/xcloner_remote_storage_page.php:85
503
  msgid "Disable"
504
  msgstr "Dezactivati"
505
 
506
- #: admin/partials/xcloner_remote_storage_page.php:88
507
  msgid "Enable"
508
  msgstr "Activati"
509
 
510
- #: admin/partials/xcloner_remote_storage_page.php:94
511
- #: admin/partials/xcloner_remote_storage_page.php:97
512
  msgid "Ftp Timeout"
513
  msgstr ""
514
 
515
- #: admin/partials/xcloner_remote_storage_page.php:103
516
  msgid "Ftp Cleanup (days)"
517
  msgstr "Curatare Ftp(zile)"
518
 
519
- #: admin/partials/xcloner_remote_storage_page.php:106
520
- #: admin/partials/xcloner_remote_storage_page.php:204
521
- #: admin/partials/xcloner_remote_storage_page.php:293
522
- #: admin/partials/xcloner_remote_storage_page.php:374
523
- #: admin/partials/xcloner_remote_storage_page.php:456
 
 
 
524
  msgid "how many days to keep the backups for"
525
  msgstr "pentru cate zile sa pastrez fisierele de backup"
526
 
527
- #: admin/partials/xcloner_remote_storage_page.php:112
528
- #: admin/partials/xcloner_remote_storage_page.php:210
529
- #: admin/partials/xcloner_remote_storage_page.php:299
530
- #: admin/partials/xcloner_remote_storage_page.php:380
531
- #: admin/partials/xcloner_remote_storage_page.php:462
 
 
 
532
  msgid "Save Settings"
533
  msgstr "Salveaza Setari"
534
 
535
- #: admin/partials/xcloner_remote_storage_page.php:117
536
- #: admin/partials/xcloner_remote_storage_page.php:215
537
- #: admin/partials/xcloner_remote_storage_page.php:304
538
- #: admin/partials/xcloner_remote_storage_page.php:385
539
- #: admin/partials/xcloner_remote_storage_page.php:467
 
 
 
540
  msgid "Verify"
541
  msgstr "Verifica"
542
 
543
- #: admin/partials/xcloner_remote_storage_page.php:128
544
  msgid "SFTP Storage"
545
  msgstr "Stocare SFTP"
546
 
547
- #: admin/partials/xcloner_remote_storage_page.php:143
548
- #: admin/partials/xcloner_remote_storage_page.php:146
549
  msgid "SFTP Hostname"
550
  msgstr ""
551
 
552
- #: admin/partials/xcloner_remote_storage_page.php:149
553
  msgid "SFTP Port"
554
  msgstr ""
555
 
556
- #: admin/partials/xcloner_remote_storage_page.php:155
557
- #: admin/partials/xcloner_remote_storage_page.php:158
558
  msgid "SFTP Username"
559
  msgstr ""
560
 
561
- #: admin/partials/xcloner_remote_storage_page.php:165
562
- #: admin/partials/xcloner_remote_storage_page.php:168
563
  msgid "SFTP Password"
564
  msgstr ""
565
 
566
- #: admin/partials/xcloner_remote_storage_page.php:174
567
- #: admin/partials/xcloner_remote_storage_page.php:177
568
  msgid "SFTP Private Key"
569
  msgstr ""
570
 
571
- #: admin/partials/xcloner_remote_storage_page.php:183
572
- #: admin/partials/xcloner_remote_storage_page.php:186
573
  msgid "SFTP Storage Folder"
574
  msgstr ""
575
 
576
- #: admin/partials/xcloner_remote_storage_page.php:192
577
- #: admin/partials/xcloner_remote_storage_page.php:195
578
  msgid "SFTP Timeout"
579
  msgstr ""
580
 
581
- #: admin/partials/xcloner_remote_storage_page.php:201
582
  msgid "SFTP Cleanup (days)"
583
  msgstr "Curatare SFTP(zile)"
584
 
585
- #: admin/partials/xcloner_remote_storage_page.php:227
586
  msgid "AWS Storage"
587
  msgstr ""
588
 
589
- #: admin/partials/xcloner_remote_storage_page.php:247
590
  #, php-format
591
  msgid "Visit %s and get your \"Key\" and \"Secret\"."
592
  msgstr ""
593
 
594
- #: admin/partials/xcloner_remote_storage_page.php:254
595
- #: admin/partials/xcloner_remote_storage_page.php:257
596
  msgid "AWS Key"
597
  msgstr ""
598
 
599
- #: admin/partials/xcloner_remote_storage_page.php:263
600
- #: admin/partials/xcloner_remote_storage_page.php:266
601
  msgid "AWS Secret"
602
  msgstr ""
603
 
604
- #: admin/partials/xcloner_remote_storage_page.php:272
605
- #: admin/partials/xcloner_remote_storage_page.php:275
606
  msgid "AWS Region"
607
  msgstr ""
608
 
609
- #: admin/partials/xcloner_remote_storage_page.php:281
610
- #: admin/partials/xcloner_remote_storage_page.php:284
611
  msgid "AWS Bucket Name"
612
  msgstr ""
613
 
614
- #: admin/partials/xcloner_remote_storage_page.php:290
615
  msgid "AWS Cleanup (days)"
616
  msgstr "Curatare AWS(zile)"
617
 
618
- #: admin/partials/xcloner_remote_storage_page.php:316
619
  msgid "Dropbox Storage"
620
  msgstr "Stocare Dropbox"
621
 
622
- #: admin/partials/xcloner_remote_storage_page.php:336
623
  #, php-format
624
  msgid "Visit %s and get your \"App secret\"."
625
  msgstr ""
626
 
627
- #: admin/partials/xcloner_remote_storage_page.php:343
628
- #: admin/partials/xcloner_remote_storage_page.php:346
629
  msgid "Dropbox Access Token"
630
  msgstr ""
631
 
632
- #: admin/partials/xcloner_remote_storage_page.php:353
633
- #: admin/partials/xcloner_remote_storage_page.php:356
634
  msgid "Dropbox App Secret"
635
  msgstr ""
636
 
637
- #: admin/partials/xcloner_remote_storage_page.php:362
638
- #: admin/partials/xcloner_remote_storage_page.php:365
639
  msgid "Dropbox Prefix"
640
  msgstr ""
641
 
642
- #: admin/partials/xcloner_remote_storage_page.php:371
643
  msgid "Dropbox Cleanup (days)"
644
  msgstr "Curatare Dropbox(zile)"
645
 
646
- #: admin/partials/xcloner_remote_storage_page.php:398
647
  msgid "Azure Blog Storage"
648
  msgstr ""
649
 
650
- #: admin/partials/xcloner_remote_storage_page.php:418
651
  #, php-format
652
  msgid "Visit %s and get your \"Api Key\"."
653
  msgstr "Vizitati %s pentru a obtine cheia \"Api Key\""
654
 
655
- #: admin/partials/xcloner_remote_storage_page.php:425
656
- #: admin/partials/xcloner_remote_storage_page.php:428
657
  msgid "Azure Account Name"
658
  msgstr ""
659
 
660
- #: admin/partials/xcloner_remote_storage_page.php:435
661
- #: admin/partials/xcloner_remote_storage_page.php:438
662
  msgid "Azure Api Key"
663
  msgstr ""
664
 
665
- #: admin/partials/xcloner_remote_storage_page.php:444
666
- #: admin/partials/xcloner_remote_storage_page.php:447
667
  msgid "Azure Container"
668
  msgstr ""
669
 
670
- #: admin/partials/xcloner_remote_storage_page.php:453
671
  msgid "Azure Cleanup (days)"
672
  msgstr "Curatare Azure(zile)"
673
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  #: admin/partials/xcloner_restore_page.php:24
675
  msgid "Restore Script Upload"
676
  msgstr "Incarcare Script Restaurare"
@@ -869,7 +1032,7 @@ msgstr "Stergeti script-ul de restaurare"
869
  msgid "Finish"
870
  msgstr "Terminare"
871
 
872
- #: admin/partials/xcloner_scheduled_backups_page.php:9
873
  #, php-format
874
  msgid ""
875
  "We have noticed that DISABLE_WP_CRON is disabled, we recommend enabling that "
@@ -880,82 +1043,82 @@ msgstr ""
880
  "activati pentru a putea rula programul manual prin intermediul server-ului "
881
  "de hosting asa cum este explicat <a href=\"%s\" target=\"_blank\">aici</a>"
882
 
883
- #: admin/partials/xcloner_scheduled_backups_page.php:21
884
- #: admin/partials/xcloner_scheduled_backups_page.php:33
885
  msgid "ID"
886
  msgstr "ID"
887
 
888
- #: admin/partials/xcloner_scheduled_backups_page.php:23
889
- #: admin/partials/xcloner_scheduled_backups_page.php:35
890
  msgid "Recurrence"
891
  msgstr "Recurenta"
892
 
893
- #: admin/partials/xcloner_scheduled_backups_page.php:24
894
- #: admin/partials/xcloner_scheduled_backups_page.php:36
895
  msgid "Next Execution"
896
  msgstr "Urmatoarea Executie"
897
 
898
- #: admin/partials/xcloner_scheduled_backups_page.php:25
899
- #: admin/partials/xcloner_scheduled_backups_page.php:37
900
- #: includes/class-xcloner-loader.php:66
901
  msgid "Remote Storage"
902
  msgstr "Stocare Externa"
903
 
904
- #: admin/partials/xcloner_scheduled_backups_page.php:26
905
- #: admin/partials/xcloner_scheduled_backups_page.php:38
906
  msgid "Last Backup"
907
  msgstr "Ultimul Backup"
908
 
909
- #: admin/partials/xcloner_scheduled_backups_page.php:27
910
- #: admin/partials/xcloner_scheduled_backups_page.php:39
911
  msgid "Status"
912
  msgstr "Status"
913
 
914
- #: admin/partials/xcloner_scheduled_backups_page.php:64
915
  msgid "Edit Schedule"
916
  msgstr "Editare Programare"
917
 
918
- #: admin/partials/xcloner_scheduled_backups_page.php:70
919
  msgid "Off"
920
  msgstr ""
921
 
922
- #: admin/partials/xcloner_scheduled_backups_page.php:73
923
  msgid "On"
924
  msgstr ""
925
 
926
- #: admin/partials/xcloner_scheduled_backups_page.php:82
927
  msgid "Scheduler Settings"
928
  msgstr "Setari Program"
929
 
930
- #: admin/partials/xcloner_scheduled_backups_page.php:83
931
  msgid "Advanced"
932
  msgstr "Avansat"
933
 
934
- #: admin/partials/xcloner_scheduled_backups_page.php:99
935
  msgid "Schedule Start At"
936
  msgstr "Programul Incepe La"
937
 
938
- #: admin/partials/xcloner_scheduled_backups_page.php:104
939
  msgid "Schedule Recurrence"
940
  msgstr "Recurenta Program"
941
 
942
- #: admin/partials/xcloner_scheduled_backups_page.php:123
943
  msgid "Send To Remote Storage "
944
  msgstr "Trimite In Spatiul De Storcare La Distanta"
945
 
946
- #: admin/partials/xcloner_scheduled_backups_page.php:131
947
  msgid "Email Notification Address"
948
  msgstr "Adresa E-Mail Pentru Notificari"
949
 
950
- #: admin/partials/xcloner_scheduled_backups_page.php:148
951
  msgid "Included Database Data"
952
  msgstr "Include Baza De Data"
953
 
954
- #: admin/partials/xcloner_scheduled_backups_page.php:155
955
  msgid "Excluded Files"
956
  msgstr "Exclude Fisiere"
957
 
958
- #: admin/partials/xcloner_scheduled_backups_page.php:164
959
  msgid "Save"
960
  msgstr "Salveaza"
961
 
@@ -972,39 +1135,39 @@ msgstr ""
972
  msgid "XCloner Activation Error"
973
  msgstr "Eroare Activare XCloner"
974
 
975
- #: includes/class-xcloner-api.php:622
976
  msgid "executed"
977
  msgstr "executat"
978
 
979
- #: includes/class-xcloner-archive.php:148
980
  #, php-format
981
  msgid "New backup generated %s"
982
  msgstr "Backup nou creat %s"
983
 
984
- #: includes/class-xcloner-archive.php:150
985
  #, php-format
986
  msgid "Generated Backup Size: %s"
987
  msgstr "Marime backup creat: %s"
988
 
989
- #: includes/class-xcloner-archive.php:158
990
  #, php-format
991
  msgid "Backup Parts: %s"
992
  msgstr "Parti Backup: %s"
993
 
994
- #: includes/class-xcloner-archive.php:171
995
  msgid "Backup Comments: "
996
  msgstr "Comentarii Backup:"
997
 
998
- #: includes/class-xcloner-archive.php:176
999
  msgid "Latest 50 Log Lines: "
1000
  msgstr "Ultimele 50 de linii log:"
1001
 
1002
- #: includes/class-xcloner-archive.php:229
1003
  #, php-format
1004
  msgid "Initializing the backup archive %s"
1005
  msgstr "Initializare archiva de backup %s"
1006
 
1007
- #: includes/class-xcloner-archive.php:238
1008
  #, php-format
1009
  msgid "Opening for append the backup archive %s"
1010
  msgstr "Deschid pentru adaugare arhiva %s"
@@ -1022,133 +1185,139 @@ msgstr "Setez header mysql"
1022
  msgid "Getting number of tables in %s"
1023
  msgstr "Obtin numarul de tabele din %s"
1024
 
1025
- #: includes/class-xcloner-database.php:279
1026
  #, php-format
1027
  msgid "Excluding table %s.%s from backup"
1028
  msgstr "Exclud tabela %s.%s din backup"
1029
 
1030
- #: includes/class-xcloner-database.php:293
1031
  msgid "Preparing the database recursion file"
1032
  msgstr "Prepar fisierul de recursie al bazei de date"
1033
 
1034
- #: includes/class-xcloner-database.php:376
1035
  #, php-format
1036
  msgid "Starting new backup dump to file %s"
1037
  msgstr "Incep un nou backup al bazei de date in fisierul %s"
1038
 
1039
- #: includes/class-xcloner-database.php:519
1040
  #, php-format
1041
  msgid "Dumping %s records starting position %s from %s.%s table"
1042
  msgstr "Incarc %s randuri incepand de la pozitia %s din %s.%s tabela"
1043
 
1044
- #: includes/class-xcloner-database.php:527
1045
  #, php-format
1046
  msgid "Dumping the structure for %s.%s table"
1047
  msgstr "Salvez structura pentru tabela %s.%s"
1048
 
1049
- #: includes/class-xcloner-database.php:620
1050
  #, php-format
1051
  msgid "Writing %s database dump headers"
1052
  msgstr "Scrie %s capul bazei de date"
1053
 
1054
- #: includes/class-xcloner-file-system.php:313
1055
  #, php-format
1056
  msgid "Starting the filesystem scanner on root folder %s"
1057
  msgstr "Incep scanarea sistemului de fisiere din %s"
1058
 
1059
- #: includes/class-xcloner-file-system.php:465
1060
  #, php-format
1061
  msgid "Excluding %s from the filesystem list, file not readable"
1062
  msgstr "Exclud %s din sistemul de fisiere, fisierul nu este accesibil"
1063
 
1064
- #: includes/class-xcloner-file-system.php:468
1065
  #, php-format
1066
  msgid "Adding %s to the filesystem list"
1067
  msgstr "Adaug %s in lista de fisiere"
1068
 
1069
- #: includes/class-xcloner-file-system.php:476
1070
  #, php-format
1071
  msgid "Excluding %s from the filesystem list, matching pattern %s"
1072
  msgstr "Exclud %s din lista de fisiere, pattern gasit %s"
1073
 
1074
- #: includes/class-xcloner-loader.php:59
1075
  msgid "Site Backup"
1076
  msgstr "Backup Site"
1077
 
1078
- #: includes/class-xcloner-loader.php:64
1079
  msgid "XCloner Dashboard"
1080
  msgstr "Panou Start XCloner"
1081
 
1082
- #: includes/class-xcloner-loader.php:64
1083
  msgid "Dashboard"
1084
  msgstr "Panou Start"
1085
 
1086
- #: includes/class-xcloner-loader.php:65
1087
  msgid "XCloner Backup Settings"
1088
  msgstr "XCloner Setari Backup"
1089
 
1090
- #: includes/class-xcloner-loader.php:65 includes/class-xcloner.php:366
1091
  msgid "Settings"
1092
  msgstr "Setari"
1093
 
1094
- #: includes/class-xcloner-loader.php:66
1095
  msgid "Remote Storage Settings"
1096
  msgstr "Setari Stocare Externa"
1097
 
1098
- #: includes/class-xcloner-loader.php:67
1099
  msgid "Manage Backups"
1100
  msgstr "Administrare Backup"
1101
 
1102
- #: includes/class-xcloner-loader.php:68
1103
  msgid "Scheduled Backups"
1104
  msgstr "Programare Backup"
1105
 
1106
- #: includes/class-xcloner-loader.php:69
1107
  msgid "Generate Backups"
1108
  msgstr "Generare Backup"
1109
 
1110
- #: includes/class-xcloner-loader.php:70
1111
  msgid "Restore Backups"
1112
  msgstr "Restaurare Backup"
1113
 
1114
- #: includes/class-xcloner-remote-storage.php:118
1115
  #, php-format
1116
  msgid "%s storage settings saved."
1117
  msgstr "%s setarile de stocare au fost salvate."
1118
 
1119
- #: includes/class-xcloner-remote-storage.php:125
1120
  #, php-format
1121
  msgid "%s connection is valid."
1122
  msgstr "%s conexiunea este valida."
1123
 
1124
- #: includes/class-xcloner-remote-storage.php:149
1125
- msgid "Could not write data"
1126
- msgstr "Nu pot scrie data"
1127
-
1128
- #: includes/class-xcloner-remote-storage.php:154
1129
  msgid "Could not read data"
1130
  msgstr "Nu pot citi data"
1131
 
1132
- #: includes/class-xcloner-remote-storage.php:159
 
 
 
 
1133
  msgid "Could not delete data"
1134
  msgstr "Nu pot sterge data"
1135
 
1136
- #: includes/class-xcloner-settings.php:219
1137
- #: includes/class-xcloner-settings.php:226
1138
- #: includes/class-xcloner-settings.php:242
1139
- #: includes/class-xcloner-settings.php:251
 
 
 
 
 
1140
  msgid " "
1141
  msgstr ""
1142
 
1143
- #: includes/class-xcloner-settings.php:234
1144
  msgid "These are advanced options recommended for developers!"
1145
  msgstr "Acestea sunt optiuni avansate pentru dezvoltatori!"
1146
 
1147
- #: includes/class-xcloner-settings.php:262
1148
  msgid "Backup Compression Level"
1149
  msgstr "Nivelul De Compresie Backup"
1150
 
1151
- #: includes/class-xcloner-settings.php:267
1152
  msgid ""
1153
  "Options between [0-9]. Value 0 means no compression, while 9 is maximum "
1154
  "compression affecting cpu load"
@@ -1156,19 +1325,19 @@ msgstr ""
1156
  "Optiuni intre [0-9]. Valoarea 0 inseamna nici o compresie, in timp ce 9 este "
1157
  "compresia maxima care insa poate afecta viteza de rulare a procesorului"
1158
 
1159
- #: includes/class-xcloner-settings.php:281
1160
  msgid "Base path location from where XCloner can start the Backup."
1161
  msgstr "Locatie de start de unde XCloner va citi fisierele"
1162
 
1163
- #: includes/class-xcloner-settings.php:295
1164
  msgid "Location where XCloner will store the Backup archives."
1165
  msgstr "Locatia unde XCloner v-a stoca fisierele de backup."
1166
 
1167
- #: includes/class-xcloner-settings.php:304
1168
  msgid "Enable XCloner Backup Log"
1169
  msgstr "Logare XCloner"
1170
 
1171
- #: includes/class-xcloner-settings.php:309
1172
  #, php-format
1173
  msgid ""
1174
  "Enable the XCloner Backup log. You will find it stored unde the Backup "
@@ -1177,11 +1346,11 @@ msgstr ""
1177
  "Activati logarea XCloner Backup. Veti gasi fisierul de logare in Calea de "
1178
  "Stocare, fisier %s"
1179
 
1180
- #: includes/class-xcloner-settings.php:316
1181
  msgid "Regex Exclude Files"
1182
  msgstr "Exclude Fisiere Prin Regex"
1183
 
1184
- #: includes/class-xcloner-settings.php:321
1185
  msgid ""
1186
  "Regular expression match to exclude files and folders, example patterns "
1187
  "provided below, one pattern per line"
@@ -1189,21 +1358,21 @@ msgstr ""
1189
  "Folosti expresii Regex pentru a exclude fisiere si directoare, exemple de "
1190
  "expresii sunt afisate mai jos, o singura expresie pe linie"
1191
 
1192
- #: includes/class-xcloner-settings.php:331
1193
  msgid "Enable Mysql Backup"
1194
  msgstr "Backup Mysql"
1195
 
1196
- #: includes/class-xcloner-settings.php:336
1197
  msgid ""
1198
  "Enable Mysql Backup Option. If you don't want to backup the database, you "
1199
  "can disable this."
1200
  msgstr "Activati optiunea de backup Mysql."
1201
 
1202
- #: includes/class-xcloner-settings.php:343
1203
  msgid "Backup only WP tables"
1204
  msgstr "Backup doar tabelele WP"
1205
 
1206
- #: includes/class-xcloner-settings.php:348
1207
  #, php-format
1208
  msgid ""
1209
  "Enable this if you only want to Backup only tables starting with '%s' prefix"
@@ -1211,157 +1380,170 @@ msgstr ""
1211
  "Activati optiunea aceasta doar daca vreti sa stocati tabelele care incep cu "
1212
  "prefixul ‘%s’"
1213
 
1214
- #: includes/class-xcloner-settings.php:355
1215
  msgid "Mysql Hostname"
1216
  msgstr ""
1217
 
1218
- #: includes/class-xcloner-settings.php:360
1219
  msgid "Wordpress mysql hostname"
1220
  msgstr ""
1221
 
1222
- #: includes/class-xcloner-settings.php:369
1223
  msgid "Mysql Username"
1224
  msgstr ""
1225
 
1226
- #: includes/class-xcloner-settings.php:374
1227
  msgid "Wordpress mysql username"
1228
  msgstr ""
1229
 
1230
- #: includes/class-xcloner-settings.php:383
1231
  msgid "Mysql Database"
1232
  msgstr ""
1233
 
1234
- #: includes/class-xcloner-settings.php:388
1235
  msgid "Wordpress mysql database"
1236
  msgstr ""
1237
 
1238
- #: includes/class-xcloner-settings.php:398
1239
  msgid "Data Size Limit Per Request"
1240
  msgstr "Limita Procesare Date Pe Actiune"
1241
 
1242
- #: includes/class-xcloner-settings.php:403
1243
  msgid ""
1244
  "Use this option to set how much file data can XCloner backup in one AJAX "
1245
  "request. Range 0-1024 MB"
1246
  msgstr ""
1247
 
1248
- #: includes/class-xcloner-settings.php:412
1249
  msgid "Files To Process Per Request"
1250
  msgstr "Numar Fisiesre De Procesat Pe Actiune"
1251
 
1252
- #: includes/class-xcloner-settings.php:417
1253
  msgid ""
1254
  "Use this option to set how many files XCloner should process at one time "
1255
  "before doing another AJAX call"
1256
  msgstr ""
1257
 
1258
- #: includes/class-xcloner-settings.php:426
1259
  msgid "Directories To Scan Per Request"
1260
  msgstr "Numar Dosare De Procesat Pe Actiune"
1261
 
1262
- #: includes/class-xcloner-settings.php:431
1263
  msgid ""
1264
  "Use this option to set how many directories XCloner should scan at one time "
1265
  "before doing another AJAX call"
1266
  msgstr ""
1267
 
1268
- #: includes/class-xcloner-settings.php:440
1269
  msgid "Database Records Per Request"
1270
  msgstr "Limita Inregistrari Baze Date Pe Actiune"
1271
 
1272
- #: includes/class-xcloner-settings.php:445
1273
  msgid ""
1274
  "Use this option to set how many database table records should be fetched per "
1275
  "AJAX request, or set to 0 to fetch all. Range 0-100000 records"
1276
  msgstr ""
1277
 
1278
- #: includes/class-xcloner-settings.php:454
1279
  msgid "Exclude files larger than (MB)"
1280
  msgstr "Exclude fisierele mai mari decat(MB)"
1281
 
1282
- #: includes/class-xcloner-settings.php:459
1283
  msgid ""
1284
  "Use this option to automatically exclude files larger than a certain size in "
1285
- "MB, or set to -1 to include all. Range 0-1000 MB"
1286
  msgstr ""
1287
 
1288
- #: includes/class-xcloner-settings.php:466
1289
  msgid "Split Backup Archive Limit (MB)"
1290
  msgstr "Limita Impartire Arhiva(MB)"
1291
 
1292
- #: includes/class-xcloner-settings.php:471
1293
  msgid ""
1294
  "Use this option to automatically split the backup archive into smaller "
1295
  "parts. Range 0-10000 MB"
1296
  msgstr ""
1297
 
1298
- #: includes/class-xcloner-settings.php:478
1299
  msgid "Force Temporary Path Within XCloner Storage"
1300
  msgstr ""
1301
 
1302
- #: includes/class-xcloner-settings.php:483
1303
  msgid ""
1304
  "Enable this option if you want the XCloner Temporary Path to be within your "
1305
  "XCloner Storage Location"
1306
  msgstr ""
1307
 
1308
- #: includes/class-xcloner-settings.php:491
1309
  msgid "Cleanup by Date(days)"
1310
  msgstr "Limita Zile"
1311
 
1312
- #: includes/class-xcloner-settings.php:496
1313
  msgid ""
1314
  "Specify the maximum number of days a backup archive can be kept on the "
1315
  "server. 0 disables this option"
1316
  msgstr ""
1317
 
1318
- #: includes/class-xcloner-settings.php:503
1319
  msgid "Cleanup by Quantity"
1320
  msgstr "Limita Fisiere"
1321
 
1322
- #: includes/class-xcloner-settings.php:508
1323
  msgid ""
1324
  "Specify the maximum number of backup archives to keep on the server. 0 "
1325
  "disables this option"
1326
  msgstr ""
1327
 
1328
- #: includes/class-xcloner-settings.php:515
1329
  msgid "Cleanup by Capacity(MB)"
1330
  msgstr "Capacitate Stocare Maxima(MB)"
1331
 
1332
- #: includes/class-xcloner-settings.php:520
1333
  msgid ""
1334
  "Remove oldest backups if all created backups exceed the configured limit in "
1335
  "Megabytes. 0 disables this option"
1336
  msgstr ""
1337
 
1338
- #: includes/class-xcloner-settings.php:528
1339
- #: includes/class-xcloner-settings.php:533
1340
  msgid "Cron frequency"
1341
  msgstr "Frecventa Cron"
1342
 
1343
- #: includes/class-xcloner.php:102
1344
  #, php-format
1345
  msgid ""
1346
  "Unable to create the Backup Storage Location Folder %s . Please fix this "
1347
  "before starting the backup process."
1348
  msgstr ""
1349
 
1350
- #: includes/class-xcloner.php:110
1351
  #, php-format
1352
  msgid ""
1353
  "Unable to write to the Backup Storage Location Folder %s . Please fix this "
1354
  "before starting the backup process."
1355
  msgstr ""
1356
 
1357
- #: includes/class-xcloner.php:400
1358
  msgid "Once Weekly"
1359
  msgstr "O data pe saptamana"
1360
 
1361
- #: includes/class-xcloner.php:405
1362
  msgid "Once a month"
1363
  msgstr "O data pe luna"
1364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1365
  #~ msgid "Provide url below to the <u>xcloner_restore.php</u> restore script"
1366
  #~ msgstr ""
1367
  #~ "Furnizati adresa web catre script-ul de restaurare <u>xcloner_restore."
1
  msgid ""
2
  msgstr ""
3
  "Project-Id-Version: \n"
4
+ "POT-Creation-Date: 2017-03-14 11:47+0200\n"
5
+ "PO-Revision-Date: 2017-03-15 20:47+0200\n"
6
  "Last-Translator: \n"
7
  "Language-Team: \n"
8
  "Language: ro_RO\n"
20
  "X-Poedit-SearchPathExcluded-0: admin/js\n"
21
  "X-Poedit-SearchPathExcluded-1: vendor\n"
22
 
23
+ #: admin/class-xcloner-admin.php:225
24
  msgid "Settings Saved"
25
  msgstr "Setari Salvate"
26
 
27
+ #: admin/class-xcloner-admin.php:246
28
  msgid "General Options"
29
  msgstr "Optiuni Generale"
30
 
31
+ #: admin/class-xcloner-admin.php:247
32
  msgid "Mysql Options"
33
  msgstr "Optiuni Mysql"
34
 
35
+ #: admin/class-xcloner-admin.php:248
36
  msgid "System Options"
37
  msgstr "Optiuni Sistem"
38
 
39
+ #: admin/class-xcloner-admin.php:249
40
  msgid "Cleanup Options"
41
  msgstr "Optiuni Curatare"
42
 
43
+ #: admin/partials/xcloner_console_page.php:10
 
 
 
 
44
  msgid "XCloner Debugger Dashboard"
45
  msgstr "Consola Debug XCloner"
46
 
57
  msgstr "Optiuni Fisiere"
58
 
59
  #: admin/partials/xcloner_generate_backups_page.php:18
60
+ #: includes/class-xcloner.php:448
61
  msgid "Generate Backup"
62
  msgstr "Creare Backup"
63
 
67
 
68
  #: admin/partials/xcloner_generate_backups_page.php:30
69
  #: admin/partials/xcloner_init_page.php:88
70
+ #: admin/partials/xcloner_manage_backups_page.php:50
71
+ #: admin/partials/xcloner_scheduled_backups_page.php:142
72
  msgid "Backup Name"
73
  msgstr "Nume Backup"
74
 
162
  msgstr "Backup Terminat"
163
 
164
  #: admin/partials/xcloner_generate_backups_page.php:190
165
+ #: admin/partials/xcloner_manage_backups_page.php:128
166
  msgid "Send Backup To Remote Storage"
167
  msgstr "Trimite Backup In Stocare La Distanta "
168
 
169
  #: admin/partials/xcloner_generate_backups_page.php:192
170
+ #: admin/partials/xcloner_manage_backups_page.php:125
171
  msgid "Download Backup"
172
  msgstr "Descarcare Backup"
173
 
174
  #: admin/partials/xcloner_generate_backups_page.php:193
175
+ #: admin/partials/xcloner_manage_backups_page.php:112
176
+ #: admin/partials/xcloner_manage_backups_page.php:130
177
  msgid "List Backup Content"
178
  msgstr "Afiseaza Continutul Backup"
179
 
197
  msgstr "(Anuleaza aceasta notificare."
198
 
199
  #: admin/partials/xcloner_generate_backups_page.php:227
200
+ #: admin/partials/xcloner_scheduled_backups_page.php:51
201
  msgid "Current Server Time"
202
  msgstr "Timpul Curent Pe Server"
203
 
204
  #: admin/partials/xcloner_generate_backups_page.php:234
205
  #: admin/partials/xcloner_init_page.php:132
206
+ #: admin/partials/xcloner_scheduled_backups_page.php:23
207
+ #: admin/partials/xcloner_scheduled_backups_page.php:35
208
+ #: admin/partials/xcloner_scheduled_backups_page.php:93
209
  msgid "Schedule Name"
210
  msgstr "Numele Programarii"
211
 
222
  msgstr "faceti o selectie"
223
 
224
  #: admin/partials/xcloner_generate_backups_page.php:253
225
+ #: admin/partials/xcloner_scheduled_backups_page.php:106
226
  msgid "Don't Repeat"
227
  msgstr "O Singura Data"
228
 
229
  #: admin/partials/xcloner_generate_backups_page.php:254
230
+ #: admin/partials/xcloner_scheduled_backups_page.php:107
231
  msgid "Hourly"
232
  msgstr "In Fiecare ora"
233
 
234
  #: admin/partials/xcloner_generate_backups_page.php:255
235
+ #: admin/partials/xcloner_scheduled_backups_page.php:108
236
  msgid "Daily"
237
  msgstr "Zilnic"
238
 
239
  #: admin/partials/xcloner_generate_backups_page.php:256
240
+ #: admin/partials/xcloner_scheduled_backups_page.php:109
241
  msgid "Weekly"
242
  msgstr "Saptamanal"
243
 
244
  #: admin/partials/xcloner_generate_backups_page.php:257
245
+ #: admin/partials/xcloner_scheduled_backups_page.php:110
246
  msgid "Monthly"
247
  msgstr "Lunar"
248
 
251
  msgstr "Selectati Frecventa De Rulare"
252
 
253
  #: admin/partials/xcloner_generate_backups_page.php:267
254
+ #: admin/partials/xcloner_scheduled_backups_page.php:119
255
  msgid "none"
256
  msgstr "nici una"
257
 
276
  msgstr "Inchide"
277
 
278
  #: admin/partials/xcloner_generate_backups_page.php:305
279
+ #: admin/partials/xcloner_manage_backups_page.php:153
280
  msgid "Listing Backup Content "
281
  msgstr "Continutul Fisierului De Backup"
282
 
283
  #: admin/partials/xcloner_generate_backups_page.php:320
284
+ #: admin/partials/xcloner_manage_backups_page.php:183
285
  msgid "Remote Storage Transfer"
286
  msgstr "Transfer In Stocare Externa"
287
 
288
  #: admin/partials/xcloner_generate_backups_page.php:329
289
+ #: admin/partials/xcloner_manage_backups_page.php:192
290
  msgid "please select..."
291
  msgstr "faceti o selectie…"
292
 
293
  #: admin/partials/xcloner_generate_backups_page.php:341
294
+ #: admin/partials/xcloner_manage_backups_page.php:204
295
  msgid "Uploading backup to the selected remote storage..."
296
  msgstr "Incarc backup-ul in spatiul de stocare selectat"
297
 
357
  msgid "Unscheduled"
358
  msgstr "Neprogramat"
359
 
360
+ #: admin/partials/xcloner_init_page.php:152
361
+ msgid "XCloner Debugger"
362
+ msgstr "Depanator XCloner"
363
+
364
+ #: admin/partials/xcloner_init_page.php:180
365
  msgid "System Check"
366
  msgstr "Verificare Sistem"
367
 
368
+ #: admin/partials/xcloner_init_page.php:183
369
+ #: includes/class-xcloner-settings.php:287
370
  msgid "Backup Start Location"
371
  msgstr "Calea Inceput Backup"
372
 
373
+ #: admin/partials/xcloner_init_page.php:186
374
+ #: includes/class-xcloner-settings.php:301
375
  msgid "Backup Storage Location"
376
  msgstr "Calea Stocare Backup"
377
 
378
+ #: admin/partials/xcloner_init_page.php:189
379
  msgid "Temporary Location"
380
  msgstr "Locatia Temporara"
381
 
382
+ #: admin/partials/xcloner_init_page.php:193
383
  msgid "PHP Version Check"
384
  msgstr "Verificare Versiune PHP"
385
 
386
+ #: admin/partials/xcloner_init_page.php:197
387
  msgid "PHP Safe Mode"
388
  msgstr ""
389
 
390
+ #: admin/partials/xcloner_init_page.php:201
391
  msgid "BACKUP READY"
392
  msgstr "PREGATIT DE BACKUP"
393
 
394
+ #: admin/partials/xcloner_init_page.php:201
395
  msgid "Backup not ready, please check above requirements"
396
  msgstr "Sistem Backup Invalid, va rog verificati cerintele date"
397
 
398
+ #: admin/partials/xcloner_init_page.php:207
399
  msgid "PHP max_execution_time"
400
  msgstr ""
401
 
402
+ #: admin/partials/xcloner_init_page.php:210
403
  msgid "PHP memory_limit"
404
  msgstr ""
405
 
406
+ #: admin/partials/xcloner_init_page.php:213
407
  msgid "PHP open_basedir"
408
  msgstr ""
409
 
410
+ #: admin/partials/xcloner_init_page.php:221
411
  msgid "Reading Time 1MB Block"
412
  msgstr "Timp citire bloc de 1MB "
413
 
 
414
  #: admin/partials/xcloner_init_page.php:221
415
+ #: admin/partials/xcloner_init_page.php:224
416
  msgid "unknown"
417
  msgstr "necunoscut"
418
 
419
+ #: admin/partials/xcloner_init_page.php:224
420
  msgid "Writing Time 1MB Block"
421
  msgstr "Timp scriere bloc de 1M"
422
 
423
+ #: admin/partials/xcloner_init_page.php:227
424
  msgid "Free Disk Space"
425
  msgstr "Spatiul Disc Liber"
426
 
427
+ #: admin/partials/xcloner_init_page.php:232
428
  msgid "Toggle Additional System Info"
429
  msgstr "Arata Informatii Sistem Aditionale"
430
 
431
+ #: admin/partials/xcloner_manage_backups_page.php:29
432
+ msgid "Change To Local Storage..."
433
+ msgstr "Vizualizare Backup Din Sistemul Local…"
434
+
435
+ #: admin/partials/xcloner_manage_backups_page.php:31
436
+ msgid "Change To Remote Storage..."
437
+ msgstr "Vizualizare Backup Din Sistemul De Stocare Extern…"
438
+
439
+ #: admin/partials/xcloner_manage_backups_page.php:51
440
  msgid "Created Time"
441
  msgstr "Timpul Creat"
442
 
443
+ #: admin/partials/xcloner_manage_backups_page.php:52
444
  msgid "Size"
445
  msgstr "Marime"
446
 
447
+ #: admin/partials/xcloner_manage_backups_page.php:53
448
+ #: admin/partials/xcloner_scheduled_backups_page.php:29
449
+ #: admin/partials/xcloner_scheduled_backups_page.php:41
450
  msgid "Action"
451
  msgstr "Actiune"
452
 
453
+ #: admin/partials/xcloner_manage_backups_page.php:88
454
+ #: admin/partials/xcloner_manage_backups_page.php:108
455
+ msgid "File does not exists on local storage"
456
+ msgstr "Fisierul nu exista in sistemul de fisierere local"
457
+
458
+ #: admin/partials/xcloner_manage_backups_page.php:114
459
+ #: admin/partials/xcloner_manage_backups_page.php:135
460
+ msgid "Push Backup To Local Storage"
461
+ msgstr "Trimite Backup Catre Sistemul De Fisiere Local"
462
+
463
+ #: admin/partials/xcloner_manage_backups_page.php:133
464
  msgid "Delete Backup"
465
  msgstr "Stergere Backup"
466
 
467
+ #: admin/partials/xcloner_manage_backups_page.php:148
468
  msgid "Delete"
469
  msgstr "Sterge"
470
 
471
+ #: admin/partials/xcloner_manage_backups_page.php:166
472
+ msgid "Transfer Remote Backup To Local Storage"
473
+ msgstr "Transferati Fisierul Backup In Sistemul De Fisiere Local"
474
+
475
+ #: admin/partials/xcloner_manage_backups_page.php:173
476
+ msgid "Uploading backup to the local storage filesystem..."
477
+ msgstr "Incarc fisierul de backup in sistemul local de fisierere…"
478
+
479
+ #: admin/partials/xcloner_manage_backups_page.php:188
480
  #, php-format
481
  msgid "Send %s to remote storage"
482
  msgstr "Trimite %s in spatiul de stocare extern"
483
 
484
+ #: admin/partials/xcloner_remote_storage_page.php:23
485
+ msgid "FTP Storage"
486
+ msgstr ""
487
 
488
+ #: admin/partials/xcloner_remote_storage_page.php:38
489
+ #: admin/partials/xcloner_remote_storage_page.php:41
490
  msgid "Ftp Hostname"
491
  msgstr ""
492
 
493
+ #: admin/partials/xcloner_remote_storage_page.php:44
494
  msgid "Ftp Port"
495
  msgstr ""
496
 
497
+ #: admin/partials/xcloner_remote_storage_page.php:50
498
+ #: admin/partials/xcloner_remote_storage_page.php:53
499
  msgid "Ftp Username"
500
  msgstr ""
501
 
502
+ #: admin/partials/xcloner_remote_storage_page.php:60
503
+ #: admin/partials/xcloner_remote_storage_page.php:63
504
  msgid "Ftp Password"
505
  msgstr ""
506
 
507
+ #: admin/partials/xcloner_remote_storage_page.php:69
508
+ #: admin/partials/xcloner_remote_storage_page.php:72
509
  msgid "Ftp Storage Folder"
510
  msgstr ""
511
 
512
+ #: admin/partials/xcloner_remote_storage_page.php:78
513
  msgid "Ftp Transfer Mode"
514
  msgstr ""
515
 
516
+ #: admin/partials/xcloner_remote_storage_page.php:82
517
  msgid "Passive"
518
  msgstr "Pasiv"
519
 
520
+ #: admin/partials/xcloner_remote_storage_page.php:85
521
  msgid "Active"
522
  msgstr "Activ"
523
 
524
+ #: admin/partials/xcloner_remote_storage_page.php:91
525
  msgid "Ftp Secure Connection"
526
  msgstr "Conexiune Securizata Ftp"
527
 
528
+ #: admin/partials/xcloner_remote_storage_page.php:95
529
  msgid "Disable"
530
  msgstr "Dezactivati"
531
 
532
+ #: admin/partials/xcloner_remote_storage_page.php:98
533
  msgid "Enable"
534
  msgstr "Activati"
535
 
536
+ #: admin/partials/xcloner_remote_storage_page.php:104
537
+ #: admin/partials/xcloner_remote_storage_page.php:107
538
  msgid "Ftp Timeout"
539
  msgstr ""
540
 
541
+ #: admin/partials/xcloner_remote_storage_page.php:113
542
  msgid "Ftp Cleanup (days)"
543
  msgstr "Curatare Ftp(zile)"
544
 
545
+ #: admin/partials/xcloner_remote_storage_page.php:116
546
+ #: admin/partials/xcloner_remote_storage_page.php:214
547
+ #: admin/partials/xcloner_remote_storage_page.php:303
548
+ #: admin/partials/xcloner_remote_storage_page.php:384
549
+ #: admin/partials/xcloner_remote_storage_page.php:465
550
+ #: admin/partials/xcloner_remote_storage_page.php:546
551
+ #: admin/partials/xcloner_remote_storage_page.php:635
552
+ #: admin/partials/xcloner_remote_storage_page.php:736
553
  msgid "how many days to keep the backups for"
554
  msgstr "pentru cate zile sa pastrez fisierele de backup"
555
 
556
+ #: admin/partials/xcloner_remote_storage_page.php:122
557
+ #: admin/partials/xcloner_remote_storage_page.php:220
558
+ #: admin/partials/xcloner_remote_storage_page.php:309
559
+ #: admin/partials/xcloner_remote_storage_page.php:390
560
+ #: admin/partials/xcloner_remote_storage_page.php:471
561
+ #: admin/partials/xcloner_remote_storage_page.php:552
562
+ #: admin/partials/xcloner_remote_storage_page.php:641
563
+ #: admin/partials/xcloner_remote_storage_page.php:742
564
  msgid "Save Settings"
565
  msgstr "Salveaza Setari"
566
 
567
+ #: admin/partials/xcloner_remote_storage_page.php:127
568
+ #: admin/partials/xcloner_remote_storage_page.php:225
569
+ #: admin/partials/xcloner_remote_storage_page.php:314
570
+ #: admin/partials/xcloner_remote_storage_page.php:395
571
+ #: admin/partials/xcloner_remote_storage_page.php:476
572
+ #: admin/partials/xcloner_remote_storage_page.php:557
573
+ #: admin/partials/xcloner_remote_storage_page.php:646
574
+ #: admin/partials/xcloner_remote_storage_page.php:747
575
  msgid "Verify"
576
  msgstr "Verifica"
577
 
578
+ #: admin/partials/xcloner_remote_storage_page.php:138
579
  msgid "SFTP Storage"
580
  msgstr "Stocare SFTP"
581
 
582
+ #: admin/partials/xcloner_remote_storage_page.php:153
583
+ #: admin/partials/xcloner_remote_storage_page.php:156
584
  msgid "SFTP Hostname"
585
  msgstr ""
586
 
587
+ #: admin/partials/xcloner_remote_storage_page.php:159
588
  msgid "SFTP Port"
589
  msgstr ""
590
 
591
+ #: admin/partials/xcloner_remote_storage_page.php:165
592
+ #: admin/partials/xcloner_remote_storage_page.php:168
593
  msgid "SFTP Username"
594
  msgstr ""
595
 
596
+ #: admin/partials/xcloner_remote_storage_page.php:175
597
+ #: admin/partials/xcloner_remote_storage_page.php:178
598
  msgid "SFTP Password"
599
  msgstr ""
600
 
601
+ #: admin/partials/xcloner_remote_storage_page.php:184
602
+ #: admin/partials/xcloner_remote_storage_page.php:187
603
  msgid "SFTP Private Key"
604
  msgstr ""
605
 
606
+ #: admin/partials/xcloner_remote_storage_page.php:193
607
+ #: admin/partials/xcloner_remote_storage_page.php:196
608
  msgid "SFTP Storage Folder"
609
  msgstr ""
610
 
611
+ #: admin/partials/xcloner_remote_storage_page.php:202
612
+ #: admin/partials/xcloner_remote_storage_page.php:205
613
  msgid "SFTP Timeout"
614
  msgstr ""
615
 
616
+ #: admin/partials/xcloner_remote_storage_page.php:211
617
  msgid "SFTP Cleanup (days)"
618
  msgstr "Curatare SFTP(zile)"
619
 
620
+ #: admin/partials/xcloner_remote_storage_page.php:237
621
  msgid "AWS Storage"
622
  msgstr ""
623
 
624
+ #: admin/partials/xcloner_remote_storage_page.php:257
625
  #, php-format
626
  msgid "Visit %s and get your \"Key\" and \"Secret\"."
627
  msgstr ""
628
 
629
+ #: admin/partials/xcloner_remote_storage_page.php:264
630
+ #: admin/partials/xcloner_remote_storage_page.php:267
631
  msgid "AWS Key"
632
  msgstr ""
633
 
634
+ #: admin/partials/xcloner_remote_storage_page.php:273
635
+ #: admin/partials/xcloner_remote_storage_page.php:276
636
  msgid "AWS Secret"
637
  msgstr ""
638
 
639
+ #: admin/partials/xcloner_remote_storage_page.php:282
640
+ #: admin/partials/xcloner_remote_storage_page.php:285
641
  msgid "AWS Region"
642
  msgstr ""
643
 
644
+ #: admin/partials/xcloner_remote_storage_page.php:291
645
+ #: admin/partials/xcloner_remote_storage_page.php:294
646
  msgid "AWS Bucket Name"
647
  msgstr ""
648
 
649
+ #: admin/partials/xcloner_remote_storage_page.php:300
650
  msgid "AWS Cleanup (days)"
651
  msgstr "Curatare AWS(zile)"
652
 
653
+ #: admin/partials/xcloner_remote_storage_page.php:326
654
  msgid "Dropbox Storage"
655
  msgstr "Stocare Dropbox"
656
 
657
+ #: admin/partials/xcloner_remote_storage_page.php:346
658
  #, php-format
659
  msgid "Visit %s and get your \"App secret\"."
660
  msgstr ""
661
 
662
+ #: admin/partials/xcloner_remote_storage_page.php:353
663
+ #: admin/partials/xcloner_remote_storage_page.php:356
664
  msgid "Dropbox Access Token"
665
  msgstr ""
666
 
667
+ #: admin/partials/xcloner_remote_storage_page.php:363
668
+ #: admin/partials/xcloner_remote_storage_page.php:366
669
  msgid "Dropbox App Secret"
670
  msgstr ""
671
 
672
+ #: admin/partials/xcloner_remote_storage_page.php:372
673
+ #: admin/partials/xcloner_remote_storage_page.php:375
674
  msgid "Dropbox Prefix"
675
  msgstr ""
676
 
677
+ #: admin/partials/xcloner_remote_storage_page.php:381
678
  msgid "Dropbox Cleanup (days)"
679
  msgstr "Curatare Dropbox(zile)"
680
 
681
+ #: admin/partials/xcloner_remote_storage_page.php:407
682
  msgid "Azure Blog Storage"
683
  msgstr ""
684
 
685
+ #: admin/partials/xcloner_remote_storage_page.php:427
686
  #, php-format
687
  msgid "Visit %s and get your \"Api Key\"."
688
  msgstr "Vizitati %s pentru a obtine cheia \"Api Key\""
689
 
690
+ #: admin/partials/xcloner_remote_storage_page.php:434
691
+ #: admin/partials/xcloner_remote_storage_page.php:437
692
  msgid "Azure Account Name"
693
  msgstr ""
694
 
695
+ #: admin/partials/xcloner_remote_storage_page.php:444
696
+ #: admin/partials/xcloner_remote_storage_page.php:447
697
  msgid "Azure Api Key"
698
  msgstr ""
699
 
700
+ #: admin/partials/xcloner_remote_storage_page.php:453
701
+ #: admin/partials/xcloner_remote_storage_page.php:456
702
  msgid "Azure Container"
703
  msgstr ""
704
 
705
+ #: admin/partials/xcloner_remote_storage_page.php:462
706
  msgid "Azure Cleanup (days)"
707
  msgstr "Curatare Azure(zile)"
708
 
709
+ #: admin/partials/xcloner_remote_storage_page.php:488
710
+ msgid "BackBlaze Storage"
711
+ msgstr ""
712
+
713
+ #: admin/partials/xcloner_remote_storage_page.php:508
714
+ #, php-format
715
+ msgid "Visit %s and get your Account Id and Application Key."
716
+ msgstr ""
717
+
718
+ #: admin/partials/xcloner_remote_storage_page.php:515
719
+ #: admin/partials/xcloner_remote_storage_page.php:518
720
+ msgid "BackBlaze Account Id"
721
+ msgstr ""
722
+
723
+ #: admin/partials/xcloner_remote_storage_page.php:525
724
+ #: admin/partials/xcloner_remote_storage_page.php:528
725
+ msgid "BackBlaze Application Key"
726
+ msgstr ""
727
+
728
+ #: admin/partials/xcloner_remote_storage_page.php:534
729
+ #: admin/partials/xcloner_remote_storage_page.php:537
730
+ msgid "BackBlaze Bucket Name"
731
+ msgstr ""
732
+
733
+ #: admin/partials/xcloner_remote_storage_page.php:543
734
+ msgid "BackBlaze Cleanup (days)"
735
+ msgstr ""
736
+
737
+ #: admin/partials/xcloner_remote_storage_page.php:569
738
+ msgid "WebDAV Storage"
739
+ msgstr ""
740
+
741
+ #: admin/partials/xcloner_remote_storage_page.php:596
742
+ #: admin/partials/xcloner_remote_storage_page.php:599
743
+ msgid "WebDAV Base Url"
744
+ msgstr ""
745
+
746
+ #: admin/partials/xcloner_remote_storage_page.php:605
747
+ #: admin/partials/xcloner_remote_storage_page.php:608
748
+ msgid "WebDAV Username"
749
+ msgstr ""
750
+
751
+ #: admin/partials/xcloner_remote_storage_page.php:614
752
+ #: admin/partials/xcloner_remote_storage_page.php:617
753
+ msgid "WebDAV Password"
754
+ msgstr ""
755
+
756
+ #: admin/partials/xcloner_remote_storage_page.php:623
757
+ #: admin/partials/xcloner_remote_storage_page.php:626
758
+ msgid "WebDAV Target Folder"
759
+ msgstr ""
760
+
761
+ #: admin/partials/xcloner_remote_storage_page.php:632
762
+ msgid "WebDAV Cleanup (days)"
763
+ msgstr ""
764
+
765
+ #: admin/partials/xcloner_remote_storage_page.php:658
766
+ msgid "Google Drive Storage"
767
+ msgstr ""
768
+
769
+ #: admin/partials/xcloner_remote_storage_page.php:682
770
+ #, php-format
771
+ msgid ""
772
+ "Visit %s to create a new application and get your Client ID and Client "
773
+ "Secret."
774
+ msgstr ""
775
+ "Vizitati %s pentru a crea o noua aplicatie si a obtine Client ID si Client "
776
+ "Secret."
777
+
778
+ #: admin/partials/xcloner_remote_storage_page.php:684
779
+ #, php-format
780
+ msgid ""
781
+ "Click here to view a short video explaining how to create the Client ID and "
782
+ "Client Secret as well as connecting XCloner with the Google Drive API %s"
783
+ msgstr ""
784
+ "Apasati aici pentru o viziona un videoclip despre cum puteti obtine "
785
+ "parametrii Client ID si Client Secret si despre cum puteti conecta XCloner "
786
+ "la Google Drive API %s"
787
+
788
+ #: admin/partials/xcloner_remote_storage_page.php:691
789
+ msgid "Client ID"
790
+ msgstr ""
791
+
792
+ #: admin/partials/xcloner_remote_storage_page.php:694
793
+ msgid "Google Client ID"
794
+ msgstr ""
795
+
796
+ #: admin/partials/xcloner_remote_storage_page.php:700
797
+ msgid "Client Secret"
798
+ msgstr ""
799
+
800
+ #: admin/partials/xcloner_remote_storage_page.php:703
801
+ msgid "Google Client Secret"
802
+ msgstr ""
803
+
804
+ #: admin/partials/xcloner_remote_storage_page.php:713
805
+ msgid "Authorize Google Drive"
806
+ msgstr "Autorizati Google Drive"
807
+
808
+ #: admin/partials/xcloner_remote_storage_page.php:714
809
+ msgid "Paste Authorization Code Here"
810
+ msgstr "Copiati Codul de Autorizare Aici"
811
+
812
+ #: admin/partials/xcloner_remote_storage_page.php:720
813
+ msgid "Folder ID or Root Path"
814
+ msgstr ""
815
+
816
+ #: admin/partials/xcloner_remote_storage_page.php:722
817
+ msgid ""
818
+ "Folder ID can be found by right clicking on the folder name and selecting "
819
+ "'Get shareable link' menu, format https://drive.google.com/open?"
820
+ "id={FOLDER_ID}<br />\n"
821
+ "\t\t\t\t\t\t\t\t\tIf you supply a folder name, it has to exists in the drive "
822
+ "root and start with / , example /backups.xcloner.com/"
823
+ msgstr ""
824
+
825
+ #: admin/partials/xcloner_remote_storage_page.php:727
826
+ msgid "Target Folder ID or Root Path"
827
+ msgstr ""
828
+
829
+ #: admin/partials/xcloner_remote_storage_page.php:733
830
+ msgid "Google Drive Cleanup (days)"
831
+ msgstr ""
832
+
833
+ #: admin/partials/xcloner_remote_storage_page.php:757
834
+ msgid "Free Download XCloner-Google-Drive Wordpress Plugin"
835
+ msgstr "Descarcati gratuit plugin-ul XWordpress Cloner-Google-Drive"
836
+
837
  #: admin/partials/xcloner_restore_page.php:24
838
  msgid "Restore Script Upload"
839
  msgstr "Incarcare Script Restaurare"
1032
  msgid "Finish"
1033
  msgstr "Terminare"
1034
 
1035
+ #: admin/partials/xcloner_scheduled_backups_page.php:10
1036
  #, php-format
1037
  msgid ""
1038
  "We have noticed that DISABLE_WP_CRON is disabled, we recommend enabling that "
1043
  "activati pentru a putea rula programul manual prin intermediul server-ului "
1044
  "de hosting asa cum este explicat <a href=\"%s\" target=\"_blank\">aici</a>"
1045
 
1046
+ #: admin/partials/xcloner_scheduled_backups_page.php:22
1047
+ #: admin/partials/xcloner_scheduled_backups_page.php:34
1048
  msgid "ID"
1049
  msgstr "ID"
1050
 
1051
+ #: admin/partials/xcloner_scheduled_backups_page.php:24
1052
+ #: admin/partials/xcloner_scheduled_backups_page.php:36
1053
  msgid "Recurrence"
1054
  msgstr "Recurenta"
1055
 
1056
+ #: admin/partials/xcloner_scheduled_backups_page.php:25
1057
+ #: admin/partials/xcloner_scheduled_backups_page.php:37
1058
  msgid "Next Execution"
1059
  msgstr "Urmatoarea Executie"
1060
 
1061
+ #: admin/partials/xcloner_scheduled_backups_page.php:26
1062
+ #: admin/partials/xcloner_scheduled_backups_page.php:38
1063
+ #: includes/class-xcloner-loader.php:69
1064
  msgid "Remote Storage"
1065
  msgstr "Stocare Externa"
1066
 
1067
+ #: admin/partials/xcloner_scheduled_backups_page.php:27
1068
+ #: admin/partials/xcloner_scheduled_backups_page.php:39
1069
  msgid "Last Backup"
1070
  msgstr "Ultimul Backup"
1071
 
1072
+ #: admin/partials/xcloner_scheduled_backups_page.php:28
1073
+ #: admin/partials/xcloner_scheduled_backups_page.php:40
1074
  msgid "Status"
1075
  msgstr "Status"
1076
 
1077
+ #: admin/partials/xcloner_scheduled_backups_page.php:65
1078
  msgid "Edit Schedule"
1079
  msgstr "Editare Programare"
1080
 
1081
+ #: admin/partials/xcloner_scheduled_backups_page.php:71
1082
  msgid "Off"
1083
  msgstr ""
1084
 
1085
+ #: admin/partials/xcloner_scheduled_backups_page.php:74
1086
  msgid "On"
1087
  msgstr ""
1088
 
1089
+ #: admin/partials/xcloner_scheduled_backups_page.php:83
1090
  msgid "Scheduler Settings"
1091
  msgstr "Setari Program"
1092
 
1093
+ #: admin/partials/xcloner_scheduled_backups_page.php:84
1094
  msgid "Advanced"
1095
  msgstr "Avansat"
1096
 
1097
+ #: admin/partials/xcloner_scheduled_backups_page.php:100
1098
  msgid "Schedule Start At"
1099
  msgstr "Programul Incepe La"
1100
 
1101
+ #: admin/partials/xcloner_scheduled_backups_page.php:105
1102
  msgid "Schedule Recurrence"
1103
  msgstr "Recurenta Program"
1104
 
1105
+ #: admin/partials/xcloner_scheduled_backups_page.php:124
1106
  msgid "Send To Remote Storage "
1107
  msgstr "Trimite In Spatiul De Storcare La Distanta"
1108
 
1109
+ #: admin/partials/xcloner_scheduled_backups_page.php:132
1110
  msgid "Email Notification Address"
1111
  msgstr "Adresa E-Mail Pentru Notificari"
1112
 
1113
+ #: admin/partials/xcloner_scheduled_backups_page.php:149
1114
  msgid "Included Database Data"
1115
  msgstr "Include Baza De Data"
1116
 
1117
+ #: admin/partials/xcloner_scheduled_backups_page.php:156
1118
  msgid "Excluded Files"
1119
  msgstr "Exclude Fisiere"
1120
 
1121
+ #: admin/partials/xcloner_scheduled_backups_page.php:165
1122
  msgid "Save"
1123
  msgstr "Salveaza"
1124
 
1135
  msgid "XCloner Activation Error"
1136
  msgstr "Eroare Activare XCloner"
1137
 
1138
+ #: includes/class-xcloner-api.php:612
1139
  msgid "executed"
1140
  msgstr "executat"
1141
 
1142
+ #: includes/class-xcloner-archive.php:153
1143
  #, php-format
1144
  msgid "New backup generated %s"
1145
  msgstr "Backup nou creat %s"
1146
 
1147
+ #: includes/class-xcloner-archive.php:155
1148
  #, php-format
1149
  msgid "Generated Backup Size: %s"
1150
  msgstr "Marime backup creat: %s"
1151
 
1152
+ #: includes/class-xcloner-archive.php:163
1153
  #, php-format
1154
  msgid "Backup Parts: %s"
1155
  msgstr "Parti Backup: %s"
1156
 
1157
+ #: includes/class-xcloner-archive.php:176
1158
  msgid "Backup Comments: "
1159
  msgstr "Comentarii Backup:"
1160
 
1161
+ #: includes/class-xcloner-archive.php:181
1162
  msgid "Latest 50 Log Lines: "
1163
  msgstr "Ultimele 50 de linii log:"
1164
 
1165
+ #: includes/class-xcloner-archive.php:236
1166
  #, php-format
1167
  msgid "Initializing the backup archive %s"
1168
  msgstr "Initializare archiva de backup %s"
1169
 
1170
+ #: includes/class-xcloner-archive.php:243
1171
  #, php-format
1172
  msgid "Opening for append the backup archive %s"
1173
  msgstr "Deschid pentru adaugare arhiva %s"
1185
  msgid "Getting number of tables in %s"
1186
  msgstr "Obtin numarul de tabele din %s"
1187
 
1188
+ #: includes/class-xcloner-database.php:281
1189
  #, php-format
1190
  msgid "Excluding table %s.%s from backup"
1191
  msgstr "Exclud tabela %s.%s din backup"
1192
 
1193
+ #: includes/class-xcloner-database.php:295
1194
  msgid "Preparing the database recursion file"
1195
  msgstr "Prepar fisierul de recursie al bazei de date"
1196
 
1197
+ #: includes/class-xcloner-database.php:378
1198
  #, php-format
1199
  msgid "Starting new backup dump to file %s"
1200
  msgstr "Incep un nou backup al bazei de date in fisierul %s"
1201
 
1202
+ #: includes/class-xcloner-database.php:520
1203
  #, php-format
1204
  msgid "Dumping %s records starting position %s from %s.%s table"
1205
  msgstr "Incarc %s randuri incepand de la pozitia %s din %s.%s tabela"
1206
 
1207
+ #: includes/class-xcloner-database.php:528
1208
  #, php-format
1209
  msgid "Dumping the structure for %s.%s table"
1210
  msgstr "Salvez structura pentru tabela %s.%s"
1211
 
1212
+ #: includes/class-xcloner-database.php:621
1213
  #, php-format
1214
  msgid "Writing %s database dump headers"
1215
  msgstr "Scrie %s capul bazei de date"
1216
 
1217
+ #: includes/class-xcloner-file-system.php:349
1218
  #, php-format
1219
  msgid "Starting the filesystem scanner on root folder %s"
1220
  msgstr "Incep scanarea sistemului de fisiere din %s"
1221
 
1222
+ #: includes/class-xcloner-file-system.php:501
1223
  #, php-format
1224
  msgid "Excluding %s from the filesystem list, file not readable"
1225
  msgstr "Exclud %s din sistemul de fisiere, fisierul nu este accesibil"
1226
 
1227
+ #: includes/class-xcloner-file-system.php:504
1228
  #, php-format
1229
  msgid "Adding %s to the filesystem list"
1230
  msgstr "Adaug %s in lista de fisiere"
1231
 
1232
+ #: includes/class-xcloner-file-system.php:512
1233
  #, php-format
1234
  msgid "Excluding %s from the filesystem list, matching pattern %s"
1235
  msgstr "Exclud %s din lista de fisiere, pattern gasit %s"
1236
 
1237
+ #: includes/class-xcloner-loader.php:62
1238
  msgid "Site Backup"
1239
  msgstr "Backup Site"
1240
 
1241
+ #: includes/class-xcloner-loader.php:67
1242
  msgid "XCloner Dashboard"
1243
  msgstr "Panou Start XCloner"
1244
 
1245
+ #: includes/class-xcloner-loader.php:67
1246
  msgid "Dashboard"
1247
  msgstr "Panou Start"
1248
 
1249
+ #: includes/class-xcloner-loader.php:68
1250
  msgid "XCloner Backup Settings"
1251
  msgstr "XCloner Setari Backup"
1252
 
1253
+ #: includes/class-xcloner-loader.php:68 includes/class-xcloner.php:447
1254
  msgid "Settings"
1255
  msgstr "Setari"
1256
 
1257
+ #: includes/class-xcloner-loader.php:69
1258
  msgid "Remote Storage Settings"
1259
  msgstr "Setari Stocare Externa"
1260
 
1261
+ #: includes/class-xcloner-loader.php:70
1262
  msgid "Manage Backups"
1263
  msgstr "Administrare Backup"
1264
 
1265
+ #: includes/class-xcloner-loader.php:71
1266
  msgid "Scheduled Backups"
1267
  msgstr "Programare Backup"
1268
 
1269
+ #: includes/class-xcloner-loader.php:72
1270
  msgid "Generate Backups"
1271
  msgstr "Generare Backup"
1272
 
1273
+ #: includes/class-xcloner-loader.php:73
1274
  msgid "Restore Backups"
1275
  msgstr "Restaurare Backup"
1276
 
1277
+ #: includes/class-xcloner-remote-storage.php:163
1278
  #, php-format
1279
  msgid "%s storage settings saved."
1280
  msgstr "%s setarile de stocare au fost salvate."
1281
 
1282
+ #: includes/class-xcloner-remote-storage.php:172
1283
  #, php-format
1284
  msgid "%s connection is valid."
1285
  msgstr "%s conexiunea este valida."
1286
 
1287
+ #: includes/class-xcloner-remote-storage.php:195
1288
+ #: includes/class-xcloner-remote-storage.php:208
 
 
 
1289
  msgid "Could not read data"
1290
  msgstr "Nu pot citi data"
1291
 
1292
+ #: includes/class-xcloner-remote-storage.php:203
1293
+ msgid "Could not write data"
1294
+ msgstr "Nu pot scrie data"
1295
+
1296
+ #: includes/class-xcloner-remote-storage.php:213
1297
  msgid "Could not delete data"
1298
  msgstr "Nu pot sterge data"
1299
 
1300
+ #: includes/class-xcloner-remote-storage.php:585
1301
+ #, php-format
1302
+ msgid "Could not find folder ID by name %s"
1303
+ msgstr ""
1304
+
1305
+ #: includes/class-xcloner-settings.php:230
1306
+ #: includes/class-xcloner-settings.php:237
1307
+ #: includes/class-xcloner-settings.php:253
1308
+ #: includes/class-xcloner-settings.php:262
1309
  msgid " "
1310
  msgstr ""
1311
 
1312
+ #: includes/class-xcloner-settings.php:245
1313
  msgid "These are advanced options recommended for developers!"
1314
  msgstr "Acestea sunt optiuni avansate pentru dezvoltatori!"
1315
 
1316
+ #: includes/class-xcloner-settings.php:273
1317
  msgid "Backup Compression Level"
1318
  msgstr "Nivelul De Compresie Backup"
1319
 
1320
+ #: includes/class-xcloner-settings.php:278
1321
  msgid ""
1322
  "Options between [0-9]. Value 0 means no compression, while 9 is maximum "
1323
  "compression affecting cpu load"
1325
  "Optiuni intre [0-9]. Valoarea 0 inseamna nici o compresie, in timp ce 9 este "
1326
  "compresia maxima care insa poate afecta viteza de rulare a procesorului"
1327
 
1328
+ #: includes/class-xcloner-settings.php:292
1329
  msgid "Base path location from where XCloner can start the Backup."
1330
  msgstr "Locatie de start de unde XCloner va citi fisierele"
1331
 
1332
+ #: includes/class-xcloner-settings.php:306
1333
  msgid "Location where XCloner will store the Backup archives."
1334
  msgstr "Locatia unde XCloner v-a stoca fisierele de backup."
1335
 
1336
+ #: includes/class-xcloner-settings.php:315
1337
  msgid "Enable XCloner Backup Log"
1338
  msgstr "Logare XCloner"
1339
 
1340
+ #: includes/class-xcloner-settings.php:320
1341
  #, php-format
1342
  msgid ""
1343
  "Enable the XCloner Backup log. You will find it stored unde the Backup "
1346
  "Activati logarea XCloner Backup. Veti gasi fisierul de logare in Calea de "
1347
  "Stocare, fisier %s"
1348
 
1349
+ #: includes/class-xcloner-settings.php:327
1350
  msgid "Regex Exclude Files"
1351
  msgstr "Exclude Fisiere Prin Regex"
1352
 
1353
+ #: includes/class-xcloner-settings.php:332
1354
  msgid ""
1355
  "Regular expression match to exclude files and folders, example patterns "
1356
  "provided below, one pattern per line"
1358
  "Folosti expresii Regex pentru a exclude fisiere si directoare, exemple de "
1359
  "expresii sunt afisate mai jos, o singura expresie pe linie"
1360
 
1361
+ #: includes/class-xcloner-settings.php:342
1362
  msgid "Enable Mysql Backup"
1363
  msgstr "Backup Mysql"
1364
 
1365
+ #: includes/class-xcloner-settings.php:347
1366
  msgid ""
1367
  "Enable Mysql Backup Option. If you don't want to backup the database, you "
1368
  "can disable this."
1369
  msgstr "Activati optiunea de backup Mysql."
1370
 
1371
+ #: includes/class-xcloner-settings.php:354
1372
  msgid "Backup only WP tables"
1373
  msgstr "Backup doar tabelele WP"
1374
 
1375
+ #: includes/class-xcloner-settings.php:359
1376
  #, php-format
1377
  msgid ""
1378
  "Enable this if you only want to Backup only tables starting with '%s' prefix"
1380
  "Activati optiunea aceasta doar daca vreti sa stocati tabelele care incep cu "
1381
  "prefixul ‘%s’"
1382
 
1383
+ #: includes/class-xcloner-settings.php:366
1384
  msgid "Mysql Hostname"
1385
  msgstr ""
1386
 
1387
+ #: includes/class-xcloner-settings.php:371
1388
  msgid "Wordpress mysql hostname"
1389
  msgstr ""
1390
 
1391
+ #: includes/class-xcloner-settings.php:380
1392
  msgid "Mysql Username"
1393
  msgstr ""
1394
 
1395
+ #: includes/class-xcloner-settings.php:385
1396
  msgid "Wordpress mysql username"
1397
  msgstr ""
1398
 
1399
+ #: includes/class-xcloner-settings.php:394
1400
  msgid "Mysql Database"
1401
  msgstr ""
1402
 
1403
+ #: includes/class-xcloner-settings.php:399
1404
  msgid "Wordpress mysql database"
1405
  msgstr ""
1406
 
1407
+ #: includes/class-xcloner-settings.php:409
1408
  msgid "Data Size Limit Per Request"
1409
  msgstr "Limita Procesare Date Pe Actiune"
1410
 
1411
+ #: includes/class-xcloner-settings.php:414
1412
  msgid ""
1413
  "Use this option to set how much file data can XCloner backup in one AJAX "
1414
  "request. Range 0-1024 MB"
1415
  msgstr ""
1416
 
1417
+ #: includes/class-xcloner-settings.php:423
1418
  msgid "Files To Process Per Request"
1419
  msgstr "Numar Fisiesre De Procesat Pe Actiune"
1420
 
1421
+ #: includes/class-xcloner-settings.php:428
1422
  msgid ""
1423
  "Use this option to set how many files XCloner should process at one time "
1424
  "before doing another AJAX call"
1425
  msgstr ""
1426
 
1427
+ #: includes/class-xcloner-settings.php:437
1428
  msgid "Directories To Scan Per Request"
1429
  msgstr "Numar Dosare De Procesat Pe Actiune"
1430
 
1431
+ #: includes/class-xcloner-settings.php:442
1432
  msgid ""
1433
  "Use this option to set how many directories XCloner should scan at one time "
1434
  "before doing another AJAX call"
1435
  msgstr ""
1436
 
1437
+ #: includes/class-xcloner-settings.php:451
1438
  msgid "Database Records Per Request"
1439
  msgstr "Limita Inregistrari Baze Date Pe Actiune"
1440
 
1441
+ #: includes/class-xcloner-settings.php:456
1442
  msgid ""
1443
  "Use this option to set how many database table records should be fetched per "
1444
  "AJAX request, or set to 0 to fetch all. Range 0-100000 records"
1445
  msgstr ""
1446
 
1447
+ #: includes/class-xcloner-settings.php:465
1448
  msgid "Exclude files larger than (MB)"
1449
  msgstr "Exclude fisierele mai mari decat(MB)"
1450
 
1451
+ #: includes/class-xcloner-settings.php:470
1452
  msgid ""
1453
  "Use this option to automatically exclude files larger than a certain size in "
1454
+ "MB, or set to 0 to include all. Range 0-1000 MB"
1455
  msgstr ""
1456
 
1457
+ #: includes/class-xcloner-settings.php:477
1458
  msgid "Split Backup Archive Limit (MB)"
1459
  msgstr "Limita Impartire Arhiva(MB)"
1460
 
1461
+ #: includes/class-xcloner-settings.php:482
1462
  msgid ""
1463
  "Use this option to automatically split the backup archive into smaller "
1464
  "parts. Range 0-10000 MB"
1465
  msgstr ""
1466
 
1467
+ #: includes/class-xcloner-settings.php:489
1468
  msgid "Force Temporary Path Within XCloner Storage"
1469
  msgstr ""
1470
 
1471
+ #: includes/class-xcloner-settings.php:494
1472
  msgid ""
1473
  "Enable this option if you want the XCloner Temporary Path to be within your "
1474
  "XCloner Storage Location"
1475
  msgstr ""
1476
 
1477
+ #: includes/class-xcloner-settings.php:502
1478
  msgid "Cleanup by Date(days)"
1479
  msgstr "Limita Zile"
1480
 
1481
+ #: includes/class-xcloner-settings.php:507
1482
  msgid ""
1483
  "Specify the maximum number of days a backup archive can be kept on the "
1484
  "server. 0 disables this option"
1485
  msgstr ""
1486
 
1487
+ #: includes/class-xcloner-settings.php:514
1488
  msgid "Cleanup by Quantity"
1489
  msgstr "Limita Fisiere"
1490
 
1491
+ #: includes/class-xcloner-settings.php:519
1492
  msgid ""
1493
  "Specify the maximum number of backup archives to keep on the server. 0 "
1494
  "disables this option"
1495
  msgstr ""
1496
 
1497
+ #: includes/class-xcloner-settings.php:526
1498
  msgid "Cleanup by Capacity(MB)"
1499
  msgstr "Capacitate Stocare Maxima(MB)"
1500
 
1501
+ #: includes/class-xcloner-settings.php:531
1502
  msgid ""
1503
  "Remove oldest backups if all created backups exceed the configured limit in "
1504
  "Megabytes. 0 disables this option"
1505
  msgstr ""
1506
 
1507
+ #: includes/class-xcloner-settings.php:539
1508
+ #: includes/class-xcloner-settings.php:544
1509
  msgid "Cron frequency"
1510
  msgstr "Frecventa Cron"
1511
 
1512
+ #: includes/class-xcloner.php:162
1513
  #, php-format
1514
  msgid ""
1515
  "Unable to create the Backup Storage Location Folder %s . Please fix this "
1516
  "before starting the backup process."
1517
  msgstr ""
1518
 
1519
+ #: includes/class-xcloner.php:170
1520
  #, php-format
1521
  msgid ""
1522
  "Unable to write to the Backup Storage Location Folder %s . Please fix this "
1523
  "before starting the backup process."
1524
  msgstr ""
1525
 
1526
+ #: includes/class-xcloner.php:474
1527
  msgid "Once Weekly"
1528
  msgstr "O data pe saptamana"
1529
 
1530
+ #: includes/class-xcloner.php:479
1531
  msgid "Once a month"
1532
  msgstr "O data pe luna"
1533
 
1534
+ #: xcloner.php:68
1535
+ #, php-format
1536
+ msgid ""
1537
+ "XCloner requires minimum PHP version %s in order to run correctly. We have "
1538
+ "detected your version as %s. Plugin is now deactivated."
1539
+ msgstr ""
1540
+
1541
+ #~ msgid "Cron Options"
1542
+ #~ msgstr "Optiuni Cron"
1543
+
1544
+ #~ msgid "Ftp Storage"
1545
+ #~ msgstr "Stocare Ftp"
1546
+
1547
  #~ msgid "Provide url below to the <u>xcloner_restore.php</u> restore script"
1548
  #~ msgstr ""
1549
  #~ "Furnizati adresa web catre script-ul de restaurare <u>xcloner_restore."
public/class-xcloner-public.php CHANGED
@@ -47,10 +47,10 @@ class Xcloner_Public {
47
  * @param string $plugin_name The name of the plugin.
48
  * @param string $version The version of this plugin.
49
  */
50
- public function __construct( $plugin_name, $version ) {
51
 
52
- $this->plugin_name = $plugin_name;
53
- $this->version = $version;
54
 
55
  }
56
 
47
  * @param string $plugin_name The name of the plugin.
48
  * @param string $version The version of this plugin.
49
  */
50
+ public function __construct( Xcloner $xcloner_container ) {
51
 
52
+ $this->plugin_name = $xcloner_container->get_plugin_name();
53
+ $this->version = $xcloner_container->get_version();
54
 
55
  }
56
 
restore/{vendor.phar → vendor.build.txt} RENAMED
Binary file
restore/xcloner_restore.php.txt CHANGED
@@ -4,25 +4,37 @@ define('AUTH_KEY', '');
4
 
5
  define("DS", DIRECTORY_SEPARATOR);
6
 
7
- if(!AUTH_KEY or !isset($_REQUEST['hash']) or $_REQUEST['hash'] != AUTH_KEY)
8
  {
9
- Xcloner_Restore::send_response("404", "Could not run restore script, AUTH_KEY missing or invalid!");
10
  exit;
11
  }
12
 
13
- //check minimum PHP version
 
 
 
 
 
 
 
 
 
 
14
 
 
15
  if(version_compare(phpversion(), Xcloner_Restore::xcloner_minimum_version, '<'))
16
- {
17
- Xcloner_Restore::send_response(500, sprintf(("XCloner requires minimum PHP version %s in order to run correctly. We have detected your version as %s"),Xcloner_Restore::xcloner_minimum_version, phpversion()) );
18
- exit;
19
 
20
- }
21
 
22
  if(file_exists("vendor.phar") and extension_loaded('phar'))
 
23
  require_once(__DIR__.DS."vendor.phar");
24
- else
25
- {
26
  $file = dirname( __FILE__ ) . DS.'vendor'.DS.'autoload.php';
27
 
28
  if(!file_exists($file))
4
 
5
  define("DS", DIRECTORY_SEPARATOR);
6
 
7
+ if(!AUTH_KEY)
8
  {
9
+ Xcloner_Restore::send_response("404", "Could not run restore script, AUTH_KEY not set!");
10
  exit;
11
  }
12
 
13
+ if(!isset($_REQUEST['hash']))
14
+ {
15
+ Xcloner_Restore::send_response("404", "Could not run restore script, sent HASH is empty!");
16
+ exit;
17
+ }
18
+
19
+ if($_REQUEST['hash'] != AUTH_KEY)
20
+ {
21
+ Xcloner_Restore::send_response("404", "Could not run restore script, AUTH_KEY doesn't match the sent HASH!");
22
+ exit;
23
+ }
24
 
25
+ //check minimum PHP version
26
  if(version_compare(phpversion(), Xcloner_Restore::xcloner_minimum_version, '<'))
27
+ {
28
+ Xcloner_Restore::send_response(500, sprintf(("XCloner requires minimum PHP version %s in order to run correctly. We have detected your version as %s"),Xcloner_Restore::xcloner_minimum_version, phpversion()) );
29
+ exit;
30
 
31
+ }
32
 
33
  if(file_exists("vendor.phar") and extension_loaded('phar'))
34
+ {
35
  require_once(__DIR__.DS."vendor.phar");
36
+ }else{
37
+
38
  $file = dirname( __FILE__ ) . DS.'vendor'.DS.'autoload.php';
39
 
40
  if(!file_exists($file))
vendor/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Multipart;
3
+
4
+ use Aws\AwsClientInterface as Client;
5
+ use Aws\CommandInterface;
6
+ use Aws\CommandPool;
7
+ use Aws\Exception\AwsException;
8
+ use Aws\Exception\MultipartUploadException;
9
+ use Aws\Result;
10
+ use Aws\ResultInterface;
11
+ use GuzzleHttp\Promise;
12
+ use GuzzleHttp\Promise\PromiseInterface;
13
+ use GuzzleHttp\Psr7;
14
+ use InvalidArgumentException as IAE;
15
+ use Psr\Http\Message\RequestInterface;
16
+
17
+ /**
18
+ * Encapsulates the execution of a multipart upload to S3 or Glacier.
19
+ *
20
+ * @internal
21
+ */
22
+ abstract class AbstractUploadManager implements Promise\PromisorInterface
23
+ {
24
+ const DEFAULT_CONCURRENCY = 5;
25
+
26
+ /** @var array Default values for base multipart configuration */
27
+ private static $defaultConfig = [
28
+ 'part_size' => null,
29
+ 'state' => null,
30
+ 'concurrency' => self::DEFAULT_CONCURRENCY,
31
+ 'before_initiate' => null,
32
+ 'before_upload' => null,
33
+ 'before_complete' => null,
34
+ 'exception_class' => 'Aws\Exception\MultipartUploadException',
35
+ ];
36
+
37
+ /** @var Client Client used for the upload. */
38
+ protected $client;
39
+
40
+ /** @var array Configuration used to perform the upload. */
41
+ protected $config;
42
+
43
+ /** @var array Service-specific information about the upload workflow. */
44
+ protected $info;
45
+
46
+ /** @var PromiseInterface Promise that represents the multipart upload. */
47
+ protected $promise;
48
+
49
+ /** @var UploadState State used to manage the upload. */
50
+ protected $state;
51
+
52
+ /**
53
+ * @param Client $client
54
+ * @param array $config
55
+ */
56
+ public function __construct(Client $client, array $config = [])
57
+ {
58
+ $this->client = $client;
59
+ $this->info = $this->loadUploadWorkflowInfo();
60
+ $this->config = $config + self::$defaultConfig;
61
+ $this->state = $this->determineState();
62
+ }
63
+
64
+ /**
65
+ * Returns the current state of the upload
66
+ *
67
+ * @return UploadState
68
+ */
69
+ public function getState()
70
+ {
71
+ return $this->state;
72
+ }
73
+
74
+ /**
75
+ * Upload the source using multipart upload operations.
76
+ *
77
+ * @return Result The result of the CompleteMultipartUpload operation.
78
+ * @throws \LogicException if the upload is already complete or aborted.
79
+ * @throws MultipartUploadException if an upload operation fails.
80
+ */
81
+ public function upload()
82
+ {
83
+ return $this->promise()->wait();
84
+ }
85
+
86
+ /**
87
+ * Upload the source asynchronously using multipart upload operations.
88
+ *
89
+ * @return PromiseInterface
90
+ */
91
+ public function promise()
92
+ {
93
+ if ($this->promise) {
94
+ return $this->promise;
95
+ }
96
+
97
+ return $this->promise = Promise\coroutine(function () {
98
+ // Initiate the upload.
99
+ if ($this->state->isCompleted()) {
100
+ throw new \LogicException('This multipart upload has already '
101
+ . 'been completed or aborted.'
102
+ );
103
+ } elseif (!$this->state->isInitiated()) {
104
+ $result = (yield $this->execCommand('initiate', $this->getInitiateParams()));
105
+ $this->state->setUploadId(
106
+ $this->info['id']['upload_id'],
107
+ $result[$this->info['id']['upload_id']]
108
+ );
109
+ $this->state->setStatus(UploadState::INITIATED);
110
+ }
111
+
112
+ // Create a command pool from a generator that yields UploadPart
113
+ // commands for each upload part.
114
+ $resultHandler = $this->getResultHandler($errors);
115
+ $commands = new CommandPool(
116
+ $this->client,
117
+ $this->getUploadCommands($resultHandler),
118
+ [
119
+ 'concurrency' => $this->config['concurrency'],
120
+ 'before' => $this->config['before_upload'],
121
+ ]
122
+ );
123
+
124
+ // Execute the pool of commands concurrently, and process errors.
125
+ yield $commands->promise();
126
+ if ($errors) {
127
+ throw new $this->config['exception_class']($this->state, $errors);
128
+ }
129
+
130
+ // Complete the multipart upload.
131
+ yield $this->execCommand('complete', $this->getCompleteParams());
132
+ $this->state->setStatus(UploadState::COMPLETED);
133
+ })->otherwise(function (\Exception $e) {
134
+ // Throw errors from the operations as a specific Multipart error.
135
+ if ($e instanceof AwsException) {
136
+ $e = new $this->config['exception_class']($this->state, $e);
137
+ }
138
+ throw $e;
139
+ });
140
+ }
141
+
142
+ protected function getConfig()
143
+ {
144
+ return $this->config;
145
+ }
146
+
147
+ /**
148
+ * Provides service-specific information about the multipart upload
149
+ * workflow.
150
+ *
151
+ * This array of data should include the keys: 'command', 'id', and 'part_num'.
152
+ *
153
+ * @return array
154
+ */
155
+ abstract protected function loadUploadWorkflowInfo();
156
+
157
+ /**
158
+ * Determines the part size to use for upload parts.
159
+ *
160
+ * Examines the provided partSize value and the source to determine the
161
+ * best possible part size.
162
+ *
163
+ * @throws \InvalidArgumentException if the part size is invalid.
164
+ *
165
+ * @return int
166
+ */
167
+ abstract protected function determinePartSize();
168
+
169
+ /**
170
+ * Uses information from the Command and Result to determine which part was
171
+ * uploaded and mark it as uploaded in the upload's state.
172
+ *
173
+ * @param CommandInterface $command
174
+ * @param ResultInterface $result
175
+ */
176
+ abstract protected function handleResult(
177
+ CommandInterface $command,
178
+ ResultInterface $result
179
+ );
180
+
181
+ /**
182
+ * Gets the service-specific parameters used to initiate the upload.
183
+ *
184
+ * @return array
185
+ */
186
+ abstract protected function getInitiateParams();
187
+
188
+ /**
189
+ * Gets the service-specific parameters used to complete the upload.
190
+ *
191
+ * @return array
192
+ */
193
+ abstract protected function getCompleteParams();
194
+
195
+ /**
196
+ * Based on the config and service-specific workflow info, creates a
197
+ * `Promise` for an `UploadState` object.
198
+ *
199
+ * @return PromiseInterface A `Promise` that resolves to an `UploadState`.
200
+ */
201
+ private function determineState()
202
+ {
203
+ // If the state was provided via config, then just use it.
204
+ if ($this->config['state'] instanceof UploadState) {
205
+ return $this->config['state'];
206
+ }
207
+
208
+ // Otherwise, construct a new state from the provided identifiers.
209
+ $required = $this->info['id'];
210
+ $id = [$required['upload_id'] => null];
211
+ unset($required['upload_id']);
212
+ foreach ($required as $key => $param) {
213
+ if (!$this->config[$key]) {
214
+ throw new IAE('You must provide a value for "' . $key . '" in '
215
+ . 'your config for the MultipartUploader for '
216
+ . $this->client->getApi()->getServiceFullName() . '.');
217
+ }
218
+ $id[$param] = $this->config[$key];
219
+ }
220
+ $state = new UploadState($id);
221
+ $state->setPartSize($this->determinePartSize());
222
+
223
+ return $state;
224
+ }
225
+
226
+ /**
227
+ * Executes a MUP command with all of the parameters for the operation.
228
+ *
229
+ * @param string $operation Name of the operation.
230
+ * @param array $params Service-specific params for the operation.
231
+ *
232
+ * @return PromiseInterface
233
+ */
234
+ private function execCommand($operation, array $params)
235
+ {
236
+ // Create the command.
237
+ $command = $this->client->getCommand(
238
+ $this->info['command'][$operation],
239
+ $params + $this->state->getId()
240
+ );
241
+
242
+ // Execute the before callback.
243
+ if (is_callable($this->config["before_{$operation}"])) {
244
+ $this->config["before_{$operation}"]($command);
245
+ }
246
+
247
+ // Execute the command asynchronously and return the promise.
248
+ return $this->client->executeAsync($command);
249
+ }
250
+
251
+ /**
252
+ * Returns a middleware for processing responses of part upload operations.
253
+ *
254
+ * - Adds an onFulfilled callback that calls the service-specific
255
+ * handleResult method on the Result of the operation.
256
+ * - Adds an onRejected callback that adds the error to an array of errors.
257
+ * - Has a passedByRef $errors arg that the exceptions get added to. The
258
+ * caller should use that &$errors array to do error handling.
259
+ *
260
+ * @param array $errors Errors from upload operations are added to this.
261
+ *
262
+ * @return callable
263
+ */
264
+ private function getResultHandler(&$errors = [])
265
+ {
266
+ return function (callable $handler) use (&$errors) {
267
+ return function (
268
+ CommandInterface $command,
269
+ RequestInterface $request = null
270
+ ) use ($handler, &$errors) {
271
+ return $handler($command, $request)->then(
272
+ function (ResultInterface $result) use ($command) {
273
+ $this->handleResult($command, $result);
274
+ return $result;
275
+ },
276
+ function (AwsException $e) use (&$errors) {
277
+ $errors[$e->getCommand()[$this->info['part_num']]] = $e;
278
+ return new Result();
279
+ }
280
+ );
281
+ };
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Creates a generator that yields part data for the upload's source.
287
+ *
288
+ * Yields associative arrays of parameters that are ultimately merged in
289
+ * with others to form the complete parameters of a command. This can
290
+ * include the Body parameter, which is a limited stream (i.e., a Stream
291
+ * object, decorated with a LimitStream).
292
+ *
293
+ * @param callable $resultHandler
294
+ *
295
+ * @return \Generator
296
+ */
297
+ abstract protected function getUploadCommands(callable $resultHandler);
298
+ }
vendor/aws/aws-sdk-php/src/Multipart/AbstractUploader.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Multipart;
3
+
4
+ use Aws\AwsClientInterface as Client;
5
+ use GuzzleHttp\Psr7;
6
+ use InvalidArgumentException as IAE;
7
+ use Psr\Http\Message\StreamInterface as Stream;
8
+
9
+ abstract class AbstractUploader extends AbstractUploadManager
10
+ {
11
+ /** @var Stream Source of the data to be uploaded. */
12
+ protected $source;
13
+
14
+ /**
15
+ * @param Client $client
16
+ * @param mixed $source
17
+ * @param array $config
18
+ */
19
+ public function __construct(Client $client, $source, array $config = [])
20
+ {
21
+ $this->source = $this->determineSource($source);
22
+ parent::__construct($client, $config);
23
+ }
24
+
25
+ /**
26
+ * Create a stream for a part that starts at the current position and
27
+ * has a length of the upload part size (or less with the final part).
28
+ *
29
+ * @param Stream $stream
30
+ *
31
+ * @return Psr7\LimitStream
32
+ */
33
+ protected function limitPartStream(Stream $stream)
34
+ {
35
+ // Limit what is read from the stream to the part size.
36
+ return new Psr7\LimitStream(
37
+ $stream,
38
+ $this->state->getPartSize(),
39
+ $this->source->tell()
40
+ );
41
+ }
42
+
43
+ protected function getUploadCommands(callable $resultHandler)
44
+ {
45
+ // Determine if the source can be seeked.
46
+ $seekable = $this->source->isSeekable()
47
+ && $this->source->getMetadata('wrapper_type') === 'plainfile';
48
+
49
+ for ($partNumber = 1; $this->isEof($seekable); $partNumber++) {
50
+ // If we haven't already uploaded this part, yield a new part.
51
+ if (!$this->state->hasPartBeenUploaded($partNumber)) {
52
+ $partStartPos = $this->source->tell();
53
+ if (!($data = $this->createPart($seekable, $partNumber))) {
54
+ break;
55
+ }
56
+ $command = $this->client->getCommand(
57
+ $this->info['command']['upload'],
58
+ $data + $this->state->getId()
59
+ );
60
+ $command->getHandlerList()->appendSign($resultHandler, 'mup');
61
+ yield $command;
62
+ if ($this->source->tell() > $partStartPos) {
63
+ continue;
64
+ }
65
+ }
66
+
67
+ // Advance the source's offset if not already advanced.
68
+ if ($seekable) {
69
+ $this->source->seek(min(
70
+ $this->source->tell() + $this->state->getPartSize(),
71
+ $this->source->getSize()
72
+ ));
73
+ } else {
74
+ $this->source->read($this->state->getPartSize());
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Generates the parameters for an upload part by analyzing a range of the
81
+ * source starting from the current offset up to the part size.
82
+ *
83
+ * @param bool $seekable
84
+ * @param int $number
85
+ *
86
+ * @return array|null
87
+ */
88
+ abstract protected function createPart($seekable, $number);
89
+
90
+ /**
91
+ * Checks if the source is at EOF.
92
+ *
93
+ * @param bool $seekable
94
+ *
95
+ * @return bool
96
+ */
97
+ private function isEof($seekable)
98
+ {
99
+ return $seekable
100
+ ? $this->source->tell() < $this->source->getSize()
101
+ : !$this->source->eof();
102
+ }
103
+
104
+ /**
105
+ * Turns the provided source into a stream and stores it.
106
+ *
107
+ * If a string is provided, it is assumed to be a filename, otherwise, it
108
+ * passes the value directly to `Psr7\stream_for()`.
109
+ *
110
+ * @param mixed $source
111
+ *
112
+ * @return Stream
113
+ */
114
+ private function determineSource($source)
115
+ {
116
+ // Use the contents of a file as the data source.
117
+ if (is_string($source)) {
118
+ $source = Psr7\try_fopen($source, 'r');
119
+ }
120
+
121
+ // Create a source stream.
122
+ $stream = Psr7\stream_for($source);
123
+ if (!$stream->isReadable()) {
124
+ throw new IAE('Source stream must be readable.');
125
+ }
126
+
127
+ return $stream;
128
+ }
129
+ }
vendor/aws/aws-sdk-php/src/Multipart/UploadState.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Multipart;
3
+
4
+ /**
5
+ * Representation of the multipart upload.
6
+ *
7
+ * This object keeps track of the state of the upload, including the status and
8
+ * which parts have been uploaded.
9
+ */
10
+ class UploadState
11
+ {
12
+ const CREATED = 0;
13
+ const INITIATED = 1;
14
+ const COMPLETED = 2;
15
+
16
+ /** @var array Params used to identity the upload. */
17
+ private $id;
18
+
19
+ /** @var int Part size being used by the upload. */
20
+ private $partSize;
21
+
22
+ /** @var array Parts that have been uploaded. */
23
+ private $uploadedParts = [];
24
+
25
+ /** @var int Identifies the status the upload. */
26
+ private $status = self::CREATED;
27
+
28
+ /**
29
+ * @param array $id Params used to identity the upload.
30
+ */
31
+ public function __construct(array $id)
32
+ {
33
+ $this->id = $id;
34
+ }
35
+
36
+ /**
37
+ * Get the upload's ID, which is a tuple of parameters that can uniquely
38
+ * identify the upload.
39
+ *
40
+ * @return array
41
+ */
42
+ public function getId()
43
+ {
44
+ return $this->id;
45
+ }
46
+
47
+ /**
48
+ * Set's the "upload_id", or 3rd part of the upload's ID. This typically
49
+ * only needs to be done after initiating an upload.
50
+ *
51
+ * @param string $key The param key of the upload_id.
52
+ * @param string $value The param value of the upload_id.
53
+ */
54
+ public function setUploadId($key, $value)
55
+ {
56
+ $this->id[$key] = $value;
57
+ }
58
+
59
+ /**
60
+ * Get the part size.
61
+ *
62
+ * @return int
63
+ */
64
+ public function getPartSize()
65
+ {
66
+ return $this->partSize;
67
+ }
68
+
69
+ /**
70
+ * Set the part size.
71
+ *
72
+ * @param $partSize int Size of upload parts.
73
+ */
74
+ public function setPartSize($partSize)
75
+ {
76
+ $this->partSize = $partSize;
77
+ }
78
+
79
+ /**
80
+ * Marks a part as being uploaded.
81
+ *
82
+ * @param int $partNumber The part number.
83
+ * @param array $partData Data from the upload operation that needs to be
84
+ * recalled during the complete operation.
85
+ */
86
+ public function markPartAsUploaded($partNumber, array $partData = [])
87
+ {
88
+ $this->uploadedParts[$partNumber] = $partData;
89
+ }
90
+
91
+ /**
92
+ * Returns whether a part has been uploaded.
93
+ *
94
+ * @param int $partNumber The part number.
95
+ *
96
+ * @return bool
97
+ */
98
+ public function hasPartBeenUploaded($partNumber)
99
+ {
100
+ return isset($this->uploadedParts[$partNumber]);
101
+ }
102
+
103
+ /**
104
+ * Returns a sorted list of all the uploaded parts.
105
+ *
106
+ * @return array
107
+ */
108
+ public function getUploadedParts()
109
+ {
110
+ ksort($this->uploadedParts);
111
+
112
+ return $this->uploadedParts;
113
+ }
114
+
115
+ /**
116
+ * Set the status of the upload.
117
+ *
118
+ * @param int $status Status is an integer code defined by the constants
119
+ * CREATED, INITIATED, and COMPLETED on this class.
120
+ */
121
+ public function setStatus($status)
122
+ {
123
+ $this->status = $status;
124
+ }
125
+
126
+ /**
127
+ * Determines whether the upload state is in the INITIATED status.
128
+ *
129
+ * @return bool
130
+ */
131
+ public function isInitiated()
132
+ {
133
+ return $this->status === self::INITIATED;
134
+ }
135
+
136
+ /**
137
+ * Determines whether the upload state is in the COMPLETED status.
138
+ *
139
+ * @return bool
140
+ */
141
+ public function isCompleted()
142
+ {
143
+ return $this->status === self::COMPLETED;
144
+ }
145
+ }
vendor/composer/autoload_files.php CHANGED
@@ -8,8 +8,15 @@ $baseDir = dirname($vendorDir);
8
  return array(
9
  'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
10
  'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
 
11
  '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
12
  'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
 
 
 
 
 
13
  '8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php',
 
14
  'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
15
  );
8
  return array(
9
  'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
10
  'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
11
+ '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
12
  '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
13
  'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
14
+ '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
15
+ '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
16
+ '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
17
+ 'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
18
+ 'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
19
  '8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php',
20
+ 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
21
  'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
22
  );
vendor/composer/autoload_psr4.php CHANGED
@@ -8,11 +8,21 @@ $baseDir = dirname($vendorDir);
8
  return array(
9
  'splitbrain\\PHPArchive\\' => array($vendorDir . '/splitbrain/php-archive/src'),
10
  'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
 
 
 
 
 
 
 
 
 
11
  'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
12
  'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
13
  'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
14
  'MicrosoftAzure\\Storage\\' => array($vendorDir . '/microsoft/azure-storage/src'),
15
  'Mhetreramesh\\Flysystem\\' => array($vendorDir . '/mhetreramesh/flysystem-backblaze/src'),
 
16
  'League\\Flysystem\\Sftp\\' => array($vendorDir . '/league/flysystem-sftp/src'),
17
  'League\\Flysystem\\Dropbox\\' => array($vendorDir . '/league/flysystem-dropbox/src'),
18
  'League\\Flysystem\\Azure\\' => array($vendorDir . '/league/flysystem-azure/src'),
8
  return array(
9
  'splitbrain\\PHPArchive\\' => array($vendorDir . '/splitbrain/php-archive/src'),
10
  'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
11
+ 'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
12
+ 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
13
+ 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
14
+ 'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
15
+ 'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
16
+ 'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'),
17
+ 'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'),
18
+ 'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'),
19
+ 'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'),
20
  'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
21
  'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
22
  'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
23
  'MicrosoftAzure\\Storage\\' => array($vendorDir . '/microsoft/azure-storage/src'),
24
  'Mhetreramesh\\Flysystem\\' => array($vendorDir . '/mhetreramesh/flysystem-backblaze/src'),
25
+ 'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav/src'),
26
  'League\\Flysystem\\Sftp\\' => array($vendorDir . '/league/flysystem-sftp/src'),
27
  'League\\Flysystem\\Dropbox\\' => array($vendorDir . '/league/flysystem-dropbox/src'),
28
  'League\\Flysystem\\Azure\\' => array($vendorDir . '/league/flysystem-azure/src'),
vendor/composer/autoload_static.php CHANGED
@@ -9,9 +9,16 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
9
  public static $files = array (
10
  'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
11
  'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
 
12
  '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
13
  'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
 
 
 
 
 
14
  '8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php',
 
15
  'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
16
  );
17
 
@@ -24,6 +31,18 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
24
  array (
25
  'phpseclib\\' => 10,
26
  ),
 
 
 
 
 
 
 
 
 
 
 
 
27
  'P' =>
28
  array (
29
  'Psr\\Log\\' => 8,
@@ -37,6 +56,7 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
37
  ),
38
  'L' =>
39
  array (
 
40
  'League\\Flysystem\\Sftp\\' => 22,
41
  'League\\Flysystem\\Dropbox\\' => 25,
42
  'League\\Flysystem\\Azure\\' => 23,
@@ -72,6 +92,42 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
72
  array (
73
  0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
74
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  'Psr\\Log\\' =>
76
  array (
77
  0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
@@ -92,6 +148,10 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
92
  array (
93
  0 => __DIR__ . '/..' . '/mhetreramesh/flysystem-backblaze/src',
94
  ),
 
 
 
 
95
  'League\\Flysystem\\Sftp\\' =>
96
  array (
97
  0 => __DIR__ . '/..' . '/league/flysystem-sftp/src',
9
  public static $files = array (
10
  'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
11
  'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
12
+ '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
13
  '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
14
  'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
15
+ '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
16
+ '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
17
+ '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
18
+ 'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
19
+ 'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
20
  '8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php',
21
+ 'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
22
  'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
23
  );
24
 
31
  array (
32
  'phpseclib\\' => 10,
33
  ),
34
+ 'S' =>
35
+ array (
36
+ 'Sabre\\Xml\\' => 10,
37
+ 'Sabre\\VObject\\' => 14,
38
+ 'Sabre\\Uri\\' => 10,
39
+ 'Sabre\\HTTP\\' => 11,
40
+ 'Sabre\\Event\\' => 12,
41
+ 'Sabre\\DAV\\' => 10,
42
+ 'Sabre\\DAVACL\\' => 13,
43
+ 'Sabre\\CardDAV\\' => 14,
44
+ 'Sabre\\CalDAV\\' => 13,
45
+ ),
46
  'P' =>
47
  array (
48
  'Psr\\Log\\' => 8,
56
  ),
57
  'L' =>
58
  array (
59
+ 'League\\Flysystem\\WebDAV\\' => 24,
60
  'League\\Flysystem\\Sftp\\' => 22,
61
  'League\\Flysystem\\Dropbox\\' => 25,
62
  'League\\Flysystem\\Azure\\' => 23,
92
  array (
93
  0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
94
  ),
95
+ 'Sabre\\Xml\\' =>
96
+ array (
97
+ 0 => __DIR__ . '/..' . '/sabre/xml/lib',
98
+ ),
99
+ 'Sabre\\VObject\\' =>
100
+ array (
101
+ 0 => __DIR__ . '/..' . '/sabre/vobject/lib',
102
+ ),
103
+ 'Sabre\\Uri\\' =>
104
+ array (
105
+ 0 => __DIR__ . '/..' . '/sabre/uri/lib',
106
+ ),
107
+ 'Sabre\\HTTP\\' =>
108
+ array (
109
+ 0 => __DIR__ . '/..' . '/sabre/http/lib',
110
+ ),
111
+ 'Sabre\\Event\\' =>
112
+ array (
113
+ 0 => __DIR__ . '/..' . '/sabre/event/lib',
114
+ ),
115
+ 'Sabre\\DAV\\' =>
116
+ array (
117
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV',
118
+ ),
119
+ 'Sabre\\DAVACL\\' =>
120
+ array (
121
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL',
122
+ ),
123
+ 'Sabre\\CardDAV\\' =>
124
+ array (
125
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV',
126
+ ),
127
+ 'Sabre\\CalDAV\\' =>
128
+ array (
129
+ 0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV',
130
+ ),
131
  'Psr\\Log\\' =>
132
  array (
133
  0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
148
  array (
149
  0 => __DIR__ . '/..' . '/mhetreramesh/flysystem-backblaze/src',
150
  ),
151
+ 'League\\Flysystem\\WebDAV\\' =>
152
+ array (
153
+ 0 => __DIR__ . '/..' . '/league/flysystem-webdav/src',
154
+ ),
155
  'League\\Flysystem\\Sftp\\' =>
156
  array (
157
  0 => __DIR__ . '/..' . '/league/flysystem-sftp/src',
vendor/composer/installed.json CHANGED
@@ -1227,5 +1227,473 @@
1227
  "client",
1228
  "filesystem"
1229
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1230
  }
1231
  ]
1227
  "client",
1228
  "filesystem"
1229
  ]
1230
+ },
1231
+ {
1232
+ "name": "sabre/uri",
1233
+ "version": "1.2.1",
1234
+ "version_normalized": "1.2.1.0",
1235
+ "source": {
1236
+ "type": "git",
1237
+ "url": "https://github.com/fruux/sabre-uri.git",
1238
+ "reference": "ada354d83579565949d80b2e15593c2371225e61"
1239
+ },
1240
+ "dist": {
1241
+ "type": "zip",
1242
+ "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/ada354d83579565949d80b2e15593c2371225e61",
1243
+ "reference": "ada354d83579565949d80b2e15593c2371225e61",
1244
+ "shasum": ""
1245
+ },
1246
+ "require": {
1247
+ "php": ">=5.4.7"
1248
+ },
1249
+ "require-dev": {
1250
+ "phpunit/phpunit": ">=4.0,<6.0",
1251
+ "sabre/cs": "~1.0.0"
1252
+ },
1253
+ "time": "2017-02-20T19:59:28+00:00",
1254
+ "type": "library",
1255
+ "installation-source": "dist",
1256
+ "autoload": {
1257
+ "files": [
1258
+ "lib/functions.php"
1259
+ ],
1260
+ "psr-4": {
1261
+ "Sabre\\Uri\\": "lib/"
1262
+ }
1263
+ },
1264
+ "notification-url": "https://packagist.org/downloads/",
1265
+ "license": [
1266
+ "BSD-3-Clause"
1267
+ ],
1268
+ "authors": [
1269
+ {
1270
+ "name": "Evert Pot",
1271
+ "email": "me@evertpot.com",
1272
+ "homepage": "http://evertpot.com/",
1273
+ "role": "Developer"
1274
+ }
1275
+ ],
1276
+ "description": "Functions for making sense out of URIs.",
1277
+ "homepage": "http://sabre.io/uri/",
1278
+ "keywords": [
1279
+ "rfc3986",
1280
+ "uri",
1281
+ "url"
1282
+ ]
1283
+ },
1284
+ {
1285
+ "name": "sabre/xml",
1286
+ "version": "1.5.0",
1287
+ "version_normalized": "1.5.0.0",
1288
+ "source": {
1289
+ "type": "git",
1290
+ "url": "https://github.com/fruux/sabre-xml.git",
1291
+ "reference": "59b20e5bbace9912607481634f97d05a776ffca7"
1292
+ },
1293
+ "dist": {
1294
+ "type": "zip",
1295
+ "url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7",
1296
+ "reference": "59b20e5bbace9912607481634f97d05a776ffca7",
1297
+ "shasum": ""
1298
+ },
1299
+ "require": {
1300
+ "ext-dom": "*",
1301
+ "ext-xmlreader": "*",
1302
+ "ext-xmlwriter": "*",
1303
+ "lib-libxml": ">=2.6.20",
1304
+ "php": ">=5.5.5",
1305
+ "sabre/uri": ">=1.0,<3.0.0"
1306
+ },
1307
+ "require-dev": {
1308
+ "phpunit/phpunit": "*",
1309
+ "sabre/cs": "~1.0.0"
1310
+ },
1311
+ "time": "2016-10-09T22:57:52+00:00",
1312
+ "type": "library",
1313
+ "installation-source": "dist",
1314
+ "autoload": {
1315
+ "psr-4": {
1316
+ "Sabre\\Xml\\": "lib/"
1317
+ },
1318
+ "files": [
1319
+ "lib/Deserializer/functions.php",
1320
+ "lib/Serializer/functions.php"
1321
+ ]
1322
+ },
1323
+ "notification-url": "https://packagist.org/downloads/",
1324
+ "license": [
1325
+ "BSD-3-Clause"
1326
+ ],
1327
+ "authors": [
1328
+ {
1329
+ "name": "Evert Pot",
1330
+ "email": "me@evertpot.com",
1331
+ "homepage": "http://evertpot.com/",
1332
+ "role": "Developer"
1333
+ },
1334
+ {
1335
+ "name": "Markus Staab",
1336
+ "email": "markus.staab@redaxo.de",
1337
+ "role": "Developer"
1338
+ }
1339
+ ],
1340
+ "description": "sabre/xml is an XML library that you may not hate.",
1341
+ "homepage": "https://sabre.io/xml/",
1342
+ "keywords": [
1343
+ "XMLReader",
1344
+ "XMLWriter",
1345
+ "dom",
1346
+ "xml"
1347
+ ]
1348
+ },
1349
+ {
1350
+ "name": "sabre/vobject",
1351
+ "version": "4.1.2",
1352
+ "version_normalized": "4.1.2.0",
1353
+ "source": {
1354
+ "type": "git",
1355
+ "url": "https://github.com/fruux/sabre-vobject.git",
1356
+ "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c"
1357
+ },
1358
+ "dist": {
1359
+ "type": "zip",
1360
+ "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
1361
+ "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
1362
+ "shasum": ""
1363
+ },
1364
+ "require": {
1365
+ "ext-mbstring": "*",
1366
+ "php": ">=5.5",
1367
+ "sabre/xml": ">=1.5 <3.0"
1368
+ },
1369
+ "require-dev": {
1370
+ "phpunit/phpunit": "*",
1371
+ "sabre/cs": "^1.0.0"
1372
+ },
1373
+ "suggest": {
1374
+ "hoa/bench": "If you would like to run the benchmark scripts"
1375
+ },
1376
+ "time": "2016-12-06T04:14:09+00:00",
1377
+ "bin": [
1378
+ "bin/vobject",
1379
+ "bin/generate_vcards"
1380
+ ],
1381
+ "type": "library",
1382
+ "extra": {
1383
+ "branch-alias": {
1384
+ "dev-master": "4.0.x-dev"
1385
+ }
1386
+ },
1387
+ "installation-source": "dist",
1388
+ "autoload": {
1389
+ "psr-4": {
1390
+ "Sabre\\VObject\\": "lib/"
1391
+ }
1392
+ },
1393
+ "notification-url": "https://packagist.org/downloads/",
1394
+ "license": [
1395
+ "BSD-3-Clause"
1396
+ ],
1397
+ "authors": [
1398
+ {
1399
+ "name": "Evert Pot",
1400
+ "email": "me@evertpot.com",
1401
+ "homepage": "http://evertpot.com/",
1402
+ "role": "Developer"
1403
+ },
1404
+ {
1405
+ "name": "Dominik Tobschall",
1406
+ "email": "dominik@fruux.com",
1407
+ "homepage": "http://tobschall.de/",
1408
+ "role": "Developer"
1409
+ },
1410
+ {
1411
+ "name": "Ivan Enderlin",
1412
+ "email": "ivan.enderlin@hoa-project.net",
1413
+ "homepage": "http://mnt.io/",
1414
+ "role": "Developer"
1415
+ }
1416
+ ],
1417
+ "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
1418
+ "homepage": "http://sabre.io/vobject/",
1419
+ "keywords": [
1420
+ "availability",
1421
+ "freebusy",
1422
+ "iCalendar",
1423
+ "ical",
1424
+ "ics",
1425
+ "jCal",
1426
+ "jCard",
1427
+ "recurrence",
1428
+ "rfc2425",
1429
+ "rfc2426",
1430
+ "rfc2739",
1431
+ "rfc4770",
1432
+ "rfc5545",
1433
+ "rfc5546",
1434
+ "rfc6321",
1435
+ "rfc6350",
1436
+ "rfc6351",
1437
+ "rfc6474",
1438
+ "rfc6638",
1439
+ "rfc6715",
1440
+ "rfc6868",
1441
+ "vCalendar",
1442
+ "vCard",
1443
+ "vcf",
1444
+ "xCal",
1445
+ "xCard"
1446
+ ]
1447
+ },
1448
+ {
1449
+ "name": "sabre/event",
1450
+ "version": "3.0.0",
1451
+ "version_normalized": "3.0.0.0",
1452
+ "source": {
1453
+ "type": "git",
1454
+ "url": "https://github.com/fruux/sabre-event.git",
1455
+ "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
1456
+ },
1457
+ "dist": {
1458
+ "type": "zip",
1459
+ "url": "https://api.github.com/repos/fruux/sabre-event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534",
1460
+ "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534",
1461
+ "shasum": ""
1462
+ },
1463
+ "require": {
1464
+ "php": ">=5.5"
1465
+ },
1466
+ "require-dev": {
1467
+ "phpunit/phpunit": "*",
1468
+ "sabre/cs": "~0.0.4"
1469
+ },
1470
+ "time": "2015-11-05T20:14:39+00:00",
1471
+ "type": "library",
1472
+ "installation-source": "dist",
1473
+ "autoload": {
1474
+ "psr-4": {
1475
+ "Sabre\\Event\\": "lib/"
1476
+ },
1477
+ "files": [
1478
+ "lib/coroutine.php",
1479
+ "lib/Loop/functions.php",
1480
+ "lib/Promise/functions.php"
1481
+ ]
1482
+ },
1483
+ "notification-url": "https://packagist.org/downloads/",
1484
+ "license": [
1485
+ "BSD-3-Clause"
1486
+ ],
1487
+ "authors": [
1488
+ {
1489
+ "name": "Evert Pot",
1490
+ "email": "me@evertpot.com",
1491
+ "homepage": "http://evertpot.com/",
1492
+ "role": "Developer"
1493
+ }
1494
+ ],
1495
+ "description": "sabre/event is a library for lightweight event-based programming",
1496
+ "homepage": "http://sabre.io/event/",
1497
+ "keywords": [
1498
+ "EventEmitter",
1499
+ "async",
1500
+ "events",
1501
+ "hooks",
1502
+ "plugin",
1503
+ "promise",
1504
+ "signal"
1505
+ ]
1506
+ },
1507
+ {
1508
+ "name": "sabre/http",
1509
+ "version": "4.2.2",
1510
+ "version_normalized": "4.2.2.0",
1511
+ "source": {
1512
+ "type": "git",
1513
+ "url": "https://github.com/fruux/sabre-http.git",
1514
+ "reference": "dd50e7260356f4599d40270826f9548b23efa204"
1515
+ },
1516
+ "dist": {
1517
+ "type": "zip",
1518
+ "url": "https://api.github.com/repos/fruux/sabre-http/zipball/dd50e7260356f4599d40270826f9548b23efa204",
1519
+ "reference": "dd50e7260356f4599d40270826f9548b23efa204",
1520
+ "shasum": ""
1521
+ },
1522
+ "require": {
1523
+ "ext-ctype": "*",
1524
+ "ext-mbstring": "*",
1525
+ "php": ">=5.4",
1526
+ "sabre/event": ">=1.0.0,<4.0.0",
1527
+ "sabre/uri": "~1.0"
1528
+ },
1529
+ "require-dev": {
1530
+ "phpunit/phpunit": "~4.3",
1531
+ "sabre/cs": "~0.0.1"
1532
+ },
1533
+ "suggest": {
1534
+ "ext-curl": " to make http requests with the Client class"
1535
+ },
1536
+ "time": "2017-01-02T19:38:42+00:00",
1537
+ "type": "library",
1538
+ "installation-source": "dist",
1539
+ "autoload": {
1540
+ "files": [
1541
+ "lib/functions.php"
1542
+ ],
1543
+ "psr-4": {
1544
+ "Sabre\\HTTP\\": "lib/"
1545
+ }
1546
+ },
1547
+ "notification-url": "https://packagist.org/downloads/",
1548
+ "license": [
1549
+ "BSD-3-Clause"
1550
+ ],
1551
+ "authors": [
1552
+ {
1553
+ "name": "Evert Pot",
1554
+ "email": "me@evertpot.com",
1555
+ "homepage": "http://evertpot.com/",
1556
+ "role": "Developer"
1557
+ }
1558
+ ],
1559
+ "description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
1560
+ "homepage": "https://github.com/fruux/sabre-http",
1561
+ "keywords": [
1562
+ "http"
1563
+ ]
1564
+ },
1565
+ {
1566
+ "name": "sabre/dav",
1567
+ "version": "3.2.2",
1568
+ "version_normalized": "3.2.2.0",
1569
+ "source": {
1570
+ "type": "git",
1571
+ "url": "https://github.com/fruux/sabre-dav.git",
1572
+ "reference": "e987775e619728f12205606c9cc3ee565ffb1516"
1573
+ },
1574
+ "dist": {
1575
+ "type": "zip",
1576
+ "url": "https://api.github.com/repos/fruux/sabre-dav/zipball/e987775e619728f12205606c9cc3ee565ffb1516",
1577
+ "reference": "e987775e619728f12205606c9cc3ee565ffb1516",
1578
+ "shasum": ""
1579
+ },
1580
+ "require": {
1581
+ "ext-ctype": "*",
1582
+ "ext-date": "*",
1583
+ "ext-dom": "*",
1584
+ "ext-iconv": "*",
1585
+ "ext-mbstring": "*",
1586
+ "ext-pcre": "*",
1587
+ "ext-simplexml": "*",
1588
+ "ext-spl": "*",
1589
+ "lib-libxml": ">=2.7.0",
1590
+ "php": ">=5.5.0",
1591
+ "psr/log": "^1.0",
1592
+ "sabre/event": ">=2.0.0, <4.0.0",
1593
+ "sabre/http": "^4.2.1",
1594
+ "sabre/uri": "^1.0.1",
1595
+ "sabre/vobject": "^4.1.0",
1596
+ "sabre/xml": "^1.4.0"
1597
+ },
1598
+ "require-dev": {
1599
+ "evert/phpdoc-md": "~0.1.0",
1600
+ "monolog/monolog": "^1.18",
1601
+ "phpunit/phpunit": "> 4.8, <6.0.0",
1602
+ "sabre/cs": "^1.0.0"
1603
+ },
1604
+ "suggest": {
1605
+ "ext-curl": "*",
1606
+ "ext-pdo": "*"
1607
+ },
1608
+ "time": "2017-02-15T03:06:08+00:00",
1609
+ "bin": [
1610
+ "bin/sabredav",
1611
+ "bin/naturalselection"
1612
+ ],
1613
+ "type": "library",
1614
+ "extra": {
1615
+ "branch-alias": {
1616
+ "dev-master": "3.1.0-dev"
1617
+ }
1618
+ },
1619
+ "installation-source": "dist",
1620
+ "autoload": {
1621
+ "psr-4": {
1622
+ "Sabre\\DAV\\": "lib/DAV/",
1623
+ "Sabre\\DAVACL\\": "lib/DAVACL/",
1624
+ "Sabre\\CalDAV\\": "lib/CalDAV/",
1625
+ "Sabre\\CardDAV\\": "lib/CardDAV/"
1626
+ }
1627
+ },
1628
+ "notification-url": "https://packagist.org/downloads/",
1629
+ "license": [
1630
+ "BSD-3-Clause"
1631
+ ],
1632
+ "authors": [
1633
+ {
1634
+ "name": "Evert Pot",
1635
+ "email": "me@evertpot.com",
1636
+ "homepage": "http://evertpot.com/",
1637
+ "role": "Developer"
1638
+ }
1639
+ ],
1640
+ "description": "WebDAV Framework for PHP",
1641
+ "homepage": "http://sabre.io/",
1642
+ "keywords": [
1643
+ "CalDAV",
1644
+ "CardDAV",
1645
+ "WebDAV",
1646
+ "framework",
1647
+ "iCalendar"
1648
+ ]
1649
+ },
1650
+ {
1651
+ "name": "league/flysystem-webdav",
1652
+ "version": "1.0.5",
1653
+ "version_normalized": "1.0.5.0",
1654
+ "source": {
1655
+ "type": "git",
1656
+ "url": "https://github.com/thephpleague/flysystem-webdav.git",
1657
+ "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7"
1658
+ },
1659
+ "dist": {
1660
+ "type": "zip",
1661
+ "url": "https://api.github.com/repos/thephpleague/flysystem-webdav/zipball/5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
1662
+ "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
1663
+ "shasum": ""
1664
+ },
1665
+ "require": {
1666
+ "league/flysystem": "~1.0",
1667
+ "php": ">=5.5.0",
1668
+ "sabre/dav": "~3.1"
1669
+ },
1670
+ "require-dev": {
1671
+ "mockery/mockery": "~0.9",
1672
+ "phpunit/phpunit": "~4.0"
1673
+ },
1674
+ "time": "2016-12-14T11:28:55+00:00",
1675
+ "type": "library",
1676
+ "extra": {
1677
+ "branch-alias": {
1678
+ "dev-master": "1.0-dev"
1679
+ }
1680
+ },
1681
+ "installation-source": "dist",
1682
+ "autoload": {
1683
+ "psr-4": {
1684
+ "League\\Flysystem\\WebDAV\\": "src"
1685
+ }
1686
+ },
1687
+ "notification-url": "https://packagist.org/downloads/",
1688
+ "license": [
1689
+ "MIT"
1690
+ ],
1691
+ "authors": [
1692
+ {
1693
+ "name": "Frank de Jonge",
1694
+ "email": "info@frenky.net"
1695
+ }
1696
+ ],
1697
+ "description": "Flysystem adapter for WebDAV"
1698
  }
1699
  ]
vendor/league/flysystem-webdav/LICENCE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2014 Frank de Jonge
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/league/flysystem-webdav/changelog.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ ### 1.0.5 - 2016-12-14
4
+
5
+ ### Fixed
6
+
7
+ * Copy now also works with native webdav capabilities.
8
+
9
+ ## 1.0.4 - 2016-08-11
10
+
11
+ ### Fixed
12
+
13
+ * The getMetadata function now also catches http exceptions.
14
+
15
+ ## 1.0.3 - 2016-07-11
16
+
17
+ ### Improved
18
+
19
+ * The `sabre/dav` version is upped to ~3.1.
20
+
21
+ ## 1.0.2 - 2016-03-17
22
+
23
+ ### Improved
24
+
25
+ * Listing contents now prevents 301 redirects by adding a trailing slash.
26
+
27
+ ## 1.0.1 - 2015-05-18
28
+
29
+ ### Fixed
30
+
31
+ * Corrected namespace for missing object exception catching.
32
+ * Corrected last-modified return type handling (int -> [int])
33
+
34
+
35
+ ## 1.0.0
36
+
37
+ Initial release
vendor/league/flysystem-webdav/composer.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "league/flysystem-webdav",
3
+ "description": "Flysystem adapter for WebDAV",
4
+ "license": "MIT",
5
+ "authors": [
6
+ {
7
+ "name": "Frank de Jonge",
8
+ "email": "info@frenky.net"
9
+ }
10
+ ],
11
+ "require": {
12
+ "php": ">=5.5.0",
13
+ "league/flysystem": "~1.0",
14
+ "sabre/dav": "~3.1"
15
+ },
16
+ "require-dev": {
17
+ "phpunit/phpunit": "~4.0",
18
+ "mockery/mockery": "~0.9"
19
+ },
20
+ "autoload": {
21
+ "psr-4": {
22
+ "League\\Flysystem\\WebDAV\\": "src"
23
+ }
24
+ },
25
+ "config": {
26
+ "bin-dir": "bin"
27
+ },
28
+ "extra": {
29
+ "branch-alias": {
30
+ "dev-master": "1.0-dev"
31
+ }
32
+ }
33
+ }
vendor/league/flysystem-webdav/src/WebDAVAdapter.php ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace League\Flysystem\WebDAV;
4
+
5
+ use League\Flysystem\Adapter\AbstractAdapter;
6
+ use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
7
+ use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
8
+ use League\Flysystem\Adapter\Polyfill\StreamedTrait;
9
+ use League\Flysystem\Config;
10
+ use League\Flysystem\Util;
11
+ use LogicException;
12
+ use Sabre\DAV\Client;
13
+ use Sabre\DAV\Exception;
14
+ use Sabre\DAV\Exception\NotFound;
15
+ use Sabre\HTTP\ClientHttpException;
16
+ use Sabre\HTTP\HttpException;
17
+
18
+ class WebDAVAdapter extends AbstractAdapter
19
+ {
20
+ use StreamedTrait;
21
+ use StreamedCopyTrait {
22
+ StreamedCopyTrait::copy as streamedCopy;
23
+ }
24
+ use NotSupportingVisibilityTrait;
25
+
26
+ /**
27
+ * @var array
28
+ */
29
+ protected static $resultMap = [
30
+ '{DAV:}getcontentlength' => 'size',
31
+ '{DAV:}getcontenttype' => 'mimetype',
32
+ 'content-length' => 'size',
33
+ 'content-type' => 'mimetype',
34
+ ];
35
+
36
+ /**
37
+ * @var Client
38
+ */
39
+ protected $client;
40
+
41
+ /**
42
+ * @var bool
43
+ */
44
+ protected $useStreamedCopy = true;
45
+
46
+ /**
47
+ * Constructor.
48
+ *
49
+ * @param Client $client
50
+ * @param string $prefix
51
+ * @param bool $useStreamedCopy
52
+ */
53
+ public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
54
+ {
55
+ $this->client = $client;
56
+ $this->setPathPrefix($prefix);
57
+ $this->setUseStreamedCopy($useStreamedCopy);
58
+ }
59
+
60
+ protected function stream($path, $resource, Config $config, $fallback)
61
+ {
62
+ Util::rewindStream($resource);
63
+ $fallbackCall = [$this, $fallback];
64
+
65
+ return call_user_func($fallbackCall, $path, $resource, $config);
66
+ }
67
+
68
+ /**
69
+ * url encode a path
70
+ *
71
+ * @param string $path
72
+ *
73
+ * @return string
74
+ */
75
+ protected function encodePath($path)
76
+ {
77
+ $a = explode('/', $path);
78
+ for ($i=0; $i<count($a); $i++) {
79
+ $a[$i] = rawurlencode($a[$i]);
80
+ }
81
+ return implode('/', $a);
82
+ }
83
+
84
+ /**
85
+ * {@inheritdoc}
86
+ */
87
+ public function getMetadata($path)
88
+ {
89
+ $location = $this->applyPathPrefix($this->encodePath($path));
90
+
91
+ try {
92
+ $result = $this->client->propFind($location, [
93
+ '{DAV:}displayname',
94
+ '{DAV:}getcontentlength',
95
+ '{DAV:}getcontenttype',
96
+ '{DAV:}getlastmodified',
97
+ ]);
98
+
99
+ return $this->normalizeObject($result, $path);
100
+ } catch (Exception $e) {
101
+ return false;
102
+ } catch (HttpException $e) {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * {@inheritdoc}
109
+ */
110
+ public function has($path)
111
+ {
112
+ return $this->getMetadata($path);
113
+ }
114
+
115
+ /**
116
+ * {@inheritdoc}
117
+ */
118
+ public function read($path)
119
+ {
120
+ $location = $this->applyPathPrefix($this->encodePath($path));
121
+
122
+ try {
123
+ $response = $this->client->request('GET', $location);
124
+
125
+ if ($response['statusCode'] !== 200) {
126
+ return false;
127
+ }
128
+
129
+ return array_merge([
130
+ 'contents' => $response['body'],
131
+ 'timestamp' => strtotime(is_array($response['headers']['last-modified'])
132
+ ? current($response['headers']['last-modified'])
133
+ : $response['headers']['last-modified']),
134
+ 'path' => $path,
135
+ ], Util::map($response['headers'], static::$resultMap));
136
+ } catch (Exception $e) {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * {@inheritdoc}
143
+ */
144
+ public function write($path, $contents, Config $config)
145
+ {
146
+ if (!$this->createDir(Util::dirname($path), $config)) {
147
+ return false;
148
+ }
149
+
150
+ $location = $this->applyPathPrefix($this->encodePath($path));
151
+ $response = $this->client->request('PUT', $location, $contents);
152
+
153
+ if ($response['statusCode'] >= 400) {
154
+ return false;
155
+ }
156
+
157
+ $result = compact('path', 'contents');
158
+
159
+ if ($config->get('visibility')) {
160
+ throw new LogicException(__CLASS__.' does not support visibility settings.');
161
+ }
162
+
163
+ return $result;
164
+ }
165
+
166
+ /**
167
+ * {@inheritdoc}
168
+ */
169
+ public function update($path, $contents, Config $config)
170
+ {
171
+ return $this->write($path, $contents, $config);
172
+ }
173
+
174
+ /**
175
+ * {@inheritdoc}
176
+ */
177
+ public function rename($path, $newpath)
178
+ {
179
+ $location = $this->applyPathPrefix($this->encodePath($path));
180
+ $newLocation = $this->applyPathPrefix($this->encodePath($newpath));
181
+
182
+ try {
183
+ $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
184
+ 'Destination' => '/'.ltrim($newLocation, '/'),
185
+ ]);
186
+
187
+ if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
188
+ return true;
189
+ }
190
+ } catch (NotFound $e) {
191
+ // Would have returned false here, but would be redundant
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * {@inheritdoc}
199
+ */
200
+ public function copy($path, $newpath)
201
+ {
202
+ if ($this->useStreamedCopy === true) {
203
+ return $this->streamedCopy($path, $newpath);
204
+ } else {
205
+ return $this->nativeCopy($path, $newpath);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * {@inheritdoc}
211
+ */
212
+ public function delete($path)
213
+ {
214
+ $location = $this->applyPathPrefix($this->encodePath($path));
215
+
216
+ try {
217
+ $this->client->request('DELETE', $location);
218
+
219
+ return true;
220
+ } catch (NotFound $e) {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * {@inheritdoc}
227
+ */
228
+ public function createDir($path, Config $config)
229
+ {
230
+ $encodedPath = $this->encodePath($path);
231
+ $path = trim($path, '/');
232
+
233
+ $result = compact('path') + ['type' => 'dir'];
234
+
235
+ if (Util::normalizeDirname($path) === '' || $this->has($path)) {
236
+ return $result;
237
+ }
238
+
239
+ $directories = explode('/', $path);
240
+ if (count($directories) > 1) {
241
+ $parentDirectories = array_splice($directories, 0, count($directories) - 1);
242
+ if (!$this->createDir(implode('/', $parentDirectories), $config)) {
243
+ return false;
244
+ }
245
+ }
246
+
247
+ $location = $this->applyPathPrefix($encodedPath);
248
+ $response = $this->client->request('MKCOL', $location);
249
+
250
+ if ($response['statusCode'] !== 201) {
251
+ return false;
252
+ }
253
+
254
+ return $result;
255
+ }
256
+
257
+ /**
258
+ * {@inheritdoc}
259
+ */
260
+ public function deleteDir($dirname)
261
+ {
262
+ return $this->delete($dirname);
263
+ }
264
+
265
+ /**
266
+ * {@inheritdoc}
267
+ */
268
+ public function listContents($directory = '', $recursive = false)
269
+ {
270
+ $location = $this->applyPathPrefix($this->encodePath($directory));
271
+ $response = $this->client->propFind($location . '/', [
272
+ '{DAV:}displayname',
273
+ '{DAV:}getcontentlength',
274
+ '{DAV:}getcontenttype',
275
+ '{DAV:}getlastmodified',
276
+ ], 1);
277
+
278
+ array_shift($response);
279
+ $result = [];
280
+
281
+ foreach ($response as $path => $object) {
282
+ $path = urldecode($this->removePathPrefix($path));
283
+ $object = $this->normalizeObject($object, $path);
284
+ $result[] = $object;
285
+
286
+ if ($recursive && $object['type'] === 'dir') {
287
+ $result = array_merge($result, $this->listContents($object['path'], true));
288
+ }
289
+ }
290
+
291
+ return $result;
292
+ }
293
+
294
+ /**
295
+ * {@inheritdoc}
296
+ */
297
+ public function getSize($path)
298
+ {
299
+ return $this->getMetadata($path);
300
+ }
301
+
302
+ /**
303
+ * {@inheritdoc}
304
+ */
305
+ public function getTimestamp($path)
306
+ {
307
+ return $this->getMetadata($path);
308
+ }
309
+
310
+ /**
311
+ * {@inheritdoc}
312
+ */
313
+ public function getMimetype($path)
314
+ {
315
+ return $this->getMetadata($path);
316
+ }
317
+
318
+ /**
319
+ * @return boolean
320
+ */
321
+ public function getUseStreamedCopy()
322
+ {
323
+ return $this->useStreamedCopy;
324
+ }
325
+
326
+ /**
327
+ * @param boolean $useStreamedCopy
328
+ */
329
+ public function setUseStreamedCopy($useStreamedCopy)
330
+ {
331
+ $this->useStreamedCopy = (bool)$useStreamedCopy;
332
+ }
333
+
334
+ /**
335
+ * Copy a file through WebDav COPY method.
336
+ *
337
+ * @param string $path
338
+ * @param string $newPath
339
+ *
340
+ * @return bool
341
+ */
342
+ protected function nativeCopy($path, $newPath)
343
+ {
344
+ if (!$this->createDir(Util::dirname($newPath), new Config())) {
345
+ return false;
346
+ }
347
+
348
+ $location = $this->applyPathPrefix($this->encodePath($path));
349
+ $newLocation = $this->applyPathPrefix($this->encodePath($newPath));
350
+
351
+ try {
352
+ $destination = $this->client->getAbsoluteUrl($newLocation);
353
+ $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
354
+ 'Destination' => $destination,
355
+ ]);
356
+
357
+ if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
358
+ return true;
359
+ }
360
+ } catch (NotFound $e) {
361
+ // Would have returned false here, but would be redundant
362
+ }
363
+
364
+ return false;
365
+ }
366
+
367
+ /**
368
+ * Normalise a WebDAV repsonse object.
369
+ *
370
+ * @param array $object
371
+ * @param string $path
372
+ *
373
+ * @return array
374
+ */
375
+ protected function normalizeObject(array $object, $path)
376
+ {
377
+ if (! isset($object['{DAV:}getcontentlength']) or $object['{DAV:}getcontentlength'] == "") {
378
+ return ['type' => 'dir', 'path' => trim($path, '/')];
379
+ }
380
+
381
+ $result = Util::map($object, static::$resultMap);
382
+
383
+ if (isset($object['{DAV:}getlastmodified'])) {
384
+ $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
385
+ }
386
+
387
+ $result['type'] = 'file';
388
+ $result['path'] = trim($path, '/');
389
+
390
+ return $result;
391
+ }
392
+ }
vendor/sabre/dav/.gitignore ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unit tests
2
+ tests/temp
3
+ tests/.sabredav
4
+ tests/cov
5
+
6
+ # Custom settings for tests
7
+ tests/config.user.php
8
+
9
+ # ViM
10
+ *.swp
11
+
12
+ # Composer
13
+ composer.lock
14
+ vendor
15
+
16
+ # Composer binaries
17
+ bin/phing
18
+ bin/phpunit
19
+ bin/vobject
20
+ bin/generate_vcards
21
+ bin/phpdocmd
22
+ bin/phpunit
23
+ bin/php-cs-fixer
24
+ bin/sabre-cs-fixer
25
+
26
+ # Assuming every .php file in the root is for testing
27
+ /*.php
28
+
29
+ # Other testing stuff
30
+ /tmpdata
31
+ /data
32
+ /public
33
+
34
+ # Build
35
+ build
36
+ build.properties
37
+
38
+ # Docs
39
+ docs/api
40
+ docs/wikidocs
41
+
42
+ # Mac
43
+ .DS_Store
vendor/sabre/dav/.travis.yml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: php
2
+ php:
3
+ - 5.5
4
+ - 5.6
5
+ - 7.0
6
+ - 7.1
7
+
8
+
9
+ env:
10
+ matrix:
11
+ - LOWEST_DEPS="" TEST_DEPS=""
12
+ - LOWEST_DEPS="--prefer-lowest" TEST_DEPS="tests/Sabre/"
13
+
14
+ services:
15
+ - mysql
16
+ - postgresql
17
+
18
+ sudo: false
19
+
20
+ before_script:
21
+ - mysql -e 'create database sabredav_test'
22
+ - psql -c "create database sabredav_test" -U postgres
23
+ - psql -c "create user sabredav with PASSWORD 'sabredav';GRANT ALL PRIVILEGES ON DATABASE sabredav_test TO sabredav" -U postgres
24
+ # - composer self-update
25
+ - composer update --prefer-dist $LOWEST_DEPS
26
+
27
+ # addons:
28
+ # postgresql: "9.5"
29
+
30
+ script:
31
+ - ./bin/phpunit --configuration tests/phpunit.xml.dist $TEST_DEPS
32
+ - ./bin/sabre-cs-fixer fix . --dry-run --diff
33
+
34
+ cache:
35
+ directories:
36
+ - $HOME/.composer/cache
vendor/sabre/dav/CHANGELOG.md ADDED
@@ -0,0 +1,2378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ChangeLog
2
+ =========
3
+
4
+ 3.2.2 (2017-02-14)
5
+ ------------------
6
+
7
+ * #943: Fix CardDAV XML reporting bug, which was affecting several CardDAV
8
+ clients. Bug was introduced in 3.2.1.
9
+ * The zip release ships with [sabre/vobject 4.1.2][vobj],
10
+ [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
11
+ [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].
12
+
13
+
14
+ 3.2.1 (2017-01-28)
15
+ ------------------
16
+
17
+ * #877: Fix for syncing large calendars when using the Sqlite PDO backend.
18
+ (@theseer).
19
+ * #889 Added support for filtering vCard properties in the addressbook-query
20
+ REPORT (@DeepDiver1975).
21
+ * The zip release ships with [sabre/vobject 4.1.2][vobj],
22
+ [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
23
+ [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].
24
+
25
+
26
+ 3.2.0 (2016-06-27)
27
+ ------------------
28
+
29
+ * The default ACL rules allow an unauthenticated user to read information
30
+ about nodes that don't have their own ACL defined. This was a security
31
+ problem.
32
+ * The zip release ships with [sabre/vobject 4.1.0][vobj],
33
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
34
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
35
+
36
+
37
+ 3.2.0-beta1 (2016-05-20)
38
+ ------------------------
39
+
40
+ * #833: Calendars throw exceptions when the sharing plugin is not enabled.
41
+ * #834: Return vCards exactly as they were stored if we don't need to convert
42
+ in between versions.
43
+ * The zip release ships with [sabre/vobject 4.1.0][vobj],
44
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
45
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
46
+
47
+
48
+ 3.2.0-alpha1 (2016-05-09)
49
+ -------------------------
50
+
51
+ * Database changes for CalDAV. If you are using the CalDAV PDO backends, you
52
+ must migrate. Run `./bin/migrateto32.php` for more info.
53
+ * Support for WebDAV Resource Sharing, an upcoming standard.
54
+ * Added support for sharing in the CalDAV PDO backend! Users can now invite
55
+ others to their calendar and give them read/read-write access!
56
+ * #397: Support for PSR-3. You can now log exceptions with your favourite
57
+ psr3-compatible logging tool.
58
+ * #825: Actual proper, tested support for PostgreSQL. We require version 9.5.
59
+ * Removed database migration script for sabre/dav 1.7. To update from that
60
+ version you now first need to update to sabre/dav 3.1.
61
+ * Removed deprecated function: `Sabre\DAV\Auth\Plugin::getCurrentUser()`.
62
+ * #774: Fixes for getting free disk space on Windows.
63
+ * #803: Major changes in the sharing API. If you were using an old sabre/dav
64
+ sharing api, head to the website for more detailed migration notes.
65
+ * #657: Support for optional auth using `{DAV:}unauthorized` and `{DAV:}all`
66
+ privileges. This allows you to assign a privilege to a resource, allowing
67
+ non-authenticated users to access it. For instance, this could allow you
68
+ to create a public read-only collection.
69
+ * #812 #814: ICS/VCF exporter now includes a more useful filename in its
70
+ `Content-Disposition` header. (@Xenopathic).
71
+ * #801: BC break: If you were using the `Href` object before, it's behavior
72
+ now changed a bit, and `LocalHref` was added to replace the old, default
73
+ behavior of `Href`. See the migration doc for more info.
74
+ * Removed `Sabre\DAVACL\Plugin::$allowAccessToNodesWithoutACL` setting.
75
+ Instead, you can provide a set of default ACL rules with
76
+ `Sabre\DAVACL\Plugin::setDefaultAcl()`.
77
+ * Introduced `Sabre\DAVACL\ACLTrait` which contains a default implementation
78
+ of `Sabre\DAV\IACL` with some sane defaults. We're using this trait all over
79
+ the place now, reducing the amount of boilerplate.
80
+ * Plugins can now control the "Supported Privilege Set".
81
+ * Added Sharing, ICSExport and VCFExport plugins to `groupwareserver.php`
82
+ example.
83
+ * The `{DAV:}all` privilege is now no longer abstract, so it can be assigned
84
+ directly. We're using the `{DAV:}all` privilege now in a lot of cases where
85
+ we before assigned both `{DAV:}read` and `{DAV:}write`.
86
+ * Resources that are not collections no longer support the `{DAV:}bind` and
87
+ `{DAV:}unbind` privileges.
88
+ * Corrected the CalDAV-scheduling related privileges.
89
+ * Doing an `UNLOCK` no longer requires the `{DAV:}write-content` privilege.
90
+ * Added a new `getPrincipalByUri` plugin event. Allowing plugins to request
91
+ quickly where a principal lives on a server.
92
+ * Renamed `phpunit.xml` to `phpunit.xml.dist` to make local modifications easy.
93
+ * Functionality from `IShareableCalendar` is merged into `ISharedCalendar`.
94
+ * #751: Fixed XML responses from failing `MKCOL` requests.
95
+ * #600: Support for `principal-match` ACL `REPORT`.
96
+ * #599: Support for `acl-principal-prop-set` ACL `REPORT`.
97
+ * #798: Added an index on `firstoccurence` field in MySQL CalDAV backend. This
98
+ should speed up common calendar-query requests.
99
+ * #759: DAV\Client is now able to actually correctly resolve relative urls.
100
+ * #671: We are no longer checking the `read-free-busy` privilege on individual
101
+ calendars during freebusy operations in the scheduling plugin. Instead, we
102
+ check the `schedule-query-freebusy` privilege on the target users' inbox,
103
+ which validates access for the entire account, per the spec.
104
+ * The zip release ships with [sabre/vobject 4.1.0][vobj],
105
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
106
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
107
+
108
+
109
+ 3.1.5 (????-??-??)
110
+ ------------------
111
+
112
+ * Fixed: Creating a new calendar on some MySQL configurations caused an error.
113
+ * #889 Added support for filtering vCard properties in the addressbook-query
114
+ REPORT (@DeepDiver1975).
115
+
116
+
117
+
118
+ 3.1.4 (2016-05-28)
119
+ ------------------
120
+
121
+ * #834: Backport from `master`: Return vCards exactly as they were stored if
122
+ we don't need to convert in between versions. This should speed up many
123
+ large addressbook syncs sometimes up to 50%.
124
+ * The zip release ships with [sabre/vobject 4.1.0][vobj],
125
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
126
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].
127
+
128
+
129
+ 3.1.3 (2016-04-06)
130
+ ------------------
131
+
132
+ * Set minimum libxml version to 2.7.0 in `composer.json`.
133
+ * #805: It wasn't possible to create calendars that hold events, journals and
134
+ todos using MySQL, because the `components` column was 1 byte too small.
135
+ * The zip release ships with [sabre/vobject 4.1.0][vobj],
136
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
137
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
138
+
139
+
140
+ 3.1.2 (2016-03-12)
141
+ ------------------
142
+
143
+ * #784: Sync logs for address books were not correctly cleaned up after
144
+ deleting them.
145
+ * #787: Cannot use non-seekable stream-wrappers with range requests.
146
+ * Faster XML parsing and generating due to sabre/xml update.
147
+ * #793: The Sqlite schema is now more strict and more similar to the MySQL
148
+ schema. This solves a problem within Baikal.
149
+ * The zip release ships with [sabre/vobject 4.0.3][vobj],
150
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
151
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
152
+
153
+
154
+ 3.1.1 (2016-01-25)
155
+ ------------------
156
+
157
+ * #755: The brower plugin and some operations would break when scheduling and
158
+ delegation would both be enabled.
159
+ * #757: A bunch of unittest improvements (@jakobsack).
160
+ * The zip release ships with [sabre/vobject 4.0.2][vobj],
161
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
162
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
163
+
164
+
165
+ 3.1.0 (2016-01-06)
166
+ ------------------
167
+
168
+ * Better error message when the browser plugin is not enabled.
169
+ * Added a super minimal server example.
170
+ * #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
171
+ use emoji in some tables where you couldn't before.
172
+ * #710: Provide an Auth backend that acts as a helper for people implementing
173
+ OAuth2 Bearer token. (@fkooman).
174
+ * #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
175
+ * #727: Added another workaround to make CalDAV work for Windows 10 clients.
176
+ * #742: Fixes to make sure that vobject 4 is correctly supported.
177
+ * #726: Better error reporting in `Client::propPatch`. We're now throwing
178
+ exceptions.
179
+ * #608: When a HTTP error is triggered during `Client:propFind`, we're now
180
+ throwing `Sabre\HTTP\ClientHttpException` instead of `Sabre\DAV\Exception`.
181
+ This new exception contains a LOT more information about the problem.
182
+ * #721: Events are now handled in the correct order for `COPY` requests.
183
+ Before this subtle bugs could appear that could cause data-loss.
184
+ * #747: Now throwing exceptions and setting the HTTP status to 500 in subtle
185
+ cases where no other plugin set a correct HTTP status.
186
+ * #686: Corrected PDO principal backend's findByURI for email addresses that
187
+ don't match the exact capitalization.
188
+ * #512: The client now has it's own `User-Agent`.
189
+ * #720: Some browser improvements.
190
+ * The zip release ships with [sabre/vobject 4.0.1][vobj],
191
+ [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
192
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
193
+
194
+
195
+ 3.1.0-alpha2 (2015-09-05)
196
+ -------------------------
197
+
198
+ * Massive calendars and addressbooks should see a big drop in peak memory
199
+ usage.
200
+ * Fixed a privilege bug in the availability system.
201
+ * #697: Added a "tableName" member to the PropertyStorage PDO backend. (@Frzk).
202
+ * #699: PostgreSQL fix for the Locks PDO backend. (@TCKnet)
203
+ * Removed the `simplefsserver.php` example file. It's not simple enough.
204
+ * #703: PropPatch in client is not correctly encoded.
205
+ * #709: Throw exception when running into empty
206
+ `supported-calendar-component-set`.
207
+ * #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
208
+ fixes issues when using sabre/dav as a client.
209
+ * The zip release ships with [sabre/vobject 4.0.0-alpha2][vobj],
210
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
211
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
212
+
213
+
214
+ 3.1.0-alpha1 (2015-07-19)
215
+ -------------------------
216
+
217
+ * Now requires PHP 5.5
218
+ * Upgraded to vobject 4, which is a lot faster.
219
+ * Support for PHP 7.
220
+ * #690: Support for `calendar-availability`, draft 05.
221
+ [reference][calendar-availability].
222
+ * #691: Workaround for broken Windows Phone client.
223
+ * The zip release ships with [sabre/vobject 4.0.0-alpha1][vobj],
224
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
225
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
226
+
227
+
228
+ 3.0.10 (2016-??-??)
229
+ ------------------
230
+
231
+ * #889 Added support for filtering vCard properties in the addressbook-query
232
+ REPORT (@DeepDiver1975).
233
+
234
+
235
+ 3.0.9 (2016-04-06)
236
+ ------------------
237
+
238
+ * Set minimum libxml version to 2.7.0 in `composer.json`.
239
+ * #727: Added another workaround to make CalDAV work for Windows 10 clients.
240
+ * #805: It wasn't possible to create calendars that hold events, journals and
241
+ todos using MySQL, because the `components` column was 1 byte too small.
242
+ * The zip release ships with [sabre/vobject 3.5.1][vobj],
243
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
244
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
245
+
246
+
247
+ 3.0.8 (2016-03-12)
248
+ ------------------
249
+
250
+ * #784: Sync logs for address books were not correctly cleaned up after
251
+ deleting them.
252
+ * #787: Cannot use non-seekable stream-wrappers with range requests.
253
+ * Faster XML parsing and generating due to sabre/xml update.
254
+ * The zip release ships with [sabre/vobject 3.5.0][vobj],
255
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
256
+ [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].
257
+
258
+
259
+ 3.0.7 (2016-01-12)
260
+ ------------------
261
+
262
+ * #752: PHP 7 support for 3.0 branch. (@DeepDiver1975)
263
+ * The zip release ships with [sabre/vobject 3.5.0][vobj],
264
+ [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
265
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
266
+
267
+
268
+ 3.0.6 (2016-01-04)
269
+ ------------------
270
+
271
+ * #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
272
+ use emoji in some tables where you couldn't before.
273
+ * #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
274
+ * #734: Return `418 I'm a Teapot` when generating a multistatus response that
275
+ has resources with no returned properties.
276
+ * #740: Bugs in `migrate20.php` script.
277
+ * The zip release ships with [sabre/vobject 3.4.8][vobj],
278
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
279
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].
280
+
281
+
282
+ 3.0.5 (2015-09-15)
283
+ ------------------
284
+
285
+ * #704: Fixed broken uri encoding in multistatus responses. This affected
286
+ at least CyberDuck, but probably also others.
287
+ * The zip release ships with [sabre/vobject 3.4.7][vobj],
288
+ * The zip release ships with [sabre/vobject 3.4.7][vobj],
289
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
290
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
291
+
292
+
293
+ 3.0.4 (2015-09-06)
294
+ ------------------
295
+
296
+ * #703: PropPatch in client is not correctly encoded.
297
+ * #709: Throw exception when running into empty
298
+ `supported-calendar-component-set`.
299
+ * #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
300
+ fixes issues when using sabre/dav as a client.
301
+ * #705: A `MOVE` request that gets prevented from deleting the source resource
302
+ will still remove the target resource. Now all events are triggered before
303
+ any destructive operations.
304
+ * The zip release ships with [sabre/vobject 3.4.7][vobj],
305
+ [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
306
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].
307
+
308
+
309
+ 3.0.3 (2015-08-06)
310
+ ------------------
311
+
312
+ * #700: Digest Auth fails on `HEAD` requests.
313
+ * Fixed example files to no longer use now-deprecated realm argument.
314
+ * The zip release ships with [sabre/vobject 3.4.6][vobj],
315
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
316
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
317
+
318
+
319
+ 3.0.2 (2015-07-21)
320
+ ------------------
321
+
322
+ * #657: Migration script would break when coming a cross an iCalendar object
323
+ with no UID.
324
+ * #691: Workaround for broken Windows Phone client.
325
+ * Fixed a whole bunch of incorrect php docblocks.
326
+ * The zip release ships with [sabre/vobject 3.4.5][vobj],
327
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
328
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
329
+
330
+
331
+ 3.0.1 (2015-07-02)
332
+ ------------------
333
+
334
+ * #674: Postgres sql file fixes. (@davesouthey)
335
+ * #677: Resources with the name '0' would not get retrieved when using
336
+ `Depth: infinity` in a `PROPFIND` request.
337
+ * #680: Fix 'autoprefixing' of dead `{DAV:}href` properties.
338
+ * #675: NTLM support in DAV\Client. (@k42b3)
339
+ * The zip release ships with [sabre/vobject 3.4.5][vobj],
340
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
341
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].
342
+
343
+
344
+ 3.0.0 (2015-06-02)
345
+ ------------------
346
+
347
+ * No changes since last beta.
348
+ * The zip release ships with [sabre/vobject 3.4.5][vobj],
349
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
350
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
351
+
352
+
353
+ 3.0.0-beta3 (2015-05-29)
354
+ ------------------------
355
+
356
+ * Fixed deserializing href properties with no value.
357
+ * Fixed deserializing `{DAV:}propstat` without a `{DAV:}prop`.
358
+ * #668: More information about vcf-export-plugin in browser plugin.
359
+ * #669: Add export button to browser plugin for address books. (@mgee)
360
+ * #670: multiget report hrefs were not decoded.
361
+ * The zip release ships with [sabre/vobject 3.4.4][vobj],
362
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
363
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
364
+
365
+
366
+ 3.0.0-beta2 (2015-05-27)
367
+ ------------------------
368
+
369
+ * A node's properties should not overwrite properties that were already set.
370
+ * Some uris were not correctly encoded in notifications.
371
+ * The zip release ships with [sabre/vobject 3.4.4][vobj],
372
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
373
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
374
+
375
+
376
+ 3.0.0-beta1 (2015-05-25)
377
+ ------------------------
378
+
379
+ * `migrate22.php` is now called `migrate30.php`.
380
+ * Using php-cs-fixer for automated coding standards enforcement and fixing.
381
+ * #660: principals could break html output.
382
+ * #662: Fixed several bugs in the `share` request parser.
383
+ * #665: Fix a bug in serialization of complex properties in the proppatch
384
+ request in the client.
385
+ * #666: expand-property report did not correctly prepend the base uri when
386
+ generating uris, this caused delegation to break.
387
+ * #659: Don't throw errors when when etag-related checks are done on
388
+ collections.
389
+ * Fully supporting the updated `Prefer` header syntax, as defined in
390
+ [rfc7240][rfc7240].
391
+ * The zip release ships with [sabre/vobject 3.4.3][vobj],
392
+ [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
393
+ [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].
394
+
395
+
396
+ 3.0.0-alpha1 (2015-05-19)
397
+ -------------------------
398
+
399
+ * It's now possible to get all property information from files using the
400
+ browser plugin.
401
+ * Browser plugin will now show a 'calendar export' button when the
402
+ ics-export plugin is enabled.
403
+ * Some nodes that by default showed the current time as their last
404
+ modification time, now no longer has a last modification time.
405
+ * CardDAV namespace was missing from default namespaceMap.
406
+ * #646: Properties can now control their own HTML output in the browser plugin.
407
+ * #646: Nicer HTML output for the `{DAV:}acl` property.
408
+ * Browser plugin no longer shows a few properties that take up a lot of space,
409
+ but are likely not really interesting for most users.
410
+ * #654: Added a collection, `Sabre\DAVACL\FS\HomeCollection` for automatically
411
+ creating a private home collection per-user.
412
+ * Changed all MySQL columns from `VARCHAR` to `VARBINARY` where possible.
413
+ * Improved older migration scripts a bit to allow easier testing.
414
+ * The zip release ships with [sabre/vobject 3.4.3][vobj],
415
+ [sabre/http 4.0.0-alpha3][http], [sabre/event 2.0.2][evnt],
416
+ [sabre/uri 1.0.1][uri] and [sabre/xml 0.4.3][xml].
417
+
418
+
419
+ 2.2.0-alpha4 (2015-04-13)
420
+ -------------------------
421
+
422
+ * Complete rewrite of the XML system. We now use our own [sabre/xml][xml],
423
+ which has a much smarter XML Reader and Writer.
424
+ * BC Break: It's no longer possible to instantiate the Locks plugin without
425
+ a locks backend. I'm not sure why this ever made sense.
426
+ * Simplified the Locking system and fixed a bug related to if tokens checking
427
+ locks unrelated to the current request.
428
+ * `FSExt` Directory and File no longer do custom property storage. This
429
+ functionality is already covered pretty well by the `PropertyStorage` plugin,
430
+ so please switch.
431
+ * Renamed `Sabre\CardDAV\UserAddressBooks` to `Sabre\CardDAV\AddressBookHome`
432
+ to be more consistent with `CalendarHome` as well as the CardDAV
433
+ specification.
434
+ * `Sabre\DAV\IExtendedCollection` now receives a `Sabre\DAV\MkCol` object as
435
+ its second argument, and no longer receives seperate properties and
436
+ resourcetype arguments.
437
+ * `MKCOL` now integrates better with propertystorage plugins.
438
+ * #623: Remove need of temporary files when working with Range requests.
439
+ (@dratini0)
440
+ * The zip release ships with [sabre/vobject 3.4.2][vobj],
441
+ [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt],
442
+ [sabre/uri 1.0.0][uri] and [sabre/xml 0.4.3][xml].
443
+
444
+
445
+ 2.2.0-alpha3 (2015-02-25)
446
+ -------------------------
447
+
448
+ * Contains all the changes introduced between 2.1.2 and 2.1.3.
449
+ * The zip release ships with [sabre/vobject 3.4.2][vobj],
450
+ [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt] and
451
+ [sabre/uri 1.0.0][uri].
452
+
453
+
454
+ 2.2.0-alpha2 (2015-01-09)
455
+ -------------------------
456
+
457
+ * Renamed `Sabre\DAV\Auth\Backend\BackendInterface::requireAuth` to
458
+ `challenge`, which is a more correct and better sounding name.
459
+ * The zip release ships with [sabre/vobject 3.3.5][vobj],
460
+ [sabre/http 3.0.4][http], [sabre/event 2.0.1][evnt].
461
+
462
+
463
+ 2.2.0-alpha1 (2014-12-10)
464
+ -------------------------
465
+
466
+ * The browser plugin now has a new page with information about your sabredav
467
+ server, and shows information about every plugin that's loaded in the
468
+ system.
469
+ * #191: The Authentication system can now support multiple authentication
470
+ backends.
471
+ * Removed: all `$tableName` arguments from every PDO backend. This was already
472
+ deprecated, but has now been fully removed. All of these have been replaced
473
+ with public properties.
474
+ * Deleted several classes that were already deprecated much earlier:
475
+ * `Sabre\CalDAV\CalendarRootNode`
476
+ * `Sabre\CalDAV\UserCalendars`
477
+ * `Sabre\DAV\Exception\FileNotFound`
478
+ * `Sabre\DAV\Locks\Backend\FS`
479
+ * `Sabre\DAV\PartialUpdate\IFile`
480
+ * `Sabre\DAV\URLUtil`
481
+ * Removed: `Sabre\DAV\Client::addTrustedCertificates` and
482
+ `Sabre\DAV\Client::setVerifyPeer`.
483
+ * Removed: `Sabre\DAV\Plugin::getPlugin()` can now no longer return plugins
484
+ based on its class name.
485
+ * Removed: `Sabre\DAVACL\Plugin::getPrincipalByEmail()`.
486
+ * #560: GuessContentType plugin will now set content-type to
487
+ `application/octet-stream` if a better content-type could not be determined.
488
+ * #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to
489
+ specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`.
490
+ * #582: Authentication backend interface changed to be stateless. If you
491
+ implemented your own authentication backend, make sure you upgrade your class
492
+ to the latest API!
493
+ * #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use
494
+ `Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead.
495
+ * #193: Fix `Sabre\DAV\FSExt\Directory::getQuotaInfo()` on windows.
496
+
497
+
498
+ 2.1.11 (2016-10-06)
499
+ -------------------
500
+
501
+ * #805: It wasn't possible to create calendars that hold events, journals and
502
+ todos using MySQL, because the `components` column was 1 byte too small.
503
+ * The zip release ships with [sabre/vobject 3.5.3][vobj],
504
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
505
+
506
+
507
+ 2.1.10 (2016-03-10)
508
+ -------------------
509
+
510
+ * #784: Sync logs for address books were not correctly cleaned up after
511
+ deleting them.
512
+ * The zip release ships with [sabre/vobject 3.5.0][vobj],
513
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
514
+
515
+
516
+ 2.1.9 (2016-01-25)
517
+ ------------------
518
+
519
+ * #674: PHP7 support (@DeepDiver1975).
520
+ * The zip release ships with [sabre/vobject 3.5.0][vobj],
521
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
522
+
523
+
524
+ 2.1.8 (2016-01-04)
525
+ ------------------
526
+
527
+ * #729: Fixed a caching problem in the Tree object.
528
+ * #740: Bugs in `migrate20.php` script.
529
+ * The zip release ships with [sabre/vobject 3.4.8][vobj],
530
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
531
+
532
+
533
+ 2.1.7 (2015-09-05)
534
+ ------------------
535
+
536
+ * #705: A `MOVE` request that gets prevented from deleting the source resource
537
+ will still remove the target resource. Now all events are triggered before
538
+ any destructive operations.
539
+ * The zip release ships with [sabre/vobject 3.4.7][vobj],
540
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
541
+
542
+
543
+ 2.1.6 (2015-07-21)
544
+ ------------------
545
+
546
+ * #657: Migration script would break when coming a cross an iCalendar object
547
+ with no UID.
548
+ * #691: Workaround for broken Windows Phone client.
549
+ * The zip release ships with [sabre/vobject 3.4.5][vobj],
550
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
551
+
552
+
553
+ 2.1.5 (2015-07-11)
554
+ ------------------
555
+
556
+ * #677: Resources with the name '0' would not get retrieved when using
557
+ `Depth: infinity` in a `PROPFIND` request.
558
+ * The zip release ships with [sabre/vobject 3.4.5][vobj],
559
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
560
+
561
+
562
+ 2.1.4 (2015-05-25)
563
+ ------------------
564
+
565
+ * #651: Double-encoded path in the browser plugin. Should fix a few broken
566
+ links in some setups.
567
+ * #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
568
+ * #658: Updating `schedule-calendar-default-URL` does not work well, so we're
569
+ disabling it until there's a better fix.
570
+ * The zip release ships with [sabre/vobject 3.4.3][vobj],
571
+ [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].
572
+
573
+
574
+ 2.1.3 (2015-02-25)
575
+ ------------------
576
+
577
+ * #586: `SCHEDULE-STATUS` should not contain a reason-phrase.
578
+ * #539: Fixed a bug related to scheduling in shared calendars.
579
+ * #595: Support for calendar-timezone in iCalendar exports.
580
+ * #581: findByUri would send empty prefixes to the principal backend (@soydeedo)
581
+ * #611: Escaping a bit more HTML output in the browser plugin. (@LukasReschke)
582
+ * #610: Don't allow discovery of arbitrary files using `..` in the browser
583
+ plugin (@LukasReschke).
584
+ * Browser plugin now shows quota properties.
585
+ * #612: PropertyStorage didn't delete properties from nodes when a node's
586
+ parents get deleted.
587
+ * #581: Fixed problems related to finding attendee information during
588
+ scheduling.
589
+ * The zip release ships with [sabre/vobject 3.4.2][vobj],
590
+ [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].
591
+
592
+
593
+ 2.1.2 (2014-12-10)
594
+ ------------------
595
+
596
+ * #566: Another issue related to the migration script, which would cause
597
+ scheduling to not work well for events that were already added before the
598
+ migration.
599
+ * #567: Doing freebusy requests on accounts that had 0 calendars would throw
600
+ a `E_NOTICE`.
601
+ * #572: `HEAD` requests trigger a PHP warning.
602
+ * #579: Browser plugin can throw exception for a few resourcetypes that didn't
603
+ have an icon defined.
604
+ * The zip release ships with [sabre/vobject 3.3.4][vobj],
605
+ [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].
606
+
607
+
608
+ 2.1.1 (2014-11-22)
609
+ ------------------
610
+
611
+ * #561: IMip Plugin didn't strip mailto: from email addresses.
612
+ * #566: Migration process had 2 problems related to adding the `uid` field
613
+ to the `calendarobjects` table.
614
+ * The zip release ships with [sabre/vobject 3.3.4][vobj],
615
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
616
+
617
+
618
+ 2.1.0 (2014-11-19)
619
+ ------------------
620
+
621
+ * #541: CalDAV PDO backend didn't respect overridden PDO table names.
622
+ * #550: Scheduling invites are no longer delivered into shared calendars.
623
+ * #554: `calendar-multiget` `REPORT` did not work on inbox items.
624
+ * #555: The `calendar-timezone` property is now respected for floating times
625
+ and all-day events in the `calendar-query`, `calendar-multiget` and
626
+ `free-busy-query` REPORTs.
627
+ * #555: The `calendar-timezone` property is also respected for scheduling
628
+ free-busy requests.
629
+ * #547: CalDAV system too aggressively 'corrects' incoming iCalendar data, and
630
+ as a result doesn't return an etag for common cases.
631
+ * The zip release ships with [sabre/vobject 3.3.4][vobj],
632
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
633
+
634
+
635
+ 2.1.0-alpha2 (2014-10-23)
636
+ -------------------------
637
+
638
+ * Added: calendar-user-address-set to default principal search properties
639
+ list. This should fix iOS attendee autocomplete support.
640
+ * Changed: Moved all 'notifications' functionality from `Sabre\CalDAV\Plugin`
641
+ to a new plugin: `Sabre\CalDAV\Notifications\Plugin`. If you want to use
642
+ notifications-related functionality, just add this plugin.
643
+ * Changed: Accessing the caldav inbox, outbox or notification collection no
644
+ longer triggers getCalendarsForUser() on backends.
645
+ * #533: New invites are no longer delivered to taks-only calendars.
646
+ * #538: Added `calendarObjectChange` event.
647
+ * Scheduling speedups.
648
+ * #539: added `afterResponse` event. (@joserobleda)
649
+ * Deprecated: All the "tableName" constructor arguments for all the PDO
650
+ backends are now deprecated. They still work, but will be removed in the
651
+ next major sabredav version. Every argument that is now deprecated can now
652
+ be accessed as a public property on the respective backends.
653
+ * #529: Added getCalendarObjectByUID to PDO backend, speeding up scheduling
654
+ operations on large calendars.
655
+ * The zip release ships with [sabre/vobject 3.3.3][vobj],
656
+ [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].
657
+
658
+
659
+ 2.1.0-alpha1 (2014-09-23)
660
+ -------------------------
661
+
662
+ * Added: Support for [rfc6638][rfc6638], also known as CalDAV Scheduling.
663
+ * Added: Automatically converting between vCard 3, 4 and jCard using the
664
+ `Accept:` header, in CardDAV reports, and automatically converting from
665
+ jCard to vCard upon `PUT`. It's important to note that your backends _may_
666
+ now receive both vCard 3.0 and 4.0.
667
+ * Added: #444. Collections can now opt-in to support high-speed `MOVE`.
668
+ * Changed: PropertyStorage backends now have a `move` method.
669
+ * Added: `beforeMove`, and `afterMove` events.
670
+ * Changed: A few database changes for the CalDAV PDO backend. Make sure you
671
+ run `bin/migrate21.php` to upgrade your database schema.
672
+ * Changed: CalDAV backends have a new method: `getCalendarObjectByUID`. This
673
+ method MUST be implemented by all backends, but the `AbstractBackend` has a
674
+ simple default implementation for this.
675
+ * Changed: `Sabre\CalDAV\UserCalendars` has been renamed to
676
+ `Sabre\CalDAV\CalendarHome`.
677
+ * Changed: `Sabre\CalDAV\CalendarRootNode` has been renamed to
678
+ `Sabre\CalDAV\CalendarRoot`.
679
+ * Changed: The IMipHandler has been completely removed. With CalDAV scheduling
680
+ support, it is no longer needed. It's functionality has been replaced by
681
+ `Sabre\CalDAV\Schedule\IMipPlugin`, which can now send emails for clients
682
+ other than iCal.
683
+ * Removed: `Sabre\DAV\ObjectTree` and `Sabre\DAV\Tree\FileSystem`. All this
684
+ functionality has been merged into `Sabre\DAV\Tree`.
685
+ * Changed: PrincipalBackend now has a findByUri method.
686
+ * Changed: `PrincipalBackend::searchPrincipals` has a new optional `test`
687
+ argument.
688
+ * Added: Support for the `{http://calendarserver.org/ns/}email-address-set`
689
+ property.
690
+ * #460: PropertyStorage must move properties during `MOVE` requests.
691
+ * Changed: Restructured the zip distribution to be a little bit more lean
692
+ and consistent.
693
+ * #524: Full support for the `test="anyof"` attribute in principal-search
694
+ `REPORT`.
695
+ * #472: Always returning lock tokens in the lockdiscovery property.
696
+ * Directory entries in the Browser plugin are sorted by type and name.
697
+ (@aklomp)
698
+ * #486: It's now possible to return additional properties when an 'allprop'
699
+ PROPFIND request is being done. (@aklomp)
700
+ * Changed: Now return HTTP errors when an addressbook-query REPORT is done
701
+ on a uri that's not a vcard. This should help with debugging this common
702
+ mistake.
703
+ * Changed: `PUT` requests with a `Content-Range` header now emit a 400 status
704
+ instead of 501, as per RFC7231.
705
+ * Added: Browser plugin can now display the contents of the
706
+ `{DAV:}supported-privilege-set` property.
707
+ * Added: Now reporting `CALDAV:max-resource-size`, but we're not actively
708
+ restricting it yet.
709
+ * Changed: CalDAV plugin is now responsible for reporting
710
+ `CALDAV:supported-collation-set` and `CALDAV:supported-calendar-data`
711
+ properties.
712
+ * Added: Now reporting `CARDDAV:max-resource-size`, but we're not actively
713
+ restricting it yet.
714
+ * Added: Support for `CARDDAV:supported-collation-set`.
715
+ * Changed: CardDAV plugin is now responsible for reporting
716
+ `CARDDAV:supported-address-data`. This functionality has been removed from
717
+ the CardDAV PDO backend.
718
+ * When a REPORT is not supported, we now emit HTTP error 415, instead of 403.
719
+ * #348: `HEAD` requests now work wherever `GET` also works.
720
+ * Changed: Lower priority for the iMip plugins `schedule` event listener.
721
+ * Added: #523 Custom CalDAV backends can now mark any calendar as read-only.
722
+ * The zip release ships with [sabre/vobject 3.3.3][vobj],
723
+ [sabre/http 3.0.0][http], and [sabre/event 2.0.0][evnt].
724
+
725
+
726
+ 2.0.9 (2015-09-04)
727
+ ------------------
728
+
729
+ * #705: A `MOVE` request that gets prevented from deleting the source resource
730
+ will still remove the target resource. Now all events are triggered before
731
+ any destructive operations.
732
+ * The zip release ships with [sabre/vobject 3.4.6][vobj],
733
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
734
+
735
+
736
+
737
+ 2.0.8 (2015-07-11)
738
+ ------------------
739
+
740
+ * #677: Resources with the name '0' would not get retrieved when using
741
+ `Depth: infinity` in a `PROPFIND` request.
742
+ * The zip release ships with [sabre/vobject 3.3.5][vobj],
743
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
744
+
745
+
746
+ 2.0.7 (2015-05-25)
747
+ ------------------
748
+
749
+ * #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
750
+ * The zip release ships with [sabre/vobject 3.3.4][vobj],
751
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
752
+
753
+
754
+ 2.0.6 (2014-12-10)
755
+ ------------------
756
+
757
+ * Added `Sabre\CalDAV\CalendarRoot` as an alias for
758
+ `Sabre\CalDAV\CalendarRootNode`. The latter is going to be deprecated in 2.1,
759
+ so this makes it slightly easier to write code that works in both branches.
760
+ * #497: Making sure we're initializing the sync-token field with a value after
761
+ migration.
762
+ * The zip release ships with [sabre/vobject 3.3.4][vobj],
763
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
764
+
765
+
766
+ 2.0.5 (2014-10-14)
767
+ ------------------
768
+
769
+ * #514: CalDAV PDO backend didn't work when overriding the 'calendar changes'
770
+ database table name.
771
+ * #515: 304 status code was not being sent when checking preconditions.
772
+ * The zip release ships with [sabre/vobject 3.3.3][vobj],
773
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
774
+
775
+
776
+ 2.0.4 (2014-08-27)
777
+ ------------------
778
+
779
+ * #483: typo in calendars creation for PostgreSQL.
780
+ * #487: Locks are now automatically removed after a node has been deleted.
781
+ * #496: Improve CalDAV and CardDAV sync when there is no webdav-sync support.
782
+ * Added: Automatically mapping internal sync-tokens to getctag.
783
+ * The zip release ships with [sabre/vobject 3.3.1][vobj],
784
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
785
+
786
+
787
+ 2.0.3 (2014-07-14)
788
+ ------------------
789
+
790
+ * #474: Fixed PropertyStorage `pathFilter()`.
791
+ * #476: CSP policy incorrect, causing stylesheets to not load in the browser
792
+ plugin.
793
+ * #475: Href properties in the browser plugin sometimes included a backslash.
794
+ * #478: `TooMuchMatches` exception never worked. This was fixed, and we also
795
+ took this opportunity to rename it to `TooManyMatches`.
796
+ * The zip release ships with [sabre/vobject 3.2.4][vobj],
797
+ [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].
798
+
799
+
800
+ 2.0.2 (2014-06-12)
801
+ ------------------
802
+
803
+ * #470: Fixed compatibility with PHP < 5.4.14.
804
+ * #467: Fixed a problem in `examples/calendarserver.php`.
805
+ * #466: All the postgresql sample files have been updated.
806
+ * Fixed: An error would be thrown if a client did a propfind on a node the
807
+ user didn't have access to.
808
+ * Removed: Old and broken example code from the `examples/` directory.
809
+ * The zip release ships with [sabre/vobject 3.2.3][vobj],
810
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.1][evnt].
811
+
812
+
813
+ 2.0.1 (2014-05-28)
814
+ ------------------
815
+
816
+ * #459: PROPFIND requests on Files with no Depth header would return a fatal
817
+ error.
818
+ * #464: A PROPFIND allprops request should not return properties with status
819
+ 404.
820
+ * The zip release ships with [sabre/vobject 3.2.2][vobj],
821
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
822
+
823
+
824
+ 2.0.0 (2014-05-22)
825
+ ------------------
826
+
827
+ * The zip release ships with [sabre/vobject 3.2.2][vobj],
828
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
829
+ * Fixed: #456: Issue in sqlite migration script.
830
+ * Updated: MySQL database schema optimized by using more efficient column types.
831
+ * Cleaned up browser design.
832
+
833
+
834
+ 2.0.0-beta1 (2014-05-15)
835
+ -------------------------
836
+
837
+ * The zip release ships with [sabre/vobject 3.2.2][vobj],
838
+ [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
839
+ * BC Break: Property updating and fetching got refactored. Read the [migration
840
+ document][mi20] for more information. This allows for creation of a generic
841
+ property storage, and other property-related functionality that was not
842
+ possible before.
843
+ * BC Break: Removed `propertyUpdate`, `beforeGetProperties` and
844
+ `afterGetProperties` events.
845
+ * Fixed: #413: Memory optimizations for the CardDAV PDO backend.
846
+ * Updated: Brand new browser plugin with more debugging features and a design
847
+ that is slightly less painful.
848
+ * Added: Support for the `{DAV:}supported-method-set` property server-wide.
849
+ * Making it easier for implementors to override how the CardDAV addressbook
850
+ home is located.
851
+ * Fixed: Issue #422 Preconditions were not being set on PUT on non-existent
852
+ files. Not really a chance for data-loss, but incorrect nevertheless.
853
+ * Fixed: Issue #428: Etag check with `If:` fails if the target is a collection.
854
+ * Fixed: Issues #430, #431, #433: Locks plugin didn't not properly release
855
+ filesystem based locks.
856
+ * Fixed: #443. Support for creating new calendar subscriptions for OS X 10.9.2
857
+ and up.
858
+ * Removed: `Sabre\DAV\Server::NODE_*` constants.
859
+ * Moved all precondition checking into a central place, instead of having to
860
+ think about it on a per-method basis.
861
+ * jCal transformation for calendar-query REPORT now works again.
862
+ * Switched to PSR-4
863
+ * Fixed: #175. Returning ETag header upon a failed `If-Match` or
864
+ `If-None-Match` check.
865
+ * Removed: `lib/Sabre/autoload.php`. Use `vendor/autoload.php` instead.
866
+ * Removed: all the rfc documentation from the sabre/dav source. This made the
867
+ package needlessly larger.
868
+ * Updated: Issue #439. Lots of updates in PATCH support. The
869
+ Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be
870
+ removed in a future version.
871
+ * Added: `Sabre\DAV\Exception\LengthRequired`.
872
+
873
+ 1.9.0-alpha2 (2014-01-14)
874
+ -------------------------
875
+
876
+ * The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.1, and
877
+ sabre/event 1.0.0.
878
+ * Added: Browser can now inspect any node, if ?sabreaction=browser is appended.
879
+ * Fixed: Issue #178. Support for multiple items in the Timeout header.
880
+ * Fixed: Issue #382. Stricter checking if calendar-query is allowed to run.
881
+ * Added: Depth: Infinity support for PROPFIND request. Thanks Thomas Müller and
882
+ Markus Goetz.
883
+
884
+
885
+ 1.9.0-alpha1 (2013-11-07)
886
+ -------------------------
887
+
888
+ * The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.0alpha5, and
889
+ sabre/event 1.0.0.
890
+ * BC Break: The CardDAV and CalDAV BackendInterface each have a new method:
891
+ getMultipleCards and getMultipleCalendarObjects. The Abstract and PDO backends
892
+ have default implementations, but if you implement that interface directly,
893
+ this method is now required.
894
+ * BC Break: XML property classes now receive an extra argument in their
895
+ unserialize method ($propertyMap). This allows for recursively parsing
896
+ properties, if needed.
897
+ * BC Break: Now using sabre/event for event emitting/subscription. For plugin
898
+ authors this means Server::subscribeEvent is now Server::on, and
899
+ Server::broadcastEvent is now Server::emit.
900
+ * BC Break: Almost all core functionality moved into a CorePlugin.
901
+ * BC Break: Most events triggered by the server got an overhaul.
902
+ * Changed: Sabre\HTTP now moved into a dedicated sabre/http package.
903
+ * Added: Support for WebDAV-sync (rfc6578).
904
+ * Added: Support for caldav-subscriptions, which is an easy way for caldav
905
+ clients to manage a list of subscriptions on the server.
906
+ * Added: Support for emitting and receiving jCal instead of iCalendar for
907
+ CalDAV.
908
+ * Added: BasicCallback authenticaton backend, for creating simple authentication
909
+ systems without having to define any classes.
910
+ * Added: A $transactionType property on the server class. This can be used for
911
+ logging and performance measuring purposes.
912
+ * Fixed: If event handlers modify the request body from a PUT request, an ETag
913
+ is no longer sent back.
914
+ * Added: Sabre\DAV\IMultiGet to optimize requests that retrieve information
915
+ about lists of resources.
916
+ * Added: MultiGet support to default CalDAV and CardDAV backends, speeding up
917
+ the multiget and sync reports quite a bit!
918
+ * Added: ICSExportPlugin can now generate jCal, filter on time-ranges and expand
919
+ recurrences.
920
+ * Fixed: Read-only access to calendars still allows the sharee to modify basic
921
+ calendar properties, such as the displayname and color.
922
+ * Changed: The default supportedPrivilegeSet has changed. Most privileges are no
923
+ longer marked as abstract.
924
+ * Changed: More elegant ACL management for CalendarObject and Card nodes.
925
+ * Added: Browser plugin now marks a carddav directory as type Directory, and a
926
+ shared calendar as 'Shared'.
927
+ * Added: When debugExceptions is turned on, all previous exceptions are also
928
+ traversed.
929
+ * Removed: Got rid of the Version classes for CalDAV, CardDAV, HTTP, and DAVACL.
930
+ Now that there's no separate packages anymore, this makes a bit more sense.
931
+ * Added: Generalized the multistatus response parser a bit more, for better
932
+ re-use.
933
+ * Added: Sabre\DAV\Client now has support for complex properties for PROPPATCH.
934
+ (Issue #299).
935
+ * Added: Sabre\DAV\Client has support for gzip and deflate encoding.
936
+ * Added: Sabre\DAV\Client now has support for sending objects as streams.
937
+ * Added: Deserializer for {DAV:}current-user-privilege-set.
938
+ * Added: Addressbooks or backends can now specify custom acl rules when creating
939
+ cards.
940
+ * Added: The ability for plugins to validate custom tokens in If: headers.
941
+ * Changed: Completely refactored the Lock plugin to deal with the new If: header
942
+ system.
943
+ * Added: Checking preconditions for MOVE, COPY, DELETE and PROPPATCH methods.
944
+ * Added: has() method on DAV\Property\SupportedReportSet.
945
+ * Added: If header now gets checked (with ETag) all the time. Before the dealing
946
+ with the If-header was a responsibility of the Locking plugin.
947
+ * Fixed: Outbox access for delegates.
948
+ * Added: Issue 333: It's now possible to override the calendar-home in the
949
+ CalDAV plugin.
950
+ * Added: A negotiateContentType to HTTP\Request. A convenience method.
951
+ * Fixed: Issue 349: Denying copying or moving a resource into it's own subtree.
952
+ * Fixed: SabreDAV catches every exception again.
953
+ * Added: Issue #358, adding a component=vevent parameter to the content-types
954
+ for calendar objects, if the caldav backend provides this info.
955
+
956
+
957
+ 1.8.12-stable (2015-01-21)
958
+ --------------------------
959
+
960
+ * The zip release ships with sabre/vobject 2.1.7.
961
+ * #568: Support empty usernames and passwords in basic auth.
962
+
963
+
964
+ 1.8.11 (2014-12-10)
965
+ -------------------
966
+
967
+ * The zip release ships with sabre/vobject 2.1.6.
968
+ * Updated: MySQL database schema optimized by using more efficient column types.
969
+ * #516: The DAV client will now only redirect to HTTP and HTTPS urls.
970
+
971
+
972
+ 1.8.10 (2014-05-15)
973
+ -------------------
974
+
975
+ * The zip release ships with sabre/vobject 2.1.4.
976
+ * includes changes from version 1.7.12.
977
+
978
+
979
+ 1.8.9 (2014-02-26)
980
+ ------------------
981
+
982
+ * The zip release ships with sabre/vobject 2.1.3.
983
+ * includes changes from version 1.7.11.
984
+
985
+
986
+ 1.8.8 (2014-02-09)
987
+ ------------------
988
+
989
+ * includes changes from version 1.7.10.
990
+ * The zip release ships with sabre/vobject 2.1.3.
991
+
992
+ 1.8.7 (2013-10-02)
993
+ ------------------
994
+
995
+ * the zip release ships with sabre/vobject 2.1.3.
996
+ * includes changes from version 1.7.9.
997
+
998
+
999
+ 1.8.6 (2013-06-18)
1000
+ ------------------
1001
+
1002
+ * The zip release ships with sabre/vobject 2.1.0.
1003
+ * Includes changes from version 1.7.8.
1004
+
1005
+
1006
+ 1.8.5 (2013-04-11)
1007
+ ------------------
1008
+
1009
+ * The zip release ships with sabre/vobject 2.0.7.
1010
+ * Includes changes from version 1.7.7.
1011
+
1012
+
1013
+ 1.8.4 (2013-04-08)
1014
+ ------------------
1015
+
1016
+ * The zip release ships with sabre/vobject 2.0.7.
1017
+ * Includes changes from version 1.7.6.
1018
+
1019
+
1020
+ 1.8.3 (2013-03-01)
1021
+ ------------------
1022
+
1023
+ * The zip release ships with sabre/vobject 2.0.6.
1024
+ * Includes changes from version 1.7.5.
1025
+ * Fixed: organizer email-address for shared calendars is now prefixed with
1026
+ mailto:, as it should.
1027
+
1028
+
1029
+ 1.8.2 (2013-01-19)
1030
+ ------------------
1031
+
1032
+ * The zip release ships with sabre/vobject 2.0.5.
1033
+ * Includes changes from version 1.7.4.
1034
+
1035
+
1036
+ 1.8.1 (2012-12-01)
1037
+ ------------------
1038
+
1039
+ * The zip release ships with sabre/vobject 2.0.5.
1040
+ * Includes changes from version 1.7.3.
1041
+ * Fixed: Typo in 1.7 migration script caused it to fail.
1042
+
1043
+
1044
+ 1.8.0 (2012-11-08)
1045
+ ------------------
1046
+
1047
+ * The zip release ships with sabre/vobject 2.0.5.
1048
+ * BC Break: Moved the entire codebase to PHP namespaces.
1049
+ * BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks, Principals) now
1050
+ has consistent naming conventions. There's a BackendInterface, and an
1051
+ AbstractBackend class.
1052
+ * BC Break: Changed a bunch of constructor signatures in the CalDAV package, to
1053
+ reduce dependencies on the ACL package.
1054
+ * BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method, so
1055
+ sharees can figure out who is also on a shared calendar.
1056
+ * Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support for
1057
+ principal-property-search on any node.
1058
+ * Added: Simple console script to fire up a fileserver in the current directory
1059
+ using PHP 5.4's built-in webserver.
1060
+ * Added: Sharee's can now also read out the list of invites for a shared
1061
+ calendar.
1062
+ * Added: The Proxy principal classes now both implement an interface, for
1063
+ greater flexibility.
1064
+
1065
+
1066
+ 1.7.13 (2014-07-28)
1067
+ -------------------
1068
+
1069
+ * The zip release ships with sabre/vobject 2.1.4.
1070
+ * Changed: Removed phing and went with a custom build script for now.
1071
+
1072
+
1073
+ 1.7.12 (2014-05-15)
1074
+ -------------------
1075
+
1076
+ * The zip release ships with sabre/vobject 2.1.4.
1077
+ * Updated: Issue #439. Lots of updates in PATCH support. The
1078
+ Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be removed
1079
+ in a future version.
1080
+ * Fixed: Restoring old setting after changing libxml_disable_entity_loader.
1081
+ * Fixed: Issue #422: Preconditions were not being set on PUT on non-existent
1082
+ files. Not really a chance for data-loss, but incorrect nevertheless.
1083
+ * Fixed: Issue #427: Now checking preconditions on DELETE requests.
1084
+ * Fixed: Issue #428: Etag check with If: fails if the target is a collection.
1085
+ * Fixed: Issue #393: PATCH request with missing end-range was handled
1086
+ incorrectly.
1087
+ * Added: Sabre_DAV_Exception_LengthRequired to omit 411 errors.
1088
+
1089
+
1090
+ 1.7.11 (2014-02-26)
1091
+ -------------------
1092
+
1093
+ * The zip release ships with sabre/vobject 2.1.3.
1094
+ * Fixed: Issue #407: large downloads failed.
1095
+ * Fixed: Issue #414: XXE security problem on older PHP versions.
1096
+
1097
+
1098
+ 1.7.10 (2014-02-09)
1099
+ -------------------
1100
+
1101
+ * Fixed: Issue #374: Don't urlescape colon (:) when it's not required.
1102
+ * Fixed: Potential security vulnerability in the http client.
1103
+
1104
+
1105
+ 1.7.9 (2013-10-02)
1106
+ ------------------
1107
+
1108
+ * The zip release ships with sabre/vobject 2.1.3.
1109
+ * Fixed: Issue #365. Incorrect output when principal urls have spaces in them.
1110
+ * Added: Issue #367: Automatically adding a UID to vcards that don't have them.
1111
+
1112
+
1113
+ 1.7.8 (2013-06-17)
1114
+ ------------------
1115
+
1116
+ * The zip release ships with sabre/vobject 2.1.0.
1117
+ * Changed: Sabre\DAV\Client::verifyPeer is now a protected property (instead of
1118
+ private).
1119
+ * Fixed: Text was incorrectly escaped in the Href and HrefList properties,
1120
+ disallowing urls with ampersands (&) in them.
1121
+ * Added: deserializer for Sabre\DAVACL\Property\CurrentUserPrivilegeSet.
1122
+ * Fixed: Issue 335: Client only deserializes properties with status 200.
1123
+ * Fixed: Issue 341: Escaping xml in 423 Locked error responses.
1124
+ * Added: Issue 339: beforeGetPropertiesForPath event.
1125
+
1126
+
1127
+ 1.7.7 (2013-04-11)
1128
+ ------------------
1129
+
1130
+ * The zip release ships with sabre/vobject 2.0.7.
1131
+ * Fixed: Assets in the browser plugins were not being served on windows
1132
+ machines.
1133
+
1134
+
1135
+ 1.7.6 (2013-04-08)
1136
+ ------------------
1137
+
1138
+ * The zip release ships with sabre/vobject 2.0.7.
1139
+ * Fixed: vcardurl in database schema can now hold 255 characters instead of 80
1140
+ (which is often way to small).
1141
+ * Fixed: The browser plugin potentially allowed people to open any arbitrary
1142
+ file on windows servers (CVE-2013-1939).
1143
+
1144
+
1145
+ 1.7.5 (2013-03-01)
1146
+ ------------------
1147
+
1148
+ * The zip release ships with sabre/vobject 2.0.6.
1149
+ * Change: No longer advertising support for 4.0 vcards. iOS and OS X address
1150
+ book don't handle this well, and just advertising 3.0 support seems like the
1151
+ most logical course of action.
1152
+ * Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
1153
+ don't use this..).
1154
+
1155
+
1156
+ 1.7.4 (2013-01-19)
1157
+ ------------------
1158
+
1159
+ * The zip release ships with sabre/vobject 2.0.5.
1160
+ * Changed: To be compatible with MS Office 2011 for Mac, a workaround was
1161
+ removed that was added to support old versions of Windows XP (pre-SP3).
1162
+ Indeed! We needed a crazy workaround to work with one MS product in the past,
1163
+ and we can't keep that workaround to be compatible with another MS product.
1164
+ * Fixed: expand-properties REPORT had incorrect values for the href element.
1165
+ * Fixed: Range requests now work for non-seekable streams. (Thanks Alfred
1166
+ Klomp).
1167
+ * Fixed: Changed serialization of {DAV:}getlastmodified and {DAV:}supportedlock
1168
+ to improve compatibility with MS Office 2011 for Mac.
1169
+ * Changed: reverted the automatic translation of 'DAV:' xml namespaces to
1170
+ 'urn:DAV' when parsing files. Issues were reported with libxml 2.6.32, on a
1171
+ relatively recent debian release, so we'll wait till 2015 to take this one out
1172
+ again.
1173
+ * Added: Sabre_DAV_Exception_ServiceUnavailable, for emitting 503's.
1174
+
1175
+
1176
+ 1.7.3 (2012-12-01)
1177
+ ------------------
1178
+
1179
+ * The zip release ships with sabre/vobject 2.0.5.
1180
+ * Fixed: Removing double slashes from getPropertiesForPath.
1181
+ * Change: Marked a few more properties in the CardDAV as protected, instead of
1182
+ private.
1183
+ * Fixed: SharingPlugin now plays nicer with other plugins with similar
1184
+ functionality.
1185
+ * Fixed: Issue 174. Sending back HTTP/1.0 for requests with this version.
1186
+
1187
+
1188
+ 1.7.2 (2012-11-08)
1189
+ ------------------
1190
+
1191
+ * The zip release ships with sabre/vobject 2.0.5.
1192
+ * Added: ACL plugin advertises support for 'calendarserver-principal-
1193
+ property-search'.
1194
+ * Fixed: [#153] Allowing for relative http principals in iMip requests.
1195
+ * Added: Support for cs:first-name and cs:last-name properties in sharing
1196
+ invites.
1197
+ * Fixed: Made a bunch of properties protected, where they were private before.
1198
+ * Added: Some non-standard properties for sharing to improve compatibility.
1199
+ * Fixed: some bugfixes in postgres sql script.
1200
+ * Fixed: When requesting some properties using PROPFIND, they could show up as
1201
+ both '200 Ok' and '403 Forbidden'.
1202
+ * Fixed: calendar-proxy principals were not checked for deeper principal
1203
+ membership than 1 level.
1204
+ * Fixed: setGroupMemberSet argument now correctly receives relative principal
1205
+ urls, instead of the absolute ones.
1206
+ * Fixed: Server class will filter out any bonus properties if any extra were
1207
+ returned. This means the implementor of the IProperty class can be a bit
1208
+ lazier when implementing. Note: bug numbers after this line refer to Google
1209
+ Code tickets. We're using github now.
1210
+
1211
+
1212
+ 1.7.1 (2012-10-07)
1213
+ ------------------
1214
+
1215
+ * Fixed: include path problem in the migration script.
1216
+
1217
+
1218
+ 1.7.0 (2012-10-06)
1219
+ ------------------
1220
+
1221
+ * BC Break: The calendarobjects database table has a bunch of new fields, and a
1222
+ migration script is required to ensure everything will keep working. Read the
1223
+ wiki for more details.
1224
+ * BC Break: The ICalendar interface now has a new method: calendarQuery.
1225
+ * BC Break: In this version a number of classes have been deleted, that have
1226
+ been previously deprecated. Namely: - Sabre_DAV_Directory (now:
1227
+ Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now:
1228
+ Sabre_DAV_SimpleCollection)
1229
+ * BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument.
1230
+ If you extended this class, you should fix this method. It's only used for
1231
+ informational purposes.
1232
+ * BC Break: The DAV: namespace is no longer converted to urn:DAV. This was a
1233
+ workaround for a bug in older PHP versions (pre-5.3).
1234
+ * Removed: Sabre.includes.php was deprecated, and is now removed.
1235
+ * Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please use
1236
+ Sabre_DAV_Server and check the examples in the examples/ directory.
1237
+ * Changed: The Sabre_VObject library now spawned into it's own project! The
1238
+ VObject library is still included in the SabreDAV zip package.
1239
+ * Added: Experimental interfaces to allow implementation of caldav-sharing. Note
1240
+ that no implementation is provided yet, just the api hooks.
1241
+ * Added: Free-busy reporting compliant with the caldav-scheduling standard. This
1242
+ allows iCal and other clients to fetch other users' free-busy data.
1243
+ * Added: Experimental NotificationSupport interface to add caldav notifications.
1244
+ * Added: VCF Export plugin. If enabled, it can generate an export of an entire
1245
+ addressbook.
1246
+ * Added: Support for PATCH using a SabreDAV format, to live-patch files.
1247
+ * Added: Support for Prefer: return-minimal and Brief: t headers for PROPFIND
1248
+ and PROPPATCH requests.
1249
+ * Changed: Responsibility for dealing with the calendar-query is now moved from
1250
+ the CalDAV plugin to the CalDAV backends. This allows for heavy optimizations.
1251
+ * Changed: The CalDAV PDO backend is now a lot faster for common calendar
1252
+ queries.
1253
+ * Changed: We are now using the composer autoloader.
1254
+ * Changed: The CalDAV backend now all implement an interface.
1255
+ * Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is now the
1256
+ basis of every property class.
1257
+ * Update: Caching results for principal lookups. This should cut down queries
1258
+ and performance for a number of heavy requests.
1259
+ * Update: ObjectTree caches lookups much more aggresively, which will help
1260
+ especially speeding up a bunch of REPORT queries.
1261
+ * Added: Support for the schedule-calendar-transp property.
1262
+ * Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded.
1263
+ * Fixed: Workaround for the SOGO connector, as it doesn't understand receiving
1264
+ "text/x-vcard; charset=utf-8" for a contenttype.
1265
+ * Added: Sabre_DAV_Client now throws more specific exceptions in cases where we
1266
+ already has an exception class.
1267
+ * Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH method
1268
+ to update parts of a file.
1269
+ * Added: Tons of timezone name mappings for Microsoft Exchange.
1270
+ * Added: Support for an 'exception' event in the server class.
1271
+ * Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!)
1272
+ * Fixed: Rejecting calendar objects if they are not in the
1273
+ supported-calendar-component list. (thanks Armin!)
1274
+ * Fixed: Issue 219: serialize() now reorders correctly.
1275
+ * Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes if there is
1276
+ whitespace in $dom.
1277
+ * Fixed: Returning 409 Conflict instead of 500 when an attempt is made to create
1278
+ a file as a child of something that's not a collection.
1279
+ * Fixed: Issue 237: xml-encoding values in SabreDAV error responses.
1280
+ * Fixed: Returning 403, instead of 501 when an unknown REPORT is requested.
1281
+ * Fixed: Postfixing slash on {DAV:}owner properties.
1282
+ * Fixed: Several embarrassing spelling mistakes in docblocks.
1283
+
1284
+
1285
+ 1.6.10 (2013-06-17)
1286
+ -------------------
1287
+
1288
+ * Fixed: Text was incorrectly escaped in the Href and HrefList properties,
1289
+ disallowing urls with ampersands (&) in them.
1290
+ * Fixed: Issue 341: Escaping xml in 423 Locked error responses.
1291
+
1292
+
1293
+ 1.6.9 (2013-04-11)
1294
+ ------------------
1295
+
1296
+ * Fixed: Assets in the browser plugins were not being served on windows
1297
+ machines.
1298
+
1299
+
1300
+ 1.6.8 (2013-04-08)
1301
+ ------------------
1302
+
1303
+ * Fixed: vcardurl in database schema can now hold 255 characters instead of 80
1304
+ (which is often way to small).
1305
+ * Fixed: The browser plugin potentially allowed people to open any arbitrary
1306
+ file on windows servers. (CVE-2013-1939).
1307
+
1308
+
1309
+ 1.6.7 (2013-03-01)
1310
+ ------------------
1311
+
1312
+ * Change: No longer advertising support for 4.0 vcards. iOS and OS X address
1313
+ book don't handle this well, and just advertising 3.0 support seems like the
1314
+ most logical course of action.
1315
+ * Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
1316
+ don't use this..).
1317
+
1318
+
1319
+ 1.6.6 (2013-01-19)
1320
+ ------------------
1321
+
1322
+ * Fixed: Backported a fix for broken XML serialization in error responses.
1323
+ (Thanks @DeepDiver1975!)
1324
+
1325
+
1326
+ 1.6.5 (2012-10-04)
1327
+ ------------------
1328
+
1329
+ * Fixed: Workaround for line-ending bug OS X 10.8 addressbook has.
1330
+ * Added: Ability to allow users to set SSL certificates for the Client class.
1331
+ (Thanks schiesbn!).
1332
+ * Fixed: Directory indexes with lots of nodes should be a lot faster.
1333
+ * Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with
1334
+ Sabre_DAV_Client, and no valid properties are returned.
1335
+ * Fixed: Issue with filtering on alarms in tasks.
1336
+
1337
+
1338
+ 1.6.4 (2012-08-02)
1339
+ ------------------
1340
+
1341
+ * Fixed: Issue 220: Calendar-query filters may fail when filtering on alarms, if
1342
+ an overridden event has it's alarm removed.
1343
+ * Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler.
1344
+ * Fixed: Issue 222: beforeWriteContent shouldn't be called for lock requests.
1345
+ * Fixed: Problem with POST requests to the outbox if mailto: was not lower
1346
+ cased.
1347
+ * Fixed: Yearly recurrence rule expansion on leap-days no behaves correctly.
1348
+ * Fixed: Correctly checking if recurring, all-day events with no dtstart fall in
1349
+ a timerange if the start of the time-range exceeds the start of the instance
1350
+ of an event, but not the end.
1351
+ * Fixed: All-day recurring events wouldn't match if an occurence ended exactly
1352
+ on the start of a time-range.
1353
+ * Fixed: HTTP basic auth did not correctly deal with passwords containing colons
1354
+ on some servers.
1355
+ * Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the
1356
+ calendar-query REPORT and free-busy calculations.
1357
+
1358
+
1359
+ 1.6.3 (2012-06-12)
1360
+ ------------------
1361
+
1362
+ * Added: It's now possible to specify in Sabre_DAV_Client which type of
1363
+ authentication is to be used.
1364
+ * Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed.
1365
+ * Fixed: Issue 205: Parsing an iCalendar 0-second date interval.
1366
+ * Fixed: Issue 112: Stronger validation of iCalendar objects. Now making sure
1367
+ every iCalendar object only contains 1 component, and disallowing vcards,
1368
+ forcing every component to have a UID.
1369
+ * Fixed: Basic validation for vcards in the CardDAV plugin.
1370
+ * Fixed: Issue 213: Workaround for an Evolution bug, that prevented it from
1371
+ updating events.
1372
+ * Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in a
1373
+ recurring event could result in an endless loop.
1374
+ * Fixed: All uri fields are now a maximum of 200 characters. The Bynari outlook
1375
+ plugin used much longer strings so this should improve compatibility.
1376
+ * Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See
1377
+ https://bugs.kde.org/show_bug.cgi?id=300047
1378
+ * Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken.
1379
+
1380
+
1381
+ 1.6.2 (2012-04-16)
1382
+ ------------------
1383
+
1384
+ * Fixed: Sabre_VObject_Node::$parent should have been public.
1385
+ * Fixed: Recurrence rules of events are now taken into consideration when doing
1386
+ time-range queries on alarms.
1387
+ * Fixed: Added a workaround for the fact that php's DateInterval cannot parse
1388
+ weeks and days at the same time.
1389
+ * Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's
1390
+ version number from various outputs.
1391
+ * Fixed: DTSTART values would be incorrect when expanding events.
1392
+ * Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY BYDAY
1393
+ recurrences.
1394
+ * Fixed: Issue 203: A problem with overridden events hitting the exact date and
1395
+ time of a subsequent event in the recurrence set.
1396
+ * Fixed: There was a problem with recurrence rules, for example the 5th tuesday
1397
+ of the month, if this day did not exist.
1398
+ * Added: New HTTP status codes from draft-nottingham-http-new-status-04.
1399
+
1400
+
1401
+ 1.6.1 (2012-03-05)
1402
+ ------------------
1403
+
1404
+ * Added: createFile and put() can now return an ETag.
1405
+ * Added: Sending back an ETag on for operations on CardDAV backends. This should
1406
+ help with OS X 10.6 Addressbook compatibility.
1407
+ * Fixed: Fixed a bug where an infinite loop could occur in the recurrence
1408
+ iterator if the recurrence was YEARLY, with a BYMONTH rule, and either BYDAY
1409
+ or BYMONTHDAY match the first day of the month.
1410
+ * Fixed: Events that are excluded using EXDATE are still counted in the COUNT=
1411
+ parameter in the RRULE property.
1412
+ * Added: Support for time-range filters on VALARM components.
1413
+ * Fixed: Correctly filtering all-day events.
1414
+ * Fixed: Sending back correct mimetypes from the browser plugin (thanks
1415
+ Jürgen).
1416
+ * Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency.
1417
+ * Fixed: Calendardata would be destroyed when performing a MOVE request.
1418
+
1419
+
1420
+ 1.6.0 (2012-02-22)
1421
+ ------------------
1422
+
1423
+ * BC Break: Now requires PHP 5.3
1424
+ * BC Break: Any node that implemented Sabre_DAVACL_IACL must now also implement
1425
+ the getSupportedPrivilegeSet method. See website for details.
1426
+ * BC Break: Moved functions from Sabre_CalDAV_XMLUtil to
1427
+ Sabre_VObject_DateTimeParser.
1428
+ * BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods:
1429
+ 'searchPrincipals' and 'updatePrincipal'.
1430
+ * BC Break: Sabre_DAV_ILockable is removed and all related per-node locking
1431
+ functionality.
1432
+ * BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of
1433
+ Sabre_DAV_Exception_NotFound. The former will be removed in a later version.
1434
+ * BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead.
1435
+ * BC Break: Sabre_CalDAV_Server is now deprecated, check out the documentation
1436
+ on how to setup a caldav server with just Sabre_DAV_Server.
1437
+ * BC Break: Default Principals PDO backend now needs a new field in the
1438
+ 'principals' table. See the website for details.
1439
+ * Added: Ability to create new calendars and addressbooks from within the
1440
+ browser plugin.
1441
+ * Added: Browser plugin: icons for various nodes.
1442
+ * Added: Support for FREEBUSY reports!
1443
+ * Added: Support for creating principals with admin-level privileges.
1444
+ * Added: Possibility to let server send out invitation emails on behalf of
1445
+ CalDAV client, using Sabre_CalDAV_Schedule_IMip.
1446
+ * Changed: beforeCreateFile event now passes data argument by reference.
1447
+ * Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now be
1448
+ specified in Sabre_VObject_Property::$classMap.
1449
+ * Added: Ability for plugins to tell the ACL plugin which principal plugins are
1450
+ searchable.
1451
+ * Added: [DAVACL] Per-node overriding of supported privileges. This allows for
1452
+ custom privileges where needed.
1453
+ * Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin, which
1454
+ allows for easy searching for principals, based on their properties.
1455
+ * Added: Sabre_VObject_Component::getComponents() to return a list of only
1456
+ components and not properties.
1457
+ * Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV,
1458
+ DAVACL, HTTP, VObject) as an alternative to the autoloader. This often works
1459
+ much faster.
1460
+ * Added: Support for the 'Me card', which allows Addressbook.app users specify
1461
+ which vcard is their own.
1462
+ * Added: Support for updating principal properties in the DAVACL principal
1463
+ backends.
1464
+ * Changed: Major refactoring in the calendar-query REPORT code. Should make
1465
+ things more flexible and correct.
1466
+ * Changed: The calendar-proxy-[read|write] principals will now only appear in
1467
+ the tree, if they actually exist in the Principal backend. This should reduce
1468
+ some problems people have been having with this.
1469
+ * Changed: Sabre_VObject_Element_* classes are now renamed to
1470
+ Sabre_VObject_Property. Old classes are retained for backwards compatibility,
1471
+ but this will be removed in the future.
1472
+ * Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports based on
1473
+ lists of events.
1474
+ * Added: Sabre_VObject_RecurrenceIterator to find all the dates and times for
1475
+ recurring events.
1476
+ * Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT.
1477
+ * Fixed: Issue 154: Encoding of VObject parameters with no value was incorrect.
1478
+ * Added: Support for {DAV:}acl-restrictions property from RFC3744.
1479
+ * Added: The contentlength for calendar objects can now be supplied by a CalDAV
1480
+ backend, allowing for more optimizations.
1481
+ * Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath.
1482
+ * Fixed: {DAV:}getcontentlength may now be not specified.
1483
+ * Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths from
1484
+ clients. This means that + will now be treated as a literal rather than a
1485
+ space, and this should improve compatibility with the Windows built-in client.
1486
+ * Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402 status
1487
+ codes.
1488
+ * Added: Some mysql unique constraints to example files.
1489
+ * Fixed: Correctly formatting HTTP dates.
1490
+ * Fixed: Issue 94: Sending back Last-Modified header for 304 responses.
1491
+ * Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal,
1492
+ Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar.
1493
+ * Changed: Properties are now also automatically mapped to their appropriate
1494
+ classes, if they are created using the add() or __set() methods.
1495
+ * Changed: Cloning VObject objects now clones the entire tree, rather than just
1496
+ the default shallow copy.
1497
+ * Added: Support for recurrence expansion in the CALDAV:calendar-multiget and
1498
+ CALDAV:calendar-query REPORTS.
1499
+ * Changed: CalDAV PDO backend now sorts calendars based on the internal
1500
+ 'calendarorder' field.
1501
+ * Added: Issue 181: Carddav backends may no optionally not supply the carddata
1502
+ in getCards, if etag and size are specified. This may speed up certain
1503
+ requests.
1504
+ * Added: More arguments to beforeWriteContent and beforeCreateFile (see
1505
+ WritingPlugins wiki document).
1506
+ * Added: Hook for iCalendar validation. This allows us to validate iCalendar
1507
+ objects when they're uploaded. At the moment we're just validating syntax.
1508
+ * Added: VObject now support Windows Timezone names correctly (thanks mrpace2).
1509
+ * Added: If a timezonename could not be detected, we fall back on the default
1510
+ PHP timezone.
1511
+ * Added: Now a Composer package (thanks willdurand).
1512
+ * Fixed: Support for \N as a newline character in the VObject reader.
1513
+ * Added: afterWriteContent, afterCreateFile and afterUnbind events.
1514
+ * Added: Postgresql example files. Not part of the unittests though, so use at
1515
+ your own risk.
1516
+ * Fixed: Issue 182: Removed backticks from sql queries, so it will work with
1517
+ Postgres.
1518
+
1519
+
1520
+ 1.5.9 (2012-04-16)
1521
+ ------------------
1522
+
1523
+ * Fixed: Issue with parsing timezone identifiers that were surrounded by quotes.
1524
+ (Fixes emClient compatibility).
1525
+
1526
+
1527
+ 1.5.8 (2012-02-22)
1528
+ ------------------
1529
+
1530
+ * Fixed: Issue 95: Another timezone parsing issue, this time in calendar-query.
1531
+
1532
+
1533
+ 1.5.7 (2012-02-19)
1534
+ ------------------
1535
+
1536
+ * Fixed: VObject properties are now always encoded before components.
1537
+ * Fixed: Sabre_DAVACL had issues with multiple levels of privilege aggregration.
1538
+ * Changed: Added 'GuessContentType' plugin to fileserver.php example.
1539
+ * Fixed: The Browser plugin will now trigger the correct events when creating
1540
+ files.
1541
+ * Fixed: The ICSExportPlugin now considers ACL's.
1542
+ * Added: Made it optional to supply carddata from an Addressbook backend when
1543
+ requesting getCards. This can make some operations much faster, and could
1544
+ result in much lower memory use.
1545
+ * Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file.
1546
+ * Fixed: Issue 191: beforeUnlock was triggered twice.
1547
+
1548
+
1549
+ 1.5.6 (2012-01-07)
1550
+ ------------------
1551
+
1552
+ * Fixed: Issue 174: VObject could break UTF-8 characters.
1553
+ * Fixed: pear package installation issues.
1554
+
1555
+
1556
+ 1.5.5 (2011-12-16)
1557
+ ------------------
1558
+
1559
+ * Fixed: CalDAV time-range filter workaround for recurring events.
1560
+ * Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple files to
1561
+ be locked at the same time.
1562
+
1563
+
1564
+ 1.5.4 (2011-10-28)
1565
+ ------------------
1566
+
1567
+ * Fixed: GuessContentType plugin now supports mixed case file extensions.
1568
+ * Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME').
1569
+ * Changed: Sending back HTTP 204 after a PUT request on an existing resource
1570
+ instead of HTTP 200. This should fix Evolution CardDAV client compatibility.
1571
+ * Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available.
1572
+ * Added: All VObject elements now have a reference to their parent node.
1573
+
1574
+
1575
+ 1.5.3 (2011-09-28)
1576
+ ------------------
1577
+
1578
+ * Fixed: Sabre_DAV_Collection was missing from the includes file.
1579
+ * Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in
1580
+ uppercase.
1581
+ * Fixed: Issue 153: Support for files with mixed newline styles in
1582
+ Sabre_VObject.
1583
+ * Fixed: Issue 159: Automatically converting any vcard and icalendardata to
1584
+ UTF-8.
1585
+ * Added: Sabre_DAV_SimpleFile class for easy static file creation.
1586
+ * Added: Issue 158: Support for the CARDDAV:supported-address-data property.
1587
+
1588
+
1589
+ 1.5.2 (2011-09-21)
1590
+ ------------------
1591
+
1592
+ * Fixed: carddata and calendardata MySQL fields are now of type 'mediumblob'.
1593
+ 'TEXT' was too small sometimes to hold all the data.
1594
+ * Fixed: {DAV:}supported-report-set is now correctly reporting the reports for
1595
+ IAddressBook.
1596
+ * Added: Sabre_VObject_Property::add() to add duplicate parameters to
1597
+ properties.
1598
+ * Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject
1599
+ interfaces.
1600
+ * Fixed: Issue 140: Not returning 201 Created if an event cancelled the creation
1601
+ of a file.
1602
+ * Fixed: Issue 150: Faster URLUtil::encodePath() implementation.
1603
+ * Fixed: Issue 144: Browser plugin could interfere with
1604
+ TemporaryFileFilterPlugin if it was loaded first.
1605
+ * Added: It's not possible to specify more 'alternate uris' in principal
1606
+ backends.
1607
+
1608
+
1609
+ 1.5.1 (2011-08-24)
1610
+ ------------------
1611
+
1612
+ * Fixed: Issue 137. Hiding action interface in HTML browser for non-collections.
1613
+ * Fixed: addressbook-query is now correctly returned from the
1614
+ {DAV:}supported-report-set property.
1615
+ * Fixed: Issue 142: Bugs in groupwareserver.php example.
1616
+ * Fixed: Issue 139: Rejecting PUT requests with Content-Range.
1617
+
1618
+
1619
+ 1.5.0 (2011-08-12)
1620
+ ------------------
1621
+
1622
+ * Added: CardDAV support.
1623
+ * Added: An experimental WebDAV client.
1624
+ * Added: MIME-Directory grouping support in the VObject library. This is very
1625
+ useful for people attempting to parse vcards.
1626
+ * BC Break: Adding parameters with the VObject libraries now overwrites the
1627
+ previous parameter, rather than just add it. This makes more sense for 99% of
1628
+ the cases.
1629
+ * BC Break: lib/Sabre.autoload.php is now removed in favor of
1630
+ lib/Sabre/autoload.php.
1631
+ * Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in a
1632
+ future version. Use Sabre_DAV_Collection instead.
1633
+ * Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be removed in
1634
+ a future version. Use Sabre_DAV_SimpleCollection instead.
1635
+ * Fixed: Problem with overriding tablenames for the CalDAV backend.
1636
+ * Added: Clark-notation parser to XML utility.
1637
+ * Added: unset() support to VObject components.
1638
+ * Fixed: Refactored CalDAV property fetching to be faster and simpler.
1639
+ * Added: Central string-matcher for CalDAV and CardDAV plugins.
1640
+ * Added: i;unicode-casemap support
1641
+ * Fixed: VObject bug: wouldn't parse parameters if they weren't specified in
1642
+ uppercase.
1643
+ * Fixed: VObject bug: Parameters now behave more like Properties.
1644
+ * Fixed: VObject bug: Parameters with no value are now correctly parsed.
1645
+ * Changed: If calendars don't specify which components they allow, 'all'
1646
+ components are assumed (e.g.: VEVENT, VTODO, VJOURNAL).
1647
+ * Changed: Browser plugin now uses POST variable 'sabreAction' instead of
1648
+ 'action' to reduce the chance of collisions.
1649
+
1650
+
1651
+ 1.4.4 (2011-07-07)
1652
+ ------------------
1653
+
1654
+ * Fixed: Issue 131: Custom CalDAV backends could break in certain cases.
1655
+ * Added: The option to override the default tablename all PDO backends use.
1656
+ (Issue 60).
1657
+ * Fixed: Issue 124: 'File' authentication backend now takes realm into
1658
+ consideration.
1659
+ * Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This allows
1660
+ users to update the {DAV:}group-member-set property.
1661
+ * Added: Helper functions for DateTime-values in Sabre_VObject package.
1662
+ * Added: VObject library can now automatically map iCalendar properties to
1663
+ custom classes.
1664
+
1665
+
1666
+ 1.4.3 (2011-04-25)
1667
+ ------------------
1668
+
1669
+ * Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug.
1670
+ * Fixed: datatype of lastmodified field in mysql.calendars.sql. Please change
1671
+ the DATETIME field to an INT to ensure this field will work correctly.
1672
+ * Change: Sabre_DAV_Property_Principal is now renamed to
1673
+ Sabre_DAVACL_Property_Principal.
1674
+ * Added: API level support for ACL HTTP method.
1675
+ * Fixed: Bug in serializing {DAV:}acl property.
1676
+ * Added: deserializer for {DAV:}resourcetype property.
1677
+ * Added: deserializer for {DAV:}acl property.
1678
+ * Added: deserializer for {DAV:}principal property.
1679
+
1680
+
1681
+ 1.4.2-beta (2011-04-01)
1682
+ -----------------------
1683
+
1684
+ * Added: It's not possible to disable listing of nodes that are denied read
1685
+ access by ACL.
1686
+ * Fixed: Changed a few properties in CalDAV classes from private to protected.
1687
+ * Fixed: Issue 119: Terrible things could happen when relying on guessBaseUri,
1688
+ the server was running on the root of the domain and a user tried to access a
1689
+ file ending in .php. This is a slight BC break.
1690
+ * Fixed: Issue 118: Lock tokens in If headers without a uri should be treated as
1691
+ the request uri, not 'all relevant uri's.
1692
+ * Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in cases
1693
+ where there were similar named locked files in a directory.
1694
+
1695
+
1696
+ 1.4.1-beta (2011-02-26)
1697
+ -----------------------
1698
+
1699
+ * Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks.
1700
+ * Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when running
1701
+ on apache, so a few workarounds were added.
1702
+ * Change: Slightly changed CalDAV Backend API's, to allow for heavy
1703
+ optimizations. This is non-bc breaking.
1704
+
1705
+
1706
+ 1.4.0-beta (2011-02-12)
1707
+ -----------------------
1708
+
1709
+ * Added: Partly RFC3744 ACL support.
1710
+ * Added: Calendar-delegation (caldav-proxy) support.
1711
+ * BC break: In order to fix Issue 99, a new argument had to be added to
1712
+ Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for details.
1713
+ * Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be removed
1714
+ in a later version. Use PDO or the new File class instead.
1715
+ * Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked deprecated, and
1716
+ will be removed in a future version. Please use Sabre_VObject instead.
1717
+ * Removed: All principal-related functionality has been removed from the
1718
+ Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin.
1719
+ * Added: VObject library, for easy vcard/icalendar parsing using a natural
1720
+ interface.
1721
+ * Added: Ability to automatically generate full .ics feeds off calendars. To
1722
+ use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your calendar
1723
+ url.
1724
+ * Added: Plugins can now specify a pluginname, for easy access using
1725
+ Sabre_DAV_Server::getPlugin().
1726
+ * Added: beforeGetProperties event.
1727
+ * Added: updateProperties event.
1728
+ * Added: Principal listings and calendar-access can now be done privately,
1729
+ disallowing users from accessing or modifying other users' data.
1730
+ * Added: You can now pass arrays to the Sabre_DAV_Server constructor. If it's an
1731
+ array with node-objects, a Root collection will automatically be created, and
1732
+ the nodes are used as top-level children.
1733
+ * Added: The principal base uri is now customizable. It used to be hardcoded to
1734
+ 'principals/[user]'.
1735
+ * Added: getSupportedReportSet method in ServerPlugin class. This allows you to
1736
+ easily specify which reports you're implementing.
1737
+ * Added: A '..' link to the HTML browser.
1738
+ * Fixed: Issue 99: Locks on child elements were ignored when their parent nodes
1739
+ were deleted.
1740
+ * Fixed: Issue 90: lockdiscovery property and LOCK response now include a
1741
+ {DAV}lockroot element.
1742
+ * Fixed: Issue 96: support for 'default' collation in CalDAV text-match filters.
1743
+ * Fixed: Issue 102: Ensuring that copy and move with identical source and
1744
+ destination uri's fails.
1745
+ * Fixed: Issue 105: Supporting MKCALENDAR with no body.
1746
+ * Fixed: Issue 109: Small fixes in Sabre_HTTP_Util.
1747
+ * Fixed: Issue 111: Properly catching the ownername in a lock (if it's a string)
1748
+ * Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the root
1749
+ node.
1750
+ * Added: Global way to easily supply new resourcetypes for certain node classes.
1751
+ * Fixed: Issue 59: Allowing the user to override the authentication realm in
1752
+ Sabre_CalDAV_Server.
1753
+ * Update: Issue 97: Looser time-range checking if there's a recurrence rule in
1754
+ an event. This fixes 'missing recurring events'.
1755
+
1756
+
1757
+ 1.3.0 (2010-10-14)
1758
+ ------------------
1759
+
1760
+ * Added: childExists method to Sabre_DAV_ICollection. This is an api break, so
1761
+ if you implement Sabre_DAV_ICollection directly, add the method.
1762
+ * Changed: Almost all HTTP method implementations now take a uri argument,
1763
+ including events. This allows for internal rerouting of certain calls. If you
1764
+ have custom plugins, make sure they use this argument. If they don't, they
1765
+ will likely still work, but it might get in the way of future changes.
1766
+ * Changed: All getETag methods MUST now surround the etag with double-quotes.
1767
+ This was a mistake made in all previous SabreDAV versions. If you don't do
1768
+ this, any If-Match, If-None-Match and If: headers using Etags will work
1769
+ incorrectly. (Issue 85).
1770
+ * Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to easily
1771
+ implement basic authentication.
1772
+ * Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden instead.
1773
+ * Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection instead.
1774
+ * Added: Browser plugin now uses {DAV:}displayname if this property is
1775
+ available.
1776
+ * Added: Cache layer in the ObjectTree.
1777
+ * Added: Tree classes now have a delete and getChildren method.
1778
+ * Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if the
1779
+ date is an exact match.
1780
+ * Fixed: Support for multiple ETags in If-Match and If-None-Match headers.
1781
+ * Fixed: Improved baseUrl handling.
1782
+ * Fixed: Issue 67: Non-seekable stream support in ::put()/::get().
1783
+ * Fixed: Issue 65: Invalid dates are now ignored.
1784
+ * Updated: Refactoring in Sabre_CalDAV to make everything a bit more ledgable.
1785
+ * Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on
1786
+ Windows.
1787
+ * Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to 'file
1788
+ size'-1.
1789
+
1790
+
1791
+ 1.2.5 (2010-08-18)
1792
+ ------------------
1793
+
1794
+ * Fixed: Issue 73: guessBaseUrl fails for some servers.
1795
+ * Fixed: Issue 67: SabreDAV works better with non-seekable streams.
1796
+ * Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if
1797
+ the date is an exact match.
1798
+
1799
+
1800
+ 1.2.4 (2010-07-13)
1801
+ ------------------
1802
+
1803
+ * Fixed: Issue 62: Guessing baseUrl fails when url contains a query-string.
1804
+ * Added: Apache configuration sample for CGI/FastCGI setups.
1805
+ * Fixed: Issue 64: Only returning calendar-data when it was actually requested.
1806
+
1807
+
1808
+ 1.2.3 (2010-06-26)
1809
+ ------------------
1810
+
1811
+ * Fixed: Issue 57: Supporting quotes around etags in If-Match and If-None-Match
1812
+
1813
+
1814
+ 1.2.2 (2010-06-21)
1815
+ ------------------
1816
+
1817
+ * Updated: SabreDAV now attempts to guess the BaseURI if it's not set.
1818
+ * Updated: Better compatibility with BitKinex
1819
+ * Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET
1820
+ requests.
1821
+ * Fixed: Issue with certain encoded paths in Browser Plugin.
1822
+
1823
+
1824
+ 1.2.1 (2010-06-07)
1825
+ ------------------
1826
+
1827
+ * Fixed: Issue 50, patch by Mattijs Hoitink.
1828
+ * Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter.
1829
+ * Fixed: Issue 38, Allowing custom filters to be added to TemporaryFileFilter.
1830
+ * Fixed: Issue 53, ETags in the If: header were always failing. This behaviour
1831
+ is now corrected.
1832
+ * Added: Apache Authentication backend, in case authentication through .htaccess
1833
+ is desired.
1834
+ * Updated: Small improvements to example files.
1835
+
1836
+
1837
+ 1.2.0 (2010-05-24)
1838
+ ------------------
1839
+
1840
+ * Fixed: Browser plugin now displays international characters.
1841
+ * Changed: More properties in CalDAV classes are now protected instead of
1842
+ private.
1843
+
1844
+
1845
+ 1.2.0beta3 (2010-05-14)
1846
+ -----------------------
1847
+
1848
+ * Fixed: Custom properties were not properly sent back for allprops requests.
1849
+ * Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007.
1850
+ * Changed: Removed CalDAV items from includes.php, and added a few missing ones.
1851
+
1852
+
1853
+ 1.2.0beta2 (2010-05-04)
1854
+ -----------------------
1855
+
1856
+ * Fixed: Issue 46: Fatal error for some non-existent nodes.
1857
+ * Updated: some example sql to include email address.
1858
+ * Added: 208 and 508 statuscodes from RFC5842.
1859
+ * Added: Apache2 configuration examples
1860
+
1861
+
1862
+ 1.2.0beta1 (2010-04-28)
1863
+ -----------------------
1864
+
1865
+ * Fixed: redundant namespace declaration in resourcetypes.
1866
+ * Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable
1867
+ interface is used.
1868
+ * Changed: using http://sabredav.org/ns for all custom xml properties.
1869
+ * Added: email address property to principals.
1870
+ * Updated: CalendarObject validation.
1871
+
1872
+
1873
+ 1.2.0alpha4 (2010-04-24)
1874
+ ------------------------
1875
+
1876
+ * Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since,
1877
+ If-Unmodified-Since.
1878
+ * Changed: Brand new build system. Functionality is split up between Sabre,
1879
+ Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to that a new
1880
+ non-pear package will be created with all this functionality combined.
1881
+ * Changed: Autoloader moved to Sabre/autoload.php.
1882
+ * Changed: The Allow: header is now more accurate, with appropriate HTTP methods
1883
+ per uri.
1884
+ * Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few
1885
+ places where Sabre_DAV_Exception_NotImplemented was used.
1886
+
1887
+
1888
+ 1.2.0alpha3 (2010-04-20)
1889
+ ------------------------
1890
+
1891
+ * Update: Complete rewrite of property updating. Now easier to use and atomic.
1892
+ * Fixed: Issue 16, automatically adding trailing / to baseUri.
1893
+ * Added: text/plain is used for .txt files in GuessContentType plugin.
1894
+ * Added: support for principal-property-search and principal-search-property-set
1895
+ reports.
1896
+ * Added: Issue 31: Hiding exception information by default. Can be turned on
1897
+ with the Sabre_DAV_Server::$debugExceptions property.
1898
+
1899
+
1900
+ 1.2.0alpha2 (2010-04-08)
1901
+ ------------------------
1902
+
1903
+ * Added: Calendars are now private and can only be read by the owner.
1904
+ * Fixed: double namespace declaration in multistatus responses.
1905
+ * Added: MySQL database dumps. MySQL is now also supported next to SQLite.
1906
+ * Added: expand-properties REPORT from RFC 3253.
1907
+ * Added: Sabre_DAV_Property_IHref interface for properties exposing urls.
1908
+ * Added: Issue 25: Throwing error on broken Finder behaviour.
1909
+ * Changed: Authentication backend is now aware of current user.
1910
+
1911
+
1912
+ 1.2.0alpha1 (2010-03-31)
1913
+ ------------------------
1914
+
1915
+ * Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
1916
+ characters.
1917
+ * Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes Office
1918
+ 2010 compatibility.
1919
+ * Added: Issue 35: SabreDAV version to header to OPTIONS response to ease
1920
+ debugging.
1921
+ * Fixed: Issue 36: Incorrect variable name, throwing error in some requests.
1922
+ * Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
1923
+ * Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
1924
+ * Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales.
1925
+ * Added: More unittests.
1926
+ * Added: SabreDAV version to all error responses.
1927
+ * Added: URLUtil class for decoding urls.
1928
+ * Changed: Now using pear.sabredav.org pear channel.
1929
+ * Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method.
1930
+
1931
+
1932
+ 1.1.2-alpha (2010-03-18)
1933
+ ------------------------
1934
+
1935
+ * Added: RFC5397 - current-user-principal support.
1936
+ * Fixed: Issue 27: encoding entities in property responses.
1937
+ * Added: naturalselection script now allows the user to specify a 'minimum
1938
+ number of bytes' for deletion. This should reduce load due to less crawling
1939
+ * Added: Full support for the calendar-query report.
1940
+ * Added: More unittests.
1941
+ * Added: Support for complex property deserialization through the static
1942
+ ::unserialize() method.
1943
+ * Added: Support for modifying calendar-component-set
1944
+ * Fixed: Issue 29: Added TIMEOUT_INFINITE constant
1945
+
1946
+
1947
+ 1.1.1-alpha (2010-03-11)
1948
+ ------------------------
1949
+
1950
+ * Added: RFC5689 - Extended MKCOL support.
1951
+ * Fixed: Evolution support for CalDAV.
1952
+ * Fixed: PDO-locks backend was pretty much completely broken. This is 100%
1953
+ unittested now.
1954
+ * Added: support for ctags.
1955
+ * Fixed: Comma's between HTTP methods in 'Allow' method.
1956
+ * Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a
1957
+ datadirectory must always be specified from now on.
1958
+ * Changed: Moved Sabre_DAV_Server::parseProps to
1959
+ Sabre_DAV_XMLUtil::parseProperties.
1960
+ * Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection.
1961
+ * Changed: Sabre_DAV_Exception_PermissionDenied is now
1962
+ Sabre_DAV_Exception_Forbidden.
1963
+ * Changed: Sabre_CalDAV_ICalendarCollection is removed.
1964
+ * Added: Sabre_DAV_IExtendedCollection.
1965
+ * Added: Many more unittests.
1966
+ * Added: support for calendar-timezone property.
1967
+
1968
+
1969
+ 1.1.0-alpha (2010-03-01)
1970
+ ------------------------
1971
+
1972
+ * Note: This version is forked from version 1.0.5, so release dates may be out
1973
+ of order.
1974
+ * Added: CalDAV - RFC 4791
1975
+ * Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for this.
1976
+ * Added: PDO authentication backend.
1977
+ * Added: Example sql for auth, caldav, locks for sqlite.
1978
+ * Added: Sabre_DAV_Browser_GuessContentType plugin
1979
+ * Changed: Authentication plugin refactored, making it possible to implement
1980
+ non-digest authentication.
1981
+ * Fixed: Better error display in browser plugin.
1982
+ * Added: Support for {DAV:}supported-report-set
1983
+ * Added: XML utility class with helper functions for the WebDAV protocol.
1984
+ * Added: Tons of unittests
1985
+ * Added: PrincipalCollection and Principal classes
1986
+ * Added: Sabre_DAV_Server::getProperties for easy property retrieval
1987
+ * Changed: {DAV:}resourceType defaults to 0
1988
+ * Changed: Any non-null resourceType now gets a / appended to the href value.
1989
+ Before this was just for {DAV:}collection's, but this is now also the case for
1990
+ for example {DAV:}principal.
1991
+ * Changed: The Href property class can now optionally create non-relative uri's.
1992
+ * Changed: Sabre_HTTP_Response now returns false if headers are already sent and
1993
+ header-methods are called.
1994
+ * Fixed: Issue 19: HEAD requests on Collections
1995
+ * Fixed: Issue 21: Typo in Sabre_DAV_Property_Response
1996
+ * Fixed: Issue 18: Doesn't work with Evolution Contacts
1997
+
1998
+
1999
+ 1.0.15 (2010-05-28)
2000
+ -------------------
2001
+
2002
+ * Added: Issue 31: Hiding exception information by default. Can be turned on
2003
+ with the Sabre_DAV_Server::$debugExceptions property.
2004
+ * Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also the
2005
+ case in the upcoming 1.2.0, so it will improve future compatibility.
2006
+
2007
+
2008
+ 1.0.14 (2010-04-15)
2009
+ -------------------
2010
+
2011
+ * Fixed: double namespace declaration in multistatus responses.
2012
+
2013
+
2014
+ 1.0.13 (2010-03-30)
2015
+ -------------------
2016
+
2017
+ * Fixed: Issue 40: Last references to basename/dirname
2018
+
2019
+
2020
+ 1.0.12 (2010-03-30)
2021
+ -------------------
2022
+
2023
+ * Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
2024
+ * Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
2025
+ characters.
2026
+ * Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
2027
+ * Fixed: Issue 39: Basename fails on non-utf-8 locales.
2028
+ * Added: More unittests.
2029
+ * Added: SabreDAV version to all error responses.
2030
+ * Added: URLUtil class for decoding urls.
2031
+ * Updated: Now using pear.sabredav.org pear channel.
2032
+
2033
+
2034
+ 1.0.11 (2010-03-23)
2035
+ -------------------
2036
+
2037
+ * Non-public release. This release is identical to 1.0.10, but it is used to
2038
+ test releasing packages to pear.sabredav.org.
2039
+
2040
+
2041
+ 1.0.10 (2010-03-22)
2042
+ -------------------
2043
+
2044
+ * Fixed: Issue 34: Invalid Lock-Token header response.
2045
+ * Added: Issue 35: Adding SabreDAV version to HTTP OPTIONS responses.
2046
+
2047
+
2048
+ 1.0.9 (2010-03-19)
2049
+ ------------------
2050
+
2051
+ * Fixed: Issue 27: Entities not being encoded in PROPFIND responses.
2052
+ * Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant.
2053
+
2054
+
2055
+ 1.0.8 (2010-03-03)
2056
+ ------------------
2057
+
2058
+ * Fixed: Issue 21: typos causing errors
2059
+ * Fixed: Issue 23: Comma's between methods in Allow header.
2060
+ * Added: Sabre_DAV_ICollection interface, to aid in future compatibility.
2061
+ * Added: Sabre_DAV_Exception_Forbidden exception. This will replace
2062
+ Sabre_DAV_Exception_PermissionDenied in the future, and can already be used to
2063
+ ensure future compatibility.
2064
+
2065
+
2066
+ 1.0.7 (2010-02-24)
2067
+ ------------------
2068
+
2069
+ * Fixed: Issue 19 regression for MS Office
2070
+
2071
+
2072
+ 1.0.6 (2010-02-23)
2073
+ ------------------
2074
+
2075
+ * Fixed: Issue 19: HEAD requests on Collections
2076
+
2077
+
2078
+ 1.0.5 (2010-01-22)
2079
+ ------------------
2080
+
2081
+ * Fixed: Fatal error when a malformed url was used for unlocking, in conjuction
2082
+ with Sabre.autoload.php due to a incorrect filename.
2083
+ * Fixed: Improved unittests and build system
2084
+
2085
+
2086
+ 1.0.4 (2010-01-11)
2087
+ ------------------
2088
+
2089
+ * Fixed: needed 2 different releases. One for googlecode and one for pearfarm.
2090
+ This is to retain the old method to install SabreDAV until pearfarm becomes
2091
+ the standard installation method.
2092
+
2093
+
2094
+ 1.0.3 (2010-01-11)
2095
+ ------------------
2096
+
2097
+ * Added: RFC4709 support (davmount)
2098
+ * Added: 6 unittests
2099
+ * Added: naturalselection. A tool to keep cache directories below a specified
2100
+ theshold.
2101
+ * Changed: Now using pearfarm.org channel server.
2102
+
2103
+
2104
+ 1.0.1 (2009-12-22)
2105
+ ------------------
2106
+
2107
+ * Fixed: Issue 15: typos in examples
2108
+ * Fixed: Minor pear installation issues
2109
+
2110
+
2111
+ 1.0.0 (2009-11-02)
2112
+ ------------------
2113
+
2114
+ * Added: SimpleDirectory class. This class allows creating static directory
2115
+ structures with ease.
2116
+ * Changed: Custom complex properties and exceptions now get an instance of
2117
+ Sabre_DAV_Server as their first argument in serialize()
2118
+ * Changed: Href complex property now prepends server's baseUri
2119
+ * Changed: delete before an overwriting copy/move is now handles by server class
2120
+ instead of tree classes
2121
+ * Changed: events must now explicitly return false to stop execution. Before,
2122
+ execution would be stopped by anything loosely evaluating to false.
2123
+ * Changed: the getPropertiesForPath method now takes a different set of
2124
+ arguments, and returns a different response. This allows plugin developers to
2125
+ return statuses for properties other than 200 and 404. The hrefs are now also
2126
+ always calculated relative to the baseUri, and not the uri of the request.
2127
+ * Changed: generatePropFindResponse is renamed to generateMultiStatus, and now
2128
+ takes a list of properties similar to the response of getPropertiesForPath.
2129
+ This was also needed to improve flexibility for plugin development.
2130
+ * Changed: Auth plugins are no longer included. They were not yet stable
2131
+ quality, so they will probably be reintroduced in a later version.
2132
+ * Changed: PROPPATCH also used generateMultiStatus now.
2133
+ * Removed: unknownProperties event. This is replaced by the afterGetProperties
2134
+ event, which should provide more flexibility.
2135
+ * Fixed: Only calling getSize() on IFile instances in httpHead()
2136
+ * Added: beforeBind event. This is invoked upon file or directory creation
2137
+ * Added: beforeWriteContent event, this is invoked by PUT and LOCK on an
2138
+ existing resource.
2139
+ * Added: beforeUnbind event. This is invoked right before deletion of any
2140
+ resource.
2141
+ * Added: afterGetProperties event. This event can be used to make modifications
2142
+ to property responses.
2143
+ * Added: beforeLock and beforeUnlock events.
2144
+ * Added: afterBind event.
2145
+ * Fixed: Copy and Move could fail in the root directory. This is now fixed.
2146
+ * Added: Plugins can now be retrieved by their classname. This is useful for
2147
+ inter-plugin communication.
2148
+ * Added: The Auth backend can now return usernames and user-id's.
2149
+ * Added: The Auth backend got a getUsers method
2150
+ * Added: Sabre_DAV_FSExt_Directory now returns quota info
2151
+
2152
+
2153
+ 0.12.1-beta (2009-09-11)
2154
+ ------------------------
2155
+
2156
+ * Fixed: UNLOCK bug. Unlock didn't work at all
2157
+
2158
+
2159
+ 0.12-beta (2009-09-10)
2160
+ ----------------------
2161
+
2162
+ * Updated: Browser plugin now shows multiple {DAV:}resourcetype values if
2163
+ available.
2164
+ * Added: Experimental PDO backend for Locks Manager
2165
+ * Fixed: Sending Content-Length: 0 for every empty response. This improves NGinx
2166
+ compatibility.
2167
+ * Fixed: Last modification time is reported in UTC timezone. This improves
2168
+ Finder compatibility.
2169
+
2170
+
2171
+ 0.11-beta (2009-08-11)
2172
+ ----------------------
2173
+
2174
+ * Updated: Now in Beta
2175
+ * Updated: Pear package no longer includes docs/ directory. These just contained
2176
+ rfc's, which are publicly available. This reduces the package from ~800k to
2177
+ ~60k
2178
+ * Added: generatePropfindResponse now takes a baseUri argument
2179
+ * Added: ResourceType property can now contain multiple resourcetypes.
2180
+ * Fixed: Issue 13.
2181
+
2182
+
2183
+ 0.10-alpha (2009-08-03)
2184
+ -----------------------
2185
+
2186
+ * Added: Plugin to automatically map GET requests to non-files to PROPFIND
2187
+ (Sabre_DAV_Browser_MapGetToPropFind). This should allow easier debugging of
2188
+ complicated WebDAV setups.
2189
+ * Added: Sabre_DAV_Property_Href class. For future use.
2190
+ * Added: Ability to choose to use auth-int, auth or both for HTTP Digest
2191
+ authentication. (Issue 11)
2192
+ * Changed: Made more methods in Sabre_DAV_Server public.
2193
+ * Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests to
2194
+ non-existent files. (Issue 12)
2195
+ * Added: Central list of defined xml namespace prefixes. This can reduce
2196
+ Bandwidth and legibility for xml bodies with user-defined namespaces.
2197
+ * Added: now a PEAR-compatible package again, thanks to Michael Gauthier
2198
+ * Changed: moved default copy and move logic from ObjectTree to Tree class
2199
+
2200
+ 0.9a-alpha (2009-07-21)
2201
+ ----------------------
2202
+
2203
+ * Fixed: Broken release
2204
+
2205
+ 0.9-alpha (2009-07-21)
2206
+ ----------------------
2207
+
2208
+ * Changed: Major refactoring, removed most of the logic from the Tree objects.
2209
+ The Server class now directly works with the INode, IFile and IDirectory
2210
+ objects. If you created your own Tree objects, this will most likely break in
2211
+ this release.
2212
+ * Changed: Moved all the Locking logic from the Tree and Server classes into a
2213
+ separate plugin.
2214
+ * Changed: TemporaryFileFilter is now a plugin.
2215
+ * Added: Comes with an autoloader script. This can be used instead of the
2216
+ includer script, and is preferred by some people.
2217
+ * Added: AWS Authentication class.
2218
+ * Added: simpleserversetup.py script. This will quickly get a fileserver up and
2219
+ running.
2220
+ * Added: When subscribing to events, it is now possible to supply a priority.
2221
+ This is for example needed to ensure that the Authentication Plugin is used
2222
+ before any other Plugin.
2223
+ * Added: 22 new tests.
2224
+ * Added: Users-manager plugin for .htdigest files. Experimental and subject to
2225
+ change.
2226
+ * Added: RFC 2324 HTTP 418 status code
2227
+ * Fixed: Exclusive locks could in some cases be picked up as shared locks
2228
+ * Fixed: Digest auth for non-apache servers had a bug (still not actually tested
2229
+ this well).
2230
+
2231
+
2232
+ 0.8-alpha (2009-05-30)
2233
+ ----------------------
2234
+
2235
+ * Changed: Renamed all exceptions! This is a compatibility break. Every
2236
+ Exception now follows Sabre_DAV_Exception_FileNotFound convention instead of
2237
+ Sabre_DAV_FileNotFoundException.
2238
+ * Added: Browser plugin now allows uploading and creating directories straight
2239
+ from the browser.
2240
+ * Added: 12 more unittests
2241
+ * Fixed: Locking bug, which became prevalent on Windows Vista.
2242
+ * Fixed: Netdrive support
2243
+ * Fixed: TemporaryFileFilter filtered out too many files. Fixed some of the
2244
+ regexes.
2245
+ * Fixed: Added README and ChangeLog to package
2246
+
2247
+
2248
+ 0.7-alpha (2009-03-29)
2249
+ ----------------------
2250
+
2251
+ * Added: System to return complex properties from PROPFIND.
2252
+ * Added: support for {DAV:}supportedlock.
2253
+ * Added: support for {DAV:}lockdiscovery.
2254
+ * Added: 6 new tests.
2255
+ * Added: New plugin system.
2256
+ * Added: Simple HTML directory plugin, for browser access.
2257
+ * Added: Server class now sends back standard pre-condition error xml bodies.
2258
+ This was new since RFC4918.
2259
+ * Added: Sabre_DAV_Tree_Aggregate, which can 'host' multiple Tree objects into
2260
+ one.
2261
+ * Added: simple basis for HTTP REPORT method. This method is not used yet, but
2262
+ can be used by plugins to add reports.
2263
+ * Changed: ->getSize is only called for files, no longer for collections. r303
2264
+ * Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter
2265
+ * Changed: Sabre_DAV_TemporaryFileFilter is now called
2266
+ Sabre_DAV_Tree_TemporaryFileFilter.
2267
+ * Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server
2268
+ class, and using a public property instead.
2269
+ * Fixed: bug related to parsing proppatch and propfind requests. Didn't show up
2270
+ in most clients, but it needed fixing regardless. (r255)
2271
+ * Fixed: auth-int is now properly supported within HTTP Digest.
2272
+ * Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918 sec
2273
+ 8.2.
2274
+ * Fixed: TemporaryFileFilter now lets through GET's if they actually exist on
2275
+ the backend. (r274)
2276
+ * Fixed: Some methods didn't get passed through in the FilterTree (r283).
2277
+ * Fixed: LockManager is now slightly more complex, Tree classes slightly less.
2278
+ (r287)
2279
+
2280
+
2281
+ 0.6-alpha (2009-02-16)
2282
+ ----------------------
2283
+
2284
+ * Added: Now uses streams for files, instead of strings. This means it won't
2285
+ require to hold entire files in memory, which can be an issue if you're
2286
+ dealing with big files. Note that this breaks compatibility for put() and
2287
+ createFile methods.
2288
+ * Added: HTTP Digest Authentication helper class.
2289
+ * Added: Support for HTTP Range header
2290
+ * Added: Support for ETags within If: headers
2291
+ * Added: The API can now return ETags and override the default Content-Type
2292
+ * Added: starting with basic framework for unittesting, using PHPUnit.
2293
+ * Added: 49 unittests.
2294
+ * Added: Abstraction for the HTTP request.
2295
+ * Updated: Using Clark Notation for tags in properties. This means tags are
2296
+ serialized as {namespace}tagName instead of namespace#tagName
2297
+ * Fixed: HTTP_BasicAuth class now works as expected.
2298
+ * Fixed: DAV_Server uses / for a default baseUrl.
2299
+ * Fixed: Last modification date is no longer ignored in PROPFIND.
2300
+ * Fixed: PROPFIND now sends back information about the requestUri even when
2301
+ "Depth: 1" is specified.
2302
+
2303
+
2304
+ 0.5-alpha (2009-01-14)
2305
+ ----------------------
2306
+
2307
+ * Added: Added a very simple example for implementing a mapping to PHP file
2308
+ streams. This should allow easy implementation of for example a WebDAV to FTP
2309
+ proxy.
2310
+ * Added: HTTP Basic Authentication helper class.
2311
+ * Added: Sabre_HTTP_Response class. This centralizes HTTP operations and will be
2312
+ a start towards the creating of a testing framework.
2313
+ * Updated: Backwards compatibility break: all require_once() statements are
2314
+ removed from all the files. It is now recommended to use autoloading of
2315
+ classes, or just including lib/Sabre.includes.php. This fix was made to allow
2316
+ easier integration into applications not using this standard inclusion model.
2317
+ * Updated: Better in-file documentation.
2318
+ * Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager.
2319
+ * Updated: Fixes a shared-lock bug.
2320
+ * Updated: Removed ?> from the bottom of each php file.
2321
+ * Updated: Split up some operations from Sabre_DAV_Server to
2322
+ Sabre_HTTP_Response.
2323
+ * Fixed: examples are now actually included in the pear package.
2324
+
2325
+
2326
+ 0.4-alpha (2008-11-05)
2327
+ ----------------------
2328
+
2329
+ * Passes all litmus tests!
2330
+ * Added: more examples
2331
+ * Added: Custom property support
2332
+ * Added: Shared lock support
2333
+ * Added: Depth support to locks
2334
+ * Added: Locking on unmapped urls (non-existent nodes)
2335
+ * Fixed: Advertising as WebDAV class 3 support
2336
+
2337
+
2338
+ 0.3-alpha (2008-06-29)
2339
+ ----------------------
2340
+
2341
+ * Fully working in MS Windows clients.
2342
+ * Added: temporary file filter: support for smultron files.
2343
+ * Added: Phing build scripts
2344
+ * Added: PEAR package
2345
+ * Fixed: MOVE bug identified using finder.
2346
+ * Fixed: Using gzuncompress instead of gzdecode in the temporary file filter.
2347
+ This seems more common.
2348
+
2349
+
2350
+ 0.2-alpha (2008-05-27)
2351
+ ----------------------
2352
+
2353
+ * Somewhat working in Windows clients
2354
+ * Added: Working PROPPATCH method (doesn't support custom properties yet)
2355
+ * Added: Temporary filename handling system
2356
+ * Added: Sabre_DAV_IQuota to return quota information
2357
+ * Added: PROPFIND now reads the request body and only supplies the requested
2358
+ properties
2359
+
2360
+
2361
+ 0.1-alpha (2008-04-04)
2362
+ ----------------------
2363
+
2364
+ * First release!
2365
+ * Passes litmus: basic, http and copymove test.
2366
+ * Fully working in Finder and DavFS2.
2367
+
2368
+ Project started: 2007-12-13
2369
+
2370
+ [vobj]: http://sabre.io/vobject/
2371
+ [evnt]: http://sabre.io/event/
2372
+ [http]: http://sabre.io/http/
2373
+ [uri]: http://sabre.io/uri/
2374
+ [xml]: http://sabre.io/xml/
2375
+ [mi20]: http://sabre.io/dav/upgrade/1.8-to-2.0/
2376
+ [rfc6638]: http://tools.ietf.org/html/rfc6638 "CalDAV Scheduling"
2377
+ [rfc7240]: http://tools.ietf.org/html/rfc7240
2378
+ [calendar-availability]: https://tools.ietf.org/html/draft-daboo-calendar-availability-05
vendor/sabre/dav/CONTRIBUTING.md ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Contributing to sabre projects
2
+ ==============================
3
+
4
+ Want to contribute to sabre/dav? Here are some guidelines to ensure your patch
5
+ gets accepted.
6
+
7
+
8
+ Building a new feature? Contact us first
9
+ ----------------------------------------
10
+
11
+ We may not want to accept every feature that comes our way. Sometimes
12
+ features are out of scope for our projects.
13
+
14
+ We don't want to waste your time, so by having a quick chat with us first,
15
+ you may find out quickly if the feature makes sense to us, and we can give
16
+ some tips on how to best build the feature.
17
+
18
+ If we don't accept the feature, it could be for a number of reasons. For
19
+ instance, we've rejected features in the past because we felt uncomfortable
20
+ assuming responsibility for maintaining the feature.
21
+
22
+ In those cases, it's often possible to keep the feature separate from the
23
+ sabre projects. sabre/dav for instance has a plugin system, and there's no
24
+ reason the feature can't live in a project you own.
25
+
26
+ In that case, definitely let us know about your plugin as well, so we can
27
+ feature it on [sabre.io][4].
28
+
29
+ We are often on [IRC][5], in the #sabredav channel on freenode. If there's
30
+ no one there, post a message on the [mailing list][6].
31
+
32
+
33
+ Coding standards
34
+ ----------------
35
+
36
+ sabre projects follow:
37
+
38
+ 1. [PSR-1][1]
39
+ 2. [PSR-4][2]
40
+
41
+ sabre projects don't follow [PSR-2][3].
42
+
43
+ In addition to that, here's a list of basic rules:
44
+
45
+ 1. PHP 5.4 array syntax must be used every where. This means you use `[` and
46
+ `]` instead of `array(` and `)`.
47
+ 2. Use PHP namespaces everywhere.
48
+ 3. Use 4 spaces for indentation.
49
+ 4. Try to keep your lines under 80 characters. This is not a hard rule, as
50
+ there are many places in the source where it felt more sensibile to not
51
+ do so. In particular, function declarations are never split over multiple
52
+ lines.
53
+ 5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`,
54
+ `function`, etc. they belong to.
55
+ 6. `public` must be omitted from method declarations. It must also be omitted
56
+ for static properties.
57
+ 7. All files should use unix-line endings (`\n`).
58
+ 8. Files must omit the closing php tag (`?>`).
59
+ 9. `true`, `false` and `null` are always lower-case.
60
+ 10. Constants are always upper-case.
61
+ 11. Any of the rules stated before may be broken where this is the pragmatic
62
+ thing to do.
63
+
64
+
65
+ Unit test requirements
66
+ ----------------------
67
+
68
+ Any new feature or change requires unittests. We use [PHPUnit][7] for all our
69
+ tests.
70
+
71
+ Adding unittests will greatly increase the likelyhood of us quickly accepting
72
+ your pull request. If unittests are not included though for whatever reason,
73
+ we'd still _love_ your pull request.
74
+
75
+ We may have to write the tests ourselves, which can increase the time it takes
76
+ to accept the patch, but we'd still really like your contribution!
77
+
78
+ To run the testsuite jump into the directory `cd tests` and trigger `phpunit`.
79
+ Make sure you did a `composer install` beforehand.
80
+
81
+ [1]: http://www.php-fig.org/psr/psr-1/
82
+ [2]: http://www.php-fig.org/psr/psr-4/
83
+ [3]: http://www.php-fig.org/psr/psr-2/
84
+ [4]: http://sabre.io/
85
+ [5]: irc://freenode.net/#sabredav
86
+ [6]: http://groups.google.com/group/sabredav-discuss
87
+ [7]: http://phpunit.de/
vendor/sabre/dav/LICENSE ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/).
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+ * Neither the name of SabreDAV nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software
15
+ without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
+ POSSIBILITY OF SUCH DAMAGE.
vendor/sabre/dav/README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ![sabre's logo](http://sabre.io/img/logo.png) sabre/dav
2
+ =======================================================
3
+
4
+ Introduction
5
+ ------------
6
+
7
+ sabre/dav is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.
8
+
9
+ Full documentation can be found on the website:
10
+
11
+ http://sabre.io/
12
+
13
+
14
+ Build status
15
+ ------------
16
+
17
+ | branch | status | minimum PHP version |
18
+ | ------------ | ------ | ------------------- |
19
+ | master | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 |
20
+ | 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 |
21
+ | 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 |
22
+ | 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 |
23
+ | 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 |
24
+ | 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 |
25
+ | 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 |
26
+ | 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 |
27
+
28
+ Documentation
29
+ -------------
30
+
31
+ * [Introduction](http://sabre.io/dav/).
32
+ * [Installation](http://sabre.io/dav/install/).
33
+
34
+
35
+ Made at fruux
36
+ -------------
37
+
38
+ SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
vendor/sabre/dav/bin/build.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ $tasks = [
5
+
6
+ 'buildzip' => [
7
+ 'init', 'test', 'clean',
8
+ ],
9
+ 'markrelease' => [
10
+ 'init', 'test', 'clean',
11
+ ],
12
+ 'clean' => [],
13
+ 'test' => [
14
+ 'composerupdate',
15
+ ],
16
+ 'init' => [],
17
+ 'composerupdate' => [],
18
+ ];
19
+
20
+ $default = 'buildzip';
21
+
22
+ $baseDir = __DIR__ . '/../';
23
+ chdir($baseDir);
24
+
25
+ $currentTask = $default;
26
+ if ($argc > 1) $currentTask = $argv[1];
27
+ $version = null;
28
+ if ($argc > 2) $version = $argv[2];
29
+
30
+ if (!isset($tasks[$currentTask])) {
31
+ echo "Task not found: ", $currentTask, "\n";
32
+ die(1);
33
+ }
34
+
35
+ // Creating the dependency graph
36
+ $newTaskList = [];
37
+ $oldTaskList = [$currentTask => true];
38
+
39
+ while (count($oldTaskList) > 0) {
40
+
41
+ foreach ($oldTaskList as $task => $foo) {
42
+
43
+ if (!isset($tasks[$task])) {
44
+ echo "Dependency not found: " . $task, "\n";
45
+ die(1);
46
+ }
47
+ $dependencies = $tasks[$task];
48
+
49
+ $fullFilled = true;
50
+ foreach ($dependencies as $dependency) {
51
+ if (isset($newTaskList[$dependency])) {
52
+ // Already in the fulfilled task list.
53
+ continue;
54
+ } else {
55
+ $oldTaskList[$dependency] = true;
56
+ $fullFilled = false;
57
+ }
58
+
59
+ }
60
+ if ($fullFilled) {
61
+ unset($oldTaskList[$task]);
62
+ $newTaskList[$task] = 1;
63
+ }
64
+
65
+ }
66
+
67
+ }
68
+
69
+ foreach (array_keys($newTaskList) as $task) {
70
+
71
+ echo "task: " . $task, "\n";
72
+ call_user_func($task);
73
+ echo "\n";
74
+
75
+ }
76
+
77
+ function init() {
78
+
79
+ global $version;
80
+ if (!$version) {
81
+ include __DIR__ . '/../vendor/autoload.php';
82
+ $version = Sabre\DAV\Version::VERSION;
83
+ }
84
+
85
+ echo " Building sabre/dav " . $version, "\n";
86
+
87
+ }
88
+
89
+ function clean() {
90
+
91
+ global $baseDir;
92
+ echo " Removing build files\n";
93
+ $outputDir = $baseDir . '/build/SabreDAV';
94
+ if (is_dir($outputDir)) {
95
+ system('rm -r ' . $baseDir . '/build/SabreDAV');
96
+ }
97
+
98
+ }
99
+
100
+ function composerupdate() {
101
+
102
+ global $baseDir;
103
+ echo " Updating composer packages to latest version\n\n";
104
+ system('cd ' . $baseDir . '; composer update');
105
+ }
106
+
107
+ function test() {
108
+
109
+ global $baseDir;
110
+
111
+ echo " Running all unittests.\n";
112
+ echo " This may take a while.\n\n";
113
+ system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml.dist --stop-on-failure', $code);
114
+ if ($code != 0) {
115
+ echo "PHPUnit reported error code $code\n";
116
+ die(1);
117
+ }
118
+
119
+ }
120
+
121
+ function buildzip() {
122
+
123
+ global $baseDir, $version;
124
+ echo " Generating composer.json\n";
125
+
126
+ $input = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true);
127
+ $newComposer = [
128
+ "require" => $input['require'],
129
+ "config" => [
130
+ "bin-dir" => "./bin",
131
+ ],
132
+ "prefer-stable" => true,
133
+ "minimum-stability" => "alpha",
134
+ ];
135
+ unset(
136
+ $newComposer['require']['sabre/vobject'],
137
+ $newComposer['require']['sabre/http'],
138
+ $newComposer['require']['sabre/uri'],
139
+ $newComposer['require']['sabre/event']
140
+ );
141
+ $newComposer['require']['sabre/dav'] = $version;
142
+ mkdir('build/SabreDAV');
143
+ file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT));
144
+
145
+ echo " Downloading dependencies\n";
146
+ system("cd build/SabreDAV; composer install -n", $code);
147
+ if ($code !== 0) {
148
+ echo "Composer reported error code $code\n";
149
+ die(1);
150
+ }
151
+
152
+ echo " Removing pointless files\n";
153
+ unlink('build/SabreDAV/composer.json');
154
+ unlink('build/SabreDAV/composer.lock');
155
+
156
+ echo " Moving important files to the root of the project\n";
157
+
158
+ $fileNames = [
159
+ 'CHANGELOG.md',
160
+ 'LICENSE',
161
+ 'README.md',
162
+ 'examples',
163
+ ];
164
+ foreach ($fileNames as $fileName) {
165
+ echo " $fileName\n";
166
+ rename('build/SabreDAV/vendor/sabre/dav/' . $fileName, 'build/SabreDAV/' . $fileName);
167
+ }
168
+
169
+ // <zip destfile="build/SabreDAV-${sabredav.version}.zip" basedir="build/SabreDAV" prefix="SabreDAV/" />
170
+
171
+ echo "\n";
172
+ echo "Zipping the sabredav distribution\n\n";
173
+ system('cd build; zip -qr sabredav-' . $version . '.zip SabreDAV');
174
+
175
+ echo "Done.";
176
+
177
+ }
vendor/sabre/dav/bin/googlecode_upload.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copyright 2006, 2007 Google Inc. All Rights Reserved.
4
+ # Author: danderson@google.com (David Anderson)
5
+ #
6
+ # Script for uploading files to a Google Code project.
7
+ #
8
+ # This is intended to be both a useful script for people who want to
9
+ # streamline project uploads and a reference implementation for
10
+ # uploading files to Google Code projects.
11
+ #
12
+ # To upload a file to Google Code, you need to provide a path to the
13
+ # file on your local machine, a small summary of what the file is, a
14
+ # project name, and a valid account that is a member or owner of that
15
+ # project. You can optionally provide a list of labels that apply to
16
+ # the file. The file will be uploaded under the same name that it has
17
+ # in your local filesystem (that is, the "basename" or last path
18
+ # component). Run the script with '--help' to get the exact syntax
19
+ # and available options.
20
+ #
21
+ # Note that the upload script requests that you enter your
22
+ # googlecode.com password. This is NOT your Gmail account password!
23
+ # This is the password you use on googlecode.com for committing to
24
+ # Subversion and uploading files. You can find your password by going
25
+ # to http://code.google.com/hosting/settings when logged in with your
26
+ # Gmail account. If you have already committed to your project's
27
+ # Subversion repository, the script will automatically retrieve your
28
+ # credentials from there (unless disabled, see the output of '--help'
29
+ # for details).
30
+ #
31
+ # If you are looking at this script as a reference for implementing
32
+ # your own Google Code file uploader, then you should take a look at
33
+ # the upload() function, which is the meat of the uploader. You
34
+ # basically need to build a multipart/form-data POST request with the
35
+ # right fields and send it to https://PROJECT.googlecode.com/files .
36
+ # Authenticate the request using HTTP Basic authentication, as is
37
+ # shown below.
38
+ #
39
+ # Licensed under the terms of the Apache Software License 2.0:
40
+ # http://www.apache.org/licenses/LICENSE-2.0
41
+ #
42
+ # Questions, comments, feature requests and patches are most welcome.
43
+ # Please direct all of these to the Google Code users group:
44
+ # http://groups.google.com/group/google-code-hosting
45
+
46
+ """Google Code file uploader script.
47
+ """
48
+
49
+ __author__ = 'danderson@google.com (David Anderson)'
50
+
51
+ import httplib
52
+ import os.path
53
+ import optparse
54
+ import getpass
55
+ import base64
56
+ import sys
57
+
58
+
59
+ def upload(file, project_name, user_name, password, summary, labels=None):
60
+ """Upload a file to a Google Code project's file server.
61
+
62
+ Args:
63
+ file: The local path to the file.
64
+ project_name: The name of your project on Google Code.
65
+ user_name: Your Google account name.
66
+ password: The googlecode.com password for your account.
67
+ Note that this is NOT your global Google Account password!
68
+ summary: A small description for the file.
69
+ labels: an optional list of label strings with which to tag the file.
70
+
71
+ Returns: a tuple:
72
+ http_status: 201 if the upload succeeded, something else if an
73
+ error occurred.
74
+ http_reason: The human-readable string associated with http_status
75
+ file_url: If the upload succeeded, the URL of the file on Google
76
+ Code, None otherwise.
77
+ """
78
+ # The login is the user part of user@gmail.com. If the login provided
79
+ # is in the full user@domain form, strip it down.
80
+ if user_name.endswith('@gmail.com'):
81
+ user_name = user_name[:user_name.index('@gmail.com')]
82
+
83
+ form_fields = [('summary', summary)]
84
+ if labels is not None:
85
+ form_fields.extend([('label', l.strip()) for l in labels])
86
+
87
+ content_type, body = encode_upload_request(form_fields, file)
88
+
89
+ upload_host = '%s.googlecode.com' % project_name
90
+ upload_uri = '/files'
91
+ auth_token = base64.b64encode('%s:%s'% (user_name, password))
92
+ headers = {
93
+ 'Authorization': 'Basic %s' % auth_token,
94
+ 'User-Agent': 'Googlecode.com uploader v0.9.4',
95
+ 'Content-Type': content_type,
96
+ }
97
+
98
+ server = httplib.HTTPSConnection(upload_host)
99
+ server.request('POST', upload_uri, body, headers)
100
+ resp = server.getresponse()
101
+ server.close()
102
+
103
+ if resp.status == 201:
104
+ location = resp.getheader('Location', None)
105
+ else:
106
+ location = None
107
+ return resp.status, resp.reason, location
108
+
109
+
110
+ def encode_upload_request(fields, file_path):
111
+ """Encode the given fields and file into a multipart form body.
112
+
113
+ fields is a sequence of (name, value) pairs. file is the path of
114
+ the file to upload. The file will be uploaded to Google Code with
115
+ the same file name.
116
+
117
+ Returns: (content_type, body) ready for httplib.HTTP instance
118
+ """
119
+ BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
120
+ CRLF = '\r\n'
121
+
122
+ body = []
123
+
124
+ # Add the metadata about the upload first
125
+ for key, value in fields:
126
+ body.extend(
127
+ ['--' + BOUNDARY,
128
+ 'Content-Disposition: form-data; name="%s"' % key,
129
+ '',
130
+ value,
131
+ ])
132
+
133
+ # Now add the file itself
134
+ file_name = os.path.basename(file_path)
135
+ f = open(file_path, 'rb')
136
+ file_content = f.read()
137
+ f.close()
138
+
139
+ body.extend(
140
+ ['--' + BOUNDARY,
141
+ 'Content-Disposition: form-data; name="filename"; filename="%s"'
142
+ % file_name,
143
+ # The upload server determines the mime-type, no need to set it.
144
+ 'Content-Type: application/octet-stream',
145
+ '',
146
+ file_content,
147
+ ])
148
+
149
+ # Finalize the form body
150
+ body.extend(['--' + BOUNDARY + '--', ''])
151
+
152
+ return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
153
+
154
+
155
+ def upload_find_auth(file_path, project_name, summary, labels=None,
156
+ user_name=None, password=None, tries=3):
157
+ """Find credentials and upload a file to a Google Code project's file server.
158
+
159
+ file_path, project_name, summary, and labels are passed as-is to upload.
160
+
161
+ Args:
162
+ file_path: The local path to the file.
163
+ project_name: The name of your project on Google Code.
164
+ summary: A small description for the file.
165
+ labels: an optional list of label strings with which to tag the file.
166
+ config_dir: Path to Subversion configuration directory, 'none', or None.
167
+ user_name: Your Google account name.
168
+ tries: How many attempts to make.
169
+ """
170
+
171
+ while tries > 0:
172
+ if user_name is None:
173
+ # Read username if not specified or loaded from svn config, or on
174
+ # subsequent tries.
175
+ sys.stdout.write('Please enter your googlecode.com username: ')
176
+ sys.stdout.flush()
177
+ user_name = sys.stdin.readline().rstrip()
178
+ if password is None:
179
+ # Read password if not loaded from svn config, or on subsequent tries.
180
+ print 'Please enter your googlecode.com password.'
181
+ print '** Note that this is NOT your Gmail account password! **'
182
+ print 'It is the password you use to access Subversion repositories,'
183
+ print 'and can be found here: http://code.google.com/hosting/settings'
184
+ password = getpass.getpass()
185
+
186
+ status, reason, url = upload(file_path, project_name, user_name, password,
187
+ summary, labels)
188
+ # Returns 403 Forbidden instead of 401 Unauthorized for bad
189
+ # credentials as of 2007-07-17.
190
+ if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
191
+ # Rest for another try.
192
+ user_name = password = None
193
+ tries = tries - 1
194
+ else:
195
+ # We're done.
196
+ break
197
+
198
+ return status, reason, url
199
+
200
+
201
+ def main():
202
+ parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
203
+ '-p PROJECT [options] FILE')
204
+ parser.add_option('-s', '--summary', dest='summary',
205
+ help='Short description of the file')
206
+ parser.add_option('-p', '--project', dest='project',
207
+ help='Google Code project name')
208
+ parser.add_option('-u', '--user', dest='user',
209
+ help='Your Google Code username')
210
+ parser.add_option('-w', '--password', dest='password',
211
+ help='Your Google Code password')
212
+ parser.add_option('-l', '--labels', dest='labels',
213
+ help='An optional list of comma-separated labels to attach '
214
+ 'to the file')
215
+
216
+ options, args = parser.parse_args()
217
+
218
+ if not options.summary:
219
+ parser.error('File summary is missing.')
220
+ elif not options.project:
221
+ parser.error('Project name is missing.')
222
+ elif len(args) < 1:
223
+ parser.error('File to upload not provided.')
224
+ elif len(args) > 1:
225
+ parser.error('Only one file may be specified.')
226
+
227
+ file_path = args[0]
228
+
229
+ if options.labels:
230
+ labels = options.labels.split(',')
231
+ else:
232
+ labels = None
233
+
234
+ status, reason, url = upload_find_auth(file_path, options.project,
235
+ options.summary, labels,
236
+ options.user, options.password)
237
+ if url:
238
+ print 'The file was uploaded successfully.'
239
+ print 'URL: %s' % url
240
+ return 0
241
+ else:
242
+ print 'An error occurred. Your file was not uploaded.'
243
+ print 'Google Code upload server said: %s (%s)' % (reason, status)
244
+ return 1
245
+
246
+
247
+ if __name__ == '__main__':
248
+ sys.exit(main())
vendor/sabre/dav/bin/migrateto20.php ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ echo "SabreDAV migrate script for version 2.0\n";
5
+
6
+ if ($argc < 2) {
7
+
8
+ echo <<<HELLO
9
+
10
+ This script help you migrate from a pre-2.0 database to 2.0 and later
11
+
12
+ The 'calendars', 'addressbooks' and 'cards' tables will be upgraded, and new
13
+ tables (calendarchanges, addressbookchanges, propertystorage) will be added.
14
+
15
+ If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
16
+ run this script.
17
+
18
+ Keep in mind that ALTER TABLE commands will be executed. If you have a large
19
+ dataset this may mean that this process takes a while.
20
+
21
+ Lastly: Make a back-up first. This script has been tested, but the amount of
22
+ potential variants are extremely high, so it's impossible to deal with every
23
+ possible situation.
24
+
25
+ In the worst case, you will lose all your data. This is not an overstatement.
26
+
27
+ Usage:
28
+
29
+ php {$argv[0]} [pdo-dsn] [username] [password]
30
+
31
+ For example:
32
+
33
+ php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
34
+ php {$argv[0]} sqlite:data/sabredav.db
35
+
36
+ HELLO;
37
+
38
+ exit();
39
+
40
+ }
41
+
42
+ // There's a bunch of places where the autoloader could be, so we'll try all of
43
+ // them.
44
+ $paths = [
45
+ __DIR__ . '/../vendor/autoload.php',
46
+ __DIR__ . '/../../../autoload.php',
47
+ ];
48
+
49
+ foreach ($paths as $path) {
50
+ if (file_exists($path)) {
51
+ include $path;
52
+ break;
53
+ }
54
+ }
55
+
56
+ $dsn = $argv[1];
57
+ $user = isset($argv[2]) ? $argv[2] : null;
58
+ $pass = isset($argv[3]) ? $argv[3] : null;
59
+
60
+ echo "Connecting to database: " . $dsn . "\n";
61
+
62
+ $pdo = new PDO($dsn, $user, $pass);
63
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
64
+ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
65
+
66
+ $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
67
+
68
+ switch ($driver) {
69
+
70
+ case 'mysql' :
71
+ echo "Detected MySQL.\n";
72
+ break;
73
+ case 'sqlite' :
74
+ echo "Detected SQLite.\n";
75
+ break;
76
+ default :
77
+ echo "Error: unsupported driver: " . $driver . "\n";
78
+ die(-1);
79
+ }
80
+
81
+ foreach (['calendar', 'addressbook'] as $itemType) {
82
+
83
+ $tableName = $itemType . 's';
84
+ $tableNameOld = $tableName . '_old';
85
+ $changesTable = $itemType . 'changes';
86
+
87
+ echo "Upgrading '$tableName'\n";
88
+
89
+ // The only cross-db way to do this, is to just fetch a single record.
90
+ $row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch();
91
+
92
+ if (!$row) {
93
+
94
+ echo "No records were found in the '$tableName' table.\n";
95
+ echo "\n";
96
+ echo "We're going to rename the old table to $tableNameOld (just in case).\n";
97
+ echo "and re-create the new table.\n";
98
+
99
+ switch ($driver) {
100
+
101
+ case 'mysql' :
102
+ $pdo->exec("RENAME TABLE $tableName TO $tableNameOld");
103
+ switch ($itemType) {
104
+ case 'calendar' :
105
+ $pdo->exec("
106
+ CREATE TABLE calendars (
107
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
108
+ principaluri VARCHAR(100),
109
+ displayname VARCHAR(100),
110
+ uri VARCHAR(200),
111
+ synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
112
+ description TEXT,
113
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
114
+ calendarcolor VARCHAR(10),
115
+ timezone TEXT,
116
+ components VARCHAR(20),
117
+ transparent TINYINT(1) NOT NULL DEFAULT '0',
118
+ UNIQUE(principaluri, uri)
119
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
120
+ ");
121
+ break;
122
+ case 'addressbook' :
123
+ $pdo->exec("
124
+ CREATE TABLE addressbooks (
125
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
126
+ principaluri VARCHAR(255),
127
+ displayname VARCHAR(255),
128
+ uri VARCHAR(200),
129
+ description TEXT,
130
+ synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
131
+ UNIQUE(principaluri, uri)
132
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
133
+ ");
134
+ break;
135
+ }
136
+ break;
137
+
138
+ case 'sqlite' :
139
+
140
+ $pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld");
141
+
142
+ switch ($itemType) {
143
+ case 'calendar' :
144
+ $pdo->exec("
145
+ CREATE TABLE calendars (
146
+ id integer primary key asc,
147
+ principaluri text,
148
+ displayname text,
149
+ uri text,
150
+ synctoken integer,
151
+ description text,
152
+ calendarorder integer,
153
+ calendarcolor text,
154
+ timezone text,
155
+ components text,
156
+ transparent bool
157
+ );
158
+ ");
159
+ break;
160
+ case 'addressbook' :
161
+ $pdo->exec("
162
+ CREATE TABLE addressbooks (
163
+ id integer primary key asc,
164
+ principaluri text,
165
+ displayname text,
166
+ uri text,
167
+ description text,
168
+ synctoken integer
169
+ );
170
+ ");
171
+
172
+ break;
173
+ }
174
+ break;
175
+
176
+ }
177
+ echo "Creation of 2.0 $tableName table is complete\n";
178
+
179
+ } else {
180
+
181
+ // Checking if there's a synctoken field already.
182
+ if (array_key_exists('synctoken', $row)) {
183
+ echo "The 'synctoken' field already exists in the $tableName table.\n";
184
+ echo "It's likely you already upgraded, so we're simply leaving\n";
185
+ echo "the $tableName table alone\n";
186
+ } else {
187
+
188
+ echo "1.8 table schema detected\n";
189
+ switch ($driver) {
190
+
191
+ case 'mysql' :
192
+ $pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'");
193
+ $pdo->exec("ALTER TABLE $tableName DROP ctag");
194
+ $pdo->exec("UPDATE $tableName SET synctoken = '1'");
195
+ break;
196
+ case 'sqlite' :
197
+ $pdo->exec("ALTER TABLE $tableName ADD synctoken integer");
198
+ $pdo->exec("UPDATE $tableName SET synctoken = '1'");
199
+ echo "Note: there's no easy way to remove fields in sqlite.\n";
200
+ echo "The ctag field is no longer used, but it's kept in place\n";
201
+ break;
202
+
203
+ }
204
+
205
+ echo "Upgraded '$tableName' to 2.0 schema.\n";
206
+
207
+ }
208
+
209
+ }
210
+
211
+ try {
212
+ $pdo->query("SELECT * FROM $changesTable LIMIT 1");
213
+
214
+ echo "'$changesTable' already exists. Assuming that this part of the\n";
215
+ echo "upgrade was already completed.\n";
216
+
217
+ } catch (Exception $e) {
218
+ echo "Creating '$changesTable' table.\n";
219
+
220
+ switch ($driver) {
221
+
222
+ case 'mysql' :
223
+ $pdo->exec("
224
+ CREATE TABLE $changesTable (
225
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
226
+ uri VARCHAR(200) NOT NULL,
227
+ synctoken INT(11) UNSIGNED NOT NULL,
228
+ {$itemType}id INT(11) UNSIGNED NOT NULL,
229
+ operation TINYINT(1) NOT NULL,
230
+ INDEX {$itemType}id_synctoken ({$itemType}id, synctoken)
231
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
232
+
233
+ ");
234
+ break;
235
+ case 'sqlite' :
236
+ $pdo->exec("
237
+
238
+ CREATE TABLE $changesTable (
239
+ id integer primary key asc,
240
+ uri text,
241
+ synctoken integer,
242
+ {$itemType}id integer,
243
+ operation bool
244
+ );
245
+
246
+ ");
247
+ $pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);");
248
+ break;
249
+
250
+ }
251
+
252
+ }
253
+
254
+ }
255
+
256
+ try {
257
+ $pdo->query("SELECT * FROM calendarsubscriptions LIMIT 1");
258
+
259
+ echo "'calendarsubscriptions' already exists. Assuming that this part of the\n";
260
+ echo "upgrade was already completed.\n";
261
+
262
+ } catch (Exception $e) {
263
+ echo "Creating calendarsubscriptions table.\n";
264
+
265
+ switch ($driver) {
266
+
267
+ case 'mysql' :
268
+ $pdo->exec("
269
+ CREATE TABLE calendarsubscriptions (
270
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
271
+ uri VARCHAR(200) NOT NULL,
272
+ principaluri VARCHAR(100) NOT NULL,
273
+ source TEXT,
274
+ displayname VARCHAR(100),
275
+ refreshrate VARCHAR(10),
276
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
277
+ calendarcolor VARCHAR(10),
278
+ striptodos TINYINT(1) NULL,
279
+ stripalarms TINYINT(1) NULL,
280
+ stripattachments TINYINT(1) NULL,
281
+ lastmodified INT(11) UNSIGNED,
282
+ UNIQUE(principaluri, uri)
283
+ );
284
+ ");
285
+ break;
286
+ case 'sqlite' :
287
+ $pdo->exec("
288
+
289
+ CREATE TABLE calendarsubscriptions (
290
+ id integer primary key asc,
291
+ uri text,
292
+ principaluri text,
293
+ source text,
294
+ displayname text,
295
+ refreshrate text,
296
+ calendarorder integer,
297
+ calendarcolor text,
298
+ striptodos bool,
299
+ stripalarms bool,
300
+ stripattachments bool,
301
+ lastmodified int
302
+ );
303
+ ");
304
+
305
+ $pdo->exec("CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);");
306
+ break;
307
+
308
+ }
309
+
310
+ }
311
+
312
+ try {
313
+ $pdo->query("SELECT * FROM propertystorage LIMIT 1");
314
+
315
+ echo "'propertystorage' already exists. Assuming that this part of the\n";
316
+ echo "upgrade was already completed.\n";
317
+
318
+ } catch (Exception $e) {
319
+ echo "Creating propertystorage table.\n";
320
+
321
+ switch ($driver) {
322
+
323
+ case 'mysql' :
324
+ $pdo->exec("
325
+ CREATE TABLE propertystorage (
326
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
327
+ path VARBINARY(1024) NOT NULL,
328
+ name VARBINARY(100) NOT NULL,
329
+ value MEDIUMBLOB
330
+ );
331
+ ");
332
+ $pdo->exec("
333
+ CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100));
334
+ ");
335
+ break;
336
+ case 'sqlite' :
337
+ $pdo->exec("
338
+ CREATE TABLE propertystorage (
339
+ id integer primary key asc,
340
+ path TEXT,
341
+ name TEXT,
342
+ value TEXT
343
+ );
344
+ ");
345
+ $pdo->exec("
346
+ CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
347
+ ");
348
+
349
+ break;
350
+
351
+ }
352
+
353
+ }
354
+
355
+ echo "Upgrading cards table to 2.0 schema\n";
356
+
357
+ try {
358
+
359
+ $create = false;
360
+ $row = $pdo->query("SELECT * FROM cards LIMIT 1")->fetch();
361
+ if (!$row) {
362
+ $random = mt_rand(1000, 9999);
363
+ echo "There was no data in the cards table, so we're re-creating it\n";
364
+ echo "The old table will be renamed to cards_old$random, just in case.\n";
365
+
366
+ $create = true;
367
+
368
+ switch ($driver) {
369
+ case 'mysql' :
370
+ $pdo->exec("RENAME TABLE cards TO cards_old$random");
371
+ break;
372
+ case 'sqlite' :
373
+ $pdo->exec("ALTER TABLE cards RENAME TO cards_old$random");
374
+ break;
375
+
376
+ }
377
+ }
378
+
379
+ } catch (Exception $e) {
380
+
381
+ echo "Exception while checking cards table. Assuming that the table does not yet exist.\n";
382
+ echo "Debug: ", $e->getMessage(), "\n";
383
+ $create = true;
384
+
385
+ }
386
+
387
+ if ($create) {
388
+ switch ($driver) {
389
+ case 'mysql' :
390
+ $pdo->exec("
391
+ CREATE TABLE cards (
392
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
393
+ addressbookid INT(11) UNSIGNED NOT NULL,
394
+ carddata MEDIUMBLOB,
395
+ uri VARCHAR(200),
396
+ lastmodified INT(11) UNSIGNED,
397
+ etag VARBINARY(32),
398
+ size INT(11) UNSIGNED NOT NULL
399
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
400
+
401
+ ");
402
+ break;
403
+
404
+ case 'sqlite' :
405
+
406
+ $pdo->exec("
407
+ CREATE TABLE cards (
408
+ id integer primary key asc,
409
+ addressbookid integer,
410
+ carddata blob,
411
+ uri text,
412
+ lastmodified integer,
413
+ etag text,
414
+ size integer
415
+ );
416
+ ");
417
+ break;
418
+
419
+ }
420
+ } else {
421
+ switch ($driver) {
422
+ case 'mysql' :
423
+ $pdo->exec("
424
+ ALTER TABLE cards
425
+ ADD etag VARBINARY(32),
426
+ ADD size INT(11) UNSIGNED NOT NULL;
427
+ ");
428
+ break;
429
+
430
+ case 'sqlite' :
431
+
432
+ $pdo->exec("
433
+ ALTER TABLE cards ADD etag text;
434
+ ALTER TABLE cards ADD size integer;
435
+ ");
436
+ break;
437
+
438
+ }
439
+ echo "Reading all old vcards and populating etag and size fields.\n";
440
+ $result = $pdo->query('SELECT id, carddata FROM cards');
441
+ $stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?');
442
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
443
+ $stmt->execute([
444
+ md5($row['carddata']),
445
+ strlen($row['carddata']),
446
+ $row['id']
447
+ ]);
448
+ }
449
+
450
+
451
+ }
452
+
453
+ echo "Upgrade to 2.0 schema completed.\n";
vendor/sabre/dav/bin/migrateto21.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ echo "SabreDAV migrate script for version 2.1\n";
5
+
6
+ if ($argc < 2) {
7
+
8
+ echo <<<HELLO
9
+
10
+ This script help you migrate from a pre-2.1 database to 2.1.
11
+
12
+ Changes:
13
+ The 'calendarobjects' table will be upgraded.
14
+ 'schedulingobjects' will be created.
15
+
16
+ If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
17
+ run this script.
18
+
19
+ Keep in mind that ALTER TABLE commands will be executed. If you have a large
20
+ dataset this may mean that this process takes a while.
21
+
22
+ Lastly: Make a back-up first. This script has been tested, but the amount of
23
+ potential variants are extremely high, so it's impossible to deal with every
24
+ possible situation.
25
+
26
+ In the worst case, you will lose all your data. This is not an overstatement.
27
+
28
+ Usage:
29
+
30
+ php {$argv[0]} [pdo-dsn] [username] [password]
31
+
32
+ For example:
33
+
34
+ php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
35
+ php {$argv[0]} sqlite:data/sabredav.db
36
+
37
+ HELLO;
38
+
39
+ exit();
40
+
41
+ }
42
+
43
+ // There's a bunch of places where the autoloader could be, so we'll try all of
44
+ // them.
45
+ $paths = [
46
+ __DIR__ . '/../vendor/autoload.php',
47
+ __DIR__ . '/../../../autoload.php',
48
+ ];
49
+
50
+ foreach ($paths as $path) {
51
+ if (file_exists($path)) {
52
+ include $path;
53
+ break;
54
+ }
55
+ }
56
+
57
+ $dsn = $argv[1];
58
+ $user = isset($argv[2]) ? $argv[2] : null;
59
+ $pass = isset($argv[3]) ? $argv[3] : null;
60
+
61
+ echo "Connecting to database: " . $dsn . "\n";
62
+
63
+ $pdo = new PDO($dsn, $user, $pass);
64
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
65
+ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
66
+
67
+ $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
68
+
69
+ switch ($driver) {
70
+
71
+ case 'mysql' :
72
+ echo "Detected MySQL.\n";
73
+ break;
74
+ case 'sqlite' :
75
+ echo "Detected SQLite.\n";
76
+ break;
77
+ default :
78
+ echo "Error: unsupported driver: " . $driver . "\n";
79
+ die(-1);
80
+ }
81
+
82
+ echo "Upgrading 'calendarobjects'\n";
83
+ $addUid = false;
84
+ try {
85
+ $result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1');
86
+ $row = $result->fetch(\PDO::FETCH_ASSOC);
87
+
88
+ if (!$row) {
89
+ echo "No data in table. Going to try to add the uid field anyway.\n";
90
+ $addUid = true;
91
+ } elseif (array_key_exists('uid', $row)) {
92
+ echo "uid field exists. Assuming that this part of the migration has\n";
93
+ echo "Already been completed.\n";
94
+ } else {
95
+ echo "2.0 schema detected.\n";
96
+ $addUid = true;
97
+ }
98
+
99
+ } catch (Exception $e) {
100
+ echo "Could not find a calendarobjects table. Skipping this part of the\n";
101
+ echo "upgrade.\n";
102
+ }
103
+
104
+ if ($addUid) {
105
+
106
+ switch ($driver) {
107
+ case 'mysql' :
108
+ $pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)');
109
+ break;
110
+ case 'sqlite' :
111
+ $pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT');
112
+ break;
113
+ }
114
+
115
+ $result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
116
+ $stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?');
117
+ $counter = 0;
118
+
119
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
120
+
121
+ try {
122
+ $vobj = \Sabre\VObject\Reader::read($row['calendardata']);
123
+ } catch (\Exception $e) {
124
+ echo "Warning! Item with id $row[id] could not be parsed!\n";
125
+ continue;
126
+ }
127
+ $uid = null;
128
+ $item = $vobj->getBaseComponent();
129
+ if (!isset($item->UID)) {
130
+ echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n";
131
+ continue;
132
+ }
133
+ $uid = (string)$item->UID;
134
+ $stmt->execute([$uid, $row['id']]);
135
+ $counter++;
136
+
137
+ }
138
+
139
+ }
140
+
141
+ echo "Creating 'schedulingobjects'\n";
142
+
143
+ switch ($driver) {
144
+
145
+ case 'mysql' :
146
+ $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects
147
+ (
148
+ id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
149
+ principaluri VARCHAR(255),
150
+ calendardata MEDIUMBLOB,
151
+ uri VARCHAR(200),
152
+ lastmodified INT(11) UNSIGNED,
153
+ etag VARCHAR(32),
154
+ size INT(11) UNSIGNED NOT NULL
155
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
156
+ ');
157
+ break;
158
+
159
+
160
+ case 'sqlite' :
161
+ $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects (
162
+ id integer primary key asc,
163
+ principaluri text,
164
+ calendardata blob,
165
+ uri text,
166
+ lastmodified integer,
167
+ etag text,
168
+ size integer
169
+ )
170
+ ');
171
+ break;
172
+ }
173
+
174
+ echo "Done.\n";
175
+
176
+ echo "Upgrade to 2.1 schema completed.\n";
vendor/sabre/dav/bin/migrateto30.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ echo "SabreDAV migrate script for version 3.0\n";
5
+
6
+ if ($argc < 2) {
7
+
8
+ echo <<<HELLO
9
+
10
+ This script help you migrate from a pre-3.0 database to 3.0 and later
11
+
12
+ Changes:
13
+ * The propertystorage table has changed to allow storage of complex
14
+ properties.
15
+ * the vcardurl field in the principals table is no more. This was moved to
16
+ the propertystorage table.
17
+
18
+ Keep in mind that ALTER TABLE commands will be executed. If you have a large
19
+ dataset this may mean that this process takes a while.
20
+
21
+ Lastly: Make a back-up first. This script has been tested, but the amount of
22
+ potential variants are extremely high, so it's impossible to deal with every
23
+ possible situation.
24
+
25
+ In the worst case, you will lose all your data. This is not an overstatement.
26
+
27
+ Usage:
28
+
29
+ php {$argv[0]} [pdo-dsn] [username] [password]
30
+
31
+ For example:
32
+
33
+ php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
34
+ php {$argv[0]} sqlite:data/sabredav.db
35
+
36
+ HELLO;
37
+
38
+ exit();
39
+
40
+ }
41
+
42
+ // There's a bunch of places where the autoloader could be, so we'll try all of
43
+ // them.
44
+ $paths = [
45
+ __DIR__ . '/../vendor/autoload.php',
46
+ __DIR__ . '/../../../autoload.php',
47
+ ];
48
+
49
+ foreach ($paths as $path) {
50
+ if (file_exists($path)) {
51
+ include $path;
52
+ break;
53
+ }
54
+ }
55
+
56
+ $dsn = $argv[1];
57
+ $user = isset($argv[2]) ? $argv[2] : null;
58
+ $pass = isset($argv[3]) ? $argv[3] : null;
59
+
60
+ echo "Connecting to database: " . $dsn . "\n";
61
+
62
+ $pdo = new PDO($dsn, $user, $pass);
63
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
64
+ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
65
+
66
+ $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
67
+
68
+ switch ($driver) {
69
+
70
+ case 'mysql' :
71
+ echo "Detected MySQL.\n";
72
+ break;
73
+ case 'sqlite' :
74
+ echo "Detected SQLite.\n";
75
+ break;
76
+ default :
77
+ echo "Error: unsupported driver: " . $driver . "\n";
78
+ die(-1);
79
+ }
80
+
81
+ echo "Upgrading 'propertystorage'\n";
82
+ $addValueType = false;
83
+ try {
84
+ $result = $pdo->query('SELECT * FROM propertystorage LIMIT 1');
85
+ $row = $result->fetch(\PDO::FETCH_ASSOC);
86
+
87
+ if (!$row) {
88
+ echo "No data in table. Going to re-create the table.\n";
89
+ $random = mt_rand(1000, 9999);
90
+ echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n";
91
+
92
+ switch ($driver) {
93
+
94
+ case 'mysql' :
95
+ $pdo->exec('RENAME TABLE propertystorage TO propertystorage_old' . $random);
96
+ $pdo->exec('
97
+ CREATE TABLE propertystorage (
98
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
99
+ path VARBINARY(1024) NOT NULL,
100
+ name VARBINARY(100) NOT NULL,
101
+ valuetype INT UNSIGNED,
102
+ value MEDIUMBLOB
103
+ );
104
+ ');
105
+ $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path(600), name(100));');
106
+ break;
107
+ case 'sqlite' :
108
+ $pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old' . $random);
109
+ $pdo->exec('
110
+ CREATE TABLE propertystorage (
111
+ id integer primary key asc,
112
+ path text,
113
+ name text,
114
+ valuetype integer,
115
+ value blob
116
+ );');
117
+
118
+ $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path, name);');
119
+ break;
120
+
121
+ }
122
+ } elseif (array_key_exists('valuetype', $row)) {
123
+ echo "valuetype field exists. Assuming that this part of the migration has\n";
124
+ echo "Already been completed.\n";
125
+ } else {
126
+ echo "2.1 schema detected. Going to perform upgrade.\n";
127
+ $addValueType = true;
128
+ }
129
+
130
+ } catch (Exception $e) {
131
+ echo "Could not find a propertystorage table. Skipping this part of the\n";
132
+ echo "upgrade.\n";
133
+ echo $e->getMessage(), "\n";
134
+ }
135
+
136
+ if ($addValueType) {
137
+
138
+ switch ($driver) {
139
+ case 'mysql' :
140
+ $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED');
141
+ break;
142
+ case 'sqlite' :
143
+ $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT');
144
+
145
+ break;
146
+ }
147
+
148
+ $pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL ');
149
+
150
+ }
151
+
152
+ echo "Migrating vcardurl\n";
153
+
154
+ $result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL');
155
+ $stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)');
156
+
157
+ while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
158
+
159
+ // Inserting the new record
160
+ $stmt1->execute([
161
+ 'addressbooks/' . basename($row['uri']),
162
+ '{http://calendarserver.org/ns/}me-card',
163
+ serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']))
164
+ ]);
165
+
166
+ echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']));
167
+
168
+ }
169
+
170
+ echo "Done.\n";
171
+ echo "Upgrade to 3.0 schema completed.\n";
vendor/sabre/dav/bin/migrateto32.php ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env php
2
+ <?php
3
+
4
+ echo "SabreDAV migrate script for version 3.2\n";
5
+
6
+ if ($argc < 2) {
7
+
8
+ echo <<<HELLO
9
+
10
+ This script help you migrate from a 3.1 database to 3.2 and later
11
+
12
+ Changes:
13
+ * Created a new calendarinstances table to support calendar sharing.
14
+ * Remove a lot of columns from calendars.
15
+
16
+ Keep in mind that ALTER TABLE commands will be executed. If you have a large
17
+ dataset this may mean that this process takes a while.
18
+
19
+ Make a back-up first. This script has been tested, but the amount of
20
+ potential variants are extremely high, so it's impossible to deal with every
21
+ possible situation.
22
+
23
+ In the worst case, you will lose all your data. This is not an overstatement.
24
+
25
+ Lastly, if you are upgrading from an older version than 3.1, make sure you run
26
+ the earlier migration script first. Migration scripts must be ran in order.
27
+
28
+ Usage:
29
+
30
+ php {$argv[0]} [pdo-dsn] [username] [password]
31
+
32
+ For example:
33
+
34
+ php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
35
+ php {$argv[0]} sqlite:data/sabredav.db
36
+
37
+ HELLO;
38
+
39
+ exit();
40
+
41
+ }
42
+
43
+ // There's a bunch of places where the autoloader could be, so we'll try all of
44
+ // them.
45
+ $paths = [
46
+ __DIR__ . '/../vendor/autoload.php',
47
+ __DIR__ . '/../../../autoload.php',
48
+ ];
49
+
50
+ foreach ($paths as $path) {
51
+ if (file_exists($path)) {
52
+ include $path;
53
+ break;
54
+ }
55
+ }
56
+
57
+ $dsn = $argv[1];
58
+ $user = isset($argv[2]) ? $argv[2] : null;
59
+ $pass = isset($argv[3]) ? $argv[3] : null;
60
+
61
+ $backupPostfix = time();
62
+
63
+ echo "Connecting to database: " . $dsn . "\n";
64
+
65
+ $pdo = new PDO($dsn, $user, $pass);
66
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
67
+ $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
68
+
69
+ $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
70
+
71
+ switch ($driver) {
72
+
73
+ case 'mysql' :
74
+ echo "Detected MySQL.\n";
75
+ break;
76
+ case 'sqlite' :
77
+ echo "Detected SQLite.\n";
78
+ break;
79
+ default :
80
+ echo "Error: unsupported driver: " . $driver . "\n";
81
+ die(-1);
82
+ }
83
+
84
+ echo "Creating 'calendarinstances'\n";
85
+ $addValueType = false;
86
+ try {
87
+ $result = $pdo->query('SELECT * FROM calendarinstances LIMIT 1');
88
+ $result->fetch(\PDO::FETCH_ASSOC);
89
+ echo "calendarinstances exists. Assuming this part of the migration has already been done.\n";
90
+ } catch (Exception $e) {
91
+ echo "calendarinstances does not yet exist. Creating table and migrating data.\n";
92
+
93
+ switch ($driver) {
94
+ case 'mysql' :
95
+ $pdo->exec(<<<SQL
96
+ CREATE TABLE calendarinstances (
97
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
98
+ calendarid INTEGER UNSIGNED NOT NULL,
99
+ principaluri VARBINARY(100),
100
+ access TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1 = owner, 2 = read, 3 = readwrite',
101
+ displayname VARCHAR(100),
102
+ uri VARBINARY(200),
103
+ description TEXT,
104
+ calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
105
+ calendarcolor VARBINARY(10),
106
+ timezone TEXT,
107
+ transparent TINYINT(1) NOT NULL DEFAULT '0',
108
+ share_href VARBINARY(100),
109
+ share_displayname VARCHAR(100),
110
+ share_invitestatus TINYINT(1) NOT NULL DEFAULT '2' COMMENT '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid',
111
+ UNIQUE(principaluri, uri),
112
+ UNIQUE(calendarid, principaluri),
113
+ UNIQUE(calendarid, share_href)
114
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
115
+ SQL
116
+ );
117
+ $pdo->exec("
118
+ INSERT INTO calendarinstances
119
+ (
120
+ calendarid,
121
+ principaluri,
122
+ access,
123
+ displayname,
124
+ uri,
125
+ description,
126
+ calendarorder,
127
+ calendarcolor,
128
+ transparent
129
+ )
130
+ SELECT
131
+ id,
132
+ principaluri,
133
+ 1,
134
+ displayname,
135
+ uri,
136
+ description,
137
+ calendarorder,
138
+ calendarcolor,
139
+ transparent
140
+ FROM calendars
141
+ ");
142
+ break;
143
+ case 'sqlite' :
144
+ $pdo->exec(<<<SQL
145
+ CREATE TABLE calendarinstances (
146
+ id integer primary key asc NOT NULL,
147
+ calendarid integer,
148
+ principaluri text,
149
+ access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1',
150
+ displayname text,
151
+ uri text NOT NULL,
152
+ description text,
153
+ calendarorder integer,
154
+ calendarcolor text,
155
+ timezone text,
156
+ transparent bool,
157
+ share_href text,
158
+ share_displayname text,
159
+ share_invitestatus integer DEFAULT '2',
160
+ UNIQUE (principaluri, uri),
161
+ UNIQUE (calendarid, principaluri),
162
+ UNIQUE (calendarid, share_href)
163
+ );
164
+ SQL
165
+ );
166
+ $pdo->exec("
167
+ INSERT INTO calendarinstances
168
+ (
169
+ calendarid,
170
+ principaluri,
171
+ access,
172
+ displayname,
173
+ uri,
174
+ description,
175
+ calendarorder,
176
+ calendarcolor,
177
+ transparent
178
+ )
179
+ SELECT
180
+ id,
181
+ principaluri,
182
+ 1,
183
+ displayname,
184
+ uri,
185
+ description,
186
+ calendarorder,
187
+ calendarcolor,
188
+ transparent
189
+ FROM calendars
190
+ ");
191
+ break;
192
+ }
193
+
194
+ }
195
+ try {
196
+ $result = $pdo->query('SELECT * FROM calendars LIMIT 1');
197
+ $row = $result->fetch(\PDO::FETCH_ASSOC);
198
+
199
+ if (!$row) {
200
+ echo "Source table is empty.\n";
201
+ $migrateCalendars = true;
202
+ }
203
+
204
+ $columnCount = count($row);
205
+ if ($columnCount === 3) {
206
+ echo "The calendars table has 3 columns already. Assuming this part of the migration was already done.\n";
207
+ $migrateCalendars = false;
208
+ } else {
209
+ echo "The calendars table has " . $columnCount . " columns.\n";
210
+ $migrateCalendars = true;
211
+ }
212
+
213
+ } catch (Exception $e) {
214
+ echo "calendars table does not exist. This is a major problem. Exiting.\n";
215
+ exit(-1);
216
+ }
217
+
218
+ if ($migrateCalendars) {
219
+
220
+ $calendarBackup = 'calendars_3_1_' . $backupPostfix;
221
+ echo "Backing up 'calendars' to '", $calendarBackup, "'\n";
222
+
223
+ switch ($driver) {
224
+ case 'mysql' :
225
+ $pdo->exec('RENAME TABLE calendars TO ' . $calendarBackup);
226
+ break;
227
+ case 'sqlite' :
228
+ $pdo->exec('ALTER TABLE calendars RENAME TO ' . $calendarBackup);
229
+ break;
230
+
231
+ }
232
+
233
+ echo "Creating new calendars table.\n";
234
+ switch ($driver) {
235
+ case 'mysql' :
236
+ $pdo->exec(<<<SQL
237
+ CREATE TABLE calendars (
238
+ id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
239
+ synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
240
+ components VARBINARY(21)
241
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
242
+ SQL
243
+ );
244
+ break;
245
+ case 'sqlite' :
246
+ $pdo->exec(<<<SQL
247
+ CREATE TABLE calendars (
248
+ id integer primary key asc NOT NULL,
249
+ synctoken integer DEFAULT 1 NOT NULL,
250
+ components text NOT NULL
251
+ );
252
+ SQL
253
+ );
254
+ break;
255
+
256
+ }
257
+
258
+ echo "Migrating data from old to new table\n";
259
+
260
+ $pdo->exec(<<<SQL
261
+ INSERT INTO calendars (id, synctoken, components) SELECT id, synctoken, COALESCE(components,"VEVENT,VTODO,VJOURNAL") as components FROM $calendarBackup
262
+ SQL
263
+ );
264
+
265
+ }
266
+
267
+
268
+ echo "Upgrade to 3.2 schema completed.\n";
vendor/sabre/dav/bin/naturalselection ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ #
4
+ # Copyright (c) 2009-2010 Evert Pot
5
+ # All rights reserved.
6
+ # http://www.rooftopsolutions.nl/
7
+ #
8
+ # This utility is distributed along with SabreDAV
9
+ # license: http://sabre.io/license/ Modified BSD License
10
+
11
+ import os
12
+ from optparse import OptionParser
13
+ import time
14
+
15
+ def getfreespace(path):
16
+ stat = os.statvfs(path)
17
+ return stat.f_frsize * stat.f_bavail
18
+
19
+ def getbytesleft(path,threshold):
20
+ return getfreespace(path)-threshold
21
+
22
+ def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0):
23
+
24
+ bytes = getbytesleft(cacheDir,threshold)
25
+ if (bytes>0):
26
+ print "Bytes to go before we hit threshold:", bytes
27
+ else:
28
+ print "Threshold exceeded with:", -bytes, "bytes"
29
+ dir = os.listdir(cacheDir)
30
+ dir2 = []
31
+ for file in dir:
32
+ path = cacheDir + '/' + file
33
+ dir2.append({
34
+ "path" : path,
35
+ "atime": os.stat(path).st_atime,
36
+ "size" : os.stat(path).st_size
37
+ })
38
+
39
+ dir2.sort(lambda x,y: int(x["atime"]-y["atime"]))
40
+
41
+ filesunlinked = 0
42
+ gainedspace = 0
43
+
44
+ # Left is the amount of bytes that need to be freed up
45
+ # The default is the 'min_erase setting'
46
+ left = min_erase
47
+
48
+ # If the min_erase setting is lower than the amount of bytes over
49
+ # the threshold, we use that number instead.
50
+ if left < -bytes :
51
+ left = -bytes
52
+
53
+ print "Need to delete at least:", left;
54
+
55
+ for file in dir2:
56
+
57
+ # Only deleting files if we're not simulating
58
+ if not simulate: os.unlink(file["path"])
59
+ left = int(left - file["size"])
60
+ gainedspace = gainedspace + file["size"]
61
+ filesunlinked = filesunlinked + 1
62
+
63
+ if(left<0):
64
+ break
65
+
66
+ print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace)
67
+
68
+
69
+ time.sleep(sleep)
70
+
71
+
72
+
73
+ def main():
74
+ parser = OptionParser(
75
+ version="naturalselection v0.3",
76
+ description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" +
77
+ "This utility is distributed alongside SabreDAV.",
78
+ usage="usage: %prog [options] cacheDirectory",
79
+ )
80
+ parser.add_option(
81
+ '-s',
82
+ dest="simulate",
83
+ action="store_true",
84
+ help="Don't actually make changes, but just simulate the behaviour",
85
+ )
86
+ parser.add_option(
87
+ '-r','--runs',
88
+ help="How many times to check before exiting. -1 is infinite, which is the default",
89
+ type="int",
90
+ dest="runs",
91
+ default=-1
92
+ )
93
+ parser.add_option(
94
+ '-n','--interval',
95
+ help="Sleep time in seconds (default = 5)",
96
+ type="int",
97
+ dest="sleep",
98
+ default=5
99
+ )
100
+ parser.add_option(
101
+ '-l','--threshold',
102
+ help="Threshold in bytes (default = 10737418240, which is 10GB)",
103
+ type="int",
104
+ dest="threshold",
105
+ default=10737418240
106
+ )
107
+ parser.add_option(
108
+ '-m', '--min-erase',
109
+ help="Minimum number of bytes to erase when the threshold is reached. " +
110
+ "Setting this option higher will reduce the number of times the cache directory will need to be scanned. " +
111
+ "(the default is 1073741824, which is 1GB.)",
112
+ type="int",
113
+ dest="min_erase",
114
+ default=1073741824
115
+ )
116
+
117
+ options,args = parser.parse_args()
118
+ if len(args)<1:
119
+ parser.error("This utility requires at least 1 argument")
120
+ cacheDir = args[0]
121
+
122
+ print "Natural Selection"
123
+ print "Cache directory:", cacheDir
124
+ free = getfreespace(cacheDir);
125
+ print "Current free disk space:", free
126
+
127
+ runs = options.runs;
128
+ while runs!=0 :
129
+ run(
130
+ cacheDir,
131
+ sleep=options.sleep,
132
+ simulate=options.simulate,
133
+ threshold=options.threshold,
134
+ min_erase=options.min_erase
135
+ )
136
+ if runs>0:
137
+ runs = runs - 1
138
+
139
+ if __name__ == '__main__' :
140
+ main()
vendor/sabre/dav/bin/sabredav ADDED
@@ -0,0 +1,2 @@
 
 
1
+ #!/bin/sh
2
+ php -S 0.0.0.0:8080 `dirname $0`/sabredav.php
vendor/sabre/dav/bin/sabredav.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // SabreDAV test server.
4
+
5
+ class CliLog {
6
+
7
+ protected $stream;
8
+
9
+ function __construct() {
10
+
11
+ $this->stream = fopen('php://stdout', 'w');
12
+
13
+ }
14
+
15
+ function log($msg) {
16
+ fwrite($this->stream, $msg . "\n");
17
+ }
18
+
19
+ }
20
+
21
+ $log = new CliLog();
22
+
23
+ if (php_sapi_name() !== 'cli-server') {
24
+ die("This script is intended to run on the built-in php webserver");
25
+ }
26
+
27
+ // Finding composer
28
+
29
+
30
+ $paths = [
31
+ __DIR__ . '/../vendor/autoload.php',
32
+ __DIR__ . '/../../../autoload.php',
33
+ ];
34
+
35
+ foreach ($paths as $path) {
36
+ if (file_exists($path)) {
37
+ include $path;
38
+ break;
39
+ }
40
+ }
41
+
42
+ use Sabre\DAV;
43
+
44
+ // Root
45
+ $root = new DAV\FS\Directory(getcwd());
46
+
47
+ // Setting up server.
48
+ $server = new DAV\Server($root);
49
+
50
+ // Browser plugin
51
+ $server->addPlugin(new DAV\Browser\Plugin());
52
+
53
+ $server->exec();
vendor/sabre/dav/composer.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sabre/dav",
3
+ "type": "library",
4
+ "description": "WebDAV Framework for PHP",
5
+ "keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"],
6
+ "homepage": "http://sabre.io/",
7
+ "license" : "BSD-3-Clause",
8
+ "authors": [
9
+ {
10
+ "name": "Evert Pot",
11
+ "email": "me@evertpot.com",
12
+ "homepage" : "http://evertpot.com/",
13
+ "role" : "Developer"
14
+ }
15
+ ],
16
+ "require": {
17
+ "php": ">=5.5.0",
18
+ "sabre/vobject": "^4.1.0",
19
+ "sabre/event" : ">=2.0.0, <4.0.0",
20
+ "sabre/xml" : "^1.4.0",
21
+ "sabre/http" : "^4.2.1",
22
+ "sabre/uri" : "^1.0.1",
23
+ "ext-dom": "*",
24
+ "ext-pcre": "*",
25
+ "ext-spl": "*",
26
+ "ext-simplexml": "*",
27
+ "ext-mbstring" : "*",
28
+ "ext-ctype" : "*",
29
+ "ext-date" : "*",
30
+ "ext-iconv" : "*",
31
+ "lib-libxml" : ">=2.7.0",
32
+ "psr/log": "^1.0"
33
+ },
34
+ "require-dev" : {
35
+ "phpunit/phpunit" : "> 4.8, <6.0.0",
36
+ "evert/phpdoc-md" : "~0.1.0",
37
+ "sabre/cs" : "^1.0.0",
38
+ "monolog/monolog": "^1.18"
39
+ },
40
+ "suggest" : {
41
+ "ext-curl" : "*",
42
+ "ext-pdo" : "*"
43
+ },
44
+ "autoload": {
45
+ "psr-4" : {
46
+ "Sabre\\DAV\\" : "lib/DAV/",
47
+ "Sabre\\DAVACL\\" : "lib/DAVACL/",
48
+ "Sabre\\CalDAV\\" : "lib/CalDAV/",
49
+ "Sabre\\CardDAV\\" : "lib/CardDAV/"
50
+ }
51
+ },
52
+ "support" : {
53
+ "forum" : "https://groups.google.com/group/sabredav-discuss",
54
+ "source" : "https://github.com/fruux/sabre-dav"
55
+ },
56
+ "bin" : [
57
+ "bin/sabredav",
58
+ "bin/naturalselection"
59
+ ],
60
+ "config" : {
61
+ "bin-dir" : "./bin"
62
+ },
63
+ "extra" : {
64
+ "branch-alias": {
65
+ "dev-master": "3.1.0-dev"
66
+ }
67
+ }
68
+ }
vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\VObject;
7
+
8
+ /**
9
+ * Abstract Calendaring backend. Extend this class to create your own backends.
10
+ *
11
+ * Checkout the BackendInterface for all the methods that must be implemented.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ abstract class AbstractBackend implements BackendInterface {
18
+
19
+ /**
20
+ * Updates properties for a calendar.
21
+ *
22
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
23
+ * To do the actual updates, you must tell this object which properties
24
+ * you're going to process with the handle() method.
25
+ *
26
+ * Calling the handle method is like telling the PropPatch object "I
27
+ * promise I can handle updating this property".
28
+ *
29
+ * Read the PropPatch documentation for more info and examples.
30
+ *
31
+ * @param mixed $calendarId
32
+ * @param \Sabre\DAV\PropPatch $propPatch
33
+ * @return void
34
+ */
35
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
36
+
37
+ }
38
+
39
+ /**
40
+ * Returns a list of calendar objects.
41
+ *
42
+ * This method should work identical to getCalendarObject, but instead
43
+ * return all the calendar objects in the list as an array.
44
+ *
45
+ * If the backend supports this, it may allow for some speed-ups.
46
+ *
47
+ * @param mixed $calendarId
48
+ * @param array $uris
49
+ * @return array
50
+ */
51
+ function getMultipleCalendarObjects($calendarId, array $uris) {
52
+
53
+ return array_map(function($uri) use ($calendarId) {
54
+ return $this->getCalendarObject($calendarId, $uri);
55
+ }, $uris);
56
+
57
+ }
58
+
59
+ /**
60
+ * Performs a calendar-query on the contents of this calendar.
61
+ *
62
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
63
+ * calendar-query it is possible for a client to request a specific set of
64
+ * object, based on contents of iCalendar properties, date-ranges and
65
+ * iCalendar component types (VTODO, VEVENT).
66
+ *
67
+ * This method should just return a list of (relative) urls that match this
68
+ * query.
69
+ *
70
+ * The list of filters are specified as an array. The exact array is
71
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
72
+ *
73
+ * Note that it is extremely likely that getCalendarObject for every path
74
+ * returned from this method will be called almost immediately after. You
75
+ * may want to anticipate this to speed up these requests.
76
+ *
77
+ * This method provides a default implementation, which parses *all* the
78
+ * iCalendar objects in the specified calendar.
79
+ *
80
+ * This default may well be good enough for personal use, and calendars
81
+ * that aren't very large. But if you anticipate high usage, big calendars
82
+ * or high loads, you are strongly adviced to optimize certain paths.
83
+ *
84
+ * The best way to do so is override this method and to optimize
85
+ * specifically for 'common filters'.
86
+ *
87
+ * Requests that are extremely common are:
88
+ * * requests for just VEVENTS
89
+ * * requests for just VTODO
90
+ * * requests with a time-range-filter on either VEVENT or VTODO.
91
+ *
92
+ * ..and combinations of these requests. It may not be worth it to try to
93
+ * handle every possible situation and just rely on the (relatively
94
+ * easy to use) CalendarQueryValidator to handle the rest.
95
+ *
96
+ * Note that especially time-range-filters may be difficult to parse. A
97
+ * time-range filter specified on a VEVENT must for instance also handle
98
+ * recurrence rules correctly.
99
+ * A good example of how to interprete all these filters can also simply
100
+ * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
101
+ * as possible, so it gives you a good idea on what type of stuff you need
102
+ * to think of.
103
+ *
104
+ * @param mixed $calendarId
105
+ * @param array $filters
106
+ * @return array
107
+ */
108
+ function calendarQuery($calendarId, array $filters) {
109
+
110
+ $result = [];
111
+ $objects = $this->getCalendarObjects($calendarId);
112
+
113
+ foreach ($objects as $object) {
114
+
115
+ if ($this->validateFilterForObject($object, $filters)) {
116
+ $result[] = $object['uri'];
117
+ }
118
+
119
+ }
120
+
121
+ return $result;
122
+
123
+ }
124
+
125
+ /**
126
+ * This method validates if a filter (as passed to calendarQuery) matches
127
+ * the given object.
128
+ *
129
+ * @param array $object
130
+ * @param array $filters
131
+ * @return bool
132
+ */
133
+ protected function validateFilterForObject(array $object, array $filters) {
134
+
135
+ // Unfortunately, setting the 'calendardata' here is optional. If
136
+ // it was excluded, we actually need another call to get this as
137
+ // well.
138
+ if (!isset($object['calendardata'])) {
139
+ $object = $this->getCalendarObject($object['calendarid'], $object['uri']);
140
+ }
141
+
142
+ $vObject = VObject\Reader::read($object['calendardata']);
143
+
144
+ $validator = new CalDAV\CalendarQueryValidator();
145
+ $result = $validator->validate($vObject, $filters);
146
+
147
+ // Destroy circular references so PHP will GC the object.
148
+ $vObject->destroy();
149
+
150
+ return $result;
151
+
152
+ }
153
+
154
+ /**
155
+ * Searches through all of a users calendars and calendar objects to find
156
+ * an object with a specific UID.
157
+ *
158
+ * This method should return the path to this object, relative to the
159
+ * calendar home, so this path usually only contains two parts:
160
+ *
161
+ * calendarpath/objectpath.ics
162
+ *
163
+ * If the uid is not found, return null.
164
+ *
165
+ * This method should only consider * objects that the principal owns, so
166
+ * any calendars owned by other principals that also appear in this
167
+ * collection should be ignored.
168
+ *
169
+ * @param string $principalUri
170
+ * @param string $uid
171
+ * @return string|null
172
+ */
173
+ function getCalendarObjectByUID($principalUri, $uid) {
174
+
175
+ // Note: this is a super slow naive implementation of this method. You
176
+ // are highly recommended to optimize it, if your backend allows it.
177
+ foreach ($this->getCalendarsForUser($principalUri) as $calendar) {
178
+
179
+ // We must ignore calendars owned by other principals.
180
+ if ($calendar['principaluri'] !== $principalUri) {
181
+ continue;
182
+ }
183
+
184
+ // Ignore calendars that are shared.
185
+ if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) {
186
+ continue;
187
+ }
188
+
189
+ $results = $this->calendarQuery(
190
+ $calendar['id'],
191
+ [
192
+ 'name' => 'VCALENDAR',
193
+ 'prop-filters' => [],
194
+ 'comp-filters' => [
195
+ [
196
+ 'name' => 'VEVENT',
197
+ 'is-not-defined' => false,
198
+ 'time-range' => null,
199
+ 'comp-filters' => [],
200
+ 'prop-filters' => [
201
+ [
202
+ 'name' => 'UID',
203
+ 'is-not-defined' => false,
204
+ 'time-range' => null,
205
+ 'text-match' => [
206
+ 'value' => $uid,
207
+ 'negate-condition' => false,
208
+ 'collation' => 'i;octet',
209
+ ],
210
+ 'param-filters' => [],
211
+ ],
212
+ ]
213
+ ]
214
+ ],
215
+ ]
216
+ );
217
+ if ($results) {
218
+ // We have a match
219
+ return $calendar['uri'] . '/' . $results[0];
220
+ }
221
+
222
+ }
223
+
224
+ }
225
+
226
+ }
vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ /**
6
+ * Every CalDAV backend must at least implement this interface.
7
+ *
8
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
9
+ * @author Evert Pot (http://evertpot.com/)
10
+ * @license http://sabre.io/license/ Modified BSD License
11
+ */
12
+ interface BackendInterface {
13
+
14
+ /**
15
+ * Returns a list of calendars for a principal.
16
+ *
17
+ * Every project is an array with the following keys:
18
+ * * id, a unique id that will be used by other functions to modify the
19
+ * calendar. This can be the same as the uri or a database key.
20
+ * * uri, which is the basename of the uri with which the calendar is
21
+ * accessed.
22
+ * * principaluri. The owner of the calendar. Almost always the same as
23
+ * principalUri passed to this method.
24
+ *
25
+ * Furthermore it can contain webdav properties in clark notation. A very
26
+ * common one is '{DAV:}displayname'.
27
+ *
28
+ * Many clients also require:
29
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
30
+ * For this property, you can just return an instance of
31
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
32
+ *
33
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
34
+ * ACL will automatically be put in read-only mode.
35
+ *
36
+ * @param string $principalUri
37
+ * @return array
38
+ */
39
+ function getCalendarsForUser($principalUri);
40
+
41
+ /**
42
+ * Creates a new calendar for a principal.
43
+ *
44
+ * If the creation was a success, an id must be returned that can be used to
45
+ * reference this calendar in other methods, such as updateCalendar.
46
+ *
47
+ * The id can be any type, including ints, strings, objects or array.
48
+ *
49
+ * @param string $principalUri
50
+ * @param string $calendarUri
51
+ * @param array $properties
52
+ * @return mixed
53
+ */
54
+ function createCalendar($principalUri, $calendarUri, array $properties);
55
+
56
+ /**
57
+ * Updates properties for a calendar.
58
+ *
59
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
60
+ * To do the actual updates, you must tell this object which properties
61
+ * you're going to process with the handle() method.
62
+ *
63
+ * Calling the handle method is like telling the PropPatch object "I
64
+ * promise I can handle updating this property".
65
+ *
66
+ * Read the PropPatch documentation for more info and examples.
67
+ *
68
+ * @param mixed $calendarId
69
+ * @param \Sabre\DAV\PropPatch $propPatch
70
+ * @return void
71
+ */
72
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
73
+
74
+ /**
75
+ * Delete a calendar and all its objects
76
+ *
77
+ * @param mixed $calendarId
78
+ * @return void
79
+ */
80
+ function deleteCalendar($calendarId);
81
+
82
+ /**
83
+ * Returns all calendar objects within a calendar.
84
+ *
85
+ * Every item contains an array with the following keys:
86
+ * * calendardata - The iCalendar-compatible calendar data
87
+ * * uri - a unique key which will be used to construct the uri. This can
88
+ * be any arbitrary string, but making sure it ends with '.ics' is a
89
+ * good idea. This is only the basename, or filename, not the full
90
+ * path.
91
+ * * lastmodified - a timestamp of the last modification time
92
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
93
+ * '"abcdef"')
94
+ * * size - The size of the calendar objects, in bytes.
95
+ * * component - optional, a string containing the type of object, such
96
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
97
+ * the Content-Type header.
98
+ *
99
+ * Note that the etag is optional, but it's highly encouraged to return for
100
+ * speed reasons.
101
+ *
102
+ * The calendardata is also optional. If it's not returned
103
+ * 'getCalendarObject' will be called later, which *is* expected to return
104
+ * calendardata.
105
+ *
106
+ * If neither etag or size are specified, the calendardata will be
107
+ * used/fetched to determine these numbers. If both are specified the
108
+ * amount of times this is needed is reduced by a great degree.
109
+ *
110
+ * @param mixed $calendarId
111
+ * @return array
112
+ */
113
+ function getCalendarObjects($calendarId);
114
+
115
+ /**
116
+ * Returns information from a single calendar object, based on it's object
117
+ * uri.
118
+ *
119
+ * The object uri is only the basename, or filename and not a full path.
120
+ *
121
+ * The returned array must have the same keys as getCalendarObjects. The
122
+ * 'calendardata' object is required here though, while it's not required
123
+ * for getCalendarObjects.
124
+ *
125
+ * This method must return null if the object did not exist.
126
+ *
127
+ * @param mixed $calendarId
128
+ * @param string $objectUri
129
+ * @return array|null
130
+ */
131
+ function getCalendarObject($calendarId, $objectUri);
132
+
133
+ /**
134
+ * Returns a list of calendar objects.
135
+ *
136
+ * This method should work identical to getCalendarObject, but instead
137
+ * return all the calendar objects in the list as an array.
138
+ *
139
+ * If the backend supports this, it may allow for some speed-ups.
140
+ *
141
+ * @param mixed $calendarId
142
+ * @param array $uris
143
+ * @return array
144
+ */
145
+ function getMultipleCalendarObjects($calendarId, array $uris);
146
+
147
+ /**
148
+ * Creates a new calendar object.
149
+ *
150
+ * The object uri is only the basename, or filename and not a full path.
151
+ *
152
+ * It is possible to return an etag from this function, which will be used
153
+ * in the response to this PUT request. Note that the ETag must be
154
+ * surrounded by double-quotes.
155
+ *
156
+ * However, you should only really return this ETag if you don't mangle the
157
+ * calendar-data. If the result of a subsequent GET to this object is not
158
+ * the exact same as this request body, you should omit the ETag.
159
+ *
160
+ * @param mixed $calendarId
161
+ * @param string $objectUri
162
+ * @param string $calendarData
163
+ * @return string|null
164
+ */
165
+ function createCalendarObject($calendarId, $objectUri, $calendarData);
166
+
167
+ /**
168
+ * Updates an existing calendarobject, based on it's uri.
169
+ *
170
+ * The object uri is only the basename, or filename and not a full path.
171
+ *
172
+ * It is possible return an etag from this function, which will be used in
173
+ * the response to this PUT request. Note that the ETag must be surrounded
174
+ * by double-quotes.
175
+ *
176
+ * However, you should only really return this ETag if you don't mangle the
177
+ * calendar-data. If the result of a subsequent GET to this object is not
178
+ * the exact same as this request body, you should omit the ETag.
179
+ *
180
+ * @param mixed $calendarId
181
+ * @param string $objectUri
182
+ * @param string $calendarData
183
+ * @return string|null
184
+ */
185
+ function updateCalendarObject($calendarId, $objectUri, $calendarData);
186
+
187
+ /**
188
+ * Deletes an existing calendar object.
189
+ *
190
+ * The object uri is only the basename, or filename and not a full path.
191
+ *
192
+ * @param mixed $calendarId
193
+ * @param string $objectUri
194
+ * @return void
195
+ */
196
+ function deleteCalendarObject($calendarId, $objectUri);
197
+
198
+ /**
199
+ * Performs a calendar-query on the contents of this calendar.
200
+ *
201
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
202
+ * calendar-query it is possible for a client to request a specific set of
203
+ * object, based on contents of iCalendar properties, date-ranges and
204
+ * iCalendar component types (VTODO, VEVENT).
205
+ *
206
+ * This method should just return a list of (relative) urls that match this
207
+ * query.
208
+ *
209
+ * The list of filters are specified as an array. The exact array is
210
+ * documented by Sabre\CalDAV\CalendarQueryParser.
211
+ *
212
+ * Note that it is extremely likely that getCalendarObject for every path
213
+ * returned from this method will be called almost immediately after. You
214
+ * may want to anticipate this to speed up these requests.
215
+ *
216
+ * This method provides a default implementation, which parses *all* the
217
+ * iCalendar objects in the specified calendar.
218
+ *
219
+ * This default may well be good enough for personal use, and calendars
220
+ * that aren't very large. But if you anticipate high usage, big calendars
221
+ * or high loads, you are strongly adviced to optimize certain paths.
222
+ *
223
+ * The best way to do so is override this method and to optimize
224
+ * specifically for 'common filters'.
225
+ *
226
+ * Requests that are extremely common are:
227
+ * * requests for just VEVENTS
228
+ * * requests for just VTODO
229
+ * * requests with a time-range-filter on either VEVENT or VTODO.
230
+ *
231
+ * ..and combinations of these requests. It may not be worth it to try to
232
+ * handle every possible situation and just rely on the (relatively
233
+ * easy to use) CalendarQueryValidator to handle the rest.
234
+ *
235
+ * Note that especially time-range-filters may be difficult to parse. A
236
+ * time-range filter specified on a VEVENT must for instance also handle
237
+ * recurrence rules correctly.
238
+ * A good example of how to interprete all these filters can also simply
239
+ * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
240
+ * as possible, so it gives you a good idea on what type of stuff you need
241
+ * to think of.
242
+ *
243
+ * @param mixed $calendarId
244
+ * @param array $filters
245
+ * @return array
246
+ */
247
+ function calendarQuery($calendarId, array $filters);
248
+
249
+ /**
250
+ * Searches through all of a users calendars and calendar objects to find
251
+ * an object with a specific UID.
252
+ *
253
+ * This method should return the path to this object, relative to the
254
+ * calendar home, so this path usually only contains two parts:
255
+ *
256
+ * calendarpath/objectpath.ics
257
+ *
258
+ * If the uid is not found, return null.
259
+ *
260
+ * This method should only consider * objects that the principal owns, so
261
+ * any calendars owned by other principals that also appear in this
262
+ * collection should be ignored.
263
+ *
264
+ * @param string $principalUri
265
+ * @param string $uid
266
+ * @return string|null
267
+ */
268
+ function getCalendarObjectByUID($principalUri, $uid);
269
+
270
+ }
vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ use Sabre\CalDAV\Xml\Notification\NotificationInterface;
6
+
7
+ /**
8
+ * Adds caldav notification support to a backend.
9
+ *
10
+ * Note: This feature is experimental, and may change in between different
11
+ * SabreDAV versions.
12
+ *
13
+ * Notifications are defined at:
14
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
15
+ *
16
+ * These notifications are basically a list of server-generated notifications
17
+ * displayed to the user. Users can dismiss notifications by deleting them.
18
+ *
19
+ * The primary usecase is to allow for calendar-sharing.
20
+ *
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://evertpot.com/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ interface NotificationSupport extends BackendInterface {
26
+
27
+ /**
28
+ * Returns a list of notifications for a given principal url.
29
+ *
30
+ * @param string $principalUri
31
+ * @return NotificationInterface[]
32
+ */
33
+ function getNotificationsForPrincipal($principalUri);
34
+
35
+ /**
36
+ * This deletes a specific notifcation.
37
+ *
38
+ * This may be called by a client once it deems a notification handled.
39
+ *
40
+ * @param string $principalUri
41
+ * @param NotificationInterface $notification
42
+ * @return void
43
+ */
44
+ function deleteNotification($principalUri, NotificationInterface $notification);
45
+
46
+ /**
47
+ * This method is called when a user replied to a request to share.
48
+ *
49
+ * If the user chose to accept the share, this method should return the
50
+ * newly created calendar url.
51
+ *
52
+ * @param string $href The sharee who is replying (often a mailto: address)
53
+ * @param int $status One of the SharingPlugin::STATUS_* constants
54
+ * @param string $calendarUri The url to the calendar thats being shared
55
+ * @param string $inReplyTo The unique id this message is a response to
56
+ * @param string $summary A description of the reply
57
+ * @return null|string
58
+ */
59
+ function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
60
+
61
+ }
vendor/sabre/dav/lib/CalDAV/Backend/PDO.php ADDED
@@ -0,0 +1,1511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\DAV;
7
+ use Sabre\DAV\Exception\Forbidden;
8
+ use Sabre\DAV\Xml\Element\Sharee;
9
+ use Sabre\VObject;
10
+
11
+ /**
12
+ * PDO CalDAV backend
13
+ *
14
+ * This backend is used to store calendar-data in a PDO database, such as
15
+ * sqlite or MySQL
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ class PDO extends AbstractBackend
22
+ implements
23
+ SyncSupport,
24
+ SubscriptionSupport,
25
+ SchedulingSupport,
26
+ SharingSupport {
27
+
28
+ /**
29
+ * We need to specify a max date, because we need to stop *somewhere*
30
+ *
31
+ * On 32 bit system the maximum for a signed integer is 2147483647, so
32
+ * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
33
+ * in 2038-01-19 to avoid problems when the date is converted
34
+ * to a unix timestamp.
35
+ */
36
+ const MAX_DATE = '2038-01-01';
37
+
38
+ /**
39
+ * pdo
40
+ *
41
+ * @var \PDO
42
+ */
43
+ protected $pdo;
44
+
45
+ /**
46
+ * The table name that will be used for calendars
47
+ *
48
+ * @var string
49
+ */
50
+ public $calendarTableName = 'calendars';
51
+
52
+ /**
53
+ * The table name that will be used for calendars instances.
54
+ *
55
+ * A single calendar can have multiple instances, if the calendar is
56
+ * shared.
57
+ *
58
+ * @var string
59
+ */
60
+ public $calendarInstancesTableName = 'calendarinstances';
61
+
62
+ /**
63
+ * The table name that will be used for calendar objects
64
+ *
65
+ * @var string
66
+ */
67
+ public $calendarObjectTableName = 'calendarobjects';
68
+
69
+ /**
70
+ * The table name that will be used for tracking changes in calendars.
71
+ *
72
+ * @var string
73
+ */
74
+ public $calendarChangesTableName = 'calendarchanges';
75
+
76
+ /**
77
+ * The table name that will be used inbox items.
78
+ *
79
+ * @var string
80
+ */
81
+ public $schedulingObjectTableName = 'schedulingobjects';
82
+
83
+ /**
84
+ * The table name that will be used for calendar subscriptions.
85
+ *
86
+ * @var string
87
+ */
88
+ public $calendarSubscriptionsTableName = 'calendarsubscriptions';
89
+
90
+ /**
91
+ * List of CalDAV properties, and how they map to database fieldnames
92
+ * Add your own properties by simply adding on to this array.
93
+ *
94
+ * Note that only string-based properties are supported here.
95
+ *
96
+ * @var array
97
+ */
98
+ public $propertyMap = [
99
+ '{DAV:}displayname' => 'displayname',
100
+ '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
101
+ '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
102
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
103
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
104
+ ];
105
+
106
+ /**
107
+ * List of subscription properties, and how they map to database fieldnames.
108
+ *
109
+ * @var array
110
+ */
111
+ public $subscriptionPropertyMap = [
112
+ '{DAV:}displayname' => 'displayname',
113
+ '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
114
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
115
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
116
+ '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
117
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
118
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
119
+ ];
120
+
121
+ /**
122
+ * Creates the backend
123
+ *
124
+ * @param \PDO $pdo
125
+ */
126
+ function __construct(\PDO $pdo) {
127
+
128
+ $this->pdo = $pdo;
129
+
130
+ }
131
+
132
+ /**
133
+ * Returns a list of calendars for a principal.
134
+ *
135
+ * Every project is an array with the following keys:
136
+ * * id, a unique id that will be used by other functions to modify the
137
+ * calendar. This can be the same as the uri or a database key.
138
+ * * uri. This is just the 'base uri' or 'filename' of the calendar.
139
+ * * principaluri. The owner of the calendar. Almost always the same as
140
+ * principalUri passed to this method.
141
+ *
142
+ * Furthermore it can contain webdav properties in clark notation. A very
143
+ * common one is '{DAV:}displayname'.
144
+ *
145
+ * Many clients also require:
146
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
147
+ * For this property, you can just return an instance of
148
+ * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
149
+ *
150
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
151
+ * ACL will automatically be put in read-only mode.
152
+ *
153
+ * @param string $principalUri
154
+ * @return array
155
+ */
156
+ function getCalendarsForUser($principalUri) {
157
+
158
+ $fields = array_values($this->propertyMap);
159
+ $fields[] = 'calendarid';
160
+ $fields[] = 'uri';
161
+ $fields[] = 'synctoken';
162
+ $fields[] = 'components';
163
+ $fields[] = 'principaluri';
164
+ $fields[] = 'transparent';
165
+ $fields[] = 'access';
166
+
167
+ // Making fields a comma-delimited list
168
+ $fields = implode(', ', $fields);
169
+ $stmt = $this->pdo->prepare(<<<SQL
170
+ SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
171
+ LEFT JOIN {$this->calendarTableName} ON
172
+ {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
173
+ WHERE principaluri = ? ORDER BY calendarorder ASC
174
+ SQL
175
+ );
176
+ $stmt->execute([$principalUri]);
177
+
178
+ $calendars = [];
179
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
180
+
181
+ $components = [];
182
+ if ($row['components']) {
183
+ $components = explode(',', $row['components']);
184
+ }
185
+
186
+ $calendar = [
187
+ 'id' => [(int)$row['calendarid'], (int)$row['id']],
188
+ 'uri' => $row['uri'],
189
+ 'principaluri' => $row['principaluri'],
190
+ '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
191
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
192
+ '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
193
+ '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
194
+ 'share-resource-uri' => '/ns/share/' . $row['calendarid'],
195
+ ];
196
+
197
+ $calendar['share-access'] = (int)$row['access'];
198
+ // 1 = owner, 2 = readonly, 3 = readwrite
199
+ if ($row['access'] > 1) {
200
+ // We need to find more information about the original owner.
201
+ //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
202
+ //$stmt2->execute([$row['id']]);
203
+
204
+ // read-only is for backwards compatbility. Might go away in
205
+ // the future.
206
+ $calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
207
+ }
208
+
209
+ foreach ($this->propertyMap as $xmlName => $dbName) {
210
+ $calendar[$xmlName] = $row[$dbName];
211
+ }
212
+
213
+ $calendars[] = $calendar;
214
+
215
+ }
216
+
217
+ return $calendars;
218
+
219
+ }
220
+
221
+ /**
222
+ * Creates a new calendar for a principal.
223
+ *
224
+ * If the creation was a success, an id must be returned that can be used
225
+ * to reference this calendar in other methods, such as updateCalendar.
226
+ *
227
+ * @param string $principalUri
228
+ * @param string $calendarUri
229
+ * @param array $properties
230
+ * @return string
231
+ */
232
+ function createCalendar($principalUri, $calendarUri, array $properties) {
233
+
234
+ $fieldNames = [
235
+ 'principaluri',
236
+ 'uri',
237
+ 'transparent',
238
+ 'calendarid',
239
+ ];
240
+ $values = [
241
+ ':principaluri' => $principalUri,
242
+ ':uri' => $calendarUri,
243
+ ':transparent' => 0,
244
+ ];
245
+
246
+
247
+ $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
248
+ if (!isset($properties[$sccs])) {
249
+ // Default value
250
+ $components = 'VEVENT,VTODO';
251
+ } else {
252
+ if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
253
+ throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
254
+ }
255
+ $components = implode(',', $properties[$sccs]->getValue());
256
+ }
257
+ $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
258
+ if (isset($properties[$transp])) {
259
+ $values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
260
+ }
261
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)");
262
+ $stmt->execute([$components]);
263
+
264
+ $calendarId = $this->pdo->lastInsertId(
265
+ $this->calendarTableName . '_id_seq'
266
+ );
267
+
268
+ $values[':calendarid'] = $calendarId;
269
+
270
+ foreach ($this->propertyMap as $xmlName => $dbName) {
271
+ if (isset($properties[$xmlName])) {
272
+
273
+ $values[':' . $dbName] = $properties[$xmlName];
274
+ $fieldNames[] = $dbName;
275
+ }
276
+ }
277
+
278
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
279
+
280
+ $stmt->execute($values);
281
+
282
+ return [
283
+ $calendarId,
284
+ $this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq')
285
+ ];
286
+
287
+ }
288
+
289
+ /**
290
+ * Updates properties for a calendar.
291
+ *
292
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
293
+ * To do the actual updates, you must tell this object which properties
294
+ * you're going to process with the handle() method.
295
+ *
296
+ * Calling the handle method is like telling the PropPatch object "I
297
+ * promise I can handle updating this property".
298
+ *
299
+ * Read the PropPatch documentation for more info and examples.
300
+ *
301
+ * @param mixed $calendarId
302
+ * @param \Sabre\DAV\PropPatch $propPatch
303
+ * @return void
304
+ */
305
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
306
+
307
+ if (!is_array($calendarId)) {
308
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
309
+ }
310
+ list($calendarId, $instanceId) = $calendarId;
311
+
312
+ $supportedProperties = array_keys($this->propertyMap);
313
+ $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
314
+
315
+ $propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) {
316
+ $newValues = [];
317
+ foreach ($mutations as $propertyName => $propertyValue) {
318
+
319
+ switch ($propertyName) {
320
+ case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
321
+ $fieldName = 'transparent';
322
+ $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
323
+ break;
324
+ default :
325
+ $fieldName = $this->propertyMap[$propertyName];
326
+ $newValues[$fieldName] = $propertyValue;
327
+ break;
328
+ }
329
+
330
+ }
331
+ $valuesSql = [];
332
+ foreach ($newValues as $fieldName => $value) {
333
+ $valuesSql[] = $fieldName . ' = ?';
334
+ }
335
+
336
+ $stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
337
+ $newValues['id'] = $instanceId;
338
+ $stmt->execute(array_values($newValues));
339
+
340
+ $this->addChange($calendarId, "", 2);
341
+
342
+ return true;
343
+
344
+ });
345
+
346
+ }
347
+
348
+ /**
349
+ * Delete a calendar and all it's objects
350
+ *
351
+ * @param mixed $calendarId
352
+ * @return void
353
+ */
354
+ function deleteCalendar($calendarId) {
355
+
356
+ if (!is_array($calendarId)) {
357
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
358
+ }
359
+ list($calendarId, $instanceId) = $calendarId;
360
+
361
+ $stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?');
362
+ $stmt->execute([$instanceId]);
363
+ $access = (int)$stmt->fetchColumn();
364
+
365
+ if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
366
+
367
+ /**
368
+ * If the user is the owner of the calendar, we delete all data and all
369
+ * instances.
370
+ **/
371
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
372
+ $stmt->execute([$calendarId]);
373
+
374
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
375
+ $stmt->execute([$calendarId]);
376
+
377
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?');
378
+ $stmt->execute([$calendarId]);
379
+
380
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
381
+ $stmt->execute([$calendarId]);
382
+
383
+ } else {
384
+
385
+ /**
386
+ * If it was an instance of a shared calendar, we only delete that
387
+ * instance.
388
+ */
389
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
390
+ $stmt->execute([$instanceId]);
391
+
392
+ }
393
+
394
+
395
+ }
396
+
397
+ /**
398
+ * Returns all calendar objects within a calendar.
399
+ *
400
+ * Every item contains an array with the following keys:
401
+ * * calendardata - The iCalendar-compatible calendar data
402
+ * * uri - a unique key which will be used to construct the uri. This can
403
+ * be any arbitrary string, but making sure it ends with '.ics' is a
404
+ * good idea. This is only the basename, or filename, not the full
405
+ * path.
406
+ * * lastmodified - a timestamp of the last modification time
407
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
408
+ * ' "abcdef"')
409
+ * * size - The size of the calendar objects, in bytes.
410
+ * * component - optional, a string containing the type of object, such
411
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
412
+ * the Content-Type header.
413
+ *
414
+ * Note that the etag is optional, but it's highly encouraged to return for
415
+ * speed reasons.
416
+ *
417
+ * The calendardata is also optional. If it's not returned
418
+ * 'getCalendarObject' will be called later, which *is* expected to return
419
+ * calendardata.
420
+ *
421
+ * If neither etag or size are specified, the calendardata will be
422
+ * used/fetched to determine these numbers. If both are specified the
423
+ * amount of times this is needed is reduced by a great degree.
424
+ *
425
+ * @param mixed $calendarId
426
+ * @return array
427
+ */
428
+ function getCalendarObjects($calendarId) {
429
+
430
+ if (!is_array($calendarId)) {
431
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
432
+ }
433
+ list($calendarId, $instanceId) = $calendarId;
434
+
435
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
436
+ $stmt->execute([$calendarId]);
437
+
438
+ $result = [];
439
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
440
+ $result[] = [
441
+ 'id' => $row['id'],
442
+ 'uri' => $row['uri'],
443
+ 'lastmodified' => (int)$row['lastmodified'],
444
+ 'etag' => '"' . $row['etag'] . '"',
445
+ 'size' => (int)$row['size'],
446
+ 'component' => strtolower($row['componenttype']),
447
+ ];
448
+ }
449
+
450
+ return $result;
451
+
452
+ }
453
+
454
+ /**
455
+ * Returns information from a single calendar object, based on it's object
456
+ * uri.
457
+ *
458
+ * The object uri is only the basename, or filename and not a full path.
459
+ *
460
+ * The returned array must have the same keys as getCalendarObjects. The
461
+ * 'calendardata' object is required here though, while it's not required
462
+ * for getCalendarObjects.
463
+ *
464
+ * This method must return null if the object did not exist.
465
+ *
466
+ * @param mixed $calendarId
467
+ * @param string $objectUri
468
+ * @return array|null
469
+ */
470
+ function getCalendarObject($calendarId, $objectUri) {
471
+
472
+ if (!is_array($calendarId)) {
473
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
474
+ }
475
+ list($calendarId, $instanceId) = $calendarId;
476
+
477
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
478
+ $stmt->execute([$calendarId, $objectUri]);
479
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
480
+
481
+ if (!$row) return null;
482
+
483
+ return [
484
+ 'id' => $row['id'],
485
+ 'uri' => $row['uri'],
486
+ 'lastmodified' => (int)$row['lastmodified'],
487
+ 'etag' => '"' . $row['etag'] . '"',
488
+ 'size' => (int)$row['size'],
489
+ 'calendardata' => $row['calendardata'],
490
+ 'component' => strtolower($row['componenttype']),
491
+ ];
492
+
493
+ }
494
+
495
+ /**
496
+ * Returns a list of calendar objects.
497
+ *
498
+ * This method should work identical to getCalendarObject, but instead
499
+ * return all the calendar objects in the list as an array.
500
+ *
501
+ * If the backend supports this, it may allow for some speed-ups.
502
+ *
503
+ * @param mixed $calendarId
504
+ * @param array $uris
505
+ * @return array
506
+ */
507
+ function getMultipleCalendarObjects($calendarId, array $uris) {
508
+
509
+ if (!is_array($calendarId)) {
510
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
511
+ }
512
+ list($calendarId, $instanceId) = $calendarId;
513
+
514
+ $result = [];
515
+ foreach (array_chunk($uris, 900) as $chunk) {
516
+ $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
517
+ // Inserting a whole bunch of question marks
518
+ $query .= implode(',', array_fill(0, count($chunk), '?'));
519
+ $query .= ')';
520
+
521
+ $stmt = $this->pdo->prepare($query);
522
+ $stmt->execute(array_merge([$calendarId], $chunk));
523
+
524
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
525
+
526
+ $result[] = [
527
+ 'id' => $row['id'],
528
+ 'uri' => $row['uri'],
529
+ 'lastmodified' => (int)$row['lastmodified'],
530
+ 'etag' => '"' . $row['etag'] . '"',
531
+ 'size' => (int)$row['size'],
532
+ 'calendardata' => $row['calendardata'],
533
+ 'component' => strtolower($row['componenttype']),
534
+ ];
535
+
536
+ }
537
+ }
538
+ return $result;
539
+
540
+ }
541
+
542
+
543
+ /**
544
+ * Creates a new calendar object.
545
+ *
546
+ * The object uri is only the basename, or filename and not a full path.
547
+ *
548
+ * It is possible return an etag from this function, which will be used in
549
+ * the response to this PUT request. Note that the ETag must be surrounded
550
+ * by double-quotes.
551
+ *
552
+ * However, you should only really return this ETag if you don't mangle the
553
+ * calendar-data. If the result of a subsequent GET to this object is not
554
+ * the exact same as this request body, you should omit the ETag.
555
+ *
556
+ * @param mixed $calendarId
557
+ * @param string $objectUri
558
+ * @param string $calendarData
559
+ * @return string|null
560
+ */
561
+ function createCalendarObject($calendarId, $objectUri, $calendarData) {
562
+
563
+ if (!is_array($calendarId)) {
564
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
565
+ }
566
+ list($calendarId, $instanceId) = $calendarId;
567
+
568
+ $extraData = $this->getDenormalizedData($calendarData);
569
+
570
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
571
+ $stmt->execute([
572
+ $calendarId,
573
+ $objectUri,
574
+ $calendarData,
575
+ time(),
576
+ $extraData['etag'],
577
+ $extraData['size'],
578
+ $extraData['componentType'],
579
+ $extraData['firstOccurence'],
580
+ $extraData['lastOccurence'],
581
+ $extraData['uid'],
582
+ ]);
583
+ $this->addChange($calendarId, $objectUri, 1);
584
+
585
+ return '"' . $extraData['etag'] . '"';
586
+
587
+ }
588
+
589
+ /**
590
+ * Updates an existing calendarobject, based on it's uri.
591
+ *
592
+ * The object uri is only the basename, or filename and not a full path.
593
+ *
594
+ * It is possible return an etag from this function, which will be used in
595
+ * the response to this PUT request. Note that the ETag must be surrounded
596
+ * by double-quotes.
597
+ *
598
+ * However, you should only really return this ETag if you don't mangle the
599
+ * calendar-data. If the result of a subsequent GET to this object is not
600
+ * the exact same as this request body, you should omit the ETag.
601
+ *
602
+ * @param mixed $calendarId
603
+ * @param string $objectUri
604
+ * @param string $calendarData
605
+ * @return string|null
606
+ */
607
+ function updateCalendarObject($calendarId, $objectUri, $calendarData) {
608
+
609
+ if (!is_array($calendarId)) {
610
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
611
+ }
612
+ list($calendarId, $instanceId) = $calendarId;
613
+
614
+ $extraData = $this->getDenormalizedData($calendarData);
615
+
616
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
617
+ $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
618
+
619
+ $this->addChange($calendarId, $objectUri, 2);
620
+
621
+ return '"' . $extraData['etag'] . '"';
622
+
623
+ }
624
+
625
+ /**
626
+ * Parses some information from calendar objects, used for optimized
627
+ * calendar-queries.
628
+ *
629
+ * Returns an array with the following keys:
630
+ * * etag - An md5 checksum of the object without the quotes.
631
+ * * size - Size of the object in bytes
632
+ * * componentType - VEVENT, VTODO or VJOURNAL
633
+ * * firstOccurence
634
+ * * lastOccurence
635
+ * * uid - value of the UID property
636
+ *
637
+ * @param string $calendarData
638
+ * @return array
639
+ */
640
+ protected function getDenormalizedData($calendarData) {
641
+
642
+ $vObject = VObject\Reader::read($calendarData);
643
+ $componentType = null;
644
+ $component = null;
645
+ $firstOccurence = null;
646
+ $lastOccurence = null;
647
+ $uid = null;
648
+ foreach ($vObject->getComponents() as $component) {
649
+ if ($component->name !== 'VTIMEZONE') {
650
+ $componentType = $component->name;
651
+ $uid = (string)$component->UID;
652
+ break;
653
+ }
654
+ }
655
+ if (!$componentType) {
656
+ throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
657
+ }
658
+ if ($componentType === 'VEVENT') {
659
+ $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
660
+ // Finding the last occurence is a bit harder
661
+ if (!isset($component->RRULE)) {
662
+ if (isset($component->DTEND)) {
663
+ $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
664
+ } elseif (isset($component->DURATION)) {
665
+ $endDate = clone $component->DTSTART->getDateTime();
666
+ $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
667
+ $lastOccurence = $endDate->getTimeStamp();
668
+ } elseif (!$component->DTSTART->hasTime()) {
669
+ $endDate = clone $component->DTSTART->getDateTime();
670
+ $endDate = $endDate->modify('+1 day');
671
+ $lastOccurence = $endDate->getTimeStamp();
672
+ } else {
673
+ $lastOccurence = $firstOccurence;
674
+ }
675
+ } else {
676
+ $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
677
+ $maxDate = new \DateTime(self::MAX_DATE);
678
+ if ($it->isInfinite()) {
679
+ $lastOccurence = $maxDate->getTimeStamp();
680
+ } else {
681
+ $end = $it->getDtEnd();
682
+ while ($it->valid() && $end < $maxDate) {
683
+ $end = $it->getDtEnd();
684
+ $it->next();
685
+
686
+ }
687
+ $lastOccurence = $end->getTimeStamp();
688
+ }
689
+
690
+ }
691
+
692
+ // Ensure Occurence values are positive
693
+ if ($firstOccurence < 0) $firstOccurence = 0;
694
+ if ($lastOccurence < 0) $lastOccurence = 0;
695
+ }
696
+
697
+ // Destroy circular references to PHP will GC the object.
698
+ $vObject->destroy();
699
+
700
+ return [
701
+ 'etag' => md5($calendarData),
702
+ 'size' => strlen($calendarData),
703
+ 'componentType' => $componentType,
704
+ 'firstOccurence' => $firstOccurence,
705
+ 'lastOccurence' => $lastOccurence,
706
+ 'uid' => $uid,
707
+ ];
708
+
709
+ }
710
+
711
+ /**
712
+ * Deletes an existing calendar object.
713
+ *
714
+ * The object uri is only the basename, or filename and not a full path.
715
+ *
716
+ * @param mixed $calendarId
717
+ * @param string $objectUri
718
+ * @return void
719
+ */
720
+ function deleteCalendarObject($calendarId, $objectUri) {
721
+
722
+ if (!is_array($calendarId)) {
723
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
724
+ }
725
+ list($calendarId, $instanceId) = $calendarId;
726
+
727
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
728
+ $stmt->execute([$calendarId, $objectUri]);
729
+
730
+ $this->addChange($calendarId, $objectUri, 3);
731
+
732
+ }
733
+
734
+ /**
735
+ * Performs a calendar-query on the contents of this calendar.
736
+ *
737
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
738
+ * calendar-query it is possible for a client to request a specific set of
739
+ * object, based on contents of iCalendar properties, date-ranges and
740
+ * iCalendar component types (VTODO, VEVENT).
741
+ *
742
+ * This method should just return a list of (relative) urls that match this
743
+ * query.
744
+ *
745
+ * The list of filters are specified as an array. The exact array is
746
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
747
+ *
748
+ * Note that it is extremely likely that getCalendarObject for every path
749
+ * returned from this method will be called almost immediately after. You
750
+ * may want to anticipate this to speed up these requests.
751
+ *
752
+ * This method provides a default implementation, which parses *all* the
753
+ * iCalendar objects in the specified calendar.
754
+ *
755
+ * This default may well be good enough for personal use, and calendars
756
+ * that aren't very large. But if you anticipate high usage, big calendars
757
+ * or high loads, you are strongly adviced to optimize certain paths.
758
+ *
759
+ * The best way to do so is override this method and to optimize
760
+ * specifically for 'common filters'.
761
+ *
762
+ * Requests that are extremely common are:
763
+ * * requests for just VEVENTS
764
+ * * requests for just VTODO
765
+ * * requests with a time-range-filter on a VEVENT.
766
+ *
767
+ * ..and combinations of these requests. It may not be worth it to try to
768
+ * handle every possible situation and just rely on the (relatively
769
+ * easy to use) CalendarQueryValidator to handle the rest.
770
+ *
771
+ * Note that especially time-range-filters may be difficult to parse. A
772
+ * time-range filter specified on a VEVENT must for instance also handle
773
+ * recurrence rules correctly.
774
+ * A good example of how to interpret all these filters can also simply
775
+ * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
776
+ * as possible, so it gives you a good idea on what type of stuff you need
777
+ * to think of.
778
+ *
779
+ * This specific implementation (for the PDO) backend optimizes filters on
780
+ * specific components, and VEVENT time-ranges.
781
+ *
782
+ * @param mixed $calendarId
783
+ * @param array $filters
784
+ * @return array
785
+ */
786
+ function calendarQuery($calendarId, array $filters) {
787
+
788
+ if (!is_array($calendarId)) {
789
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
790
+ }
791
+ list($calendarId, $instanceId) = $calendarId;
792
+
793
+ $componentType = null;
794
+ $requirePostFilter = true;
795
+ $timeRange = null;
796
+
797
+ // if no filters were specified, we don't need to filter after a query
798
+ if (!$filters['prop-filters'] && !$filters['comp-filters']) {
799
+ $requirePostFilter = false;
800
+ }
801
+
802
+ // Figuring out if there's a component filter
803
+ if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
804
+ $componentType = $filters['comp-filters'][0]['name'];
805
+
806
+ // Checking if we need post-filters
807
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
808
+ $requirePostFilter = false;
809
+ }
810
+ // There was a time-range filter
811
+ if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
812
+ $timeRange = $filters['comp-filters'][0]['time-range'];
813
+
814
+ // If start time OR the end time is not specified, we can do a
815
+ // 100% accurate mysql query.
816
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
817
+ $requirePostFilter = false;
818
+ }
819
+ }
820
+
821
+ }
822
+
823
+ if ($requirePostFilter) {
824
+ $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
825
+ } else {
826
+ $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
827
+ }
828
+
829
+ $values = [
830
+ 'calendarid' => $calendarId,
831
+ ];
832
+
833
+ if ($componentType) {
834
+ $query .= " AND componenttype = :componenttype";
835
+ $values['componenttype'] = $componentType;
836
+ }
837
+
838
+ if ($timeRange && $timeRange['start']) {
839
+ $query .= " AND lastoccurence > :startdate";
840
+ $values['startdate'] = $timeRange['start']->getTimeStamp();
841
+ }
842
+ if ($timeRange && $timeRange['end']) {
843
+ $query .= " AND firstoccurence < :enddate";
844
+ $values['enddate'] = $timeRange['end']->getTimeStamp();
845
+ }
846
+
847
+ $stmt = $this->pdo->prepare($query);
848
+ $stmt->execute($values);
849
+
850
+ $result = [];
851
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
852
+ if ($requirePostFilter) {
853
+ if (!$this->validateFilterForObject($row, $filters)) {
854
+ continue;
855
+ }
856
+ }
857
+ $result[] = $row['uri'];
858
+
859
+ }
860
+
861
+ return $result;
862
+
863
+ }
864
+
865
+ /**
866
+ * Searches through all of a users calendars and calendar objects to find
867
+ * an object with a specific UID.
868
+ *
869
+ * This method should return the path to this object, relative to the
870
+ * calendar home, so this path usually only contains two parts:
871
+ *
872
+ * calendarpath/objectpath.ics
873
+ *
874
+ * If the uid is not found, return null.
875
+ *
876
+ * This method should only consider * objects that the principal owns, so
877
+ * any calendars owned by other principals that also appear in this
878
+ * collection should be ignored.
879
+ *
880
+ * @param string $principalUri
881
+ * @param string $uid
882
+ * @return string|null
883
+ */
884
+ function getCalendarObjectByUID($principalUri, $uid) {
885
+
886
+ $query = <<<SQL
887
+ SELECT
888
+ calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
889
+ FROM
890
+ $this->calendarObjectTableName AS calendarobjects
891
+ LEFT JOIN
892
+ $this->calendarInstancesTableName AS calendar_instances
893
+ ON calendarobjects.calendarid = calendar_instances.calendarid
894
+ WHERE
895
+ calendar_instances.principaluri = ?
896
+ AND
897
+ calendarobjects.uid = ?
898
+ SQL;
899
+
900
+ $stmt = $this->pdo->prepare($query);
901
+ $stmt->execute([$principalUri, $uid]);
902
+
903
+ if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
904
+ return $row['calendaruri'] . '/' . $row['objecturi'];
905
+ }
906
+
907
+ }
908
+
909
+ /**
910
+ * The getChanges method returns all the changes that have happened, since
911
+ * the specified syncToken in the specified calendar.
912
+ *
913
+ * This function should return an array, such as the following:
914
+ *
915
+ * [
916
+ * 'syncToken' => 'The current synctoken',
917
+ * 'added' => [
918
+ * 'new.txt',
919
+ * ],
920
+ * 'modified' => [
921
+ * 'modified.txt',
922
+ * ],
923
+ * 'deleted' => [
924
+ * 'foo.php.bak',
925
+ * 'old.txt'
926
+ * ]
927
+ * ];
928
+ *
929
+ * The returned syncToken property should reflect the *current* syncToken
930
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
931
+ * property this is needed here too, to ensure the operation is atomic.
932
+ *
933
+ * If the $syncToken argument is specified as null, this is an initial
934
+ * sync, and all members should be reported.
935
+ *
936
+ * The modified property is an array of nodenames that have changed since
937
+ * the last token.
938
+ *
939
+ * The deleted property is an array with nodenames, that have been deleted
940
+ * from collection.
941
+ *
942
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
943
+ * 1, you only have to report changes that happened only directly in
944
+ * immediate descendants. If it's 2, it should also include changes from
945
+ * the nodes below the child collections. (grandchildren)
946
+ *
947
+ * The $limit argument allows a client to specify how many results should
948
+ * be returned at most. If the limit is not specified, it should be treated
949
+ * as infinite.
950
+ *
951
+ * If the limit (infinite or not) is higher than you're willing to return,
952
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
953
+ *
954
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
955
+ * return null.
956
+ *
957
+ * The limit is 'suggestive'. You are free to ignore it.
958
+ *
959
+ * @param mixed $calendarId
960
+ * @param string $syncToken
961
+ * @param int $syncLevel
962
+ * @param int $limit
963
+ * @return array
964
+ */
965
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
966
+
967
+ if (!is_array($calendarId)) {
968
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
969
+ }
970
+ list($calendarId, $instanceId) = $calendarId;
971
+
972
+ // Current synctoken
973
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
974
+ $stmt->execute([$calendarId]);
975
+ $currentToken = $stmt->fetchColumn(0);
976
+
977
+ if (is_null($currentToken)) return null;
978
+
979
+ $result = [
980
+ 'syncToken' => $currentToken,
981
+ 'added' => [],
982
+ 'modified' => [],
983
+ 'deleted' => [],
984
+ ];
985
+
986
+ if ($syncToken) {
987
+
988
+ $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
989
+ if ($limit > 0) $query .= " LIMIT " . (int)$limit;
990
+
991
+ // Fetching all changes
992
+ $stmt = $this->pdo->prepare($query);
993
+ $stmt->execute([$syncToken, $currentToken, $calendarId]);
994
+
995
+ $changes = [];
996
+
997
+ // This loop ensures that any duplicates are overwritten, only the
998
+ // last change on a node is relevant.
999
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1000
+
1001
+ $changes[$row['uri']] = $row['operation'];
1002
+
1003
+ }
1004
+
1005
+ foreach ($changes as $uri => $operation) {
1006
+
1007
+ switch ($operation) {
1008
+ case 1 :
1009
+ $result['added'][] = $uri;
1010
+ break;
1011
+ case 2 :
1012
+ $result['modified'][] = $uri;
1013
+ break;
1014
+ case 3 :
1015
+ $result['deleted'][] = $uri;
1016
+ break;
1017
+ }
1018
+
1019
+ }
1020
+ } else {
1021
+ // No synctoken supplied, this is the initial sync.
1022
+ $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?";
1023
+ $stmt = $this->pdo->prepare($query);
1024
+ $stmt->execute([$calendarId]);
1025
+
1026
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1027
+ }
1028
+ return $result;
1029
+
1030
+ }
1031
+
1032
+ /**
1033
+ * Adds a change record to the calendarchanges table.
1034
+ *
1035
+ * @param mixed $calendarId
1036
+ * @param string $objectUri
1037
+ * @param int $operation 1 = add, 2 = modify, 3 = delete.
1038
+ * @return void
1039
+ */
1040
+ protected function addChange($calendarId, $objectUri, $operation) {
1041
+
1042
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
1043
+ $stmt->execute([
1044
+ $objectUri,
1045
+ $calendarId,
1046
+ $operation,
1047
+ $calendarId
1048
+ ]);
1049
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
1050
+ $stmt->execute([
1051
+ $calendarId
1052
+ ]);
1053
+
1054
+ }
1055
+
1056
+ /**
1057
+ * Returns a list of subscriptions for a principal.
1058
+ *
1059
+ * Every subscription is an array with the following keys:
1060
+ * * id, a unique id that will be used by other functions to modify the
1061
+ * subscription. This can be the same as the uri or a database key.
1062
+ * * uri. This is just the 'base uri' or 'filename' of the subscription.
1063
+ * * principaluri. The owner of the subscription. Almost always the same as
1064
+ * principalUri passed to this method.
1065
+ * * source. Url to the actual feed
1066
+ *
1067
+ * Furthermore, all the subscription info must be returned too:
1068
+ *
1069
+ * 1. {DAV:}displayname
1070
+ * 2. {http://apple.com/ns/ical/}refreshrate
1071
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1072
+ * should not be stripped).
1073
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1074
+ * should not be stripped).
1075
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1076
+ * attachments should not be stripped).
1077
+ * 7. {http://apple.com/ns/ical/}calendar-color
1078
+ * 8. {http://apple.com/ns/ical/}calendar-order
1079
+ * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1080
+ * (should just be an instance of
1081
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1082
+ * default components).
1083
+ *
1084
+ * @param string $principalUri
1085
+ * @return array
1086
+ */
1087
+ function getSubscriptionsForUser($principalUri) {
1088
+
1089
+ $fields = array_values($this->subscriptionPropertyMap);
1090
+ $fields[] = 'id';
1091
+ $fields[] = 'uri';
1092
+ $fields[] = 'source';
1093
+ $fields[] = 'principaluri';
1094
+ $fields[] = 'lastmodified';
1095
+
1096
+ // Making fields a comma-delimited list
1097
+ $fields = implode(', ', $fields);
1098
+ $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
1099
+ $stmt->execute([$principalUri]);
1100
+
1101
+ $subscriptions = [];
1102
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1103
+
1104
+ $subscription = [
1105
+ 'id' => $row['id'],
1106
+ 'uri' => $row['uri'],
1107
+ 'principaluri' => $row['principaluri'],
1108
+ 'source' => $row['source'],
1109
+ 'lastmodified' => $row['lastmodified'],
1110
+
1111
+ '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1112
+ ];
1113
+
1114
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1115
+ if (!is_null($row[$dbName])) {
1116
+ $subscription[$xmlName] = $row[$dbName];
1117
+ }
1118
+ }
1119
+
1120
+ $subscriptions[] = $subscription;
1121
+
1122
+ }
1123
+
1124
+ return $subscriptions;
1125
+
1126
+ }
1127
+
1128
+ /**
1129
+ * Creates a new subscription for a principal.
1130
+ *
1131
+ * If the creation was a success, an id must be returned that can be used to reference
1132
+ * this subscription in other methods, such as updateSubscription.
1133
+ *
1134
+ * @param string $principalUri
1135
+ * @param string $uri
1136
+ * @param array $properties
1137
+ * @return mixed
1138
+ */
1139
+ function createSubscription($principalUri, $uri, array $properties) {
1140
+
1141
+ $fieldNames = [
1142
+ 'principaluri',
1143
+ 'uri',
1144
+ 'source',
1145
+ 'lastmodified',
1146
+ ];
1147
+
1148
+ if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1149
+ throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1150
+ }
1151
+
1152
+ $values = [
1153
+ ':principaluri' => $principalUri,
1154
+ ':uri' => $uri,
1155
+ ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1156
+ ':lastmodified' => time(),
1157
+ ];
1158
+
1159
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
1160
+ if (isset($properties[$xmlName])) {
1161
+
1162
+ $values[':' . $dbName] = $properties[$xmlName];
1163
+ $fieldNames[] = $dbName;
1164
+ }
1165
+ }
1166
+
1167
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
1168
+ $stmt->execute($values);
1169
+
1170
+ return $this->pdo->lastInsertId(
1171
+ $this->calendarSubscriptionsTableName . '_id_seq'
1172
+ );
1173
+
1174
+ }
1175
+
1176
+ /**
1177
+ * Updates a subscription
1178
+ *
1179
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1180
+ * To do the actual updates, you must tell this object which properties
1181
+ * you're going to process with the handle() method.
1182
+ *
1183
+ * Calling the handle method is like telling the PropPatch object "I
1184
+ * promise I can handle updating this property".
1185
+ *
1186
+ * Read the PropPatch documentation for more info and examples.
1187
+ *
1188
+ * @param mixed $subscriptionId
1189
+ * @param \Sabre\DAV\PropPatch $propPatch
1190
+ * @return void
1191
+ */
1192
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
1193
+
1194
+ $supportedProperties = array_keys($this->subscriptionPropertyMap);
1195
+ $supportedProperties[] = '{http://calendarserver.org/ns/}source';
1196
+
1197
+ $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1198
+
1199
+ $newValues = [];
1200
+
1201
+ foreach ($mutations as $propertyName => $propertyValue) {
1202
+
1203
+ if ($propertyName === '{http://calendarserver.org/ns/}source') {
1204
+ $newValues['source'] = $propertyValue->getHref();
1205
+ } else {
1206
+ $fieldName = $this->subscriptionPropertyMap[$propertyName];
1207
+ $newValues[$fieldName] = $propertyValue;
1208
+ }
1209
+
1210
+ }
1211
+
1212
+ // Now we're generating the sql query.
1213
+ $valuesSql = [];
1214
+ foreach ($newValues as $fieldName => $value) {
1215
+ $valuesSql[] = $fieldName . ' = ?';
1216
+ }
1217
+
1218
+ $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?");
1219
+ $newValues['lastmodified'] = time();
1220
+ $newValues['id'] = $subscriptionId;
1221
+ $stmt->execute(array_values($newValues));
1222
+
1223
+ return true;
1224
+
1225
+ });
1226
+
1227
+ }
1228
+
1229
+ /**
1230
+ * Deletes a subscription
1231
+ *
1232
+ * @param mixed $subscriptionId
1233
+ * @return void
1234
+ */
1235
+ function deleteSubscription($subscriptionId) {
1236
+
1237
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?');
1238
+ $stmt->execute([$subscriptionId]);
1239
+
1240
+ }
1241
+
1242
+ /**
1243
+ * Returns a single scheduling object.
1244
+ *
1245
+ * The returned array should contain the following elements:
1246
+ * * uri - A unique basename for the object. This will be used to
1247
+ * construct a full uri.
1248
+ * * calendardata - The iCalendar object
1249
+ * * lastmodified - The last modification date. Can be an int for a unix
1250
+ * timestamp, or a PHP DateTime object.
1251
+ * * etag - A unique token that must change if the object changed.
1252
+ * * size - The size of the object, in bytes.
1253
+ *
1254
+ * @param string $principalUri
1255
+ * @param string $objectUri
1256
+ * @return array
1257
+ */
1258
+ function getSchedulingObject($principalUri, $objectUri) {
1259
+
1260
+ $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
1261
+ $stmt->execute([$principalUri, $objectUri]);
1262
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
1263
+
1264
+ if (!$row) return null;
1265
+
1266
+ return [
1267
+ 'uri' => $row['uri'],
1268
+ 'calendardata' => $row['calendardata'],
1269
+ 'lastmodified' => $row['lastmodified'],
1270
+ 'etag' => '"' . $row['etag'] . '"',
1271
+ 'size' => (int)$row['size'],
1272
+ ];
1273
+
1274
+ }
1275
+
1276
+ /**
1277
+ * Returns all scheduling objects for the inbox collection.
1278
+ *
1279
+ * These objects should be returned as an array. Every item in the array
1280
+ * should follow the same structure as returned from getSchedulingObject.
1281
+ *
1282
+ * The main difference is that 'calendardata' is optional.
1283
+ *
1284
+ * @param string $principalUri
1285
+ * @return array
1286
+ */
1287
+ function getSchedulingObjects($principalUri) {
1288
+
1289
+ $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?');
1290
+ $stmt->execute([$principalUri]);
1291
+
1292
+ $result = [];
1293
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1294
+ $result[] = [
1295
+ 'calendardata' => $row['calendardata'],
1296
+ 'uri' => $row['uri'],
1297
+ 'lastmodified' => $row['lastmodified'],
1298
+ 'etag' => '"' . $row['etag'] . '"',
1299
+ 'size' => (int)$row['size'],
1300
+ ];
1301
+ }
1302
+
1303
+ return $result;
1304
+
1305
+ }
1306
+
1307
+ /**
1308
+ * Deletes a scheduling object
1309
+ *
1310
+ * @param string $principalUri
1311
+ * @param string $objectUri
1312
+ * @return void
1313
+ */
1314
+ function deleteSchedulingObject($principalUri, $objectUri) {
1315
+
1316
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
1317
+ $stmt->execute([$principalUri, $objectUri]);
1318
+
1319
+ }
1320
+
1321
+ /**
1322
+ * Creates a new scheduling object. This should land in a users' inbox.
1323
+ *
1324
+ * @param string $principalUri
1325
+ * @param string $objectUri
1326
+ * @param string $objectData
1327
+ * @return void
1328
+ */
1329
+ function createSchedulingObject($principalUri, $objectUri, $objectData) {
1330
+
1331
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
1332
+ $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]);
1333
+
1334
+ }
1335
+
1336
+ /**
1337
+ * Updates the list of shares.
1338
+ *
1339
+ * @param mixed $calendarId
1340
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
1341
+ * @return void
1342
+ */
1343
+ function updateInvites($calendarId, array $sharees) {
1344
+
1345
+ if (!is_array($calendarId)) {
1346
+ throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
1347
+ }
1348
+ $currentInvites = $this->getInvites($calendarId);
1349
+ list($calendarId, $instanceId) = $calendarId;
1350
+
1351
+ $removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)");
1352
+ $updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?");
1353
+
1354
+ $insertStmt = $this->pdo->prepare('
1355
+ INSERT INTO ' . $this->calendarInstancesTableName . '
1356
+ (
1357
+ calendarid,
1358
+ principaluri,
1359
+ access,
1360
+ displayname,
1361
+ uri,
1362
+ description,
1363
+ calendarorder,
1364
+ calendarcolor,
1365
+ timezone,
1366
+ transparent,
1367
+ share_href,
1368
+ share_displayname,
1369
+ share_invitestatus
1370
+ )
1371
+ SELECT
1372
+ ?,
1373
+ ?,
1374
+ ?,
1375
+ displayname,
1376
+ ?,
1377
+ description,
1378
+ calendarorder,
1379
+ calendarcolor,
1380
+ timezone,
1381
+ 1,
1382
+ ?,
1383
+ ?,
1384
+ ?
1385
+ FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
1386
+
1387
+ foreach ($sharees as $sharee) {
1388
+
1389
+ if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) {
1390
+ // if access was set no NOACCESS, it means access for an
1391
+ // existing sharee was removed.
1392
+ $removeStmt->execute([$calendarId, $sharee->href]);
1393
+ continue;
1394
+ }
1395
+
1396
+ if (is_null($sharee->principal)) {
1397
+ // If the server could not determine the principal automatically,
1398
+ // we will mark the invite status as invalid.
1399
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
1400
+ } else {
1401
+ // Because sabre/dav does not yet have an invitation system,
1402
+ // every invite is automatically accepted for now.
1403
+ $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
1404
+ }
1405
+
1406
+ foreach ($currentInvites as $oldSharee) {
1407
+
1408
+ if ($oldSharee->href === $sharee->href) {
1409
+ // This is an update
1410
+ $sharee->properties = array_merge(
1411
+ $oldSharee->properties,
1412
+ $sharee->properties
1413
+ );
1414
+ $updateStmt->execute([
1415
+ $sharee->access,
1416
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
1417
+ $sharee->inviteStatus ?: $oldSharee->inviteStatus,
1418
+ $calendarId,
1419
+ $sharee->href
1420
+ ]);
1421
+ continue 2;
1422
+ }
1423
+
1424
+ }
1425
+ // If we got here, it means it was a new sharee
1426
+ $insertStmt->execute([
1427
+ $calendarId,
1428
+ $sharee->principal,
1429
+ $sharee->access,
1430
+ \Sabre\DAV\UUIDUtil::getUUID(),
1431
+ $sharee->href,
1432
+ isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
1433
+ $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
1434
+ $instanceId
1435
+ ]);
1436
+
1437
+ }
1438
+
1439
+ }
1440
+
1441
+ /**
1442
+ * Returns the list of people whom a calendar is shared with.
1443
+ *
1444
+ * Every item in the returned list must be a Sharee object with at
1445
+ * least the following properties set:
1446
+ * $href
1447
+ * $shareAccess
1448
+ * $inviteStatus
1449
+ *
1450
+ * and optionally:
1451
+ * $properties
1452
+ *
1453
+ * @param mixed $calendarId
1454
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
1455
+ */
1456
+ function getInvites($calendarId) {
1457
+
1458
+ if (!is_array($calendarId)) {
1459
+ throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
1460
+ }
1461
+ list($calendarId, $instanceId) = $calendarId;
1462
+
1463
+ $query = <<<SQL
1464
+ SELECT
1465
+ principaluri,
1466
+ access,
1467
+ share_href,
1468
+ share_displayname,
1469
+ share_invitestatus
1470
+ FROM {$this->calendarInstancesTableName}
1471
+ WHERE
1472
+ calendarid = ?
1473
+ SQL;
1474
+
1475
+ $stmt = $this->pdo->prepare($query);
1476
+ $stmt->execute([$calendarId]);
1477
+
1478
+ $result = [];
1479
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1480
+
1481
+ $result[] = new Sharee([
1482
+ 'href' => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
1483
+ 'access' => (int)$row['access'],
1484
+ /// Everyone is always immediately accepted, for now.
1485
+ 'inviteStatus' => (int)$row['share_invitestatus'],
1486
+ 'properties' =>
1487
+ !empty($row['share_displayname'])
1488
+ ? ['{DAV:}displayname' => $row['share_displayname']]
1489
+ : [],
1490
+ 'principal' => $row['principaluri'],
1491
+ ]);
1492
+
1493
+ }
1494
+ return $result;
1495
+
1496
+ }
1497
+
1498
+ /**
1499
+ * Publishes a calendar
1500
+ *
1501
+ * @param mixed $calendarId
1502
+ * @param bool $value
1503
+ * @return void
1504
+ */
1505
+ function setPublishStatus($calendarId, $value) {
1506
+
1507
+ throw new DAV\Exception\NotImplemented('Not implemented');
1508
+
1509
+ }
1510
+
1511
+ }
vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ /**
6
+ * Implementing this interface adds CalDAV Scheduling support to your caldav
7
+ * server, as defined in rfc6638.
8
+ *
9
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
10
+ * @author Evert Pot (http://evertpot.com/)
11
+ * @license http://sabre.io/license/ Modified BSD License
12
+ */
13
+ interface SchedulingSupport extends BackendInterface {
14
+
15
+ /**
16
+ * Returns a single scheduling object for the inbox collection.
17
+ *
18
+ * The returned array should contain the following elements:
19
+ * * uri - A unique basename for the object. This will be used to
20
+ * construct a full uri.
21
+ * * calendardata - The iCalendar object
22
+ * * lastmodified - The last modification date. Can be an int for a unix
23
+ * timestamp, or a PHP DateTime object.
24
+ * * etag - A unique token that must change if the object changed.
25
+ * * size - The size of the object, in bytes.
26
+ *
27
+ * @param string $principalUri
28
+ * @param string $objectUri
29
+ * @return array
30
+ */
31
+ function getSchedulingObject($principalUri, $objectUri);
32
+
33
+ /**
34
+ * Returns all scheduling objects for the inbox collection.
35
+ *
36
+ * These objects should be returned as an array. Every item in the array
37
+ * should follow the same structure as returned from getSchedulingObject.
38
+ *
39
+ * The main difference is that 'calendardata' is optional.
40
+ *
41
+ * @param string $principalUri
42
+ * @return array
43
+ */
44
+ function getSchedulingObjects($principalUri);
45
+
46
+ /**
47
+ * Deletes a scheduling object from the inbox collection.
48
+ *
49
+ * @param string $principalUri
50
+ * @param string $objectUri
51
+ * @return void
52
+ */
53
+ function deleteSchedulingObject($principalUri, $objectUri);
54
+
55
+ /**
56
+ * Creates a new scheduling object. This should land in a users' inbox.
57
+ *
58
+ * @param string $principalUri
59
+ * @param string $objectUri
60
+ * @param string $objectData
61
+ * @return void
62
+ */
63
+ function createSchedulingObject($principalUri, $objectUri, $objectData);
64
+
65
+ }
vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ /**
6
+ * Adds support for sharing features to a CalDAV server.
7
+ *
8
+ * CalDAV backends that implement this interface, must make the following
9
+ * modifications to getCalendarsForUser:
10
+ *
11
+ * 1. Return shared calendars for users.
12
+ * 2. For every calendar, return calendar-resource-uri. This strings is a URI or
13
+ * relative URI reference that must be unique for every calendar, but
14
+ * identical for every instance of the same shared calendar.
15
+ * 3. For every calendar, you must return a share-access element. This element
16
+ * should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* constants and
17
+ * indicates the access level the user has.
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ interface SharingSupport extends BackendInterface {
24
+
25
+ /**
26
+ * Updates the list of shares.
27
+ *
28
+ * @param mixed $calendarId
29
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
30
+ * @return void
31
+ */
32
+ function updateInvites($calendarId, array $sharees);
33
+
34
+ /**
35
+ * Returns the list of people whom this calendar is shared with.
36
+ *
37
+ * Every item in the returned list must be a Sharee object with at
38
+ * least the following properties set:
39
+ * $href
40
+ * $shareAccess
41
+ * $inviteStatus
42
+ *
43
+ * and optionally:
44
+ * $properties
45
+ *
46
+ * @param mixed $calendarId
47
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
48
+ */
49
+ function getInvites($calendarId);
50
+
51
+ /**
52
+ * Publishes a calendar
53
+ *
54
+ * @param mixed $calendarId
55
+ * @param bool $value
56
+ * @return void
57
+ */
58
+ function setPublishStatus($calendarId, $value);
59
+
60
+ }
vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.php ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\DAV;
7
+
8
+ /**
9
+ * Simple PDO CalDAV backend.
10
+ *
11
+ * This class is basically the most minimum example to get a caldav backend up
12
+ * and running. This class uses the following schema (MySQL example):
13
+ *
14
+ * CREATE TABLE simple_calendars (
15
+ * id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
16
+ * uri VARBINARY(200) NOT NULL,
17
+ * principaluri VARBINARY(200) NOT NULL
18
+ * );
19
+ *
20
+ * CREATE TABLE simple_calendarobjects (
21
+ * id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
22
+ * calendarid INT UNSIGNED NOT NULL,
23
+ * uri VARBINARY(200) NOT NULL,
24
+ * calendardata MEDIUMBLOB
25
+ * )
26
+ *
27
+ * To make this class work, you absolutely need to have the PropertyStorage
28
+ * plugin enabled.
29
+ *
30
+ * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
31
+ * @author Evert Pot (http://evertpot.com/)
32
+ * @license http://sabre.io/license/ Modified BSD License
33
+ */
34
+ class SimplePDO extends AbstractBackend {
35
+
36
+ /**
37
+ * pdo
38
+ *
39
+ * @var \PDO
40
+ */
41
+ protected $pdo;
42
+
43
+ /**
44
+ * Creates the backend
45
+ *
46
+ * @param \PDO $pdo
47
+ */
48
+ function __construct(\PDO $pdo) {
49
+
50
+ $this->pdo = $pdo;
51
+
52
+ }
53
+
54
+ /**
55
+ * Returns a list of calendars for a principal.
56
+ *
57
+ * Every project is an array with the following keys:
58
+ * * id, a unique id that will be used by other functions to modify the
59
+ * calendar. This can be the same as the uri or a database key.
60
+ * * uri. This is just the 'base uri' or 'filename' of the calendar.
61
+ * * principaluri. The owner of the calendar. Almost always the same as
62
+ * principalUri passed to this method.
63
+ *
64
+ * Furthermore it can contain webdav properties in clark notation. A very
65
+ * common one is '{DAV:}displayname'.
66
+ *
67
+ * Many clients also require:
68
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
69
+ * For this property, you can just return an instance of
70
+ * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
71
+ *
72
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
73
+ * ACL will automatically be put in read-only mode.
74
+ *
75
+ * @param string $principalUri
76
+ * @return array
77
+ */
78
+ function getCalendarsForUser($principalUri) {
79
+
80
+ // Making fields a comma-delimited list
81
+ $stmt = $this->pdo->prepare("SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC");
82
+ $stmt->execute([$principalUri]);
83
+
84
+ $calendars = [];
85
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
86
+
87
+ $calendars[] = [
88
+ 'id' => $row['id'],
89
+ 'uri' => $row['uri'],
90
+ 'principaluri' => $principalUri,
91
+ ];
92
+
93
+ }
94
+
95
+ return $calendars;
96
+
97
+ }
98
+
99
+ /**
100
+ * Creates a new calendar for a principal.
101
+ *
102
+ * If the creation was a success, an id must be returned that can be used
103
+ * to reference this calendar in other methods, such as updateCalendar.
104
+ *
105
+ * @param string $principalUri
106
+ * @param string $calendarUri
107
+ * @param array $properties
108
+ * @return string
109
+ */
110
+ function createCalendar($principalUri, $calendarUri, array $properties) {
111
+
112
+ $stmt = $this->pdo->prepare("INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)");
113
+ $stmt->execute([$principalUri, $calendarUri]);
114
+
115
+ return $this->pdo->lastInsertId();
116
+
117
+ }
118
+
119
+ /**
120
+ * Delete a calendar and all it's objects
121
+ *
122
+ * @param string $calendarId
123
+ * @return void
124
+ */
125
+ function deleteCalendar($calendarId) {
126
+
127
+ $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?');
128
+ $stmt->execute([$calendarId]);
129
+
130
+ $stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?');
131
+ $stmt->execute([$calendarId]);
132
+
133
+ }
134
+
135
+ /**
136
+ * Returns all calendar objects within a calendar.
137
+ *
138
+ * Every item contains an array with the following keys:
139
+ * * calendardata - The iCalendar-compatible calendar data
140
+ * * uri - a unique key which will be used to construct the uri. This can
141
+ * be any arbitrary string, but making sure it ends with '.ics' is a
142
+ * good idea. This is only the basename, or filename, not the full
143
+ * path.
144
+ * * lastmodified - a timestamp of the last modification time
145
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
146
+ * ' "abcdef"')
147
+ * * size - The size of the calendar objects, in bytes.
148
+ * * component - optional, a string containing the type of object, such
149
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
150
+ * the Content-Type header.
151
+ *
152
+ * Note that the etag is optional, but it's highly encouraged to return for
153
+ * speed reasons.
154
+ *
155
+ * The calendardata is also optional. If it's not returned
156
+ * 'getCalendarObject' will be called later, which *is* expected to return
157
+ * calendardata.
158
+ *
159
+ * If neither etag or size are specified, the calendardata will be
160
+ * used/fetched to determine these numbers. If both are specified the
161
+ * amount of times this is needed is reduced by a great degree.
162
+ *
163
+ * @param string $calendarId
164
+ * @return array
165
+ */
166
+ function getCalendarObjects($calendarId) {
167
+
168
+ $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ?');
169
+ $stmt->execute([$calendarId]);
170
+
171
+ $result = [];
172
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
173
+ $result[] = [
174
+ 'id' => $row['id'],
175
+ 'uri' => $row['uri'],
176
+ 'etag' => '"' . md5($row['calendardata']) . '"',
177
+ 'calendarid' => $calendarId,
178
+ 'size' => strlen($row['calendardata']),
179
+ 'calendardata' => $row['calendardata'],
180
+ ];
181
+ }
182
+
183
+ return $result;
184
+
185
+ }
186
+
187
+ /**
188
+ * Returns information from a single calendar object, based on it's object
189
+ * uri.
190
+ *
191
+ * The object uri is only the basename, or filename and not a full path.
192
+ *
193
+ * The returned array must have the same keys as getCalendarObjects. The
194
+ * 'calendardata' object is required here though, while it's not required
195
+ * for getCalendarObjects.
196
+ *
197
+ * This method must return null if the object did not exist.
198
+ *
199
+ * @param string $calendarId
200
+ * @param string $objectUri
201
+ * @return array|null
202
+ */
203
+ function getCalendarObject($calendarId, $objectUri) {
204
+
205
+ $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
206
+ $stmt->execute([$calendarId, $objectUri]);
207
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
208
+
209
+ if (!$row) return null;
210
+
211
+ return [
212
+ 'id' => $row['id'],
213
+ 'uri' => $row['uri'],
214
+ 'etag' => '"' . md5($row['calendardata']) . '"',
215
+ 'calendarid' => $calendarId,
216
+ 'size' => strlen($row['calendardata']),
217
+ 'calendardata' => $row['calendardata'],
218
+ ];
219
+
220
+ }
221
+
222
+ /**
223
+ * Creates a new calendar object.
224
+ *
225
+ * The object uri is only the basename, or filename and not a full path.
226
+ *
227
+ * It is possible return an etag from this function, which will be used in
228
+ * the response to this PUT request. Note that the ETag must be surrounded
229
+ * by double-quotes.
230
+ *
231
+ * However, you should only really return this ETag if you don't mangle the
232
+ * calendar-data. If the result of a subsequent GET to this object is not
233
+ * the exact same as this request body, you should omit the ETag.
234
+ *
235
+ * @param mixed $calendarId
236
+ * @param string $objectUri
237
+ * @param string $calendarData
238
+ * @return string|null
239
+ */
240
+ function createCalendarObject($calendarId, $objectUri, $calendarData) {
241
+
242
+ $stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)');
243
+ $stmt->execute([
244
+ $calendarId,
245
+ $objectUri,
246
+ $calendarData
247
+ ]);
248
+
249
+ return '"' . md5($calendarData) . '"';
250
+
251
+ }
252
+
253
+ /**
254
+ * Updates an existing calendarobject, based on it's uri.
255
+ *
256
+ * The object uri is only the basename, or filename and not a full path.
257
+ *
258
+ * It is possible return an etag from this function, which will be used in
259
+ * the response to this PUT request. Note that the ETag must be surrounded
260
+ * by double-quotes.
261
+ *
262
+ * However, you should only really return this ETag if you don't mangle the
263
+ * calendar-data. If the result of a subsequent GET to this object is not
264
+ * the exact same as this request body, you should omit the ETag.
265
+ *
266
+ * @param mixed $calendarId
267
+ * @param string $objectUri
268
+ * @param string $calendarData
269
+ * @return string|null
270
+ */
271
+ function updateCalendarObject($calendarId, $objectUri, $calendarData) {
272
+
273
+ $stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?');
274
+ $stmt->execute([$calendarData, $calendarId, $objectUri]);
275
+
276
+ return '"' . md5($calendarData) . '"';
277
+
278
+ }
279
+
280
+ /**
281
+ * Deletes an existing calendar object.
282
+ *
283
+ * The object uri is only the basename, or filename and not a full path.
284
+ *
285
+ * @param string $calendarId
286
+ * @param string $objectUri
287
+ * @return void
288
+ */
289
+ function deleteCalendarObject($calendarId, $objectUri) {
290
+
291
+ $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
292
+ $stmt->execute([$calendarId, $objectUri]);
293
+
294
+ }
295
+
296
+ }
vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * Every CalDAV backend must at least implement this interface.
9
+ *
10
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11
+ * @author Evert Pot (http://evertpot.com/)
12
+ * @license http://sabre.io/license/ Modified BSD License
13
+ */
14
+ interface SubscriptionSupport extends BackendInterface {
15
+
16
+ /**
17
+ * Returns a list of subscriptions for a principal.
18
+ *
19
+ * Every subscription is an array with the following keys:
20
+ * * id, a unique id that will be used by other functions to modify the
21
+ * subscription. This can be the same as the uri or a database key.
22
+ * * uri. This is just the 'base uri' or 'filename' of the subscription.
23
+ * * principaluri. The owner of the subscription. Almost always the same as
24
+ * principalUri passed to this method.
25
+ *
26
+ * Furthermore, all the subscription info must be returned too:
27
+ *
28
+ * 1. {DAV:}displayname
29
+ * 2. {http://apple.com/ns/ical/}refreshrate
30
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
31
+ * should not be stripped).
32
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
33
+ * should not be stripped).
34
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
35
+ * attachments should not be stripped).
36
+ * 6. {http://calendarserver.org/ns/}source (Must be a
37
+ * Sabre\DAV\Property\Href).
38
+ * 7. {http://apple.com/ns/ical/}calendar-color
39
+ * 8. {http://apple.com/ns/ical/}calendar-order
40
+ * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
41
+ * (should just be an instance of
42
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
43
+ * default components).
44
+ *
45
+ * @param string $principalUri
46
+ * @return array
47
+ */
48
+ function getSubscriptionsForUser($principalUri);
49
+
50
+ /**
51
+ * Creates a new subscription for a principal.
52
+ *
53
+ * If the creation was a success, an id must be returned that can be used to reference
54
+ * this subscription in other methods, such as updateSubscription.
55
+ *
56
+ * @param string $principalUri
57
+ * @param string $uri
58
+ * @param array $properties
59
+ * @return mixed
60
+ */
61
+ function createSubscription($principalUri, $uri, array $properties);
62
+
63
+ /**
64
+ * Updates a subscription
65
+ *
66
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
67
+ * To do the actual updates, you must tell this object which properties
68
+ * you're going to process with the handle() method.
69
+ *
70
+ * Calling the handle method is like telling the PropPatch object "I
71
+ * promise I can handle updating this property".
72
+ *
73
+ * Read the PropPatch documentation for more info and examples.
74
+ *
75
+ * @param mixed $subscriptionId
76
+ * @param \Sabre\DAV\PropPatch $propPatch
77
+ * @return void
78
+ */
79
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch);
80
+
81
+ /**
82
+ * Deletes a subscription.
83
+ *
84
+ * @param mixed $subscriptionId
85
+ * @return void
86
+ */
87
+ function deleteSubscription($subscriptionId);
88
+
89
+ }
vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Backend;
4
+
5
+ /**
6
+ * WebDAV-sync support for CalDAV backends.
7
+ *
8
+ * In order for backends to advertise support for WebDAV-sync, this interface
9
+ * must be implemented.
10
+ *
11
+ * Implementing this can result in a significant reduction of bandwidth and CPU
12
+ * time.
13
+ *
14
+ * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
15
+ * property from getCalendarsFromUser.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ interface SyncSupport extends BackendInterface {
22
+
23
+ /**
24
+ * The getChanges method returns all the changes that have happened, since
25
+ * the specified syncToken in the specified calendar.
26
+ *
27
+ * This function should return an array, such as the following:
28
+ *
29
+ * [
30
+ * 'syncToken' => 'The current synctoken',
31
+ * 'added' => [
32
+ * 'new.txt',
33
+ * ],
34
+ * 'modified' => [
35
+ * 'modified.txt',
36
+ * ],
37
+ * 'deleted' => [
38
+ * 'foo.php.bak',
39
+ * 'old.txt'
40
+ * ]
41
+ * );
42
+ *
43
+ * The returned syncToken property should reflect the *current* syncToken
44
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
45
+ * property This is * needed here too, to ensure the operation is atomic.
46
+ *
47
+ * If the $syncToken argument is specified as null, this is an initial
48
+ * sync, and all members should be reported.
49
+ *
50
+ * The modified property is an array of nodenames that have changed since
51
+ * the last token.
52
+ *
53
+ * The deleted property is an array with nodenames, that have been deleted
54
+ * from collection.
55
+ *
56
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
57
+ * 1, you only have to report changes that happened only directly in
58
+ * immediate descendants. If it's 2, it should also include changes from
59
+ * the nodes below the child collections. (grandchildren)
60
+ *
61
+ * The $limit argument allows a client to specify how many results should
62
+ * be returned at most. If the limit is not specified, it should be treated
63
+ * as infinite.
64
+ *
65
+ * If the limit (infinite or not) is higher than you're willing to return,
66
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
67
+ *
68
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
69
+ * return null.
70
+ *
71
+ * The limit is 'suggestive'. You are free to ignore it.
72
+ *
73
+ * @param string $calendarId
74
+ * @param string $syncToken
75
+ * @param int $syncLevel
76
+ * @param int $limit
77
+ * @return array
78
+ */
79
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);
80
+
81
+ }
vendor/sabre/dav/lib/CalDAV/Calendar.php ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\PropPatch;
7
+ use Sabre\DAVACL;
8
+
9
+ /**
10
+ * This object represents a CalDAV calendar.
11
+ *
12
+ * A calendar can contain multiple TODO and or Events. These are represented
13
+ * as \Sabre\CalDAV\CalendarObject objects.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {
20
+
21
+ use DAVACL\ACLTrait;
22
+
23
+ /**
24
+ * This is an array with calendar information
25
+ *
26
+ * @var array
27
+ */
28
+ protected $calendarInfo;
29
+
30
+ /**
31
+ * CalDAV backend
32
+ *
33
+ * @var Backend\BackendInterface
34
+ */
35
+ protected $caldavBackend;
36
+
37
+ /**
38
+ * Constructor
39
+ *
40
+ * @param Backend\BackendInterface $caldavBackend
41
+ * @param array $calendarInfo
42
+ */
43
+ function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
44
+
45
+ $this->caldavBackend = $caldavBackend;
46
+ $this->calendarInfo = $calendarInfo;
47
+
48
+ }
49
+
50
+ /**
51
+ * Returns the name of the calendar
52
+ *
53
+ * @return string
54
+ */
55
+ function getName() {
56
+
57
+ return $this->calendarInfo['uri'];
58
+
59
+ }
60
+
61
+ /**
62
+ * Updates properties on this node.
63
+ *
64
+ * This method received a PropPatch object, which contains all the
65
+ * information about the update.
66
+ *
67
+ * To update specific properties, call the 'handle' method on this object.
68
+ * Read the PropPatch documentation for more information.
69
+ *
70
+ * @param PropPatch $propPatch
71
+ * @return void
72
+ */
73
+ function propPatch(PropPatch $propPatch) {
74
+
75
+ return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);
76
+
77
+ }
78
+
79
+ /**
80
+ * Returns the list of properties
81
+ *
82
+ * @param array $requestedProperties
83
+ * @return array
84
+ */
85
+ function getProperties($requestedProperties) {
86
+
87
+ $response = [];
88
+
89
+ foreach ($this->calendarInfo as $propName => $propValue) {
90
+
91
+ if (!is_null($propValue) && $propName[0] === '{')
92
+ $response[$propName] = $this->calendarInfo[$propName];
93
+
94
+ }
95
+ return $response;
96
+
97
+ }
98
+
99
+ /**
100
+ * Returns a calendar object
101
+ *
102
+ * The contained calendar objects are for example Events or Todo's.
103
+ *
104
+ * @param string $name
105
+ * @return \Sabre\CalDAV\ICalendarObject
106
+ */
107
+ function getChild($name) {
108
+
109
+ $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
110
+
111
+ if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found');
112
+
113
+ $obj['acl'] = $this->getChildACL();
114
+
115
+ return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
116
+
117
+ }
118
+
119
+ /**
120
+ * Returns the full list of calendar objects
121
+ *
122
+ * @return array
123
+ */
124
+ function getChildren() {
125
+
126
+ $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
127
+ $children = [];
128
+ foreach ($objs as $obj) {
129
+ $obj['acl'] = $this->getChildACL();
130
+ $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
131
+ }
132
+ return $children;
133
+
134
+ }
135
+
136
+ /**
137
+ * This method receives a list of paths in it's first argument.
138
+ * It must return an array with Node objects.
139
+ *
140
+ * If any children are not found, you do not have to return them.
141
+ *
142
+ * @param string[] $paths
143
+ * @return array
144
+ */
145
+ function getMultipleChildren(array $paths) {
146
+
147
+ $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
148
+ $children = [];
149
+ foreach ($objs as $obj) {
150
+ $obj['acl'] = $this->getChildACL();
151
+ $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
152
+ }
153
+ return $children;
154
+
155
+ }
156
+
157
+ /**
158
+ * Checks if a child-node exists.
159
+ *
160
+ * @param string $name
161
+ * @return bool
162
+ */
163
+ function childExists($name) {
164
+
165
+ $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
166
+ if (!$obj)
167
+ return false;
168
+ else
169
+ return true;
170
+
171
+ }
172
+
173
+ /**
174
+ * Creates a new directory
175
+ *
176
+ * We actually block this, as subdirectories are not allowed in calendars.
177
+ *
178
+ * @param string $name
179
+ * @return void
180
+ */
181
+ function createDirectory($name) {
182
+
183
+ throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');
184
+
185
+ }
186
+
187
+ /**
188
+ * Creates a new file
189
+ *
190
+ * The contents of the new file must be a valid ICalendar string.
191
+ *
192
+ * @param string $name
193
+ * @param resource $calendarData
194
+ * @return string|null
195
+ */
196
+ function createFile($name, $calendarData = null) {
197
+
198
+ if (is_resource($calendarData)) {
199
+ $calendarData = stream_get_contents($calendarData);
200
+ }
201
+ return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData);
202
+
203
+ }
204
+
205
+ /**
206
+ * Deletes the calendar.
207
+ *
208
+ * @return void
209
+ */
210
+ function delete() {
211
+
212
+ $this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
213
+
214
+ }
215
+
216
+ /**
217
+ * Renames the calendar. Note that most calendars use the
218
+ * {DAV:}displayname to display a name to display a name.
219
+ *
220
+ * @param string $newName
221
+ * @return void
222
+ */
223
+ function setName($newName) {
224
+
225
+ throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
226
+
227
+ }
228
+
229
+ /**
230
+ * Returns the last modification date as a unix timestamp.
231
+ *
232
+ * @return null
233
+ */
234
+ function getLastModified() {
235
+
236
+ return null;
237
+
238
+ }
239
+
240
+ /**
241
+ * Returns the owner principal
242
+ *
243
+ * This must be a url to a principal, or null if there's no owner
244
+ *
245
+ * @return string|null
246
+ */
247
+ function getOwner() {
248
+
249
+ return $this->calendarInfo['principaluri'];
250
+
251
+ }
252
+
253
+ /**
254
+ * Returns a list of ACE's for this node.
255
+ *
256
+ * Each ACE has the following properties:
257
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
258
+ * currently the only supported privileges
259
+ * * 'principal', a url to the principal who owns the node
260
+ * * 'protected' (optional), indicating that this ACE is not allowed to
261
+ * be updated.
262
+ *
263
+ * @return array
264
+ */
265
+ function getACL() {
266
+
267
+ $acl = [
268
+ [
269
+ 'privilege' => '{DAV:}read',
270
+ 'principal' => $this->getOwner(),
271
+ 'protected' => true,
272
+ ],
273
+ [
274
+ 'privilege' => '{DAV:}read',
275
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
276
+ 'protected' => true,
277
+ ],
278
+ [
279
+ 'privilege' => '{DAV:}read',
280
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
281
+ 'protected' => true,
282
+ ],
283
+ [
284
+ 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
285
+ 'principal' => '{DAV:}authenticated',
286
+ 'protected' => true,
287
+ ],
288
+
289
+ ];
290
+ if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
291
+ $acl[] = [
292
+ 'privilege' => '{DAV:}write',
293
+ 'principal' => $this->getOwner(),
294
+ 'protected' => true,
295
+ ];
296
+ $acl[] = [
297
+ 'privilege' => '{DAV:}write',
298
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
299
+ 'protected' => true,
300
+ ];
301
+ }
302
+
303
+ return $acl;
304
+
305
+ }
306
+
307
+ /**
308
+ * This method returns the ACL's for calendar objects in this calendar.
309
+ * The result of this method automatically gets passed to the
310
+ * calendar-object nodes in the calendar.
311
+ *
312
+ * @return array
313
+ */
314
+ function getChildACL() {
315
+
316
+ $acl = [
317
+ [
318
+ 'privilege' => '{DAV:}read',
319
+ 'principal' => $this->getOwner(),
320
+ 'protected' => true,
321
+ ],
322
+
323
+ [
324
+ 'privilege' => '{DAV:}read',
325
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
326
+ 'protected' => true,
327
+ ],
328
+ [
329
+ 'privilege' => '{DAV:}read',
330
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
331
+ 'protected' => true,
332
+ ],
333
+
334
+ ];
335
+ if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
336
+ $acl[] = [
337
+ 'privilege' => '{DAV:}write',
338
+ 'principal' => $this->getOwner(),
339
+ 'protected' => true,
340
+ ];
341
+ $acl[] = [
342
+ 'privilege' => '{DAV:}write',
343
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
344
+ 'protected' => true,
345
+ ];
346
+
347
+ }
348
+ return $acl;
349
+
350
+ }
351
+
352
+
353
+ /**
354
+ * Performs a calendar-query on the contents of this calendar.
355
+ *
356
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
357
+ * calendar-query it is possible for a client to request a specific set of
358
+ * object, based on contents of iCalendar properties, date-ranges and
359
+ * iCalendar component types (VTODO, VEVENT).
360
+ *
361
+ * This method should just return a list of (relative) urls that match this
362
+ * query.
363
+ *
364
+ * The list of filters are specified as an array. The exact array is
365
+ * documented by Sabre\CalDAV\CalendarQueryParser.
366
+ *
367
+ * @param array $filters
368
+ * @return array
369
+ */
370
+ function calendarQuery(array $filters) {
371
+
372
+ return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
373
+
374
+ }
375
+
376
+ /**
377
+ * This method returns the current sync-token for this collection.
378
+ * This can be any string.
379
+ *
380
+ * If null is returned from this function, the plugin assumes there's no
381
+ * sync information available.
382
+ *
383
+ * @return string|null
384
+ */
385
+ function getSyncToken() {
386
+
387
+ if (
388
+ $this->caldavBackend instanceof Backend\SyncSupport &&
389
+ isset($this->calendarInfo['{DAV:}sync-token'])
390
+ ) {
391
+ return $this->calendarInfo['{DAV:}sync-token'];
392
+ }
393
+ if (
394
+ $this->caldavBackend instanceof Backend\SyncSupport &&
395
+ isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
396
+ ) {
397
+ return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
398
+ }
399
+
400
+ }
401
+
402
+ /**
403
+ * The getChanges method returns all the changes that have happened, since
404
+ * the specified syncToken and the current collection.
405
+ *
406
+ * This function should return an array, such as the following:
407
+ *
408
+ * [
409
+ * 'syncToken' => 'The current synctoken',
410
+ * 'added' => [
411
+ * 'new.txt',
412
+ * ],
413
+ * 'modified' => [
414
+ * 'modified.txt',
415
+ * ],
416
+ * 'deleted' => [
417
+ * 'foo.php.bak',
418
+ * 'old.txt'
419
+ * ]
420
+ * ];
421
+ *
422
+ * The syncToken property should reflect the *current* syncToken of the
423
+ * collection, as reported getSyncToken(). This is needed here too, to
424
+ * ensure the operation is atomic.
425
+ *
426
+ * If the syncToken is specified as null, this is an initial sync, and all
427
+ * members should be reported.
428
+ *
429
+ * The modified property is an array of nodenames that have changed since
430
+ * the last token.
431
+ *
432
+ * The deleted property is an array with nodenames, that have been deleted
433
+ * from collection.
434
+ *
435
+ * The second argument is basically the 'depth' of the report. If it's 1,
436
+ * you only have to report changes that happened only directly in immediate
437
+ * descendants. If it's 2, it should also include changes from the nodes
438
+ * below the child collections. (grandchildren)
439
+ *
440
+ * The third (optional) argument allows a client to specify how many
441
+ * results should be returned at most. If the limit is not specified, it
442
+ * should be treated as infinite.
443
+ *
444
+ * If the limit (infinite or not) is higher than you're willing to return,
445
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
446
+ *
447
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
448
+ * return null.
449
+ *
450
+ * The limit is 'suggestive'. You are free to ignore it.
451
+ *
452
+ * @param string $syncToken
453
+ * @param int $syncLevel
454
+ * @param int $limit
455
+ * @return array
456
+ */
457
+ function getChanges($syncToken, $syncLevel, $limit = null) {
458
+
459
+ if (!$this->caldavBackend instanceof Backend\SyncSupport) {
460
+ return null;
461
+ }
462
+
463
+ return $this->caldavBackend->getChangesForCalendar(
464
+ $this->calendarInfo['id'],
465
+ $syncToken,
466
+ $syncLevel,
467
+ $limit
468
+ );
469
+
470
+ }
471
+
472
+ }
vendor/sabre/dav/lib/CalDAV/CalendarHome.php ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\Exception\NotFound;
7
+ use Sabre\DAV\MkCol;
8
+ use Sabre\DAVACL;
9
+ use Sabre\HTTP\URLUtil;
10
+
11
+ /**
12
+ * The CalendarHome represents a node that is usually in a users'
13
+ * calendar-homeset.
14
+ *
15
+ * It contains all the users' calendars, and can optionally contain a
16
+ * notifications collection, calendar subscriptions, a users' inbox, and a
17
+ * users' outbox.
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
24
+
25
+ use DAVACL\ACLTrait;
26
+
27
+ /**
28
+ * CalDAV backend
29
+ *
30
+ * @var Backend\BackendInterface
31
+ */
32
+ protected $caldavBackend;
33
+
34
+ /**
35
+ * Principal information
36
+ *
37
+ * @var array
38
+ */
39
+ protected $principalInfo;
40
+
41
+ /**
42
+ * Constructor
43
+ *
44
+ * @param Backend\BackendInterface $caldavBackend
45
+ * @param array $principalInfo
46
+ */
47
+ function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
48
+
49
+ $this->caldavBackend = $caldavBackend;
50
+ $this->principalInfo = $principalInfo;
51
+
52
+ }
53
+
54
+ /**
55
+ * Returns the name of this object
56
+ *
57
+ * @return string
58
+ */
59
+ function getName() {
60
+
61
+ list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
62
+ return $name;
63
+
64
+ }
65
+
66
+ /**
67
+ * Updates the name of this object
68
+ *
69
+ * @param string $name
70
+ * @return void
71
+ */
72
+ function setName($name) {
73
+
74
+ throw new DAV\Exception\Forbidden();
75
+
76
+ }
77
+
78
+ /**
79
+ * Deletes this object
80
+ *
81
+ * @return void
82
+ */
83
+ function delete() {
84
+
85
+ throw new DAV\Exception\Forbidden();
86
+
87
+ }
88
+
89
+ /**
90
+ * Returns the last modification date
91
+ *
92
+ * @return int
93
+ */
94
+ function getLastModified() {
95
+
96
+ return null;
97
+
98
+ }
99
+
100
+ /**
101
+ * Creates a new file under this object.
102
+ *
103
+ * This is currently not allowed
104
+ *
105
+ * @param string $filename
106
+ * @param resource $data
107
+ * @return void
108
+ */
109
+ function createFile($filename, $data = null) {
110
+
111
+ throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
112
+
113
+ }
114
+
115
+ /**
116
+ * Creates a new directory under this object.
117
+ *
118
+ * This is currently not allowed.
119
+ *
120
+ * @param string $filename
121
+ * @return void
122
+ */
123
+ function createDirectory($filename) {
124
+
125
+ throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
126
+
127
+ }
128
+
129
+ /**
130
+ * Returns a single calendar, by name
131
+ *
132
+ * @param string $name
133
+ * @return Calendar
134
+ */
135
+ function getChild($name) {
136
+
137
+ // Special nodes
138
+ if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
139
+ return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
140
+ }
141
+ if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
142
+ return new Schedule\Outbox($this->principalInfo['uri']);
143
+ }
144
+ if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
145
+ return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
146
+ }
147
+
148
+ // Calendars
149
+ foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
150
+ if ($calendar['uri'] === $name) {
151
+ if ($this->caldavBackend instanceof Backend\SharingSupport) {
152
+ return new SharedCalendar($this->caldavBackend, $calendar);
153
+ } else {
154
+ return new Calendar($this->caldavBackend, $calendar);
155
+ }
156
+ }
157
+ }
158
+
159
+ if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
160
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
161
+ if ($subscription['uri'] === $name) {
162
+ return new Subscriptions\Subscription($this->caldavBackend, $subscription);
163
+ }
164
+ }
165
+
166
+ }
167
+
168
+ throw new NotFound('Node with name \'' . $name . '\' could not be found');
169
+
170
+ }
171
+
172
+ /**
173
+ * Checks if a calendar exists.
174
+ *
175
+ * @param string $name
176
+ * @return bool
177
+ */
178
+ function childExists($name) {
179
+
180
+ try {
181
+ return !!$this->getChild($name);
182
+ } catch (NotFound $e) {
183
+ return false;
184
+ }
185
+
186
+ }
187
+
188
+ /**
189
+ * Returns a list of calendars
190
+ *
191
+ * @return array
192
+ */
193
+ function getChildren() {
194
+
195
+ $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
196
+ $objs = [];
197
+ foreach ($calendars as $calendar) {
198
+ if ($this->caldavBackend instanceof Backend\SharingSupport) {
199
+ $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
200
+ } else {
201
+ $objs[] = new Calendar($this->caldavBackend, $calendar);
202
+ }
203
+ }
204
+
205
+ if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
206
+ $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
207
+ $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
208
+ }
209
+
210
+ // We're adding a notifications node, if it's supported by the backend.
211
+ if ($this->caldavBackend instanceof Backend\NotificationSupport) {
212
+ $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
213
+ }
214
+
215
+ // If the backend supports subscriptions, we'll add those as well,
216
+ if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
217
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
218
+ $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
219
+ }
220
+ }
221
+
222
+ return $objs;
223
+
224
+ }
225
+
226
+ /**
227
+ * Creates a new calendar or subscription.
228
+ *
229
+ * @param string $name
230
+ * @param MkCol $mkCol
231
+ * @throws DAV\Exception\InvalidResourceType
232
+ * @return void
233
+ */
234
+ function createExtendedCollection($name, MkCol $mkCol) {
235
+
236
+ $isCalendar = false;
237
+ $isSubscription = false;
238
+ foreach ($mkCol->getResourceType() as $rt) {
239
+ switch ($rt) {
240
+ case '{DAV:}collection' :
241
+ case '{http://calendarserver.org/ns/}shared-owner' :
242
+ // ignore
243
+ break;
244
+ case '{urn:ietf:params:xml:ns:caldav}calendar' :
245
+ $isCalendar = true;
246
+ break;
247
+ case '{http://calendarserver.org/ns/}subscribed' :
248
+ $isSubscription = true;
249
+ break;
250
+ default :
251
+ throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
252
+ }
253
+ }
254
+
255
+ $properties = $mkCol->getRemainingValues();
256
+ $mkCol->setRemainingResultCode(201);
257
+
258
+ if ($isSubscription) {
259
+ if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
260
+ throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
261
+ }
262
+ $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
263
+
264
+ } elseif ($isCalendar) {
265
+ $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
266
+
267
+ } else {
268
+ throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
269
+
270
+ }
271
+
272
+ }
273
+
274
+ /**
275
+ * Returns the owner of the calendar home.
276
+ *
277
+ * @return string
278
+ */
279
+ function getOwner() {
280
+
281
+ return $this->principalInfo['uri'];
282
+
283
+ }
284
+
285
+ /**
286
+ * Returns a list of ACE's for this node.
287
+ *
288
+ * Each ACE has the following properties:
289
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
290
+ * currently the only supported privileges
291
+ * * 'principal', a url to the principal who owns the node
292
+ * * 'protected' (optional), indicating that this ACE is not allowed to
293
+ * be updated.
294
+ *
295
+ * @return array
296
+ */
297
+ function getACL() {
298
+
299
+ return [
300
+ [
301
+ 'privilege' => '{DAV:}read',
302
+ 'principal' => $this->principalInfo['uri'],
303
+ 'protected' => true,
304
+ ],
305
+ [
306
+ 'privilege' => '{DAV:}write',
307
+ 'principal' => $this->principalInfo['uri'],
308
+ 'protected' => true,
309
+ ],
310
+ [
311
+ 'privilege' => '{DAV:}read',
312
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
313
+ 'protected' => true,
314
+ ],
315
+ [
316
+ 'privilege' => '{DAV:}write',
317
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
318
+ 'protected' => true,
319
+ ],
320
+ [
321
+ 'privilege' => '{DAV:}read',
322
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
323
+ 'protected' => true,
324
+ ],
325
+
326
+ ];
327
+
328
+ }
329
+
330
+
331
+ /**
332
+ * This method is called when a user replied to a request to share.
333
+ *
334
+ * This method should return the url of the newly created calendar if the
335
+ * share was accepted.
336
+ *
337
+ * @param string $href The sharee who is replying (often a mailto: address)
338
+ * @param int $status One of the SharingPlugin::STATUS_* constants
339
+ * @param string $calendarUri The url to the calendar thats being shared
340
+ * @param string $inReplyTo The unique id this message is a response to
341
+ * @param string $summary A description of the reply
342
+ * @return null|string
343
+ */
344
+ function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
345
+
346
+ if (!$this->caldavBackend instanceof Backend\SharingSupport) {
347
+ throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
348
+ }
349
+
350
+ return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
351
+
352
+ }
353
+
354
+ /**
355
+ * Searches through all of a users calendars and calendar objects to find
356
+ * an object with a specific UID.
357
+ *
358
+ * This method should return the path to this object, relative to the
359
+ * calendar home, so this path usually only contains two parts:
360
+ *
361
+ * calendarpath/objectpath.ics
362
+ *
363
+ * If the uid is not found, return null.
364
+ *
365
+ * This method should only consider * objects that the principal owns, so
366
+ * any calendars owned by other principals that also appear in this
367
+ * collection should be ignored.
368
+ *
369
+ * @param string $uid
370
+ * @return string|null
371
+ */
372
+ function getCalendarObjectByUID($uid) {
373
+
374
+ return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
375
+
376
+ }
377
+
378
+ }
vendor/sabre/dav/lib/CalDAV/CalendarObject.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ /**
6
+ * The CalendarObject represents a single VEVENT or VTODO within a Calendar.
7
+ *
8
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
9
+ * @author Evert Pot (http://evertpot.com/)
10
+ * @license http://sabre.io/license/ Modified BSD License
11
+ */
12
+ class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {
13
+
14
+ use \Sabre\DAVACL\ACLTrait;
15
+
16
+ /**
17
+ * Sabre\CalDAV\Backend\BackendInterface
18
+ *
19
+ * @var Backend\AbstractBackend
20
+ */
21
+ protected $caldavBackend;
22
+
23
+ /**
24
+ * Array with information about this CalendarObject
25
+ *
26
+ * @var array
27
+ */
28
+ protected $objectData;
29
+
30
+ /**
31
+ * Array with information about the containing calendar
32
+ *
33
+ * @var array
34
+ */
35
+ protected $calendarInfo;
36
+
37
+ /**
38
+ * Constructor
39
+ *
40
+ * The following properties may be passed within $objectData:
41
+ *
42
+ * * calendarid - This must refer to a calendarid from a caldavBackend
43
+ * * uri - A unique uri. Only the 'basename' must be passed.
44
+ * * calendardata (optional) - The iCalendar data
45
+ * * etag - (optional) The etag for this object, MUST be encloded with
46
+ * double-quotes.
47
+ * * size - (optional) The size of the data in bytes.
48
+ * * lastmodified - (optional) format as a unix timestamp.
49
+ * * acl - (optional) Use this to override the default ACL for the node.
50
+ *
51
+ * @param Backend\BackendInterface $caldavBackend
52
+ * @param array $calendarInfo
53
+ * @param array $objectData
54
+ */
55
+ function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData) {
56
+
57
+ $this->caldavBackend = $caldavBackend;
58
+
59
+ if (!isset($objectData['uri'])) {
60
+ throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
61
+ }
62
+
63
+ $this->calendarInfo = $calendarInfo;
64
+ $this->objectData = $objectData;
65
+
66
+ }
67
+
68
+ /**
69
+ * Returns the uri for this object
70
+ *
71
+ * @return string
72
+ */
73
+ function getName() {
74
+
75
+ return $this->objectData['uri'];
76
+
77
+ }
78
+
79
+ /**
80
+ * Returns the ICalendar-formatted object
81
+ *
82
+ * @return string
83
+ */
84
+ function get() {
85
+
86
+ // Pre-populating the 'calendardata' is optional, if we don't have it
87
+ // already we fetch it from the backend.
88
+ if (!isset($this->objectData['calendardata'])) {
89
+ $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
90
+ }
91
+ return $this->objectData['calendardata'];
92
+
93
+ }
94
+
95
+ /**
96
+ * Updates the ICalendar-formatted object
97
+ *
98
+ * @param string|resource $calendarData
99
+ * @return string
100
+ */
101
+ function put($calendarData) {
102
+
103
+ if (is_resource($calendarData)) {
104
+ $calendarData = stream_get_contents($calendarData);
105
+ }
106
+ $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
107
+ $this->objectData['calendardata'] = $calendarData;
108
+ $this->objectData['etag'] = $etag;
109
+
110
+ return $etag;
111
+
112
+ }
113
+
114
+ /**
115
+ * Deletes the calendar object
116
+ *
117
+ * @return void
118
+ */
119
+ function delete() {
120
+
121
+ $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
122
+
123
+ }
124
+
125
+ /**
126
+ * Returns the mime content-type
127
+ *
128
+ * @return string
129
+ */
130
+ function getContentType() {
131
+
132
+ $mime = 'text/calendar; charset=utf-8';
133
+ if (isset($this->objectData['component']) && $this->objectData['component']) {
134
+ $mime .= '; component=' . $this->objectData['component'];
135
+ }
136
+ return $mime;
137
+
138
+ }
139
+
140
+ /**
141
+ * Returns an ETag for this object.
142
+ *
143
+ * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
144
+ *
145
+ * @return string
146
+ */
147
+ function getETag() {
148
+
149
+ if (isset($this->objectData['etag'])) {
150
+ return $this->objectData['etag'];
151
+ } else {
152
+ return '"' . md5($this->get()) . '"';
153
+ }
154
+
155
+ }
156
+
157
+ /**
158
+ * Returns the last modification date as a unix timestamp
159
+ *
160
+ * @return int
161
+ */
162
+ function getLastModified() {
163
+
164
+ return $this->objectData['lastmodified'];
165
+
166
+ }
167
+
168
+ /**
169
+ * Returns the size of this object in bytes
170
+ *
171
+ * @return int
172
+ */
173
+ function getSize() {
174
+
175
+ if (array_key_exists('size', $this->objectData)) {
176
+ return $this->objectData['size'];
177
+ } else {
178
+ return strlen($this->get());
179
+ }
180
+
181
+ }
182
+
183
+ /**
184
+ * Returns the owner principal
185
+ *
186
+ * This must be a url to a principal, or null if there's no owner
187
+ *
188
+ * @return string|null
189
+ */
190
+ function getOwner() {
191
+
192
+ return $this->calendarInfo['principaluri'];
193
+
194
+ }
195
+
196
+ /**
197
+ * Returns a list of ACE's for this node.
198
+ *
199
+ * Each ACE has the following properties:
200
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
201
+ * currently the only supported privileges
202
+ * * 'principal', a url to the principal who owns the node
203
+ * * 'protected' (optional), indicating that this ACE is not allowed to
204
+ * be updated.
205
+ *
206
+ * @return array
207
+ */
208
+ function getACL() {
209
+
210
+ // An alternative acl may be specified in the object data.
211
+ if (isset($this->objectData['acl'])) {
212
+ return $this->objectData['acl'];
213
+ }
214
+
215
+ // The default ACL
216
+ return [
217
+ [
218
+ 'privilege' => '{DAV:}all',
219
+ 'principal' => $this->calendarInfo['principaluri'],
220
+ 'protected' => true,
221
+ ],
222
+ [
223
+ 'privilege' => '{DAV:}all',
224
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
225
+ 'protected' => true,
226
+ ],
227
+ [
228
+ 'privilege' => '{DAV:}read',
229
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
230
+ 'protected' => true,
231
+ ],
232
+
233
+ ];
234
+
235
+ }
236
+
237
+ }
vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use DateTime;
6
+ use Sabre\VObject;
7
+
8
+ /**
9
+ * CalendarQuery Validator
10
+ *
11
+ * This class is responsible for checking if an iCalendar object matches a set
12
+ * of filters. The main function to do this is 'validate'.
13
+ *
14
+ * This is used to determine which icalendar objects should be returned for a
15
+ * calendar-query REPORT request.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ class CalendarQueryValidator {
22
+
23
+ /**
24
+ * Verify if a list of filters applies to the calendar data object
25
+ *
26
+ * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27
+ *
28
+ * @param VObject\Component\VCalendar $vObject
29
+ * @param array $filters
30
+ * @return bool
31
+ */
32
+ function validate(VObject\Component\VCalendar $vObject, array $filters) {
33
+
34
+ // The top level object is always a component filter.
35
+ // We'll parse it manually, as it's pretty simple.
36
+ if ($vObject->name !== $filters['name']) {
37
+ return false;
38
+ }
39
+
40
+ return
41
+ $this->validateCompFilters($vObject, $filters['comp-filters']) &&
42
+ $this->validatePropFilters($vObject, $filters['prop-filters']);
43
+
44
+
45
+ }
46
+
47
+ /**
48
+ * This method checks the validity of comp-filters.
49
+ *
50
+ * A list of comp-filters needs to be specified. Also the parent of the
51
+ * component we're checking should be specified, not the component to check
52
+ * itself.
53
+ *
54
+ * @param VObject\Component $parent
55
+ * @param array $filters
56
+ * @return bool
57
+ */
58
+ protected function validateCompFilters(VObject\Component $parent, array $filters) {
59
+
60
+ foreach ($filters as $filter) {
61
+
62
+ $isDefined = isset($parent->{$filter['name']});
63
+
64
+ if ($filter['is-not-defined']) {
65
+
66
+ if ($isDefined) {
67
+ return false;
68
+ } else {
69
+ continue;
70
+ }
71
+
72
+ }
73
+ if (!$isDefined) {
74
+ return false;
75
+ }
76
+
77
+ if ($filter['time-range']) {
78
+ foreach ($parent->{$filter['name']} as $subComponent) {
79
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
80
+ continue 2;
81
+ }
82
+ }
83
+ return false;
84
+ }
85
+
86
+ if (!$filter['comp-filters'] && !$filter['prop-filters']) {
87
+ continue;
88
+ }
89
+
90
+ // If there are sub-filters, we need to find at least one component
91
+ // for which the subfilters hold true.
92
+ foreach ($parent->{$filter['name']} as $subComponent) {
93
+
94
+ if (
95
+ $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
96
+ $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
97
+ // We had a match, so this comp-filter succeeds
98
+ continue 2;
99
+ }
100
+
101
+ }
102
+
103
+ // If we got here it means there were sub-comp-filters or
104
+ // sub-prop-filters and there was no match. This means this filter
105
+ // needs to return false.
106
+ return false;
107
+
108
+ }
109
+
110
+ // If we got here it means we got through all comp-filters alive so the
111
+ // filters were all true.
112
+ return true;
113
+
114
+ }
115
+
116
+ /**
117
+ * This method checks the validity of prop-filters.
118
+ *
119
+ * A list of prop-filters needs to be specified. Also the parent of the
120
+ * property we're checking should be specified, not the property to check
121
+ * itself.
122
+ *
123
+ * @param VObject\Component $parent
124
+ * @param array $filters
125
+ * @return bool
126
+ */
127
+ protected function validatePropFilters(VObject\Component $parent, array $filters) {
128
+
129
+ foreach ($filters as $filter) {
130
+
131
+ $isDefined = isset($parent->{$filter['name']});
132
+
133
+ if ($filter['is-not-defined']) {
134
+
135
+ if ($isDefined) {
136
+ return false;
137
+ } else {
138
+ continue;
139
+ }
140
+
141
+ }
142
+ if (!$isDefined) {
143
+ return false;
144
+ }
145
+
146
+ if ($filter['time-range']) {
147
+ foreach ($parent->{$filter['name']} as $subComponent) {
148
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
149
+ continue 2;
150
+ }
151
+ }
152
+ return false;
153
+ }
154
+
155
+ if (!$filter['param-filters'] && !$filter['text-match']) {
156
+ continue;
157
+ }
158
+
159
+ // If there are sub-filters, we need to find at least one property
160
+ // for which the subfilters hold true.
161
+ foreach ($parent->{$filter['name']} as $subComponent) {
162
+
163
+ if (
164
+ $this->validateParamFilters($subComponent, $filter['param-filters']) &&
165
+ (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
166
+ ) {
167
+ // We had a match, so this prop-filter succeeds
168
+ continue 2;
169
+ }
170
+
171
+ }
172
+
173
+ // If we got here it means there were sub-param-filters or
174
+ // text-match filters and there was no match. This means the
175
+ // filter needs to return false.
176
+ return false;
177
+
178
+ }
179
+
180
+ // If we got here it means we got through all prop-filters alive so the
181
+ // filters were all true.
182
+ return true;
183
+
184
+ }
185
+
186
+ /**
187
+ * This method checks the validity of param-filters.
188
+ *
189
+ * A list of param-filters needs to be specified. Also the parent of the
190
+ * parameter we're checking should be specified, not the parameter to check
191
+ * itself.
192
+ *
193
+ * @param VObject\Property $parent
194
+ * @param array $filters
195
+ * @return bool
196
+ */
197
+ protected function validateParamFilters(VObject\Property $parent, array $filters) {
198
+
199
+ foreach ($filters as $filter) {
200
+
201
+ $isDefined = isset($parent[$filter['name']]);
202
+
203
+ if ($filter['is-not-defined']) {
204
+
205
+ if ($isDefined) {
206
+ return false;
207
+ } else {
208
+ continue;
209
+ }
210
+
211
+ }
212
+ if (!$isDefined) {
213
+ return false;
214
+ }
215
+
216
+ if (!$filter['text-match']) {
217
+ continue;
218
+ }
219
+
220
+ // If there are sub-filters, we need to find at least one parameter
221
+ // for which the subfilters hold true.
222
+ foreach ($parent[$filter['name']]->getParts() as $paramPart) {
223
+
224
+ if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
225
+ // We had a match, so this param-filter succeeds
226
+ continue 2;
227
+ }
228
+
229
+ }
230
+
231
+ // If we got here it means there was a text-match filter and there
232
+ // were no matches. This means the filter needs to return false.
233
+ return false;
234
+
235
+ }
236
+
237
+ // If we got here it means we got through all param-filters alive so the
238
+ // filters were all true.
239
+ return true;
240
+
241
+ }
242
+
243
+ /**
244
+ * This method checks the validity of a text-match.
245
+ *
246
+ * A single text-match should be specified as well as the specific property
247
+ * or parameter we need to validate.
248
+ *
249
+ * @param VObject\Node|string $check Value to check against.
250
+ * @param array $textMatch
251
+ * @return bool
252
+ */
253
+ protected function validateTextMatch($check, array $textMatch) {
254
+
255
+ if ($check instanceof VObject\Node) {
256
+ $check = $check->getValue();
257
+ }
258
+
259
+ $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
260
+
261
+ return ($textMatch['negate-condition'] xor $isMatching);
262
+
263
+ }
264
+
265
+ /**
266
+ * Validates if a component matches the given time range.
267
+ *
268
+ * This is all based on the rules specified in rfc4791, which are quite
269
+ * complex.
270
+ *
271
+ * @param VObject\Node $component
272
+ * @param DateTime $start
273
+ * @param DateTime $end
274
+ * @return bool
275
+ */
276
+ protected function validateTimeRange(VObject\Node $component, $start, $end) {
277
+
278
+ if (is_null($start)) {
279
+ $start = new DateTime('1900-01-01');
280
+ }
281
+ if (is_null($end)) {
282
+ $end = new DateTime('3000-01-01');
283
+ }
284
+
285
+ switch ($component->name) {
286
+
287
+ case 'VEVENT' :
288
+ case 'VTODO' :
289
+ case 'VJOURNAL' :
290
+
291
+ return $component->isInTimeRange($start, $end);
292
+
293
+ case 'VALARM' :
294
+
295
+ // If the valarm is wrapped in a recurring event, we need to
296
+ // expand the recursions, and validate each.
297
+ //
298
+ // Our datamodel doesn't easily allow us to do this straight
299
+ // in the VALARM component code, so this is a hack, and an
300
+ // expensive one too.
301
+ if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302
+
303
+ // Fire up the iterator!
304
+ $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
305
+ while ($it->valid()) {
306
+ $expandedEvent = $it->getEventObject();
307
+
308
+ // We need to check from these expanded alarms, which
309
+ // one is the first to trigger. Based on this, we can
310
+ // determine if we can 'give up' expanding events.
311
+ $firstAlarm = null;
312
+ if ($expandedEvent->VALARM !== null) {
313
+ foreach ($expandedEvent->VALARM as $expandedAlarm) {
314
+
315
+ $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
316
+ if ($expandedAlarm->isInTimeRange($start, $end)) {
317
+ return true;
318
+ }
319
+
320
+ if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321
+ // This is an alarm with a non-relative trigger
322
+ // time, likely created by a buggy client. The
323
+ // implication is that every alarm in this
324
+ // recurring event trigger at the exact same
325
+ // time. It doesn't make sense to traverse
326
+ // further.
327
+ } else {
328
+ // We store the first alarm as a means to
329
+ // figure out when we can stop traversing.
330
+ if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
331
+ $firstAlarm = $effectiveTrigger;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ if (is_null($firstAlarm)) {
337
+ // No alarm was found.
338
+ //
339
+ // Or technically: No alarm that will change for
340
+ // every instance of the recurrence was found,
341
+ // which means we can assume there was no match.
342
+ return false;
343
+ }
344
+ if ($firstAlarm > $end) {
345
+ return false;
346
+ }
347
+ $it->next();
348
+ }
349
+ return false;
350
+ } else {
351
+ return $component->isInTimeRange($start, $end);
352
+ }
353
+
354
+ case 'VFREEBUSY' :
355
+ throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
356
+
357
+ case 'COMPLETED' :
358
+ case 'CREATED' :
359
+ case 'DTEND' :
360
+ case 'DTSTAMP' :
361
+ case 'DTSTART' :
362
+ case 'DUE' :
363
+ case 'LAST-MODIFIED' :
364
+ return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
365
+
366
+
367
+
368
+ default :
369
+ throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
370
+
371
+ }
372
+
373
+ }
374
+
375
+ }
vendor/sabre/dav/lib/CalDAV/CalendarRoot.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAVACL\PrincipalBackend;
6
+
7
+ /**
8
+ * Calendars collection
9
+ *
10
+ * This object is responsible for generating a list of calendar-homes for each
11
+ * user.
12
+ *
13
+ * This is the top-most node for the calendars tree. In most servers this class
14
+ * represents the "/calendars" path.
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection {
21
+
22
+ /**
23
+ * CalDAV backend
24
+ *
25
+ * @var Backend\BackendInterface
26
+ */
27
+ protected $caldavBackend;
28
+
29
+ /**
30
+ * Constructor
31
+ *
32
+ * This constructor needs both an authentication and a caldav backend.
33
+ *
34
+ * By default this class will show a list of calendar collections for
35
+ * principals in the 'principals' collection. If your main principals are
36
+ * actually located in a different path, use the $principalPrefix argument
37
+ * to override this.
38
+ *
39
+ * @param PrincipalBackend\BackendInterface $principalBackend
40
+ * @param Backend\BackendInterface $caldavBackend
41
+ * @param string $principalPrefix
42
+ */
43
+ function __construct(PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {
44
+
45
+ parent::__construct($principalBackend, $principalPrefix);
46
+ $this->caldavBackend = $caldavBackend;
47
+
48
+ }
49
+
50
+ /**
51
+ * Returns the nodename
52
+ *
53
+ * We're overriding this, because the default will be the 'principalPrefix',
54
+ * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT
55
+ *
56
+ * @return string
57
+ */
58
+ function getName() {
59
+
60
+ return Plugin::CALENDAR_ROOT;
61
+
62
+ }
63
+
64
+ /**
65
+ * This method returns a node for a principal.
66
+ *
67
+ * The passed array contains principal information, and is guaranteed to
68
+ * at least contain a uri item. Other properties may or may not be
69
+ * supplied by the authentication backend.
70
+ *
71
+ * @param array $principal
72
+ * @return \Sabre\DAV\INode
73
+ */
74
+ function getChildForPrincipal(array $principal) {
75
+
76
+ return new CalendarHome($this->caldavBackend, $principal);
77
+
78
+ }
79
+
80
+ }
vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Exception;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\DAV;
7
+
8
+ /**
9
+ * InvalidComponentType
10
+ *
11
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
12
+ * @author Evert Pot (http://evertpot.com/)
13
+ * @license http://sabre.io/license/ Modified BSD License
14
+ */
15
+ class InvalidComponentType extends DAV\Exception\Forbidden {
16
+
17
+ /**
18
+ * Adds in extra information in the xml response.
19
+ *
20
+ * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
21
+ *
22
+ * @param DAV\Server $server
23
+ * @param \DOMElement $errorNode
24
+ * @return void
25
+ */
26
+ function serialize(DAV\Server $server, \DOMElement $errorNode) {
27
+
28
+ $doc = $errorNode->ownerDocument;
29
+
30
+ $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component');
31
+ $errorNode->appendChild($np);
32
+
33
+ }
34
+
35
+ }
vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use DateTime;
6
+ use DateTimeZone;
7
+ use Sabre\DAV;
8
+ use Sabre\DAV\Exception\BadRequest;
9
+ use Sabre\HTTP\RequestInterface;
10
+ use Sabre\HTTP\ResponseInterface;
11
+ use Sabre\VObject;
12
+
13
+ /**
14
+ * ICS Exporter
15
+ *
16
+ * This plugin adds the ability to export entire calendars as .ics files.
17
+ * This is useful for clients that don't support CalDAV yet. They often do
18
+ * support ics files.
19
+ *
20
+ * To use this, point a http client to a caldav calendar, and add ?expand to
21
+ * the url.
22
+ *
23
+ * Further options that can be added to the url:
24
+ * start=123456789 - Only return events after the given unix timestamp
25
+ * end=123245679 - Only return events from before the given unix timestamp
26
+ * expand=1 - Strip timezone information and expand recurring events.
27
+ * If you'd like to expand, you _must_ also specify start
28
+ * and end.
29
+ *
30
+ * By default this plugin returns data in the text/calendar format (iCalendar
31
+ * 2.0). If you'd like to receive jCal data instead, you can use an Accept
32
+ * header:
33
+ *
34
+ * Accept: application/calendar+json
35
+ *
36
+ * Alternatively, you can also specify this in the url using
37
+ * accept=application/calendar+json, or accept=jcal for short. If the url
38
+ * parameter and Accept header is specified, the url parameter wins.
39
+ *
40
+ * Note that specifying a start or end data implies that only events will be
41
+ * returned. VTODO and VJOURNAL will be stripped.
42
+ *
43
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
44
+ * @author Evert Pot (http://evertpot.com/)
45
+ * @license http://sabre.io/license/ Modified BSD License
46
+ */
47
+ class ICSExportPlugin extends DAV\ServerPlugin {
48
+
49
+ /**
50
+ * Reference to Server class
51
+ *
52
+ * @var \Sabre\DAV\Server
53
+ */
54
+ protected $server;
55
+
56
+ /**
57
+ * Initializes the plugin and registers event handlers
58
+ *
59
+ * @param \Sabre\DAV\Server $server
60
+ * @return void
61
+ */
62
+ function initialize(DAV\Server $server) {
63
+
64
+ $this->server = $server;
65
+ $server->on('method:GET', [$this, 'httpGet'], 90);
66
+ $server->on('browserButtonActions', function($path, $node, &$actions) {
67
+ if ($node instanceof ICalendar) {
68
+ $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
69
+ }
70
+ });
71
+
72
+ }
73
+
74
+ /**
75
+ * Intercepts GET requests on calendar urls ending with ?export.
76
+ *
77
+ * @param RequestInterface $request
78
+ * @param ResponseInterface $response
79
+ * @return bool
80
+ */
81
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
82
+
83
+ $queryParams = $request->getQueryParameters();
84
+ if (!array_key_exists('export', $queryParams)) return;
85
+
86
+ $path = $request->getPath();
87
+
88
+ $node = $this->server->getProperties($path, [
89
+ '{DAV:}resourcetype',
90
+ '{DAV:}displayname',
91
+ '{http://sabredav.org/ns}sync-token',
92
+ '{DAV:}sync-token',
93
+ '{http://apple.com/ns/ical/}calendar-color',
94
+ ]);
95
+
96
+ if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
97
+ return;
98
+ }
99
+ // Marking the transactionType, for logging purposes.
100
+ $this->server->transactionType = 'get-calendar-export';
101
+
102
+ $properties = $node;
103
+
104
+ $start = null;
105
+ $end = null;
106
+ $expand = false;
107
+ $componentType = false;
108
+ if (isset($queryParams['start'])) {
109
+ if (!ctype_digit($queryParams['start'])) {
110
+ throw new BadRequest('The start= parameter must contain a unix timestamp');
111
+ }
112
+ $start = DateTime::createFromFormat('U', $queryParams['start']);
113
+ }
114
+ if (isset($queryParams['end'])) {
115
+ if (!ctype_digit($queryParams['end'])) {
116
+ throw new BadRequest('The end= parameter must contain a unix timestamp');
117
+ }
118
+ $end = DateTime::createFromFormat('U', $queryParams['end']);
119
+ }
120
+ if (isset($queryParams['expand']) && !!$queryParams['expand']) {
121
+ if (!$start || !$end) {
122
+ throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
123
+ }
124
+ $expand = true;
125
+ $componentType = 'VEVENT';
126
+ }
127
+ if (isset($queryParams['componentType'])) {
128
+ if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
129
+ throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
130
+ }
131
+ $componentType = $queryParams['componentType'];
132
+ }
133
+
134
+ $format = \Sabre\HTTP\Util::Negotiate(
135
+ $request->getHeader('Accept'),
136
+ [
137
+ 'text/calendar',
138
+ 'application/calendar+json',
139
+ ]
140
+ );
141
+
142
+ if (isset($queryParams['accept'])) {
143
+ if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
144
+ $format = 'application/calendar+json';
145
+ }
146
+ }
147
+ if (!$format) {
148
+ $format = 'text/calendar';
149
+ }
150
+
151
+ $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
152
+
153
+ // Returning false to break the event chain
154
+ return false;
155
+
156
+ }
157
+
158
+ /**
159
+ * This method is responsible for generating the actual, full response.
160
+ *
161
+ * @param string $path
162
+ * @param DateTime|null $start
163
+ * @param DateTime|null $end
164
+ * @param bool $expand
165
+ * @param string $componentType
166
+ * @param string $format
167
+ * @param array $properties
168
+ * @param ResponseInterface $response
169
+ */
170
+ protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
171
+
172
+ $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
173
+ $calendarNode = $this->server->tree->getNodeForPath($path);
174
+
175
+ $blobs = [];
176
+ if ($start || $end || $componentType) {
177
+
178
+ // If there was a start or end filter, we need to enlist
179
+ // calendarQuery for speed.
180
+ $queryResult = $calendarNode->calendarQuery([
181
+ 'name' => 'VCALENDAR',
182
+ 'comp-filters' => [
183
+ [
184
+ 'name' => $componentType,
185
+ 'comp-filters' => [],
186
+ 'prop-filters' => [],
187
+ 'is-not-defined' => false,
188
+ 'time-range' => [
189
+ 'start' => $start,
190
+ 'end' => $end,
191
+ ],
192
+ ],
193
+ ],
194
+ 'prop-filters' => [],
195
+ 'is-not-defined' => false,
196
+ 'time-range' => null,
197
+ ]);
198
+
199
+ // queryResult is just a list of base urls. We need to prefix the
200
+ // calendar path.
201
+ $queryResult = array_map(
202
+ function($item) use ($path) {
203
+ return $path . '/' . $item;
204
+ },
205
+ $queryResult
206
+ );
207
+ $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
208
+ unset($queryResult);
209
+
210
+ } else {
211
+ $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
212
+ }
213
+
214
+ // Flattening the arrays
215
+ foreach ($nodes as $node) {
216
+ if (isset($node[200][$calDataProp])) {
217
+ $blobs[$node['href']] = $node[200][$calDataProp];
218
+ }
219
+ }
220
+ unset($nodes);
221
+
222
+ $mergedCalendar = $this->mergeObjects(
223
+ $properties,
224
+ $blobs
225
+ );
226
+
227
+ if ($expand) {
228
+ $calendarTimeZone = null;
229
+ // We're expanding, and for that we need to figure out the
230
+ // calendar's timezone.
231
+ $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
232
+ $tzResult = $this->server->getProperties($path, [$tzProp]);
233
+ if (isset($tzResult[$tzProp])) {
234
+ // This property contains a VCALENDAR with a single
235
+ // VTIMEZONE.
236
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
237
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
238
+ // Destroy circular references to PHP will GC the object.
239
+ $vtimezoneObj->destroy();
240
+ unset($vtimezoneObj);
241
+ } else {
242
+ // Defaulting to UTC.
243
+ $calendarTimeZone = new DateTimeZone('UTC');
244
+ }
245
+
246
+ $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
247
+ }
248
+
249
+ $filenameExtension = '.ics';
250
+
251
+ switch ($format) {
252
+ case 'text/calendar' :
253
+ $mergedCalendar = $mergedCalendar->serialize();
254
+ $filenameExtension = '.ics';
255
+ break;
256
+ case 'application/calendar+json' :
257
+ $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
258
+ $filenameExtension = '.json';
259
+ break;
260
+ }
261
+
262
+ $filename = preg_replace(
263
+ '/[^a-zA-Z0-9-_ ]/um',
264
+ '',
265
+ $calendarNode->getName()
266
+ );
267
+ $filename .= '-' . date('Y-m-d') . $filenameExtension;
268
+
269
+ $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
270
+ $response->setHeader('Content-Type', $format);
271
+
272
+ $response->setStatus(200);
273
+ $response->setBody($mergedCalendar);
274
+
275
+ }
276
+
277
+ /**
278
+ * Merges all calendar objects, and builds one big iCalendar blob.
279
+ *
280
+ * @param array $properties Some CalDAV properties
281
+ * @param array $inputObjects
282
+ * @return VObject\Component\VCalendar
283
+ */
284
+ function mergeObjects(array $properties, array $inputObjects) {
285
+
286
+ $calendar = new VObject\Component\VCalendar();
287
+ $calendar->VERSION = '2.0';
288
+ if (DAV\Server::$exposeVersion) {
289
+ $calendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
290
+ } else {
291
+ $calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
292
+ }
293
+ if (isset($properties['{DAV:}displayname'])) {
294
+ $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
295
+ }
296
+ if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
297
+ $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
298
+ }
299
+
300
+ $collectedTimezones = [];
301
+
302
+ $timezones = [];
303
+ $objects = [];
304
+
305
+ foreach ($inputObjects as $href => $inputObject) {
306
+
307
+ $nodeComp = VObject\Reader::read($inputObject);
308
+
309
+ foreach ($nodeComp->children() as $child) {
310
+
311
+ switch ($child->name) {
312
+ case 'VEVENT' :
313
+ case 'VTODO' :
314
+ case 'VJOURNAL' :
315
+ $objects[] = clone $child;
316
+ break;
317
+
318
+ // VTIMEZONE is special, because we need to filter out the duplicates
319
+ case 'VTIMEZONE' :
320
+ // Naively just checking tzid.
321
+ if (in_array((string)$child->TZID, $collectedTimezones)) continue;
322
+
323
+ $timezones[] = clone $child;
324
+ $collectedTimezones[] = $child->TZID;
325
+ break;
326
+
327
+ }
328
+
329
+ }
330
+ // Destroy circular references to PHP will GC the object.
331
+ $nodeComp->destroy();
332
+ unset($nodeComp);
333
+
334
+ }
335
+
336
+ foreach ($timezones as $tz) $calendar->add($tz);
337
+ foreach ($objects as $obj) $calendar->add($obj);
338
+
339
+ return $calendar;
340
+
341
+ }
342
+
343
+ /**
344
+ * Returns a plugin name.
345
+ *
346
+ * Using this name other plugins will be able to access other plugins
347
+ * using \Sabre\DAV\Server::getPlugin
348
+ *
349
+ * @return string
350
+ */
351
+ function getPluginName() {
352
+
353
+ return 'ics-export';
354
+
355
+ }
356
+
357
+ /**
358
+ * Returns a bunch of meta-data about the plugin.
359
+ *
360
+ * Providing this information is optional, and is mainly displayed by the
361
+ * Browser plugin.
362
+ *
363
+ * The description key in the returned array may contain html and will not
364
+ * be sanitized.
365
+ *
366
+ * @return array
367
+ */
368
+ function getPluginInfo() {
369
+
370
+ return [
371
+ 'name' => $this->getPluginName(),
372
+ 'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
373
+ 'link' => 'http://sabre.io/dav/ics-export-plugin/',
374
+ ];
375
+
376
+ }
377
+
378
+ }
vendor/sabre/dav/lib/CalDAV/ICalendar.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAVACL;
6
+
7
+ /**
8
+ * Calendar interface
9
+ *
10
+ * Implement this interface to allow a node to be recognized as an calendar.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL {
17
+
18
+ }
vendor/sabre/dav/lib/CalDAV/ICalendarObject.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * CalendarObject interface
9
+ *
10
+ * Extend the ICalendarObject interface to allow your custom nodes to be picked up as
11
+ * CalendarObjects.
12
+ *
13
+ * Calendar objects are resources such as Events, Todo's or Journals.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ interface ICalendarObject extends DAV\IFile {
20
+
21
+ }
vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ /**
6
+ * This interface represents a node that may contain calendar objects.
7
+ *
8
+ * This is the shared parent for both the Inbox collection and calendars
9
+ * resources.
10
+ *
11
+ * In most cases you will likely want to look at ICalendar instead of this
12
+ * interface.
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ interface ICalendarObjectContainer extends \Sabre\DAV\ICollection {
19
+
20
+ /**
21
+ * Performs a calendar-query on the contents of this calendar.
22
+ *
23
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
24
+ * calendar-query it is possible for a client to request a specific set of
25
+ * object, based on contents of iCalendar properties, date-ranges and
26
+ * iCalendar component types (VTODO, VEVENT).
27
+ *
28
+ * This method should just return a list of (relative) urls that match this
29
+ * query.
30
+ *
31
+ * The list of filters are specified as an array. The exact array is
32
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
33
+ *
34
+ * @param array $filters
35
+ * @return array
36
+ */
37
+ function calendarQuery(array $filters);
38
+
39
+ }
vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV\Sharing\ISharedNode;
6
+
7
+ /**
8
+ * This interface represents a Calendar that is shared by a different user.
9
+ *
10
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11
+ * @author Evert Pot (http://evertpot.com/)
12
+ * @license http://sabre.io/license/ Modified BSD License
13
+ */
14
+ interface ISharedCalendar extends ISharedNode {
15
+
16
+ /**
17
+ * Marks this calendar as published.
18
+ *
19
+ * Publishing a calendar should automatically create a read-only, public,
20
+ * subscribable calendar.
21
+ *
22
+ * @param bool $value
23
+ * @return void
24
+ */
25
+ function setPublishStatus($value);
26
+ }
vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Notifications;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\DAV;
7
+ use Sabre\DAVACL;
8
+
9
+ /**
10
+ * This node represents a list of notifications.
11
+ *
12
+ * It provides no additional functionality, but you must implement this
13
+ * interface to allow the Notifications plugin to mark the collection
14
+ * as a notifications collection.
15
+ *
16
+ * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
17
+ * its children.
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
24
+
25
+ use DAVACL\ACLTrait;
26
+
27
+ /**
28
+ * The notification backend
29
+ *
30
+ * @var CalDAV\Backend\NotificationSupport
31
+ */
32
+ protected $caldavBackend;
33
+
34
+ /**
35
+ * Principal uri
36
+ *
37
+ * @var string
38
+ */
39
+ protected $principalUri;
40
+
41
+ /**
42
+ * Constructor
43
+ *
44
+ * @param CalDAV\Backend\NotificationSupport $caldavBackend
45
+ * @param string $principalUri
46
+ */
47
+ function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
48
+
49
+ $this->caldavBackend = $caldavBackend;
50
+ $this->principalUri = $principalUri;
51
+
52
+ }
53
+
54
+ /**
55
+ * Returns all notifications for a principal
56
+ *
57
+ * @return array
58
+ */
59
+ function getChildren() {
60
+
61
+ $children = [];
62
+ $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
63
+
64
+ foreach ($notifications as $notification) {
65
+
66
+ $children[] = new Node(
67
+ $this->caldavBackend,
68
+ $this->principalUri,
69
+ $notification
70
+ );
71
+ }
72
+
73
+ return $children;
74
+
75
+ }
76
+
77
+ /**
78
+ * Returns the name of this object
79
+ *
80
+ * @return string
81
+ */
82
+ function getName() {
83
+
84
+ return 'notifications';
85
+
86
+ }
87
+
88
+ /**
89
+ * Returns the owner principal
90
+ *
91
+ * This must be a url to a principal, or null if there's no owner
92
+ *
93
+ * @return string|null
94
+ */
95
+ function getOwner() {
96
+
97
+ return $this->principalUri;
98
+
99
+ }
100
+
101
+ }
vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Notifications;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * This node represents a list of notifications.
9
+ *
10
+ * It provides no additional functionality, but you must implement this
11
+ * interface to allow the Notifications plugin to mark the collection
12
+ * as a notifications collection.
13
+ *
14
+ * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
15
+ * its children.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ interface ICollection extends DAV\ICollection {
22
+
23
+ }
vendor/sabre/dav/lib/CalDAV/Notifications/INode.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Notifications;
4
+
5
+ use Sabre\CalDAV\Xml\Notification\NotificationInterface;
6
+
7
+ /**
8
+ * This node represents a single notification.
9
+ *
10
+ * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
11
+ * MUST return an xml document that matches the requirements of the
12
+ * 'caldav-notifications.txt' spec.
13
+ *
14
+ * For a complete example, check out the Notification class, which contains
15
+ * some helper functions.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ interface INode {
22
+
23
+ /**
24
+ * This method must return an xml element, using the
25
+ * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
26
+ *
27
+ * @return NotificationInterface
28
+ */
29
+ function getNotificationType();
30
+
31
+ /**
32
+ * Returns the etag for the notification.
33
+ *
34
+ * The etag must be surrounded by literal double-quotes.
35
+ *
36
+ * @return string
37
+ */
38
+ function getETag();
39
+
40
+ }
vendor/sabre/dav/lib/CalDAV/Notifications/Node.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Notifications;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\CalDAV\Xml\Notification\NotificationInterface;
7
+ use Sabre\DAV;
8
+ use Sabre\DAVACL;
9
+
10
+ /**
11
+ * This node represents a single notification.
12
+ *
13
+ * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
14
+ * MUST return an xml document that matches the requirements of the
15
+ * 'caldav-notifications.txt' spec.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ class Node extends DAV\File implements INode, DAVACL\IACL {
22
+
23
+ use DAVACL\ACLTrait;
24
+
25
+ /**
26
+ * The notification backend
27
+ *
28
+ * @var CalDAV\Backend\NotificationSupport
29
+ */
30
+ protected $caldavBackend;
31
+
32
+ /**
33
+ * The actual notification
34
+ *
35
+ * @var NotificationInterface
36
+ */
37
+ protected $notification;
38
+
39
+ /**
40
+ * Owner principal of the notification
41
+ *
42
+ * @var string
43
+ */
44
+ protected $principalUri;
45
+
46
+ /**
47
+ * Constructor
48
+ *
49
+ * @param CalDAV\Backend\NotificationSupport $caldavBackend
50
+ * @param string $principalUri
51
+ * @param NotificationInterface $notification
52
+ */
53
+ function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {
54
+
55
+ $this->caldavBackend = $caldavBackend;
56
+ $this->principalUri = $principalUri;
57
+ $this->notification = $notification;
58
+
59
+ }
60
+
61
+ /**
62
+ * Returns the path name for this notification
63
+ *
64
+ * @return string
65
+ */
66
+ function getName() {
67
+
68
+ return $this->notification->getId() . '.xml';
69
+
70
+ }
71
+
72
+ /**
73
+ * Returns the etag for the notification.
74
+ *
75
+ * The etag must be surrounded by litteral double-quotes.
76
+ *
77
+ * @return string
78
+ */
79
+ function getETag() {
80
+
81
+ return $this->notification->getETag();
82
+
83
+ }
84
+
85
+ /**
86
+ * This method must return an xml element, using the
87
+ * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
88
+ *
89
+ * @return NotificationInterface
90
+ */
91
+ function getNotificationType() {
92
+
93
+ return $this->notification;
94
+
95
+ }
96
+
97
+ /**
98
+ * Deletes this notification
99
+ *
100
+ * @return void
101
+ */
102
+ function delete() {
103
+
104
+ $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
105
+
106
+ }
107
+
108
+ /**
109
+ * Returns the owner principal
110
+ *
111
+ * This must be a url to a principal, or null if there's no owner
112
+ *
113
+ * @return string|null
114
+ */
115
+ function getOwner() {
116
+
117
+ return $this->principalUri;
118
+
119
+ }
120
+
121
+ }
vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Notifications;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\INode as BaseINode;
7
+ use Sabre\DAV\PropFind;
8
+ use Sabre\DAV\Server;
9
+ use Sabre\DAV\ServerPlugin;
10
+ use Sabre\DAVACL;
11
+ use Sabre\HTTP\RequestInterface;
12
+ use Sabre\HTTP\ResponseInterface;
13
+
14
+ /**
15
+ * Notifications plugin
16
+ *
17
+ * This plugin implements several features required by the caldav-notification
18
+ * draft specification.
19
+ *
20
+ * Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but
21
+ * this has since been split up.
22
+ *
23
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
24
+ * @author Evert Pot (http://evertpot.com/)
25
+ * @license http://sabre.io/license/ Modified BSD License
26
+ */
27
+ class Plugin extends ServerPlugin {
28
+
29
+ /**
30
+ * This is the namespace for the proprietary calendarserver extensions
31
+ */
32
+ const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
33
+
34
+ /**
35
+ * Reference to the main server object.
36
+ *
37
+ * @var Server
38
+ */
39
+ protected $server;
40
+
41
+ /**
42
+ * Returns a plugin name.
43
+ *
44
+ * Using this name other plugins will be able to access other plugins
45
+ * using \Sabre\DAV\Server::getPlugin
46
+ *
47
+ * @return string
48
+ */
49
+ function getPluginName() {
50
+
51
+ return 'notifications';
52
+
53
+ }
54
+
55
+ /**
56
+ * This initializes the plugin.
57
+ *
58
+ * This function is called by Sabre\DAV\Server, after
59
+ * addPlugin is called.
60
+ *
61
+ * This method should set up the required event subscriptions.
62
+ *
63
+ * @param Server $server
64
+ * @return void
65
+ */
66
+ function initialize(Server $server) {
67
+
68
+ $this->server = $server;
69
+ $server->on('method:GET', [$this, 'httpGet'], 90);
70
+ $server->on('propFind', [$this, 'propFind']);
71
+
72
+ $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
73
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
74
+
75
+ array_push($server->protectedProperties,
76
+ '{' . self::NS_CALENDARSERVER . '}notification-URL',
77
+ '{' . self::NS_CALENDARSERVER . '}notificationtype'
78
+ );
79
+
80
+ }
81
+
82
+ /**
83
+ * PropFind
84
+ *
85
+ * @param PropFind $propFind
86
+ * @param BaseINode $node
87
+ * @return void
88
+ */
89
+ function propFind(PropFind $propFind, BaseINode $node) {
90
+
91
+ $caldavPlugin = $this->server->getPlugin('caldav');
92
+
93
+ if ($node instanceof DAVACL\IPrincipal) {
94
+
95
+ $principalUrl = $node->getPrincipalUrl();
96
+
97
+ // notification-URL property
98
+ $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {
99
+
100
+ $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
101
+ return new DAV\Xml\Property\Href($notificationPath);
102
+
103
+ });
104
+
105
+ }
106
+
107
+ if ($node instanceof INode) {
108
+
109
+ $propFind->handle(
110
+ '{' . self::NS_CALENDARSERVER . '}notificationtype',
111
+ [$node, 'getNotificationType']
112
+ );
113
+
114
+ }
115
+
116
+ }
117
+
118
+ /**
119
+ * This event is triggered before the usual GET request handler.
120
+ *
121
+ * We use this to intercept GET calls to notification nodes, and return the
122
+ * proper response.
123
+ *
124
+ * @param RequestInterface $request
125
+ * @param ResponseInterface $response
126
+ * @return void
127
+ */
128
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
129
+
130
+ $path = $request->getPath();
131
+
132
+ try {
133
+ $node = $this->server->tree->getNodeForPath($path);
134
+ } catch (DAV\Exception\NotFound $e) {
135
+ return;
136
+ }
137
+
138
+ if (!$node instanceof INode)
139
+ return;
140
+
141
+ $writer = $this->server->xml->getWriter();
142
+ $writer->contextUri = $this->server->getBaseUri();
143
+ $writer->openMemory();
144
+ $writer->startDocument('1.0', 'UTF-8');
145
+ $writer->startElement('{http://calendarserver.org/ns/}notification');
146
+ $node->getNotificationType()->xmlSerializeFull($writer);
147
+ $writer->endElement();
148
+
149
+ $response->setHeader('Content-Type', 'application/xml');
150
+ $response->setHeader('ETag', $node->getETag());
151
+ $response->setStatus(200);
152
+ $response->setBody($writer->outputMemory());
153
+
154
+ // Return false to break the event chain.
155
+ return false;
156
+
157
+ }
158
+
159
+ /**
160
+ * Returns a bunch of meta-data about the plugin.
161
+ *
162
+ * Providing this information is optional, and is mainly displayed by the
163
+ * Browser plugin.
164
+ *
165
+ * The description key in the returned array may contain html and will not
166
+ * be sanitized.
167
+ *
168
+ * @return array
169
+ */
170
+ function getPluginInfo() {
171
+
172
+ return [
173
+ 'name' => $this->getPluginName(),
174
+ 'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
175
+ 'link' => 'http://sabre.io/dav/caldav-sharing/',
176
+ ];
177
+
178
+ }
179
+
180
+ }
vendor/sabre/dav/lib/CalDAV/Plugin.php ADDED
@@ -0,0 +1,1068 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use DateTimeZone;
6
+ use Sabre\DAV;
7
+ use Sabre\DAV\Exception\BadRequest;
8
+ use Sabre\DAV\INode;
9
+ use Sabre\DAV\MkCol;
10
+ use Sabre\DAV\Xml\Property\LocalHref;
11
+ use Sabre\DAVACL;
12
+ use Sabre\HTTP;
13
+ use Sabre\HTTP\RequestInterface;
14
+ use Sabre\HTTP\ResponseInterface;
15
+ use Sabre\Uri;
16
+ use Sabre\VObject;
17
+
18
+ /**
19
+ * CalDAV plugin
20
+ *
21
+ * This plugin provides functionality added by CalDAV (RFC 4791)
22
+ * It implements new reports, and the MKCALENDAR method.
23
+ *
24
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
25
+ * @author Evert Pot (http://evertpot.com/)
26
+ * @license http://sabre.io/license/ Modified BSD License
27
+ */
28
+ class Plugin extends DAV\ServerPlugin {
29
+
30
+ /**
31
+ * This is the official CalDAV namespace
32
+ */
33
+ const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
34
+
35
+ /**
36
+ * This is the namespace for the proprietary calendarserver extensions
37
+ */
38
+ const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
39
+
40
+ /**
41
+ * The hardcoded root for calendar objects. It is unfortunate
42
+ * that we're stuck with it, but it will have to do for now
43
+ */
44
+ const CALENDAR_ROOT = 'calendars';
45
+
46
+ /**
47
+ * Reference to server object
48
+ *
49
+ * @var DAV\Server
50
+ */
51
+ protected $server;
52
+
53
+ /**
54
+ * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
55
+ * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
56
+ * capping it to 10M here.
57
+ */
58
+ protected $maxResourceSize = 10000000;
59
+
60
+ /**
61
+ * Use this method to tell the server this plugin defines additional
62
+ * HTTP methods.
63
+ *
64
+ * This method is passed a uri. It should only return HTTP methods that are
65
+ * available for the specified uri.
66
+ *
67
+ * @param string $uri
68
+ * @return array
69
+ */
70
+ function getHTTPMethods($uri) {
71
+
72
+ // The MKCALENDAR is only available on unmapped uri's, whose
73
+ // parents extend IExtendedCollection
74
+ list($parent, $name) = Uri\split($uri);
75
+
76
+ $node = $this->server->tree->getNodeForPath($parent);
77
+
78
+ if ($node instanceof DAV\IExtendedCollection) {
79
+ try {
80
+ $node->getChild($name);
81
+ } catch (DAV\Exception\NotFound $e) {
82
+ return ['MKCALENDAR'];
83
+ }
84
+ }
85
+ return [];
86
+
87
+ }
88
+
89
+ /**
90
+ * Returns the path to a principal's calendar home.
91
+ *
92
+ * The return url must not end with a slash.
93
+ * This function should return null in case a principal did not have
94
+ * a calendar home.
95
+ *
96
+ * @param string $principalUrl
97
+ * @return string
98
+ */
99
+ function getCalendarHomeForPrincipal($principalUrl) {
100
+
101
+ // The default behavior for most sabre/dav servers is that there is a
102
+ // principals root node, which contains users directly under it.
103
+ //
104
+ // This function assumes that there are two components in a principal
105
+ // path. If there's more, we don't return a calendar home. This
106
+ // excludes things like the calendar-proxy-read principal (which it
107
+ // should).
108
+ $parts = explode('/', trim($principalUrl, '/'));
109
+ if (count($parts) !== 2) return;
110
+ if ($parts[0] !== 'principals') return;
111
+
112
+ return self::CALENDAR_ROOT . '/' . $parts[1];
113
+
114
+ }
115
+
116
+ /**
117
+ * Returns a list of features for the DAV: HTTP header.
118
+ *
119
+ * @return array
120
+ */
121
+ function getFeatures() {
122
+
123
+ return ['calendar-access', 'calendar-proxy'];
124
+
125
+ }
126
+
127
+ /**
128
+ * Returns a plugin name.
129
+ *
130
+ * Using this name other plugins will be able to access other plugins
131
+ * using DAV\Server::getPlugin
132
+ *
133
+ * @return string
134
+ */
135
+ function getPluginName() {
136
+
137
+ return 'caldav';
138
+
139
+ }
140
+
141
+ /**
142
+ * Returns a list of reports this plugin supports.
143
+ *
144
+ * This will be used in the {DAV:}supported-report-set property.
145
+ * Note that you still need to subscribe to the 'report' event to actually
146
+ * implement them
147
+ *
148
+ * @param string $uri
149
+ * @return array
150
+ */
151
+ function getSupportedReportSet($uri) {
152
+
153
+ $node = $this->server->tree->getNodeForPath($uri);
154
+
155
+ $reports = [];
156
+ if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
157
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
158
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
159
+ }
160
+ if ($node instanceof ICalendar) {
161
+ $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
162
+ }
163
+ // iCal has a bug where it assumes that sync support is enabled, only
164
+ // if we say we support it on the calendar-home, even though this is
165
+ // not actually the case.
166
+ if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
167
+ $reports[] = '{DAV:}sync-collection';
168
+ }
169
+ return $reports;
170
+
171
+ }
172
+
173
+ /**
174
+ * Initializes the plugin
175
+ *
176
+ * @param DAV\Server $server
177
+ * @return void
178
+ */
179
+ function initialize(DAV\Server $server) {
180
+
181
+ $this->server = $server;
182
+
183
+ $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
184
+ $server->on('report', [$this, 'report']);
185
+ $server->on('propFind', [$this, 'propFind']);
186
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
187
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
188
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
189
+ $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
190
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
191
+
192
+ $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
193
+ $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
194
+
195
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
196
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
197
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
198
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
199
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
200
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
201
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
202
+
203
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
204
+
205
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
206
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
207
+
208
+ array_push($server->protectedProperties,
209
+
210
+ '{' . self::NS_CALDAV . '}supported-calendar-component-set',
211
+ '{' . self::NS_CALDAV . '}supported-calendar-data',
212
+ '{' . self::NS_CALDAV . '}max-resource-size',
213
+ '{' . self::NS_CALDAV . '}min-date-time',
214
+ '{' . self::NS_CALDAV . '}max-date-time',
215
+ '{' . self::NS_CALDAV . '}max-instances',
216
+ '{' . self::NS_CALDAV . '}max-attendees-per-instance',
217
+ '{' . self::NS_CALDAV . '}calendar-home-set',
218
+ '{' . self::NS_CALDAV . '}supported-collation-set',
219
+ '{' . self::NS_CALDAV . '}calendar-data',
220
+
221
+ // CalendarServer extensions
222
+ '{' . self::NS_CALENDARSERVER . '}getctag',
223
+ '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
224
+ '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
225
+
226
+ );
227
+
228
+ if ($aclPlugin = $server->getPlugin('acl')) {
229
+ $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
230
+ }
231
+ }
232
+
233
+ /**
234
+ * This functions handles REPORT requests specific to CalDAV
235
+ *
236
+ * @param string $reportName
237
+ * @param mixed $report
238
+ * @param mixed $path
239
+ * @return bool
240
+ */
241
+ function report($reportName, $report, $path) {
242
+
243
+ switch ($reportName) {
244
+ case '{' . self::NS_CALDAV . '}calendar-multiget' :
245
+ $this->server->transactionType = 'report-calendar-multiget';
246
+ $this->calendarMultiGetReport($report);
247
+ return false;
248
+ case '{' . self::NS_CALDAV . '}calendar-query' :
249
+ $this->server->transactionType = 'report-calendar-query';
250
+ $this->calendarQueryReport($report);
251
+ return false;
252
+ case '{' . self::NS_CALDAV . '}free-busy-query' :
253
+ $this->server->transactionType = 'report-free-busy-query';
254
+ $this->freeBusyQueryReport($report);
255
+ return false;
256
+
257
+ }
258
+
259
+
260
+ }
261
+
262
+ /**
263
+ * This function handles the MKCALENDAR HTTP method, which creates
264
+ * a new calendar.
265
+ *
266
+ * @param RequestInterface $request
267
+ * @param ResponseInterface $response
268
+ * @return bool
269
+ */
270
+ function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {
271
+
272
+ $body = $request->getBodyAsString();
273
+ $path = $request->getPath();
274
+
275
+ $properties = [];
276
+
277
+ if ($body) {
278
+
279
+ try {
280
+ $mkcalendar = $this->server->xml->expect(
281
+ '{urn:ietf:params:xml:ns:caldav}mkcalendar',
282
+ $body
283
+ );
284
+ } catch (\Sabre\Xml\ParseException $e) {
285
+ throw new BadRequest($e->getMessage(), null, $e);
286
+ }
287
+ $properties = $mkcalendar->getProperties();
288
+
289
+ }
290
+
291
+ // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
292
+ // subscriptions. Before that it used MKCOL which was the correct way
293
+ // to do this.
294
+ //
295
+ // If the body had a {DAV:}resourcetype, it means we stumbled upon this
296
+ // request, and we simply use it instead of the pre-defined list.
297
+ if (isset($properties['{DAV:}resourcetype'])) {
298
+ $resourceType = $properties['{DAV:}resourcetype']->getValue();
299
+ } else {
300
+ $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
301
+ }
302
+
303
+ $this->server->createCollection($path, new MkCol($resourceType, $properties));
304
+
305
+ $response->setStatus(201);
306
+ $response->setHeader('Content-Length', 0);
307
+
308
+ // This breaks the method chain.
309
+ return false;
310
+ }
311
+
312
+ /**
313
+ * PropFind
314
+ *
315
+ * This method handler is invoked before any after properties for a
316
+ * resource are fetched. This allows us to add in any CalDAV specific
317
+ * properties.
318
+ *
319
+ * @param DAV\PropFind $propFind
320
+ * @param DAV\INode $node
321
+ * @return void
322
+ */
323
+ function propFind(DAV\PropFind $propFind, DAV\INode $node) {
324
+
325
+ $ns = '{' . self::NS_CALDAV . '}';
326
+
327
+ if ($node instanceof ICalendarObjectContainer) {
328
+
329
+ $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
330
+ $propFind->handle($ns . 'supported-calendar-data', function() {
331
+ return new Xml\Property\SupportedCalendarData();
332
+ });
333
+ $propFind->handle($ns . 'supported-collation-set', function() {
334
+ return new Xml\Property\SupportedCollationSet();
335
+ });
336
+
337
+ }
338
+
339
+ if ($node instanceof DAVACL\IPrincipal) {
340
+
341
+ $principalUrl = $node->getPrincipalUrl();
342
+
343
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
344
+
345
+ $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
346
+ if (is_null($calendarHomePath)) return null;
347
+ return new LocalHref($calendarHomePath . '/');
348
+
349
+ });
350
+ // The calendar-user-address-set property is basically mapped to
351
+ // the {DAV:}alternate-URI-set property.
352
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
353
+ $addresses = $node->getAlternateUriSet();
354
+ $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
355
+ return new LocalHref($addresses);
356
+ });
357
+ // For some reason somebody thought it was a good idea to add
358
+ // another one of these properties. We're supporting it too.
359
+ $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
360
+ $addresses = $node->getAlternateUriSet();
361
+ $emails = [];
362
+ foreach ($addresses as $address) {
363
+ if (substr($address, 0, 7) === 'mailto:') {
364
+ $emails[] = substr($address, 7);
365
+ }
366
+ }
367
+ return new Xml\Property\EmailAddressSet($emails);
368
+ });
369
+
370
+ // These two properties are shortcuts for ical to easily find
371
+ // other principals this principal has access to.
372
+ $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
373
+ $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
374
+
375
+ if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
376
+
377
+ $aclPlugin = $this->server->getPlugin('acl');
378
+ $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
379
+ $readList = [];
380
+ $writeList = [];
381
+
382
+ foreach ($membership as $group) {
383
+
384
+ $groupNode = $this->server->tree->getNodeForPath($group);
385
+
386
+ $listItem = Uri\split($group)[0] . '/';
387
+
388
+ // If the node is either ap proxy-read or proxy-write
389
+ // group, we grab the parent principal and add it to the
390
+ // list.
391
+ if ($groupNode instanceof Principal\IProxyRead) {
392
+ $readList[] = $listItem;
393
+ }
394
+ if ($groupNode instanceof Principal\IProxyWrite) {
395
+ $writeList[] = $listItem;
396
+ }
397
+
398
+ }
399
+
400
+ $propFind->set($propRead, new LocalHref($readList));
401
+ $propFind->set($propWrite, new LocalHref($writeList));
402
+
403
+ }
404
+
405
+ } // instanceof IPrincipal
406
+
407
+ if ($node instanceof ICalendarObject) {
408
+
409
+ // The calendar-data property is not supposed to be a 'real'
410
+ // property, but in large chunks of the spec it does act as such.
411
+ // Therefore we simply expose it as a property.
412
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
413
+ $val = $node->get();
414
+ if (is_resource($val))
415
+ $val = stream_get_contents($val);
416
+
417
+ // Taking out \r to not screw up the xml output
418
+ return str_replace("\r", "", $val);
419
+
420
+ });
421
+
422
+ }
423
+
424
+ }
425
+
426
+ /**
427
+ * This function handles the calendar-multiget REPORT.
428
+ *
429
+ * This report is used by the client to fetch the content of a series
430
+ * of urls. Effectively avoiding a lot of redundant requests.
431
+ *
432
+ * @param CalendarMultiGetReport $report
433
+ * @return void
434
+ */
435
+ function calendarMultiGetReport($report) {
436
+
437
+ $needsJson = $report->contentType === 'application/calendar+json';
438
+
439
+ $timeZones = [];
440
+ $propertyList = [];
441
+
442
+ $paths = array_map(
443
+ [$this->server, 'calculateUri'],
444
+ $report->hrefs
445
+ );
446
+
447
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
448
+
449
+ if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
450
+ $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
451
+
452
+ if ($report->expand) {
453
+ // We're expanding, and for that we need to figure out the
454
+ // calendar's timezone.
455
+ list($calendarPath) = Uri\split($uri);
456
+ if (!isset($timeZones[$calendarPath])) {
457
+ // Checking the calendar-timezone property.
458
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
459
+ $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
460
+ if (isset($tzResult[$tzProp])) {
461
+ // This property contains a VCALENDAR with a single
462
+ // VTIMEZONE.
463
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
464
+ $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
465
+ } else {
466
+ // Defaulting to UTC.
467
+ $timeZone = new DateTimeZone('UTC');
468
+ }
469
+ $timeZones[$calendarPath] = $timeZone;
470
+ }
471
+
472
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
473
+ }
474
+ if ($needsJson) {
475
+ $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
476
+ } else {
477
+ $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
478
+ }
479
+ // Destroy circular references so PHP will garbage collect the
480
+ // object.
481
+ $vObject->destroy();
482
+ }
483
+
484
+ $propertyList[] = $objProps;
485
+
486
+ }
487
+
488
+ $prefer = $this->server->getHTTPPrefer();
489
+
490
+ $this->server->httpResponse->setStatus(207);
491
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
492
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
493
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
494
+
495
+ }
496
+
497
+ /**
498
+ * This function handles the calendar-query REPORT
499
+ *
500
+ * This report is used by clients to request calendar objects based on
501
+ * complex conditions.
502
+ *
503
+ * @param Xml\Request\CalendarQueryReport $report
504
+ * @return void
505
+ */
506
+ function calendarQueryReport($report) {
507
+
508
+ $path = $this->server->getRequestUri();
509
+
510
+ $needsJson = $report->contentType === 'application/calendar+json';
511
+
512
+ $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
513
+ $depth = $this->server->getHTTPDepth(0);
514
+
515
+ // The default result is an empty array
516
+ $result = [];
517
+
518
+ $calendarTimeZone = null;
519
+ if ($report->expand) {
520
+ // We're expanding, and for that we need to figure out the
521
+ // calendar's timezone.
522
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
523
+ $tzResult = $this->server->getProperties($path, [$tzProp]);
524
+ if (isset($tzResult[$tzProp])) {
525
+ // This property contains a VCALENDAR with a single
526
+ // VTIMEZONE.
527
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
528
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
529
+
530
+ // Destroy circular references so PHP will garbage collect the
531
+ // object.
532
+ $vtimezoneObj->destroy();
533
+ } else {
534
+ // Defaulting to UTC.
535
+ $calendarTimeZone = new DateTimeZone('UTC');
536
+ }
537
+ }
538
+
539
+ // The calendarobject was requested directly. In this case we handle
540
+ // this locally.
541
+ if ($depth == 0 && $node instanceof ICalendarObject) {
542
+
543
+ $requestedCalendarData = true;
544
+ $requestedProperties = $report->properties;
545
+
546
+ if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
547
+
548
+ // We always retrieve calendar-data, as we need it for filtering.
549
+ $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
550
+
551
+ // If calendar-data wasn't explicitly requested, we need to remove
552
+ // it after processing.
553
+ $requestedCalendarData = false;
554
+ }
555
+
556
+ $properties = $this->server->getPropertiesForPath(
557
+ $path,
558
+ $requestedProperties,
559
+ 0
560
+ );
561
+
562
+ // This array should have only 1 element, the first calendar
563
+ // object.
564
+ $properties = current($properties);
565
+
566
+ // If there wasn't any calendar-data returned somehow, we ignore
567
+ // this.
568
+ if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
569
+
570
+ $validator = new CalendarQueryValidator();
571
+
572
+ $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
573
+ if ($validator->validate($vObject, $report->filters)) {
574
+
575
+ // If the client didn't require the calendar-data property,
576
+ // we won't give it back.
577
+ if (!$requestedCalendarData) {
578
+ unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
579
+ } else {
580
+
581
+
582
+ if ($report->expand) {
583
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
584
+ }
585
+ if ($needsJson) {
586
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
587
+ } elseif ($report->expand) {
588
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
589
+ }
590
+ }
591
+
592
+ $result = [$properties];
593
+
594
+ }
595
+ // Destroy circular references so PHP will garbage collect the
596
+ // object.
597
+ $vObject->destroy();
598
+
599
+ }
600
+
601
+ }
602
+
603
+ if ($node instanceof ICalendarObjectContainer && $depth === 0) {
604
+
605
+ if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
606
+ // Microsoft clients incorrectly supplied depth as 0, when it actually
607
+ // should have set depth to 1. We're implementing a workaround here
608
+ // to deal with this.
609
+ //
610
+ // This targets at least the following clients:
611
+ // Windows 10
612
+ // Windows Phone 8, 10
613
+ $depth = 1;
614
+ } else {
615
+ throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
616
+ }
617
+
618
+ }
619
+
620
+ // If we're dealing with a calendar, the calendar itself is responsible
621
+ // for the calendar-query.
622
+ if ($node instanceof ICalendarObjectContainer && $depth == 1) {
623
+
624
+ $nodePaths = $node->calendarQuery($report->filters);
625
+
626
+ foreach ($nodePaths as $path) {
627
+
628
+ list($properties) =
629
+ $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);
630
+
631
+ if (($needsJson || $report->expand)) {
632
+ $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);
633
+
634
+ if ($report->expand) {
635
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
636
+ }
637
+
638
+ if ($needsJson) {
639
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
640
+ } else {
641
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
642
+ }
643
+
644
+ // Destroy circular references so PHP will garbage collect the
645
+ // object.
646
+ $vObject->destroy();
647
+ }
648
+ $result[] = $properties;
649
+
650
+ }
651
+
652
+ }
653
+
654
+ $prefer = $this->server->getHTTPPrefer();
655
+
656
+ $this->server->httpResponse->setStatus(207);
657
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
658
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
659
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
660
+
661
+ }
662
+
663
+ /**
664
+ * This method is responsible for parsing the request and generating the
665
+ * response for the CALDAV:free-busy-query REPORT.
666
+ *
667
+ * @param Xml\Request\FreeBusyQueryReport $report
668
+ * @return void
669
+ */
670
+ protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {
671
+
672
+ $uri = $this->server->getRequestUri();
673
+
674
+ $acl = $this->server->getPlugin('acl');
675
+ if ($acl) {
676
+ $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
677
+ }
678
+
679
+ $calendar = $this->server->tree->getNodeForPath($uri);
680
+ if (!$calendar instanceof ICalendar) {
681
+ throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
682
+ }
683
+
684
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
685
+
686
+ // Figuring out the default timezone for the calendar, for floating
687
+ // times.
688
+ $calendarProps = $this->server->getProperties($uri, [$tzProp]);
689
+
690
+ if (isset($calendarProps[$tzProp])) {
691
+ $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
692
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
693
+ // Destroy circular references so PHP will garbage collect the object.
694
+ $vtimezoneObj->destroy();
695
+ } else {
696
+ $calendarTimeZone = new DateTimeZone('UTC');
697
+ }
698
+
699
+ // Doing a calendar-query first, to make sure we get the most
700
+ // performance.
701
+ $urls = $calendar->calendarQuery([
702
+ 'name' => 'VCALENDAR',
703
+ 'comp-filters' => [
704
+ [
705
+ 'name' => 'VEVENT',
706
+ 'comp-filters' => [],
707
+ 'prop-filters' => [],
708
+ 'is-not-defined' => false,
709
+ 'time-range' => [
710
+ 'start' => $report->start,
711
+ 'end' => $report->end,
712
+ ],
713
+ ],
714
+ ],
715
+ 'prop-filters' => [],
716
+ 'is-not-defined' => false,
717
+ 'time-range' => null,
718
+ ]);
719
+
720
+ $objects = array_map(function($url) use ($calendar) {
721
+ $obj = $calendar->getChild($url)->get();
722
+ return $obj;
723
+ }, $urls);
724
+
725
+ $generator = new VObject\FreeBusyGenerator();
726
+ $generator->setObjects($objects);
727
+ $generator->setTimeRange($report->start, $report->end);
728
+ $generator->setTimeZone($calendarTimeZone);
729
+ $result = $generator->getResult();
730
+ $result = $result->serialize();
731
+
732
+ $this->server->httpResponse->setStatus(200);
733
+ $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
734
+ $this->server->httpResponse->setHeader('Content-Length', strlen($result));
735
+ $this->server->httpResponse->setBody($result);
736
+
737
+ }
738
+
739
+ /**
740
+ * This method is triggered before a file gets updated with new content.
741
+ *
742
+ * This plugin uses this method to ensure that CalDAV objects receive
743
+ * valid calendar data.
744
+ *
745
+ * @param string $path
746
+ * @param DAV\IFile $node
747
+ * @param resource $data
748
+ * @param bool $modified Should be set to true, if this event handler
749
+ * changed &$data.
750
+ * @return void
751
+ */
752
+ function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
753
+
754
+ if (!$node instanceof ICalendarObject)
755
+ return;
756
+
757
+ // We're onyl interested in ICalendarObject nodes that are inside of a
758
+ // real calendar. This is to avoid triggering validation and scheduling
759
+ // for non-calendars (such as an inbox).
760
+ list($parent) = Uri\split($path);
761
+ $parentNode = $this->server->tree->getNodeForPath($parent);
762
+
763
+ if (!$parentNode instanceof ICalendar)
764
+ return;
765
+
766
+ $this->validateICalendar(
767
+ $data,
768
+ $path,
769
+ $modified,
770
+ $this->server->httpRequest,
771
+ $this->server->httpResponse,
772
+ false
773
+ );
774
+
775
+ }
776
+
777
+ /**
778
+ * This method is triggered before a new file is created.
779
+ *
780
+ * This plugin uses this method to ensure that newly created calendar
781
+ * objects contain valid calendar data.
782
+ *
783
+ * @param string $path
784
+ * @param resource $data
785
+ * @param DAV\ICollection $parentNode
786
+ * @param bool $modified Should be set to true, if this event handler
787
+ * changed &$data.
788
+ * @return void
789
+ */
790
+ function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
791
+
792
+ if (!$parentNode instanceof ICalendar)
793
+ return;
794
+
795
+ $this->validateICalendar(
796
+ $data,
797
+ $path,
798
+ $modified,
799
+ $this->server->httpRequest,
800
+ $this->server->httpResponse,
801
+ true
802
+ );
803
+
804
+ }
805
+
806
+ /**
807
+ * Checks if the submitted iCalendar data is in fact, valid.
808
+ *
809
+ * An exception is thrown if it's not.
810
+ *
811
+ * @param resource|string $data
812
+ * @param string $path
813
+ * @param bool $modified Should be set to true, if this event handler
814
+ * changed &$data.
815
+ * @param RequestInterface $request The http request.
816
+ * @param ResponseInterface $response The http response.
817
+ * @param bool $isNew Is the item a new one, or an update.
818
+ * @return void
819
+ */
820
+ protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {
821
+
822
+ // If it's a stream, we convert it to a string first.
823
+ if (is_resource($data)) {
824
+ $data = stream_get_contents($data);
825
+ }
826
+
827
+ $before = $data;
828
+
829
+ try {
830
+
831
+ // If the data starts with a [, we can reasonably assume we're dealing
832
+ // with a jCal object.
833
+ if (substr($data, 0, 1) === '[') {
834
+ $vobj = VObject\Reader::readJson($data);
835
+
836
+ // Converting $data back to iCalendar, as that's what we
837
+ // technically support everywhere.
838
+ $data = $vobj->serialize();
839
+ $modified = true;
840
+ } else {
841
+ $vobj = VObject\Reader::read($data);
842
+ }
843
+
844
+ } catch (VObject\ParseException $e) {
845
+
846
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
847
+
848
+ }
849
+
850
+ if ($vobj->name !== 'VCALENDAR') {
851
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
852
+ }
853
+
854
+ $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
855
+
856
+ // Get the Supported Components for the target calendar
857
+ list($parentPath) = Uri\split($path);
858
+ $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
859
+
860
+ if (isset($calendarProperties[$sCCS])) {
861
+ $supportedComponents = $calendarProperties[$sCCS]->getValue();
862
+ } else {
863
+ $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
864
+ }
865
+
866
+ $foundType = null;
867
+
868
+ foreach ($vobj->getComponents() as $component) {
869
+ switch ($component->name) {
870
+ case 'VTIMEZONE' :
871
+ continue 2;
872
+ case 'VEVENT' :
873
+ case 'VTODO' :
874
+ case 'VJOURNAL' :
875
+ $foundType = $component->name;
876
+ break;
877
+ }
878
+
879
+ }
880
+
881
+ if (!$foundType || !in_array($foundType, $supportedComponents)) {
882
+ throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
883
+ }
884
+
885
+ $options = VObject\Node::PROFILE_CALDAV;
886
+ $prefer = $this->server->getHTTPPrefer();
887
+
888
+ if ($prefer['handling'] !== 'strict') {
889
+ $options |= VObject\Node::REPAIR;
890
+ }
891
+
892
+ $messages = $vobj->validate($options);
893
+
894
+ $highestLevel = 0;
895
+ $warningMessage = null;
896
+
897
+ // $messages contains a list of problems with the vcard, along with
898
+ // their severity.
899
+ foreach ($messages as $message) {
900
+
901
+ if ($message['level'] > $highestLevel) {
902
+ // Recording the highest reported error level.
903
+ $highestLevel = $message['level'];
904
+ $warningMessage = $message['message'];
905
+ }
906
+ switch ($message['level']) {
907
+
908
+ case 1 :
909
+ // Level 1 means that there was a problem, but it was repaired.
910
+ $modified = true;
911
+ break;
912
+ case 2 :
913
+ // Level 2 means a warning, but not critical
914
+ break;
915
+ case 3 :
916
+ // Level 3 means a critical error
917
+ throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);
918
+
919
+ }
920
+
921
+ }
922
+ if ($warningMessage) {
923
+ $response->setHeader(
924
+ 'X-Sabre-Ew-Gross',
925
+ 'iCalendar validation warning: ' . $warningMessage
926
+ );
927
+ }
928
+
929
+ // We use an extra variable to allow event handles to tell us whether
930
+ // the object was modified or not.
931
+ //
932
+ // This helps us determine if we need to re-serialize the object.
933
+ $subModified = false;
934
+
935
+ $this->server->emit(
936
+ 'calendarObjectChange',
937
+ [
938
+ $request,
939
+ $response,
940
+ $vobj,
941
+ $parentPath,
942
+ &$subModified,
943
+ $isNew
944
+ ]
945
+ );
946
+
947
+ if ($modified || $subModified) {
948
+ // An event handler told us that it modified the object.
949
+ $data = $vobj->serialize();
950
+
951
+ // Using md5 to figure out if there was an *actual* change.
952
+ if (!$modified && strcmp($data, $before) !== 0) {
953
+ $modified = true;
954
+ }
955
+
956
+ }
957
+
958
+ // Destroy circular references so PHP will garbage collect the object.
959
+ $vobj->destroy();
960
+
961
+ }
962
+
963
+ /**
964
+ * This method is triggered whenever a subsystem reqeuests the privileges
965
+ * that are supported on a particular node.
966
+ *
967
+ * @param INode $node
968
+ * @param array $supportedPrivilegeSet
969
+ */
970
+ function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
971
+
972
+ if ($node instanceof ICalendar) {
973
+ $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
974
+ 'abstract' => false,
975
+ 'aggregates' => [],
976
+ ];
977
+ }
978
+ }
979
+
980
+ /**
981
+ * This method is used to generate HTML output for the
982
+ * DAV\Browser\Plugin. This allows us to generate an interface users
983
+ * can use to create new calendars.
984
+ *
985
+ * @param DAV\INode $node
986
+ * @param string $output
987
+ * @return bool
988
+ */
989
+ function htmlActionsPanel(DAV\INode $node, &$output) {
990
+
991
+ if (!$node instanceof CalendarHome)
992
+ return;
993
+
994
+ $output .= '<tr><td colspan="2"><form method="post" action="">
995
+ <h3>Create new calendar</h3>
996
+ <input type="hidden" name="sabreAction" value="mkcol" />
997
+ <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
998
+ <label>Name (uri):</label> <input type="text" name="name" /><br />
999
+ <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
1000
+ <input type="submit" value="create" />
1001
+ </form>
1002
+ </td></tr>';
1003
+
1004
+ return false;
1005
+
1006
+ }
1007
+
1008
+ /**
1009
+ * This event is triggered after GET requests.
1010
+ *
1011
+ * This is used to transform data into jCal, if this was requested.
1012
+ *
1013
+ * @param RequestInterface $request
1014
+ * @param ResponseInterface $response
1015
+ * @return void
1016
+ */
1017
+ function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
1018
+
1019
+ if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
1020
+ return;
1021
+ }
1022
+
1023
+ $result = HTTP\Util::negotiate(
1024
+ $request->getHeader('Accept'),
1025
+ ['text/calendar', 'application/calendar+json']
1026
+ );
1027
+
1028
+ if ($result !== 'application/calendar+json') {
1029
+ // Do nothing
1030
+ return;
1031
+ }
1032
+
1033
+ // Transforming.
1034
+ $vobj = VObject\Reader::read($response->getBody());
1035
+
1036
+ $jsonBody = json_encode($vobj->jsonSerialize());
1037
+ $response->setBody($jsonBody);
1038
+
1039
+ // Destroy circular references so PHP will garbage collect the object.
1040
+ $vobj->destroy();
1041
+
1042
+ $response->setHeader('Content-Type', 'application/calendar+json');
1043
+ $response->setHeader('Content-Length', strlen($jsonBody));
1044
+
1045
+ }
1046
+
1047
+ /**
1048
+ * Returns a bunch of meta-data about the plugin.
1049
+ *
1050
+ * Providing this information is optional, and is mainly displayed by the
1051
+ * Browser plugin.
1052
+ *
1053
+ * The description key in the returned array may contain html and will not
1054
+ * be sanitized.
1055
+ *
1056
+ * @return array
1057
+ */
1058
+ function getPluginInfo() {
1059
+
1060
+ return [
1061
+ 'name' => $this->getPluginName(),
1062
+ 'description' => 'Adds support for CalDAV (rfc4791)',
1063
+ 'link' => 'http://sabre.io/dav/caldav/',
1064
+ ];
1065
+
1066
+ }
1067
+
1068
+ }
vendor/sabre/dav/lib/CalDAV/Principal/Collection.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAVACL;
6
+
7
+ /**
8
+ * Principal collection
9
+ *
10
+ * This is an alternative collection to the standard ACL principal collection.
11
+ * This collection adds support for the calendar-proxy-read and
12
+ * calendar-proxy-write sub-principals, as defined by the caldav-proxy
13
+ * specification.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class Collection extends DAVACL\PrincipalCollection {
20
+
21
+ /**
22
+ * Returns a child object based on principal information
23
+ *
24
+ * @param array $principalInfo
25
+ * @return User
26
+ */
27
+ function getChildForPrincipal(array $principalInfo) {
28
+
29
+ return new User($this->principalBackend, $principalInfo);
30
+
31
+ }
32
+
33
+ }
vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAVACL;
6
+
7
+ /**
8
+ * ProxyRead principal interface
9
+ *
10
+ * Any principal node implementing this interface will be picked up as a 'proxy
11
+ * principal group'.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ interface IProxyRead extends DAVACL\IPrincipal {
18
+
19
+ }
vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAVACL;
6
+
7
+ /**
8
+ * ProxyWrite principal interface
9
+ *
10
+ * Any principal node implementing this interface will be picked up as a 'proxy
11
+ * principal group'.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ interface IProxyWrite extends DAVACL\IPrincipal {
18
+
19
+ }
vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAVACL;
7
+
8
+ /**
9
+ * ProxyRead principal
10
+ *
11
+ * This class represents a principal group, hosted under the main principal.
12
+ * This is needed to implement 'Calendar delegation' support. This class is
13
+ * instantiated by User.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class ProxyRead implements IProxyRead {
20
+
21
+ /**
22
+ * Principal information from the parent principal.
23
+ *
24
+ * @var array
25
+ */
26
+ protected $principalInfo;
27
+
28
+ /**
29
+ * Principal backend
30
+ *
31
+ * @var DAVACL\PrincipalBackend\BackendInterface
32
+ */
33
+ protected $principalBackend;
34
+
35
+ /**
36
+ * Creates the object.
37
+ *
38
+ * Note that you MUST supply the parent principal information.
39
+ *
40
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
41
+ * @param array $principalInfo
42
+ */
43
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
44
+
45
+ $this->principalInfo = $principalInfo;
46
+ $this->principalBackend = $principalBackend;
47
+
48
+ }
49
+
50
+ /**
51
+ * Returns this principals name.
52
+ *
53
+ * @return string
54
+ */
55
+ function getName() {
56
+
57
+ return 'calendar-proxy-read';
58
+
59
+ }
60
+
61
+ /**
62
+ * Returns the last modification time
63
+ *
64
+ * @return null
65
+ */
66
+ function getLastModified() {
67
+
68
+ return null;
69
+
70
+ }
71
+
72
+ /**
73
+ * Deletes the current node
74
+ *
75
+ * @throws DAV\Exception\Forbidden
76
+ * @return void
77
+ */
78
+ function delete() {
79
+
80
+ throw new DAV\Exception\Forbidden('Permission denied to delete node');
81
+
82
+ }
83
+
84
+ /**
85
+ * Renames the node
86
+ *
87
+ * @param string $name The new name
88
+ * @throws DAV\Exception\Forbidden
89
+ * @return void
90
+ */
91
+ function setName($name) {
92
+
93
+ throw new DAV\Exception\Forbidden('Permission denied to rename file');
94
+
95
+ }
96
+
97
+
98
+ /**
99
+ * Returns a list of alternative urls for a principal
100
+ *
101
+ * This can for example be an email address, or ldap url.
102
+ *
103
+ * @return array
104
+ */
105
+ function getAlternateUriSet() {
106
+
107
+ return [];
108
+
109
+ }
110
+
111
+ /**
112
+ * Returns the full principal url
113
+ *
114
+ * @return string
115
+ */
116
+ function getPrincipalUrl() {
117
+
118
+ return $this->principalInfo['uri'] . '/' . $this->getName();
119
+
120
+ }
121
+
122
+ /**
123
+ * Returns the list of group members
124
+ *
125
+ * If this principal is a group, this function should return
126
+ * all member principal uri's for the group.
127
+ *
128
+ * @return array
129
+ */
130
+ function getGroupMemberSet() {
131
+
132
+ return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
133
+
134
+ }
135
+
136
+ /**
137
+ * Returns the list of groups this principal is member of
138
+ *
139
+ * If this principal is a member of a (list of) groups, this function
140
+ * should return a list of principal uri's for it's members.
141
+ *
142
+ * @return array
143
+ */
144
+ function getGroupMembership() {
145
+
146
+ return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
147
+
148
+ }
149
+
150
+ /**
151
+ * Sets a list of group members
152
+ *
153
+ * If this principal is a group, this method sets all the group members.
154
+ * The list of members is always overwritten, never appended to.
155
+ *
156
+ * This method should throw an exception if the members could not be set.
157
+ *
158
+ * @param array $principals
159
+ * @return void
160
+ */
161
+ function setGroupMemberSet(array $principals) {
162
+
163
+ $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
164
+
165
+ }
166
+
167
+ /**
168
+ * Returns the displayname
169
+ *
170
+ * This should be a human readable name for the principal.
171
+ * If none is available, return the nodename.
172
+ *
173
+ * @return string
174
+ */
175
+ function getDisplayName() {
176
+
177
+ return $this->getName();
178
+
179
+ }
180
+
181
+ }
vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAVACL;
7
+
8
+ /**
9
+ * ProxyWrite principal
10
+ *
11
+ * This class represents a principal group, hosted under the main principal.
12
+ * This is needed to implement 'Calendar delegation' support. This class is
13
+ * instantiated by User.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class ProxyWrite implements IProxyWrite {
20
+
21
+ /**
22
+ * Parent principal information
23
+ *
24
+ * @var array
25
+ */
26
+ protected $principalInfo;
27
+
28
+ /**
29
+ * Principal Backend
30
+ *
31
+ * @var DAVACL\PrincipalBackend\BackendInterface
32
+ */
33
+ protected $principalBackend;
34
+
35
+ /**
36
+ * Creates the object
37
+ *
38
+ * Note that you MUST supply the parent principal information.
39
+ *
40
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
41
+ * @param array $principalInfo
42
+ */
43
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
44
+
45
+ $this->principalInfo = $principalInfo;
46
+ $this->principalBackend = $principalBackend;
47
+
48
+ }
49
+
50
+ /**
51
+ * Returns this principals name.
52
+ *
53
+ * @return string
54
+ */
55
+ function getName() {
56
+
57
+ return 'calendar-proxy-write';
58
+
59
+ }
60
+
61
+ /**
62
+ * Returns the last modification time
63
+ *
64
+ * @return null
65
+ */
66
+ function getLastModified() {
67
+
68
+ return null;
69
+
70
+ }
71
+
72
+ /**
73
+ * Deletes the current node
74
+ *
75
+ * @throws DAV\Exception\Forbidden
76
+ * @return void
77
+ */
78
+ function delete() {
79
+
80
+ throw new DAV\Exception\Forbidden('Permission denied to delete node');
81
+
82
+ }
83
+
84
+ /**
85
+ * Renames the node
86
+ *
87
+ * @param string $name The new name
88
+ * @throws DAV\Exception\Forbidden
89
+ * @return void
90
+ */
91
+ function setName($name) {
92
+
93
+ throw new DAV\Exception\Forbidden('Permission denied to rename file');
94
+
95
+ }
96
+
97
+
98
+ /**
99
+ * Returns a list of alternative urls for a principal
100
+ *
101
+ * This can for example be an email address, or ldap url.
102
+ *
103
+ * @return array
104
+ */
105
+ function getAlternateUriSet() {
106
+
107
+ return [];
108
+
109
+ }
110
+
111
+ /**
112
+ * Returns the full principal url
113
+ *
114
+ * @return string
115
+ */
116
+ function getPrincipalUrl() {
117
+
118
+ return $this->principalInfo['uri'] . '/' . $this->getName();
119
+
120
+ }
121
+
122
+ /**
123
+ * Returns the list of group members
124
+ *
125
+ * If this principal is a group, this function should return
126
+ * all member principal uri's for the group.
127
+ *
128
+ * @return array
129
+ */
130
+ function getGroupMemberSet() {
131
+
132
+ return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
133
+
134
+ }
135
+
136
+ /**
137
+ * Returns the list of groups this principal is member of
138
+ *
139
+ * If this principal is a member of a (list of) groups, this function
140
+ * should return a list of principal uri's for it's members.
141
+ *
142
+ * @return array
143
+ */
144
+ function getGroupMembership() {
145
+
146
+ return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
147
+
148
+ }
149
+
150
+ /**
151
+ * Sets a list of group members
152
+ *
153
+ * If this principal is a group, this method sets all the group members.
154
+ * The list of members is always overwritten, never appended to.
155
+ *
156
+ * This method should throw an exception if the members could not be set.
157
+ *
158
+ * @param array $principals
159
+ * @return void
160
+ */
161
+ function setGroupMemberSet(array $principals) {
162
+
163
+ $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
164
+
165
+ }
166
+
167
+ /**
168
+ * Returns the displayname
169
+ *
170
+ * This should be a human readable name for the principal.
171
+ * If none is available, return the nodename.
172
+ *
173
+ * @return string
174
+ */
175
+ function getDisplayName() {
176
+
177
+ return $this->getName();
178
+
179
+ }
180
+
181
+ }
vendor/sabre/dav/lib/CalDAV/Principal/User.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Principal;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAVACL;
7
+
8
+ /**
9
+ * CalDAV principal
10
+ *
11
+ * This is a standard user-principal for CalDAV. This principal is also a
12
+ * collection and returns the caldav-proxy-read and caldav-proxy-write child
13
+ * principals.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class User extends DAVACL\Principal implements DAV\ICollection {
20
+
21
+ /**
22
+ * Creates a new file in the directory
23
+ *
24
+ * @param string $name Name of the file
25
+ * @param resource $data Initial payload, passed as a readable stream resource.
26
+ * @throws DAV\Exception\Forbidden
27
+ * @return void
28
+ */
29
+ function createFile($name, $data = null) {
30
+
31
+ throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
32
+
33
+ }
34
+
35
+ /**
36
+ * Creates a new subdirectory
37
+ *
38
+ * @param string $name
39
+ * @throws DAV\Exception\Forbidden
40
+ * @return void
41
+ */
42
+ function createDirectory($name) {
43
+
44
+ throw new DAV\Exception\Forbidden('Permission denied to create directory');
45
+
46
+ }
47
+
48
+ /**
49
+ * Returns a specific child node, referenced by its name
50
+ *
51
+ * @param string $name
52
+ * @return DAV\INode
53
+ */
54
+ function getChild($name) {
55
+
56
+ $principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
57
+ if (!$principal) {
58
+ throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
59
+ }
60
+ if ($name === 'calendar-proxy-read')
61
+ return new ProxyRead($this->principalBackend, $this->principalProperties);
62
+
63
+ if ($name === 'calendar-proxy-write')
64
+ return new ProxyWrite($this->principalBackend, $this->principalProperties);
65
+
66
+ throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
67
+
68
+ }
69
+
70
+ /**
71
+ * Returns an array with all the child nodes
72
+ *
73
+ * @return DAV\INode[]
74
+ */
75
+ function getChildren() {
76
+
77
+ $r = [];
78
+ if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
79
+ $r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
80
+ }
81
+ if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
82
+ $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
83
+ }
84
+
85
+ return $r;
86
+
87
+ }
88
+
89
+ /**
90
+ * Returns whether or not the child node exists
91
+ *
92
+ * @param string $name
93
+ * @return bool
94
+ */
95
+ function childExists($name) {
96
+
97
+ try {
98
+ $this->getChild($name);
99
+ return true;
100
+ } catch (DAV\Exception\NotFound $e) {
101
+ return false;
102
+ }
103
+
104
+ }
105
+
106
+ /**
107
+ * Returns a list of ACE's for this node.
108
+ *
109
+ * Each ACE has the following properties:
110
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
111
+ * currently the only supported privileges
112
+ * * 'principal', a url to the principal who owns the node
113
+ * * 'protected' (optional), indicating that this ACE is not allowed to
114
+ * be updated.
115
+ *
116
+ * @return array
117
+ */
118
+ function getACL() {
119
+
120
+ $acl = parent::getACL();
121
+ $acl[] = [
122
+ 'privilege' => '{DAV:}read',
123
+ 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
124
+ 'protected' => true,
125
+ ];
126
+ $acl[] = [
127
+ 'privilege' => '{DAV:}read',
128
+ 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
129
+ 'protected' => true,
130
+ ];
131
+ return $acl;
132
+
133
+ }
134
+
135
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ /**
6
+ * Implement this interface to have a node be recognized as a CalDAV scheduling
7
+ * inbox.
8
+ *
9
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
10
+ * @author Evert Pot (http://evertpot.com/)
11
+ * @license http://sabre.io/license/ Modified BSD License
12
+ */
13
+ interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL {
14
+
15
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\VObject\ITip;
7
+
8
+ /**
9
+ * iMIP handler.
10
+ *
11
+ * This class is responsible for sending out iMIP messages. iMIP is the
12
+ * email-based transport for iTIP. iTIP deals with scheduling operations for
13
+ * iCalendar objects.
14
+ *
15
+ * If you want to customize the email that gets sent out, you can do so by
16
+ * extending this class and overriding the sendMessage method.
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://evertpot.com/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class IMipPlugin extends DAV\ServerPlugin {
23
+
24
+ /**
25
+ * Email address used in From: header.
26
+ *
27
+ * @var string
28
+ */
29
+ protected $senderEmail;
30
+
31
+ /**
32
+ * ITipMessage
33
+ *
34
+ * @var ITip\Message
35
+ */
36
+ protected $itipMessage;
37
+
38
+ /**
39
+ * Creates the email handler.
40
+ *
41
+ * @param string $senderEmail. The 'senderEmail' is the email that shows up
42
+ * in the 'From:' address. This should
43
+ * generally be some kind of no-reply email
44
+ * address you own.
45
+ */
46
+ function __construct($senderEmail) {
47
+
48
+ $this->senderEmail = $senderEmail;
49
+
50
+ }
51
+
52
+ /*
53
+ * This initializes the plugin.
54
+ *
55
+ * This function is called by Sabre\DAV\Server, after
56
+ * addPlugin is called.
57
+ *
58
+ * This method should set up the required event subscriptions.
59
+ *
60
+ * @param DAV\Server $server
61
+ * @return void
62
+ */
63
+ function initialize(DAV\Server $server) {
64
+
65
+ $server->on('schedule', [$this, 'schedule'], 120);
66
+
67
+ }
68
+
69
+ /**
70
+ * Returns a plugin name.
71
+ *
72
+ * Using this name other plugins will be able to access other plugins
73
+ * using \Sabre\DAV\Server::getPlugin
74
+ *
75
+ * @return string
76
+ */
77
+ function getPluginName() {
78
+
79
+ return 'imip';
80
+
81
+ }
82
+
83
+ /**
84
+ * Event handler for the 'schedule' event.
85
+ *
86
+ * @param ITip\Message $iTipMessage
87
+ * @return void
88
+ */
89
+ function schedule(ITip\Message $iTipMessage) {
90
+
91
+ // Not sending any emails if the system considers the update
92
+ // insignificant.
93
+ if (!$iTipMessage->significantChange) {
94
+ if (!$iTipMessage->scheduleStatus) {
95
+ $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
96
+ }
97
+ return;
98
+ }
99
+
100
+ $summary = $iTipMessage->message->VEVENT->SUMMARY;
101
+
102
+ if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto')
103
+ return;
104
+
105
+ if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto')
106
+ return;
107
+
108
+ $sender = substr($iTipMessage->sender, 7);
109
+ $recipient = substr($iTipMessage->recipient, 7);
110
+
111
+ if ($iTipMessage->senderName) {
112
+ $sender = $iTipMessage->senderName . ' <' . $sender . '>';
113
+ }
114
+ if ($iTipMessage->recipientName) {
115
+ $recipient = $iTipMessage->recipientName . ' <' . $recipient . '>';
116
+ }
117
+
118
+ $subject = 'SabreDAV iTIP message';
119
+ switch (strtoupper($iTipMessage->method)) {
120
+ case 'REPLY' :
121
+ $subject = 'Re: ' . $summary;
122
+ break;
123
+ case 'REQUEST' :
124
+ $subject = $summary;
125
+ break;
126
+ case 'CANCEL' :
127
+ $subject = 'Cancelled: ' . $summary;
128
+ break;
129
+ }
130
+
131
+ $headers = [
132
+ 'Reply-To: ' . $sender,
133
+ 'From: ' . $this->senderEmail,
134
+ 'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method,
135
+ ];
136
+ if (DAV\Server::$exposeVersion) {
137
+ $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION;
138
+ }
139
+ $this->mail(
140
+ $recipient,
141
+ $subject,
142
+ $iTipMessage->message->serialize(),
143
+ $headers
144
+ );
145
+ $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
146
+
147
+ }
148
+
149
+ // @codeCoverageIgnoreStart
150
+ // This is deemed untestable in a reasonable manner
151
+
152
+ /**
153
+ * This function is responsible for sending the actual email.
154
+ *
155
+ * @param string $to Recipient email address
156
+ * @param string $subject Subject of the email
157
+ * @param string $body iCalendar body
158
+ * @param array $headers List of headers
159
+ * @return void
160
+ */
161
+ protected function mail($to, $subject, $body, array $headers) {
162
+
163
+ mail($to, $subject, $body, implode("\r\n", $headers));
164
+
165
+ }
166
+
167
+ // @codeCoverageIgnoreEnd
168
+
169
+ /**
170
+ * Returns a bunch of meta-data about the plugin.
171
+ *
172
+ * Providing this information is optional, and is mainly displayed by the
173
+ * Browser plugin.
174
+ *
175
+ * The description key in the returned array may contain html and will not
176
+ * be sanitized.
177
+ *
178
+ * @return array
179
+ */
180
+ function getPluginInfo() {
181
+
182
+ return [
183
+ 'name' => $this->getPluginName(),
184
+ 'description' => 'Email delivery (rfc6047) for CalDAV scheduling',
185
+ 'link' => 'http://sabre.io/dav/scheduling/',
186
+ ];
187
+
188
+ }
189
+
190
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ /**
6
+ * Implement this interface to have a node be recognized as a CalDAV scheduling
7
+ * outbox.
8
+ *
9
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
10
+ * @author Evert Pot (http://evertpot.com/)
11
+ * @license http://sabre.io/license/ Modified BSD License
12
+ */
13
+ interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL {
14
+
15
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ /**
6
+ * The SchedulingObject represents a scheduling object in the Inbox collection
7
+ *
8
+ * @license http://sabre.io/license/ Modified BSD License
9
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
10
+ */
11
+ interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject {
12
+
13
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\CalDAV\Backend;
7
+ use Sabre\DAV;
8
+ use Sabre\DAVACL;
9
+ use Sabre\VObject;
10
+
11
+ /**
12
+ * The CalDAV scheduling inbox
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ class Inbox extends DAV\Collection implements IInbox {
19
+
20
+ use DAVACL\ACLTrait;
21
+
22
+ /**
23
+ * CalDAV backend
24
+ *
25
+ * @var Backend\BackendInterface
26
+ */
27
+ protected $caldavBackend;
28
+
29
+ /**
30
+ * The principal Uri
31
+ *
32
+ * @var string
33
+ */
34
+ protected $principalUri;
35
+
36
+ /**
37
+ * Constructor
38
+ *
39
+ * @param Backend\SchedulingSupport $caldavBackend
40
+ * @param string $principalUri
41
+ */
42
+ function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri) {
43
+
44
+ $this->caldavBackend = $caldavBackend;
45
+ $this->principalUri = $principalUri;
46
+
47
+ }
48
+
49
+ /**
50
+ * Returns the name of the node.
51
+ *
52
+ * This is used to generate the url.
53
+ *
54
+ * @return string
55
+ */
56
+ function getName() {
57
+
58
+ return 'inbox';
59
+
60
+ }
61
+
62
+ /**
63
+ * Returns an array with all the child nodes
64
+ *
65
+ * @return \Sabre\DAV\INode[]
66
+ */
67
+ function getChildren() {
68
+
69
+ $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
70
+ $children = [];
71
+ foreach ($objs as $obj) {
72
+ //$obj['acl'] = $this->getACL();
73
+ $obj['principaluri'] = $this->principalUri;
74
+ $children[] = new SchedulingObject($this->caldavBackend, $obj);
75
+ }
76
+ return $children;
77
+
78
+ }
79
+
80
+ /**
81
+ * Creates a new file in the directory
82
+ *
83
+ * Data will either be supplied as a stream resource, or in certain cases
84
+ * as a string. Keep in mind that you may have to support either.
85
+ *
86
+ * After successful creation of the file, you may choose to return the ETag
87
+ * of the new file here.
88
+ *
89
+ * The returned ETag must be surrounded by double-quotes (The quotes should
90
+ * be part of the actual string).
91
+ *
92
+ * If you cannot accurately determine the ETag, you should not return it.
93
+ * If you don't store the file exactly as-is (you're transforming it
94
+ * somehow) you should also not return an ETag.
95
+ *
96
+ * This means that if a subsequent GET to this new file does not exactly
97
+ * return the same contents of what was submitted here, you are strongly
98
+ * recommended to omit the ETag.
99
+ *
100
+ * @param string $name Name of the file
101
+ * @param resource|string $data Initial payload
102
+ * @return null|string
103
+ */
104
+ function createFile($name, $data = null) {
105
+
106
+ $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
107
+
108
+ }
109
+
110
+ /**
111
+ * Returns the owner principal
112
+ *
113
+ * This must be a url to a principal, or null if there's no owner
114
+ *
115
+ * @return string|null
116
+ */
117
+ function getOwner() {
118
+
119
+ return $this->principalUri;
120
+
121
+ }
122
+
123
+ /**
124
+ * Returns a list of ACE's for this node.
125
+ *
126
+ * Each ACE has the following properties:
127
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
128
+ * currently the only supported privileges
129
+ * * 'principal', a url to the principal who owns the node
130
+ * * 'protected' (optional), indicating that this ACE is not allowed to
131
+ * be updated.
132
+ *
133
+ * @return array
134
+ */
135
+ function getACL() {
136
+
137
+ return [
138
+ [
139
+ 'privilege' => '{DAV:}read',
140
+ 'principal' => '{DAV:}authenticated',
141
+ 'protected' => true,
142
+ ],
143
+ [
144
+ 'privilege' => '{DAV:}write-properties',
145
+ 'principal' => $this->getOwner(),
146
+ 'protected' => true,
147
+ ],
148
+ [
149
+ 'privilege' => '{DAV:}unbind',
150
+ 'principal' => $this->getOwner(),
151
+ 'protected' => true,
152
+ ],
153
+ [
154
+ 'privilege' => '{DAV:}unbind',
155
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
156
+ 'protected' => true,
157
+ ],
158
+ [
159
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver',
160
+ 'principal' => '{DAV:}authenticated',
161
+ 'protected' => true,
162
+ ],
163
+ ];
164
+
165
+ }
166
+
167
+ /**
168
+ * Performs a calendar-query on the contents of this calendar.
169
+ *
170
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
171
+ * calendar-query it is possible for a client to request a specific set of
172
+ * object, based on contents of iCalendar properties, date-ranges and
173
+ * iCalendar component types (VTODO, VEVENT).
174
+ *
175
+ * This method should just return a list of (relative) urls that match this
176
+ * query.
177
+ *
178
+ * The list of filters are specified as an array. The exact array is
179
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
180
+ *
181
+ * @param array $filters
182
+ * @return array
183
+ */
184
+ function calendarQuery(array $filters) {
185
+
186
+ $result = [];
187
+ $validator = new CalDAV\CalendarQueryValidator();
188
+
189
+ $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
190
+ foreach ($objects as $object) {
191
+ $vObject = VObject\Reader::read($object['calendardata']);
192
+ if ($validator->validate($vObject, $filters)) {
193
+ $result[] = $object['uri'];
194
+ }
195
+
196
+ // Destroy circular references to PHP will GC the object.
197
+ $vObject->destroy();
198
+ }
199
+ return $result;
200
+
201
+ }
202
+
203
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\DAV;
7
+ use Sabre\DAVACL;
8
+
9
+ /**
10
+ * The CalDAV scheduling outbox
11
+ *
12
+ * The outbox is mainly used as an endpoint in the tree for a client to do
13
+ * free-busy requests. This functionality is completely handled by the
14
+ * Scheduling plugin, so this object is actually mostly static.
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class Outbox extends DAV\Collection implements IOutbox {
21
+
22
+ use DAVACL\ACLTrait;
23
+
24
+ /**
25
+ * The principal Uri
26
+ *
27
+ * @var string
28
+ */
29
+ protected $principalUri;
30
+
31
+ /**
32
+ * Constructor
33
+ *
34
+ * @param string $principalUri
35
+ */
36
+ function __construct($principalUri) {
37
+
38
+ $this->principalUri = $principalUri;
39
+
40
+ }
41
+
42
+ /**
43
+ * Returns the name of the node.
44
+ *
45
+ * This is used to generate the url.
46
+ *
47
+ * @return string
48
+ */
49
+ function getName() {
50
+
51
+ return 'outbox';
52
+
53
+ }
54
+
55
+ /**
56
+ * Returns an array with all the child nodes
57
+ *
58
+ * @return \Sabre\DAV\INode[]
59
+ */
60
+ function getChildren() {
61
+
62
+ return [];
63
+
64
+ }
65
+
66
+ /**
67
+ * Returns the owner principal
68
+ *
69
+ * This must be a url to a principal, or null if there's no owner
70
+ *
71
+ * @return string|null
72
+ */
73
+ function getOwner() {
74
+
75
+ return $this->principalUri;
76
+
77
+ }
78
+
79
+ /**
80
+ * Returns a list of ACE's for this node.
81
+ *
82
+ * Each ACE has the following properties:
83
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
84
+ * currently the only supported privileges
85
+ * * 'principal', a url to the principal who owns the node
86
+ * * 'protected' (optional), indicating that this ACE is not allowed to
87
+ * be updated.
88
+ *
89
+ * @return array
90
+ */
91
+ function getACL() {
92
+
93
+ return [
94
+ [
95
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
96
+ 'principal' => $this->getOwner(),
97
+ 'protected' => true,
98
+ ],
99
+ [
100
+ 'privilege' => '{DAV:}read',
101
+ 'principal' => $this->getOwner(),
102
+ 'protected' => true,
103
+ ],
104
+ [
105
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
106
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
107
+ 'protected' => true,
108
+ ],
109
+ [
110
+ 'privilege' => '{DAV:}read',
111
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
112
+ 'protected' => true,
113
+ ],
114
+ [
115
+ 'privilege' => '{DAV:}read',
116
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
117
+ 'protected' => true,
118
+ ],
119
+ ];
120
+
121
+ }
122
+
123
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php ADDED
@@ -0,0 +1,1066 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ use DateTimeZone;
6
+ use Sabre\CalDAV\ICalendar;
7
+ use Sabre\CalDAV\ICalendarObject;
8
+ use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
9
+ use Sabre\DAV\Exception\BadRequest;
10
+ use Sabre\DAV\Exception\Forbidden;
11
+ use Sabre\DAV\Exception\NotFound;
12
+ use Sabre\DAV\Exception\NotImplemented;
13
+ use Sabre\DAV\INode;
14
+ use Sabre\DAV\PropFind;
15
+ use Sabre\DAV\PropPatch;
16
+ use Sabre\DAV\Server;
17
+ use Sabre\DAV\ServerPlugin;
18
+ use Sabre\DAV\Sharing;
19
+ use Sabre\DAV\Xml\Property\LocalHref;
20
+ use Sabre\DAVACL;
21
+ use Sabre\HTTP\RequestInterface;
22
+ use Sabre\HTTP\ResponseInterface;
23
+ use Sabre\VObject;
24
+ use Sabre\VObject\Component\VCalendar;
25
+ use Sabre\VObject\ITip;
26
+ use Sabre\VObject\ITip\Message;
27
+ use Sabre\VObject\Reader;
28
+
29
+ /**
30
+ * CalDAV scheduling plugin.
31
+ * =========================
32
+ *
33
+ * This plugin provides the functionality added by the "Scheduling Extensions
34
+ * to CalDAV" standard, as defined in RFC6638.
35
+ *
36
+ * calendar-auto-schedule largely works by intercepting a users request to
37
+ * update their local calendar. If a user creates a new event with attendees,
38
+ * this plugin is supposed to grab the information from that event, and notify
39
+ * the attendees of this.
40
+ *
41
+ * There's 3 possible transports for this:
42
+ * * local delivery
43
+ * * delivery through email (iMip)
44
+ * * server-to-server delivery (iSchedule)
45
+ *
46
+ * iMip is simply, because we just need to add the iTip message as an email
47
+ * attachment. Local delivery is harder, because we both need to add this same
48
+ * message to a local DAV inbox, as well as live-update the relevant events.
49
+ *
50
+ * iSchedule is something for later.
51
+ *
52
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
53
+ * @author Evert Pot (http://evertpot.com/)
54
+ * @license http://sabre.io/license/ Modified BSD License
55
+ */
56
+ class Plugin extends ServerPlugin {
57
+
58
+ /**
59
+ * This is the official CalDAV namespace
60
+ */
61
+ const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
62
+
63
+ /**
64
+ * Reference to main Server object.
65
+ *
66
+ * @var Server
67
+ */
68
+ protected $server;
69
+
70
+ /**
71
+ * Returns a list of features for the DAV: HTTP header.
72
+ *
73
+ * @return array
74
+ */
75
+ function getFeatures() {
76
+
77
+ return ['calendar-auto-schedule', 'calendar-availability'];
78
+
79
+ }
80
+
81
+ /**
82
+ * Returns the name of the plugin.
83
+ *
84
+ * Using this name other plugins will be able to access other plugins
85
+ * using Server::getPlugin
86
+ *
87
+ * @return string
88
+ */
89
+ function getPluginName() {
90
+
91
+ return 'caldav-schedule';
92
+
93
+ }
94
+
95
+ /**
96
+ * Initializes the plugin
97
+ *
98
+ * @param Server $server
99
+ * @return void
100
+ */
101
+ function initialize(Server $server) {
102
+
103
+ $this->server = $server;
104
+ $server->on('method:POST', [$this, 'httpPost']);
105
+ $server->on('propFind', [$this, 'propFind']);
106
+ $server->on('propPatch', [$this, 'propPatch']);
107
+ $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
108
+ $server->on('beforeUnbind', [$this, 'beforeUnbind']);
109
+ $server->on('schedule', [$this, 'scheduleLocalDelivery']);
110
+ $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
111
+
112
+ $ns = '{' . self::NS_CALDAV . '}';
113
+
114
+ /**
115
+ * This information ensures that the {DAV:}resourcetype property has
116
+ * the correct values.
117
+ */
118
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
119
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
120
+
121
+ /**
122
+ * Properties we protect are made read-only by the server.
123
+ */
124
+ array_push($server->protectedProperties,
125
+ $ns . 'schedule-inbox-URL',
126
+ $ns . 'schedule-outbox-URL',
127
+ $ns . 'calendar-user-address-set',
128
+ $ns . 'calendar-user-type',
129
+ $ns . 'schedule-default-calendar-URL'
130
+ );
131
+
132
+ }
133
+
134
+ /**
135
+ * Use this method to tell the server this plugin defines additional
136
+ * HTTP methods.
137
+ *
138
+ * This method is passed a uri. It should only return HTTP methods that are
139
+ * available for the specified uri.
140
+ *
141
+ * @param string $uri
142
+ * @return array
143
+ */
144
+ function getHTTPMethods($uri) {
145
+
146
+ try {
147
+ $node = $this->server->tree->getNodeForPath($uri);
148
+ } catch (NotFound $e) {
149
+ return [];
150
+ }
151
+
152
+ if ($node instanceof IOutbox) {
153
+ return ['POST'];
154
+ }
155
+
156
+ return [];
157
+
158
+ }
159
+
160
+ /**
161
+ * This method handles POST request for the outbox.
162
+ *
163
+ * @param RequestInterface $request
164
+ * @param ResponseInterface $response
165
+ * @return bool
166
+ */
167
+ function httpPost(RequestInterface $request, ResponseInterface $response) {
168
+
169
+ // Checking if this is a text/calendar content type
170
+ $contentType = $request->getHeader('Content-Type');
171
+ if (strpos($contentType, 'text/calendar') !== 0) {
172
+ return;
173
+ }
174
+
175
+ $path = $request->getPath();
176
+
177
+ // Checking if we're talking to an outbox
178
+ try {
179
+ $node = $this->server->tree->getNodeForPath($path);
180
+ } catch (NotFound $e) {
181
+ return;
182
+ }
183
+ if (!$node instanceof IOutbox)
184
+ return;
185
+
186
+ $this->server->transactionType = 'post-caldav-outbox';
187
+ $this->outboxRequest($node, $request, $response);
188
+
189
+ // Returning false breaks the event chain and tells the server we've
190
+ // handled the request.
191
+ return false;
192
+
193
+ }
194
+
195
+ /**
196
+ * This method handler is invoked during fetching of properties.
197
+ *
198
+ * We use this event to add calendar-auto-schedule-specific properties.
199
+ *
200
+ * @param PropFind $propFind
201
+ * @param INode $node
202
+ * @return void
203
+ */
204
+ function propFind(PropFind $propFind, INode $node) {
205
+
206
+ if ($node instanceof DAVACL\IPrincipal) {
207
+
208
+ $caldavPlugin = $this->server->getPlugin('caldav');
209
+ $principalUrl = $node->getPrincipalUrl();
210
+
211
+ // schedule-outbox-URL property
212
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
213
+
214
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
215
+ if (!$calendarHomePath) {
216
+ return null;
217
+ }
218
+ $outboxPath = $calendarHomePath . '/outbox/';
219
+
220
+ return new LocalHref($outboxPath);
221
+
222
+ });
223
+ // schedule-inbox-URL property
224
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
225
+
226
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
227
+ if (!$calendarHomePath) {
228
+ return null;
229
+ }
230
+ $inboxPath = $calendarHomePath . '/inbox/';
231
+
232
+ return new LocalHref($inboxPath);
233
+
234
+ });
235
+
236
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
237
+
238
+ // We don't support customizing this property yet, so in the
239
+ // meantime we just grab the first calendar in the home-set.
240
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
241
+
242
+ if (!$calendarHomePath) {
243
+ return null;
244
+ }
245
+
246
+ $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
247
+
248
+ $result = $this->server->getPropertiesForPath($calendarHomePath, [
249
+ '{DAV:}resourcetype',
250
+ '{DAV:}share-access',
251
+ $sccs,
252
+ ], 1);
253
+
254
+ foreach ($result as $child) {
255
+ if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar')) {
256
+ // Node is either not a calendar
257
+ continue;
258
+ }
259
+ if (isset($child[200]['{DAV:}share-access'])) {
260
+ $shareAccess = $child[200]['{DAV:}share-access']->getValue();
261
+ if ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) {
262
+ // Node is a shared node, not owned by the relevant
263
+ // user.
264
+ continue;
265
+ }
266
+
267
+ }
268
+ if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
269
+ // Either there is no supported-calendar-component-set
270
+ // (which is fine) or we found one that supports VEVENT.
271
+ return new LocalHref($child['href']);
272
+ }
273
+ }
274
+
275
+ });
276
+
277
+ // The server currently reports every principal to be of type
278
+ // 'INDIVIDUAL'
279
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
280
+
281
+ return 'INDIVIDUAL';
282
+
283
+ });
284
+
285
+ }
286
+
287
+ // Mapping the old property to the new property.
288
+ $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
289
+
290
+ // In case it wasn't clear, the only difference is that we map the
291
+ // old property to a different namespace.
292
+ $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
293
+ $subPropFind = new PropFind(
294
+ $propFind->getPath(),
295
+ [$availProp]
296
+ );
297
+
298
+ $this->server->getPropertiesByNode(
299
+ $subPropFind,
300
+ $node
301
+ );
302
+
303
+ $propFind->set(
304
+ '{http://calendarserver.org/ns/}calendar-availability',
305
+ $subPropFind->get($availProp),
306
+ $subPropFind->getStatus($availProp)
307
+ );
308
+
309
+ });
310
+
311
+ }
312
+
313
+ /**
314
+ * This method is called during property updates.
315
+ *
316
+ * @param string $path
317
+ * @param PropPatch $propPatch
318
+ * @return void
319
+ */
320
+ function propPatch($path, PropPatch $propPatch) {
321
+
322
+ // Mapping the old property to the new property.
323
+ $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
324
+
325
+ $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
326
+ $subPropPatch = new PropPatch([$availProp => $value]);
327
+ $this->server->emit('propPatch', [$path, $subPropPatch]);
328
+ $subPropPatch->commit();
329
+
330
+ return $subPropPatch->getResult()[$availProp];
331
+
332
+ });
333
+
334
+ }
335
+
336
+ /**
337
+ * This method is triggered whenever there was a calendar object gets
338
+ * created or updated.
339
+ *
340
+ * @param RequestInterface $request HTTP request
341
+ * @param ResponseInterface $response HTTP Response
342
+ * @param VCalendar $vCal Parsed iCalendar object
343
+ * @param mixed $calendarPath Path to calendar collection
344
+ * @param mixed $modified The iCalendar object has been touched.
345
+ * @param mixed $isNew Whether this was a new item or we're updating one
346
+ * @return void
347
+ */
348
+ function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
349
+
350
+ if (!$this->scheduleReply($this->server->httpRequest)) {
351
+ return;
352
+ }
353
+
354
+ $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
355
+
356
+ $addresses = $this->getAddressesForPrincipal(
357
+ $calendarNode->getOwner()
358
+ );
359
+
360
+ if (!$isNew) {
361
+ $node = $this->server->tree->getNodeForPath($request->getPath());
362
+ $oldObj = Reader::read($node->get());
363
+ } else {
364
+ $oldObj = null;
365
+ }
366
+
367
+ $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
368
+
369
+ if ($oldObj) {
370
+ // Destroy circular references so PHP will GC the object.
371
+ $oldObj->destroy();
372
+ }
373
+
374
+ }
375
+
376
+ /**
377
+ * This method is responsible for delivering the ITip message.
378
+ *
379
+ * @param ITip\Message $iTipMessage
380
+ * @return void
381
+ */
382
+ function deliver(ITip\Message $iTipMessage) {
383
+
384
+ $this->server->emit('schedule', [$iTipMessage]);
385
+ if (!$iTipMessage->scheduleStatus) {
386
+ $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
387
+ }
388
+ // In case the change was considered 'insignificant', we are going to
389
+ // remove any error statuses, if any. See ticket #525.
390
+ list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
391
+ if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
392
+ $iTipMessage->scheduleStatus = null;
393
+ }
394
+
395
+ }
396
+
397
+ /**
398
+ * This method is triggered before a file gets deleted.
399
+ *
400
+ * We use this event to make sure that when this happens, attendees get
401
+ * cancellations, and organizers get 'DECLINED' statuses.
402
+ *
403
+ * @param string $path
404
+ * @return void
405
+ */
406
+ function beforeUnbind($path) {
407
+
408
+ // FIXME: We shouldn't trigger this functionality when we're issuing a
409
+ // MOVE. This is a hack.
410
+ if ($this->server->httpRequest->getMethod() === 'MOVE') return;
411
+
412
+ $node = $this->server->tree->getNodeForPath($path);
413
+
414
+ if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
415
+ return;
416
+ }
417
+
418
+ if (!$this->scheduleReply($this->server->httpRequest)) {
419
+ return;
420
+ }
421
+
422
+ $addresses = $this->getAddressesForPrincipal(
423
+ $node->getOwner()
424
+ );
425
+
426
+ $broker = new ITip\Broker();
427
+ $messages = $broker->parseEvent(null, $addresses, $node->get());
428
+
429
+ foreach ($messages as $message) {
430
+ $this->deliver($message);
431
+ }
432
+
433
+ }
434
+
435
+ /**
436
+ * Event handler for the 'schedule' event.
437
+ *
438
+ * This handler attempts to look at local accounts to deliver the
439
+ * scheduling object.
440
+ *
441
+ * @param ITip\Message $iTipMessage
442
+ * @return void
443
+ */
444
+ function scheduleLocalDelivery(ITip\Message $iTipMessage) {
445
+
446
+ $aclPlugin = $this->server->getPlugin('acl');
447
+
448
+ // Local delivery is not available if the ACL plugin is not loaded.
449
+ if (!$aclPlugin) {
450
+ return;
451
+ }
452
+
453
+ $caldavNS = '{' . self::NS_CALDAV . '}';
454
+
455
+ $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
456
+ if (!$principalUri) {
457
+ $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
458
+ return;
459
+ }
460
+
461
+ // We found a principal URL, now we need to find its inbox.
462
+ // Unfortunately we may not have sufficient privileges to find this, so
463
+ // we are temporarily turning off ACL to let this come through.
464
+ //
465
+ // Once we support PHP 5.5, this should be wrapped in a try..finally
466
+ // block so we can ensure that this privilege gets added again after.
467
+ $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
468
+
469
+ $result = $this->server->getProperties(
470
+ $principalUri,
471
+ [
472
+ '{DAV:}principal-URL',
473
+ $caldavNS . 'calendar-home-set',
474
+ $caldavNS . 'schedule-inbox-URL',
475
+ $caldavNS . 'schedule-default-calendar-URL',
476
+ '{http://sabredav.org/ns}email-address',
477
+ ]
478
+ );
479
+
480
+ // Re-registering the ACL event
481
+ $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
482
+
483
+ if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
484
+ $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
485
+ return;
486
+ }
487
+ if (!isset($result[$caldavNS . 'calendar-home-set'])) {
488
+ $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
489
+ return;
490
+ }
491
+ if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
492
+ $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
493
+ return;
494
+ }
495
+
496
+ $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
497
+ $homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
498
+ $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
499
+
500
+ if ($iTipMessage->method === 'REPLY') {
501
+ $privilege = 'schedule-deliver-reply';
502
+ } else {
503
+ $privilege = 'schedule-deliver-invite';
504
+ }
505
+
506
+ if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
507
+ $iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.';
508
+ return;
509
+ }
510
+
511
+ // Next, we're going to find out if the item already exits in one of
512
+ // the users' calendars.
513
+ $uid = $iTipMessage->uid;
514
+
515
+ $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
516
+
517
+ $home = $this->server->tree->getNodeForPath($homePath);
518
+ $inbox = $this->server->tree->getNodeForPath($inboxPath);
519
+
520
+ $currentObject = null;
521
+ $objectNode = null;
522
+ $isNewNode = false;
523
+
524
+ $result = $home->getCalendarObjectByUID($uid);
525
+ if ($result) {
526
+ // There was an existing object, we need to update probably.
527
+ $objectPath = $homePath . '/' . $result;
528
+ $objectNode = $this->server->tree->getNodeForPath($objectPath);
529
+ $oldICalendarData = $objectNode->get();
530
+ $currentObject = Reader::read($oldICalendarData);
531
+ } else {
532
+ $isNewNode = true;
533
+ }
534
+
535
+ $broker = new ITip\Broker();
536
+ $newObject = $broker->processMessage($iTipMessage, $currentObject);
537
+
538
+ $inbox->createFile($newFileName, $iTipMessage->message->serialize());
539
+
540
+ if (!$newObject) {
541
+ // We received an iTip message referring to a UID that we don't
542
+ // have in any calendars yet, and processMessage did not give us a
543
+ // calendarobject back.
544
+ //
545
+ // The implication is that processMessage did not understand the
546
+ // iTip message.
547
+ $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
548
+ return;
549
+ }
550
+
551
+ // Note that we are bypassing ACL on purpose by calling this directly.
552
+ // We may need to look a bit deeper into this later. Supporting ACL
553
+ // here would be nice.
554
+ if ($isNewNode) {
555
+ $calendar = $this->server->tree->getNodeForPath($calendarPath);
556
+ $calendar->createFile($newFileName, $newObject->serialize());
557
+ } else {
558
+ // If the message was a reply, we may have to inform other
559
+ // attendees of this attendees status. Therefore we're shooting off
560
+ // another itipMessage.
561
+ if ($iTipMessage->method === 'REPLY') {
562
+ $this->processICalendarChange(
563
+ $oldICalendarData,
564
+ $newObject,
565
+ [$iTipMessage->recipient],
566
+ [$iTipMessage->sender]
567
+ );
568
+ }
569
+ $objectNode->put($newObject->serialize());
570
+ }
571
+ $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
572
+
573
+ }
574
+
575
+ /**
576
+ * This method is triggered whenever a subsystem requests the privileges
577
+ * that are supported on a particular node.
578
+ *
579
+ * We need to add a number of privileges for scheduling purposes.
580
+ *
581
+ * @param INode $node
582
+ * @param array $supportedPrivilegeSet
583
+ */
584
+ function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {
585
+
586
+ $ns = '{' . self::NS_CALDAV . '}';
587
+ if ($node instanceof IOutbox) {
588
+ $supportedPrivilegeSet[$ns . 'schedule-send'] = [
589
+ 'abstract' => false,
590
+ 'aggregates' => [
591
+ $ns . 'schedule-send-invite' => [
592
+ 'abstract' => false,
593
+ 'aggregates' => [],
594
+ ],
595
+ $ns . 'schedule-send-reply' => [
596
+ 'abstract' => false,
597
+ 'aggregates' => [],
598
+ ],
599
+ $ns . 'schedule-send-freebusy' => [
600
+ 'abstract' => false,
601
+ 'aggregates' => [],
602
+ ],
603
+ // Privilege from an earlier scheduling draft, but still
604
+ // used by some clients.
605
+ $ns . 'schedule-post-vevent' => [
606
+ 'abstract' => false,
607
+ 'aggregates' => [],
608
+ ],
609
+ ]
610
+ ];
611
+ }
612
+ if ($node instanceof IInbox) {
613
+ $supportedPrivilegeSet[$ns . 'schedule-deliver'] = [
614
+ 'abstract' => false,
615
+ 'aggregates' => [
616
+ $ns . 'schedule-deliver-invite' => [
617
+ 'abstract' => false,
618
+ 'aggregates' => [],
619
+ ],
620
+ $ns . 'schedule-deliver-reply' => [
621
+ 'abstract' => false,
622
+ 'aggregates' => [],
623
+ ],
624
+ $ns . 'schedule-query-freebusy' => [
625
+ 'abstract' => false,
626
+ 'aggregates' => [],
627
+ ],
628
+ ]
629
+ ];
630
+ }
631
+
632
+ }
633
+
634
+ /**
635
+ * This method looks at an old iCalendar object, a new iCalendar object and
636
+ * starts sending scheduling messages based on the changes.
637
+ *
638
+ * A list of addresses needs to be specified, so the system knows who made
639
+ * the update, because the behavior may be different based on if it's an
640
+ * attendee or an organizer.
641
+ *
642
+ * This method may update $newObject to add any status changes.
643
+ *
644
+ * @param VCalendar|string $oldObject
645
+ * @param VCalendar $newObject
646
+ * @param array $addresses
647
+ * @param array $ignore Any addresses to not send messages to.
648
+ * @param bool $modified A marker to indicate that the original object
649
+ * modified by this process.
650
+ * @return void
651
+ */
652
+ protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
653
+
654
+ $broker = new ITip\Broker();
655
+ $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
656
+
657
+ if ($messages) $modified = true;
658
+
659
+ foreach ($messages as $message) {
660
+
661
+ if (in_array($message->recipient, $ignore)) {
662
+ continue;
663
+ }
664
+
665
+ $this->deliver($message);
666
+
667
+ if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
668
+ if ($message->scheduleStatus) {
669
+ $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
670
+ }
671
+ unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
672
+
673
+ } else {
674
+
675
+ if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
676
+
677
+ if ($attendee->getNormalizedValue() === $message->recipient) {
678
+ if ($message->scheduleStatus) {
679
+ $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
680
+ }
681
+ unset($attendee['SCHEDULE-FORCE-SEND']);
682
+ break;
683
+ }
684
+
685
+ }
686
+
687
+ }
688
+
689
+ }
690
+
691
+ }
692
+
693
+ /**
694
+ * Returns a list of addresses that are associated with a principal.
695
+ *
696
+ * @param string $principal
697
+ * @return array
698
+ */
699
+ protected function getAddressesForPrincipal($principal) {
700
+
701
+ $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
702
+
703
+ $properties = $this->server->getProperties(
704
+ $principal,
705
+ [$CUAS]
706
+ );
707
+
708
+ // If we can't find this information, we'll stop processing
709
+ if (!isset($properties[$CUAS])) {
710
+ return;
711
+ }
712
+
713
+ $addresses = $properties[$CUAS]->getHrefs();
714
+ return $addresses;
715
+
716
+ }
717
+
718
+ /**
719
+ * This method handles POST requests to the schedule-outbox.
720
+ *
721
+ * Currently, two types of requests are supported:
722
+ * * FREEBUSY requests from RFC 6638
723
+ * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
724
+ *
725
+ * The latter is from an expired early draft of the CalDAV scheduling
726
+ * extensions, but iCal depends on a feature from that spec, so we
727
+ * implement it.
728
+ *
729
+ * @param IOutbox $outboxNode
730
+ * @param RequestInterface $request
731
+ * @param ResponseInterface $response
732
+ * @return void
733
+ */
734
+ function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {
735
+
736
+ $outboxPath = $request->getPath();
737
+
738
+ // Parsing the request body
739
+ try {
740
+ $vObject = VObject\Reader::read($request->getBody());
741
+ } catch (VObject\ParseException $e) {
742
+ throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
743
+ }
744
+
745
+ // The incoming iCalendar object must have a METHOD property, and a
746
+ // component. The combination of both determines what type of request
747
+ // this is.
748
+ $componentType = null;
749
+ foreach ($vObject->getComponents() as $component) {
750
+ if ($component->name !== 'VTIMEZONE') {
751
+ $componentType = $component->name;
752
+ break;
753
+ }
754
+ }
755
+ if (is_null($componentType)) {
756
+ throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
757
+ }
758
+
759
+ // Validating the METHOD
760
+ $method = strtoupper((string)$vObject->METHOD);
761
+ if (!$method) {
762
+ throw new BadRequest('A METHOD property must be specified in iTIP messages');
763
+ }
764
+
765
+ // So we support one type of request:
766
+ //
767
+ // REQUEST with a VFREEBUSY component
768
+
769
+ $acl = $this->server->getPlugin('acl');
770
+
771
+ if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
772
+
773
+ $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-send-freebusy');
774
+ $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
775
+
776
+ // Destroy circular references so PHP can GC the object.
777
+ $vObject->destroy();
778
+ unset($vObject);
779
+
780
+ } else {
781
+
782
+ throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
783
+
784
+ }
785
+
786
+ }
787
+
788
+ /**
789
+ * This method is responsible for parsing a free-busy query request and
790
+ * returning it's result.
791
+ *
792
+ * @param IOutbox $outbox
793
+ * @param VObject\Component $vObject
794
+ * @param RequestInterface $request
795
+ * @param ResponseInterface $response
796
+ * @return string
797
+ */
798
+ protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
799
+
800
+ $vFreeBusy = $vObject->VFREEBUSY;
801
+ $organizer = $vFreeBusy->ORGANIZER;
802
+
803
+ $organizer = (string)$organizer;
804
+
805
+ // Validating if the organizer matches the owner of the inbox.
806
+ $owner = $outbox->getOwner();
807
+
808
+ $caldavNS = '{' . self::NS_CALDAV . '}';
809
+
810
+ $uas = $caldavNS . 'calendar-user-address-set';
811
+ $props = $this->server->getProperties($owner, [$uas]);
812
+
813
+ if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
814
+ throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
815
+ }
816
+
817
+ if (!isset($vFreeBusy->ATTENDEE)) {
818
+ throw new BadRequest('You must at least specify 1 attendee');
819
+ }
820
+
821
+ $attendees = [];
822
+ foreach ($vFreeBusy->ATTENDEE as $attendee) {
823
+ $attendees[] = (string)$attendee;
824
+ }
825
+
826
+
827
+ if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
828
+ throw new BadRequest('DTSTART and DTEND must both be specified');
829
+ }
830
+
831
+ $startRange = $vFreeBusy->DTSTART->getDateTime();
832
+ $endRange = $vFreeBusy->DTEND->getDateTime();
833
+
834
+ $results = [];
835
+ foreach ($attendees as $attendee) {
836
+ $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
837
+ }
838
+
839
+ $dom = new \DOMDocument('1.0', 'utf-8');
840
+ $dom->formatOutput = true;
841
+ $scheduleResponse = $dom->createElement('cal:schedule-response');
842
+ foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
843
+
844
+ $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
845
+
846
+ }
847
+ $dom->appendChild($scheduleResponse);
848
+
849
+ foreach ($results as $result) {
850
+ $xresponse = $dom->createElement('cal:response');
851
+
852
+ $recipient = $dom->createElement('cal:recipient');
853
+ $recipientHref = $dom->createElement('d:href');
854
+
855
+ $recipientHref->appendChild($dom->createTextNode($result['href']));
856
+ $recipient->appendChild($recipientHref);
857
+ $xresponse->appendChild($recipient);
858
+
859
+ $reqStatus = $dom->createElement('cal:request-status');
860
+ $reqStatus->appendChild($dom->createTextNode($result['request-status']));
861
+ $xresponse->appendChild($reqStatus);
862
+
863
+ if (isset($result['calendar-data'])) {
864
+
865
+ $calendardata = $dom->createElement('cal:calendar-data');
866
+ $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
867
+ $xresponse->appendChild($calendardata);
868
+
869
+ }
870
+ $scheduleResponse->appendChild($xresponse);
871
+ }
872
+
873
+ $response->setStatus(200);
874
+ $response->setHeader('Content-Type', 'application/xml');
875
+ $response->setBody($dom->saveXML());
876
+
877
+ }
878
+
879
+ /**
880
+ * Returns free-busy information for a specific address. The returned
881
+ * data is an array containing the following properties:
882
+ *
883
+ * calendar-data : A VFREEBUSY VObject
884
+ * request-status : an iTip status code.
885
+ * href: The principal's email address, as requested
886
+ *
887
+ * The following request status codes may be returned:
888
+ * * 2.0;description
889
+ * * 3.7;description
890
+ *
891
+ * @param string $email address
892
+ * @param \DateTimeInterface $start
893
+ * @param \DateTimeInterface $end
894
+ * @param VObject\Component $request
895
+ * @return array
896
+ */
897
+ protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
898
+
899
+ $caldavNS = '{' . self::NS_CALDAV . '}';
900
+
901
+ $aclPlugin = $this->server->getPlugin('acl');
902
+ if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
903
+
904
+ $result = $aclPlugin->principalSearch(
905
+ ['{http://sabredav.org/ns}email-address' => $email],
906
+ [
907
+ '{DAV:}principal-URL',
908
+ $caldavNS . 'calendar-home-set',
909
+ $caldavNS . 'schedule-inbox-URL',
910
+ '{http://sabredav.org/ns}email-address',
911
+
912
+ ]
913
+ );
914
+
915
+ if (!count($result)) {
916
+ return [
917
+ 'request-status' => '3.7;Could not find principal',
918
+ 'href' => 'mailto:' . $email,
919
+ ];
920
+ }
921
+
922
+ if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
923
+ return [
924
+ 'request-status' => '3.7;No calendar-home-set property found',
925
+ 'href' => 'mailto:' . $email,
926
+ ];
927
+ }
928
+ if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
929
+ return [
930
+ 'request-status' => '3.7;No schedule-inbox-URL property found',
931
+ 'href' => 'mailto:' . $email,
932
+ ];
933
+ }
934
+ $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
935
+ $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
936
+
937
+ // Do we have permission?
938
+ $aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 'schedule-query-freebusy');
939
+
940
+ // Grabbing the calendar list
941
+ $objects = [];
942
+ $calendarTimeZone = new DateTimeZone('UTC');
943
+
944
+ foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
945
+ if (!$node instanceof ICalendar) {
946
+ continue;
947
+ }
948
+
949
+ $sct = $caldavNS . 'schedule-calendar-transp';
950
+ $ctz = $caldavNS . 'calendar-timezone';
951
+ $props = $node->getProperties([$sct, $ctz]);
952
+
953
+ if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
954
+ // If a calendar is marked as 'transparent', it means we must
955
+ // ignore it for free-busy purposes.
956
+ continue;
957
+ }
958
+
959
+ if (isset($props[$ctz])) {
960
+ $vtimezoneObj = VObject\Reader::read($props[$ctz]);
961
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
962
+
963
+ // Destroy circular references so PHP can garbage collect the object.
964
+ $vtimezoneObj->destroy();
965
+
966
+ }
967
+
968
+ // Getting the list of object uris within the time-range
969
+ $urls = $node->calendarQuery([
970
+ 'name' => 'VCALENDAR',
971
+ 'comp-filters' => [
972
+ [
973
+ 'name' => 'VEVENT',
974
+ 'comp-filters' => [],
975
+ 'prop-filters' => [],
976
+ 'is-not-defined' => false,
977
+ 'time-range' => [
978
+ 'start' => $start,
979
+ 'end' => $end,
980
+ ],
981
+ ],
982
+ ],
983
+ 'prop-filters' => [],
984
+ 'is-not-defined' => false,
985
+ 'time-range' => null,
986
+ ]);
987
+
988
+ $calObjects = array_map(function($url) use ($node) {
989
+ $obj = $node->getChild($url)->get();
990
+ return $obj;
991
+ }, $urls);
992
+
993
+ $objects = array_merge($objects, $calObjects);
994
+
995
+ }
996
+
997
+ $inboxProps = $this->server->getProperties(
998
+ $inboxUrl,
999
+ $caldavNS . 'calendar-availability'
1000
+ );
1001
+
1002
+ $vcalendar = new VObject\Component\VCalendar();
1003
+ $vcalendar->METHOD = 'REPLY';
1004
+
1005
+ $generator = new VObject\FreeBusyGenerator();
1006
+ $generator->setObjects($objects);
1007
+ $generator->setTimeRange($start, $end);
1008
+ $generator->setBaseObject($vcalendar);
1009
+ $generator->setTimeZone($calendarTimeZone);
1010
+
1011
+ if ($inboxProps) {
1012
+ $generator->setVAvailability(
1013
+ VObject\Reader::read(
1014
+ $inboxProps[$caldavNS . 'calendar-availability']
1015
+ )
1016
+ );
1017
+ }
1018
+
1019
+ $result = $generator->getResult();
1020
+
1021
+ $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
1022
+ $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
1023
+ $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
1024
+
1025
+ return [
1026
+ 'calendar-data' => $result,
1027
+ 'request-status' => '2.0;Success',
1028
+ 'href' => 'mailto:' . $email,
1029
+ ];
1030
+ }
1031
+
1032
+ /**
1033
+ * This method checks the 'Schedule-Reply' header
1034
+ * and returns false if it's 'F', otherwise true.
1035
+ *
1036
+ * @param RequestInterface $request
1037
+ * @return bool
1038
+ */
1039
+ private function scheduleReply(RequestInterface $request) {
1040
+
1041
+ $scheduleReply = $request->getHeader('Schedule-Reply');
1042
+ return $scheduleReply !== 'F';
1043
+
1044
+ }
1045
+
1046
+ /**
1047
+ * Returns a bunch of meta-data about the plugin.
1048
+ *
1049
+ * Providing this information is optional, and is mainly displayed by the
1050
+ * Browser plugin.
1051
+ *
1052
+ * The description key in the returned array may contain html and will not
1053
+ * be sanitized.
1054
+ *
1055
+ * @return array
1056
+ */
1057
+ function getPluginInfo() {
1058
+
1059
+ return [
1060
+ 'name' => $this->getPluginName(),
1061
+ 'description' => 'Adds calendar-auto-schedule, as defined in rfc6638',
1062
+ 'link' => 'http://sabre.io/dav/scheduling/',
1063
+ ];
1064
+
1065
+ }
1066
+ }
vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Schedule;
4
+
5
+ use Sabre\CalDAV\Backend;
6
+ use Sabre\DAV\Exception\MethodNotAllowed;
7
+
8
+ /**
9
+ * The SchedulingObject represents a scheduling object in the Inbox collection
10
+ *
11
+ * @author Brett (https://github.com/bretten)
12
+ * @license http://sabre.io/license/ Modified BSD License
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ */
15
+ class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject {
16
+
17
+ /**
18
+ /* The CalDAV backend
19
+ *
20
+ * @var Backend\SchedulingSupport
21
+ */
22
+ protected $caldavBackend;
23
+
24
+ /**
25
+ * Array with information about this SchedulingObject
26
+ *
27
+ * @var array
28
+ */
29
+ protected $objectData;
30
+
31
+ /**
32
+ * Constructor
33
+ *
34
+ * The following properties may be passed within $objectData:
35
+ *
36
+ * * uri - A unique uri. Only the 'basename' must be passed.
37
+ * * principaluri - the principal that owns the object.
38
+ * * calendardata (optional) - The iCalendar data
39
+ * * etag - (optional) The etag for this object, MUST be encloded with
40
+ * double-quotes.
41
+ * * size - (optional) The size of the data in bytes.
42
+ * * lastmodified - (optional) format as a unix timestamp.
43
+ * * acl - (optional) Use this to override the default ACL for the node.
44
+ *
45
+ * @param Backend\SchedulingSupport $caldavBackend
46
+ * @param array $objectData
47
+ */
48
+ function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData) {
49
+
50
+ $this->caldavBackend = $caldavBackend;
51
+
52
+ if (!isset($objectData['uri'])) {
53
+ throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
54
+ }
55
+
56
+ $this->objectData = $objectData;
57
+
58
+ }
59
+
60
+ /**
61
+ * Returns the ICalendar-formatted object
62
+ *
63
+ * @return string
64
+ */
65
+ function get() {
66
+
67
+ // Pre-populating the 'calendardata' is optional, if we don't have it
68
+ // already we fetch it from the backend.
69
+ if (!isset($this->objectData['calendardata'])) {
70
+ $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
71
+ }
72
+ return $this->objectData['calendardata'];
73
+
74
+ }
75
+
76
+ /**
77
+ * Updates the ICalendar-formatted object
78
+ *
79
+ * @param string|resource $calendarData
80
+ * @return string
81
+ */
82
+ function put($calendarData) {
83
+
84
+ throw new MethodNotAllowed('Updating scheduling objects is not supported');
85
+
86
+ }
87
+
88
+ /**
89
+ * Deletes the scheduling message
90
+ *
91
+ * @return void
92
+ */
93
+ function delete() {
94
+
95
+ $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
96
+
97
+ }
98
+
99
+ /**
100
+ * Returns the owner principal
101
+ *
102
+ * This must be a url to a principal, or null if there's no owner
103
+ *
104
+ * @return string|null
105
+ */
106
+ function getOwner() {
107
+
108
+ return $this->objectData['principaluri'];
109
+
110
+ }
111
+
112
+
113
+ /**
114
+ * Returns a list of ACE's for this node.
115
+ *
116
+ * Each ACE has the following properties:
117
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
118
+ * currently the only supported privileges
119
+ * * 'principal', a url to the principal who owns the node
120
+ * * 'protected' (optional), indicating that this ACE is not allowed to
121
+ * be updated.
122
+ *
123
+ * @return array
124
+ */
125
+ function getACL() {
126
+
127
+ // An alternative acl may be specified in the object data.
128
+ //
129
+
130
+ if (isset($this->objectData['acl'])) {
131
+ return $this->objectData['acl'];
132
+ }
133
+
134
+ // The default ACL
135
+ return [
136
+ [
137
+ 'privilege' => '{DAV:}all',
138
+ 'principal' => '{DAV:}owner',
139
+ 'protected' => true,
140
+ ],
141
+ [
142
+ 'privilege' => '{DAV:}all',
143
+ 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
144
+ 'protected' => true,
145
+ ],
146
+ [
147
+ 'privilege' => '{DAV:}read',
148
+ 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read',
149
+ 'protected' => true,
150
+ ],
151
+ ];
152
+
153
+ }
154
+
155
+ }
vendor/sabre/dav/lib/CalDAV/SharedCalendar.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV\Sharing\Plugin as SPlugin;
6
+
7
+ /**
8
+ * This object represents a CalDAV calendar that is shared by a different user.
9
+ *
10
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11
+ * @author Evert Pot (http://evertpot.com/)
12
+ * @license http://sabre.io/license/ Modified BSD License
13
+ */
14
+ class SharedCalendar extends Calendar implements ISharedCalendar {
15
+
16
+ /**
17
+ * Returns the 'access level' for the instance of this shared resource.
18
+ *
19
+ * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
20
+ * constants.
21
+ *
22
+ * @return int
23
+ */
24
+ function getShareAccess() {
25
+
26
+ return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;
27
+
28
+ }
29
+
30
+ /**
31
+ * This function must return a URI that uniquely identifies the shared
32
+ * resource. This URI should be identical across instances, and is
33
+ * also used in several other XML bodies to connect invites to
34
+ * resources.
35
+ *
36
+ * This may simply be a relative reference to the original shared instance,
37
+ * but it could also be a urn. As long as it's a valid URI and unique.
38
+ *
39
+ * @return string
40
+ */
41
+ function getShareResourceUri() {
42
+
43
+ return $this->calendarInfo['share-resource-uri'];
44
+
45
+ }
46
+
47
+ /**
48
+ * Updates the list of sharees.
49
+ *
50
+ * Every item must be a Sharee object.
51
+ *
52
+ * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
53
+ * @return void
54
+ */
55
+ function updateInvites(array $sharees) {
56
+
57
+ $this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees);
58
+
59
+ }
60
+
61
+ /**
62
+ * Returns the list of people whom this resource is shared with.
63
+ *
64
+ * Every item in the returned array must be a Sharee object with
65
+ * at least the following properties set:
66
+ *
67
+ * * $href
68
+ * * $shareAccess
69
+ * * $inviteStatus
70
+ *
71
+ * and optionally:
72
+ *
73
+ * * $properties
74
+ *
75
+ * @return \Sabre\DAV\Xml\Element\Sharee[]
76
+ */
77
+ function getInvites() {
78
+
79
+ return $this->caldavBackend->getInvites($this->calendarInfo['id']);
80
+
81
+ }
82
+
83
+ /**
84
+ * Marks this calendar as published.
85
+ *
86
+ * Publishing a calendar should automatically create a read-only, public,
87
+ * subscribable calendar.
88
+ *
89
+ * @param bool $value
90
+ * @return void
91
+ */
92
+ function setPublishStatus($value) {
93
+
94
+ $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
95
+
96
+ }
97
+
98
+ /**
99
+ * Returns a list of ACE's for this node.
100
+ *
101
+ * Each ACE has the following properties:
102
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
103
+ * currently the only supported privileges
104
+ * * 'principal', a url to the principal who owns the node
105
+ * * 'protected' (optional), indicating that this ACE is not allowed to
106
+ * be updated.
107
+ *
108
+ * @return array
109
+ */
110
+ function getACL() {
111
+
112
+ $acl = [];
113
+
114
+ switch ($this->getShareAccess()) {
115
+ case SPlugin::ACCESS_NOTSHARED :
116
+ case SPlugin::ACCESS_SHAREDOWNER :
117
+ $acl[] = [
118
+ 'privilege' => '{DAV:}share',
119
+ 'principal' => $this->calendarInfo['principaluri'],
120
+ 'protected' => true,
121
+ ];
122
+ $acl[] = [
123
+ 'privilege' => '{DAV:}share',
124
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
125
+ 'protected' => true,
126
+ ];
127
+ // No break intentional!
128
+ case SPlugin::ACCESS_READWRITE :
129
+ $acl[] = [
130
+ 'privilege' => '{DAV:}write',
131
+ 'principal' => $this->calendarInfo['principaluri'],
132
+ 'protected' => true,
133
+ ];
134
+ $acl[] = [
135
+ 'privilege' => '{DAV:}write',
136
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
137
+ 'protected' => true,
138
+ ];
139
+ // No break intentional!
140
+ case SPlugin::ACCESS_READ :
141
+ $acl[] = [
142
+ 'privilege' => '{DAV:}write-properties',
143
+ 'principal' => $this->calendarInfo['principaluri'],
144
+ 'protected' => true,
145
+ ];
146
+ $acl[] = [
147
+ 'privilege' => '{DAV:}write-properties',
148
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
149
+ 'protected' => true,
150
+ ];
151
+ $acl[] = [
152
+ 'privilege' => '{DAV:}read',
153
+ 'principal' => $this->calendarInfo['principaluri'],
154
+ 'protected' => true,
155
+ ];
156
+ $acl[] = [
157
+ 'privilege' => '{DAV:}read',
158
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
159
+ 'protected' => true,
160
+ ];
161
+ $acl[] = [
162
+ 'privilege' => '{DAV:}read',
163
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
164
+ 'protected' => true,
165
+ ];
166
+ $acl[] = [
167
+ 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
168
+ 'principal' => '{DAV:}authenticated',
169
+ 'protected' => true,
170
+ ];
171
+ break;
172
+ }
173
+ return $acl;
174
+
175
+ }
176
+
177
+
178
+ /**
179
+ * This method returns the ACL's for calendar objects in this calendar.
180
+ * The result of this method automatically gets passed to the
181
+ * calendar-object nodes in the calendar.
182
+ *
183
+ * @return array
184
+ */
185
+ function getChildACL() {
186
+
187
+ $acl = [];
188
+
189
+ switch ($this->getShareAccess()) {
190
+ case SPlugin::ACCESS_NOTSHARED :
191
+ // No break intentional
192
+ case SPlugin::ACCESS_SHAREDOWNER :
193
+ // No break intentional
194
+ case SPlugin::ACCESS_READWRITE:
195
+ $acl[] = [
196
+ 'privilege' => '{DAV:}write',
197
+ 'principal' => $this->calendarInfo['principaluri'],
198
+ 'protected' => true,
199
+ ];
200
+ $acl[] = [
201
+ 'privilege' => '{DAV:}write',
202
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
203
+ 'protected' => true,
204
+ ];
205
+ // No break intentional
206
+ case SPlugin::ACCESS_READ:
207
+ $acl[] = [
208
+ 'privilege' => '{DAV:}read',
209
+ 'principal' => $this->calendarInfo['principaluri'],
210
+ 'protected' => true,
211
+ ];
212
+ $acl[] = [
213
+ 'privilege' => '{DAV:}read',
214
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
215
+ 'protected' => true,
216
+ ];
217
+ $acl[] = [
218
+ 'privilege' => '{DAV:}read',
219
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
220
+ 'protected' => true,
221
+ ];
222
+ break;
223
+ }
224
+
225
+ return $acl;
226
+
227
+ }
228
+
229
+ }
vendor/sabre/dav/lib/CalDAV/SharingPlugin.php ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\Xml\Property\LocalHref;
7
+ use Sabre\HTTP\RequestInterface;
8
+ use Sabre\HTTP\ResponseInterface;
9
+
10
+ /**
11
+ * This plugin implements support for caldav sharing.
12
+ *
13
+ * This spec is defined at:
14
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
15
+ *
16
+ * See:
17
+ * Sabre\CalDAV\Backend\SharingSupport for all the documentation.
18
+ *
19
+ * Note: This feature is experimental, and may change in between different
20
+ * SabreDAV versions.
21
+ *
22
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
23
+ * @author Evert Pot (http://evertpot.com/)
24
+ * @license http://sabre.io/license/ Modified BSD License
25
+ */
26
+ class SharingPlugin extends DAV\ServerPlugin {
27
+
28
+ /**
29
+ * Reference to SabreDAV server object.
30
+ *
31
+ * @var DAV\Server
32
+ */
33
+ protected $server;
34
+
35
+ /**
36
+ * This method should return a list of server-features.
37
+ *
38
+ * This is for example 'versioning' and is added to the DAV: header
39
+ * in an OPTIONS response.
40
+ *
41
+ * @return array
42
+ */
43
+ function getFeatures() {
44
+
45
+ return ['calendarserver-sharing'];
46
+
47
+ }
48
+
49
+ /**
50
+ * Returns a plugin name.
51
+ *
52
+ * Using this name other plugins will be able to access other plugins
53
+ * using Sabre\DAV\Server::getPlugin
54
+ *
55
+ * @return string
56
+ */
57
+ function getPluginName() {
58
+
59
+ return 'caldav-sharing';
60
+
61
+ }
62
+
63
+ /**
64
+ * This initializes the plugin.
65
+ *
66
+ * This function is called by Sabre\DAV\Server, after
67
+ * addPlugin is called.
68
+ *
69
+ * This method should set up the required event subscriptions.
70
+ *
71
+ * @param DAV\Server $server
72
+ * @return void
73
+ */
74
+ function initialize(DAV\Server $server) {
75
+
76
+ $this->server = $server;
77
+
78
+ if (is_null($this->server->getPlugin('sharing'))) {
79
+ 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.');
80
+ }
81
+
82
+ array_push(
83
+ $this->server->protectedProperties,
84
+ '{' . Plugin::NS_CALENDARSERVER . '}invite',
85
+ '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
86
+ '{' . Plugin::NS_CALENDARSERVER . '}shared-url'
87
+ );
88
+
89
+ $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
90
+ $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
91
+
92
+ $this->server->on('propFind', [$this, 'propFindEarly']);
93
+ $this->server->on('propFind', [$this, 'propFindLate'], 150);
94
+ $this->server->on('propPatch', [$this, 'propPatch'], 40);
95
+ $this->server->on('method:POST', [$this, 'httpPost']);
96
+
97
+ }
98
+
99
+ /**
100
+ * This event is triggered when properties are requested for a certain
101
+ * node.
102
+ *
103
+ * This allows us to inject any properties early.
104
+ *
105
+ * @param DAV\PropFind $propFind
106
+ * @param DAV\INode $node
107
+ * @return void
108
+ */
109
+ function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
110
+
111
+ if ($node instanceof ISharedCalendar) {
112
+
113
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
114
+
115
+ // Fetching owner information
116
+ $props = $this->server->getPropertiesForPath($node->getOwner(), [
117
+ '{http://sabredav.org/ns}email-address',
118
+ '{DAV:}displayname',
119
+ ], 0);
120
+
121
+ $ownerInfo = [
122
+ 'href' => $node->getOwner(),
123
+ ];
124
+
125
+ if (isset($props[0][200])) {
126
+
127
+ // We're mapping the internal webdav properties to the
128
+ // elements caldav-sharing expects.
129
+ if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) {
130
+ $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address'];
131
+ }
132
+ if (isset($props[0][200]['{DAV:}displayname'])) {
133
+ $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname'];
134
+ }
135
+
136
+ }
137
+
138
+ return new Xml\Property\Invite(
139
+ $node->getInvites(),
140
+ $ownerInfo
141
+ );
142
+
143
+ });
144
+
145
+ }
146
+
147
+ }
148
+
149
+ /**
150
+ * This method is triggered *after* all properties have been retrieved.
151
+ * This allows us to inject the correct resourcetype for calendars that
152
+ * have been shared.
153
+ *
154
+ * @param DAV\PropFind $propFind
155
+ * @param DAV\INode $node
156
+ * @return void
157
+ */
158
+ function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
159
+
160
+ if ($node instanceof ISharedCalendar) {
161
+ $shareAccess = $node->getShareAccess();
162
+ if ($rt = $propFind->get('{DAV:}resourcetype')) {
163
+ switch ($shareAccess) {
164
+ case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER :
165
+ $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
166
+ break;
167
+ case \Sabre\DAV\Sharing\Plugin::ACCESS_READ :
168
+ case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE :
169
+ $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared');
170
+ break;
171
+
172
+ }
173
+ }
174
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() {
175
+ return new Xml\Property\AllowedSharingModes(true, false);
176
+ });
177
+
178
+ }
179
+
180
+ }
181
+
182
+ /**
183
+ * This method is trigged when a user attempts to update a node's
184
+ * properties.
185
+ *
186
+ * A previous draft of the sharing spec stated that it was possible to use
187
+ * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
188
+ * the calendar.
189
+ *
190
+ * Even though this is no longer in the current spec, we keep this around
191
+ * because OS X 10.7 may still make use of this feature.
192
+ *
193
+ * @param string $path
194
+ * @param DAV\PropPatch $propPatch
195
+ * @return void
196
+ */
197
+ function propPatch($path, DAV\PropPatch $propPatch) {
198
+
199
+ $node = $this->server->tree->getNodeForPath($path);
200
+ if (!$node instanceof ISharedCalendar)
201
+ return;
202
+
203
+ if ($node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER || $node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED) {
204
+
205
+ $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
206
+ if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
207
+ $shares = $node->getInvites();
208
+ foreach ($shares as $share) {
209
+ $share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
210
+ }
211
+ $node->updateInvites($shares);
212
+
213
+ return true;
214
+
215
+ });
216
+
217
+ }
218
+
219
+ }
220
+
221
+ /**
222
+ * We intercept this to handle POST requests on calendars.
223
+ *
224
+ * @param RequestInterface $request
225
+ * @param ResponseInterface $response
226
+ * @return null|bool
227
+ */
228
+ function httpPost(RequestInterface $request, ResponseInterface $response) {
229
+
230
+ $path = $request->getPath();
231
+
232
+ // Only handling xml
233
+ $contentType = $request->getHeader('Content-Type');
234
+ if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
235
+ return;
236
+
237
+ // Making sure the node exists
238
+ try {
239
+ $node = $this->server->tree->getNodeForPath($path);
240
+ } catch (DAV\Exception\NotFound $e) {
241
+ return;
242
+ }
243
+
244
+ $requestBody = $request->getBodyAsString();
245
+
246
+ // If this request handler could not deal with this POST request, it
247
+ // will return 'null' and other plugins get a chance to handle the
248
+ // request.
249
+ //
250
+ // However, we already requested the full body. This is a problem,
251
+ // because a body can only be read once. This is why we preemptively
252
+ // re-populated the request body with the existing data.
253
+ $request->setBody($requestBody);
254
+
255
+ $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
256
+
257
+ switch ($documentType) {
258
+
259
+ // Both the DAV:share-resource and CALENDARSERVER:share requests
260
+ // behave identically.
261
+ case '{' . Plugin::NS_CALENDARSERVER . '}share' :
262
+
263
+ $sharingPlugin = $this->server->getPlugin('sharing');
264
+ $sharingPlugin->shareResource($path, $message->sharees);
265
+
266
+ $response->setStatus(200);
267
+ // Adding this because sending a response body may cause issues,
268
+ // and I wanted some type of indicator the response was handled.
269
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
270
+
271
+ // Breaking the event chain
272
+ return false;
273
+
274
+ // The invite-reply document is sent when the user replies to an
275
+ // invitation of a calendar share.
276
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' :
277
+
278
+ // This only works on the calendar-home-root node.
279
+ if (!$node instanceof CalendarHome) {
280
+ return;
281
+ }
282
+ $this->server->transactionType = 'post-invite-reply';
283
+
284
+ // Getting ACL info
285
+ $acl = $this->server->getPlugin('acl');
286
+
287
+ // If there's no ACL support, we allow everything
288
+ if ($acl) {
289
+ $acl->checkPrivileges($path, '{DAV:}write');
290
+ }
291
+
292
+ $url = $node->shareReply(
293
+ $message->href,
294
+ $message->status,
295
+ $message->calendarUri,
296
+ $message->inReplyTo,
297
+ $message->summary
298
+ );
299
+
300
+ $response->setStatus(200);
301
+ // Adding this because sending a response body may cause issues,
302
+ // and I wanted some type of indicator the response was handled.
303
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
304
+
305
+ if ($url) {
306
+ $writer = $this->server->xml->getWriter();
307
+ $writer->openMemory();
308
+ $writer->startDocument();
309
+ $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
310
+ $writer->write(new LocalHref($url));
311
+ $writer->endElement();
312
+ $response->setHeader('Content-Type', 'application/xml');
313
+ $response->setBody($writer->outputMemory());
314
+
315
+ }
316
+
317
+ // Breaking the event chain
318
+ return false;
319
+
320
+ case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
321
+
322
+ // We can only deal with IShareableCalendar objects
323
+ if (!$node instanceof ISharedCalendar) {
324
+ return;
325
+ }
326
+ $this->server->transactionType = 'post-publish-calendar';
327
+
328
+ // Getting ACL info
329
+ $acl = $this->server->getPlugin('acl');
330
+
331
+ // If there's no ACL support, we allow everything
332
+ if ($acl) {
333
+ $acl->checkPrivileges($path, '{DAV:}share');
334
+ }
335
+
336
+ $node->setPublishStatus(true);
337
+
338
+ // iCloud sends back the 202, so we will too.
339
+ $response->setStatus(202);
340
+
341
+ // Adding this because sending a response body may cause issues,
342
+ // and I wanted some type of indicator the response was handled.
343
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
344
+
345
+ // Breaking the event chain
346
+ return false;
347
+
348
+ case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
349
+
350
+ // We can only deal with IShareableCalendar objects
351
+ if (!$node instanceof ISharedCalendar) {
352
+ return;
353
+ }
354
+ $this->server->transactionType = 'post-unpublish-calendar';
355
+
356
+ // Getting ACL info
357
+ $acl = $this->server->getPlugin('acl');
358
+
359
+ // If there's no ACL support, we allow everything
360
+ if ($acl) {
361
+ $acl->checkPrivileges($path, '{DAV:}share');
362
+ }
363
+
364
+ $node->setPublishStatus(false);
365
+
366
+ $response->setStatus(200);
367
+
368
+ // Adding this because sending a response body may cause issues,
369
+ // and I wanted some type of indicator the response was handled.
370
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
371
+
372
+ // Breaking the event chain
373
+ return false;
374
+
375
+ }
376
+
377
+
378
+
379
+ }
380
+
381
+ /**
382
+ * Returns a bunch of meta-data about the plugin.
383
+ *
384
+ * Providing this information is optional, and is mainly displayed by the
385
+ * Browser plugin.
386
+ *
387
+ * The description key in the returned array may contain html and will not
388
+ * be sanitized.
389
+ *
390
+ * @return array
391
+ */
392
+ function getPluginInfo() {
393
+
394
+ return [
395
+ 'name' => $this->getPluginName(),
396
+ 'description' => 'Adds support for caldav-sharing.',
397
+ 'link' => 'http://sabre.io/dav/caldav-sharing/',
398
+ ];
399
+
400
+ }
401
+ }
vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Subscriptions;
4
+
5
+ use Sabre\DAV\ICollection;
6
+ use Sabre\DAV\IProperties;
7
+
8
+ /**
9
+ * ISubscription
10
+ *
11
+ * Nodes implementing this interface represent calendar subscriptions.
12
+ *
13
+ * The subscription node doesn't do much, other than returning and updating
14
+ * subscription-related properties.
15
+ *
16
+ * The following properties should be supported:
17
+ *
18
+ * 1. {DAV:}displayname
19
+ * 2. {http://apple.com/ns/ical/}refreshrate
20
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
21
+ * should not be stripped).
22
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
23
+ * should not be stripped).
24
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
25
+ * attachments should not be stripped).
26
+ * 6. {http://calendarserver.org/ns/}source (Must be a
27
+ * Sabre\DAV\Property\Href).
28
+ * 7. {http://apple.com/ns/ical/}calendar-color
29
+ * 8. {http://apple.com/ns/ical/}calendar-order
30
+ *
31
+ * It is recommended to support every property.
32
+ *
33
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
34
+ * @author Evert Pot (http://evertpot.com/)
35
+ * @license http://sabre.io/license/ Modified BSD License
36
+ */
37
+ interface ISubscription extends ICollection, IProperties {
38
+
39
+
40
+ }
vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Subscriptions;
4
+
5
+ use Sabre\DAV\INode;
6
+ use Sabre\DAV\PropFind;
7
+ use Sabre\DAV\Server;
8
+ use Sabre\DAV\ServerPlugin;
9
+
10
+ /**
11
+ * This plugin adds calendar-subscription support to your CalDAV server.
12
+ *
13
+ * Some clients support 'managed subscriptions' server-side. This is basically
14
+ * a list of subscription urls a user is using.
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class Plugin extends ServerPlugin {
21
+
22
+ /**
23
+ * This initializes the plugin.
24
+ *
25
+ * This function is called by Sabre\DAV\Server, after
26
+ * addPlugin is called.
27
+ *
28
+ * This method should set up the required event subscriptions.
29
+ *
30
+ * @param Server $server
31
+ * @return void
32
+ */
33
+ function initialize(Server $server) {
34
+
35
+ $server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] =
36
+ '{http://calendarserver.org/ns/}subscribed';
37
+
38
+ $server->xml->elementMap['{http://calendarserver.org/ns/}source'] =
39
+ 'Sabre\\DAV\\Xml\\Property\\Href';
40
+
41
+ $server->on('propFind', [$this, 'propFind'], 150);
42
+
43
+ }
44
+
45
+ /**
46
+ * This method should return a list of server-features.
47
+ *
48
+ * This is for example 'versioning' and is added to the DAV: header
49
+ * in an OPTIONS response.
50
+ *
51
+ * @return array
52
+ */
53
+ function getFeatures() {
54
+
55
+ return ['calendarserver-subscribed'];
56
+
57
+ }
58
+
59
+ /**
60
+ * Triggered after properties have been fetched.
61
+ *
62
+ * @param PropFind $propFind
63
+ * @param INode $node
64
+ * @return void
65
+ */
66
+ function propFind(PropFind $propFind, INode $node) {
67
+
68
+ // There's a bunch of properties that must appear as a self-closing
69
+ // xml-element. This event handler ensures that this will be the case.
70
+ $props = [
71
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms',
72
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments',
73
+ '{http://calendarserver.org/ns/}subscribed-strip-todos',
74
+ ];
75
+
76
+ foreach ($props as $prop) {
77
+
78
+ if ($propFind->getStatus($prop) === 200) {
79
+ $propFind->set($prop, '', 200);
80
+ }
81
+
82
+ }
83
+
84
+ }
85
+
86
+ /**
87
+ * Returns a plugin name.
88
+ *
89
+ * Using this name other plugins will be able to access other plugins
90
+ * using \Sabre\DAV\Server::getPlugin
91
+ *
92
+ * @return string
93
+ */
94
+ function getPluginName() {
95
+
96
+ return 'subscriptions';
97
+
98
+ }
99
+
100
+ /**
101
+ * Returns a bunch of meta-data about the plugin.
102
+ *
103
+ * Providing this information is optional, and is mainly displayed by the
104
+ * Browser plugin.
105
+ *
106
+ * The description key in the returned array may contain html and will not
107
+ * be sanitized.
108
+ *
109
+ * @return array
110
+ */
111
+ function getPluginInfo() {
112
+
113
+ return [
114
+ 'name' => $this->getPluginName(),
115
+ 'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.',
116
+ 'link' => null,
117
+ ];
118
+
119
+ }
120
+ }
vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Subscriptions;
4
+
5
+ use Sabre\CalDAV\Backend\SubscriptionSupport;
6
+ use Sabre\DAV\Collection;
7
+ use Sabre\DAV\PropPatch;
8
+ use Sabre\DAV\Xml\Property\Href;
9
+ use Sabre\DAVACL\ACLTrait;
10
+ use Sabre\DAVACL\IACL;
11
+
12
+ /**
13
+ * Subscription Node
14
+ *
15
+ * This node represents a subscription.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ class Subscription extends Collection implements ISubscription, IACL {
22
+
23
+ use ACLTrait;
24
+
25
+ /**
26
+ * caldavBackend
27
+ *
28
+ * @var SubscriptionSupport
29
+ */
30
+ protected $caldavBackend;
31
+
32
+ /**
33
+ * subscriptionInfo
34
+ *
35
+ * @var array
36
+ */
37
+ protected $subscriptionInfo;
38
+
39
+ /**
40
+ * Constructor
41
+ *
42
+ * @param SubscriptionSupport $caldavBackend
43
+ * @param array $subscriptionInfo
44
+ */
45
+ function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo) {
46
+
47
+ $this->caldavBackend = $caldavBackend;
48
+ $this->subscriptionInfo = $subscriptionInfo;
49
+
50
+ $required = [
51
+ 'id',
52
+ 'uri',
53
+ 'principaluri',
54
+ 'source',
55
+ ];
56
+
57
+ foreach ($required as $r) {
58
+ if (!isset($subscriptionInfo[$r])) {
59
+ throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node');
60
+ }
61
+ }
62
+
63
+ }
64
+
65
+ /**
66
+ * Returns the name of the node.
67
+ *
68
+ * This is used to generate the url.
69
+ *
70
+ * @return string
71
+ */
72
+ function getName() {
73
+
74
+ return $this->subscriptionInfo['uri'];
75
+
76
+ }
77
+
78
+ /**
79
+ * Returns the last modification time
80
+ *
81
+ * @return int
82
+ */
83
+ function getLastModified() {
84
+
85
+ if (isset($this->subscriptionInfo['lastmodified'])) {
86
+ return $this->subscriptionInfo['lastmodified'];
87
+ }
88
+
89
+ }
90
+
91
+ /**
92
+ * Deletes the current node
93
+ *
94
+ * @return void
95
+ */
96
+ function delete() {
97
+
98
+ $this->caldavBackend->deleteSubscription(
99
+ $this->subscriptionInfo['id']
100
+ );
101
+
102
+ }
103
+
104
+ /**
105
+ * Returns an array with all the child nodes
106
+ *
107
+ * @return \Sabre\DAV\INode[]
108
+ */
109
+ function getChildren() {
110
+
111
+ return [];
112
+
113
+ }
114
+
115
+ /**
116
+ * Updates properties on this node.
117
+ *
118
+ * This method received a PropPatch object, which contains all the
119
+ * information about the update.
120
+ *
121
+ * To update specific properties, call the 'handle' method on this object.
122
+ * Read the PropPatch documentation for more information.
123
+ *
124
+ * @param PropPatch $propPatch
125
+ * @return void
126
+ */
127
+ function propPatch(PropPatch $propPatch) {
128
+
129
+ return $this->caldavBackend->updateSubscription(
130
+ $this->subscriptionInfo['id'],
131
+ $propPatch
132
+ );
133
+
134
+ }
135
+
136
+ /**
137
+ * Returns a list of properties for this nodes.
138
+ *
139
+ * The properties list is a list of propertynames the client requested,
140
+ * encoded in clark-notation {xmlnamespace}tagname.
141
+ *
142
+ * If the array is empty, it means 'all properties' were requested.
143
+ *
144
+ * Note that it's fine to liberally give properties back, instead of
145
+ * conforming to the list of requested properties.
146
+ * The Server class will filter out the extra.
147
+ *
148
+ * @param array $properties
149
+ * @return array
150
+ */
151
+ function getProperties($properties) {
152
+
153
+ $r = [];
154
+
155
+ foreach ($properties as $prop) {
156
+
157
+ switch ($prop) {
158
+ case '{http://calendarserver.org/ns/}source' :
159
+ $r[$prop] = new Href($this->subscriptionInfo['source']);
160
+ break;
161
+ default :
162
+ if (array_key_exists($prop, $this->subscriptionInfo)) {
163
+ $r[$prop] = $this->subscriptionInfo[$prop];
164
+ }
165
+ break;
166
+ }
167
+
168
+ }
169
+
170
+ return $r;
171
+
172
+ }
173
+
174
+ /**
175
+ * Returns the owner principal.
176
+ *
177
+ * This must be a url to a principal, or null if there's no owner
178
+ *
179
+ * @return string|null
180
+ */
181
+ function getOwner() {
182
+
183
+ return $this->subscriptionInfo['principaluri'];
184
+
185
+ }
186
+
187
+ /**
188
+ * Returns a list of ACE's for this node.
189
+ *
190
+ * Each ACE has the following properties:
191
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
192
+ * currently the only supported privileges
193
+ * * 'principal', a url to the principal who owns the node
194
+ * * 'protected' (optional), indicating that this ACE is not allowed to
195
+ * be updated.
196
+ *
197
+ * @return array
198
+ */
199
+ function getACL() {
200
+
201
+ return [
202
+ [
203
+ 'privilege' => '{DAV:}all',
204
+ 'principal' => $this->getOwner(),
205
+ 'protected' => true,
206
+ ],
207
+ [
208
+ 'privilege' => '{DAV:}all',
209
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
210
+ 'protected' => true,
211
+ ],
212
+ [
213
+ 'privilege' => '{DAV:}read',
214
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
215
+ 'protected' => true,
216
+ ]
217
+ ];
218
+
219
+ }
220
+
221
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Filter;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\VObject\DateTimeParser;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\XmlDeserializable;
10
+
11
+ /**
12
+ * CalendarData parser.
13
+ *
14
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-data XML
15
+ * element, as defined in:
16
+ *
17
+ * https://tools.ietf.org/html/rfc4791#section-9.6
18
+ *
19
+ * This element is used in three distinct places in the caldav spec, but in
20
+ * this case, this element class only implements the calendar-data element as
21
+ * it appears in a DAV:prop element, in a calendar-query or calendar-multiget
22
+ * REPORT request.
23
+ *
24
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
25
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
26
+ * @license http://sabre.io/license/ Modified BSD License
27
+ */
28
+ class CalendarData implements XmlDeserializable {
29
+
30
+ /**
31
+ * The deserialize method is called during xml parsing.
32
+ *
33
+ * This method is called statically, this is because in theory this method
34
+ * may be used as a type of constructor, or factory method.
35
+ *
36
+ * Often you want to return an instance of the current class, but you are
37
+ * free to return other data as well.
38
+ *
39
+ * You are responsible for advancing the reader to the next element. Not
40
+ * doing anything will result in a never-ending loop.
41
+ *
42
+ * If you just want to skip parsing for this element altogether, you can
43
+ * just call $reader->next();
44
+ *
45
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
46
+ * the next element.
47
+ *
48
+ * @param Reader $reader
49
+ * @return mixed
50
+ */
51
+ static function xmlDeserialize(Reader $reader) {
52
+
53
+ $result = [
54
+ 'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar',
55
+ 'version' => $reader->getAttribute('version') ?: '2.0',
56
+ ];
57
+
58
+ $elems = (array)$reader->parseInnerTree();
59
+ foreach ($elems as $elem) {
60
+
61
+ switch ($elem['name']) {
62
+ case '{' . Plugin::NS_CALDAV . '}expand' :
63
+
64
+ $result['expand'] = [
65
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
66
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
67
+ ];
68
+
69
+ if (!$result['expand']['start'] || !$result['expand']['end']) {
70
+ throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data');
71
+ }
72
+ if ($result['expand']['end'] <= $result['expand']['start']) {
73
+ throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data');
74
+ }
75
+ break;
76
+ }
77
+
78
+ }
79
+
80
+ return $result;
81
+
82
+ }
83
+
84
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Filter;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\VObject\DateTimeParser;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\XmlDeserializable;
10
+
11
+ /**
12
+ * CompFilter parser.
13
+ *
14
+ * This class parses the {urn:ietf:params:xml:ns:caldav}comp-filter XML
15
+ * element, as defined in:
16
+ *
17
+ * https://tools.ietf.org/html/rfc4791#section-9.6
18
+ *
19
+ * The result will be spit out as an array.
20
+ *
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ class CompFilter implements XmlDeserializable {
26
+
27
+ /**
28
+ * The deserialize method is called during xml parsing.
29
+ *
30
+ * This method is called statically, this is because in theory this method
31
+ * may be used as a type of constructor, or factory method.
32
+ *
33
+ * Often you want to return an instance of the current class, but you are
34
+ * free to return other data as well.
35
+ *
36
+ * You are responsible for advancing the reader to the next element. Not
37
+ * doing anything will result in a never-ending loop.
38
+ *
39
+ * If you just want to skip parsing for this element altogether, you can
40
+ * just call $reader->next();
41
+ *
42
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
43
+ * the next element.
44
+ *
45
+ * @param Reader $reader
46
+ * @return mixed
47
+ */
48
+ static function xmlDeserialize(Reader $reader) {
49
+
50
+ $result = [
51
+ 'name' => null,
52
+ 'is-not-defined' => false,
53
+ 'comp-filters' => [],
54
+ 'prop-filters' => [],
55
+ 'time-range' => false,
56
+ ];
57
+
58
+ $att = $reader->parseAttributes();
59
+ $result['name'] = $att['name'];
60
+
61
+ $elems = $reader->parseInnerTree();
62
+
63
+ if (is_array($elems)) foreach ($elems as $elem) {
64
+
65
+ switch ($elem['name']) {
66
+
67
+ case '{' . Plugin::NS_CALDAV . '}comp-filter' :
68
+ $result['comp-filters'][] = $elem['value'];
69
+ break;
70
+ case '{' . Plugin::NS_CALDAV . '}prop-filter' :
71
+ $result['prop-filters'][] = $elem['value'];
72
+ break;
73
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
74
+ $result['is-not-defined'] = true;
75
+ break;
76
+ case '{' . Plugin::NS_CALDAV . '}time-range' :
77
+ if ($result['name'] === 'VCALENDAR') {
78
+ throw new BadRequest('You cannot add time-range filters on the VCALENDAR component');
79
+ }
80
+ $result['time-range'] = [
81
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
82
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
83
+ ];
84
+ if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
85
+ throw new BadRequest('The end-date must be larger than the start-date');
86
+ }
87
+ break;
88
+
89
+ }
90
+
91
+ }
92
+
93
+ return $result;
94
+
95
+ }
96
+
97
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Filter;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Reader;
7
+ use Sabre\Xml\XmlDeserializable;
8
+
9
+ /**
10
+ * PropFilter parser.
11
+ *
12
+ * This class parses the {urn:ietf:params:xml:ns:caldav}param-filter XML
13
+ * element, as defined in:
14
+ *
15
+ * https://tools.ietf.org/html/rfc4791#section-9.7.3
16
+ *
17
+ * The result will be spit out as an array.
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class ParamFilter implements XmlDeserializable {
24
+
25
+ /**
26
+ * The deserialize method is called during xml parsing.
27
+ *
28
+ * This method is called statically, this is because in theory this method
29
+ * may be used as a type of constructor, or factory method.
30
+ *
31
+ * Often you want to return an instance of the current class, but you are
32
+ * free to return other data as well.
33
+ *
34
+ * Important note 2: You are responsible for advancing the reader to the
35
+ * next element. Not doing anything will result in a never-ending loop.
36
+ *
37
+ * If you just want to skip parsing for this element altogether, you can
38
+ * just call $reader->next();
39
+ *
40
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
41
+ * the next element.
42
+ *
43
+ * @param Reader $reader
44
+ * @return mixed
45
+ */
46
+ static function xmlDeserialize(Reader $reader) {
47
+
48
+ $result = [
49
+ 'name' => null,
50
+ 'is-not-defined' => false,
51
+ 'text-match' => null,
52
+ ];
53
+
54
+ $att = $reader->parseAttributes();
55
+ $result['name'] = $att['name'];
56
+
57
+ $elems = $reader->parseInnerTree();
58
+
59
+ if (is_array($elems)) foreach ($elems as $elem) {
60
+
61
+ switch ($elem['name']) {
62
+
63
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
64
+ $result['is-not-defined'] = true;
65
+ break;
66
+ case '{' . Plugin::NS_CALDAV . '}text-match' :
67
+ $result['text-match'] = [
68
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
69
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
70
+ 'value' => $elem['value'],
71
+ ];
72
+ break;
73
+
74
+ }
75
+
76
+ }
77
+
78
+ return $result;
79
+
80
+ }
81
+
82
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Filter;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\VObject\DateTimeParser;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\XmlDeserializable;
10
+
11
+ /**
12
+ * PropFilter parser.
13
+ *
14
+ * This class parses the {urn:ietf:params:xml:ns:caldav}prop-filter XML
15
+ * element, as defined in:
16
+ *
17
+ * https://tools.ietf.org/html/rfc4791#section-9.7.2
18
+ *
19
+ * The result will be spit out as an array.
20
+ *
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ class PropFilter implements XmlDeserializable {
26
+
27
+ /**
28
+ * The deserialize method is called during xml parsing.
29
+ *
30
+ * This method is called statically, this is because in theory this method
31
+ * may be used as a type of constructor, or factory method.
32
+ *
33
+ * Often you want to return an instance of the current class, but you are
34
+ * free to return other data as well.
35
+ *
36
+ * You are responsible for advancing the reader to the next element. Not
37
+ * doing anything will result in a never-ending loop.
38
+ *
39
+ * If you just want to skip parsing for this element altogether, you can
40
+ * just call $reader->next();
41
+ *
42
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
43
+ * the next element.
44
+ *
45
+ * @param Reader $reader
46
+ * @return mixed
47
+ */
48
+ static function xmlDeserialize(Reader $reader) {
49
+
50
+ $result = [
51
+ 'name' => null,
52
+ 'is-not-defined' => false,
53
+ 'param-filters' => [],
54
+ 'text-match' => null,
55
+ 'time-range' => false,
56
+ ];
57
+
58
+ $att = $reader->parseAttributes();
59
+ $result['name'] = $att['name'];
60
+
61
+ $elems = $reader->parseInnerTree();
62
+
63
+ if (is_array($elems)) foreach ($elems as $elem) {
64
+
65
+ switch ($elem['name']) {
66
+
67
+ case '{' . Plugin::NS_CALDAV . '}param-filter' :
68
+ $result['param-filters'][] = $elem['value'];
69
+ break;
70
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
71
+ $result['is-not-defined'] = true;
72
+ break;
73
+ case '{' . Plugin::NS_CALDAV . '}time-range' :
74
+ $result['time-range'] = [
75
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
76
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
77
+ ];
78
+ if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
79
+ throw new BadRequest('The end-date must be larger than the start-date');
80
+ }
81
+ break;
82
+ case '{' . Plugin::NS_CALDAV . '}text-match' :
83
+ $result['text-match'] = [
84
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
85
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
86
+ 'value' => $elem['value'],
87
+ ];
88
+ break;
89
+
90
+ }
91
+
92
+ }
93
+
94
+ return $result;
95
+
96
+ }
97
+
98
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Notification;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\CalDAV\SharingPlugin as SharingPlugin;
7
+ use Sabre\DAV;
8
+ use Sabre\Xml\Writer;
9
+
10
+ /**
11
+ * This class represents the cs:invite-notification notification element.
12
+ *
13
+ * This element is defined here:
14
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class Invite implements NotificationInterface {
21
+
22
+ /**
23
+ * A unique id for the message
24
+ *
25
+ * @var string
26
+ */
27
+ protected $id;
28
+
29
+ /**
30
+ * Timestamp of the notification
31
+ *
32
+ * @var DateTime
33
+ */
34
+ protected $dtStamp;
35
+
36
+ /**
37
+ * A url to the recipient of the notification. This can be an email
38
+ * address (mailto:), or a principal url.
39
+ *
40
+ * @var string
41
+ */
42
+ protected $href;
43
+
44
+ /**
45
+ * The type of message, see the SharingPlugin::STATUS_* constants.
46
+ *
47
+ * @var int
48
+ */
49
+ protected $type;
50
+
51
+ /**
52
+ * True if access to a calendar is read-only.
53
+ *
54
+ * @var bool
55
+ */
56
+ protected $readOnly;
57
+
58
+ /**
59
+ * A url to the shared calendar.
60
+ *
61
+ * @var string
62
+ */
63
+ protected $hostUrl;
64
+
65
+ /**
66
+ * Url to the sharer of the calendar
67
+ *
68
+ * @var string
69
+ */
70
+ protected $organizer;
71
+
72
+ /**
73
+ * The name of the sharer.
74
+ *
75
+ * @var string
76
+ */
77
+ protected $commonName;
78
+
79
+ /**
80
+ * The name of the sharer.
81
+ *
82
+ * @var string
83
+ */
84
+ protected $firstName;
85
+
86
+ /**
87
+ * The name of the sharer.
88
+ *
89
+ * @var string
90
+ */
91
+ protected $lastName;
92
+
93
+ /**
94
+ * A description of the share request
95
+ *
96
+ * @var string
97
+ */
98
+ protected $summary;
99
+
100
+ /**
101
+ * The Etag for the notification
102
+ *
103
+ * @var string
104
+ */
105
+ protected $etag;
106
+
107
+ /**
108
+ * The list of supported components
109
+ *
110
+ * @var CalDAV\Xml\Property\SupportedCalendarComponentSet
111
+ */
112
+ protected $supportedComponents;
113
+
114
+ /**
115
+ * Creates the Invite notification.
116
+ *
117
+ * This constructor receives an array with the following elements:
118
+ *
119
+ * * id - A unique id
120
+ * * etag - The etag
121
+ * * dtStamp - A DateTime object with a timestamp for the notification.
122
+ * * type - The type of notification, see SharingPlugin::STATUS_*
123
+ * constants for details.
124
+ * * readOnly - This must be set to true, if this is an invite for
125
+ * read-only access to a calendar.
126
+ * * hostUrl - A url to the shared calendar.
127
+ * * organizer - Url to the sharer principal.
128
+ * * commonName - The real name of the sharer (optional).
129
+ * * firstName - The first name of the sharer (optional).
130
+ * * lastName - The last name of the sharer (optional).
131
+ * * summary - Description of the share, can be the same as the
132
+ * calendar, but may also be modified (optional).
133
+ * * supportedComponents - An instance of
134
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
135
+ * This allows the client to determine which components
136
+ * will be supported in the shared calendar. This is
137
+ * also optional.
138
+ *
139
+ * @param array $values All the options
140
+ */
141
+ function __construct(array $values) {
142
+
143
+ $required = [
144
+ 'id',
145
+ 'etag',
146
+ 'href',
147
+ 'dtStamp',
148
+ 'type',
149
+ 'readOnly',
150
+ 'hostUrl',
151
+ 'organizer',
152
+ ];
153
+ foreach ($required as $item) {
154
+ if (!isset($values[$item])) {
155
+ throw new \InvalidArgumentException($item . ' is a required constructor option');
156
+ }
157
+ }
158
+
159
+ foreach ($values as $key => $value) {
160
+ if (!property_exists($this, $key)) {
161
+ throw new \InvalidArgumentException('Unknown option: ' . $key);
162
+ }
163
+ $this->$key = $value;
164
+ }
165
+
166
+ }
167
+
168
+ /**
169
+ * The xmlSerialize method is called during xml writing.
170
+ *
171
+ * Use the $writer argument to write its own xml serialization.
172
+ *
173
+ * An important note: do _not_ create a parent element. Any element
174
+ * implementing XmlSerializable should only ever write what's considered
175
+ * its 'inner xml'.
176
+ *
177
+ * The parent of the current element is responsible for writing a
178
+ * containing element.
179
+ *
180
+ * This allows serializers to be re-used for different element names.
181
+ *
182
+ * If you are opening new elements, you must also close them again.
183
+ *
184
+ * @param Writer $writer
185
+ * @return void
186
+ */
187
+ function xmlSerialize(Writer $writer) {
188
+
189
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-notification');
190
+
191
+ }
192
+
193
+ /**
194
+ * This method serializes the entire notification, as it is used in the
195
+ * response body.
196
+ *
197
+ * @param Writer $writer
198
+ * @return void
199
+ */
200
+ function xmlSerializeFull(Writer $writer) {
201
+
202
+ $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';
203
+
204
+ $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
205
+ $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
206
+
207
+ $writer->startElement($cs . 'invite-notification');
208
+
209
+ $writer->writeElement($cs . 'uid', $this->id);
210
+ $writer->writeElement('{DAV:}href', $this->href);
211
+
212
+ switch ($this->type) {
213
+
214
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
215
+ $writer->writeElement($cs . 'invite-accepted');
216
+ break;
217
+ case DAV\Sharing\Plugin::INVITE_NORESPONSE :
218
+ $writer->writeElement($cs . 'invite-noresponse');
219
+ break;
220
+
221
+ }
222
+
223
+ $writer->writeElement($cs . 'hosturl', [
224
+ '{DAV:}href' => $writer->contextUri . $this->hostUrl
225
+ ]);
226
+
227
+ if ($this->summary) {
228
+ $writer->writeElement($cs . 'summary', $this->summary);
229
+ }
230
+
231
+ $writer->startElement($cs . 'access');
232
+ if ($this->readOnly) {
233
+ $writer->writeElement($cs . 'read');
234
+ } else {
235
+ $writer->writeElement($cs . 'read-write');
236
+ }
237
+ $writer->endElement(); // access
238
+
239
+ $writer->startElement($cs . 'organizer');
240
+ // If the organizer contains a 'mailto:' part, it means it should be
241
+ // treated as absolute.
242
+ if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') {
243
+ $writer->writeElement('{DAV:}href', $this->organizer);
244
+ } else {
245
+ $writer->writeElement('{DAV:}href', $writer->contextUri . $this->organizer);
246
+ }
247
+ if ($this->commonName) {
248
+ $writer->writeElement($cs . 'common-name', $this->commonName);
249
+ }
250
+ if ($this->firstName) {
251
+ $writer->writeElement($cs . 'first-name', $this->firstName);
252
+ }
253
+ if ($this->lastName) {
254
+ $writer->writeElement($cs . 'last-name', $this->lastName);
255
+ }
256
+ $writer->endElement(); // organizer
257
+
258
+ if ($this->commonName) {
259
+ $writer->writeElement($cs . 'organizer-cn', $this->commonName);
260
+ }
261
+ if ($this->firstName) {
262
+ $writer->writeElement($cs . 'organizer-first', $this->firstName);
263
+ }
264
+ if ($this->lastName) {
265
+ $writer->writeElement($cs . 'organizer-last', $this->lastName);
266
+ }
267
+ if ($this->supportedComponents) {
268
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set', $this->supportedComponents);
269
+ }
270
+
271
+ $writer->endElement(); // invite-notification
272
+
273
+ }
274
+
275
+ /**
276
+ * Returns a unique id for this notification
277
+ *
278
+ * This is just the base url. This should generally be some kind of unique
279
+ * id.
280
+ *
281
+ * @return string
282
+ */
283
+ function getId() {
284
+
285
+ return $this->id;
286
+
287
+ }
288
+
289
+ /**
290
+ * Returns the ETag for this notification.
291
+ *
292
+ * The ETag must be surrounded by literal double-quotes.
293
+ *
294
+ * @return string
295
+ */
296
+ function getETag() {
297
+
298
+ return $this->etag;
299
+
300
+ }
301
+
302
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Notification;
4
+
5
+ use Sabre\CalDAV;
6
+ use Sabre\CalDAV\SharingPlugin;
7
+ use Sabre\DAV;
8
+ use Sabre\Xml\Writer;
9
+
10
+ /**
11
+ * This class represents the cs:invite-reply notification element.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ class InviteReply implements NotificationInterface {
18
+
19
+ /**
20
+ * A unique id for the message
21
+ *
22
+ * @var string
23
+ */
24
+ protected $id;
25
+
26
+ /**
27
+ * Timestamp of the notification
28
+ *
29
+ * @var DateTime
30
+ */
31
+ protected $dtStamp;
32
+
33
+ /**
34
+ * The unique id of the notification this was a reply to.
35
+ *
36
+ * @var string
37
+ */
38
+ protected $inReplyTo;
39
+
40
+ /**
41
+ * A url to the recipient of the original (!) notification.
42
+ *
43
+ * @var string
44
+ */
45
+ protected $href;
46
+
47
+ /**
48
+ * The type of message, see the SharingPlugin::STATUS_ constants.
49
+ *
50
+ * @var int
51
+ */
52
+ protected $type;
53
+
54
+ /**
55
+ * A url to the shared calendar.
56
+ *
57
+ * @var string
58
+ */
59
+ protected $hostUrl;
60
+
61
+ /**
62
+ * A description of the share request
63
+ *
64
+ * @var string
65
+ */
66
+ protected $summary;
67
+
68
+ /**
69
+ * Notification Etag
70
+ *
71
+ * @var string
72
+ */
73
+ protected $etag;
74
+
75
+ /**
76
+ * Creates the Invite Reply Notification.
77
+ *
78
+ * This constructor receives an array with the following elements:
79
+ *
80
+ * * id - A unique id
81
+ * * etag - The etag
82
+ * * dtStamp - A DateTime object with a timestamp for the notification.
83
+ * * inReplyTo - This should refer to the 'id' of the notification
84
+ * this is a reply to.
85
+ * * type - The type of notification, see SharingPlugin::STATUS_*
86
+ * constants for details.
87
+ * * hostUrl - A url to the shared calendar.
88
+ * * summary - Description of the share, can be the same as the
89
+ * calendar, but may also be modified (optional).
90
+ *
91
+ * @param array $values
92
+ */
93
+ function __construct(array $values) {
94
+
95
+ $required = [
96
+ 'id',
97
+ 'etag',
98
+ 'href',
99
+ 'dtStamp',
100
+ 'inReplyTo',
101
+ 'type',
102
+ 'hostUrl',
103
+ ];
104
+ foreach ($required as $item) {
105
+ if (!isset($values[$item])) {
106
+ throw new \InvalidArgumentException($item . ' is a required constructor option');
107
+ }
108
+ }
109
+
110
+ foreach ($values as $key => $value) {
111
+ if (!property_exists($this, $key)) {
112
+ throw new \InvalidArgumentException('Unknown option: ' . $key);
113
+ }
114
+ $this->$key = $value;
115
+ }
116
+
117
+ }
118
+
119
+ /**
120
+ * The xmlSerialize method is called during xml writing.
121
+ *
122
+ * Use the $writer argument to write its own xml serialization.
123
+ *
124
+ * An important note: do _not_ create a parent element. Any element
125
+ * implementing XmlSerializable should only ever write what's considered
126
+ * its 'inner xml'.
127
+ *
128
+ * The parent of the current element is responsible for writing a
129
+ * containing element.
130
+ *
131
+ * This allows serializers to be re-used for different element names.
132
+ *
133
+ * If you are opening new elements, you must also close them again.
134
+ *
135
+ * @param Writer $writer
136
+ * @return void
137
+ */
138
+ function xmlSerialize(Writer $writer) {
139
+
140
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-reply');
141
+
142
+ }
143
+
144
+ /**
145
+ * This method serializes the entire notification, as it is used in the
146
+ * response body.
147
+ *
148
+ * @param Writer $writer
149
+ * @return void
150
+ */
151
+ function xmlSerializeFull(Writer $writer) {
152
+
153
+ $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';
154
+
155
+ $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
156
+ $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
157
+
158
+ $writer->startElement($cs . 'invite-reply');
159
+
160
+ $writer->writeElement($cs . 'uid', $this->id);
161
+ $writer->writeElement($cs . 'in-reply-to', $this->inReplyTo);
162
+ $writer->writeElement('{DAV:}href', $this->href);
163
+
164
+ switch ($this->type) {
165
+
166
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
167
+ $writer->writeElement($cs . 'invite-accepted');
168
+ break;
169
+ case DAV\Sharing\Plugin::INVITE_DECLINED :
170
+ $writer->writeElement($cs . 'invite-declined');
171
+ break;
172
+
173
+ }
174
+
175
+ $writer->writeElement($cs . 'hosturl', [
176
+ '{DAV:}href' => $writer->contextUri . $this->hostUrl
177
+ ]);
178
+
179
+ if ($this->summary) {
180
+ $writer->writeElement($cs . 'summary', $this->summary);
181
+ }
182
+ $writer->endElement(); // invite-reply
183
+
184
+ }
185
+
186
+ /**
187
+ * Returns a unique id for this notification
188
+ *
189
+ * This is just the base url. This should generally be some kind of unique
190
+ * id.
191
+ *
192
+ * @return string
193
+ */
194
+ function getId() {
195
+
196
+ return $this->id;
197
+
198
+ }
199
+
200
+ /**
201
+ * Returns the ETag for this notification.
202
+ *
203
+ * The ETag must be surrounded by literal double-quotes.
204
+ *
205
+ * @return string
206
+ */
207
+ function getETag() {
208
+
209
+ return $this->etag;
210
+
211
+ }
212
+
213
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Notification;
4
+
5
+ use Sabre\Xml\Writer;
6
+ use Sabre\Xml\XmlSerializable;
7
+
8
+ /**
9
+ * This interface reflects a single notification type.
10
+ *
11
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
12
+ * @author Evert Pot (http://evertpot.com/)
13
+ * @license http://sabre.io/license/ Modified BSD License
14
+ */
15
+ interface NotificationInterface extends XmlSerializable {
16
+
17
+ /**
18
+ * This method serializes the entire notification, as it is used in the
19
+ * response body.
20
+ *
21
+ * @param Writer $writer
22
+ * @return void
23
+ */
24
+ function xmlSerializeFull(Writer $writer);
25
+
26
+ /**
27
+ * Returns a unique id for this notification
28
+ *
29
+ * This is just the base url. This should generally be some kind of unique
30
+ * id.
31
+ *
32
+ * @return string
33
+ */
34
+ function getId();
35
+
36
+ /**
37
+ * Returns the ETag for this notification.
38
+ *
39
+ * The ETag must be surrounded by literal double-quotes.
40
+ *
41
+ * @return string
42
+ */
43
+ function getETag();
44
+
45
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Notification;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Writer;
7
+
8
+ /**
9
+ * SystemStatus notification
10
+ *
11
+ * This notification can be used to indicate to the user that the system is
12
+ * down.
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ class SystemStatus implements NotificationInterface {
19
+
20
+ const TYPE_LOW = 1;
21
+ const TYPE_MEDIUM = 2;
22
+ const TYPE_HIGH = 3;
23
+
24
+ /**
25
+ * A unique id
26
+ *
27
+ * @var string
28
+ */
29
+ protected $id;
30
+
31
+ /**
32
+ * The type of alert. This should be one of the TYPE_ constants.
33
+ *
34
+ * @var int
35
+ */
36
+ protected $type;
37
+
38
+ /**
39
+ * A human-readable description of the problem.
40
+ *
41
+ * @var string
42
+ */
43
+ protected $description;
44
+
45
+ /**
46
+ * A url to a website with more information for the user.
47
+ *
48
+ * @var string
49
+ */
50
+ protected $href;
51
+
52
+ /**
53
+ * Notification Etag
54
+ *
55
+ * @var string
56
+ */
57
+ protected $etag;
58
+
59
+ /**
60
+ * Creates the notification.
61
+ *
62
+ * Some kind of unique id should be provided. This is used to generate a
63
+ * url.
64
+ *
65
+ * @param string $id
66
+ * @param string $etag
67
+ * @param int $type
68
+ * @param string $description
69
+ * @param string $href
70
+ */
71
+ function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) {
72
+
73
+ $this->id = $id;
74
+ $this->type = $type;
75
+ $this->description = $description;
76
+ $this->href = $href;
77
+ $this->etag = $etag;
78
+
79
+ }
80
+
81
+ /**
82
+ * The serialize method is called during xml writing.
83
+ *
84
+ * It should use the $writer argument to encode this object into XML.
85
+ *
86
+ * Important note: it is not needed to create the parent element. The
87
+ * parent element is already created, and we only have to worry about
88
+ * attributes, child elements and text (if any).
89
+ *
90
+ * Important note 2: If you are writing any new elements, you are also
91
+ * responsible for closing them.
92
+ *
93
+ * @param Writer $writer
94
+ * @return void
95
+ */
96
+ function xmlSerialize(Writer $writer) {
97
+
98
+ switch ($this->type) {
99
+ case self::TYPE_LOW :
100
+ $type = 'low';
101
+ break;
102
+ case self::TYPE_MEDIUM :
103
+ $type = 'medium';
104
+ break;
105
+ default :
106
+ case self::TYPE_HIGH :
107
+ $type = 'high';
108
+ break;
109
+ }
110
+
111
+ $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}systemstatus');
112
+ $writer->writeAttribute('type', $type);
113
+ $writer->endElement();
114
+
115
+ }
116
+
117
+ /**
118
+ * This method serializes the entire notification, as it is used in the
119
+ * response body.
120
+ *
121
+ * @param Writer $writer
122
+ * @return void
123
+ */
124
+ function xmlSerializeFull(Writer $writer) {
125
+
126
+ $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
127
+ switch ($this->type) {
128
+ case self::TYPE_LOW :
129
+ $type = 'low';
130
+ break;
131
+ case self::TYPE_MEDIUM :
132
+ $type = 'medium';
133
+ break;
134
+ default :
135
+ case self::TYPE_HIGH :
136
+ $type = 'high';
137
+ break;
138
+ }
139
+
140
+ $writer->startElement($cs . 'systemstatus');
141
+ $writer->writeAttribute('type', $type);
142
+
143
+
144
+ if ($this->description) {
145
+ $writer->writeElement($cs . 'description', $this->description);
146
+ }
147
+ if ($this->href) {
148
+ $writer->writeElement('{DAV:}href', $this->href);
149
+ }
150
+
151
+ $writer->endElement(); // systemstatus
152
+
153
+ }
154
+
155
+ /**
156
+ * Returns a unique id for this notification
157
+ *
158
+ * This is just the base url. This should generally be some kind of unique
159
+ * id.
160
+ *
161
+ * @return string
162
+ */
163
+ function getId() {
164
+
165
+ return $this->id;
166
+
167
+ }
168
+
169
+ /*
170
+ * Returns the ETag for this notification.
171
+ *
172
+ * The ETag must be surrounded by literal double-quotes.
173
+ *
174
+ * @return string
175
+ */
176
+ function getETag() {
177
+
178
+ return $this->etag;
179
+
180
+ }
181
+
182
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Writer;
7
+ use Sabre\Xml\XmlSerializable;
8
+
9
+ /**
10
+ * AllowedSharingModes
11
+ *
12
+ * This property encodes the 'allowed-sharing-modes' property, as defined by
13
+ * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
14
+ * namespace.
15
+ *
16
+ * This property is a representation of the supported-calendar_component-set
17
+ * property in the CalDAV namespace. It simply requires an array of components,
18
+ * such as VEVENT, VTODO
19
+ *
20
+ * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ class AllowedSharingModes implements XmlSerializable {
26
+
27
+ /**
28
+ * Whether or not a calendar can be shared with another user
29
+ *
30
+ * @var bool
31
+ */
32
+ protected $canBeShared;
33
+
34
+ /**
35
+ * Whether or not the calendar can be placed on a public url.
36
+ *
37
+ * @var bool
38
+ */
39
+ protected $canBePublished;
40
+
41
+ /**
42
+ * Constructor
43
+ *
44
+ * @param bool $canBeShared
45
+ * @param bool $canBePublished
46
+ * @return void
47
+ */
48
+ function __construct($canBeShared, $canBePublished) {
49
+
50
+ $this->canBeShared = $canBeShared;
51
+ $this->canBePublished = $canBePublished;
52
+
53
+ }
54
+
55
+ /**
56
+ * The xmlSerialize method is called during xml writing.
57
+ *
58
+ * Use the $writer argument to write its own xml serialization.
59
+ *
60
+ * An important note: do _not_ create a parent element. Any element
61
+ * implementing XmlSerializable should only ever write what's considered
62
+ * its 'inner xml'.
63
+ *
64
+ * The parent of the current element is responsible for writing a
65
+ * containing element.
66
+ *
67
+ * This allows serializers to be re-used for different element names.
68
+ *
69
+ * If you are opening new elements, you must also close them again.
70
+ *
71
+ * @param Writer $writer
72
+ * @return void
73
+ */
74
+ function xmlSerialize(Writer $writer) {
75
+
76
+ if ($this->canBeShared) {
77
+ $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-shared');
78
+ }
79
+ if ($this->canBePublished) {
80
+ $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-published');
81
+ }
82
+
83
+ }
84
+
85
+
86
+
87
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\Xml\Writer;
6
+ use Sabre\Xml\XmlSerializable;
7
+
8
+ /**
9
+ * email-address-set property
10
+ *
11
+ * This property represents the email-address-set property in the
12
+ * http://calendarserver.org/ns/ namespace.
13
+ *
14
+ * It's a list of email addresses associated with a user.
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class EmailAddressSet implements XmlSerializable {
21
+
22
+ /**
23
+ * emails
24
+ *
25
+ * @var array
26
+ */
27
+ private $emails;
28
+
29
+ /**
30
+ * __construct
31
+ *
32
+ * @param array $emails
33
+ */
34
+ function __construct(array $emails) {
35
+
36
+ $this->emails = $emails;
37
+
38
+ }
39
+
40
+ /**
41
+ * Returns the email addresses
42
+ *
43
+ * @return array
44
+ */
45
+ function getValue() {
46
+
47
+ return $this->emails;
48
+
49
+ }
50
+
51
+ /**
52
+ * The xmlSerialize method is called during xml writing.
53
+ *
54
+ * Use the $writer argument to write its own xml serialization.
55
+ *
56
+ * An important note: do _not_ create a parent element. Any element
57
+ * implementing XmlSerializable should only ever write what's considered
58
+ * its 'inner xml'.
59
+ *
60
+ * The parent of the current element is responsible for writing a
61
+ * containing element.
62
+ *
63
+ * This allows serializers to be re-used for different element names.
64
+ *
65
+ * If you are opening new elements, you must also close them again.
66
+ *
67
+ * @param Writer $writer
68
+ * @return void
69
+ */
70
+ function xmlSerialize(Writer $writer) {
71
+
72
+ foreach ($this->emails as $email) {
73
+
74
+ $writer->writeElement('{http://calendarserver.org/ns/}email-address', $email);
75
+
76
+ }
77
+
78
+ }
79
+
80
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV;
7
+ use Sabre\DAV\Xml\Element\Sharee;
8
+ use Sabre\Xml\Writer;
9
+ use Sabre\Xml\XmlSerializable;
10
+
11
+ /**
12
+ * Invite property
13
+ *
14
+ * This property encodes the 'invite' property, as defined by
15
+ * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
16
+ * namespace.
17
+ *
18
+ * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class Invite implements XmlSerializable {
24
+
25
+ /**
26
+ * The list of users a calendar has been shared to.
27
+ *
28
+ * @var Sharee[]
29
+ */
30
+ protected $sharees;
31
+
32
+ /**
33
+ * Creates the property.
34
+ *
35
+ * @param Sharee[] $sharees
36
+ */
37
+ function __construct(array $sharees) {
38
+
39
+ $this->sharees = $sharees;
40
+
41
+ }
42
+
43
+ /**
44
+ * Returns the list of users, as it was passed to the constructor.
45
+ *
46
+ * @return array
47
+ */
48
+ function getValue() {
49
+
50
+ return $this->sharees;
51
+
52
+ }
53
+
54
+ /**
55
+ * The xmlSerialize method is called during xml writing.
56
+ *
57
+ * Use the $writer argument to write its own xml serialization.
58
+ *
59
+ * An important note: do _not_ create a parent element. Any element
60
+ * implementing XmlSerializable should only ever write what's considered
61
+ * its 'inner xml'.
62
+ *
63
+ * The parent of the current element is responsible for writing a
64
+ * containing element.
65
+ *
66
+ * This allows serializers to be re-used for different element names.
67
+ *
68
+ * If you are opening new elements, you must also close them again.
69
+ *
70
+ * @param Writer $writer
71
+ * @return void
72
+ */
73
+ function xmlSerialize(Writer $writer) {
74
+
75
+ $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
76
+
77
+ foreach ($this->sharees as $sharee) {
78
+
79
+ if ($sharee->access === DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
80
+ $writer->startElement($cs . 'organizer');
81
+ } else {
82
+ $writer->startElement($cs . 'user');
83
+
84
+ switch ($sharee->inviteStatus) {
85
+ case DAV\Sharing\Plugin::INVITE_ACCEPTED :
86
+ $writer->writeElement($cs . 'invite-accepted');
87
+ break;
88
+ case DAV\Sharing\Plugin::INVITE_DECLINED :
89
+ $writer->writeElement($cs . 'invite-declined');
90
+ break;
91
+ case DAV\Sharing\Plugin::INVITE_NORESPONSE :
92
+ $writer->writeElement($cs . 'invite-noresponse');
93
+ break;
94
+ case DAV\Sharing\Plugin::INVITE_INVALID :
95
+ $writer->writeElement($cs . 'invite-invalid');
96
+ break;
97
+ }
98
+
99
+ $writer->startElement($cs . 'access');
100
+ switch ($sharee->access) {
101
+ case DAV\Sharing\Plugin::ACCESS_READWRITE :
102
+ $writer->writeElement($cs . 'read-write');
103
+ break;
104
+ case DAV\Sharing\Plugin::ACCESS_READ :
105
+ $writer->writeElement($cs . 'read');
106
+ break;
107
+
108
+ }
109
+ $writer->endElement(); // access
110
+
111
+ }
112
+
113
+ $href = new DAV\Xml\Property\Href($sharee->href);
114
+ $href->xmlSerialize($writer);
115
+
116
+ if (isset($sharee->properties['{DAV:}displayname'])) {
117
+ $writer->writeElement($cs . 'common-name', $sharee->properties['{DAV:}displayname']);
118
+ }
119
+ if ($sharee->comment) {
120
+ $writer->writeElement($cs . 'summary', $sharee->comment);
121
+ }
122
+ $writer->endElement(); // organizer or user
123
+
124
+ }
125
+
126
+ }
127
+
128
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Deserializer;
7
+ use Sabre\Xml\Element;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\Writer;
10
+
11
+ /**
12
+ * schedule-calendar-transp property.
13
+ *
14
+ * This property is a representation of the schedule-calendar-transp property.
15
+ * This property is defined in:
16
+ *
17
+ * http://tools.ietf.org/html/rfc6638#section-9.1
18
+ *
19
+ * Its values are either 'transparent' or 'opaque'. If it's transparent, it
20
+ * means that this calendar will not be taken into consideration when a
21
+ * different user queries for free-busy information. If it's 'opaque', it will.
22
+ *
23
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
24
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
25
+ * @license http://sabre.io/license/ Modified BSD License
26
+ */
27
+ class ScheduleCalendarTransp implements Element {
28
+
29
+ const TRANSPARENT = 'transparent';
30
+ const OPAQUE = 'opaque';
31
+
32
+ /**
33
+ * value
34
+ *
35
+ * @var string
36
+ */
37
+ protected $value;
38
+
39
+ /**
40
+ * Creates the property
41
+ *
42
+ * @param string $value
43
+ */
44
+ function __construct($value) {
45
+
46
+ if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) {
47
+ throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"');
48
+ }
49
+ $this->value = $value;
50
+
51
+ }
52
+
53
+ /**
54
+ * Returns the current value
55
+ *
56
+ * @return string
57
+ */
58
+ function getValue() {
59
+
60
+ return $this->value;
61
+
62
+ }
63
+
64
+ /**
65
+ * The xmlSerialize method is called during xml writing.
66
+ *
67
+ * Use the $writer argument to write its own xml serialization.
68
+ *
69
+ * An important note: do _not_ create a parent element. Any element
70
+ * implementing XmlSerializable should only ever write what's considered
71
+ * its 'inner xml'.
72
+ *
73
+ * The parent of the current element is responsible for writing a
74
+ * containing element.
75
+ *
76
+ * This allows serializers to be re-used for different element names.
77
+ *
78
+ * If you are opening new elements, you must also close them again.
79
+ *
80
+ * @param Writer $writer
81
+ * @return void
82
+ */
83
+ function xmlSerialize(Writer $writer) {
84
+
85
+ switch ($this->value) {
86
+ case self::TRANSPARENT :
87
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}transparent');
88
+ break;
89
+ case self::OPAQUE :
90
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}opaque');
91
+ break;
92
+ }
93
+
94
+ }
95
+
96
+ /**
97
+ * The deserialize method is called during xml parsing.
98
+ *
99
+ * This method is called statically, this is because in theory this method
100
+ * may be used as a type of constructor, or factory method.
101
+ *
102
+ * Often you want to return an instance of the current class, but you are
103
+ * free to return other data as well.
104
+ *
105
+ * You are responsible for advancing the reader to the next element. Not
106
+ * doing anything will result in a never-ending loop.
107
+ *
108
+ * If you just want to skip parsing for this element altogether, you can
109
+ * just call $reader->next();
110
+ *
111
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
112
+ * the next element.
113
+ *
114
+ * @param Reader $reader
115
+ * @return mixed
116
+ */
117
+ static function xmlDeserialize(Reader $reader) {
118
+
119
+ $elems = Deserializer\enum($reader, Plugin::NS_CALDAV);
120
+
121
+ if (in_array('transparent', $elems)) {
122
+ $value = self::TRANSPARENT;
123
+ } else {
124
+ $value = self::OPAQUE;
125
+ }
126
+ return new self($value);
127
+
128
+ }
129
+
130
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Element;
7
+ use Sabre\Xml\ParseException;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\Writer;
10
+
11
+ /**
12
+ * SupportedCalendarComponentSet property.
13
+ *
14
+ * This class represents the
15
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set property, as
16
+ * defined in:
17
+ *
18
+ * https://tools.ietf.org/html/rfc4791#section-5.2.3
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://evertpot.com/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ class SupportedCalendarComponentSet implements Element {
25
+
26
+ /**
27
+ * List of supported components.
28
+ *
29
+ * This array will contain values such as VEVENT, VTODO and VJOURNAL.
30
+ *
31
+ * @var array
32
+ */
33
+ protected $components = [];
34
+
35
+ /**
36
+ * Creates the property.
37
+ *
38
+ * @param array $components
39
+ */
40
+ function __construct(array $components) {
41
+
42
+ $this->components = $components;
43
+
44
+ }
45
+
46
+ /**
47
+ * Returns the list of supported components
48
+ *
49
+ * @return array
50
+ */
51
+ function getValue() {
52
+
53
+ return $this->components;
54
+
55
+ }
56
+
57
+ /**
58
+ * The xmlSerialize method is called during xml writing.
59
+ *
60
+ * Use the $writer argument to write its own xml serialization.
61
+ *
62
+ * An important note: do _not_ create a parent element. Any element
63
+ * implementing XmlSerializable should only ever write what's considered
64
+ * its 'inner xml'.
65
+ *
66
+ * The parent of the current element is responsible for writing a
67
+ * containing element.
68
+ *
69
+ * This allows serializers to be re-used for different element names.
70
+ *
71
+ * If you are opening new elements, you must also close them again.
72
+ *
73
+ * @param Writer $writer
74
+ * @return void
75
+ */
76
+ function xmlSerialize(Writer $writer) {
77
+
78
+ foreach ($this->components as $component) {
79
+
80
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}comp');
81
+ $writer->writeAttributes(['name' => $component]);
82
+ $writer->endElement();
83
+
84
+ }
85
+
86
+ }
87
+
88
+ /**
89
+ * The deserialize method is called during xml parsing.
90
+ *
91
+ * This method is called statically, this is because in theory this method
92
+ * may be used as a type of constructor, or factory method.
93
+ *
94
+ * Often you want to return an instance of the current class, but you are
95
+ * free to return other data as well.
96
+ *
97
+ * You are responsible for advancing the reader to the next element. Not
98
+ * doing anything will result in a never-ending loop.
99
+ *
100
+ * If you just want to skip parsing for this element altogether, you can
101
+ * just call $reader->next();
102
+ *
103
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
104
+ * the next element.
105
+ *
106
+ * @param Reader $reader
107
+ * @return mixed
108
+ */
109
+ static function xmlDeserialize(Reader $reader) {
110
+
111
+ $elems = $reader->parseInnerTree();
112
+
113
+ $components = [];
114
+
115
+ foreach ((array)$elems as $elem) {
116
+ if ($elem['name'] === '{' . Plugin::NS_CALDAV . '}comp') {
117
+ $components[] = $elem['attributes']['name'];
118
+ }
119
+ }
120
+
121
+ if (!$components) {
122
+ throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element');
123
+ }
124
+
125
+ return new self($components);
126
+
127
+ }
128
+
129
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Writer;
7
+ use Sabre\Xml\XmlSerializable;
8
+
9
+ /**
10
+ * Supported-calendar-data property
11
+ *
12
+ * This property is a representation of the supported-calendar-data property
13
+ * in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
14
+ * so the value is currently hardcoded.
15
+ *
16
+ * This property is defined in:
17
+ * http://tools.ietf.org/html/rfc4791#section-5.2.4
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class SupportedCalendarData implements XmlSerializable {
24
+
25
+ /**
26
+ * The xmlSerialize method is called during xml writing.
27
+ *
28
+ * Use the $writer argument to write its own xml serialization.
29
+ *
30
+ * An important note: do _not_ create a parent element. Any element
31
+ * implementing XmlSerializable should only ever write what's considered
32
+ * its 'inner xml'.
33
+ *
34
+ * The parent of the current element is responsible for writing a
35
+ * containing element.
36
+ *
37
+ * This allows serializers to be re-used for different element names.
38
+ *
39
+ * If you are opening new elements, you must also close them again.
40
+ *
41
+ * @param Writer $writer
42
+ * @return void
43
+ */
44
+ function xmlSerialize(Writer $writer) {
45
+
46
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
47
+ $writer->writeAttributes([
48
+ 'content-type' => 'text/calendar',
49
+ 'version' => '2.0',
50
+ ]);
51
+ $writer->endElement(); // calendar-data
52
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
53
+ $writer->writeAttributes([
54
+ 'content-type' => 'application/calendar+json',
55
+ ]);
56
+ $writer->endElement(); // calendar-data
57
+
58
+ }
59
+
60
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Property;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Xml\Writer;
7
+ use Sabre\Xml\XmlSerializable;
8
+
9
+ /**
10
+ * supported-collation-set property
11
+ *
12
+ * This property is a representation of the supported-collation-set property
13
+ * in the CalDAV namespace.
14
+ *
15
+ * This property is defined in:
16
+ * http://tools.ietf.org/html/rfc4791#section-7.5.1
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://evertpot.com/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class SupportedCollationSet implements XmlSerializable {
23
+
24
+ /**
25
+ * The xmlSerialize method is called during xml writing.
26
+ *
27
+ * Use the $writer argument to write its own xml serialization.
28
+ *
29
+ * An important note: do _not_ create a parent element. Any element
30
+ * implementing XmlSerializable should only ever write what's considered
31
+ * its 'inner xml'.
32
+ *
33
+ * The parent of the current element is responsible for writing a
34
+ * containing element.
35
+ *
36
+ * This allows serializers to be re-used for different element names.
37
+ *
38
+ * If you are opening new elements, you must also close them again.
39
+ *
40
+ * @param Writer $writer
41
+ * @return void
42
+ */
43
+ function xmlSerialize(Writer $writer) {
44
+
45
+ $collations = [
46
+ 'i;ascii-casemap',
47
+ 'i;octet',
48
+ 'i;unicode-casemap'
49
+ ];
50
+
51
+ foreach ($collations as $collation) {
52
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation);
53
+ }
54
+
55
+ }
56
+
57
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\Uri;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * CalendarMultiGetReport request parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-multiget
14
+ * REPORT, as defined in:
15
+ *
16
+ * https://tools.ietf.org/html/rfc4791#section-7.9
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class CalendarMultiGetReport implements XmlDeserializable {
23
+
24
+ /**
25
+ * An array with requested properties.
26
+ *
27
+ * @var array
28
+ */
29
+ public $properties;
30
+
31
+ /**
32
+ * This is an array with the urls that are being requested.
33
+ *
34
+ * @var array
35
+ */
36
+ public $hrefs;
37
+
38
+ /**
39
+ * If the calendar data must be expanded, this will contain an array with 2
40
+ * elements: start and end.
41
+ *
42
+ * Each may be a DateTime or null.
43
+ *
44
+ * @var array|null
45
+ */
46
+ public $expand = null;
47
+
48
+ /**
49
+ * The mimetype of the content that should be returend. Usually
50
+ * text/calendar.
51
+ *
52
+ * @var string
53
+ */
54
+ public $contentType = null;
55
+
56
+ /**
57
+ * The version of calendar-data that should be returned. Usually '2.0',
58
+ * referring to iCalendar 2.0.
59
+ *
60
+ * @var string
61
+ */
62
+ public $version = null;
63
+
64
+ /**
65
+ * The deserialize method is called during xml parsing.
66
+ *
67
+ * This method is called statically, this is because in theory this method
68
+ * may be used as a type of constructor, or factory method.
69
+ *
70
+ * Often you want to return an instance of the current class, but you are
71
+ * free to return other data as well.
72
+ *
73
+ * You are responsible for advancing the reader to the next element. Not
74
+ * doing anything will result in a never-ending loop.
75
+ *
76
+ * If you just want to skip parsing for this element altogether, you can
77
+ * just call $reader->next();
78
+ *
79
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
80
+ * the next element.
81
+ *
82
+ * @param Reader $reader
83
+ * @return mixed
84
+ */
85
+ static function xmlDeserialize(Reader $reader) {
86
+
87
+ $elems = $reader->parseInnerTree([
88
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
89
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
90
+ ]);
91
+
92
+ $newProps = [
93
+ 'hrefs' => [],
94
+ 'properties' => [],
95
+ ];
96
+
97
+ foreach ($elems as $elem) {
98
+
99
+ switch ($elem['name']) {
100
+
101
+ case '{DAV:}prop' :
102
+ $newProps['properties'] = array_keys($elem['value']);
103
+ if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
104
+ $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
105
+ }
106
+ break;
107
+ case '{DAV:}href' :
108
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
109
+ break;
110
+
111
+ }
112
+
113
+ }
114
+
115
+ $obj = new self();
116
+ foreach ($newProps as $key => $value) {
117
+ $obj->$key = $value;
118
+ }
119
+
120
+ return $obj;
121
+
122
+ }
123
+
124
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * CalendarQueryReport request parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query
14
+ * REPORT, as defined in:
15
+ *
16
+ * https://tools.ietf.org/html/rfc4791#section-7.9
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class CalendarQueryReport implements XmlDeserializable {
23
+
24
+ /**
25
+ * An array with requested properties.
26
+ *
27
+ * @var array
28
+ */
29
+ public $properties;
30
+
31
+ /**
32
+ * List of property/component filters.
33
+ *
34
+ * @var array
35
+ */
36
+ public $filters;
37
+
38
+ /**
39
+ * If the calendar data must be expanded, this will contain an array with 2
40
+ * elements: start and end.
41
+ *
42
+ * Each may be a DateTime or null.
43
+ *
44
+ * @var array|null
45
+ */
46
+ public $expand = null;
47
+
48
+ /**
49
+ * The mimetype of the content that should be returend. Usually
50
+ * text/calendar.
51
+ *
52
+ * @var string
53
+ */
54
+ public $contentType = null;
55
+
56
+ /**
57
+ * The version of calendar-data that should be returned. Usually '2.0',
58
+ * referring to iCalendar 2.0.
59
+ *
60
+ * @var string
61
+ */
62
+ public $version = null;
63
+
64
+ /**
65
+ * The deserialize method is called during xml parsing.
66
+ *
67
+ * This method is called statically, this is because in theory this method
68
+ * may be used as a type of constructor, or factory method.
69
+ *
70
+ * Often you want to return an instance of the current class, but you are
71
+ * free to return other data as well.
72
+ *
73
+ * You are responsible for advancing the reader to the next element. Not
74
+ * doing anything will result in a never-ending loop.
75
+ *
76
+ * If you just want to skip parsing for this element altogether, you can
77
+ * just call $reader->next();
78
+ *
79
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
80
+ * the next element.
81
+ *
82
+ * @param Reader $reader
83
+ * @return mixed
84
+ */
85
+ static function xmlDeserialize(Reader $reader) {
86
+
87
+ $elems = $reader->parseInnerTree([
88
+ '{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter',
89
+ '{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter',
90
+ '{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter',
91
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
92
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
93
+ ]);
94
+
95
+ $newProps = [
96
+ 'filters' => null,
97
+ 'properties' => [],
98
+ ];
99
+
100
+ if (!is_array($elems)) $elems = [];
101
+
102
+ foreach ($elems as $elem) {
103
+
104
+ switch ($elem['name']) {
105
+
106
+ case '{DAV:}prop' :
107
+ $newProps['properties'] = array_keys($elem['value']);
108
+ if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
109
+ $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
110
+ }
111
+ break;
112
+ case '{' . Plugin::NS_CALDAV . '}filter' :
113
+ foreach ($elem['value'] as $subElem) {
114
+ if ($subElem['name'] === '{' . Plugin::NS_CALDAV . '}comp-filter') {
115
+ if (!is_null($newProps['filters'])) {
116
+ throw new BadRequest('Only one top-level comp-filter may be defined');
117
+ }
118
+ $newProps['filters'] = $subElem['value'];
119
+ }
120
+ }
121
+ break;
122
+
123
+ }
124
+
125
+ }
126
+
127
+ if (is_null($newProps['filters'])) {
128
+ throw new BadRequest('The {' . Plugin::NS_CALDAV . '}filter element is required for this request');
129
+ }
130
+
131
+ $obj = new self();
132
+ foreach ($newProps as $key => $value) {
133
+ $obj->$key = $value;
134
+ }
135
+ return $obj;
136
+
137
+ }
138
+
139
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\VObject\DateTimeParser;
8
+ use Sabre\Xml\Reader;
9
+ use Sabre\Xml\XmlDeserializable;
10
+
11
+ /**
12
+ * FreeBusyQueryReport
13
+ *
14
+ * This class parses the {DAV:}free-busy-query REPORT, as defined in:
15
+ *
16
+ * http://tools.ietf.org/html/rfc3253#section-3.8
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://evertpot.com/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class FreeBusyQueryReport implements XmlDeserializable {
23
+
24
+ /**
25
+ * Starttime of report
26
+ *
27
+ * @var DateTime|null
28
+ */
29
+ public $start;
30
+
31
+ /**
32
+ * End time of report
33
+ *
34
+ * @var DateTime|null
35
+ */
36
+ public $end;
37
+
38
+ /**
39
+ * The deserialize method is called during xml parsing.
40
+ *
41
+ * This method is called statically, this is because in theory this method
42
+ * may be used as a type of constructor, or factory method.
43
+ *
44
+ * Often you want to return an instance of the current class, but you are
45
+ * free to return other data as well.
46
+ *
47
+ * You are responsible for advancing the reader to the next element. Not
48
+ * doing anything will result in a never-ending loop.
49
+ *
50
+ * If you just want to skip parsing for this element altogether, you can
51
+ * just call $reader->next();
52
+ *
53
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
54
+ * the next element.
55
+ *
56
+ * @param Reader $reader
57
+ * @return mixed
58
+ */
59
+ static function xmlDeserialize(Reader $reader) {
60
+
61
+ $timeRange = '{' . Plugin::NS_CALDAV . '}time-range';
62
+
63
+ $start = null;
64
+ $end = null;
65
+
66
+ foreach ((array)$reader->parseInnerTree([]) as $elem) {
67
+
68
+ if ($elem['name'] !== $timeRange) continue;
69
+
70
+ $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start'];
71
+ $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end'];
72
+
73
+ }
74
+ if (!$start && !$end) {
75
+ throw new BadRequest('The freebusy report must have a time-range element');
76
+ }
77
+ if ($start) {
78
+ $start = DateTimeParser::parseDateTime($start);
79
+ }
80
+ if ($end) {
81
+ $end = DateTimeParser::parseDateTime($end);
82
+ }
83
+ $result = new self();
84
+ $result->start = $start;
85
+ $result->end = $end;
86
+
87
+ return $result;
88
+
89
+ }
90
+
91
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\CalDAV\SharingPlugin;
7
+ use Sabre\DAV;
8
+ use Sabre\DAV\Exception\BadRequest;
9
+ use Sabre\Xml\Element\KeyValue;
10
+ use Sabre\Xml\Reader;
11
+ use Sabre\Xml\XmlDeserializable;
12
+
13
+ /**
14
+ * Invite-reply POST request parser
15
+ *
16
+ * This class parses the invite-reply POST request, as defined in:
17
+ *
18
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://evertpot.com/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ class InviteReply implements XmlDeserializable {
25
+
26
+ /**
27
+ * The sharee calendar user address.
28
+ *
29
+ * This is the address that the original invite was set to
30
+ *
31
+ * @var string
32
+ */
33
+ public $href;
34
+
35
+ /**
36
+ * The uri to the calendar that was being shared.
37
+ *
38
+ * @var string
39
+ */
40
+ public $calendarUri;
41
+
42
+ /**
43
+ * The id of the invite message that's being responded to
44
+ *
45
+ * @var string
46
+ */
47
+ public $inReplyTo;
48
+
49
+ /**
50
+ * An optional message
51
+ *
52
+ * @var string
53
+ */
54
+ public $summary;
55
+
56
+ /**
57
+ * Either SharingPlugin::STATUS_ACCEPTED or SharingPlugin::STATUS_DECLINED.
58
+ *
59
+ * @var int
60
+ */
61
+ public $status;
62
+
63
+ /**
64
+ * Constructor
65
+ *
66
+ * @param string $href
67
+ * @param string $calendarUri
68
+ * @param string $inReplyTo
69
+ * @param string $summary
70
+ * @param int $status
71
+ */
72
+ function __construct($href, $calendarUri, $inReplyTo, $summary, $status) {
73
+
74
+ $this->href = $href;
75
+ $this->calendarUri = $calendarUri;
76
+ $this->inReplyTo = $inReplyTo;
77
+ $this->summary = $summary;
78
+ $this->status = $status;
79
+
80
+ }
81
+
82
+ /**
83
+ * The deserialize method is called during xml parsing.
84
+ *
85
+ * This method is called statically, this is because in theory this method
86
+ * may be used as a type of constructor, or factory method.
87
+ *
88
+ * Often you want to return an instance of the current class, but you are
89
+ * free to return other data as well.
90
+ *
91
+ * You are responsible for advancing the reader to the next element. Not
92
+ * doing anything will result in a never-ending loop.
93
+ *
94
+ * If you just want to skip parsing for this element altogether, you can
95
+ * just call $reader->next();
96
+ *
97
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
98
+ * the next element.
99
+ *
100
+ * @param Reader $reader
101
+ * @return mixed
102
+ */
103
+ static function xmlDeserialize(Reader $reader) {
104
+
105
+ $elems = KeyValue::xmlDeserialize($reader);
106
+
107
+ $href = null;
108
+ $calendarUri = null;
109
+ $inReplyTo = null;
110
+ $summary = null;
111
+ $status = null;
112
+
113
+ foreach ($elems as $name => $value) {
114
+
115
+ switch ($name) {
116
+
117
+ case '{' . Plugin::NS_CALENDARSERVER . '}hosturl' :
118
+ foreach ($value as $bla) {
119
+ if ($bla['name'] === '{DAV:}href') {
120
+ $calendarUri = $bla['value'];
121
+ }
122
+ }
123
+ break;
124
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' :
125
+ $status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
126
+ break;
127
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' :
128
+ $status = DAV\Sharing\Plugin::INVITE_DECLINED;
129
+ break;
130
+ case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' :
131
+ $inReplyTo = $value;
132
+ break;
133
+ case '{' . Plugin::NS_CALENDARSERVER . '}summary' :
134
+ $summary = $value;
135
+ break;
136
+ case '{DAV:}href' :
137
+ $href = $value;
138
+ break;
139
+ }
140
+
141
+ }
142
+ if (is_null($calendarUri)) {
143
+ throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist');
144
+ }
145
+
146
+ return new self($href, $calendarUri, $inReplyTo, $summary, $status);
147
+
148
+ }
149
+
150
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\Xml\Reader;
6
+ use Sabre\Xml\XmlDeserializable;
7
+
8
+ /**
9
+ * MKCALENDAR parser.
10
+ *
11
+ * This class parses the MKCALENDAR request, as defined in:
12
+ *
13
+ * https://tools.ietf.org/html/rfc4791#section-5.3.1
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class MkCalendar implements XmlDeserializable {
20
+
21
+ /**
22
+ * The list of properties that will be set.
23
+ *
24
+ * @var array
25
+ */
26
+ public $properties = [];
27
+
28
+ /**
29
+ * Returns the list of properties the calendar will be initialized with.
30
+ *
31
+ * @return array
32
+ */
33
+ function getProperties() {
34
+
35
+ return $this->properties;
36
+
37
+ }
38
+
39
+ /**
40
+ * The deserialize method is called during xml parsing.
41
+ *
42
+ * This method is called statically, this is because in theory this method
43
+ * may be used as a type of constructor, or factory method.
44
+ *
45
+ * Often you want to return an instance of the current class, but you are
46
+ * free to return other data as well.
47
+ *
48
+ * You are responsible for advancing the reader to the next element. Not
49
+ * doing anything will result in a never-ending loop.
50
+ *
51
+ * If you just want to skip parsing for this element altogether, you can
52
+ * just call $reader->next();
53
+ *
54
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
55
+ * the next element.
56
+ *
57
+ * @param Reader $reader
58
+ * @return mixed
59
+ */
60
+ static function xmlDeserialize(Reader $reader) {
61
+
62
+ $self = new self();
63
+
64
+ $elementMap = $reader->elementMap;
65
+ $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
66
+ $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
67
+ $elems = $reader->parseInnerTree($elementMap);
68
+
69
+ foreach ($elems as $elem) {
70
+ if ($elem['name'] === '{DAV:}set') {
71
+ $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
72
+ }
73
+ }
74
+
75
+ return $self;
76
+
77
+ }
78
+
79
+ }
vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CalDAV\Xml\Request;
4
+
5
+ use Sabre\CalDAV\Plugin;
6
+ use Sabre\DAV\Xml\Element\Sharee;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * Share POST request parser
12
+ *
13
+ * This class parses the share POST request, as defined in:
14
+ *
15
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ class Share implements XmlDeserializable {
22
+
23
+ /**
24
+ * The list of new people added or updated or removed from the share.
25
+ *
26
+ * @var Sharee[]
27
+ */
28
+ public $sharees = [];
29
+
30
+ /**
31
+ * Constructor
32
+ *
33
+ * @param Sharee[] $sharees
34
+ */
35
+ function __construct(array $sharees) {
36
+
37
+ $this->sharees = $sharees;
38
+
39
+ }
40
+
41
+ /**
42
+ * The deserialize method is called during xml parsing.
43
+ *
44
+ * This method is called statically, this is because in theory this method
45
+ * may be used as a type of constructor, or factory method.
46
+ *
47
+ * Often you want to return an instance of the current class, but you are
48
+ * free to return other data as well.
49
+ *
50
+ * You are responsible for advancing the reader to the next element. Not
51
+ * doing anything will result in a never-ending loop.
52
+ *
53
+ * If you just want to skip parsing for this element altogether, you can
54
+ * just call $reader->next();
55
+ *
56
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
57
+ * the next element.
58
+ *
59
+ * @param Reader $reader
60
+ * @return mixed
61
+ */
62
+ static function xmlDeserialize(Reader $reader) {
63
+
64
+ $elems = $reader->parseGetElements([
65
+ '{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue',
66
+ '{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue',
67
+ ]);
68
+
69
+ $sharees = [];
70
+
71
+ foreach ($elems as $elem) {
72
+ switch ($elem['name']) {
73
+
74
+ case '{' . Plugin::NS_CALENDARSERVER . '}set' :
75
+ $sharee = $elem['value'];
76
+
77
+ $sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary';
78
+ $commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name';
79
+
80
+ $properties = [];
81
+ if (isset($sharee[$commonName])) {
82
+ $properties['{DAV:}displayname'] = $sharee[$commonName];
83
+ }
84
+
85
+ $access = array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee)
86
+ ? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE
87
+ : \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
88
+
89
+ $sharees[] = new Sharee([
90
+ 'href' => $sharee['{DAV:}href'],
91
+ 'properties' => $properties,
92
+ 'access' => $access,
93
+ 'comment' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null
94
+ ]);
95
+ break;
96
+
97
+ case '{' . Plugin::NS_CALENDARSERVER . '}remove' :
98
+ $sharees[] = new Sharee([
99
+ 'href' => $elem['value']['{DAV:}href'],
100
+ 'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS
101
+ ]);
102
+ break;
103
+
104
+ }
105
+ }
106
+
107
+ return new self($sharees);
108
+
109
+ }
110
+
111
+ }
vendor/sabre/dav/lib/CardDAV/AddressBook.php ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAVACL;
7
+
8
+ /**
9
+ * The AddressBook class represents a CardDAV addressbook, owned by a specific user
10
+ *
11
+ * The AddressBook can contain multiple vcards
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet {
18
+
19
+ use DAVACL\ACLTrait;
20
+
21
+ /**
22
+ * This is an array with addressbook information
23
+ *
24
+ * @var array
25
+ */
26
+ protected $addressBookInfo;
27
+
28
+ /**
29
+ * CardDAV backend
30
+ *
31
+ * @var Backend\BackendInterface
32
+ */
33
+ protected $carddavBackend;
34
+
35
+ /**
36
+ * Constructor
37
+ *
38
+ * @param Backend\BackendInterface $carddavBackend
39
+ * @param array $addressBookInfo
40
+ */
41
+ function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) {
42
+
43
+ $this->carddavBackend = $carddavBackend;
44
+ $this->addressBookInfo = $addressBookInfo;
45
+
46
+ }
47
+
48
+ /**
49
+ * Returns the name of the addressbook
50
+ *
51
+ * @return string
52
+ */
53
+ function getName() {
54
+
55
+ return $this->addressBookInfo['uri'];
56
+
57
+ }
58
+
59
+ /**
60
+ * Returns a card
61
+ *
62
+ * @param string $name
63
+ * @return Card
64
+ */
65
+ function getChild($name) {
66
+
67
+ $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
68
+ if (!$obj) throw new DAV\Exception\NotFound('Card not found');
69
+ return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
70
+
71
+ }
72
+
73
+ /**
74
+ * Returns the full list of cards
75
+ *
76
+ * @return array
77
+ */
78
+ function getChildren() {
79
+
80
+ $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
81
+ $children = [];
82
+ foreach ($objs as $obj) {
83
+ $obj['acl'] = $this->getChildACL();
84
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
85
+ }
86
+ return $children;
87
+
88
+ }
89
+
90
+ /**
91
+ * This method receives a list of paths in it's first argument.
92
+ * It must return an array with Node objects.
93
+ *
94
+ * If any children are not found, you do not have to return them.
95
+ *
96
+ * @param string[] $paths
97
+ * @return array
98
+ */
99
+ function getMultipleChildren(array $paths) {
100
+
101
+ $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
102
+ $children = [];
103
+ foreach ($objs as $obj) {
104
+ $obj['acl'] = $this->getChildACL();
105
+ $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
106
+ }
107
+ return $children;
108
+
109
+ }
110
+
111
+ /**
112
+ * Creates a new directory
113
+ *
114
+ * We actually block this, as subdirectories are not allowed in addressbooks.
115
+ *
116
+ * @param string $name
117
+ * @return void
118
+ */
119
+ function createDirectory($name) {
120
+
121
+ throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed');
122
+
123
+ }
124
+
125
+ /**
126
+ * Creates a new file
127
+ *
128
+ * The contents of the new file must be a valid VCARD.
129
+ *
130
+ * This method may return an ETag.
131
+ *
132
+ * @param string $name
133
+ * @param resource $vcardData
134
+ * @return string|null
135
+ */
136
+ function createFile($name, $vcardData = null) {
137
+
138
+ if (is_resource($vcardData)) {
139
+ $vcardData = stream_get_contents($vcardData);
140
+ }
141
+ // Converting to UTF-8, if needed
142
+ $vcardData = DAV\StringUtil::ensureUTF8($vcardData);
143
+
144
+ return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData);
145
+
146
+ }
147
+
148
+ /**
149
+ * Deletes the entire addressbook.
150
+ *
151
+ * @return void
152
+ */
153
+ function delete() {
154
+
155
+ $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
156
+
157
+ }
158
+
159
+ /**
160
+ * Renames the addressbook
161
+ *
162
+ * @param string $newName
163
+ * @return void
164
+ */
165
+ function setName($newName) {
166
+
167
+ throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
168
+
169
+ }
170
+
171
+ /**
172
+ * Returns the last modification date as a unix timestamp.
173
+ *
174
+ * @return void
175
+ */
176
+ function getLastModified() {
177
+
178
+ return null;
179
+
180
+ }
181
+
182
+ /**
183
+ * Updates properties on this node.
184
+ *
185
+ * This method received a PropPatch object, which contains all the
186
+ * information about the update.
187
+ *
188
+ * To update specific properties, call the 'handle' method on this object.
189
+ * Read the PropPatch documentation for more information.
190
+ *
191
+ * @param DAV\PropPatch $propPatch
192
+ * @return void
193
+ */
194
+ function propPatch(DAV\PropPatch $propPatch) {
195
+
196
+ return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch);
197
+
198
+ }
199
+
200
+ /**
201
+ * Returns a list of properties for this nodes.
202
+ *
203
+ * The properties list is a list of propertynames the client requested,
204
+ * encoded in clark-notation {xmlnamespace}tagname
205
+ *
206
+ * If the array is empty, it means 'all properties' were requested.
207
+ *
208
+ * @param array $properties
209
+ * @return array
210
+ */
211
+ function getProperties($properties) {
212
+
213
+ $response = [];
214
+ foreach ($properties as $propertyName) {
215
+
216
+ if (isset($this->addressBookInfo[$propertyName])) {
217
+
218
+ $response[$propertyName] = $this->addressBookInfo[$propertyName];
219
+
220
+ }
221
+
222
+ }
223
+
224
+ return $response;
225
+
226
+ }
227
+
228
+ /**
229
+ * Returns the owner principal
230
+ *
231
+ * This must be a url to a principal, or null if there's no owner
232
+ *
233
+ * @return string|null
234
+ */
235
+ function getOwner() {
236
+
237
+ return $this->addressBookInfo['principaluri'];
238
+
239
+ }
240
+
241
+
242
+ /**
243
+ * This method returns the ACL's for card nodes in this address book.
244
+ * The result of this method automatically gets passed to the
245
+ * card nodes in this address book.
246
+ *
247
+ * @return array
248
+ */
249
+ function getChildACL() {
250
+
251
+ return [
252
+ [
253
+ 'privilege' => '{DAV:}all',
254
+ 'principal' => $this->getOwner(),
255
+ 'protected' => true,
256
+ ],
257
+ ];
258
+
259
+ }
260
+
261
+
262
+ /**
263
+ * This method returns the current sync-token for this collection.
264
+ * This can be any string.
265
+ *
266
+ * If null is returned from this function, the plugin assumes there's no
267
+ * sync information available.
268
+ *
269
+ * @return string|null
270
+ */
271
+ function getSyncToken() {
272
+
273
+ if (
274
+ $this->carddavBackend instanceof Backend\SyncSupport &&
275
+ isset($this->addressBookInfo['{DAV:}sync-token'])
276
+ ) {
277
+ return $this->addressBookInfo['{DAV:}sync-token'];
278
+ }
279
+ if (
280
+ $this->carddavBackend instanceof Backend\SyncSupport &&
281
+ isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
282
+ ) {
283
+ return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
284
+ }
285
+
286
+ }
287
+
288
+ /**
289
+ * The getChanges method returns all the changes that have happened, since
290
+ * the specified syncToken and the current collection.
291
+ *
292
+ * This function should return an array, such as the following:
293
+ *
294
+ * [
295
+ * 'syncToken' => 'The current synctoken',
296
+ * 'added' => [
297
+ * 'new.txt',
298
+ * ],
299
+ * 'modified' => [
300
+ * 'modified.txt',
301
+ * ],
302
+ * 'deleted' => [
303
+ * 'foo.php.bak',
304
+ * 'old.txt'
305
+ * ]
306
+ * ];
307
+ *
308
+ * The syncToken property should reflect the *current* syncToken of the
309
+ * collection, as reported getSyncToken(). This is needed here too, to
310
+ * ensure the operation is atomic.
311
+ *
312
+ * If the syncToken is specified as null, this is an initial sync, and all
313
+ * members should be reported.
314
+ *
315
+ * The modified property is an array of nodenames that have changed since
316
+ * the last token.
317
+ *
318
+ * The deleted property is an array with nodenames, that have been deleted
319
+ * from collection.
320
+ *
321
+ * The second argument is basically the 'depth' of the report. If it's 1,
322
+ * you only have to report changes that happened only directly in immediate
323
+ * descendants. If it's 2, it should also include changes from the nodes
324
+ * below the child collections. (grandchildren)
325
+ *
326
+ * The third (optional) argument allows a client to specify how many
327
+ * results should be returned at most. If the limit is not specified, it
328
+ * should be treated as infinite.
329
+ *
330
+ * If the limit (infinite or not) is higher than you're willing to return,
331
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
332
+ *
333
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
334
+ * return null.
335
+ *
336
+ * The limit is 'suggestive'. You are free to ignore it.
337
+ *
338
+ * @param string $syncToken
339
+ * @param int $syncLevel
340
+ * @param int $limit
341
+ * @return array
342
+ */
343
+ function getChanges($syncToken, $syncLevel, $limit = null) {
344
+
345
+ if (!$this->carddavBackend instanceof Backend\SyncSupport) {
346
+ return null;
347
+ }
348
+
349
+ return $this->carddavBackend->getChangesForAddressBook(
350
+ $this->addressBookInfo['id'],
351
+ $syncToken,
352
+ $syncLevel,
353
+ $limit
354
+ );
355
+
356
+ }
357
+ }
vendor/sabre/dav/lib/CardDAV/AddressBookHome.php ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\MkCol;
7
+ use Sabre\DAVACL;
8
+ use Sabre\Uri;
9
+
10
+ /**
11
+ * AddressBook Home class
12
+ *
13
+ * This collection contains a list of addressbooks associated with one user.
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {
20
+
21
+ use DAVACL\ACLTrait;
22
+
23
+ /**
24
+ * Principal uri
25
+ *
26
+ * @var array
27
+ */
28
+ protected $principalUri;
29
+
30
+ /**
31
+ * carddavBackend
32
+ *
33
+ * @var Backend\BackendInterface
34
+ */
35
+ protected $carddavBackend;
36
+
37
+ /**
38
+ * Constructor
39
+ *
40
+ * @param Backend\BackendInterface $carddavBackend
41
+ * @param string $principalUri
42
+ */
43
+ function __construct(Backend\BackendInterface $carddavBackend, $principalUri) {
44
+
45
+ $this->carddavBackend = $carddavBackend;
46
+ $this->principalUri = $principalUri;
47
+
48
+ }
49
+
50
+ /**
51
+ * Returns the name of this object
52
+ *
53
+ * @return string
54
+ */
55
+ function getName() {
56
+
57
+ list(, $name) = Uri\split($this->principalUri);
58
+ return $name;
59
+
60
+ }
61
+
62
+ /**
63
+ * Updates the name of this object
64
+ *
65
+ * @param string $name
66
+ * @return void
67
+ */
68
+ function setName($name) {
69
+
70
+ throw new DAV\Exception\MethodNotAllowed();
71
+
72
+ }
73
+
74
+ /**
75
+ * Deletes this object
76
+ *
77
+ * @return void
78
+ */
79
+ function delete() {
80
+
81
+ throw new DAV\Exception\MethodNotAllowed();
82
+
83
+ }
84
+
85
+ /**
86
+ * Returns the last modification date
87
+ *
88
+ * @return int
89
+ */
90
+ function getLastModified() {
91
+
92
+ return null;
93
+
94
+ }
95
+
96
+ /**
97
+ * Creates a new file under this object.
98
+ *
99
+ * This is currently not allowed
100
+ *
101
+ * @param string $filename
102
+ * @param resource $data
103
+ * @return void
104
+ */
105
+ function createFile($filename, $data = null) {
106
+
107
+ throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
108
+
109
+ }
110
+
111
+ /**
112
+ * Creates a new directory under this object.
113
+ *
114
+ * This is currently not allowed.
115
+ *
116
+ * @param string $filename
117
+ * @return void
118
+ */
119
+ function createDirectory($filename) {
120
+
121
+ throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
122
+
123
+ }
124
+
125
+ /**
126
+ * Returns a single addressbook, by name
127
+ *
128
+ * @param string $name
129
+ * @todo needs optimizing
130
+ * @return AddressBook
131
+ */
132
+ function getChild($name) {
133
+
134
+ foreach ($this->getChildren() as $child) {
135
+ if ($name == $child->getName())
136
+ return $child;
137
+
138
+ }
139
+ throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found');
140
+
141
+ }
142
+
143
+ /**
144
+ * Returns a list of addressbooks
145
+ *
146
+ * @return array
147
+ */
148
+ function getChildren() {
149
+
150
+ $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
151
+ $objs = [];
152
+ foreach ($addressbooks as $addressbook) {
153
+ $objs[] = new AddressBook($this->carddavBackend, $addressbook);
154
+ }
155
+ return $objs;
156
+
157
+ }
158
+
159
+ /**
160
+ * Creates a new address book.
161
+ *
162
+ * @param string $name
163
+ * @param MkCol $mkCol
164
+ * @throws DAV\Exception\InvalidResourceType
165
+ * @return void
166
+ */
167
+ function createExtendedCollection($name, MkCol $mkCol) {
168
+
169
+ if (!$mkCol->hasResourceType('{' . Plugin::NS_CARDDAV . '}addressbook')) {
170
+ throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
171
+ }
172
+ $properties = $mkCol->getRemainingValues();
173
+ $mkCol->setRemainingResultCode(201);
174
+ $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties);
175
+
176
+ }
177
+
178
+ /**
179
+ * Returns the owner principal
180
+ *
181
+ * This must be a url to a principal, or null if there's no owner
182
+ *
183
+ * @return string|null
184
+ */
185
+ function getOwner() {
186
+
187
+ return $this->principalUri;
188
+
189
+ }
190
+
191
+ }
vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAVACL;
6
+
7
+ /**
8
+ * AddressBook rootnode
9
+ *
10
+ * This object lists a collection of users, which can contain addressbooks.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ class AddressBookRoot extends DAVACL\AbstractPrincipalCollection {
17
+
18
+ /**
19
+ * Principal Backend
20
+ *
21
+ * @var DAVACL\PrincipalBackend\BackendInterface
22
+ */
23
+ protected $principalBackend;
24
+
25
+ /**
26
+ * CardDAV backend
27
+ *
28
+ * @var Backend\BackendInterface
29
+ */
30
+ protected $carddavBackend;
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * This constructor needs both a principal and a carddav backend.
36
+ *
37
+ * By default this class will show a list of addressbook collections for
38
+ * principals in the 'principals' collection. If your main principals are
39
+ * actually located in a different path, use the $principalPrefix argument
40
+ * to override this.
41
+ *
42
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
43
+ * @param Backend\BackendInterface $carddavBackend
44
+ * @param string $principalPrefix
45
+ */
46
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') {
47
+
48
+ $this->carddavBackend = $carddavBackend;
49
+ parent::__construct($principalBackend, $principalPrefix);
50
+
51
+ }
52
+
53
+ /**
54
+ * Returns the name of the node
55
+ *
56
+ * @return string
57
+ */
58
+ function getName() {
59
+
60
+ return Plugin::ADDRESSBOOK_ROOT;
61
+
62
+ }
63
+
64
+ /**
65
+ * This method returns a node for a principal.
66
+ *
67
+ * The passed array contains principal information, and is guaranteed to
68
+ * at least contain a uri item. Other properties may or may not be
69
+ * supplied by the authentication backend.
70
+ *
71
+ * @param array $principal
72
+ * @return \Sabre\DAV\INode
73
+ */
74
+ function getChildForPrincipal(array $principal) {
75
+
76
+ return new AddressBookHome($this->carddavBackend, $principal['uri']);
77
+
78
+ }
79
+
80
+ }
vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Backend;
4
+
5
+ /**
6
+ * CardDAV abstract Backend
7
+ *
8
+ * This class serves as a base-class for addressbook backends
9
+ *
10
+ * This class doesn't do much, but it was added for consistency.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ abstract class AbstractBackend implements BackendInterface {
17
+
18
+ /**
19
+ * Returns a list of cards.
20
+ *
21
+ * This method should work identical to getCard, but instead return all the
22
+ * cards in the list as an array.
23
+ *
24
+ * If the backend supports this, it may allow for some speed-ups.
25
+ *
26
+ * @param mixed $addressBookId
27
+ * @param array $uris
28
+ * @return array
29
+ */
30
+ function getMultipleCards($addressBookId, array $uris) {
31
+
32
+ return array_map(function($uri) use ($addressBookId) {
33
+ return $this->getCard($addressBookId, $uri);
34
+ }, $uris);
35
+
36
+ }
37
+
38
+ }
vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Backend;
4
+
5
+ /**
6
+ * CardDAV Backend Interface
7
+ *
8
+ * Any CardDAV backend must implement this interface.
9
+ *
10
+ * Note that there are references to 'addressBookId' scattered throughout the
11
+ * class. The value of the addressBookId is completely up to you, it can be any
12
+ * arbitrary value you can use as an unique identifier.
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ interface BackendInterface {
19
+
20
+ /**
21
+ * Returns the list of addressbooks for a specific user.
22
+ *
23
+ * Every addressbook should have the following properties:
24
+ * id - an arbitrary unique id
25
+ * uri - the 'basename' part of the url
26
+ * principaluri - Same as the passed parameter
27
+ *
28
+ * Any additional clark-notation property may be passed besides this. Some
29
+ * common ones are :
30
+ * {DAV:}displayname
31
+ * {urn:ietf:params:xml:ns:carddav}addressbook-description
32
+ * {http://calendarserver.org/ns/}getctag
33
+ *
34
+ * @param string $principalUri
35
+ * @return array
36
+ */
37
+ function getAddressBooksForUser($principalUri);
38
+
39
+ /**
40
+ * Updates properties for an address book.
41
+ *
42
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
43
+ * To do the actual updates, you must tell this object which properties
44
+ * you're going to process with the handle() method.
45
+ *
46
+ * Calling the handle method is like telling the PropPatch object "I
47
+ * promise I can handle updating this property".
48
+ *
49
+ * Read the PropPatch documentation for more info and examples.
50
+ *
51
+ * @param string $addressBookId
52
+ * @param \Sabre\DAV\PropPatch $propPatch
53
+ * @return void
54
+ */
55
+ function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);
56
+
57
+ /**
58
+ * Creates a new address book.
59
+ *
60
+ * This method should return the id of the new address book. The id can be
61
+ * in any format, including ints, strings, arrays or objects.
62
+ *
63
+ * @param string $principalUri
64
+ * @param string $url Just the 'basename' of the url.
65
+ * @param array $properties
66
+ * @return mixed
67
+ */
68
+ function createAddressBook($principalUri, $url, array $properties);
69
+
70
+ /**
71
+ * Deletes an entire addressbook and all its contents
72
+ *
73
+ * @param mixed $addressBookId
74
+ * @return void
75
+ */
76
+ function deleteAddressBook($addressBookId);
77
+
78
+ /**
79
+ * Returns all cards for a specific addressbook id.
80
+ *
81
+ * This method should return the following properties for each card:
82
+ * * carddata - raw vcard data
83
+ * * uri - Some unique url
84
+ * * lastmodified - A unix timestamp
85
+ *
86
+ * It's recommended to also return the following properties:
87
+ * * etag - A unique etag. This must change every time the card changes.
88
+ * * size - The size of the card in bytes.
89
+ *
90
+ * If these last two properties are provided, less time will be spent
91
+ * calculating them. If they are specified, you can also ommit carddata.
92
+ * This may speed up certain requests, especially with large cards.
93
+ *
94
+ * @param mixed $addressbookId
95
+ * @return array
96
+ */
97
+ function getCards($addressbookId);
98
+
99
+ /**
100
+ * Returns a specfic card.
101
+ *
102
+ * The same set of properties must be returned as with getCards. The only
103
+ * exception is that 'carddata' is absolutely required.
104
+ *
105
+ * If the card does not exist, you must return false.
106
+ *
107
+ * @param mixed $addressBookId
108
+ * @param string $cardUri
109
+ * @return array
110
+ */
111
+ function getCard($addressBookId, $cardUri);
112
+
113
+ /**
114
+ * Returns a list of cards.
115
+ *
116
+ * This method should work identical to getCard, but instead return all the
117
+ * cards in the list as an array.
118
+ *
119
+ * If the backend supports this, it may allow for some speed-ups.
120
+ *
121
+ * @param mixed $addressBookId
122
+ * @param array $uris
123
+ * @return array
124
+ */
125
+ function getMultipleCards($addressBookId, array $uris);
126
+
127
+ /**
128
+ * Creates a new card.
129
+ *
130
+ * The addressbook id will be passed as the first argument. This is the
131
+ * same id as it is returned from the getAddressBooksForUser method.
132
+ *
133
+ * The cardUri is a base uri, and doesn't include the full path. The
134
+ * cardData argument is the vcard body, and is passed as a string.
135
+ *
136
+ * It is possible to return an ETag from this method. This ETag is for the
137
+ * newly created resource, and must be enclosed with double quotes (that
138
+ * is, the string itself must contain the double quotes).
139
+ *
140
+ * You should only return the ETag if you store the carddata as-is. If a
141
+ * subsequent GET request on the same card does not have the same body,
142
+ * byte-by-byte and you did return an ETag here, clients tend to get
143
+ * confused.
144
+ *
145
+ * If you don't return an ETag, you can just return null.
146
+ *
147
+ * @param mixed $addressBookId
148
+ * @param string $cardUri
149
+ * @param string $cardData
150
+ * @return string|null
151
+ */
152
+ function createCard($addressBookId, $cardUri, $cardData);
153
+
154
+ /**
155
+ * Updates a card.
156
+ *
157
+ * The addressbook id will be passed as the first argument. This is the
158
+ * same id as it is returned from the getAddressBooksForUser method.
159
+ *
160
+ * The cardUri is a base uri, and doesn't include the full path. The
161
+ * cardData argument is the vcard body, and is passed as a string.
162
+ *
163
+ * It is possible to return an ETag from this method. This ETag should
164
+ * match that of the updated resource, and must be enclosed with double
165
+ * quotes (that is: the string itself must contain the actual quotes).
166
+ *
167
+ * You should only return the ETag if you store the carddata as-is. If a
168
+ * subsequent GET request on the same card does not have the same body,
169
+ * byte-by-byte and you did return an ETag here, clients tend to get
170
+ * confused.
171
+ *
172
+ * If you don't return an ETag, you can just return null.
173
+ *
174
+ * @param mixed $addressBookId
175
+ * @param string $cardUri
176
+ * @param string $cardData
177
+ * @return string|null
178
+ */
179
+ function updateCard($addressBookId, $cardUri, $cardData);
180
+
181
+ /**
182
+ * Deletes a card
183
+ *
184
+ * @param mixed $addressBookId
185
+ * @param string $cardUri
186
+ * @return bool
187
+ */
188
+ function deleteCard($addressBookId, $cardUri);
189
+
190
+ }
vendor/sabre/dav/lib/CardDAV/Backend/PDO.php ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Backend;
4
+
5
+ use Sabre\CardDAV;
6
+ use Sabre\DAV;
7
+
8
+ /**
9
+ * PDO CardDAV backend
10
+ *
11
+ * This CardDAV backend uses PDO to store addressbooks
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ class PDO extends AbstractBackend implements SyncSupport {
18
+
19
+ /**
20
+ * PDO connection
21
+ *
22
+ * @var PDO
23
+ */
24
+ protected $pdo;
25
+
26
+ /**
27
+ * The PDO table name used to store addressbooks
28
+ */
29
+ public $addressBooksTableName = 'addressbooks';
30
+
31
+ /**
32
+ * The PDO table name used to store cards
33
+ */
34
+ public $cardsTableName = 'cards';
35
+
36
+ /**
37
+ * The table name that will be used for tracking changes in address books.
38
+ *
39
+ * @var string
40
+ */
41
+ public $addressBookChangesTableName = 'addressbookchanges';
42
+
43
+ /**
44
+ * Sets up the object
45
+ *
46
+ * @param \PDO $pdo
47
+ */
48
+ function __construct(\PDO $pdo) {
49
+
50
+ $this->pdo = $pdo;
51
+
52
+ }
53
+
54
+ /**
55
+ * Returns the list of addressbooks for a specific user.
56
+ *
57
+ * @param string $principalUri
58
+ * @return array
59
+ */
60
+ function getAddressBooksForUser($principalUri) {
61
+
62
+ $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM ' . $this->addressBooksTableName . ' WHERE principaluri = ?');
63
+ $stmt->execute([$principalUri]);
64
+
65
+ $addressBooks = [];
66
+
67
+ foreach ($stmt->fetchAll() as $row) {
68
+
69
+ $addressBooks[] = [
70
+ 'id' => $row['id'],
71
+ 'uri' => $row['uri'],
72
+ 'principaluri' => $row['principaluri'],
73
+ '{DAV:}displayname' => $row['displayname'],
74
+ '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
75
+ '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
76
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
77
+ ];
78
+
79
+ }
80
+
81
+ return $addressBooks;
82
+
83
+ }
84
+
85
+
86
+ /**
87
+ * Updates properties for an address book.
88
+ *
89
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
90
+ * To do the actual updates, you must tell this object which properties
91
+ * you're going to process with the handle() method.
92
+ *
93
+ * Calling the handle method is like telling the PropPatch object "I
94
+ * promise I can handle updating this property".
95
+ *
96
+ * Read the PropPatch documentation for more info and examples.
97
+ *
98
+ * @param string $addressBookId
99
+ * @param \Sabre\DAV\PropPatch $propPatch
100
+ * @return void
101
+ */
102
+ function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
103
+
104
+ $supportedProperties = [
105
+ '{DAV:}displayname',
106
+ '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description',
107
+ ];
108
+
109
+ $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
110
+
111
+ $updates = [];
112
+ foreach ($mutations as $property => $newValue) {
113
+
114
+ switch ($property) {
115
+ case '{DAV:}displayname' :
116
+ $updates['displayname'] = $newValue;
117
+ break;
118
+ case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
119
+ $updates['description'] = $newValue;
120
+ break;
121
+ }
122
+ }
123
+ $query = 'UPDATE ' . $this->addressBooksTableName . ' SET ';
124
+ $first = true;
125
+ foreach ($updates as $key => $value) {
126
+ if ($first) {
127
+ $first = false;
128
+ } else {
129
+ $query .= ', ';
130
+ }
131
+ $query .= ' ' . $key . ' = :' . $key . ' ';
132
+ }
133
+ $query .= ' WHERE id = :addressbookid';
134
+
135
+ $stmt = $this->pdo->prepare($query);
136
+ $updates['addressbookid'] = $addressBookId;
137
+
138
+ $stmt->execute($updates);
139
+
140
+ $this->addChange($addressBookId, "", 2);
141
+
142
+ return true;
143
+
144
+ });
145
+
146
+ }
147
+
148
+ /**
149
+ * Creates a new address book
150
+ *
151
+ * @param string $principalUri
152
+ * @param string $url Just the 'basename' of the url.
153
+ * @param array $properties
154
+ * @return int Last insert id
155
+ */
156
+ function createAddressBook($principalUri, $url, array $properties) {
157
+
158
+ $values = [
159
+ 'displayname' => null,
160
+ 'description' => null,
161
+ 'principaluri' => $principalUri,
162
+ 'uri' => $url,
163
+ ];
164
+
165
+ foreach ($properties as $property => $newValue) {
166
+
167
+ switch ($property) {
168
+ case '{DAV:}displayname' :
169
+ $values['displayname'] = $newValue;
170
+ break;
171
+ case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
172
+ $values['description'] = $newValue;
173
+ break;
174
+ default :
175
+ throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
176
+ }
177
+
178
+ }
179
+
180
+ $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
181
+ $stmt = $this->pdo->prepare($query);
182
+ $stmt->execute($values);
183
+ return $this->pdo->lastInsertId(
184
+ $this->addressBooksTableName . '_id_seq'
185
+ );
186
+
187
+ }
188
+
189
+ /**
190
+ * Deletes an entire addressbook and all its contents
191
+ *
192
+ * @param int $addressBookId
193
+ * @return void
194
+ */
195
+ function deleteAddressBook($addressBookId) {
196
+
197
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
198
+ $stmt->execute([$addressBookId]);
199
+
200
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
201
+ $stmt->execute([$addressBookId]);
202
+
203
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBookChangesTableName . ' WHERE addressbookid = ?');
204
+ $stmt->execute([$addressBookId]);
205
+
206
+ }
207
+
208
+ /**
209
+ * Returns all cards for a specific addressbook id.
210
+ *
211
+ * This method should return the following properties for each card:
212
+ * * carddata - raw vcard data
213
+ * * uri - Some unique url
214
+ * * lastmodified - A unix timestamp
215
+ *
216
+ * It's recommended to also return the following properties:
217
+ * * etag - A unique etag. This must change every time the card changes.
218
+ * * size - The size of the card in bytes.
219
+ *
220
+ * If these last two properties are provided, less time will be spent
221
+ * calculating them. If they are specified, you can also ommit carddata.
222
+ * This may speed up certain requests, especially with large cards.
223
+ *
224
+ * @param mixed $addressbookId
225
+ * @return array
226
+ */
227
+ function getCards($addressbookId) {
228
+
229
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
230
+ $stmt->execute([$addressbookId]);
231
+
232
+ $result = [];
233
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
234
+ $row['etag'] = '"' . $row['etag'] . '"';
235
+ $row['lastmodified'] = (int)$row['lastmodified'];
236
+ $result[] = $row;
237
+ }
238
+ return $result;
239
+
240
+ }
241
+
242
+ /**
243
+ * Returns a specific card.
244
+ *
245
+ * The same set of properties must be returned as with getCards. The only
246
+ * exception is that 'carddata' is absolutely required.
247
+ *
248
+ * If the card does not exist, you must return false.
249
+ *
250
+ * @param mixed $addressBookId
251
+ * @param string $cardUri
252
+ * @return array
253
+ */
254
+ function getCard($addressBookId, $cardUri) {
255
+
256
+ $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
257
+ $stmt->execute([$addressBookId, $cardUri]);
258
+
259
+ $result = $stmt->fetch(\PDO::FETCH_ASSOC);
260
+
261
+ if (!$result) return false;
262
+
263
+ $result['etag'] = '"' . $result['etag'] . '"';
264
+ $result['lastmodified'] = (int)$result['lastmodified'];
265
+ return $result;
266
+
267
+ }
268
+
269
+ /**
270
+ * Returns a list of cards.
271
+ *
272
+ * This method should work identical to getCard, but instead return all the
273
+ * cards in the list as an array.
274
+ *
275
+ * If the backend supports this, it may allow for some speed-ups.
276
+ *
277
+ * @param mixed $addressBookId
278
+ * @param array $uris
279
+ * @return array
280
+ */
281
+ function getMultipleCards($addressBookId, array $uris) {
282
+
283
+ $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri IN (';
284
+ // Inserting a whole bunch of question marks
285
+ $query .= implode(',', array_fill(0, count($uris), '?'));
286
+ $query .= ')';
287
+
288
+ $stmt = $this->pdo->prepare($query);
289
+ $stmt->execute(array_merge([$addressBookId], $uris));
290
+ $result = [];
291
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
292
+ $row['etag'] = '"' . $row['etag'] . '"';
293
+ $row['lastmodified'] = (int)$row['lastmodified'];
294
+ $result[] = $row;
295
+ }
296
+ return $result;
297
+
298
+ }
299
+
300
+ /**
301
+ * Creates a new card.
302
+ *
303
+ * The addressbook id will be passed as the first argument. This is the
304
+ * same id as it is returned from the getAddressBooksForUser method.
305
+ *
306
+ * The cardUri is a base uri, and doesn't include the full path. The
307
+ * cardData argument is the vcard body, and is passed as a string.
308
+ *
309
+ * It is possible to return an ETag from this method. This ETag is for the
310
+ * newly created resource, and must be enclosed with double quotes (that
311
+ * is, the string itself must contain the double quotes).
312
+ *
313
+ * You should only return the ETag if you store the carddata as-is. If a
314
+ * subsequent GET request on the same card does not have the same body,
315
+ * byte-by-byte and you did return an ETag here, clients tend to get
316
+ * confused.
317
+ *
318
+ * If you don't return an ETag, you can just return null.
319
+ *
320
+ * @param mixed $addressBookId
321
+ * @param string $cardUri
322
+ * @param string $cardData
323
+ * @return string|null
324
+ */
325
+ function createCard($addressBookId, $cardUri, $cardData) {
326
+
327
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
328
+
329
+ $etag = md5($cardData);
330
+
331
+ $stmt->execute([
332
+ $cardData,
333
+ $cardUri,
334
+ time(),
335
+ $addressBookId,
336
+ strlen($cardData),
337
+ $etag,
338
+ ]);
339
+
340
+ $this->addChange($addressBookId, $cardUri, 1);
341
+
342
+ return '"' . $etag . '"';
343
+
344
+ }
345
+
346
+ /**
347
+ * Updates a card.
348
+ *
349
+ * The addressbook id will be passed as the first argument. This is the
350
+ * same id as it is returned from the getAddressBooksForUser method.
351
+ *
352
+ * The cardUri is a base uri, and doesn't include the full path. The
353
+ * cardData argument is the vcard body, and is passed as a string.
354
+ *
355
+ * It is possible to return an ETag from this method. This ETag should
356
+ * match that of the updated resource, and must be enclosed with double
357
+ * quotes (that is: the string itself must contain the actual quotes).
358
+ *
359
+ * You should only return the ETag if you store the carddata as-is. If a
360
+ * subsequent GET request on the same card does not have the same body,
361
+ * byte-by-byte and you did return an ETag here, clients tend to get
362
+ * confused.
363
+ *
364
+ * If you don't return an ETag, you can just return null.
365
+ *
366
+ * @param mixed $addressBookId
367
+ * @param string $cardUri
368
+ * @param string $cardData
369
+ * @return string|null
370
+ */
371
+ function updateCard($addressBookId, $cardUri, $cardData) {
372
+
373
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?');
374
+
375
+ $etag = md5($cardData);
376
+ $stmt->execute([
377
+ $cardData,
378
+ time(),
379
+ strlen($cardData),
380
+ $etag,
381
+ $cardUri,
382
+ $addressBookId
383
+ ]);
384
+
385
+ $this->addChange($addressBookId, $cardUri, 2);
386
+
387
+ return '"' . $etag . '"';
388
+
389
+ }
390
+
391
+ /**
392
+ * Deletes a card
393
+ *
394
+ * @param mixed $addressBookId
395
+ * @param string $cardUri
396
+ * @return bool
397
+ */
398
+ function deleteCard($addressBookId, $cardUri) {
399
+
400
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
401
+ $stmt->execute([$addressBookId, $cardUri]);
402
+
403
+ $this->addChange($addressBookId, $cardUri, 3);
404
+
405
+ return $stmt->rowCount() === 1;
406
+
407
+ }
408
+
409
+ /**
410
+ * The getChanges method returns all the changes that have happened, since
411
+ * the specified syncToken in the specified address book.
412
+ *
413
+ * This function should return an array, such as the following:
414
+ *
415
+ * [
416
+ * 'syncToken' => 'The current synctoken',
417
+ * 'added' => [
418
+ * 'new.txt',
419
+ * ],
420
+ * 'modified' => [
421
+ * 'updated.txt',
422
+ * ],
423
+ * 'deleted' => [
424
+ * 'foo.php.bak',
425
+ * 'old.txt'
426
+ * ]
427
+ * ];
428
+ *
429
+ * The returned syncToken property should reflect the *current* syncToken
430
+ * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
431
+ * property. This is needed here too, to ensure the operation is atomic.
432
+ *
433
+ * If the $syncToken argument is specified as null, this is an initial
434
+ * sync, and all members should be reported.
435
+ *
436
+ * The modified property is an array of nodenames that have changed since
437
+ * the last token.
438
+ *
439
+ * The deleted property is an array with nodenames, that have been deleted
440
+ * from collection.
441
+ *
442
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
443
+ * 1, you only have to report changes that happened only directly in
444
+ * immediate descendants. If it's 2, it should also include changes from
445
+ * the nodes below the child collections. (grandchildren)
446
+ *
447
+ * The $limit argument allows a client to specify how many results should
448
+ * be returned at most. If the limit is not specified, it should be treated
449
+ * as infinite.
450
+ *
451
+ * If the limit (infinite or not) is higher than you're willing to return,
452
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
453
+ *
454
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
455
+ * return null.
456
+ *
457
+ * The limit is 'suggestive'. You are free to ignore it.
458
+ *
459
+ * @param string $addressBookId
460
+ * @param string $syncToken
461
+ * @param int $syncLevel
462
+ * @param int $limit
463
+ * @return array
464
+ */
465
+ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
466
+
467
+ // Current synctoken
468
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
469
+ $stmt->execute([$addressBookId]);
470
+ $currentToken = $stmt->fetchColumn(0);
471
+
472
+ if (is_null($currentToken)) return null;
473
+
474
+ $result = [
475
+ 'syncToken' => $currentToken,
476
+ 'added' => [],
477
+ 'modified' => [],
478
+ 'deleted' => [],
479
+ ];
480
+
481
+ if ($syncToken) {
482
+
483
+ $query = "SELECT uri, operation FROM " . $this->addressBookChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken";
484
+ if ($limit > 0) $query .= " LIMIT " . (int)$limit;
485
+
486
+ // Fetching all changes
487
+ $stmt = $this->pdo->prepare($query);
488
+ $stmt->execute([$syncToken, $currentToken, $addressBookId]);
489
+
490
+ $changes = [];
491
+
492
+ // This loop ensures that any duplicates are overwritten, only the
493
+ // last change on a node is relevant.
494
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
495
+
496
+ $changes[$row['uri']] = $row['operation'];
497
+
498
+ }
499
+
500
+ foreach ($changes as $uri => $operation) {
501
+
502
+ switch ($operation) {
503
+ case 1:
504
+ $result['added'][] = $uri;
505
+ break;
506
+ case 2:
507
+ $result['modified'][] = $uri;
508
+ break;
509
+ case 3:
510
+ $result['deleted'][] = $uri;
511
+ break;
512
+ }
513
+
514
+ }
515
+ } else {
516
+ // No synctoken supplied, this is the initial sync.
517
+ $query = "SELECT uri FROM " . $this->cardsTableName . " WHERE addressbookid = ?";
518
+ $stmt = $this->pdo->prepare($query);
519
+ $stmt->execute([$addressBookId]);
520
+
521
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
522
+ }
523
+ return $result;
524
+
525
+ }
526
+
527
+ /**
528
+ * Adds a change record to the addressbookchanges table.
529
+ *
530
+ * @param mixed $addressBookId
531
+ * @param string $objectUri
532
+ * @param int $operation 1 = add, 2 = modify, 3 = delete
533
+ * @return void
534
+ */
535
+ protected function addChange($addressBookId, $objectUri, $operation) {
536
+
537
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
538
+ $stmt->execute([
539
+ $objectUri,
540
+ $addressBookId,
541
+ $operation,
542
+ $addressBookId
543
+ ]);
544
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
545
+ $stmt->execute([
546
+ $addressBookId
547
+ ]);
548
+
549
+ }
550
+ }
vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Backend;
4
+
5
+ /**
6
+ * WebDAV-sync support for CardDAV backends.
7
+ *
8
+ * In order for backends to advertise support for WebDAV-sync, this interface
9
+ * must be implemented.
10
+ *
11
+ * Implementing this can result in a significant reduction of bandwidth and CPU
12
+ * time.
13
+ *
14
+ * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
15
+ * property from getAddressBooksForUser.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ interface SyncSupport extends BackendInterface {
22
+
23
+ /**
24
+ * The getChanges method returns all the changes that have happened, since
25
+ * the specified syncToken in the specified address book.
26
+ *
27
+ * This function should return an array, such as the following:
28
+ *
29
+ * [
30
+ * 'syncToken' => 'The current synctoken',
31
+ * 'added' => [
32
+ * 'new.txt',
33
+ * ],
34
+ * 'modified' => [
35
+ * 'modified.txt',
36
+ * ],
37
+ * 'deleted' => [
38
+ * 'foo.php.bak',
39
+ * 'old.txt'
40
+ * ]
41
+ * ];
42
+ *
43
+ * The returned syncToken property should reflect the *current* syncToken
44
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
45
+ * property. This is needed here too, to ensure the operation is atomic.
46
+ *
47
+ * If the $syncToken argument is specified as null, this is an initial
48
+ * sync, and all members should be reported.
49
+ *
50
+ * The modified property is an array of nodenames that have changed since
51
+ * the last token.
52
+ *
53
+ * The deleted property is an array with nodenames, that have been deleted
54
+ * from collection.
55
+ *
56
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
57
+ * 1, you only have to report changes that happened only directly in
58
+ * immediate descendants. If it's 2, it should also include changes from
59
+ * the nodes below the child collections. (grandchildren)
60
+ *
61
+ * The $limit argument allows a client to specify how many results should
62
+ * be returned at most. If the limit is not specified, it should be treated
63
+ * as infinite.
64
+ *
65
+ * If the limit (infinite or not) is higher than you're willing to return,
66
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
67
+ *
68
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
69
+ * return null.
70
+ *
71
+ * The limit is 'suggestive'. You are free to ignore it.
72
+ *
73
+ * @param string $addressBookId
74
+ * @param string $syncToken
75
+ * @param int $syncLevel
76
+ * @param int $limit
77
+ * @return array
78
+ */
79
+ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);
80
+
81
+ }
vendor/sabre/dav/lib/CardDAV/Card.php ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAVACL;
7
+
8
+ /**
9
+ * The Card object represents a single Card from an addressbook
10
+ *
11
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
12
+ * @author Evert Pot (http://evertpot.com/)
13
+ * @license http://sabre.io/license/ Modified BSD License
14
+ */
15
+ class Card extends DAV\File implements ICard, DAVACL\IACL {
16
+
17
+ use DAVACL\ACLTrait;
18
+
19
+ /**
20
+ * CardDAV backend
21
+ *
22
+ * @var Backend\BackendInterface
23
+ */
24
+ protected $carddavBackend;
25
+
26
+ /**
27
+ * Array with information about this Card
28
+ *
29
+ * @var array
30
+ */
31
+ protected $cardData;
32
+
33
+ /**
34
+ * Array with information about the containing addressbook
35
+ *
36
+ * @var array
37
+ */
38
+ protected $addressBookInfo;
39
+
40
+ /**
41
+ * Constructor
42
+ *
43
+ * @param Backend\BackendInterface $carddavBackend
44
+ * @param array $addressBookInfo
45
+ * @param array $cardData
46
+ */
47
+ function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo, array $cardData) {
48
+
49
+ $this->carddavBackend = $carddavBackend;
50
+ $this->addressBookInfo = $addressBookInfo;
51
+ $this->cardData = $cardData;
52
+
53
+ }
54
+
55
+ /**
56
+ * Returns the uri for this object
57
+ *
58
+ * @return string
59
+ */
60
+ function getName() {
61
+
62
+ return $this->cardData['uri'];
63
+
64
+ }
65
+
66
+ /**
67
+ * Returns the VCard-formatted object
68
+ *
69
+ * @return string
70
+ */
71
+ function get() {
72
+
73
+ // Pre-populating 'carddata' is optional. If we don't yet have it
74
+ // already, we fetch it from the backend.
75
+ if (!isset($this->cardData['carddata'])) {
76
+ $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
77
+ }
78
+ return $this->cardData['carddata'];
79
+
80
+ }
81
+
82
+ /**
83
+ * Updates the VCard-formatted object
84
+ *
85
+ * @param string $cardData
86
+ * @return string|null
87
+ */
88
+ function put($cardData) {
89
+
90
+ if (is_resource($cardData))
91
+ $cardData = stream_get_contents($cardData);
92
+
93
+ // Converting to UTF-8, if needed
94
+ $cardData = DAV\StringUtil::ensureUTF8($cardData);
95
+
96
+ $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData);
97
+ $this->cardData['carddata'] = $cardData;
98
+ $this->cardData['etag'] = $etag;
99
+
100
+ return $etag;
101
+
102
+ }
103
+
104
+ /**
105
+ * Deletes the card
106
+ *
107
+ * @return void
108
+ */
109
+ function delete() {
110
+
111
+ $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);
112
+
113
+ }
114
+
115
+ /**
116
+ * Returns the mime content-type
117
+ *
118
+ * @return string
119
+ */
120
+ function getContentType() {
121
+
122
+ return 'text/vcard; charset=utf-8';
123
+
124
+ }
125
+
126
+ /**
127
+ * Returns an ETag for this object
128
+ *
129
+ * @return string
130
+ */
131
+ function getETag() {
132
+
133
+ if (isset($this->cardData['etag'])) {
134
+ return $this->cardData['etag'];
135
+ } else {
136
+ $data = $this->get();
137
+ if (is_string($data)) {
138
+ return '"' . md5($data) . '"';
139
+ } else {
140
+ // We refuse to calculate the md5 if it's a stream.
141
+ return null;
142
+ }
143
+ }
144
+
145
+ }
146
+
147
+ /**
148
+ * Returns the last modification date as a unix timestamp
149
+ *
150
+ * @return int
151
+ */
152
+ function getLastModified() {
153
+
154
+ return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null;
155
+
156
+ }
157
+
158
+ /**
159
+ * Returns the size of this object in bytes
160
+ *
161
+ * @return int
162
+ */
163
+ function getSize() {
164
+
165
+ if (array_key_exists('size', $this->cardData)) {
166
+ return $this->cardData['size'];
167
+ } else {
168
+ return strlen($this->get());
169
+ }
170
+
171
+ }
172
+
173
+ /**
174
+ * Returns the owner principal
175
+ *
176
+ * This must be a url to a principal, or null if there's no owner
177
+ *
178
+ * @return string|null
179
+ */
180
+ function getOwner() {
181
+
182
+ return $this->addressBookInfo['principaluri'];
183
+
184
+ }
185
+
186
+
187
+ /**
188
+ * Returns a list of ACE's for this node.
189
+ *
190
+ * Each ACE has the following properties:
191
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
192
+ * currently the only supported privileges
193
+ * * 'principal', a url to the principal who owns the node
194
+ * * 'protected' (optional), indicating that this ACE is not allowed to
195
+ * be updated.
196
+ *
197
+ * @return array
198
+ */
199
+ function getACL() {
200
+
201
+ // An alternative acl may be specified through the cardData array.
202
+ if (isset($this->cardData['acl'])) {
203
+ return $this->cardData['acl'];
204
+ }
205
+
206
+ return [
207
+ [
208
+ 'privilege' => '{DAV:}all',
209
+ 'principal' => $this->addressBookInfo['principaluri'],
210
+ 'protected' => true,
211
+ ],
212
+ ];
213
+
214
+ }
215
+
216
+ }
vendor/sabre/dav/lib/CardDAV/IAddressBook.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * AddressBook interface
9
+ *
10
+ * Implement this interface to allow a node to be recognized as an addressbook.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ interface IAddressBook extends DAV\ICollection {
17
+
18
+ }
vendor/sabre/dav/lib/CardDAV/ICard.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * Card interface
9
+ *
10
+ * Extend the ICard interface to allow your custom nodes to be picked up as
11
+ * 'Cards'.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ interface ICard extends DAV\IFile {
18
+
19
+ }
vendor/sabre/dav/lib/CardDAV/IDirectory.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ /**
6
+ * IDirectory interface
7
+ *
8
+ * Implement this interface to have an addressbook marked as a 'directory'. A
9
+ * directory is an (often) global addressbook.
10
+ *
11
+ * A full description can be found in the IETF draft:
12
+ * - draft-daboo-carddav-directory-gateway
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ interface IDirectory extends IAddressBook {
19
+
20
+ }
vendor/sabre/dav/lib/CardDAV/Plugin.php ADDED
@@ -0,0 +1,940 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\Exception\ReportNotSupported;
7
+ use Sabre\DAV\Xml\Property\LocalHref;
8
+ use Sabre\DAVACL;
9
+ use Sabre\HTTP;
10
+ use Sabre\HTTP\RequestInterface;
11
+ use Sabre\HTTP\ResponseInterface;
12
+ use Sabre\VObject;
13
+
14
+ /**
15
+ * CardDAV plugin
16
+ *
17
+ * The CardDAV plugin adds CardDAV functionality to the WebDAV server
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class Plugin extends DAV\ServerPlugin {
24
+
25
+ /**
26
+ * Url to the addressbooks
27
+ */
28
+ const ADDRESSBOOK_ROOT = 'addressbooks';
29
+
30
+ /**
31
+ * xml namespace for CardDAV elements
32
+ */
33
+ const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
34
+
35
+ /**
36
+ * Add urls to this property to have them automatically exposed as
37
+ * 'directories' to the user.
38
+ *
39
+ * @var array
40
+ */
41
+ public $directories = [];
42
+
43
+ /**
44
+ * Server class
45
+ *
46
+ * @var DAV\Server
47
+ */
48
+ protected $server;
49
+
50
+ /**
51
+ * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
52
+ * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
53
+ * capping it to 10M here.
54
+ */
55
+ protected $maxResourceSize = 10000000;
56
+
57
+ /**
58
+ * Initializes the plugin
59
+ *
60
+ * @param DAV\Server $server
61
+ * @return void
62
+ */
63
+ function initialize(DAV\Server $server) {
64
+
65
+ /* Events */
66
+ $server->on('propFind', [$this, 'propFindEarly']);
67
+ $server->on('propFind', [$this, 'propFindLate'], 150);
68
+ $server->on('report', [$this, 'report']);
69
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
70
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
71
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
72
+ $server->on('afterMethod:GET', [$this, 'httpAfterGet']);
73
+
74
+ $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
75
+
76
+ $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
77
+ $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
78
+
79
+ /* Mapping Interfaces to {DAV:}resourcetype values */
80
+ $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
81
+ $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory';
82
+
83
+ /* Adding properties that may never be changed */
84
+ $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
85
+ $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
86
+ $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
87
+ $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';
88
+
89
+ $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';
90
+
91
+ $this->server = $server;
92
+
93
+ }
94
+
95
+ /**
96
+ * Returns a list of supported features.
97
+ *
98
+ * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
99
+ *
100
+ * @return array
101
+ */
102
+ function getFeatures() {
103
+
104
+ return ['addressbook'];
105
+
106
+ }
107
+
108
+ /**
109
+ * Returns a list of reports this plugin supports.
110
+ *
111
+ * This will be used in the {DAV:}supported-report-set property.
112
+ * Note that you still need to subscribe to the 'report' event to actually
113
+ * implement them
114
+ *
115
+ * @param string $uri
116
+ * @return array
117
+ */
118
+ function getSupportedReportSet($uri) {
119
+
120
+ $node = $this->server->tree->getNodeForPath($uri);
121
+ if ($node instanceof IAddressBook || $node instanceof ICard) {
122
+ return [
123
+ '{' . self::NS_CARDDAV . '}addressbook-multiget',
124
+ '{' . self::NS_CARDDAV . '}addressbook-query',
125
+ ];
126
+ }
127
+ return [];
128
+
129
+ }
130
+
131
+
132
+ /**
133
+ * Adds all CardDAV-specific properties
134
+ *
135
+ * @param DAV\PropFind $propFind
136
+ * @param DAV\INode $node
137
+ * @return void
138
+ */
139
+ function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
140
+
141
+ $ns = '{' . self::NS_CARDDAV . '}';
142
+
143
+ if ($node instanceof IAddressBook) {
144
+
145
+ $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
146
+ $propFind->handle($ns . 'supported-address-data', function() {
147
+ return new Xml\Property\SupportedAddressData();
148
+ });
149
+ $propFind->handle($ns . 'supported-collation-set', function() {
150
+ return new Xml\Property\SupportedCollationSet();
151
+ });
152
+
153
+ }
154
+ if ($node instanceof DAVACL\IPrincipal) {
155
+
156
+ $path = $propFind->getPath();
157
+
158
+ $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
159
+ return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/');
160
+ });
161
+
162
+ if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
163
+ return new LocalHref($this->directories);
164
+ });
165
+
166
+ }
167
+
168
+ if ($node instanceof ICard) {
169
+
170
+ // The address-data property is not supposed to be a 'real'
171
+ // property, but in large chunks of the spec it does act as such.
172
+ // Therefore we simply expose it as a property.
173
+ $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) {
174
+ $val = $node->get();
175
+ if (is_resource($val))
176
+ $val = stream_get_contents($val);
177
+
178
+ return $val;
179
+
180
+ });
181
+
182
+ }
183
+
184
+ }
185
+
186
+ /**
187
+ * This functions handles REPORT requests specific to CardDAV
188
+ *
189
+ * @param string $reportName
190
+ * @param \DOMNode $dom
191
+ * @param mixed $path
192
+ * @return bool
193
+ */
194
+ function report($reportName, $dom, $path) {
195
+
196
+ switch ($reportName) {
197
+ case '{' . self::NS_CARDDAV . '}addressbook-multiget' :
198
+ $this->server->transactionType = 'report-addressbook-multiget';
199
+ $this->addressbookMultiGetReport($dom);
200
+ return false;
201
+ case '{' . self::NS_CARDDAV . '}addressbook-query' :
202
+ $this->server->transactionType = 'report-addressbook-query';
203
+ $this->addressBookQueryReport($dom);
204
+ return false;
205
+ default :
206
+ return;
207
+
208
+ }
209
+
210
+
211
+ }
212
+
213
+ /**
214
+ * Returns the addressbook home for a given principal
215
+ *
216
+ * @param string $principal
217
+ * @return string
218
+ */
219
+ protected function getAddressbookHomeForPrincipal($principal) {
220
+
221
+ list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal);
222
+ return self::ADDRESSBOOK_ROOT . '/' . $principalId;
223
+
224
+ }
225
+
226
+
227
+ /**
228
+ * This function handles the addressbook-multiget REPORT.
229
+ *
230
+ * This report is used by the client to fetch the content of a series
231
+ * of urls. Effectively avoiding a lot of redundant requests.
232
+ *
233
+ * @param Xml\Request\AddressBookMultiGetReport $report
234
+ * @return void
235
+ */
236
+ function addressbookMultiGetReport($report) {
237
+
238
+ $contentType = $report->contentType;
239
+ $version = $report->version;
240
+ if ($version) {
241
+ $contentType .= '; version=' . $version;
242
+ }
243
+
244
+ $vcardType = $this->negotiateVCard(
245
+ $contentType
246
+ );
247
+
248
+ $propertyList = [];
249
+ $paths = array_map(
250
+ [$this->server, 'calculateUri'],
251
+ $report->hrefs
252
+ );
253
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
254
+
255
+ if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) {
256
+
257
+ $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
258
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'],
259
+ $vcardType
260
+ );
261
+
262
+ }
263
+ $propertyList[] = $props;
264
+
265
+ }
266
+
267
+ $prefer = $this->server->getHTTPPrefer();
268
+
269
+ $this->server->httpResponse->setStatus(207);
270
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
271
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
272
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
273
+
274
+ }
275
+
276
+ /**
277
+ * This method is triggered before a file gets updated with new content.
278
+ *
279
+ * This plugin uses this method to ensure that Card nodes receive valid
280
+ * vcard data.
281
+ *
282
+ * @param string $path
283
+ * @param DAV\IFile $node
284
+ * @param resource $data
285
+ * @param bool $modified Should be set to true, if this event handler
286
+ * changed &$data.
287
+ * @return void
288
+ */
289
+ function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
290
+
291
+ if (!$node instanceof ICard)
292
+ return;
293
+
294
+ $this->validateVCard($data, $modified);
295
+
296
+ }
297
+
298
+ /**
299
+ * This method is triggered before a new file is created.
300
+ *
301
+ * This plugin uses this method to ensure that Card nodes receive valid
302
+ * vcard data.
303
+ *
304
+ * @param string $path
305
+ * @param resource $data
306
+ * @param DAV\ICollection $parentNode
307
+ * @param bool $modified Should be set to true, if this event handler
308
+ * changed &$data.
309
+ * @return void
310
+ */
311
+ function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
312
+
313
+ if (!$parentNode instanceof IAddressBook)
314
+ return;
315
+
316
+ $this->validateVCard($data, $modified);
317
+
318
+ }
319
+
320
+ /**
321
+ * Checks if the submitted iCalendar data is in fact, valid.
322
+ *
323
+ * An exception is thrown if it's not.
324
+ *
325
+ * @param resource|string $data
326
+ * @param bool $modified Should be set to true, if this event handler
327
+ * changed &$data.
328
+ * @return void
329
+ */
330
+ protected function validateVCard(&$data, &$modified) {
331
+
332
+ // If it's a stream, we convert it to a string first.
333
+ if (is_resource($data)) {
334
+ $data = stream_get_contents($data);
335
+ }
336
+
337
+ $before = $data;
338
+
339
+ try {
340
+
341
+ // If the data starts with a [, we can reasonably assume we're dealing
342
+ // with a jCal object.
343
+ if (substr($data, 0, 1) === '[') {
344
+ $vobj = VObject\Reader::readJson($data);
345
+
346
+ // Converting $data back to iCalendar, as that's what we
347
+ // technically support everywhere.
348
+ $data = $vobj->serialize();
349
+ $modified = true;
350
+ } else {
351
+ $vobj = VObject\Reader::read($data);
352
+ }
353
+
354
+ } catch (VObject\ParseException $e) {
355
+
356
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage());
357
+
358
+ }
359
+
360
+ if ($vobj->name !== 'VCARD') {
361
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
362
+ }
363
+
364
+ $options = VObject\Node::PROFILE_CARDDAV;
365
+ $prefer = $this->server->getHTTPPrefer();
366
+
367
+ if ($prefer['handling'] !== 'strict') {
368
+ $options |= VObject\Node::REPAIR;
369
+ }
370
+
371
+ $messages = $vobj->validate($options);
372
+
373
+ $highestLevel = 0;
374
+ $warningMessage = null;
375
+
376
+ // $messages contains a list of problems with the vcard, along with
377
+ // their severity.
378
+ foreach ($messages as $message) {
379
+
380
+ if ($message['level'] > $highestLevel) {
381
+ // Recording the highest reported error level.
382
+ $highestLevel = $message['level'];
383
+ $warningMessage = $message['message'];
384
+ }
385
+
386
+ switch ($message['level']) {
387
+
388
+ case 1 :
389
+ // Level 1 means that there was a problem, but it was repaired.
390
+ $modified = true;
391
+ break;
392
+ case 2 :
393
+ // Level 2 means a warning, but not critical
394
+ break;
395
+ case 3 :
396
+ // Level 3 means a critical error
397
+ throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: ' . $message['message']);
398
+
399
+ }
400
+
401
+ }
402
+ if ($warningMessage) {
403
+ $this->server->httpResponse->setHeader(
404
+ 'X-Sabre-Ew-Gross',
405
+ 'vCard validation warning: ' . $warningMessage
406
+ );
407
+
408
+ // Re-serializing object.
409
+ $data = $vobj->serialize();
410
+ if (!$modified && strcmp($data, $before) !== 0) {
411
+ // This ensures that the system does not send an ETag back.
412
+ $modified = true;
413
+ }
414
+ }
415
+
416
+ // Destroy circular references to PHP will GC the object.
417
+ $vobj->destroy();
418
+ }
419
+
420
+
421
+ /**
422
+ * This function handles the addressbook-query REPORT
423
+ *
424
+ * This report is used by the client to filter an addressbook based on a
425
+ * complex query.
426
+ *
427
+ * @param Xml\Request\AddressBookQueryReport $report
428
+ * @return void
429
+ */
430
+ protected function addressbookQueryReport($report) {
431
+
432
+ $depth = $this->server->getHTTPDepth(0);
433
+
434
+ if ($depth == 0) {
435
+ $candidateNodes = [
436
+ $this->server->tree->getNodeForPath($this->server->getRequestUri())
437
+ ];
438
+ if (!$candidateNodes[0] instanceof ICard) {
439
+ throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
440
+ }
441
+ } else {
442
+ $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
443
+ }
444
+
445
+ $contentType = $report->contentType;
446
+ if ($report->version) {
447
+ $contentType .= '; version=' . $report->version;
448
+ }
449
+
450
+ $vcardType = $this->negotiateVCard(
451
+ $contentType
452
+ );
453
+
454
+ $validNodes = [];
455
+ foreach ($candidateNodes as $node) {
456
+
457
+ if (!$node instanceof ICard)
458
+ continue;
459
+
460
+ $blob = $node->get();
461
+ if (is_resource($blob)) {
462
+ $blob = stream_get_contents($blob);
463
+ }
464
+
465
+ if (!$this->validateFilters($blob, $report->filters, $report->test)) {
466
+ continue;
467
+ }
468
+
469
+ $validNodes[] = $node;
470
+
471
+ if ($report->limit && $report->limit <= count($validNodes)) {
472
+ // We hit the maximum number of items, we can stop now.
473
+ break;
474
+ }
475
+
476
+ }
477
+
478
+ $result = [];
479
+ foreach ($validNodes as $validNode) {
480
+
481
+ if ($depth == 0) {
482
+ $href = $this->server->getRequestUri();
483
+ } else {
484
+ $href = $this->server->getRequestUri() . '/' . $validNode->getName();
485
+ }
486
+
487
+ list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
488
+
489
+ if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) {
490
+
491
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
492
+ $props[200]['{' . self::NS_CARDDAV . '}address-data'],
493
+ $vcardType,
494
+ $report->addressDataProperties
495
+ );
496
+
497
+ }
498
+ $result[] = $props;
499
+
500
+ }
501
+
502
+ $prefer = $this->server->getHTTPPrefer();
503
+
504
+ $this->server->httpResponse->setStatus(207);
505
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
506
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
507
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
508
+
509
+ }
510
+
511
+ /**
512
+ * Validates if a vcard makes it throught a list of filters.
513
+ *
514
+ * @param string $vcardData
515
+ * @param array $filters
516
+ * @param string $test anyof or allof (which means OR or AND)
517
+ * @return bool
518
+ */
519
+ function validateFilters($vcardData, array $filters, $test) {
520
+
521
+
522
+ if (!$filters) return true;
523
+ $vcard = VObject\Reader::read($vcardData);
524
+
525
+ foreach ($filters as $filter) {
526
+
527
+ $isDefined = isset($vcard->{$filter['name']});
528
+ if ($filter['is-not-defined']) {
529
+ if ($isDefined) {
530
+ $success = false;
531
+ } else {
532
+ $success = true;
533
+ }
534
+ } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
535
+
536
+ // We only need to check for existence
537
+ $success = $isDefined;
538
+
539
+ } else {
540
+
541
+ $vProperties = $vcard->select($filter['name']);
542
+
543
+ $results = [];
544
+ if ($filter['param-filters']) {
545
+ $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
546
+ }
547
+ if ($filter['text-matches']) {
548
+ $texts = [];
549
+ foreach ($vProperties as $vProperty)
550
+ $texts[] = $vProperty->getValue();
551
+
552
+ $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
553
+ }
554
+
555
+ if (count($results) === 1) {
556
+ $success = $results[0];
557
+ } else {
558
+ if ($filter['test'] === 'anyof') {
559
+ $success = $results[0] || $results[1];
560
+ } else {
561
+ $success = $results[0] && $results[1];
562
+ }
563
+ }
564
+
565
+ } // else
566
+
567
+ // There are two conditions where we can already determine whether
568
+ // or not this filter succeeds.
569
+ if ($test === 'anyof' && $success) {
570
+
571
+ // Destroy circular references to PHP will GC the object.
572
+ $vcard->destroy();
573
+
574
+ return true;
575
+ }
576
+ if ($test === 'allof' && !$success) {
577
+
578
+ // Destroy circular references to PHP will GC the object.
579
+ $vcard->destroy();
580
+
581
+ return false;
582
+ }
583
+
584
+ } // foreach
585
+
586
+
587
+ // Destroy circular references to PHP will GC the object.
588
+ $vcard->destroy();
589
+
590
+ // If we got all the way here, it means we haven't been able to
591
+ // determine early if the test failed or not.
592
+ //
593
+ // This implies for 'anyof' that the test failed, and for 'allof' that
594
+ // we succeeded. Sounds weird, but makes sense.
595
+ return $test === 'allof';
596
+
597
+ }
598
+
599
+ /**
600
+ * Validates if a param-filter can be applied to a specific property.
601
+ *
602
+ * @todo currently we're only validating the first parameter of the passed
603
+ * property. Any subsequence parameters with the same name are
604
+ * ignored.
605
+ * @param array $vProperties
606
+ * @param array $filters
607
+ * @param string $test
608
+ * @return bool
609
+ */
610
+ protected function validateParamFilters(array $vProperties, array $filters, $test) {
611
+
612
+ foreach ($filters as $filter) {
613
+
614
+ $isDefined = false;
615
+ foreach ($vProperties as $vProperty) {
616
+ $isDefined = isset($vProperty[$filter['name']]);
617
+ if ($isDefined) break;
618
+ }
619
+
620
+ if ($filter['is-not-defined']) {
621
+ if ($isDefined) {
622
+ $success = false;
623
+ } else {
624
+ $success = true;
625
+ }
626
+
627
+ // If there's no text-match, we can just check for existence
628
+ } elseif (!$filter['text-match'] || !$isDefined) {
629
+
630
+ $success = $isDefined;
631
+
632
+ } else {
633
+
634
+ $success = false;
635
+ foreach ($vProperties as $vProperty) {
636
+ // If we got all the way here, we'll need to validate the
637
+ // text-match filter.
638
+ $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
639
+ if ($success) break;
640
+ }
641
+ if ($filter['text-match']['negate-condition']) {
642
+ $success = !$success;
643
+ }
644
+
645
+ } // else
646
+
647
+ // There are two conditions where we can already determine whether
648
+ // or not this filter succeeds.
649
+ if ($test === 'anyof' && $success) {
650
+ return true;
651
+ }
652
+ if ($test === 'allof' && !$success) {
653
+ return false;
654
+ }
655
+
656
+ }
657
+
658
+ // If we got all the way here, it means we haven't been able to
659
+ // determine early if the test failed or not.
660
+ //
661
+ // This implies for 'anyof' that the test failed, and for 'allof' that
662
+ // we succeeded. Sounds weird, but makes sense.
663
+ return $test === 'allof';
664
+
665
+ }
666
+
667
+ /**
668
+ * Validates if a text-filter can be applied to a specific property.
669
+ *
670
+ * @param array $texts
671
+ * @param array $filters
672
+ * @param string $test
673
+ * @return bool
674
+ */
675
+ protected function validateTextMatches(array $texts, array $filters, $test) {
676
+
677
+ foreach ($filters as $filter) {
678
+
679
+ $success = false;
680
+ foreach ($texts as $haystack) {
681
+ $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
682
+
683
+ // Breaking on the first match
684
+ if ($success) break;
685
+ }
686
+ if ($filter['negate-condition']) {
687
+ $success = !$success;
688
+ }
689
+
690
+ if ($success && $test === 'anyof')
691
+ return true;
692
+
693
+ if (!$success && $test == 'allof')
694
+ return false;
695
+
696
+
697
+ }
698
+
699
+ // If we got all the way here, it means we haven't been able to
700
+ // determine early if the test failed or not.
701
+ //
702
+ // This implies for 'anyof' that the test failed, and for 'allof' that
703
+ // we succeeded. Sounds weird, but makes sense.
704
+ return $test === 'allof';
705
+
706
+ }
707
+
708
+ /**
709
+ * This event is triggered when fetching properties.
710
+ *
711
+ * This event is scheduled late in the process, after most work for
712
+ * propfind has been done.
713
+ *
714
+ * @param DAV\PropFind $propFind
715
+ * @param DAV\INode $node
716
+ * @return void
717
+ */
718
+ function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
719
+
720
+ // If the request was made using the SOGO connector, we must rewrite
721
+ // the content-type property. By default SabreDAV will send back
722
+ // text/x-vcard; charset=utf-8, but for SOGO we must strip that last
723
+ // part.
724
+ if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) {
725
+ return;
726
+ }
727
+ $contentType = $propFind->get('{DAV:}getcontenttype');
728
+ list($part) = explode(';', $contentType);
729
+ if ($part === 'text/x-vcard' || $part === 'text/vcard') {
730
+ $propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
731
+ }
732
+
733
+ }
734
+
735
+ /**
736
+ * This method is used to generate HTML output for the
737
+ * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
738
+ * can use to create new addressbooks.
739
+ *
740
+ * @param DAV\INode $node
741
+ * @param string $output
742
+ * @return bool
743
+ */
744
+ function htmlActionsPanel(DAV\INode $node, &$output) {
745
+
746
+ if (!$node instanceof AddressBookHome)
747
+ return;
748
+
749
+ $output .= '<tr><td colspan="2"><form method="post" action="">
750
+ <h3>Create new address book</h3>
751
+ <input type="hidden" name="sabreAction" value="mkcol" />
752
+ <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CARDDAV . '}addressbook" />
753
+ <label>Name (uri):</label> <input type="text" name="name" /><br />
754
+ <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
755
+ <input type="submit" value="create" />
756
+ </form>
757
+ </td></tr>';
758
+
759
+ return false;
760
+
761
+ }
762
+
763
+ /**
764
+ * This event is triggered after GET requests.
765
+ *
766
+ * This is used to transform data into jCal, if this was requested.
767
+ *
768
+ * @param RequestInterface $request
769
+ * @param ResponseInterface $response
770
+ * @return void
771
+ */
772
+ function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
773
+
774
+ if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) {
775
+ return;
776
+ }
777
+
778
+ $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
779
+
780
+ $newBody = $this->convertVCard(
781
+ $response->getBody(),
782
+ $target
783
+ );
784
+
785
+ $response->setBody($newBody);
786
+ $response->setHeader('Content-Type', $mimeType . '; charset=utf-8');
787
+ $response->setHeader('Content-Length', strlen($newBody));
788
+
789
+ }
790
+
791
+ /**
792
+ * This helper function performs the content-type negotiation for vcards.
793
+ *
794
+ * It will return one of the following strings:
795
+ * 1. vcard3
796
+ * 2. vcard4
797
+ * 3. jcard
798
+ *
799
+ * It defaults to vcard3.
800
+ *
801
+ * @param string $input
802
+ * @param string $mimeType
803
+ * @return string
804
+ */
805
+ protected function negotiateVCard($input, &$mimeType = null) {
806
+
807
+ $result = HTTP\Util::negotiate(
808
+ $input,
809
+ [
810
+ // Most often used mime-type. Version 3
811
+ 'text/x-vcard',
812
+ // The correct standard mime-type. Defaults to version 3 as
813
+ // well.
814
+ 'text/vcard',
815
+ // vCard 4
816
+ 'text/vcard; version=4.0',
817
+ // vCard 3
818
+ 'text/vcard; version=3.0',
819
+ // jCard
820
+ 'application/vcard+json',
821
+ ]
822
+ );
823
+
824
+ $mimeType = $result;
825
+ switch ($result) {
826
+
827
+ default :
828
+ case 'text/x-vcard' :
829
+ case 'text/vcard' :
830
+ case 'text/vcard; version=3.0' :
831
+ $mimeType = 'text/vcard';
832
+ return 'vcard3';
833
+ case 'text/vcard; version=4.0' :
834
+ return 'vcard4';
835
+ case 'application/vcard+json' :
836
+ return 'jcard';
837
+
838
+ // @codeCoverageIgnoreStart
839
+ }
840
+ // @codeCoverageIgnoreEnd
841
+
842
+ }
843
+
844
+ /**
845
+ * Converts a vcard blob to a different version, or jcard.
846
+ *
847
+ * @param string|resource $data
848
+ * @param string $target
849
+ * @param array $propertiesFilter
850
+ * @return string
851
+ */
852
+ protected function convertVCard($data, $target, array $propertiesFilter = null) {
853
+
854
+ if (is_resource($data)) {
855
+ $data = stream_get_contents($data);
856
+ }
857
+ $input = VObject\Reader::read($data);
858
+ if (!empty($propertiesFilter)) {
859
+ $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
860
+ $keys = array_unique(array_map(function($child) {
861
+ return $child->name;
862
+ }, $input->children()));
863
+ $keys = array_diff($keys, $propertiesFilter);
864
+ foreach ($keys as $key) {
865
+ unset($input->$key);
866
+ }
867
+ $data = $input->serialize();
868
+ }
869
+ $output = null;
870
+ try {
871
+
872
+ switch ($target) {
873
+ default :
874
+ case 'vcard3' :
875
+ if ($input->getDocumentType() === VObject\Document::VCARD30) {
876
+ // Do nothing
877
+ return $data;
878
+ }
879
+ $output = $input->convert(VObject\Document::VCARD30);
880
+ return $output->serialize();
881
+ case 'vcard4' :
882
+ if ($input->getDocumentType() === VObject\Document::VCARD40) {
883
+ // Do nothing
884
+ return $data;
885
+ }
886
+ $output = $input->convert(VObject\Document::VCARD40);
887
+ return $output->serialize();
888
+ case 'jcard' :
889
+ $output = $input->convert(VObject\Document::VCARD40);
890
+ return json_encode($output);
891
+
892
+ }
893
+
894
+ } finally {
895
+
896
+ // Destroy circular references to PHP will GC the object.
897
+ $input->destroy();
898
+ if (!is_null($output)) {
899
+ $output->destroy();
900
+ }
901
+ }
902
+
903
+ }
904
+
905
+ /**
906
+ * Returns a plugin name.
907
+ *
908
+ * Using this name other plugins will be able to access other plugins
909
+ * using DAV\Server::getPlugin
910
+ *
911
+ * @return string
912
+ */
913
+ function getPluginName() {
914
+
915
+ return 'carddav';
916
+
917
+ }
918
+
919
+ /**
920
+ * Returns a bunch of meta-data about the plugin.
921
+ *
922
+ * Providing this information is optional, and is mainly displayed by the
923
+ * Browser plugin.
924
+ *
925
+ * The description key in the returned array may contain html and will not
926
+ * be sanitized.
927
+ *
928
+ * @return array
929
+ */
930
+ function getPluginInfo() {
931
+
932
+ return [
933
+ 'name' => $this->getPluginName(),
934
+ 'description' => 'Adds support for CardDAV (rfc6352)',
935
+ 'link' => 'http://sabre.io/dav/carddav/',
936
+ ];
937
+
938
+ }
939
+
940
+ }
vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\HTTP\RequestInterface;
7
+ use Sabre\HTTP\ResponseInterface;
8
+ use Sabre\VObject;
9
+
10
+ /**
11
+ * VCF Exporter
12
+ *
13
+ * This plugin adds the ability to export entire address books as .vcf files.
14
+ * This is useful for clients that don't support CardDAV yet. They often do
15
+ * support vcf files.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @author Thomas Tanghus (http://tanghus.net/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class VCFExportPlugin extends DAV\ServerPlugin {
23
+
24
+ /**
25
+ * Reference to Server class
26
+ *
27
+ * @var DAV\Server
28
+ */
29
+ protected $server;
30
+
31
+ /**
32
+ * Initializes the plugin and registers event handlers
33
+ *
34
+ * @param DAV\Server $server
35
+ * @return void
36
+ */
37
+ function initialize(DAV\Server $server) {
38
+
39
+ $this->server = $server;
40
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
41
+ $server->on('browserButtonActions', function($path, $node, &$actions) {
42
+ if ($node instanceof IAddressBook) {
43
+ $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="book"></span></a>';
44
+ }
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Intercepts GET requests on addressbook urls ending with ?export.
50
+ *
51
+ * @param RequestInterface $request
52
+ * @param ResponseInterface $response
53
+ * @return bool
54
+ */
55
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
56
+
57
+ $queryParams = $request->getQueryParameters();
58
+ if (!array_key_exists('export', $queryParams)) return;
59
+
60
+ $path = $request->getPath();
61
+
62
+ $node = $this->server->tree->getNodeForPath($path);
63
+
64
+ if (!($node instanceof IAddressBook)) return;
65
+
66
+ $this->server->transactionType = 'get-addressbook-export';
67
+
68
+ // Checking ACL, if available.
69
+ if ($aclPlugin = $this->server->getPlugin('acl')) {
70
+ $aclPlugin->checkPrivileges($path, '{DAV:}read');
71
+ }
72
+
73
+ $nodes = $this->server->getPropertiesForPath($path, [
74
+ '{' . Plugin::NS_CARDDAV . '}address-data',
75
+ ], 1);
76
+
77
+ $format = 'text/directory';
78
+
79
+ $output = null;
80
+ $filenameExtension = null;
81
+
82
+ switch ($format) {
83
+ case 'text/directory':
84
+ $output = $this->generateVCF($nodes);
85
+ $filenameExtension = '.vcf';
86
+ break;
87
+ }
88
+
89
+ $filename = preg_replace(
90
+ '/[^a-zA-Z0-9-_ ]/um',
91
+ '',
92
+ $node->getName()
93
+ );
94
+ $filename .= '-' . date('Y-m-d') . $filenameExtension;
95
+
96
+ $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
97
+ $response->setHeader('Content-Type', $format);
98
+
99
+ $response->setStatus(200);
100
+ $response->setBody($output);
101
+
102
+ // Returning false to break the event chain
103
+ return false;
104
+
105
+ }
106
+
107
+ /**
108
+ * Merges all vcard objects, and builds one big vcf export
109
+ *
110
+ * @param array $nodes
111
+ * @return string
112
+ */
113
+ function generateVCF(array $nodes) {
114
+
115
+ $output = "";
116
+
117
+ foreach ($nodes as $node) {
118
+
119
+ if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) {
120
+ continue;
121
+ }
122
+ $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data'];
123
+
124
+ // Parsing this node so VObject can clean up the output.
125
+ $vcard = VObject\Reader::read($nodeData);
126
+ $output .= $vcard->serialize();
127
+
128
+ // Destroy circular references to PHP will GC the object.
129
+ $vcard->destroy();
130
+
131
+ }
132
+
133
+ return $output;
134
+
135
+ }
136
+
137
+ /**
138
+ * Returns a plugin name.
139
+ *
140
+ * Using this name other plugins will be able to access other plugins
141
+ * using \Sabre\DAV\Server::getPlugin
142
+ *
143
+ * @return string
144
+ */
145
+ function getPluginName() {
146
+
147
+ return 'vcf-export';
148
+
149
+ }
150
+
151
+ /**
152
+ * Returns a bunch of meta-data about the plugin.
153
+ *
154
+ * Providing this information is optional, and is mainly displayed by the
155
+ * Browser plugin.
156
+ *
157
+ * The description key in the returned array may contain html and will not
158
+ * be sanitized.
159
+ *
160
+ * @return array
161
+ */
162
+ function getPluginInfo() {
163
+
164
+ return [
165
+ 'name' => $this->getPluginName(),
166
+ 'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.',
167
+ 'link' => 'http://sabre.io/dav/vcf-export-plugin/',
168
+ ];
169
+
170
+ }
171
+
172
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Filter;
4
+
5
+ use Sabre\Xml\Reader;
6
+ use Sabre\Xml\XmlDeserializable;
7
+
8
+ /**
9
+ * AddressData parser.
10
+ *
11
+ * This class parses the {urn:ietf:params:xml:ns:carddav}address-data XML
12
+ * element, as defined in:
13
+ *
14
+ * http://tools.ietf.org/html/rfc6352#section-10.4
15
+ *
16
+ * This element is used in two distinct places, but this one specifically
17
+ * encodes the address-data element as it appears in the addressbook-query
18
+ * adressbook-multiget REPORT requests.
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ class AddressData implements XmlDeserializable {
25
+
26
+ /**
27
+ * The deserialize method is called during xml parsing.
28
+ *
29
+ * This method is called statically, this is because in theory this method
30
+ * may be used as a type of constructor, or factory method.
31
+ *
32
+ * Often you want to return an instance of the current class, but you are
33
+ * free to return other data as well.
34
+ *
35
+ * You are responsible for advancing the reader to the next element. Not
36
+ * doing anything will result in a never-ending loop.
37
+ *
38
+ * If you just want to skip parsing for this element altogether, you can
39
+ * just call $reader->next();
40
+ *
41
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
42
+ * the next element.
43
+ *
44
+ * @param Reader $reader
45
+ * @return mixed
46
+ */
47
+ static function xmlDeserialize(Reader $reader) {
48
+
49
+ $result = [
50
+ 'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard',
51
+ 'version' => $reader->getAttribute('version') ?: '3.0',
52
+ ];
53
+
54
+ $elems = (array)$reader->parseInnerTree();
55
+ $result['addressDataProperties'] = array_map(function($element) {
56
+ return $element['attributes']['name'];
57
+ }, $elems);
58
+
59
+ return $result;
60
+
61
+ }
62
+
63
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Filter;
4
+
5
+ use Sabre\CardDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\Xml\Element;
8
+ use Sabre\Xml\Reader;
9
+
10
+ /**
11
+ * ParamFilter parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:carddav}param-filter XML
14
+ * element, as defined in:
15
+ *
16
+ * http://tools.ietf.org/html/rfc6352#section-10.5.2
17
+ *
18
+ * The result will be spit out as an array.
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://evertpot.com/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ abstract class ParamFilter implements Element {
25
+
26
+ /**
27
+ * The deserialize method is called during xml parsing.
28
+ *
29
+ * This method is called statically, this is because in theory this method
30
+ * may be used as a type of constructor, or factory method.
31
+ *
32
+ * Often you want to return an instance of the current class, but you are
33
+ * free to return other data as well.
34
+ *
35
+ * You are responsible for advancing the reader to the next element. Not
36
+ * doing anything will result in a never-ending loop.
37
+ *
38
+ * If you just want to skip parsing for this element altogether, you can
39
+ * just call $reader->next();
40
+ *
41
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
42
+ * the next element.
43
+ *
44
+ * @param Reader $reader
45
+ * @return mixed
46
+ */
47
+ static function xmlDeserialize(Reader $reader) {
48
+
49
+ $result = [
50
+ 'name' => null,
51
+ 'is-not-defined' => false,
52
+ 'text-match' => null,
53
+ ];
54
+
55
+ $att = $reader->parseAttributes();
56
+ $result['name'] = $att['name'];
57
+
58
+ $elems = $reader->parseInnerTree();
59
+
60
+ if (is_array($elems)) foreach ($elems as $elem) {
61
+
62
+ switch ($elem['name']) {
63
+
64
+ case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
65
+ $result['is-not-defined'] = true;
66
+ break;
67
+ case '{' . Plugin::NS_CARDDAV . '}text-match' :
68
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
69
+
70
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
71
+ throw new BadRequest('Unknown match-type: ' . $matchType);
72
+ }
73
+ $result['text-match'] = [
74
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
75
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
76
+ 'value' => $elem['value'],
77
+ 'match-type' => $matchType,
78
+ ];
79
+ break;
80
+
81
+ }
82
+
83
+ }
84
+
85
+ return $result;
86
+
87
+ }
88
+
89
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Filter;
4
+
5
+ use Sabre\CardDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * PropFilter parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:carddav}prop-filter XML
14
+ * element, as defined in:
15
+ *
16
+ * http://tools.ietf.org/html/rfc6352#section-10.5.1
17
+ *
18
+ * The result will be spit out as an array.
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://evertpot.com/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ class PropFilter implements XmlDeserializable {
25
+
26
+ /**
27
+ * The deserialize method is called during xml parsing.
28
+ *
29
+ * This method is called statically, this is because in theory this method
30
+ * may be used as a type of constructor, or factory method.
31
+ *
32
+ * Often you want to return an instance of the current class, but you are
33
+ * free to return other data as well.
34
+ *
35
+ * You are responsible for advancing the reader to the next element. Not
36
+ * doing anything will result in a never-ending loop.
37
+ *
38
+ * If you just want to skip parsing for this element altogether, you can
39
+ * just call $reader->next();
40
+ *
41
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
42
+ * the next element.
43
+ *
44
+ * @param Reader $reader
45
+ * @return mixed
46
+ */
47
+ static function xmlDeserialize(Reader $reader) {
48
+
49
+ $result = [
50
+ 'name' => null,
51
+ 'test' => 'anyof',
52
+ 'is-not-defined' => false,
53
+ 'param-filters' => [],
54
+ 'text-matches' => [],
55
+ ];
56
+
57
+ $att = $reader->parseAttributes();
58
+ $result['name'] = $att['name'];
59
+
60
+ if (isset($att['test']) && $att['test'] === 'allof') {
61
+ $result['test'] = 'allof';
62
+ }
63
+
64
+ $elems = $reader->parseInnerTree();
65
+
66
+ if (is_array($elems)) foreach ($elems as $elem) {
67
+
68
+ switch ($elem['name']) {
69
+
70
+ case '{' . Plugin::NS_CARDDAV . '}param-filter' :
71
+ $result['param-filters'][] = $elem['value'];
72
+ break;
73
+ case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
74
+ $result['is-not-defined'] = true;
75
+ break;
76
+ case '{' . Plugin::NS_CARDDAV . '}text-match' :
77
+ $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';
78
+
79
+ if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
80
+ throw new BadRequest('Unknown match-type: ' . $matchType);
81
+ }
82
+ $result['text-matches'][] = [
83
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
84
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
85
+ 'value' => $elem['value'],
86
+ 'match-type' => $matchType,
87
+ ];
88
+ break;
89
+
90
+ }
91
+
92
+ }
93
+
94
+ return $result;
95
+
96
+ }
97
+
98
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Property;
4
+
5
+ use Sabre\CardDAV\Plugin;
6
+ use Sabre\Xml\Writer;
7
+ use Sabre\Xml\XmlSerializable;
8
+
9
+ /**
10
+ * Supported-address-data property
11
+ *
12
+ * This property is a representation of the supported-address-data property
13
+ * in the CardDAV namespace.
14
+ *
15
+ * This property is defined in:
16
+ *
17
+ * http://tools.ietf.org/html/rfc6352#section-6.2.2
18
+ *
19
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ class SupportedAddressData implements XmlSerializable {
24
+
25
+ /**
26
+ * supported versions
27
+ *
28
+ * @var array
29
+ */
30
+ protected $supportedData = [];
31
+
32
+ /**
33
+ * Creates the property
34
+ *
35
+ * @param array|null $supportedData
36
+ */
37
+ function __construct(array $supportedData = null) {
38
+
39
+ if (is_null($supportedData)) {
40
+ $supportedData = [
41
+ ['contentType' => 'text/vcard', 'version' => '3.0'],
42
+ ['contentType' => 'text/vcard', 'version' => '4.0'],
43
+ ['contentType' => 'application/vcard+json', 'version' => '4.0'],
44
+ ];
45
+ }
46
+
47
+ $this->supportedData = $supportedData;
48
+
49
+ }
50
+
51
+ /**
52
+ * The xmlSerialize method is called during xml writing.
53
+ *
54
+ * Use the $writer argument to write its own xml serialization.
55
+ *
56
+ * An important note: do _not_ create a parent element. Any element
57
+ * implementing XmlSerializable should only ever write what's considered
58
+ * its 'inner xml'.
59
+ *
60
+ * The parent of the current element is responsible for writing a
61
+ * containing element.
62
+ *
63
+ * This allows serializers to be re-used for different element names.
64
+ *
65
+ * If you are opening new elements, you must also close them again.
66
+ *
67
+ * @param Writer $writer
68
+ * @return void
69
+ */
70
+ function xmlSerialize(Writer $writer) {
71
+
72
+ foreach ($this->supportedData as $supported) {
73
+ $writer->startElement('{' . Plugin::NS_CARDDAV . '}address-data-type');
74
+ $writer->writeAttributes([
75
+ 'content-type' => $supported['contentType'],
76
+ 'version' => $supported['version']
77
+ ]);
78
+ $writer->endElement(); // address-data-type
79
+ }
80
+
81
+ }
82
+
83
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Property;
4
+
5
+ use Sabre\Xml\Writer;
6
+ use Sabre\Xml\XmlSerializable;
7
+
8
+ /**
9
+ * supported-collation-set property
10
+ *
11
+ * This property is a representation of the supported-collation-set property
12
+ * in the CardDAV namespace.
13
+ *
14
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
15
+ * @author Evert Pot (http://evertpot.com/)
16
+ * @license http://sabre.io/license/ Modified BSD License
17
+ */
18
+ class SupportedCollationSet implements XmlSerializable {
19
+
20
+ /**
21
+ * The xmlSerialize method is called during xml writing.
22
+ *
23
+ * Use the $writer argument to write its own xml serialization.
24
+ *
25
+ * An important note: do _not_ create a parent element. Any element
26
+ * implementing XmlSerializable should only ever write what's considered
27
+ * its 'inner xml'.
28
+ *
29
+ * The parent of the current element is responsible for writing a
30
+ * containing element.
31
+ *
32
+ * This allows serializers to be re-used for different element names.
33
+ *
34
+ * If you are opening new elements, you must also close them again.
35
+ *
36
+ * @param Writer $writer
37
+ * @return void
38
+ */
39
+ function xmlSerialize(Writer $writer) {
40
+
41
+ foreach (['i;ascii-casemap', 'i;octet', 'i;unicode-casemap'] as $coll) {
42
+ $writer->writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll);
43
+ }
44
+
45
+ }
46
+
47
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Request;
4
+
5
+ use Sabre\CardDAV\Plugin;
6
+ use Sabre\Uri;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * AddressBookMultiGetReport request parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-multiget
14
+ * REPORT, as defined in:
15
+ *
16
+ * http://tools.ietf.org/html/rfc6352#section-8.7
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class AddressBookMultiGetReport implements XmlDeserializable {
23
+
24
+ /**
25
+ * An array with requested properties.
26
+ *
27
+ * @var array
28
+ */
29
+ public $properties;
30
+
31
+ /**
32
+ * This is an array with the urls that are being requested.
33
+ *
34
+ * @var array
35
+ */
36
+ public $hrefs;
37
+
38
+ /**
39
+ * The mimetype of the content that should be returend. Usually
40
+ * text/vcard.
41
+ *
42
+ * @var string
43
+ */
44
+ public $contentType = null;
45
+
46
+ /**
47
+ * The version of vcard data that should be returned. Usually 3.0,
48
+ * referring to vCard 3.0.
49
+ *
50
+ * @var string
51
+ */
52
+ public $version = null;
53
+
54
+ /**
55
+ * The deserialize method is called during xml parsing.
56
+ *
57
+ * This method is called statically, this is because in theory this method
58
+ * may be used as a type of constructor, or factory method.
59
+ *
60
+ * Often you want to return an instance of the current class, but you are
61
+ * free to return other data as well.
62
+ *
63
+ * You are responsible for advancing the reader to the next element. Not
64
+ * doing anything will result in a never-ending loop.
65
+ *
66
+ * If you just want to skip parsing for this element altogether, you can
67
+ * just call $reader->next();
68
+ *
69
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
70
+ * the next element.
71
+ *
72
+ * @param Reader $reader
73
+ * @return mixed
74
+ */
75
+ static function xmlDeserialize(Reader $reader) {
76
+
77
+ $elems = $reader->parseInnerTree([
78
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
79
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
80
+ ]);
81
+
82
+ $newProps = [
83
+ 'hrefs' => [],
84
+ 'properties' => []
85
+ ];
86
+
87
+ foreach ($elems as $elem) {
88
+
89
+ switch ($elem['name']) {
90
+
91
+ case '{DAV:}prop' :
92
+ $newProps['properties'] = array_keys($elem['value']);
93
+ if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
94
+ $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
95
+ }
96
+ break;
97
+ case '{DAV:}href' :
98
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
99
+ break;
100
+
101
+ }
102
+
103
+ }
104
+
105
+ $obj = new self();
106
+ foreach ($newProps as $key => $value) {
107
+ $obj->$key = $value;
108
+ }
109
+ return $obj;
110
+
111
+ }
112
+
113
+ }
vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\CardDAV\Xml\Request;
4
+
5
+ use Sabre\CardDAV\Plugin;
6
+ use Sabre\DAV\Exception\BadRequest;
7
+ use Sabre\Xml\Reader;
8
+ use Sabre\Xml\XmlDeserializable;
9
+
10
+ /**
11
+ * AddressBookQueryReport request parser.
12
+ *
13
+ * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-query
14
+ * REPORT, as defined in:
15
+ *
16
+ * http://tools.ietf.org/html/rfc6352#section-8.6
17
+ *
18
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ class AddressBookQueryReport implements XmlDeserializable {
23
+
24
+ /**
25
+ * An array with requested properties.
26
+ *
27
+ * @var array
28
+ */
29
+ public $properties;
30
+
31
+ /**
32
+ * An array with requested vcard properties.
33
+ *
34
+ * @var array
35
+ */
36
+ public $addressDataProperties = [];
37
+
38
+ /**
39
+ * List of property/component filters.
40
+ *
41
+ * This is an array with filters. Every item is a property filter. Every
42
+ * property filter has the following keys:
43
+ * * name - name of the component to filter on
44
+ * * test - anyof or allof
45
+ * * is-not-defined - Test for non-existence
46
+ * * param-filters - A list of parameter filters on the property
47
+ * * text-matches - A list of text values the filter needs to match
48
+ *
49
+ * Each param-filter has the following keys:
50
+ * * name - name of the parameter
51
+ * * is-not-defined - Test for non-existence
52
+ * * text-match - Match the parameter value
53
+ *
54
+ * Each text-match in property filters, and the single text-match in
55
+ * param-filters have the following keys:
56
+ *
57
+ * * value - value to match
58
+ * * match-type - contains, starts-with, ends-with, equals
59
+ * * negate-condition - Do the opposite match
60
+ * * collation - Usually i;unicode-casemap
61
+ *
62
+ * @var array
63
+ */
64
+ public $filters;
65
+
66
+ /**
67
+ * The number of results the client wants
68
+ *
69
+ * null means it wasn't specified, which in most cases means 'all results'.
70
+ *
71
+ * @var int|null
72
+ */
73
+ public $limit;
74
+
75
+ /**
76
+ * Either 'anyof' or 'allof'
77
+ *
78
+ * @var string
79
+ */
80
+ public $test;
81
+
82
+ /**
83
+ * The mimetype of the content that should be returend. Usually
84
+ * text/vcard.
85
+ *
86
+ * @var string
87
+ */
88
+ public $contentType = null;
89
+
90
+ /**
91
+ * The version of vcard data that should be returned. Usually 3.0,
92
+ * referring to vCard 3.0.
93
+ *
94
+ * @var string
95
+ */
96
+ public $version = null;
97
+
98
+
99
+ /**
100
+ * The deserialize method is called during xml parsing.
101
+ *
102
+ * This method is called statically, this is because in theory this method
103
+ * may be used as a type of constructor, or factory method.
104
+ *
105
+ * Often you want to return an instance of the current class, but you are
106
+ * free to return other data as well.
107
+ *
108
+ * You are responsible for advancing the reader to the next element. Not
109
+ * doing anything will result in a never-ending loop.
110
+ *
111
+ * If you just want to skip parsing for this element altogether, you can
112
+ * just call $reader->next();
113
+ *
114
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
115
+ * the next element.
116
+ *
117
+ * @param Reader $reader
118
+ * @return mixed
119
+ */
120
+ static function xmlDeserialize(Reader $reader) {
121
+
122
+ $elems = (array)$reader->parseInnerTree([
123
+ '{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
124
+ '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
125
+ '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
126
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
127
+ ]);
128
+
129
+ $newProps = [
130
+ 'filters' => null,
131
+ 'properties' => [],
132
+ 'test' => 'anyof',
133
+ 'limit' => null,
134
+ ];
135
+
136
+ if (!is_array($elems)) $elems = [];
137
+
138
+ foreach ($elems as $elem) {
139
+
140
+ switch ($elem['name']) {
141
+
142
+ case '{DAV:}prop' :
143
+ $newProps['properties'] = array_keys($elem['value']);
144
+ if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
145
+ $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
146
+ }
147
+ break;
148
+ case '{' . Plugin::NS_CARDDAV . '}filter' :
149
+
150
+ if (!is_null($newProps['filters'])) {
151
+ throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element');
152
+ }
153
+ if (isset($elem['attributes']['test'])) {
154
+ $newProps['test'] = $elem['attributes']['test'];
155
+ if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') {
156
+ throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
157
+ }
158
+ }
159
+
160
+ $newProps['filters'] = [];
161
+ foreach ((array)$elem['value'] as $subElem) {
162
+ if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') {
163
+ $newProps['filters'][] = $subElem['value'];
164
+ }
165
+ }
166
+ break;
167
+ case '{' . Plugin::NS_CARDDAV . '}limit' :
168
+ foreach ($elem['value'] as $child) {
169
+ if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') {
170
+ $newProps['limit'] = (int)$child['value'];
171
+ }
172
+ }
173
+ break;
174
+
175
+ }
176
+
177
+ }
178
+
179
+ if (is_null($newProps['filters'])) {
180
+ /*
181
+ * We are supposed to throw this error, but KDE sometimes does not
182
+ * include the filter element, and we need to treat it as if no
183
+ * filters are supplied
184
+ */
185
+ //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
186
+ $newProps['filters'] = [];
187
+
188
+ }
189
+
190
+ $obj = new self();
191
+ foreach ($newProps as $key => $value) {
192
+ $obj->$key = $value;
193
+ }
194
+
195
+ return $obj;
196
+
197
+ }
198
+
199
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\HTTP;
7
+ use Sabre\HTTP\RequestInterface;
8
+ use Sabre\HTTP\ResponseInterface;
9
+
10
+ /**
11
+ * HTTP Basic authentication backend class
12
+ *
13
+ * This class can be used by authentication objects wishing to use HTTP Basic
14
+ * Most of the digest logic is handled, implementors just need to worry about
15
+ * the validateUserPass method.
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author James David Low (http://jameslow.com/)
19
+ * @author Evert Pot (http://evertpot.com/)
20
+ * @license http://sabre.io/license/ Modified BSD License
21
+ */
22
+ abstract class AbstractBasic implements BackendInterface {
23
+
24
+ /**
25
+ * Authentication Realm.
26
+ *
27
+ * The realm is often displayed by browser clients when showing the
28
+ * authentication dialog.
29
+ *
30
+ * @var string
31
+ */
32
+ protected $realm = 'sabre/dav';
33
+
34
+ /**
35
+ * This is the prefix that will be used to generate principal urls.
36
+ *
37
+ * @var string
38
+ */
39
+ protected $principalPrefix = 'principals/';
40
+
41
+ /**
42
+ * Validates a username and password
43
+ *
44
+ * This method should return true or false depending on if login
45
+ * succeeded.
46
+ *
47
+ * @param string $username
48
+ * @param string $password
49
+ * @return bool
50
+ */
51
+ abstract protected function validateUserPass($username, $password);
52
+
53
+ /**
54
+ * Sets the authentication realm for this backend.
55
+ *
56
+ * @param string $realm
57
+ * @return void
58
+ */
59
+ function setRealm($realm) {
60
+
61
+ $this->realm = $realm;
62
+
63
+ }
64
+
65
+ /**
66
+ * When this method is called, the backend must check if authentication was
67
+ * successful.
68
+ *
69
+ * The returned value must be one of the following
70
+ *
71
+ * [true, "principals/username"]
72
+ * [false, "reason for failure"]
73
+ *
74
+ * If authentication was successful, it's expected that the authentication
75
+ * backend returns a so-called principal url.
76
+ *
77
+ * Examples of a principal url:
78
+ *
79
+ * principals/admin
80
+ * principals/user1
81
+ * principals/users/joe
82
+ * principals/uid/123457
83
+ *
84
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
85
+ * return a string such as:
86
+ *
87
+ * principals/users/[username]
88
+ *
89
+ * @param RequestInterface $request
90
+ * @param ResponseInterface $response
91
+ * @return array
92
+ */
93
+ function check(RequestInterface $request, ResponseInterface $response) {
94
+
95
+ $auth = new HTTP\Auth\Basic(
96
+ $this->realm,
97
+ $request,
98
+ $response
99
+ );
100
+
101
+ $userpass = $auth->getCredentials();
102
+ if (!$userpass) {
103
+ return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
104
+ }
105
+ if (!$this->validateUserPass($userpass[0], $userpass[1])) {
106
+ return [false, "Username or password was incorrect"];
107
+ }
108
+ return [true, $this->principalPrefix . $userpass[0]];
109
+
110
+ }
111
+
112
+ /**
113
+ * This method is called when a user could not be authenticated, and
114
+ * authentication was required for the current request.
115
+ *
116
+ * This gives you the opportunity to set authentication headers. The 401
117
+ * status code will already be set.
118
+ *
119
+ * In this case of Basic Auth, this would for example mean that the
120
+ * following header needs to be set:
121
+ *
122
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
123
+ *
124
+ * Keep in mind that in the case of multiple authentication backends, other
125
+ * WWW-Authenticate headers may already have been set, and you'll want to
126
+ * append your own WWW-Authenticate header instead of overwriting the
127
+ * existing one.
128
+ *
129
+ * @param RequestInterface $request
130
+ * @param ResponseInterface $response
131
+ * @return void
132
+ */
133
+ function challenge(RequestInterface $request, ResponseInterface $response) {
134
+
135
+ $auth = new HTTP\Auth\Basic(
136
+ $this->realm,
137
+ $request,
138
+ $response
139
+ );
140
+ $auth->requireLogin();
141
+
142
+ }
143
+
144
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\HTTP;
7
+ use Sabre\HTTP\RequestInterface;
8
+ use Sabre\HTTP\ResponseInterface;
9
+
10
+ /**
11
+ * HTTP Bearer authentication backend class
12
+ *
13
+ * This class can be used by authentication objects wishing to use HTTP Bearer
14
+ * Most of the digest logic is handled, implementors just need to worry about
15
+ * the validateBearerToken method.
16
+ *
17
+ * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
18
+ * @author François Kooman (https://tuxed.net/)
19
+ * @author James David Low (http://jameslow.com/)
20
+ * @author Evert Pot (http://evertpot.com/)
21
+ * @license http://sabre.io/license/ Modified BSD License
22
+ */
23
+ abstract class AbstractBearer implements BackendInterface {
24
+
25
+ /**
26
+ * Authentication Realm.
27
+ *
28
+ * The realm is often displayed by browser clients when showing the
29
+ * authentication dialog.
30
+ *
31
+ * @var string
32
+ */
33
+ protected $realm = 'sabre/dav';
34
+
35
+ /**
36
+ * Validates a Bearer token
37
+ *
38
+ * This method should return the full principal url, or false if the
39
+ * token was incorrect.
40
+ *
41
+ * @param string $bearerToken
42
+ * @return string|false
43
+ */
44
+ abstract protected function validateBearerToken($bearerToken);
45
+
46
+ /**
47
+ * Sets the authentication realm for this backend.
48
+ *
49
+ * @param string $realm
50
+ * @return void
51
+ */
52
+ function setRealm($realm) {
53
+
54
+ $this->realm = $realm;
55
+
56
+ }
57
+
58
+ /**
59
+ * When this method is called, the backend must check if authentication was
60
+ * successful.
61
+ *
62
+ * The returned value must be one of the following
63
+ *
64
+ * [true, "principals/username"]
65
+ * [false, "reason for failure"]
66
+ *
67
+ * If authentication was successful, it's expected that the authentication
68
+ * backend returns a so-called principal url.
69
+ *
70
+ * Examples of a principal url:
71
+ *
72
+ * principals/admin
73
+ * principals/user1
74
+ * principals/users/joe
75
+ * principals/uid/123457
76
+ *
77
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
78
+ * return a string such as:
79
+ *
80
+ * principals/users/[username]
81
+ *
82
+ * @param RequestInterface $request
83
+ * @param ResponseInterface $response
84
+ * @return array
85
+ */
86
+ function check(RequestInterface $request, ResponseInterface $response) {
87
+
88
+ $auth = new HTTP\Auth\Bearer(
89
+ $this->realm,
90
+ $request,
91
+ $response
92
+ );
93
+
94
+ $bearerToken = $auth->getToken($request);
95
+ if (!$bearerToken) {
96
+ return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"];
97
+ }
98
+ $principalUrl = $this->validateBearerToken($bearerToken);
99
+ if (!$principalUrl) {
100
+ return [false, "Bearer token was incorrect"];
101
+ }
102
+ return [true, $principalUrl];
103
+
104
+ }
105
+
106
+ /**
107
+ * This method is called when a user could not be authenticated, and
108
+ * authentication was required for the current request.
109
+ *
110
+ * This gives you the opportunity to set authentication headers. The 401
111
+ * status code will already be set.
112
+ *
113
+ * In this case of Bearer Auth, this would for example mean that the
114
+ * following header needs to be set:
115
+ *
116
+ * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
117
+ *
118
+ * Keep in mind that in the case of multiple authentication backends, other
119
+ * WWW-Authenticate headers may already have been set, and you'll want to
120
+ * append your own WWW-Authenticate header instead of overwriting the
121
+ * existing one.
122
+ *
123
+ * @param RequestInterface $request
124
+ * @param ResponseInterface $response
125
+ * @return void
126
+ */
127
+ function challenge(RequestInterface $request, ResponseInterface $response) {
128
+
129
+ $auth = new HTTP\Auth\Bearer(
130
+ $this->realm,
131
+ $request,
132
+ $response
133
+ );
134
+ $auth->requireLogin();
135
+
136
+ }
137
+
138
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\HTTP;
7
+ use Sabre\HTTP\RequestInterface;
8
+ use Sabre\HTTP\ResponseInterface;
9
+
10
+ /**
11
+ * HTTP Digest authentication backend class
12
+ *
13
+ * This class can be used by authentication objects wishing to use HTTP Digest
14
+ * Most of the digest logic is handled, implementors just need to worry about
15
+ * the getDigestHash method
16
+ *
17
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
18
+ * @author Evert Pot (http://evertpot.com/)
19
+ * @license http://sabre.io/license/ Modified BSD License
20
+ */
21
+ abstract class AbstractDigest implements BackendInterface {
22
+
23
+ /**
24
+ * Authentication Realm.
25
+ *
26
+ * The realm is often displayed by browser clients when showing the
27
+ * authentication dialog.
28
+ *
29
+ * @var string
30
+ */
31
+ protected $realm = 'SabreDAV';
32
+
33
+ /**
34
+ * This is the prefix that will be used to generate principal urls.
35
+ *
36
+ * @var string
37
+ */
38
+ protected $principalPrefix = 'principals/';
39
+
40
+ /**
41
+ * Sets the authentication realm for this backend.
42
+ *
43
+ * Be aware that for Digest authentication, the realm influences the digest
44
+ * hash. Choose the realm wisely, because if you change it later, all the
45
+ * existing hashes will break and nobody can authenticate.
46
+ *
47
+ * @param string $realm
48
+ * @return void
49
+ */
50
+ function setRealm($realm) {
51
+
52
+ $this->realm = $realm;
53
+
54
+ }
55
+
56
+ /**
57
+ * Returns a users digest hash based on the username and realm.
58
+ *
59
+ * If the user was not known, null must be returned.
60
+ *
61
+ * @param string $realm
62
+ * @param string $username
63
+ * @return string|null
64
+ */
65
+ abstract function getDigestHash($realm, $username);
66
+
67
+ /**
68
+ * When this method is called, the backend must check if authentication was
69
+ * successful.
70
+ *
71
+ * The returned value must be one of the following
72
+ *
73
+ * [true, "principals/username"]
74
+ * [false, "reason for failure"]
75
+ *
76
+ * If authentication was successful, it's expected that the authentication
77
+ * backend returns a so-called principal url.
78
+ *
79
+ * Examples of a principal url:
80
+ *
81
+ * principals/admin
82
+ * principals/user1
83
+ * principals/users/joe
84
+ * principals/uid/123457
85
+ *
86
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
87
+ * return a string such as:
88
+ *
89
+ * principals/users/[username]
90
+ *
91
+ * @param RequestInterface $request
92
+ * @param ResponseInterface $response
93
+ * @return array
94
+ */
95
+ function check(RequestInterface $request, ResponseInterface $response) {
96
+
97
+ $digest = new HTTP\Auth\Digest(
98
+ $this->realm,
99
+ $request,
100
+ $response
101
+ );
102
+ $digest->init();
103
+
104
+ $username = $digest->getUsername();
105
+
106
+ // No username was given
107
+ if (!$username) {
108
+ return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"];
109
+ }
110
+
111
+ $hash = $this->getDigestHash($this->realm, $username);
112
+ // If this was false, the user account didn't exist
113
+ if ($hash === false || is_null($hash)) {
114
+ return [false, "Username or password was incorrect"];
115
+ }
116
+ if (!is_string($hash)) {
117
+ throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
118
+ }
119
+
120
+ // If this was false, the password or part of the hash was incorrect.
121
+ if (!$digest->validateA1($hash)) {
122
+ return [false, "Username or password was incorrect"];
123
+ }
124
+
125
+ return [true, $this->principalPrefix . $username];
126
+
127
+ }
128
+
129
+ /**
130
+ * This method is called when a user could not be authenticated, and
131
+ * authentication was required for the current request.
132
+ *
133
+ * This gives you the opportunity to set authentication headers. The 401
134
+ * status code will already be set.
135
+ *
136
+ * In this case of Basic Auth, this would for example mean that the
137
+ * following header needs to be set:
138
+ *
139
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
140
+ *
141
+ * Keep in mind that in the case of multiple authentication backends, other
142
+ * WWW-Authenticate headers may already have been set, and you'll want to
143
+ * append your own WWW-Authenticate header instead of overwriting the
144
+ * existing one.
145
+ *
146
+ * @param RequestInterface $request
147
+ * @param ResponseInterface $response
148
+ * @return void
149
+ */
150
+ function challenge(RequestInterface $request, ResponseInterface $response) {
151
+
152
+ $auth = new HTTP\Auth\Digest(
153
+ $this->realm,
154
+ $request,
155
+ $response
156
+ );
157
+ $auth->init();
158
+
159
+ $oldStatus = $response->getStatus() ?: 200;
160
+ $auth->requireLogin();
161
+
162
+ // Preventing the digest utility from modifying the http status code,
163
+ // this should be handled by the main plugin.
164
+ $response->setStatus($oldStatus);
165
+
166
+ }
167
+
168
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\HTTP\RequestInterface;
6
+ use Sabre\HTTP\ResponseInterface;
7
+
8
+ /**
9
+ * Apache authenticator
10
+ *
11
+ * This authentication backend assumes that authentication has been
12
+ * configured in apache, rather than within SabreDAV.
13
+ *
14
+ * Make sure apache is properly configured for this to work.
15
+ *
16
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
17
+ * @author Evert Pot (http://evertpot.com/)
18
+ * @license http://sabre.io/license/ Modified BSD License
19
+ */
20
+ class Apache implements BackendInterface {
21
+
22
+ /**
23
+ * This is the prefix that will be used to generate principal urls.
24
+ *
25
+ * @var string
26
+ */
27
+ protected $principalPrefix = 'principals/';
28
+
29
+ /**
30
+ * When this method is called, the backend must check if authentication was
31
+ * successful.
32
+ *
33
+ * The returned value must be one of the following
34
+ *
35
+ * [true, "principals/username"]
36
+ * [false, "reason for failure"]
37
+ *
38
+ * If authentication was successful, it's expected that the authentication
39
+ * backend returns a so-called principal url.
40
+ *
41
+ * Examples of a principal url:
42
+ *
43
+ * principals/admin
44
+ * principals/user1
45
+ * principals/users/joe
46
+ * principals/uid/123457
47
+ *
48
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
49
+ * return a string such as:
50
+ *
51
+ * principals/users/[username]
52
+ *
53
+ * @param RequestInterface $request
54
+ * @param ResponseInterface $response
55
+ * @return array
56
+ */
57
+ function check(RequestInterface $request, ResponseInterface $response) {
58
+
59
+ $remoteUser = $request->getRawServerValue('REMOTE_USER');
60
+ if (is_null($remoteUser)) {
61
+ $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
62
+ }
63
+ if (is_null($remoteUser)) {
64
+ return [false, 'No REMOTE_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
65
+ }
66
+
67
+ return [true, $this->principalPrefix . $remoteUser];
68
+
69
+ }
70
+
71
+ /**
72
+ * This method is called when a user could not be authenticated, and
73
+ * authentication was required for the current request.
74
+ *
75
+ * This gives you the opportunity to set authentication headers. The 401
76
+ * status code will already be set.
77
+ *
78
+ * In this case of Basic Auth, this would for example mean that the
79
+ * following header needs to be set:
80
+ *
81
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
82
+ *
83
+ * Keep in mind that in the case of multiple authentication backends, other
84
+ * WWW-Authenticate headers may already have been set, and you'll want to
85
+ * append your own WWW-Authenticate header instead of overwriting the
86
+ * existing one.
87
+ *
88
+ * @param RequestInterface $request
89
+ * @param ResponseInterface $response
90
+ * @return void
91
+ */
92
+ function challenge(RequestInterface $request, ResponseInterface $response) {
93
+
94
+ }
95
+
96
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\HTTP\RequestInterface;
6
+ use Sabre\HTTP\ResponseInterface;
7
+
8
+ /**
9
+ * This is the base class for any authentication object.
10
+ *
11
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
12
+ * @author Evert Pot (http://evertpot.com/)
13
+ * @license http://sabre.io/license/ Modified BSD License
14
+ */
15
+ interface BackendInterface {
16
+
17
+ /**
18
+ * When this method is called, the backend must check if authentication was
19
+ * successful.
20
+ *
21
+ * The returned value must be one of the following
22
+ *
23
+ * [true, "principals/username"]
24
+ * [false, "reason for failure"]
25
+ *
26
+ * If authentication was successful, it's expected that the authentication
27
+ * backend returns a so-called principal url.
28
+ *
29
+ * Examples of a principal url:
30
+ *
31
+ * principals/admin
32
+ * principals/user1
33
+ * principals/users/joe
34
+ * principals/uid/123457
35
+ *
36
+ * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
37
+ * return a string such as:
38
+ *
39
+ * principals/users/[username]
40
+ *
41
+ * @param RequestInterface $request
42
+ * @param ResponseInterface $response
43
+ * @return array
44
+ */
45
+ function check(RequestInterface $request, ResponseInterface $response);
46
+
47
+ /**
48
+ * This method is called when a user could not be authenticated, and
49
+ * authentication was required for the current request.
50
+ *
51
+ * This gives you the opportunity to set authentication headers. The 401
52
+ * status code will already be set.
53
+ *
54
+ * In this case of Basic Auth, this would for example mean that the
55
+ * following header needs to be set:
56
+ *
57
+ * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
58
+ *
59
+ * Keep in mind that in the case of multiple authentication backends, other
60
+ * WWW-Authenticate headers may already have been set, and you'll want to
61
+ * append your own WWW-Authenticate header instead of overwriting the
62
+ * existing one.
63
+ *
64
+ * @param RequestInterface $request
65
+ * @param ResponseInterface $response
66
+ * @return void
67
+ */
68
+ function challenge(RequestInterface $request, ResponseInterface $response);
69
+
70
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ /**
6
+ * Extremely simply HTTP Basic auth backend.
7
+ *
8
+ * This backend basically works by calling a callback, which receives a
9
+ * username and password.
10
+ * The callback must return true or false depending on if authentication was
11
+ * correct.
12
+ *
13
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
14
+ * @author Evert Pot (http://evertpot.com/)
15
+ * @license http://sabre.io/license/ Modified BSD License
16
+ */
17
+ class BasicCallBack extends AbstractBasic {
18
+
19
+ /**
20
+ * Callback
21
+ *
22
+ * @var callable
23
+ */
24
+ protected $callBack;
25
+
26
+ /**
27
+ * Creates the backend.
28
+ *
29
+ * A callback must be provided to handle checking the username and
30
+ * password.
31
+ *
32
+ * @param callable $callBack
33
+ * @return void
34
+ */
35
+ function __construct(callable $callBack) {
36
+
37
+ $this->callBack = $callBack;
38
+
39
+ }
40
+
41
+ /**
42
+ * Validates a username and password
43
+ *
44
+ * This method should return true or false depending on if login
45
+ * succeeded.
46
+ *
47
+ * @param string $username
48
+ * @param string $password
49
+ * @return bool
50
+ */
51
+ protected function validateUserPass($username, $password) {
52
+
53
+ $cb = $this->callBack;
54
+ return $cb($username, $password);
55
+
56
+ }
57
+
58
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/File.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ use Sabre\DAV;
6
+
7
+ /**
8
+ * This is an authentication backend that uses a file to manage passwords.
9
+ *
10
+ * The backend file must conform to Apache's htdigest format
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ class File extends AbstractDigest {
17
+
18
+ /**
19
+ * List of users
20
+ *
21
+ * @var array
22
+ */
23
+ protected $users = [];
24
+
25
+ /**
26
+ * Creates the backend object.
27
+ *
28
+ * If the filename argument is passed in, it will parse out the specified file first.
29
+ *
30
+ * @param string|null $filename
31
+ */
32
+ function __construct($filename = null) {
33
+
34
+ if (!is_null($filename))
35
+ $this->loadFile($filename);
36
+
37
+ }
38
+
39
+ /**
40
+ * Loads an htdigest-formatted file. This method can be called multiple times if
41
+ * more than 1 file is used.
42
+ *
43
+ * @param string $filename
44
+ * @return void
45
+ */
46
+ function loadFile($filename) {
47
+
48
+ foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) {
49
+
50
+ if (substr_count($line, ":") !== 2)
51
+ throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons');
52
+
53
+ list($username, $realm, $A1) = explode(':', $line);
54
+
55
+ if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1))
56
+ throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash');
57
+
58
+ $this->users[$realm . ':' . $username] = $A1;
59
+
60
+ }
61
+
62
+ }
63
+
64
+ /**
65
+ * Returns a users' information
66
+ *
67
+ * @param string $realm
68
+ * @param string $username
69
+ * @return string
70
+ */
71
+ function getDigestHash($realm, $username) {
72
+
73
+ return isset($this->users[$realm . ':' . $username]) ? $this->users[$realm . ':' . $username] : false;
74
+
75
+ }
76
+
77
+ }
vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth\Backend;
4
+
5
+ /**
6
+ * This is an authentication backend that uses a database to manage passwords.
7
+ *
8
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
9
+ * @author Evert Pot (http://evertpot.com/)
10
+ * @license http://sabre.io/license/ Modified BSD License
11
+ */
12
+ class PDO extends AbstractDigest {
13
+
14
+ /**
15
+ * Reference to PDO connection
16
+ *
17
+ * @var PDO
18
+ */
19
+ protected $pdo;
20
+
21
+ /**
22
+ * PDO table name we'll be using
23
+ *
24
+ * @var string
25
+ */
26
+ public $tableName = 'users';
27
+
28
+
29
+ /**
30
+ * Creates the backend object.
31
+ *
32
+ * If the filename argument is passed in, it will parse out the specified file fist.
33
+ *
34
+ * @param \PDO $pdo
35
+ */
36
+ function __construct(\PDO $pdo) {
37
+
38
+ $this->pdo = $pdo;
39
+
40
+ }
41
+
42
+ /**
43
+ * Returns the digest hash for a user.
44
+ *
45
+ * @param string $realm
46
+ * @param string $username
47
+ * @return string|null
48
+ */
49
+ function getDigestHash($realm, $username) {
50
+
51
+ $stmt = $this->pdo->prepare('SELECT digesta1 FROM ' . $this->tableName . ' WHERE username = ?');
52
+ $stmt->execute([$username]);
53
+ return $stmt->fetchColumn() ?: null;
54
+
55
+ }
56
+
57
+ }
vendor/sabre/dav/lib/DAV/Auth/Plugin.php ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Auth;
4
+
5
+ use Sabre\DAV\Exception\NotAuthenticated;
6
+ use Sabre\DAV\Server;
7
+ use Sabre\DAV\ServerPlugin;
8
+ use Sabre\HTTP\RequestInterface;
9
+ use Sabre\HTTP\ResponseInterface;
10
+
11
+ /**
12
+ * This plugin provides Authentication for a WebDAV server.
13
+ *
14
+ * It works by providing a Auth\Backend class. Several examples of these
15
+ * classes can be found in the Backend directory.
16
+ *
17
+ * It's possible to provide more than one backend to this plugin. If more than
18
+ * one backend was provided, each backend will attempt to authenticate. Only if
19
+ * all backends fail, we throw a 401.
20
+ *
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://evertpot.com/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ class Plugin extends ServerPlugin {
26
+
27
+ /**
28
+ * By default this plugin will require that the user is authenticated,
29
+ * and refuse any access if the user is not authenticated.
30
+ *
31
+ * If this setting is set to false, we let the user through, whether they
32
+ * are authenticated or not.
33
+ *
34
+ * This is useful if you want to allow both authenticated and
35
+ * unauthenticated access to your server.
36
+ *
37
+ * @param bool
38
+ */
39
+ public $autoRequireLogin = true;
40
+
41
+ /**
42
+ * authentication backends
43
+ */
44
+ protected $backends;
45
+
46
+ /**
47
+ * The currently logged in principal. Will be `null` if nobody is currently
48
+ * logged in.
49
+ *
50
+ * @var string|null
51
+ */
52
+ protected $currentPrincipal;
53
+
54
+ /**
55
+ * Creates the authentication plugin
56
+ *
57
+ * @param Backend\BackendInterface $authBackend
58
+ */
59
+ function __construct(Backend\BackendInterface $authBackend = null) {
60
+
61
+ if (!is_null($authBackend)) {
62
+ $this->addBackend($authBackend);
63
+ }
64
+
65
+ }
66
+
67
+ /**
68
+ * Adds an authentication backend to the plugin.
69
+ *
70
+ * @param Backend\BackendInterface $authBackend
71
+ * @return void
72
+ */
73
+ function addBackend(Backend\BackendInterface $authBackend) {
74
+
75
+ $this->backends[] = $authBackend;
76
+
77
+ }
78
+
79
+ /**
80
+ * Initializes the plugin. This function is automatically called by the server
81
+ *
82
+ * @param Server $server
83
+ * @return void
84
+ */
85
+ function initialize(Server $server) {
86
+
87
+ $server->on('beforeMethod', [$this, 'beforeMethod'], 10);
88
+
89
+ }
90
+
91
+ /**
92
+ * Returns a plugin name.
93
+ *
94
+ * Using this name other plugins will be able to access other plugins
95
+ * using DAV\Server::getPlugin
96
+ *
97
+ * @return string
98
+ */
99
+ function getPluginName() {
100
+
101
+ return 'auth';
102
+
103
+ }
104
+
105
+ /**
106
+ * Returns the currently logged-in principal.
107
+ *
108
+ * This will return a string such as:
109
+ *
110
+ * principals/username
111
+ * principals/users/username
112
+ *
113
+ * This method will return null if nobody is logged in.
114
+ *
115
+ * @return string|null
116
+ */
117
+ function getCurrentPrincipal() {
118
+
119
+ return $this->currentPrincipal;
120
+
121
+ }
122
+
123
+ /**
124
+ * This method is called before any HTTP method and forces users to be authenticated
125
+ *
126
+ * @param RequestInterface $request
127
+ * @param ResponseInterface $response
128
+ * @return bool
129
+ */
130
+ function beforeMethod(RequestInterface $request, ResponseInterface $response) {
131
+
132
+ if ($this->currentPrincipal) {
133
+
134
+ // We already have authentication information. This means that the
135
+ // event has already fired earlier, and is now likely fired for a
136
+ // sub-request.
137
+ //
138
+ // We don't want to authenticate users twice, so we simply don't do
139
+ // anything here. See Issue #700 for additional reasoning.
140
+ //
141
+ // This is not a perfect solution, but will be fixed once the
142
+ // "currently authenticated principal" is information that's not
143
+ // not associated with the plugin, but rather per-request.
144
+ //
145
+ // See issue #580 for more information about that.
146
+ return;
147
+
148
+ }
149
+
150
+ $authResult = $this->check($request, $response);
151
+
152
+ if ($authResult[0]) {
153
+ // Auth was successful
154
+ $this->currentPrincipal = $authResult[1];
155
+ $this->loginFailedReasons = null;
156
+ return;
157
+ }
158
+
159
+
160
+
161
+ // If we got here, it means that no authentication backend was
162
+ // successful in authenticating the user.
163
+ $this->currentPrincipal = null;
164
+ $this->loginFailedReasons = $authResult[1];
165
+
166
+ if ($this->autoRequireLogin) {
167
+ $this->challenge($request, $response);
168
+ throw new NotAuthenticated(implode(', ', $authResult[1]));
169
+ }
170
+
171
+ }
172
+
173
+ /**
174
+ * Checks authentication credentials, and logs the user in if possible.
175
+ *
176
+ * This method returns an array. The first item in the array is a boolean
177
+ * indicating if login was successful.
178
+ *
179
+ * If login was successful, the second item in the array will contain the
180
+ * current principal url/path of the logged in user.
181
+ *
182
+ * If login was not successful, the second item in the array will contain a
183
+ * an array with strings. The strings are a list of reasons why login was
184
+ * unsuccessful. For every auth backend there will be one reason, so usually
185
+ * there's just one.
186
+ *
187
+ * @param RequestInterface $request
188
+ * @param ResponseInterface $response
189
+ * @return array
190
+ */
191
+ function check(RequestInterface $request, ResponseInterface $response) {
192
+
193
+ if (!$this->backends) {
194
+ throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
195
+ }
196
+ $reasons = [];
197
+ foreach ($this->backends as $backend) {
198
+
199
+ $result = $backend->check(
200
+ $request,
201
+ $response
202
+ );
203
+
204
+ if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) {
205
+ throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
206
+ }
207
+
208
+ if ($result[0]) {
209
+ $this->currentPrincipal = $result[1];
210
+ // Exit early
211
+ return [true, $result[1]];
212
+ }
213
+ $reasons[] = $result[1];
214
+
215
+ }
216
+
217
+ return [false, $reasons];
218
+
219
+ }
220
+
221
+ /**
222
+ * This method sends authentication challenges to the user.
223
+ *
224
+ * This method will for example cause a HTTP Basic backend to set a
225
+ * WWW-Authorization header, indicating to the client that it should
226
+ * authenticate.
227
+ *
228
+ * @param RequestInterface $request
229
+ * @param ResponseInterface $response
230
+ * @return array
231
+ */
232
+ function challenge(RequestInterface $request, ResponseInterface $response) {
233
+
234
+ foreach ($this->backends as $backend) {
235
+ $backend->challenge($request, $response);
236
+ }
237
+
238
+ }
239
+
240
+ /**
241
+ * List of reasons why login failed for the last login operation.
242
+ *
243
+ * @var string[]|null
244
+ */
245
+ protected $loginFailedReasons;
246
+
247
+ /**
248
+ * Returns a list of reasons why login was unsuccessful.
249
+ *
250
+ * This method will return the login failed reasons for the last login
251
+ * operation. One for each auth backend.
252
+ *
253
+ * This method returns null if the last authentication attempt was
254
+ * successful, or if there was no authentication attempt yet.
255
+ *
256
+ * @return string[]|null
257
+ */
258
+ function getLoginFailedReasons() {
259
+
260
+ return $this->loginFailedReasons;
261
+
262
+ }
263
+
264
+ /**
265
+ * Returns a bunch of meta-data about the plugin.
266
+ *
267
+ * Providing this information is optional, and is mainly displayed by the
268
+ * Browser plugin.
269
+ *
270
+ * The description key in the returned array may contain html and will not
271
+ * be sanitized.
272
+ *
273
+ * @return array
274
+ */
275
+ function getPluginInfo() {
276
+
277
+ return [
278
+ 'name' => $this->getPluginName(),
279
+ 'description' => 'Generic authentication plugin',
280
+ 'link' => 'http://sabre.io/dav/authentication/',
281
+ ];
282
+
283
+ }
284
+
285
+ }
vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\Inode;
7
+ use Sabre\DAV\PropFind;
8
+ use Sabre\HTTP\URLUtil;
9
+
10
+ /**
11
+ * GuessContentType plugin
12
+ *
13
+ * A lot of the built-in File objects just return application/octet-stream
14
+ * as a content-type by default. This is a problem for some clients, because
15
+ * they expect a correct contenttype.
16
+ *
17
+ * There's really no accurate, fast and portable way to determine the contenttype
18
+ * so this extension does what the rest of the world does, and guesses it based
19
+ * on the file extension.
20
+ *
21
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
22
+ * @author Evert Pot (http://evertpot.com/)
23
+ * @license http://sabre.io/license/ Modified BSD License
24
+ */
25
+ class GuessContentType extends DAV\ServerPlugin {
26
+
27
+ /**
28
+ * List of recognized file extensions
29
+ *
30
+ * Feel free to add more
31
+ *
32
+ * @var array
33
+ */
34
+ public $extensionMap = [
35
+
36
+ // images
37
+ 'jpg' => 'image/jpeg',
38
+ 'gif' => 'image/gif',
39
+ 'png' => 'image/png',
40
+
41
+ // groupware
42
+ 'ics' => 'text/calendar',
43
+ 'vcf' => 'text/vcard',
44
+
45
+ // text
46
+ 'txt' => 'text/plain',
47
+
48
+ ];
49
+
50
+ /**
51
+ * Initializes the plugin
52
+ *
53
+ * @param DAV\Server $server
54
+ * @return void
55
+ */
56
+ function initialize(DAV\Server $server) {
57
+
58
+ // Using a relatively low priority (200) to allow other extensions
59
+ // to set the content-type first.
60
+ $server->on('propFind', [$this, 'propFind'], 200);
61
+
62
+ }
63
+
64
+ /**
65
+ * Our PROPFIND handler
66
+ *
67
+ * Here we set a contenttype, if the node didn't already have one.
68
+ *
69
+ * @param PropFind $propFind
70
+ * @param INode $node
71
+ * @return void
72
+ */
73
+ function propFind(PropFind $propFind, INode $node) {
74
+
75
+ $propFind->handle('{DAV:}getcontenttype', function() use ($propFind) {
76
+
77
+ list(, $fileName) = URLUtil::splitPath($propFind->getPath());
78
+ return $this->getContentType($fileName);
79
+
80
+ });
81
+
82
+ }
83
+
84
+ /**
85
+ * Simple method to return the contenttype
86
+ *
87
+ * @param string $fileName
88
+ * @return string
89
+ */
90
+ protected function getContentType($fileName) {
91
+
92
+ // Just grabbing the extension
93
+ $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
94
+ if (isset($this->extensionMap[$extension])) {
95
+ return $this->extensionMap[$extension];
96
+ }
97
+ return 'application/octet-stream';
98
+
99
+ }
100
+
101
+ }
vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ /**
6
+ * WebDAV properties that implement this interface are able to generate their
7
+ * own html output for the browser plugin.
8
+ *
9
+ * This is only useful for display purposes, and might make it a bit easier for
10
+ * people to read and understand the value of some properties.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ interface HtmlOutput {
17
+
18
+ /**
19
+ * Generate html representation for this value.
20
+ *
21
+ * The html output is 100% trusted, and no effort is being made to sanitize
22
+ * it. It's up to the implementor to sanitize user provided values.
23
+ *
24
+ * The output must be in UTF-8.
25
+ *
26
+ * The baseUri parameter is a url to the root of the application, and can
27
+ * be used to construct local links.
28
+ *
29
+ * @param HtmlOutputHelper $html
30
+ * @return string
31
+ */
32
+ function toHtml(HtmlOutputHelper $html);
33
+
34
+ }
vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ use Sabre\Uri;
6
+ use Sabre\Xml\Service as XmlService;
7
+
8
+ /**
9
+ * This class provides a few utility functions for easily generating HTML for
10
+ * the browser plugin.
11
+ *
12
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
13
+ * @author Evert Pot (http://evertpot.com/)
14
+ * @license http://sabre.io/license/ Modified BSD License
15
+ */
16
+ class HtmlOutputHelper {
17
+
18
+ /**
19
+ * Link to the root of the application.
20
+ *
21
+ * @var string
22
+ */
23
+ protected $baseUri;
24
+
25
+ /**
26
+ * List of xml namespaces.
27
+ *
28
+ * @var array
29
+ */
30
+ protected $namespaceMap;
31
+
32
+ /**
33
+ * Creates the object.
34
+ *
35
+ * baseUri must point to the root of the application. This will be used to
36
+ * easily generate links.
37
+ *
38
+ * The namespaceMap contains an array with the list of xml namespaces and
39
+ * their prefixes. WebDAV uses a lot of XML with complex namespaces, so
40
+ * that can be used to make output a lot shorter.
41
+ *
42
+ * @param string $baseUri
43
+ * @param array $namespaceMap
44
+ */
45
+ function __construct($baseUri, array $namespaceMap) {
46
+
47
+ $this->baseUri = $baseUri;
48
+ $this->namespaceMap = $namespaceMap;
49
+
50
+ }
51
+
52
+ /**
53
+ * Generates a 'full' url based on a relative one.
54
+ *
55
+ * For relative urls, the base of the application is taken as the reference
56
+ * url, not the 'current url of the current request'.
57
+ *
58
+ * Absolute urls are left alone.
59
+ *
60
+ * @param string $path
61
+ * @return string
62
+ */
63
+ function fullUrl($path) {
64
+
65
+ return Uri\resolve($this->baseUri, $path);
66
+
67
+ }
68
+
69
+ /**
70
+ * Escape string for HTML output.
71
+ *
72
+ * @param string $input
73
+ * @return string
74
+ */
75
+ function h($input) {
76
+
77
+ return htmlspecialchars($input, ENT_COMPAT, 'UTF-8');
78
+
79
+ }
80
+
81
+ /**
82
+ * Generates a full <a>-tag.
83
+ *
84
+ * Url is automatically expanded. If label is not specified, we re-use the
85
+ * url.
86
+ *
87
+ * @param string $url
88
+ * @param string $label
89
+ * @return string
90
+ */
91
+ function link($url, $label = null) {
92
+
93
+ $url = $this->h($this->fullUrl($url));
94
+ return '<a href="' . $url . '">' . ($label ? $this->h($label) : $url) . '</a>';
95
+
96
+ }
97
+
98
+ /**
99
+ * This method takes an xml element in clark-notation, and turns it into a
100
+ * shortened version with a prefix, if it was a known namespace.
101
+ *
102
+ * @param string $element
103
+ * @return string
104
+ */
105
+ function xmlName($element) {
106
+
107
+ list($ns, $localName) = XmlService::parseClarkNotation($element);
108
+ if (isset($this->namespaceMap[$ns])) {
109
+ $propName = $this->namespaceMap[$ns] . ':' . $localName;
110
+ } else {
111
+ $propName = $element;
112
+ }
113
+ return "<span title=\"" . $this->h($element) . "\">" . $this->h($propName) . "</span>";
114
+
115
+ }
116
+
117
+ }
vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\HTTP\RequestInterface;
7
+ use Sabre\HTTP\ResponseInterface;
8
+
9
+ /**
10
+ * This is a simple plugin that will map any GET request for non-files to
11
+ * PROPFIND allprops-requests.
12
+ *
13
+ * This should allow easy debugging of PROPFIND
14
+ *
15
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
16
+ * @author Evert Pot (http://evertpot.com/)
17
+ * @license http://sabre.io/license/ Modified BSD License
18
+ */
19
+ class MapGetToPropFind extends DAV\ServerPlugin {
20
+
21
+ /**
22
+ * reference to server class
23
+ *
24
+ * @var DAV\Server
25
+ */
26
+ protected $server;
27
+
28
+ /**
29
+ * Initializes the plugin and subscribes to events
30
+ *
31
+ * @param DAV\Server $server
32
+ * @return void
33
+ */
34
+ function initialize(DAV\Server $server) {
35
+
36
+ $this->server = $server;
37
+ $this->server->on('method:GET', [$this, 'httpGet'], 90);
38
+ }
39
+
40
+ /**
41
+ * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request
42
+ *
43
+ * @param RequestInterface $request
44
+ * @param ResponseInterface $response
45
+ * @return bool
46
+ */
47
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
48
+
49
+ $node = $this->server->tree->getNodeForPath($request->getPath());
50
+ if ($node instanceof DAV\IFile) return;
51
+
52
+ $subRequest = clone $request;
53
+ $subRequest->setMethod('PROPFIND');
54
+
55
+ $this->server->invokeMethod($subRequest, $response);
56
+ return false;
57
+
58
+ }
59
+
60
+ }
vendor/sabre/dav/lib/DAV/Browser/Plugin.php ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ use Sabre\DAV;
6
+ use Sabre\DAV\MkCol;
7
+ use Sabre\HTTP\RequestInterface;
8
+ use Sabre\HTTP\ResponseInterface;
9
+ use Sabre\HTTP\URLUtil;
10
+
11
+ /**
12
+ * Browser Plugin
13
+ *
14
+ * This plugin provides a html representation, so that a WebDAV server may be accessed
15
+ * using a browser.
16
+ *
17
+ * The class intercepts GET requests to collection resources and generates a simple
18
+ * html index.
19
+ *
20
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
21
+ * @author Evert Pot (http://evertpot.com/)
22
+ * @license http://sabre.io/license/ Modified BSD License
23
+ */
24
+ class Plugin extends DAV\ServerPlugin {
25
+
26
+ /**
27
+ * reference to server class
28
+ *
29
+ * @var DAV\Server
30
+ */
31
+ protected $server;
32
+
33
+ /**
34
+ * enablePost turns on the 'actions' panel, which allows people to create
35
+ * folders and upload files straight from a browser.
36
+ *
37
+ * @var bool
38
+ */
39
+ protected $enablePost = true;
40
+
41
+ /**
42
+ * A list of properties that are usually not interesting. This can cut down
43
+ * the browser output a bit by removing the properties that most people
44
+ * will likely not want to see.
45
+ *
46
+ * @var array
47
+ */
48
+ public $uninterestingProperties = [
49
+ '{DAV:}supportedlock',
50
+ '{DAV:}acl-restrictions',
51
+ // '{DAV:}supported-privilege-set',
52
+ '{DAV:}supported-method-set',
53
+ ];
54
+
55
+ /**
56
+ * Creates the object.
57
+ *
58
+ * By default it will allow file creation and uploads.
59
+ * Specify the first argument as false to disable this
60
+ *
61
+ * @param bool $enablePost
62
+ */
63
+ function __construct($enablePost = true) {
64
+
65
+ $this->enablePost = $enablePost;
66
+
67
+ }
68
+
69
+ /**
70
+ * Initializes the plugin and subscribes to events
71
+ *
72
+ * @param DAV\Server $server
73
+ * @return void
74
+ */
75
+ function initialize(DAV\Server $server) {
76
+
77
+ $this->server = $server;
78
+ $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
79
+ $this->server->on('method:GET', [$this, 'httpGet'], 200);
80
+ $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
81
+ if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
82
+ }
83
+
84
+ /**
85
+ * This method intercepts GET requests that have ?sabreAction=info
86
+ * appended to the URL
87
+ *
88
+ * @param RequestInterface $request
89
+ * @param ResponseInterface $response
90
+ * @return bool
91
+ */
92
+ function httpGetEarly(RequestInterface $request, ResponseInterface $response) {
93
+
94
+ $params = $request->getQueryParameters();
95
+ if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') {
96
+ return $this->httpGet($request, $response);
97
+ }
98
+
99
+ }
100
+
101
+ /**
102
+ * This method intercepts GET requests to collections and returns the html
103
+ *
104
+ * @param RequestInterface $request
105
+ * @param ResponseInterface $response
106
+ * @return bool
107
+ */
108
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
109
+
110
+ // We're not using straight-up $_GET, because we want everything to be
111
+ // unit testable.
112
+ $getVars = $request->getQueryParameters();
113
+
114
+ // CSP headers
115
+ $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
116
+
117
+ $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
118
+
119
+ switch ($sabreAction) {
120
+
121
+ case 'asset' :
122
+ // Asset handling, such as images
123
+ $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
124
+ return false;
125
+ default :
126
+ case 'info' :
127
+ try {
128
+ $this->server->tree->getNodeForPath($request->getPath());
129
+ } catch (DAV\Exception\NotFound $e) {
130
+ // We're simply stopping when the file isn't found to not interfere
131
+ // with other plugins.
132
+ return;
133
+ }
134
+
135
+ $response->setStatus(200);
136
+ $response->setHeader('Content-Type', 'text/html; charset=utf-8');
137
+
138
+ $response->setBody(
139
+ $this->generateDirectoryIndex($request->getPath())
140
+ );
141
+
142
+ return false;
143
+
144
+ case 'plugins' :
145
+ $response->setStatus(200);
146
+ $response->setHeader('Content-Type', 'text/html; charset=utf-8');
147
+
148
+ $response->setBody(
149
+ $this->generatePluginListing()
150
+ );
151
+
152
+ return false;
153
+
154
+ }
155
+
156
+ }
157
+
158
+ /**
159
+ * Handles POST requests for tree operations.
160
+ *
161
+ * @param RequestInterface $request
162
+ * @param ResponseInterface $response
163
+ * @return bool
164
+ */
165
+ function httpPOST(RequestInterface $request, ResponseInterface $response) {
166
+
167
+ $contentType = $request->getHeader('Content-Type');
168
+ list($contentType) = explode(';', $contentType);
169
+ if ($contentType !== 'application/x-www-form-urlencoded' &&
170
+ $contentType !== 'multipart/form-data') {
171
+ return;
172
+ }
173
+ $postVars = $request->getPostData();
174
+
175
+ if (!isset($postVars['sabreAction']))
176
+ return;
177
+
178
+ $uri = $request->getPath();
179
+
180
+ if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
181
+
182
+ switch ($postVars['sabreAction']) {
183
+
184
+ case 'mkcol' :
185
+ if (isset($postVars['name']) && trim($postVars['name'])) {
186
+ // Using basename() because we won't allow slashes
187
+ list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
188
+
189
+ if (isset($postVars['resourceType'])) {
190
+ $resourceType = explode(',', $postVars['resourceType']);
191
+ } else {
192
+ $resourceType = ['{DAV:}collection'];
193
+ }
194
+
195
+ $properties = [];
196
+ foreach ($postVars as $varName => $varValue) {
197
+ // Any _POST variable in clark notation is treated
198
+ // like a property.
199
+ if ($varName[0] === '{') {
200
+ // PHP will convert any dots to underscores.
201
+ // This leaves us with no way to differentiate
202
+ // the two.
203
+ // Therefore we replace the string *DOT* with a
204
+ // real dot. * is not allowed in uris so we
205
+ // should be good.
206
+ $varName = str_replace('*DOT*', '.', $varName);
207
+ $properties[$varName] = $varValue;
208
+ }
209
+ }
210
+
211
+ $mkCol = new MkCol(
212
+ $resourceType,
213
+ $properties
214
+ );
215
+ $this->server->createCollection($uri . '/' . $folderName, $mkCol);
216
+ }
217
+ break;
218
+
219
+ // @codeCoverageIgnoreStart
220
+ case 'put' :
221
+
222
+ if ($_FILES) $file = current($_FILES);
223
+ else break;
224
+
225
+ list(, $newName) = URLUtil::splitPath(trim($file['name']));
226
+ if (isset($postVars['name']) && trim($postVars['name']))
227
+ $newName = trim($postVars['name']);
228
+
229
+ // Making sure we only have a 'basename' component
230
+ list(, $newName) = URLUtil::splitPath($newName);
231
+
232
+ if (is_uploaded_file($file['tmp_name'])) {
233
+ $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
234
+ }
235
+ break;
236
+ // @codeCoverageIgnoreEnd
237
+
238
+ }
239
+
240
+ }
241
+ $response->setHeader('Location', $request->getUrl());
242
+ $response->setStatus(302);
243
+ return false;
244
+
245
+ }
246
+
247
+ /**
248
+ * Escapes a string for html.
249
+ *
250
+ * @param string $value
251
+ * @return string
252
+ */
253
+ function escapeHTML($value) {
254
+
255
+ return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
256
+
257
+ }
258
+
259
+ /**
260
+ * Generates the html directory index for a given url
261
+ *
262
+ * @param string $path
263
+ * @return string
264
+ */
265
+ function generateDirectoryIndex($path) {
266
+
267
+ $html = $this->generateHeader($path ? $path : '/', $path);
268
+
269
+ $node = $this->server->tree->getNodeForPath($path);
270
+ if ($node instanceof DAV\ICollection) {
271
+
272
+ $html .= "<section><h1>Nodes</h1>\n";
273
+ $html .= "<table class=\"nodeTable\">";
274
+
275
+ $subNodes = $this->server->getPropertiesForChildren($path, [
276
+ '{DAV:}displayname',
277
+ '{DAV:}resourcetype',
278
+ '{DAV:}getcontenttype',
279
+ '{DAV:}getcontentlength',
280
+ '{DAV:}getlastmodified',
281
+ ]);
282
+
283
+ foreach ($subNodes as $subPath => $subProps) {
284
+
285
+ $subNode = $this->server->tree->getNodeForPath($subPath);
286
+ $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath);
287
+ list(, $displayPath) = URLUtil::splitPath($subPath);
288
+
289
+ $subNodes[$subPath]['subNode'] = $subNode;
290
+ $subNodes[$subPath]['fullPath'] = $fullPath;
291
+ $subNodes[$subPath]['displayPath'] = $displayPath;
292
+ }
293
+ uasort($subNodes, [$this, 'compareNodes']);
294
+
295
+ foreach ($subNodes as $subProps) {
296
+ $type = [
297
+ 'string' => 'Unknown',
298
+ 'icon' => 'cog',
299
+ ];
300
+ if (isset($subProps['{DAV:}resourcetype'])) {
301
+ $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
302
+ }
303
+
304
+ $html .= '<tr>';
305
+ $html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>';
306
+ $html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>';
307
+ $html .= '<td>';
308
+ if (isset($subProps['{DAV:}getcontentlength'])) {
309
+ $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes');
310
+ }
311
+ $html .= '</td><td>';
312
+ if (isset($subProps['{DAV:}getlastmodified'])) {
313
+ $lastMod = $subProps['{DAV:}getlastmodified']->getTime();
314
+ $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
315
+ }
316
+ $html .= '</td>';
317
+
318
+ $buttonActions = '';
319
+ if ($subProps['subNode'] instanceof DAV\IFile) {
320
+ $buttonActions = '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
321
+ }
322
+ $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
323
+
324
+ $html .= '<td>' . $buttonActions . '</td>';
325
+ $html .= '</tr>';
326
+ }
327
+
328
+ $html .= '</table>';
329
+
330
+ }
331
+
332
+ $html .= "</section>";
333
+ $html .= "<section><h1>Properties</h1>";
334
+ $html .= "<table class=\"propTable\">";
335
+
336
+ // Allprops request
337
+ $propFind = new PropFindAll($path);
338
+ $properties = $this->server->getPropertiesByNode($propFind, $node);
339
+
340
+ $properties = $propFind->getResultForMultiStatus()[200];
341
+
342
+ foreach ($properties as $propName => $propValue) {
343
+ if (!in_array($propName, $this->uninterestingProperties)) {
344
+ $html .= $this->drawPropertyRow($propName, $propValue);
345
+ }
346
+
347
+ }
348
+
349
+
350
+ $html .= "</table>";
351
+ $html .= "</section>";
352
+
353
+ /* Start of generating actions */
354
+
355
+ $output = '';
356
+ if ($this->enablePost) {
357
+ $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
358
+ }
359
+
360
+ if ($output) {
361
+
362
+ $html .= "<section><h1>Actions</h1>";
363
+ $html .= "<div class=\"actions\">\n";
364
+ $html .= $output;
365
+ $html .= "</div>\n";
366
+ $html .= "</section>\n";
367
+ }
368
+
369
+ $html .= $this->generateFooter();
370
+
371
+ $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
372
+
373
+ return $html;
374
+
375
+ }
376
+
377
+ /**
378
+ * Generates the 'plugins' page.
379
+ *
380
+ * @return string
381
+ */
382
+ function generatePluginListing() {
383
+
384
+ $html = $this->generateHeader('Plugins');
385
+
386
+ $html .= "<section><h1>Plugins</h1>";
387
+ $html .= "<table class=\"propTable\">";
388
+ foreach ($this->server->getPlugins() as $plugin) {
389
+ $info = $plugin->getPluginInfo();
390
+ $html .= '<tr><th>' . $info['name'] . '</th>';
391
+ $html .= '<td>' . $info['description'] . '</td>';
392
+ $html .= '<td>';
393
+ if (isset($info['link']) && $info['link']) {
394
+ $html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>';
395
+ }
396
+ $html .= '</td></tr>';
397
+ }
398
+ $html .= "</table>";
399
+ $html .= "</section>";
400
+
401
+ /* Start of generating actions */
402
+
403
+ $html .= $this->generateFooter();
404
+
405
+ return $html;
406
+
407
+ }
408
+
409
+ /**
410
+ * Generates the first block of HTML, including the <head> tag and page
411
+ * header.
412
+ *
413
+ * Returns footer.
414
+ *
415
+ * @param string $title
416
+ * @param string $path
417
+ * @return string
418
+ */
419
+ function generateHeader($title, $path = null) {
420
+
421
+ $version = '';
422
+ if (DAV\Server::$exposeVersion) {
423
+ $version = DAV\Version::VERSION;
424
+ }
425
+
426
+ $vars = [
427
+ 'title' => $this->escapeHTML($title),
428
+ 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
429
+ 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
430
+ 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
431
+ 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
432
+ 'baseUrl' => $this->server->getBaseUri(),
433
+ ];
434
+
435
+ $html = <<<HTML
436
+ <!DOCTYPE html>
437
+ <html>
438
+ <head>
439
+ <title>$vars[title] - sabre/dav $version</title>
440
+ <link rel="shortcut icon" href="$vars[favicon]" type="image/vnd.microsoft.icon" />
441
+ <link rel="stylesheet" href="$vars[style]" type="text/css" />
442
+ <link rel="stylesheet" href="$vars[iconstyle]" type="text/css" />
443
+
444
+ </head>
445
+ <body>
446
+ <header>
447
+ <div class="logo">
448
+ <a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a>
449
+ </div>
450
+ </header>
451
+
452
+ <nav>
453
+ HTML;
454
+
455
+ // If the path is empty, there's no parent.
456
+ if ($path) {
457
+ list($parentUri) = URLUtil::splitPath($path);
458
+ $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
459
+ $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
460
+ } else {
461
+ $html .= '<span class="btn disabled">⇤ Go to parent</span>';
462
+ }
463
+
464
+ $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
465
+
466
+ $html .= "</nav>";
467
+
468
+ return $html;
469
+
470
+ }
471
+
472
+ /**
473
+ * Generates the page footer.
474
+ *
475
+ * Returns html.
476
+ *
477
+ * @return string
478
+ */
479
+ function generateFooter() {
480
+
481
+ $version = '';
482
+ if (DAV\Server::$exposeVersion) {
483
+ $version = DAV\Version::VERSION;
484
+ }
485
+ return <<<HTML
486
+ <footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer>
487
+ </body>
488
+ </html>
489
+ HTML;
490
+
491
+ }
492
+
493
+ /**
494
+ * This method is used to generate the 'actions panel' output for
495
+ * collections.
496
+ *
497
+ * This specifically generates the interfaces for creating new files, and
498
+ * creating new directories.
499
+ *
500
+ * @param DAV\INode $node
501
+ * @param mixed $output
502
+ * @param string $path
503
+ * @return void
504
+ */
505
+ function htmlActionsPanel(DAV\INode $node, &$output, $path) {
506
+
507
+ if (!$node instanceof DAV\ICollection)
508
+ return;
509
+
510
+ // We also know fairly certain that if an object is a non-extended
511
+ // SimpleCollection, we won't need to show the panel either.
512
+ if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
513
+ return;
514
+
515
+ $output .= <<<HTML
516
+ <form method="post" action="">
517
+ <h3>Create new folder</h3>
518
+ <input type="hidden" name="sabreAction" value="mkcol" />
519
+ <label>Name:</label> <input type="text" name="name" /><br />
520
+ <input type="submit" value="create" />
521
+ </form>
522
+ <form method="post" action="" enctype="multipart/form-data">
523
+ <h3>Upload file</h3>
524
+ <input type="hidden" name="sabreAction" value="put" />
525
+ <label>Name (optional):</label> <input type="text" name="name" /><br />
526
+ <label>File:</label> <input type="file" name="file" /><br />
527
+ <input type="submit" value="upload" />
528
+ </form>
529
+ HTML;
530
+
531
+ }
532
+
533
+ /**
534
+ * This method takes a path/name of an asset and turns it into url
535
+ * suiteable for http access.
536
+ *
537
+ * @param string $assetName
538
+ * @return string
539
+ */
540
+ protected function getAssetUrl($assetName) {
541
+
542
+ return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
543
+
544
+ }
545
+
546
+ /**
547
+ * This method returns a local pathname to an asset.
548
+ *
549
+ * @param string $assetName
550
+ * @throws DAV\Exception\NotFound
551
+ * @return string
552
+ */
553
+ protected function getLocalAssetPath($assetName) {
554
+
555
+ $assetDir = __DIR__ . '/assets/';
556
+ $path = $assetDir . $assetName;
557
+
558
+ // Making sure people aren't trying to escape from the base path.
559
+ $path = str_replace('\\', '/', $path);
560
+ if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
561
+ throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
562
+ }
563
+ if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) {
564
+ return $path;
565
+ }
566
+ throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
567
+ }
568
+
569
+ /**
570
+ * This method reads an asset from disk and generates a full http response.
571
+ *
572
+ * @param string $assetName
573
+ * @return void
574
+ */
575
+ protected function serveAsset($assetName) {
576
+
577
+ $assetPath = $this->getLocalAssetPath($assetName);
578
+
579
+ // Rudimentary mime type detection
580
+ $mime = 'application/octet-stream';
581
+ $map = [
582
+ 'ico' => 'image/vnd.microsoft.icon',
583
+ 'png' => 'image/png',
584
+ 'css' => 'text/css',
585
+ ];
586
+
587
+ $ext = substr($assetName, strrpos($assetName, '.') + 1);
588
+ if (isset($map[$ext])) {
589
+ $mime = $map[$ext];
590
+ }
591
+
592
+ $this->server->httpResponse->setHeader('Content-Type', $mime);
593
+ $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
594
+ $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
595
+ $this->server->httpResponse->setStatus(200);
596
+ $this->server->httpResponse->setBody(fopen($assetPath, 'r'));
597
+
598
+ }
599
+
600
+ /**
601
+ * Sort helper function: compares two directory entries based on type and
602
+ * display name. Collections sort above other types.
603
+ *
604
+ * @param array $a
605
+ * @param array $b
606
+ * @return int
607
+ */
608
+ protected function compareNodes($a, $b) {
609
+
610
+ $typeA = (isset($a['{DAV:}resourcetype']))
611
+ ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
612
+ : false;
613
+
614
+ $typeB = (isset($b['{DAV:}resourcetype']))
615
+ ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
616
+ : false;
617
+
618
+ // If same type, sort alphabetically by filename:
619
+ if ($typeA === $typeB) {
620
+ return strnatcasecmp($a['displayPath'], $b['displayPath']);
621
+ }
622
+ return (($typeA < $typeB) ? 1 : -1);
623
+
624
+ }
625
+
626
+ /**
627
+ * Maps a resource type to a human-readable string and icon.
628
+ *
629
+ * @param array $resourceTypes
630
+ * @param DAV\INode $node
631
+ * @return array
632
+ */
633
+ private function mapResourceType(array $resourceTypes, $node) {
634
+
635
+ if (!$resourceTypes) {
636
+ if ($node instanceof DAV\IFile) {
637
+ return [
638
+ 'string' => 'File',
639
+ 'icon' => 'file',
640
+ ];
641
+ } else {
642
+ return [
643
+ 'string' => 'Unknown',
644
+ 'icon' => 'cog',
645
+ ];
646
+ }
647
+ }
648
+
649
+ $types = [
650
+ '{http://calendarserver.org/ns/}calendar-proxy-write' => [
651
+ 'string' => 'Proxy-Write',
652
+ 'icon' => 'people',
653
+ ],
654
+ '{http://calendarserver.org/ns/}calendar-proxy-read' => [
655
+ 'string' => 'Proxy-Read',
656
+ 'icon' => 'people',
657
+ ],
658
+ '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
659
+ 'string' => 'Outbox',
660
+ 'icon' => 'inbox',
661
+ ],
662
+ '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
663
+ 'string' => 'Inbox',
664
+ 'icon' => 'inbox',
665
+ ],
666
+ '{urn:ietf:params:xml:ns:caldav}calendar' => [
667
+ 'string' => 'Calendar',
668
+ 'icon' => 'calendar',
669
+ ],
670
+ '{http://calendarserver.org/ns/}shared-owner' => [
671
+ 'string' => 'Shared',
672
+ 'icon' => 'calendar',
673
+ ],
674
+ '{http://calendarserver.org/ns/}subscribed' => [
675
+ 'string' => 'Subscription',
676
+ 'icon' => 'calendar',
677
+ ],
678
+ '{urn:ietf:params:xml:ns:carddav}directory' => [
679
+ 'string' => 'Directory',
680
+ 'icon' => 'globe',
681
+ ],
682
+ '{urn:ietf:params:xml:ns:carddav}addressbook' => [
683
+ 'string' => 'Address book',
684
+ 'icon' => 'book',
685
+ ],
686
+ '{DAV:}principal' => [
687
+ 'string' => 'Principal',
688
+ 'icon' => 'person',
689
+ ],
690
+ '{DAV:}collection' => [
691
+ 'string' => 'Collection',
692
+ 'icon' => 'folder',
693
+ ],
694
+ ];
695
+
696
+ $info = [
697
+ 'string' => [],
698
+ 'icon' => 'cog',
699
+ ];
700
+ foreach ($resourceTypes as $k => $resourceType) {
701
+ if (isset($types[$resourceType])) {
702
+ $info['string'][] = $types[$resourceType]['string'];
703
+ } else {
704
+ $info['string'][] = $resourceType;
705
+ }
706
+ }
707
+ foreach ($types as $key => $resourceInfo) {
708
+ if (in_array($key, $resourceTypes)) {
709
+ $info['icon'] = $resourceInfo['icon'];
710
+ break;
711
+ }
712
+ }
713
+ $info['string'] = implode(', ', $info['string']);
714
+
715
+ return $info;
716
+
717
+ }
718
+
719
+ /**
720
+ * Draws a table row for a property
721
+ *
722
+ * @param string $name
723
+ * @param mixed $value
724
+ * @return string
725
+ */
726
+ private function drawPropertyRow($name, $value) {
727
+
728
+ $html = new HtmlOutputHelper(
729
+ $this->server->getBaseUri(),
730
+ $this->server->xml->namespaceMap
731
+ );
732
+
733
+ return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>";
734
+
735
+ }
736
+
737
+ /**
738
+ * Draws a table row for a property
739
+ *
740
+ * @param HtmlOutputHelper $html
741
+ * @param mixed $value
742
+ * @return string
743
+ */
744
+ private function drawPropertyValue($html, $value) {
745
+
746
+ if (is_scalar($value)) {
747
+ return $html->h($value);
748
+ } elseif ($value instanceof HtmlOutput) {
749
+ return $value->toHtml($html);
750
+ } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
751
+
752
+ // There's no default html output for this property, we're going
753
+ // to output the actual xml serialization instead.
754
+ $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
755
+ // removing first and last line, as they contain our root
756
+ // element.
757
+ $xml = explode("\n", $xml);
758
+ $xml = array_slice($xml, 2, -2);
759
+ return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>";
760
+
761
+ } else {
762
+ return "<em>unknown</em>";
763
+ }
764
+
765
+ }
766
+
767
+ /**
768
+ * Returns a plugin name.
769
+ *
770
+ * Using this name other plugins will be able to access other plugins;
771
+ * using \Sabre\DAV\Server::getPlugin
772
+ *
773
+ * @return string
774
+ */
775
+ function getPluginName() {
776
+
777
+ return 'browser';
778
+
779
+ }
780
+
781
+ /**
782
+ * Returns a bunch of meta-data about the plugin.
783
+ *
784
+ * Providing this information is optional, and is mainly displayed by the
785
+ * Browser plugin.
786
+ *
787
+ * The description key in the returned array may contain html and will not
788
+ * be sanitized.
789
+ *
790
+ * @return array
791
+ */
792
+ function getPluginInfo() {
793
+
794
+ return [
795
+ 'name' => $this->getPluginName(),
796
+ 'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
797
+ 'link' => 'http://sabre.io/dav/browser-plugin/',
798
+ ];
799
+
800
+ }
801
+
802
+ }
vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabre\DAV\Browser;
4
+
5
+ use Sabre\DAV\PropFind;
6
+
7
+ /**
8
+ * This class is used by the browser plugin to trick the system in returning
9
+ * every defined property.
10
+ *
11
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
12
+ * @author Evert Pot (http://evertpot.com/)
13
+ * @license http://sabre.io/license/ Modified BSD License
14
+ */
15
+ class PropFindAll extends PropFind {
16
+
17
+ /**
18
+ * Creates the PROPFIND object
19
+ *
20
+ * @param string $path
21
+ */
22
+ function __construct($path) {
23
+
24
+ parent::__construct($path, []);
25
+
26
+ }
27
+
28
+ /**
29
+ * Handles a specific property.
30
+ *
31
+ * This method checks whether the specified property was requested in this
32
+ * PROPFIND request, and if so, it will call the callback and use the
33
+ * return value for it's value.
34
+ *
35
+ * Example:
36
+ *
37
+ * $propFind->handle('{DAV:}displayname', function() {
38
+ * return 'hello';
39
+ * });
40
+ *
41
+ * Note that handle will only work the first time. If null is returned, the
42
+ * value is ignored.
43
+ *
44
+ * It's also possible to not pass a callback, but immediately pass a value
45
+ *
46
+ * @param string $propertyName
47
+ * @param mixed $valueOrCallBack
48
+ * @return void
49
+ */
50
+ function handle($propertyName, $valueOrCallBack) {
51
+
52
+ if (is_callable($valueOrCallBack)) {
53
+ $value = $valueOrCallBack();
54
+ } else {
55
+ $value = $valueOrCallBack;
56
+ }
57
+ if (!is_null($value)) {
58
+ $this->result[$propertyName] = [200, $value];
59
+ }
60
+
61
+ }
62
+
63
+ /**
64
+ * Sets the value of the property
65
+ *
66
+ * If status is not supplied, the status will default to 200 for non-null
67
+ * properties, and 404 for null properties.
68
+ *
69
+ * @param string $propertyName
70
+ * @param mixed $value
71
+ * @param int $status
72
+ * @return void
73
+ */
74
+ function set($propertyName, $value, $status = null) {
75
+
76
+ if (is_null($status)) {
77
+ $status = is_null($value) ? 404 : 200;
78
+ }
79
+ $this->result[$propertyName] = [$status, $value];
80
+
81
+ }
82
+
83
+ /**
84
+ * Returns the current value for a property.
85
+ *
86
+ * @param string $propertyName
87
+ * @return mixed
88
+ */
89
+ function get($propertyName) {
90
+
91
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
92
+
93
+ }
94
+
95
+ /**
96
+ * Returns the current status code for a property name.
97
+ *
98
+ * If the property does not appear in the list of requested properties,
99
+ * null will be returned.
100
+ *
101
+ * @param string $propertyName
102
+ * @return int|null
103
+ */
104
+ function getStatus($propertyName) {
105
+
106
+ return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404;
107
+
108
+ }
109
+
110
+ /**
111
+ * Returns all propertynames that have a 404 status, and thus don't have a
112
+ * value yet.
113
+ *
114
+ * @return array
115
+ */
116
+ function get404Properties() {
117
+
118
+ $result = [];
119
+ foreach ($this->result as $propertyName => $stuff) {
120
+ if ($stuff[0] === 404) {
121
+ $result[] = $propertyName;
122
+ }
123
+ }
124
+ // If there's nothing in this list, we're adding one fictional item.
125
+ if (!$result) {
126
+ $result[] = '{http://sabredav.org/ns}idk';
127
+ }
128
+ return $result;
129
+
130
+ }
131
+
132
+ }
vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico ADDED
Binary file
vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Waybury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family: 'Icons';
3
+ src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot');
4
+ 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');
5
+ font-weight: normal;
6
+ font-style: normal;
7
+ }
8
+
9
+ .oi[data-glyph].oi-text-replace {
10
+ font-size: 0;
11
+ line-height: 0;
12
+ }
13
+
14
+ .oi[data-glyph].oi-text-replace:before {
15
+ width: 1em;
16
+ text-align: center;
17
+ }
18
+
19
+ .oi[data-glyph]:before {
20
+ font-family: 'Icons';
21
+ display: inline-block;
22
+ speak: none;
23
+ line-height: 1;
24
+ vertical-align: baseline;
25
+ font-weight: normal;
26
+ font-style: normal;
27
+ -webkit-font-smoothing: antialiased;
28
+ -moz-osx-font-smoothing: grayscale;
29
+ }
30
+
31
+ .oi[data-glyph]:empty:before {
32
+ width: 1em;
33
+ text-align: center;
34
+ box-sizing: content-box;
35
+ }
36
+
37
+ .oi[data-glyph].oi-align-left:before {
38
+ text-align: left;
39
+ }
40
+
41
+ .oi[data-glyph].oi-align-right:before {
42
+ text-align: right;
43
+ }
44
+
45
+ .oi[data-glyph].oi-align-center:before {
46
+ text-align: center;
47
+ }
48
+
49
+ .oi[data-glyph].oi-flip-horizontal:before {
50
+ -webkit-transform: scale(-1, 1);
51
+ -ms-transform: scale(-1, 1);
52
+ transform: scale(-1, 1);
53
+ }
54
+ .oi[data-glyph].oi-flip-vertical:before {
55
+ -webkit-transform: scale(1, -1);
56
+ -ms-transform: scale(-1, 1);
57
+ transform: scale(1, -1);
58
+ }
59
+ .oi[data-glyph].oi-flip-horizontal-vertical:before {
60
+ -webkit-transform: scale(-1, -1);
61
+ -ms-transform: scale(-1, 1);
62
+ transform: scale(-1, -1);
63
+ }
64
+
65
+
66
+ .oi[data-glyph=account-login]:before { content:'\e000'; }
67
+
68
+ .oi[data-glyph=account-logout]:before { content:'\e001'; }
69
+
70
+ .oi[data-glyph=action-redo]:before { content:'\e002'; }
71
+
72
+ .oi[data-glyph=action-undo]:before { content:'\e003'; }
73
+
74
+ .oi[data-glyph=align-center]:before { content:'\e004'; }
75
+
76
+ .oi[data-glyph=align-left]:before { content:'\e005'; }
77
+
78
+ .oi[data-glyph=align-right]:before { content:'\e006'; }
79
+
80
+ .oi[data-glyph=aperture]:before { content:'\e007'; }
81
+
82
+ .oi[data-glyph=arrow-bottom]:before { content:'\e008'; }
83
+
84
+ .oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; }
85
+
86
+ .oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; }
87
+
88
+ .oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; }
89
+
90
+ .oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; }
91
+
92
+ .oi[data-glyph=arrow-left]:before { content:'\e00d'; }
93
+
94
+ .oi[data-glyph=arrow-right]:before { content:'\e00e'; }
95
+
96
+ .oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; }
97
+
98
+ .oi[data-glyph=arrow-thick-left]:before { content:'\e010'; }
99
+
100
+ .oi[data-glyph=arrow-thick-right]:before { content:'\e011'; }
101
+
102
+ .oi[data-glyph=arrow-thick-top]:before { content:'\e012'; }
103
+
104
+ .oi[data-glyph=arrow-top]:before { content:'\e013'; }
105
+
106
+ .oi[data-glyph=audio-spectrum]:before { content:'\e014'; }
107
+
108
+ .oi[data-glyph=audio]:before { content:'\e015'; }
109
+
110
+ .oi[data-glyph=badge]:before { content:'\e016'; }
111
+
112
+ .oi[data-glyph=ban]:before { content:'\e017'; }
113
+
114
+ .oi[data-glyph=bar-chart]:before { content:'\e018'; }
115
+
116
+ .oi[data-glyph=basket]:before { content:'\e019'; }
117
+
118
+ .oi[data-glyph=battery-empty]:before { content:'\e01a'; }
119
+
120
+ .oi[data-glyph=battery-full]:before { content:'\e01b'; }
121
+
122
+ .oi[data-glyph=beaker]:before { content:'\e01c'; }
123
+
124
+ .oi[data-glyph=bell]:before { content:'\e01d'; }
125
+
126
+ .oi[data-glyph=bluetooth]:before { content:'\e01e'; }
127
+
128
+ .oi[data-glyph=bold]:before { content:'\e01f'; }
129
+
130
+ .oi[data-glyph=bolt]:before { content:'\e020'; }
131
+
132
+ .oi[data-glyph=book]:before { content:'\e021'; }
133
+
134
+ .oi[data-glyph=bookmark]:before { content:'\e022'; }
135
+
136
+ .oi[data-glyph=box]:before { content:'\e023'; }
137
+
138
+ .oi[data-glyph=briefcase]:before { content:'\e024'; }
139
+
140
+ .oi[data-glyph=british-pound]:before { content:'\e025'; }
141
+
142
+ .oi[data-glyph=browser]:before { content:'\e026'; }
143
+
144
+ .oi[data-glyph=brush]:before { content:'\e027'; }
145
+
146
+ .oi[data-glyph=bug]:before { content:'\e028'; }
147
+
148
+ .oi[data-glyph=bullhorn]:before { content:'\e029'; }
149
+
150
+ .oi[data-glyph=calculator]:before { content:'\e02a'; }
151
+
152
+ .oi[data-glyph=calendar]:before { content:'\e02b'; }
153
+
154
+ .oi[data-glyph=camera-slr]:before { content:'\e02c'; }
155
+
156
+ .oi[data-glyph=caret-bottom]:before { content:'\e02d'; }
157
+
158
+ .oi[data-glyph=caret-left]:before { content:'\e02e'; }
159
+
160
+ .oi[data-glyph=caret-right]:before { content:'\e02f'; }
161
+
162
+ .oi[data-glyph=caret-top]:before { content:'\e030'; }
163
+
164
+