<?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 McCorkell <robin@mccorkell.me.uk>
 * @author Roeland Jago Douma <rullzer@owncloud.com>
 * @author scolebrook <scolebrook@mac.com>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 * @author Vincent Petry <pvince81@owncloud.com>
 * @author Semih Serhat Karakaya <karakayasemi@itu.edu.tr>
 *
 * @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 OC\Share;

use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Mail\IMailer;
use OCP\ILogger;
use OCP\Defaults;
use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;
use OCP\Util;
use OC\Share\Filters\MailNotificationFilter;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\GenericEvent;

/**
 * Class MailNotifications
 *
 * @package OC\Share
 */
class MailNotifications {
	/** @var IManager */
	private $shareManager;
	/** @var IL10N */
	private $l;
	/** @var IMailer */
	private $mailer;
	/** @var IConfig */
	private $config;
	/** @var Defaults */
	private $defaults;
	/** @var ILogger */
	private $logger;
	/** @var IURLGenerator */
	private $urlGenerator;
	/** @var EventDispatcher */
	private $eventDispatcher;
	/** @var \OCP\Activity\IManager */
	private $activityManager;
	/** @var IRootFolder */
	private $rootFolder;

	/**
	 * @param IManager $shareManager
	 * @param IL10N $l10n
	 * @param IMailer $mailer
	 * @param IConfig $config
	 * @param ILogger $logger
	 * @param Defaults $defaults
	 * @param IURLGenerator $urlGenerator
	 * @param EventDispatcher $eventDispatcher
	 * @param \OCP\Activity\IManager $activityManager
	 */
	public function __construct(
		IManager $shareManager,
		IL10N $l10n,
		IMailer $mailer,
		IConfig $config,
		ILogger $logger,
		Defaults $defaults,
		IURLGenerator $urlGenerator,
		EventDispatcher $eventDispatcher,
		\OCP\Activity\IManager $activityManager,
		IRootFolder $rootFolder
	) {
		$this->shareManager = $shareManager;
		$this->l = $l10n;
		$this->mailer = $mailer;
		$this->config = $config;
		$this->logger = $logger;
		$this->defaults = $defaults;
		$this->urlGenerator = $urlGenerator;
		$this->eventDispatcher = $eventDispatcher;
		$this->activityManager = $activityManager;
		$this->rootFolder = $rootFolder;
	}

	/**
	 * inform users if a file was shared with them
	 *
	 * @param IUser $sender
	 * @param Node shared node
	 * @param string $shareType share type
	 * @param IUser[] $recipientList list of recipients
	 * @return array list of user to whom the mail send operation failed
	 * @throws \OCP\Files\NotFoundException
	 * @throws GenericShareException
	 */
	public function sendInternalShareMail($sender, $node, $shareType, $recipientList): array {
		if ($this->config->getAppValue('core', 'shareapi_allow_mail_notification', 'no') !== 'yes') {
			$message_t = $this->l->t('Internal mail notification for shared files is not allowed');
			throw new GenericShareException($message_t, $message_t, 403);
		}
		$noMail = [];

		foreach ($recipientList as $recipient) {
			$recipientDisplayName = $recipient->getDisplayName();
			$to = $recipient->getEMailAddress();

			if ($to === null || $to === '') {
				$noMail[] = $recipientDisplayName;
				continue;
			}

			$items = $this->shareManager->getSharedWith($recipient->getUID(), $shareType, $node);
			if (\count($items) === 0) {
				$noMail[] = $recipientDisplayName;
				continue;
			}
			/** @var IShare $firstItem */
			$firstItem = $items[0];

			$filename = \trim($firstItem->getTarget(), '/');
			$expiration = null;
			if ($firstItem->getExpirationDate() instanceof \DateTime) {
				try {
					$date = $firstItem->getExpirationDate();
					$expiration = $date->getTimestamp();
				} catch (\Exception $e) {
					$this->logger->error("Couldn't read date: ".$e->getMessage(), ['app' => 'sharing']);
				}
			}

			$link = $this->urlGenerator->linkToRouteAbsolute(
				'files.viewcontroller.showFile',
				['fileId' => $firstItem->getNodeId()]
			);

			$filter = new MailNotificationFilter([
				'link' => $link,
				'file' => $filename,
				'senderDisplayName' => $sender->getDisplayName()
			]);

			$unescapedFilename = $filename;
			$link = $filter->getLink();

			$defaultLang = $this->config->getSystemValue('default_language', 'en');
			$recipientLanguageCode = $this->config->getUserValue($recipient->getUID(), 'core', 'lang', $defaultLang);
			$recipientL10N = \OC::$server->getL10N('lib');
			if ($this->l->getLanguageCode() !== $recipientLanguageCode) {
				$recipientL10N = \OC::$server->getL10N('lib', $recipientLanguageCode);
				$subject = (string)$recipientL10N->t('%s shared »%s« with you', [$filter->getSenderDisplayName(), $unescapedFilename]);
			} else {
				$subject = (string)$this->l->t('%s shared »%s« with you', [$filter->getSenderDisplayName(), $unescapedFilename]);
			}

			list($htmlBody, $textBody) = $this->createMailBody(
				$unescapedFilename,
				$link,
				$expiration,
				$filter->getSenderDisplayName(),
				null,
				'internal',
				$recipientL10N
			);
			$replyTo = $this->getReplyTo($sender->getEMailAddress());

			// send it out now
			try {
				$message = $this->mailer->createMessage();
				$message->setSubject($subject);
				$message->setTo([$to => $recipientDisplayName]);
				$message->setHtmlBody($htmlBody);
				$message->setPlainBody($textBody);
				$message->setFrom($this->getFrom($this->l, $filter->getSenderDisplayName()));
				$message->setReplyTo([$replyTo]);

				$this->mailer->send($message);
			} catch (\Exception $e) {
				$this->logger->error("Can't send mail to inform the user about an internal share: ".$e->getMessage(), ['app' => 'sharing']);
				$noMail[] = $recipientDisplayName;
			}
		}

		return $noMail;
	}

	/**
	 *
	 * @param IUser $sender
	 * @param string[] $recipients recipient email addresses
	 * @param string $link the share link
	 * @param string $personalNote sender note
	 * @throws GenericShareException
	 * @throws NotFoundException
	 * @return string[] $result of failed recipients
	 */
	public function sendLinkShareMail($sender, $recipients, $link, $personalNote = null) {
		if ($this->config->getAppValue('core', 'shareapi_allow_public_notification', 'no') !== 'yes') {
			$message_t = $this->l->t('Public link mail notification is not allowed');
			throw new GenericShareException($message_t, $message_t, 403);
		}
		$recipientsAsString = \implode(', ', $recipients);
		$currentUser = $sender->getUID();
		$notificationLang = $this->config->getAppValue(
			'core',
			'shareapi_public_notification_lang',
			null
		);
		$linkParts = \explode('/', $link);
		$token = \array_pop($linkParts);
		try {
			$share = $this->shareManager->getShareByToken($token);
			if ($share->getShareOwner() !== $currentUser && $share->getSharedBy() !== $currentUser) {
				return $recipients;
			}
		} catch (ShareNotFound $e) {
			return $recipients;
		}
		$l10n = \OC::$server->getL10N('lib', $notificationLang);
		$expirationDate = null;
		if ($share->getExpirationDate()) {
			$expirationDate = $share->getExpirationDate()->getTimestamp();
		}
		$shareNode = $share->getNode();
		$filter = new MailNotificationFilter([
			'link' => $link,
			'file' => $shareNode->getName(),
			'expirationDate' => $expirationDate,
			'personalNote' => $personalNote,
			'senderDisplayName' => $sender->getDisplayName()
		]);
		$subject = (string)$l10n->t('%s shared »%s« with you', [$filter->getSenderDisplayName(), $filter->getFile(true)]);
		list($htmlBody, $textBody) = $this->createMailBody(
			$filter->getFile(),
			$filter->getLink(),
			$filter->getExpirationDate(),
			$filter->getSenderDisplayName(),
			$filter->getPersonalNote(),
			'',
			$l10n
		);
		$from = $this->getFrom($l10n, $filter->getSenderDisplayName());
		$replyTo = $this->getReplyTo($sender->getEMailAddress());

		$event = new GenericEvent(null, ['link' => $link, 'to' => $recipientsAsString]);
		$this->eventDispatcher->dispatch($event, 'share.sendmail');
		$failedRecipients =  $this->sendLinkShareMailFromBody($recipients, $subject, $htmlBody, $textBody, $from, $replyTo);
		if (empty($failedRecipients)) {
			$event = $this->activityManager->generateEvent();
			// it can be a re-share, get actual share file node
			$userFolder = $this->rootFolder->getUserFolder($sender->getUID());
			$shareFileNodes = $userFolder->getById($shareNode->getId(), true);
			$shareFileNode = $shareFileNodes[0] ?? null;
			$path = $userFolder->getRelativePath($shareFileNode->getPath());
			$event->setApp(\OCA\Files_Sharing\Activity::FILES_SHARING_APP)
				->setType(\OCA\Files_Sharing\Activity::TYPE_SHARED)
				->setAuthor($currentUser)
				->setAffectedUser($currentUser)
				->setObject('files', $share->getNodeId(), $path)
				->setSubject(\OCA\Files_Sharing\Activity::SUBJECT_SHARED_EMAIL, [$path, $recipientsAsString]);
			$this->activityManager->publish($event);
		}
		return $failedRecipients;
	}

	/**
	 * inform recipients about public link share
	 *
	 * @param string[] $recipients recipient list
	 * @param string $subject the shared file
	 * @param string $htmlBody the public link
	 * @param string $textBody the public link
	 * @param string[] $from From field of the mail
	 * @param string $replyTo sender user e-mail address
	 *
	 * @return string[] $result of failed recipients
	 */
	public function sendLinkShareMailFromBody($recipients, $subject, $htmlBody, $textBody, $from, $replyTo) {
		$failedRecipients = [];
		try {
			/**
			 * Send separate mail for all recipients
			 */
			foreach ($recipients as $recipient) {
				$message = $this->mailer->createMessage();
				$message->setSubject($subject);
				$message->setTo([$recipient]);
				$message->setHtmlBody($htmlBody);
				$message->setPlainBody($textBody);
				$message->setFrom($from);
				$message->setReplyTo([$replyTo]);
				$result = $this->mailer->send($message);
				if (!empty($result)) {
					$failedRecipients [] = $recipient;
				}
			}
		} catch (\Exception $e) {
			$allRecipients = \implode(',', $recipients);
			$this->logger->error(
				"Can't send mail with public link to $allRecipients: ".$e->getMessage(),
				['app' => 'sharing']
			);
			return $recipients;
		}
		return $failedRecipients;
	}

	/**
	 * create mail body for plain text and html mail
	 *
	 * @param string $filename the shared file
	 * @param string $link link to the shared file
	 * @param int $expiration expiration date (timestamp)
	 * @param string $senderDisplayName display name of the sender user
	 * @param string $personalNote optional personal note
	 * @param string $prefix prefix of mail template files
	 * @param IL10N|null $overrideL10n
	 *
	 * @return array an array of the html mail body and the plain text mail body
	 */
	protected function createMailBody(
		$filename,
		$link,
		$expiration,
		$senderDisplayName,
		$personalNote = null,
		$prefix = '',
		$overrideL10n = null
	) {
		$l10n = $overrideL10n === null ? $this->l : $overrideL10n;
		$formattedDate = $expiration ?  $l10n->l('date', $expiration) : null;

		$html = new \OC_Template(
			'core',
			$prefix . 'mail',
			'',
			true,
			$l10n->getLanguageCode()
		);
		$html->assign('link', $link);
		$html->assign('user_displayname', $senderDisplayName);
		$html->assign('filename', $filename);
		$html->assign('expiration', $formattedDate);
		if ($personalNote !== null && $personalNote !== '') {
			$html->assign('personal_note', \nl2br($personalNote));
		}
		$htmlMail = $html->fetchPage();

		$plainText = new \OC_Template(
			'core',
			$prefix . 'altmail',
			'',
			true,
			$l10n->getLanguageCode()
		);
		$plainText->assign('link', $link);
		$plainText->assign('user_displayname', $senderDisplayName);
		$plainText->assign('filename', $filename);
		$plainText->assign('expiration', $formattedDate);
		if ($personalNote !== null && $personalNote !== '') {
			$plainText->assign('personal_note', $personalNote);
		}
		$plainTextMail = $plainText->fetchPage();

		return [$htmlMail, $plainTextMail];
	}

	/**
	 * Get default sender details
	 *
	 * @param IL10N $l10n
	 * @param string $senderDisplayName
	 *
	 * @return string[]
	 */
	protected function getFrom($l10n, $senderDisplayName) {
		$removesenderdisplayname = $this->config->getSystemValue('remove_sender_display_name', false);
		if ($removesenderdisplayname) {
			return [
					Util::getDefaultEmailAddress('sharing-noreply') => (string) $l10n->t(
						'%s',
						[
							$this->defaults->getName()
						]
					)
			];
		}
		return [
			Util::getDefaultEmailAddress('sharing-noreply') => (string) $l10n->t(
				'%s via %s',
				[
					$senderDisplayName,
					$this->defaults->getName()
				]
			)
		];
	}

	/**
	 * @param string $senderMailAddress
	 * @return string
	 */
	protected function getReplyTo($senderMailAddress) {
		if (empty($senderMailAddress)) {
			return Util::getDefaultEmailAddress('sharing-noreply');
		}
		return $senderMailAddress;
	}
}
