2017-04-26 17:54:53 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
|
|
|
*
|
|
|
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace OCA\SharePoint\Storage;
|
|
|
|
|
|
|
|
use Icewind\Streams\CallbackWrapper;
|
|
|
|
use Icewind\Streams\IteratorDirectory;
|
|
|
|
use OC\Cache\CappedMemoryCache;
|
|
|
|
use OC\Files\Storage\Common;
|
|
|
|
use OCA\SharePoint\Client;
|
|
|
|
use OCA\SharePoint\ClientFactory;
|
2021-03-13 03:04:55 +03:00
|
|
|
use OCA\SharePoint\ContextsFactory;
|
2021-03-29 15:14:22 +03:00
|
|
|
use OCA\SharePoint\Helper\RequestsWrapper;
|
2021-03-13 03:04:55 +03:00
|
|
|
use OCA\SharePoint\NotFoundException;
|
2017-04-26 17:54:53 +03:00
|
|
|
use OCP\Files\FileInfo;
|
2019-04-05 18:10:47 +03:00
|
|
|
use OCP\ILogger;
|
2017-04-26 17:54:53 +03:00
|
|
|
use OCP\ITempManager;
|
|
|
|
use Office365\PHP\Client\Runtime\ClientObjectCollection;
|
|
|
|
use Office365\PHP\Client\SharePoint\File;
|
|
|
|
use Office365\PHP\Client\SharePoint\Folder;
|
|
|
|
|
|
|
|
class Storage extends Common {
|
2021-03-05 15:59:32 +03:00
|
|
|
public const SP_PROPERTY_SIZE = 'Length';
|
|
|
|
public const SP_PROPERTY_MTIME = 'TimeLastModified';
|
|
|
|
public const SP_PROPERTY_MODIFIED = 'Modified';
|
|
|
|
public const SP_PROPERTY_MTIME_LAST_ITEM = 'LastItemModifiedDate';
|
|
|
|
public const SP_PROPERTY_URL = 'ServerRelativeUrl';
|
|
|
|
public const SP_PROPERTY_NAME = 'Name';
|
|
|
|
|
|
|
|
public const SP_PERMISSION_READ = 1;
|
|
|
|
public const SP_PERMISSION_CREATE = 2;
|
|
|
|
public const SP_PERMISSION_UPDATE = 3;
|
|
|
|
public const SP_PERMISSION_DELETE = 4;
|
2017-04-26 17:54:53 +03:00
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
protected $server;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
protected $documentLibrary;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
protected $authUser;
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
protected $authPwd;
|
|
|
|
|
|
|
|
/** @var Client */
|
|
|
|
protected $spClient;
|
|
|
|
|
|
|
|
/** @var CappedMemoryCache */
|
|
|
|
protected $fileCache;
|
2021-03-13 03:04:55 +03:00
|
|
|
/** @var false|mixed */
|
2021-03-24 12:34:33 +03:00
|
|
|
protected $forceNtlm;
|
2017-04-26 17:54:53 +03:00
|
|
|
|
|
|
|
/** @var ContextsFactory */
|
|
|
|
private $contextsFactory;
|
|
|
|
|
|
|
|
/** @var ITempManager */
|
|
|
|
private $tempManager;
|
|
|
|
|
|
|
|
public function __construct($parameters) {
|
2019-03-26 14:36:12 +03:00
|
|
|
$this->server = rtrim($parameters['host'], '/') . '/';
|
|
|
|
$this->documentLibrary = ltrim($parameters['documentLibrary'], '/');
|
2017-04-26 17:54:53 +03:00
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if (strpos($this->documentLibrary, '"') !== false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
// they are, amongst others, not allowed and we use it in the filter
|
|
|
|
// cf. https://support.microsoft.com/en-us/kb/2933738
|
|
|
|
// TODO: verify, it talks about files and folders mostly
|
|
|
|
throw new \InvalidArgumentException('Illegal character in Document Library Name');
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if (!isset($parameters['user']) || !isset($parameters['password'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
throw new \UnexpectedValueException('No user or password given');
|
|
|
|
}
|
|
|
|
$this->authUser = $parameters['user'];
|
2021-03-05 15:59:32 +03:00
|
|
|
$this->authPwd = $parameters['password'];
|
2021-03-24 12:34:33 +03:00
|
|
|
$this->forceNtlm = $parameters['forceNtlm'] ?? false;
|
2017-04-26 17:54:53 +03:00
|
|
|
|
|
|
|
$this->fixDI($parameters);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the identifier for the storage,
|
|
|
|
* the returned id should be the same for every storage object that is created with the same parameters
|
|
|
|
* and two storage objects with the same id should refer to two storages that display the same files.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function getId() {
|
|
|
|
return 'SharePoint::' . $this->server . '::' . $this->documentLibrary . '::' . $this->authUser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.mkdir.php
|
|
|
|
* implementations need to implement a recursive mkdir
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function mkdir($path) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
try {
|
|
|
|
$folder = $this->spClient->createFolder($serverUrl);
|
|
|
|
$this->fileCache->set($serverUrl, [
|
|
|
|
'instance' => $folder,
|
|
|
|
'children' => [
|
|
|
|
'folders' => $folder->getFolders(),
|
|
|
|
'files' => $folder->getFiles()
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
$this->fileCache->remove($serverUrl);
|
2019-04-05 18:10:47 +03:00
|
|
|
\OC::$server->getLogger()->logException($e,
|
|
|
|
[
|
|
|
|
'app' => 'sharepoint',
|
|
|
|
'level' => ILogger::INFO
|
|
|
|
]
|
|
|
|
);
|
2017-04-26 17:54:53 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.rmdir.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function rmdir($path) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
try {
|
|
|
|
$folder = $this->getFileOrFolder($serverUrl);
|
|
|
|
$this->spClient->delete($folder);
|
|
|
|
$this->fileCache->set($serverUrl, false);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
$this->fileCache->remove($serverUrl);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.opendir.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return resource|false
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function opendir($path) {
|
|
|
|
try {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
$collections = $this->getFolderContents($serverUrl);
|
|
|
|
$files = [];
|
|
|
|
|
|
|
|
foreach ($collections as $collection) {
|
|
|
|
/** @var File[]|Folder[] $items */
|
|
|
|
$items = $collection->getData();
|
|
|
|
foreach ($items as $item) {
|
2021-03-05 15:59:32 +03:00
|
|
|
if (!$this->spClient->isHidden($item)) {
|
2019-03-26 00:59:24 +03:00
|
|
|
$files[] = $item->getProperty(Storage::SP_PROPERTY_NAME);
|
2017-04-26 17:54:53 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return IteratorDirectory::wrap($files);
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.stat.php
|
|
|
|
* only the following keys are required in the result: size and mtime
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return array|false
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function stat($path) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
try {
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($path === '' || $path === '/') {
|
2019-10-14 16:40:04 +03:00
|
|
|
return $this->statForDocumentLibrary();
|
|
|
|
}
|
2017-04-26 17:54:53 +03:00
|
|
|
$file = $this->getFileOrFolder($serverUrl);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$size = $file->getProperty(self::SP_PROPERTY_SIZE) ?: FileInfo::SPACE_UNKNOWN;
|
2019-03-22 20:30:04 +03:00
|
|
|
$mtimeValue = (string)$file->getProperty(self::SP_PROPERTY_MTIME);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($mtimeValue === '') {
|
2019-10-14 16:40:04 +03:00
|
|
|
// if sp2013 ListItemAllFields are fetched automatically
|
2020-05-08 00:34:55 +03:00
|
|
|
$mtimeValue = $file->getListItemAllFields()->getProperty(self::SP_PROPERTY_MODIFIED);
|
2019-10-14 16:40:04 +03:00
|
|
|
}
|
2019-03-22 20:30:04 +03:00
|
|
|
$name = (string)$file->getProperty(self::SP_PROPERTY_NAME);
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($mtimeValue === '') {
|
2019-03-29 14:56:21 +03:00
|
|
|
// SP2013 does not provide an mtime.
|
|
|
|
$timestamp = time();
|
|
|
|
} else {
|
|
|
|
$mtime = new \DateTime($mtimeValue);
|
|
|
|
$timestamp = $mtime->getTimestamp();
|
2019-03-22 20:30:04 +03:00
|
|
|
}
|
2017-04-26 17:54:53 +03:00
|
|
|
|
|
|
|
$stat = [
|
|
|
|
// int64, size in bytes, excluding the size of any Web Parts that are used in the file.
|
2021-03-05 15:59:32 +03:00
|
|
|
'size' => $size,
|
2019-03-22 20:30:04 +03:00
|
|
|
'mtime' => $timestamp,
|
2019-03-29 14:56:21 +03:00
|
|
|
// no property in SP 2013 & 2016, other storages do the same
|
2017-04-26 17:54:53 +03:00
|
|
|
'atime' => time(),
|
|
|
|
];
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($name !== '') {
|
2019-03-22 20:30:04 +03:00
|
|
|
// previously, checking mtime was the check, alas SP2013…
|
2017-04-26 17:54:53 +03:00
|
|
|
return $stat;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we do not get a mtime from SP, we treat it as an error
|
|
|
|
// thus returning false, according to PHP documentation on stat()
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:40:04 +03:00
|
|
|
protected function statForDocumentLibrary() {
|
|
|
|
try {
|
|
|
|
$dLib = $this->spClient->getDocumentLibrary($this->documentLibrary);
|
2020-05-08 00:34:55 +03:00
|
|
|
$mtimeValue = (string)$dLib->getProperty(self::SP_PROPERTY_MTIME_LAST_ITEM);
|
2019-10-14 16:40:04 +03:00
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
\OC::$server->getLogger()->logException($e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($mtimeValue === '') {
|
2019-10-14 16:40:04 +03:00
|
|
|
// SP2013 does not provide an mtime.
|
|
|
|
$timestamp = time();
|
|
|
|
} else {
|
|
|
|
$mtime = new \DateTime($mtimeValue);
|
|
|
|
$timestamp = $mtime->getTimestamp();
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
// int64, size in bytes, excluding the size of any Web Parts that are used in the file.
|
2021-03-05 15:59:32 +03:00
|
|
|
'size' => FileInfo::SPACE_UNKNOWN,
|
2019-10-14 16:40:04 +03:00
|
|
|
'mtime' => $timestamp,
|
|
|
|
// no property in SP 2013 & 2016, other storages do the same
|
|
|
|
'atime' => time(),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2017-04-26 17:54:53 +03:00
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.filetype.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return false|string
|
|
|
|
* @throws \Exception
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function filetype($path) {
|
|
|
|
try {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
$object = $this->getFileOrFolder($serverUrl);
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($object instanceof File) {
|
2017-04-26 17:54:53 +03:00
|
|
|
return 'file';
|
2021-03-05 15:59:32 +03:00
|
|
|
} elseif ($object instanceof Folder) {
|
2017-04-26 17:54:53 +03:00
|
|
|
return 'dir';
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.file_exists.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function file_exists($path) {
|
|
|
|
try {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
// alternative approach is to use a CAML query instead of querying
|
|
|
|
// for file and folder. It is not necessarily faster, though.
|
|
|
|
// Would need evaluation of typical use cases (I assume most often
|
|
|
|
// existing files are checked) and measurements.
|
|
|
|
$this->getFileOrFolder($serverUrl);
|
|
|
|
return true;
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.unlink.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function unlink($path) {
|
|
|
|
// file methods get called twice at least, returning true
|
2021-03-05 15:59:32 +03:00
|
|
|
if (!$this->file_exists($path)) {
|
2017-04-26 17:54:53 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
$this->spClient->delete($this->getFileOrFolder($serverUrl));
|
|
|
|
$this->fileCache->set($serverUrl, false);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path1
|
|
|
|
* @param string $path2
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function rename($path1, $path2) {
|
|
|
|
$oldPath = $this->formatPath($path1);
|
|
|
|
$newPath = $this->formatPath($path2);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$item = $this->getFileOrFolder($newPath);
|
|
|
|
$this->spClient->delete($item);
|
|
|
|
$this->fileCache->remove($newPath);
|
2021-03-05 15:59:32 +03:00
|
|
|
} catch (NotFoundException $e) {
|
2017-04-26 17:54:53 +03:00
|
|
|
// noop
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$isRenamed = $this->spClient->rename($oldPath, $newPath);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($isRenamed) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$entry = $this->fileCache->get($oldPath);
|
|
|
|
$this->fileCache->remove($newPath);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($entry !== false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$this->fileCache->set($newPath, $entry);
|
|
|
|
}
|
|
|
|
$this->fileCache->remove($oldPath);
|
|
|
|
}
|
|
|
|
return $isRenamed;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.fopen.php
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @param string $mode
|
|
|
|
* @return resource|false
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function fopen($path, $mode) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
|
|
|
|
switch ($mode) {
|
|
|
|
case 'a':
|
|
|
|
case 'ab':
|
|
|
|
case 'a+':
|
|
|
|
// no native support
|
|
|
|
return false;
|
|
|
|
case 'r':
|
|
|
|
case 'rb':
|
|
|
|
$tmpFile = $this->tempManager->getTemporaryFile();
|
|
|
|
|
|
|
|
$fp = fopen($tmpFile, 'w+');
|
2021-03-05 15:59:32 +03:00
|
|
|
if (!$this->spClient->getFileViaStream($serverUrl, $fp)) {
|
2017-04-26 17:54:53 +03:00
|
|
|
fclose($fp);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fseek($fp, 0);
|
|
|
|
return $fp;
|
|
|
|
break;
|
|
|
|
case 'r+':
|
|
|
|
case 'rb+':
|
|
|
|
case 'r+b':
|
|
|
|
// fseek 0
|
|
|
|
case 'w':
|
|
|
|
case 'w+':
|
|
|
|
case 'wb':
|
|
|
|
case 'wb+':
|
|
|
|
case 'w+b':
|
|
|
|
// truncate
|
|
|
|
// fseek 0
|
|
|
|
case 'x':
|
|
|
|
case 'x+':
|
|
|
|
case 'xb':
|
|
|
|
case 'xb+':
|
|
|
|
case 'x+b':
|
|
|
|
// fseek 0
|
|
|
|
case 'c':
|
|
|
|
case 'cb':
|
|
|
|
case 'c+':
|
|
|
|
case 'cb+':
|
|
|
|
case 'c+b':
|
|
|
|
//fseek 0
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($mode[0] === 'x' && $this->file_exists($path)) {
|
2017-04-26 17:54:53 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$tmpFile = $this->tempManager->getTemporaryFile();
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($mode[0] !== 'w' && $this->file_exists($path)) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$content = $this->fopen($path, 'r');
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($content === false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
// should not happen, but let's be safe
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$this->file_put_contents($tmpFile, $content);
|
|
|
|
}
|
|
|
|
$fp = fopen($tmpFile, $mode);
|
|
|
|
return CallbackWrapper::wrap($fp, null, null, function () use ($path, $tmpFile) {
|
|
|
|
$this->writeBack($tmpFile, $path);
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $tmpFile
|
|
|
|
* @param string $path
|
|
|
|
*/
|
|
|
|
public function writeBack($tmpFile, $path) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
$content = file_get_contents($tmpFile);
|
|
|
|
$fp = fopen($tmpFile, 'r');
|
|
|
|
|
|
|
|
try {
|
|
|
|
if ($this->file_exists($path)) {
|
|
|
|
$this->spClient->overwriteFileViaStream($serverUrl, $fp, $tmpFile);
|
|
|
|
fclose($fp);
|
|
|
|
$this->fileCache->remove($serverUrl);
|
|
|
|
} else {
|
|
|
|
$file = $this->spClient->uploadNewFile($serverUrl, $content);
|
|
|
|
$this->fileCache->set($serverUrl, ['instance' => $file]);
|
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
// noop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isCreatable($path) {
|
|
|
|
try {
|
|
|
|
return $this->hasPermission($path, self::SP_PERMISSION_CREATE);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return parent::isCreatable($path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isUpdatable($path) {
|
|
|
|
try {
|
|
|
|
return $this->hasPermission($path, self::SP_PERMISSION_UPDATE);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return parent::isUpdatable($path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isReadable($path) {
|
|
|
|
try {
|
|
|
|
return $this->hasPermission($path, self::SP_PERMISSION_READ);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return parent::isReadable($path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isDeletable($path) {
|
|
|
|
try {
|
|
|
|
return $this->hasPermission($path, self::SP_PERMISSION_DELETE);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return parent::isDeletable($path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $path
|
|
|
|
* @param int $permissionType
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasPermission($path, $permissionType) {
|
|
|
|
$serverUrl = $this->formatPath($path);
|
|
|
|
return $this->getUserPermissions($serverUrl)->has($permissionType);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.touch.php
|
|
|
|
* If the backend does not support the operation, false should be returned
|
|
|
|
*
|
|
|
|
* @param string $path
|
|
|
|
* @param int $mtime
|
|
|
|
* @return bool
|
|
|
|
* @since 6.0.0
|
|
|
|
*/
|
|
|
|
public function touch($path, $mtime = null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* work around dependency injection issues so we can test this class properly
|
|
|
|
*
|
|
|
|
* @param array $parameters
|
|
|
|
*/
|
|
|
|
private function fixDI(array $parameters) {
|
2021-03-05 15:59:32 +03:00
|
|
|
if (isset($parameters['contextFactory'])
|
|
|
|
&& $parameters['contextFactory'] instanceof ContextsFactory) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$this->contextsFactory = $parameters['contextFactory'];
|
|
|
|
} else {
|
|
|
|
$this->contextsFactory = new ContextsFactory();
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if (isset($parameters['sharePointClientFactory'])
|
|
|
|
&& $parameters['sharePointClientFactory'] instanceof ClientFactory) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$spcFactory = $parameters['sharePointClientFactory'];
|
|
|
|
} else {
|
2021-03-29 15:14:22 +03:00
|
|
|
$spcFactory = new ClientFactory(new RequestsWrapper());
|
2017-04-26 17:54:53 +03:00
|
|
|
}
|
|
|
|
$this->spClient = $spcFactory->getClient(
|
|
|
|
$this->contextsFactory,
|
|
|
|
$this->server,
|
2021-03-13 03:04:55 +03:00
|
|
|
['user' => $this->authUser, 'password' => $this->authPwd],
|
2021-03-24 12:34:33 +03:00
|
|
|
['forceNtlm' => $this->forceNtlm]
|
2017-04-26 17:54:53 +03:00
|
|
|
);
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if (isset($parameters['cappedMemoryCache'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$this->fileCache = $parameters['cappedMemoryCache'];
|
|
|
|
} else {
|
|
|
|
// there's no API to get such
|
|
|
|
$this->fileCache = new CappedMemoryCache();
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:59:32 +03:00
|
|
|
if (isset($parameters['tempManager'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$this->tempManager = $parameters['tempManager'];
|
|
|
|
} else {
|
|
|
|
$this->tempManager = \OC::$server->getTempManager();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $serverUrl
|
|
|
|
* @return ClientObjectCollection[]
|
|
|
|
*/
|
|
|
|
private function getFolderContents($serverUrl) {
|
|
|
|
$folder = $this->getFileOrFolder($serverUrl);
|
|
|
|
$entry = $this->fileCache->get($serverUrl);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($entry === null || !isset($entry['children'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$contents = $this->spClient->fetchFolderContents($folder);
|
|
|
|
$cacheItem = $entry ?: [];
|
|
|
|
$cacheItem['children'] = $contents;
|
|
|
|
$this->fileCache->set($serverUrl, $cacheItem);
|
|
|
|
|
|
|
|
// cache children instances
|
|
|
|
foreach ($contents as $collection) {
|
|
|
|
foreach ($collection->getData() as $item) {
|
|
|
|
/** @var File|Folder $item */
|
|
|
|
$url = $item->getProperty(self::SP_PROPERTY_URL);
|
2021-03-05 15:59:32 +03:00
|
|
|
if (is_null($url)) {
|
2019-10-14 16:40:04 +03:00
|
|
|
// at least on SP13 requesting self::SP_PROPERTY_URL against folders causes an exception
|
2019-04-03 19:52:29 +03:00
|
|
|
continue;
|
|
|
|
}
|
2017-04-26 17:54:53 +03:00
|
|
|
$itemEntry = $this->fileCache->get($url);
|
|
|
|
$itemEntry = $itemEntry ?: [];
|
2021-03-05 15:59:32 +03:00
|
|
|
if (!isset($itemEntry['instance'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
$itemEntry['instance'] = $item;
|
|
|
|
$this->fileCache->set($url, $itemEntry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$contents = $entry['children'];
|
|
|
|
}
|
|
|
|
return $contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $serverUrl
|
|
|
|
* @return \Office365\PHP\Client\SharePoint\BasePermissions
|
|
|
|
* @throws NotFoundException
|
|
|
|
*/
|
|
|
|
private function getUserPermissions($serverUrl) {
|
2019-10-14 16:40:04 +03:00
|
|
|
// temporarily, cf. https://github.com/vgrem/phpSPO/issues/93#issuecomment-489024363
|
|
|
|
throw new NotFoundException('Could not retrieve permissions');
|
|
|
|
|
2017-04-26 17:54:53 +03:00
|
|
|
$item = $this->getFileOrFolder($serverUrl);
|
|
|
|
$entry = $this->fileCache->get($serverUrl);
|
2021-03-05 15:59:32 +03:00
|
|
|
if (isset($entry['permissions'])) {
|
|
|
|
if ($entry['permissions'] === false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
throw new NotFoundException('Could not retrieve permissions');
|
|
|
|
}
|
|
|
|
return $entry['permissions'];
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$permissions = $this->spClient->getPermissions($item);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
$permissions = false;
|
|
|
|
}
|
|
|
|
$entry['permissions'] = $permissions;
|
|
|
|
$this->fileCache->set($serverUrl, $entry);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($entry['permissions'] === false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
throw new NotFoundException('Could not retrieve permissions');
|
|
|
|
}
|
|
|
|
return $entry['permissions'];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $serverUrl
|
|
|
|
* @return File|Folder
|
|
|
|
* @throws NotFoundException
|
|
|
|
*/
|
|
|
|
private function getFileOrFolder($serverUrl) {
|
|
|
|
$entry = $this->fileCache->get($serverUrl);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($entry === false) {
|
2017-04-26 17:54:53 +03:00
|
|
|
throw new NotFoundException('File or Folder not found');
|
2021-03-05 15:59:32 +03:00
|
|
|
} elseif ($entry === null || !isset($entry['instance'])) {
|
2017-04-26 17:54:53 +03:00
|
|
|
try {
|
2019-04-05 18:10:47 +03:00
|
|
|
$file = $this->spClient->fetchFileOrFolder($serverUrl);
|
2017-04-26 17:54:53 +03:00
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
$this->fileCache->set($serverUrl, false);
|
|
|
|
throw $e;
|
2019-03-22 20:30:04 +03:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
\OC::$server->getLogger()->logException($e, ['app' => 'sharepoint']);
|
|
|
|
throw new NotFoundException($e->getMessage(), $e->getCode(), $e);
|
2017-04-26 17:54:53 +03:00
|
|
|
}
|
|
|
|
$cacheItem = $entry ?: [];
|
|
|
|
$cacheItem['instance'] = $file;
|
|
|
|
$this->fileCache->set($serverUrl, $cacheItem);
|
|
|
|
} else {
|
|
|
|
$file = $entry['instance'];
|
|
|
|
}
|
|
|
|
return $file;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* creates the relative server "url" out of the provided path
|
|
|
|
*
|
|
|
|
* @param $path
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function formatPath($path) {
|
|
|
|
$path = trim($path, '/');
|
2019-10-14 16:40:04 +03:00
|
|
|
$docLib = $this->spClient->getDocumentLibrary($this->documentLibrary);
|
2020-05-08 00:34:55 +03:00
|
|
|
$serverUrl = $docLib->getRootFolder()->getProperty(self::SP_PROPERTY_URL);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($path !== '') {
|
2017-04-26 17:54:53 +03:00
|
|
|
$serverUrl .= '/' . $path;
|
|
|
|
}
|
|
|
|
|
|
|
|
$pathParts = explode('/', $serverUrl);
|
|
|
|
$filename = array_pop($pathParts);
|
2021-03-05 15:59:32 +03:00
|
|
|
if ($filename === '.') {
|
2017-04-26 17:54:53 +03:00
|
|
|
// remove /. from the end of the path
|
|
|
|
$serverUrl = mb_substr($serverUrl, 0, mb_strlen($serverUrl) - 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $serverUrl;
|
|
|
|
}
|
|
|
|
}
|