Plugin.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\DAV\Browser;
  4. use Sabre\DAV;
  5. use Sabre\DAV\MkCol;
  6. use Sabre\HTTP;
  7. use Sabre\HTTP\RequestInterface;
  8. use Sabre\HTTP\ResponseInterface;
  9. use Sabre\Uri;
  10. /**
  11. * Browser Plugin.
  12. *
  13. * This plugin provides a html representation, so that a WebDAV server may be accessed
  14. * using a browser.
  15. *
  16. * The class intercepts GET requests to collection resources and generates a simple
  17. * html index.
  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. * reference to server class.
  27. *
  28. * @var DAV\Server
  29. */
  30. protected $server;
  31. /**
  32. * enablePost turns on the 'actions' panel, which allows people to create
  33. * folders and upload files straight from a browser.
  34. *
  35. * @var bool
  36. */
  37. protected $enablePost = true;
  38. /**
  39. * A list of properties that are usually not interesting. This can cut down
  40. * the browser output a bit by removing the properties that most people
  41. * will likely not want to see.
  42. *
  43. * @var array
  44. */
  45. public $uninterestingProperties = [
  46. '{DAV:}supportedlock',
  47. '{DAV:}acl-restrictions',
  48. // '{DAV:}supported-privilege-set',
  49. '{DAV:}supported-method-set',
  50. ];
  51. /**
  52. * Creates the object.
  53. *
  54. * By default it will allow file creation and uploads.
  55. * Specify the first argument as false to disable this
  56. *
  57. * @param bool $enablePost
  58. */
  59. public function __construct($enablePost = true)
  60. {
  61. $this->enablePost = $enablePost;
  62. }
  63. /**
  64. * Initializes the plugin and subscribes to events.
  65. */
  66. public function initialize(DAV\Server $server)
  67. {
  68. $this->server = $server;
  69. $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
  70. $this->server->on('method:GET', [$this, 'httpGet'], 200);
  71. $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
  72. if ($this->enablePost) {
  73. $this->server->on('method:POST', [$this, 'httpPOST']);
  74. }
  75. }
  76. /**
  77. * This method intercepts GET requests that have ?sabreAction=info
  78. * appended to the URL.
  79. */
  80. public function httpGetEarly(RequestInterface $request, ResponseInterface $response)
  81. {
  82. $params = $request->getQueryParameters();
  83. if (isset($params['sabreAction']) && 'info' === $params['sabreAction']) {
  84. return $this->httpGet($request, $response);
  85. }
  86. }
  87. /**
  88. * This method intercepts GET requests to collections and returns the html.
  89. *
  90. * @return bool
  91. */
  92. public function httpGet(RequestInterface $request, ResponseInterface $response)
  93. {
  94. // We're not using straight-up $_GET, because we want everything to be
  95. // unit testable.
  96. $getVars = $request->getQueryParameters();
  97. // CSP headers
  98. $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
  99. $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
  100. switch ($sabreAction) {
  101. case 'asset':
  102. // Asset handling, such as images
  103. $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
  104. return false;
  105. default:
  106. case 'info':
  107. try {
  108. $this->server->tree->getNodeForPath($request->getPath());
  109. } catch (DAV\Exception\NotFound $e) {
  110. // We're simply stopping when the file isn't found to not interfere
  111. // with other plugins.
  112. return;
  113. }
  114. $response->setStatus(200);
  115. $response->setHeader('Content-Type', 'text/html; charset=utf-8');
  116. $response->setBody(
  117. $this->generateDirectoryIndex($request->getPath())
  118. );
  119. return false;
  120. case 'plugins':
  121. $response->setStatus(200);
  122. $response->setHeader('Content-Type', 'text/html; charset=utf-8');
  123. $response->setBody(
  124. $this->generatePluginListing()
  125. );
  126. return false;
  127. }
  128. }
  129. /**
  130. * Handles POST requests for tree operations.
  131. *
  132. * @return bool
  133. */
  134. public function httpPOST(RequestInterface $request, ResponseInterface $response)
  135. {
  136. $contentType = $request->getHeader('Content-Type');
  137. if (!\is_string($contentType)) {
  138. return;
  139. }
  140. list($contentType) = explode(';', $contentType);
  141. if ('application/x-www-form-urlencoded' !== $contentType &&
  142. 'multipart/form-data' !== $contentType) {
  143. return;
  144. }
  145. $postVars = $request->getPostData();
  146. if (!isset($postVars['sabreAction'])) {
  147. return;
  148. }
  149. $uri = $request->getPath();
  150. if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
  151. switch ($postVars['sabreAction']) {
  152. case 'mkcol':
  153. if (isset($postVars['name']) && trim($postVars['name'])) {
  154. // Using basename() because we won't allow slashes
  155. list(, $folderName) = Uri\split(trim($postVars['name']));
  156. if (isset($postVars['resourceType'])) {
  157. $resourceType = explode(',', $postVars['resourceType']);
  158. } else {
  159. $resourceType = ['{DAV:}collection'];
  160. }
  161. $properties = [];
  162. foreach ($postVars as $varName => $varValue) {
  163. // Any _POST variable in clark notation is treated
  164. // like a property.
  165. if ('{' === $varName[0]) {
  166. // PHP will convert any dots to underscores.
  167. // This leaves us with no way to differentiate
  168. // the two.
  169. // Therefore we replace the string *DOT* with a
  170. // real dot. * is not allowed in uris so we
  171. // should be good.
  172. $varName = str_replace('*DOT*', '.', $varName);
  173. $properties[$varName] = $varValue;
  174. }
  175. }
  176. $mkCol = new MkCol(
  177. $resourceType,
  178. $properties
  179. );
  180. $this->server->createCollection($uri.'/'.$folderName, $mkCol);
  181. }
  182. break;
  183. // @codeCoverageIgnoreStart
  184. case 'put':
  185. if ($_FILES) {
  186. $file = current($_FILES);
  187. } else {
  188. break;
  189. }
  190. list(, $newName) = Uri\split(trim($file['name']));
  191. if (isset($postVars['name']) && trim($postVars['name'])) {
  192. $newName = trim($postVars['name']);
  193. }
  194. // Making sure we only have a 'basename' component
  195. list(, $newName) = Uri\split($newName);
  196. if (is_uploaded_file($file['tmp_name'])) {
  197. $this->server->createFile($uri.'/'.$newName, fopen($file['tmp_name'], 'r'));
  198. }
  199. break;
  200. // @codeCoverageIgnoreEnd
  201. }
  202. }
  203. $response->setHeader('Location', $request->getUrl());
  204. $response->setStatus(302);
  205. return false;
  206. }
  207. /**
  208. * Escapes a string for html.
  209. *
  210. * @param string $value
  211. *
  212. * @return string
  213. */
  214. public function escapeHTML($value)
  215. {
  216. return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
  217. }
  218. /**
  219. * Generates the html directory index for a given url.
  220. *
  221. * @param string $path
  222. *
  223. * @return string
  224. */
  225. public function generateDirectoryIndex($path)
  226. {
  227. $html = $this->generateHeader($path ? $path : '/', $path);
  228. $node = $this->server->tree->getNodeForPath($path);
  229. if ($node instanceof DAV\ICollection) {
  230. $html .= "<section><h1>Nodes</h1>\n";
  231. $html .= '<table class="nodeTable">';
  232. $subNodes = $this->server->getPropertiesForChildren($path, [
  233. '{DAV:}displayname',
  234. '{DAV:}resourcetype',
  235. '{DAV:}getcontenttype',
  236. '{DAV:}getcontentlength',
  237. '{DAV:}getlastmodified',
  238. ]);
  239. foreach ($subNodes as $subPath => $subProps) {
  240. $subNode = $this->server->tree->getNodeForPath($subPath);
  241. $fullPath = $this->server->getBaseUri().HTTP\encodePath($subPath);
  242. list(, $displayPath) = Uri\split($subPath);
  243. $subNodes[$subPath]['subNode'] = $subNode;
  244. $subNodes[$subPath]['fullPath'] = $fullPath;
  245. $subNodes[$subPath]['displayPath'] = $displayPath;
  246. }
  247. uasort($subNodes, [$this, 'compareNodes']);
  248. foreach ($subNodes as $subProps) {
  249. $type = [
  250. 'string' => 'Unknown',
  251. 'icon' => 'cog',
  252. ];
  253. if (isset($subProps['{DAV:}resourcetype'])) {
  254. $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
  255. }
  256. $html .= '<tr>';
  257. $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>';
  258. $html .= '<td class="typeColumn">'.$this->escapeHTML($type['string']).'</td>';
  259. $html .= '<td>';
  260. if (isset($subProps['{DAV:}getcontentlength'])) {
  261. $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'].' bytes');
  262. }
  263. $html .= '</td><td>';
  264. if (isset($subProps['{DAV:}getlastmodified'])) {
  265. $lastMod = $subProps['{DAV:}getlastmodified']->getTime();
  266. $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
  267. }
  268. $html .= '</td><td>';
  269. if (isset($subProps['{DAV:}displayname'])) {
  270. $html .= $this->escapeHTML($subProps['{DAV:}displayname']);
  271. }
  272. $html .= '</td>';
  273. $buttonActions = '';
  274. if ($subProps['subNode'] instanceof DAV\IFile) {
  275. $buttonActions = '<a href="'.$this->escapeHTML($subProps['fullPath']).'?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
  276. }
  277. $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
  278. $html .= '<td>'.$buttonActions.'</td>';
  279. $html .= '</tr>';
  280. }
  281. $html .= '</table>';
  282. }
  283. $html .= '</section>';
  284. $html .= '<section><h1>Properties</h1>';
  285. $html .= '<table class="propTable">';
  286. // Allprops request
  287. $propFind = new PropFindAll($path);
  288. $properties = $this->server->getPropertiesByNode($propFind, $node);
  289. $properties = $propFind->getResultForMultiStatus()[200];
  290. foreach ($properties as $propName => $propValue) {
  291. if (!in_array($propName, $this->uninterestingProperties)) {
  292. $html .= $this->drawPropertyRow($propName, $propValue);
  293. }
  294. }
  295. $html .= '</table>';
  296. $html .= '</section>';
  297. /* Start of generating actions */
  298. $output = '';
  299. if ($this->enablePost) {
  300. $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
  301. }
  302. if ($output) {
  303. $html .= '<section><h1>Actions</h1>';
  304. $html .= "<div class=\"actions\">\n";
  305. $html .= $output;
  306. $html .= "</div>\n";
  307. $html .= "</section>\n";
  308. }
  309. $html .= $this->generateFooter();
  310. $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
  311. return $html;
  312. }
  313. /**
  314. * Generates the 'plugins' page.
  315. *
  316. * @return string
  317. */
  318. public function generatePluginListing()
  319. {
  320. $html = $this->generateHeader('Plugins');
  321. $html .= '<section><h1>Plugins</h1>';
  322. $html .= '<table class="propTable">';
  323. foreach ($this->server->getPlugins() as $plugin) {
  324. $info = $plugin->getPluginInfo();
  325. $html .= '<tr><th>'.$info['name'].'</th>';
  326. $html .= '<td>'.$info['description'].'</td>';
  327. $html .= '<td>';
  328. if (isset($info['link']) && $info['link']) {
  329. $html .= '<a href="'.$this->escapeHTML($info['link']).'"><span class="oi" data-glyph="book"></span></a>';
  330. }
  331. $html .= '</td></tr>';
  332. }
  333. $html .= '</table>';
  334. $html .= '</section>';
  335. /* Start of generating actions */
  336. $html .= $this->generateFooter();
  337. return $html;
  338. }
  339. /**
  340. * Generates the first block of HTML, including the <head> tag and page
  341. * header.
  342. *
  343. * Returns footer.
  344. *
  345. * @param string $title
  346. * @param string $path
  347. *
  348. * @return string
  349. */
  350. public function generateHeader($title, $path = null)
  351. {
  352. $version = '';
  353. if (DAV\Server::$exposeVersion) {
  354. $version = DAV\Version::VERSION;
  355. }
  356. $vars = [
  357. 'title' => $this->escapeHTML($title),
  358. 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
  359. 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
  360. 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
  361. 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
  362. 'baseUrl' => $this->server->getBaseUri(),
  363. ];
  364. $html = <<<HTML
  365. <!DOCTYPE html>
  366. <html>
  367. <head>
  368. <title>$vars[title] - sabre/dav $version</title>
  369. <link rel="shortcut icon" href="$vars[favicon]" type="image/vnd.microsoft.icon" />
  370. <link rel="stylesheet" href="$vars[style]" type="text/css" />
  371. <link rel="stylesheet" href="$vars[iconstyle]" type="text/css" />
  372. </head>
  373. <body>
  374. <header>
  375. <div class="logo">
  376. <a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a>
  377. </div>
  378. </header>
  379. <nav>
  380. HTML;
  381. // If the path is empty, there's no parent.
  382. if ($path) {
  383. list($parentUri) = Uri\split($path);
  384. $fullPath = $this->server->getBaseUri().HTTP\encodePath($parentUri);
  385. $html .= '<a href="'.$fullPath.'" class="btn">⇤ Go to parent</a>';
  386. } else {
  387. $html .= '<span class="btn disabled">⇤ Go to parent</span>';
  388. }
  389. $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
  390. $html .= '</nav>';
  391. return $html;
  392. }
  393. /**
  394. * Generates the page footer.
  395. *
  396. * Returns html.
  397. *
  398. * @return string
  399. */
  400. public function generateFooter()
  401. {
  402. $version = '';
  403. if (DAV\Server::$exposeVersion) {
  404. $version = DAV\Version::VERSION;
  405. }
  406. $year = date('Y');
  407. return <<<HTML
  408. <footer>Generated by SabreDAV $version (c)2007-$year <a href="http://sabre.io/">http://sabre.io/</a></footer>
  409. </body>
  410. </html>
  411. HTML;
  412. }
  413. /**
  414. * This method is used to generate the 'actions panel' output for
  415. * collections.
  416. *
  417. * This specifically generates the interfaces for creating new files, and
  418. * creating new directories.
  419. *
  420. * @param mixed $output
  421. * @param string $path
  422. */
  423. public function htmlActionsPanel(DAV\INode $node, &$output, $path)
  424. {
  425. if (!$node instanceof DAV\ICollection) {
  426. return;
  427. }
  428. // We also know fairly certain that if an object is a non-extended
  429. // SimpleCollection, we won't need to show the panel either.
  430. if ('Sabre\\DAV\\SimpleCollection' === get_class($node)) {
  431. return;
  432. }
  433. $output .= <<<HTML
  434. <form method="post" action="">
  435. <h3>Create new folder</h3>
  436. <input type="hidden" name="sabreAction" value="mkcol" />
  437. <label>Name:</label> <input type="text" name="name" /><br />
  438. <input type="submit" value="create" />
  439. </form>
  440. <form method="post" action="" enctype="multipart/form-data">
  441. <h3>Upload file</h3>
  442. <input type="hidden" name="sabreAction" value="put" />
  443. <label>Name (optional):</label> <input type="text" name="name" /><br />
  444. <label>File:</label> <input type="file" name="file" /><br />
  445. <input type="submit" value="upload" />
  446. </form>
  447. HTML;
  448. }
  449. /**
  450. * This method takes a path/name of an asset and turns it into url
  451. * suiteable for http access.
  452. *
  453. * @param string $assetName
  454. *
  455. * @return string
  456. */
  457. protected function getAssetUrl($assetName)
  458. {
  459. return $this->server->getBaseUri().'?sabreAction=asset&assetName='.urlencode($assetName);
  460. }
  461. /**
  462. * This method returns a local pathname to an asset.
  463. *
  464. * @param string $assetName
  465. *
  466. * @throws DAV\Exception\NotFound
  467. *
  468. * @return string
  469. */
  470. protected function getLocalAssetPath($assetName)
  471. {
  472. $assetDir = __DIR__.'/assets/';
  473. $path = $assetDir.$assetName;
  474. // Making sure people aren't trying to escape from the base path.
  475. $path = str_replace('\\', '/', $path);
  476. if (false !== strpos($path, '/../') || '/..' === strrchr($path, '/')) {
  477. throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
  478. }
  479. $realPath = realpath($path);
  480. if ($realPath && 0 === strpos($realPath, realpath($assetDir)) && file_exists($path)) {
  481. return $path;
  482. }
  483. throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
  484. }
  485. /**
  486. * This method reads an asset from disk and generates a full http response.
  487. *
  488. * @param string $assetName
  489. */
  490. protected function serveAsset($assetName)
  491. {
  492. $assetPath = $this->getLocalAssetPath($assetName);
  493. // Rudimentary mime type detection
  494. $mime = 'application/octet-stream';
  495. $map = [
  496. 'ico' => 'image/vnd.microsoft.icon',
  497. 'png' => 'image/png',
  498. 'css' => 'text/css',
  499. ];
  500. $ext = substr($assetName, strrpos($assetName, '.') + 1);
  501. if (isset($map[$ext])) {
  502. $mime = $map[$ext];
  503. }
  504. $this->server->httpResponse->setHeader('Content-Type', $mime);
  505. $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
  506. $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
  507. $this->server->httpResponse->setStatus(200);
  508. $this->server->httpResponse->setBody(fopen($assetPath, 'r'));
  509. }
  510. /**
  511. * Sort helper function: compares two directory entries based on type and
  512. * display name. Collections sort above other types.
  513. *
  514. * @param array $a
  515. * @param array $b
  516. *
  517. * @return int
  518. */
  519. protected function compareNodes($a, $b)
  520. {
  521. $typeA = (isset($a['{DAV:}resourcetype']))
  522. ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
  523. : false;
  524. $typeB = (isset($b['{DAV:}resourcetype']))
  525. ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
  526. : false;
  527. // If same type, sort alphabetically by filename:
  528. if ($typeA === $typeB) {
  529. return strnatcasecmp($a['displayPath'], $b['displayPath']);
  530. }
  531. return ($typeA < $typeB) ? 1 : -1;
  532. }
  533. /**
  534. * Maps a resource type to a human-readable string and icon.
  535. *
  536. * @param DAV\INode $node
  537. *
  538. * @return array
  539. */
  540. private function mapResourceType(array $resourceTypes, $node)
  541. {
  542. if (!$resourceTypes) {
  543. if ($node instanceof DAV\IFile) {
  544. return [
  545. 'string' => 'File',
  546. 'icon' => 'file',
  547. ];
  548. } else {
  549. return [
  550. 'string' => 'Unknown',
  551. 'icon' => 'cog',
  552. ];
  553. }
  554. }
  555. $types = [
  556. '{http://calendarserver.org/ns/}calendar-proxy-write' => [
  557. 'string' => 'Proxy-Write',
  558. 'icon' => 'people',
  559. ],
  560. '{http://calendarserver.org/ns/}calendar-proxy-read' => [
  561. 'string' => 'Proxy-Read',
  562. 'icon' => 'people',
  563. ],
  564. '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
  565. 'string' => 'Outbox',
  566. 'icon' => 'inbox',
  567. ],
  568. '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
  569. 'string' => 'Inbox',
  570. 'icon' => 'inbox',
  571. ],
  572. '{urn:ietf:params:xml:ns:caldav}calendar' => [
  573. 'string' => 'Calendar',
  574. 'icon' => 'calendar',
  575. ],
  576. '{http://calendarserver.org/ns/}shared-owner' => [
  577. 'string' => 'Shared',
  578. 'icon' => 'calendar',
  579. ],
  580. '{http://calendarserver.org/ns/}subscribed' => [
  581. 'string' => 'Subscription',
  582. 'icon' => 'calendar',
  583. ],
  584. '{urn:ietf:params:xml:ns:carddav}directory' => [
  585. 'string' => 'Directory',
  586. 'icon' => 'globe',
  587. ],
  588. '{urn:ietf:params:xml:ns:carddav}addressbook' => [
  589. 'string' => 'Address book',
  590. 'icon' => 'book',
  591. ],
  592. '{DAV:}principal' => [
  593. 'string' => 'Principal',
  594. 'icon' => 'person',
  595. ],
  596. '{DAV:}collection' => [
  597. 'string' => 'Collection',
  598. 'icon' => 'folder',
  599. ],
  600. ];
  601. $info = [
  602. 'string' => [],
  603. 'icon' => 'cog',
  604. ];
  605. foreach ($resourceTypes as $k => $resourceType) {
  606. if (isset($types[$resourceType])) {
  607. $info['string'][] = $types[$resourceType]['string'];
  608. } else {
  609. $info['string'][] = $resourceType;
  610. }
  611. }
  612. foreach ($types as $key => $resourceInfo) {
  613. if (in_array($key, $resourceTypes)) {
  614. $info['icon'] = $resourceInfo['icon'];
  615. break;
  616. }
  617. }
  618. $info['string'] = implode(', ', $info['string']);
  619. return $info;
  620. }
  621. /**
  622. * Draws a table row for a property.
  623. *
  624. * @param string $name
  625. * @param mixed $value
  626. *
  627. * @return string
  628. */
  629. private function drawPropertyRow($name, $value)
  630. {
  631. $html = new HtmlOutputHelper(
  632. $this->server->getBaseUri(),
  633. $this->server->xml->namespaceMap
  634. );
  635. return '<tr><th>'.$html->xmlName($name).'</th><td>'.$this->drawPropertyValue($html, $value).'</td></tr>';
  636. }
  637. /**
  638. * Draws a table row for a property.
  639. *
  640. * @param HtmlOutputHelper $html
  641. * @param mixed $value
  642. *
  643. * @return string
  644. */
  645. private function drawPropertyValue($html, $value)
  646. {
  647. if (is_scalar($value)) {
  648. return $html->h($value);
  649. } elseif ($value instanceof HtmlOutput) {
  650. return $value->toHtml($html);
  651. } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
  652. // There's no default html output for this property, we're going
  653. // to output the actual xml serialization instead.
  654. $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
  655. // removing first and last line, as they contain our root
  656. // element.
  657. $xml = explode("\n", $xml);
  658. $xml = array_slice($xml, 2, -2);
  659. return '<pre>'.$html->h(implode("\n", $xml)).'</pre>';
  660. } else {
  661. return '<em>unknown</em>';
  662. }
  663. }
  664. /**
  665. * Returns a plugin name.
  666. *
  667. * Using this name other plugins will be able to access other plugins;
  668. * using \Sabre\DAV\Server::getPlugin
  669. *
  670. * @return string
  671. */
  672. public function getPluginName()
  673. {
  674. return 'browser';
  675. }
  676. /**
  677. * Returns a bunch of meta-data about the plugin.
  678. *
  679. * Providing this information is optional, and is mainly displayed by the
  680. * Browser plugin.
  681. *
  682. * The description key in the returned array may contain html and will not
  683. * be sanitized.
  684. *
  685. * @return array
  686. */
  687. public function getPluginInfo()
  688. {
  689. return [
  690. 'name' => $this->getPluginName(),
  691. 'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
  692. 'link' => 'http://sabre.io/dav/browser-plugin/',
  693. ];
  694. }
  695. }