PluginManager.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud GmbH.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OCA\DAV\AppInfo;
  9. use OC\ServerContainer;
  10. use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
  11. use OCA\DAV\CalDAV\Integration\ICalendarProvider;
  12. use OCA\DAV\CardDAV\Integration\IAddressBookProvider;
  13. use OCP\App\IAppManager;
  14. use OCP\AppFramework\QueryException;
  15. use Sabre\DAV\Collection;
  16. use Sabre\DAV\ServerPlugin;
  17. use function array_map;
  18. use function class_exists;
  19. use function is_array;
  20. /**
  21. * Manager for DAV plugins from apps, used to register them
  22. * to the Sabre server.
  23. */
  24. class PluginManager {
  25. /**
  26. * App plugins
  27. *
  28. * @var ServerPlugin[]
  29. */
  30. private $plugins = [];
  31. /**
  32. * App collections
  33. *
  34. * @var Collection[]
  35. */
  36. private $collections = [];
  37. /**
  38. * Address book plugins
  39. *
  40. * @var IAddressBookProvider[]
  41. */
  42. private $addressBookPlugins = [];
  43. /**
  44. * Calendar plugins
  45. *
  46. * @var ICalendarProvider[]
  47. */
  48. private $calendarPlugins = [];
  49. /** @var bool */
  50. private $populated = false;
  51. /**
  52. * Contstruct a PluginManager
  53. *
  54. * @param ServerContainer $container server container for resolving plugin classes
  55. * @param IAppManager $appManager app manager to loading apps and their info
  56. */
  57. public function __construct(
  58. private ServerContainer $container,
  59. private IAppManager $appManager,
  60. ) {
  61. }
  62. /**
  63. * Returns an array of app-registered plugins
  64. *
  65. * @return ServerPlugin[]
  66. */
  67. public function getAppPlugins() {
  68. $this->populate();
  69. return $this->plugins;
  70. }
  71. /**
  72. * Returns an array of app-registered collections
  73. *
  74. * @return array
  75. */
  76. public function getAppCollections() {
  77. $this->populate();
  78. return $this->collections;
  79. }
  80. /**
  81. * @return IAddressBookProvider[]
  82. */
  83. public function getAddressBookPlugins(): array {
  84. $this->populate();
  85. return $this->addressBookPlugins;
  86. }
  87. /**
  88. * Returns an array of app-registered calendar plugins
  89. *
  90. * @return ICalendarProvider[]
  91. */
  92. public function getCalendarPlugins():array {
  93. $this->populate();
  94. return $this->calendarPlugins;
  95. }
  96. /**
  97. * Retrieve plugin and collection list and populate attributes
  98. */
  99. private function populate(): void {
  100. if ($this->populated) {
  101. return;
  102. }
  103. $this->populated = true;
  104. $this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class);
  105. foreach ($this->appManager->getInstalledApps() as $app) {
  106. // load plugins and collections from info.xml
  107. $info = $this->appManager->getAppInfo($app);
  108. if (!isset($info['types']) || !in_array('dav', $info['types'], true)) {
  109. continue;
  110. }
  111. $plugins = $this->loadSabrePluginsFromInfoXml($this->extractPluginList($info));
  112. foreach ($plugins as $plugin) {
  113. $this->plugins[] = $plugin;
  114. }
  115. $collections = $this->loadSabreCollectionsFromInfoXml($this->extractCollectionList($info));
  116. foreach ($collections as $collection) {
  117. $this->collections[] = $collection;
  118. }
  119. $addresbookPlugins = $this->loadSabreAddressBookPluginsFromInfoXml($this->extractAddressBookPluginList($info));
  120. foreach ($addresbookPlugins as $addresbookPlugin) {
  121. $this->addressBookPlugins[] = $addresbookPlugin;
  122. }
  123. $calendarPlugins = $this->loadSabreCalendarPluginsFromInfoXml($this->extractCalendarPluginList($info));
  124. foreach ($calendarPlugins as $calendarPlugin) {
  125. $this->calendarPlugins[] = $calendarPlugin;
  126. }
  127. }
  128. }
  129. /**
  130. * @param array $array
  131. * @return string[]
  132. */
  133. private function extractPluginList(array $array): array {
  134. if (isset($array['sabre']) && is_array($array['sabre'])) {
  135. if (isset($array['sabre']['plugins']) && is_array($array['sabre']['plugins'])) {
  136. if (isset($array['sabre']['plugins']['plugin'])) {
  137. $items = $array['sabre']['plugins']['plugin'];
  138. if (!is_array($items)) {
  139. $items = [$items];
  140. }
  141. return $items;
  142. }
  143. }
  144. }
  145. return [];
  146. }
  147. /**
  148. * @param array $array
  149. * @return string[]
  150. */
  151. private function extractCollectionList(array $array): array {
  152. if (isset($array['sabre']) && is_array($array['sabre'])) {
  153. if (isset($array['sabre']['collections']) && is_array($array['sabre']['collections'])) {
  154. if (isset($array['sabre']['collections']['collection'])) {
  155. $items = $array['sabre']['collections']['collection'];
  156. if (!is_array($items)) {
  157. $items = [$items];
  158. }
  159. return $items;
  160. }
  161. }
  162. }
  163. return [];
  164. }
  165. /**
  166. * @param array $array
  167. * @return string[]
  168. */
  169. private function extractAddressBookPluginList(array $array): array {
  170. if (!isset($array['sabre']) || !is_array($array['sabre'])) {
  171. return [];
  172. }
  173. if (!isset($array['sabre']['address-book-plugins']) || !is_array($array['sabre']['address-book-plugins'])) {
  174. return [];
  175. }
  176. if (!isset($array['sabre']['address-book-plugins']['plugin'])) {
  177. return [];
  178. }
  179. $items = $array['sabre']['address-book-plugins']['plugin'];
  180. if (!is_array($items)) {
  181. $items = [$items];
  182. }
  183. return $items;
  184. }
  185. /**
  186. * @param array $array
  187. * @return string[]
  188. */
  189. private function extractCalendarPluginList(array $array): array {
  190. if (isset($array['sabre']) && is_array($array['sabre'])) {
  191. if (isset($array['sabre']['calendar-plugins']) && is_array($array['sabre']['calendar-plugins'])) {
  192. if (isset($array['sabre']['calendar-plugins']['plugin'])) {
  193. $items = $array['sabre']['calendar-plugins']['plugin'];
  194. if (!is_array($items)) {
  195. $items = [$items];
  196. }
  197. return $items;
  198. }
  199. }
  200. }
  201. return [];
  202. }
  203. private function createClass(string $className): object {
  204. try {
  205. return $this->container->get($className);
  206. } catch (QueryException $e) {
  207. if (class_exists($className)) {
  208. return new $className();
  209. }
  210. }
  211. throw new \Exception('Could not load ' . $className, 0, $e);
  212. }
  213. /**
  214. * @param string[] $classes
  215. * @return ServerPlugin[]
  216. * @throws \Exception
  217. */
  218. private function loadSabrePluginsFromInfoXml(array $classes): array {
  219. return array_map(function (string $className): ServerPlugin {
  220. $instance = $this->createClass($className);
  221. if (!($instance instanceof ServerPlugin)) {
  222. throw new \Exception('Sabre server plugin ' . $className . ' does not implement the ' . ServerPlugin::class . ' interface');
  223. }
  224. return $instance;
  225. }, $classes);
  226. }
  227. /**
  228. * @param string[] $classes
  229. * @return Collection[]
  230. */
  231. private function loadSabreCollectionsFromInfoXml(array $classes): array {
  232. return array_map(function (string $className): Collection {
  233. $instance = $this->createClass($className);
  234. if (!($instance instanceof Collection)) {
  235. throw new \Exception('Sabre collection plugin ' . $className . ' does not implement the ' . Collection::class . ' interface');
  236. }
  237. return $instance;
  238. }, $classes);
  239. }
  240. /**
  241. * @param string[] $classes
  242. * @return IAddressBookProvider[]
  243. */
  244. private function loadSabreAddressBookPluginsFromInfoXml(array $classes): array {
  245. return array_map(function (string $className): IAddressBookProvider {
  246. $instance = $this->createClass($className);
  247. if (!($instance instanceof IAddressBookProvider)) {
  248. throw new \Exception('Sabre address book plugin class ' . $className . ' does not implement the ' . IAddressBookProvider::class . ' interface');
  249. }
  250. return $instance;
  251. }, $classes);
  252. }
  253. /**
  254. * @param string[] $classes
  255. * @return ICalendarProvider[]
  256. */
  257. private function loadSabreCalendarPluginsFromInfoXml(array $classes): array {
  258. return array_map(function (string $className): ICalendarProvider {
  259. $instance = $this->createClass($className);
  260. if (!($instance instanceof ICalendarProvider)) {
  261. throw new \Exception('Sabre calendar plugin class ' . $className . ' does not implement the ' . ICalendarProvider::class . ' interface');
  262. }
  263. return $instance;
  264. }, $classes);
  265. }
  266. }