xref: /plugin/davcal/vendor/sabre/dav/lib/DAV/Browser/Plugin.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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