owncloud/core

View on GitHub
lib/private/Share/MailNotifications.php

Summary

Maintainability
D
2 days
Test Coverage
<?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;
    }
}