<?php
/**
 * @author Björn Schießle <bjoern@schiessle.org>
 * @author Joas Schilling <coding@schilljs.com>
 * @author Lukas Reschke <lukas@statuscode.ch>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Robin Appelman <icewind@owncloud.com>
 *
 * @copyright Copyright (c) 2018, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * 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, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OCA\Files_Sharing;

use OC\Files\Filesystem;
use OCP\IURLGenerator;
use OCP\Files\IRootFolder;
use OCP\IUserSession;
use Symfony\Component\EventDispatcher\EventDispatcher;
use OCP\Share\IShare;
use Symfony\Component\EventDispatcher\GenericEvent;
use OCA\Files_Sharing\Service\NotificationPublisher;
use OCP\Activity\IManager as ActivityManager;

class Hooks {
	/**
	 * @var IURLGenerator
	 */
	private $urlGenerator;

	/**
	 * @var IRootFolder
	 */
	private $rootFolder;

	/**
	 * @var IUserSession|null
	 */
	private $userSession;

	/**
	 * @var EventDispatcher
	 */
	private $eventDispatcher;

	/**
	 * @var \OCP\Share\IManager
	 */
	private $shareManager;

	/**
	 * @var NotificationPublisher
	 */
	private $notificationPublisher;

	/**
	 * @var SharingAllowlist
	 */
	private $sharingAllowlist;

	/**
	 * @var ActivityManager
	 */
	private $activityManager;

	/**
	 * Hooks constructor.
	 *
	 * @param IRootFolder $rootFolder
	 * @param IURLGenerator $urlGenerator
	 * @param EventDispatcher $eventDispatcher
	 * @param \OCP\Share\IManager $shareManager
	 * @param NotificationPublisher $notificationPublisher
	 * @param ActivityManager $activityManager
	 * @param SharingAllowlist $sharingAllowlist
	 * @param IUserSession|null $userSession
	 */
	public function __construct(
		IRootFolder $rootFolder,
		IUrlGenerator $urlGenerator,
		EventDispatcher $eventDispatcher,
		\OCP\Share\IManager $shareManager,
		NotificationPublisher $notificationPublisher,
		ActivityManager $activityManager,
		SharingAllowlist $sharingAllowlist,
		$userSession
	) {
		$this->userSession = $userSession;
		$this->rootFolder = $rootFolder;
		$this->urlGenerator = $urlGenerator;
		$this->eventDispatcher = $eventDispatcher;
		$this->shareManager = $shareManager;
		$this->notificationPublisher = $notificationPublisher;
		$this->activityManager = $activityManager;
		$this->sharingAllowlist= $sharingAllowlist;
	}

	public static function deleteUser($params) {
		$manager = new External\Manager(
			\OC::$server->getDatabaseConnection(),
			\OC\Files\Filesystem::getMountManager(),
			\OC\Files\Filesystem::getLoader(),
			\OC::$server->getNotificationManager(),
			\OC::$server->getEventDispatcher(),
			$params['uid']
		);

		$manager->removeUserShares($params['uid']);
	}

	public static function unshareChildren($params) {
		$path = Filesystem::getView()->getAbsolutePath($params['path']);
		$view = new \OC\Files\View('/');

		// find share mount points within $path and unmount them
		$mountManager = \OC\Files\Filesystem::getMountManager();
		$mountedShares = $mountManager->findIn($path);
		foreach ($mountedShares as $mount) {
			if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
				$mountPoint = $mount->getMountPoint();
				$view->unlink($mountPoint);
			}
		}
	}

	public function registerListeners() {
		$this->eventDispatcher->addListener(
			'files.resolvePrivateLink',
			function (GenericEvent $event) {
				$uid = $event->getArgument('uid');
				$fileId = $event->getArgument('fileid');

				$link = $this->resolvePrivateLink($uid, $fileId);

				if ($link !== null) {
					$event->setArgument('resolvedWebLink', $link);
				}
			}
		);

		$this->eventDispatcher->addListener(
			'share.afterCreate',
			function (GenericEvent $event) {
				$shareObject = $event->getArgument('shareObject');
				$this->notificationPublisher->sendNotification($shareObject);
			}
		);

		$this->eventDispatcher->addListener(
			'share.afterDelete',
			function (GenericEvent $event) {
				$shareObject = $event->getArgument('shareObject');
				$this->notificationPublisher->discardNotification($shareObject);
			}
		);

		$this->eventDispatcher->addListener(
			'file.beforeGetDirect',
			function (GenericEvent $event) {
				$pathsToCheck[] = $event->getArgument('path');

				// Check only for user/group shares. Don't restrict e.g. share links
				if ($uid = $this->getCurrentUserUid()) {
					$viewOnlyHandler = new ViewOnly(
						$this->rootFolder->getUserFolder($uid)
					);
					if (!$viewOnlyHandler->check($pathsToCheck)) {
						$event->setArgument('errorMessage', 'Access to this resource or one of its sub-items has been denied.');
					}
				}
			}
		);

		$this->eventDispatcher->addListener(
			'file.beforeCreateZip',
			function (GenericEvent $event) {
				$dir = $event->getArgument('dir');
				$files = $event->getArgument('files');

				$pathsToCheck = [];
				if (\is_array($files)) {
					foreach ($files as $file) {
						$pathsToCheck[] = $dir . '/' . $file;
					}
				} elseif (\is_string($files)) {
					$pathsToCheck[] = $dir . '/' . $files;
				}

				// Check only for user/group shares. Don't restrict e.g. share links
				$uid = $this->getCurrentUserUid();
				if ($uid !== null) {
					$viewOnlyHandler = new ViewOnly(
						$this->rootFolder->getUserFolder($uid)
					);
					if (!$viewOnlyHandler->check($pathsToCheck)) {
						$event->setArgument('errorMessage', 'Access to this resource or one of its sub-items has been denied.');
						$event->setArgument('run', false);
					} else {
						$event->setArgument('run', true);
					}
				} else {
					$event->setArgument('run', true);
				}
			}
		);

		$this->eventDispatcher->addListener(
			'fromself.unshare',
			function (GenericEvent $event) {
				$activityEvent = $this->activityManager->generateEvent();
				$activityEvent->setApp(Activity::FILES_SHARING_APP)
					->setType(Activity::TYPE_SHARED)
					->setAffectedUser($event->getArgument('shareRecipient'))
					->setSubject(
						Activity::SUBJECT_UNSHARED_FROM_SELF,
						[$event->getArgument('recipientPath'), $event->getArgument('shareOwner')]
					);
				$this->activityManager->publish($activityEvent);
			}
		);

		$this->eventDispatcher->addListener('group.postDelete', function ($event) {
			$groupId = $event->getSubject()->getGID();
			$groupsAllowlist =  $this->sharingAllowlist->getPublicShareSharersGroupsAllowlist();

			if (\in_array($groupId, $groupsAllowlist)) {
				$this->sharingAllowlist->setPublicShareSharersGroupsAllowlist(array_diff($groupsAllowlist, [$groupId]));
			}
		});
	}

	private function getCurrentUserUid() {
		// User session can be null when installing oc and Hook is triggered, or
		// user is not logged in
		if ($this->userSession && $this->userSession->isLoggedIn()) {
			return $this->userSession->getUser()->getUID();
		}
		return null;
	}

	private function filterSharesByFileId($shares, $fileId) {
		return \array_filter($shares, function (IShare $share) use ($fileId) {
			return \strval($share->getNodeId()) === \strval($fileId);
		});
	}

	/**
	 * Resolves web URL that points to the "shared with you" view of the given file
	 *
	 * @param string $uid user id
	 * @param string $fileId file id
	 * @return string|null view URL or null if the file is not found or not accessible
	 */
	private function resolvePrivateLink($uid, $fileId) {
		// this is only here to verify that there is indeed such share
		$shares = $this->filterSharesByFileId($this->shareManager->getSharedWith($uid, \OCP\Share::SHARE_TYPE_USER), $fileId);
		if (empty($shares)) {
			$shares = $this->filterSharesByFileId($this->shareManager->getSharedWith($uid, \OCP\Share::SHARE_TYPE_GROUP), $fileId);
		}

		// if share exists, redirect to view
		if (!empty($shares)) {
			$params['view'] = 'sharingin';
			// scroll to the entry
			$params['scrollto'] = $fileId;
			return $this->urlGenerator->linkToRoute('files.view.index', $params);
		}

		return null;
	}

	public static function extendJsConfig($array) {
		$sharingAllowlist = new SharingAllowlist(
			\OC::$server->getConfig(),
			\OC::$server->getGroupManager()
		);

		$array['array']['oc_appconfig']['files_sharing'] = [
			'publicShareSharersGroupsAllowlist' => $sharingAllowlist->getPublicShareSharersGroupsAllowlist(),
			'publicShareSharersGroupsAllowlistEnabled' => $sharingAllowlist->isPublicShareSharersGroupsAllowlistEnabled(),
			'showPublicLinkQuickAction' => \OC::$server->getConfig()->getSystemValue('sharing.showPublicLinkQuickAction', false),
		];

		return $array;
	}
}
