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