oaeproject/Hilary

View on GitHub
packages/oae-activity/emailTemplates/mail.shared.js

Summary

Maintainability
C
1 day
Test Coverage
A
96%
/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

import _ from 'underscore';

import * as AuthzUtil from 'oae-authz/lib/util.js';

import { ActivityConstants } from 'oae-activity/lib/constants.js';

/**
 * Determine if the given string is an email
 *
 * @param  {String}     email   The string to check
 * @return {Boolean}            Whether or not the string is an email address
 */
const { isEmail } = AuthzUtil;

/**
 * Get an appropriate message to put in the email subject header.
 *
 * For an email invitation, the subject always contains the names of people who are trying to
 * invite them so it better legitimizes the email:
 *
 *  e.g., "Nicolaas Matthijs, Branden Visser and 2 others have invited you to collaborate"
 *
 * For an existing user account, we make simpler subjects dependent on their email preference:
 *
 *  - When an immediate email is sent with a single activity: `<activitySummary>`
 *  - When an immediate email is sent with multiple actors: `New activity is waiting for you`
 *  - When a daily email is sent: `Today's activity summary`
 *  - When a weekly email is sent: `Last week's activity summary`
 *
 * @param  {Object}         util            The template utility that can be used to encode HTML, translate keys, etc...
 * @param  {Resource}       recipient       The email preference for which to generate an email summary
 * @param  {Activity[]}     activitities    The activities for which to generate a summary
 * @return {String}                         An appropriate summary given the recipient information and activities
 */
const getEmailSubject = (util, recipient, activities) => {
  if (isEmail(recipient.id)) {
    // If this is an invitation to a user who doesn't have an account yet, we speak to them
    // a little bit differently because we have to legitimize our email quicker (i.e., in the
    // subject)
    if (_.size(activities) === 1) {
      return util.i18n.translate(activities[0].summary.i18nKey, activities[0].summary.i18nArguments);
    }

    const actors = _getAllEntities(activities, 'actor');
    if (_.size(actors) === 1) {
      return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_1__', {
        actor1DisplayName: actors[0].displayName
      });
    }

    if (_.size(actors) === 2) {
      return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_2__', {
        actor1DisplayName: actors[0].displayName,
        actor2DisplayName: actors[1].displayName
      });
    }

    return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_3+__', {
      actor1DisplayName: actors[0].displayName,
      numActorsMinus1: actors.length - 1
    });
  }

  // If the user already has an account, we can generalize a bit on what has happened based
  // on their email preference and number of activities
  const { emailPreference } = recipient;
  let message = util.i18n.translate('__MSG__RECENT_ACTIVITY__');
  switch (emailPreference) {
    case 'immediate': {
      message =
        activities.length === 1
          ? util.i18n.translate(activities[0].summary.i18nKey, activities[0].summary.i18nArguments)
          : util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_MULTIPLE__');
      break;
    }

    case 'daily': {
      message = util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_DAILY__');

      break;
    }

    case 'weekly': {
      message = util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_WEEKLY__');

      break;
    }
    // No default
  }

  return message;
};

/**
 * Get an appropariate summary given a user's email preference and a set of activities
 *
 * The options are dependent on their email preference:
 *     -   When an immediate email is sent with a single actor and a single activity: `<displayName> has been active recently`
 *     -   When an immediate email is sent with a single actor and multiple activities: `<displayName> has been very active recently`
 *     -   When an immediate email is sent with multiple actors: `New activity is waiting for you`
 *     -   When a daily email is sent: `Here's your summary of today's activity`
 *     -   When a weekly email is sent: `Here's your summary of last week's activity`
 *
 * @param  {Object}         util            The template utility that can be used to encode HTML, translate keys, etc
 * @param  {Resource}       recipient       The email preference for which to generate an email summary
 * @param  {Activity[]}     activitities    The activities for which to generate a summary
 * @param  {String}         baseUrl         The base url that each link should have as a prefix
 * @return {String}                         An appropriate summary given the user's email preference and given activities
 */
const getEmailSummary = function (util, recipient, activities, baseUrl) {
  if (isEmail(recipient.id)) {
    // For an email invitation, we always use the subject invitation language, even for the
    // summary
    const actors = _getAllEntities(activities, 'actor');
    if (_.size(actors) === 1) {
      return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_1__', {
        actor1DisplayName: actors[0].displayName
      });
    }

    if (_.size(actors) === 2) {
      return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_2__', {
        actor1DisplayName: actors[0].displayName,
        actor2DisplayName: actors[1].displayName
      });
    }

    return util.i18n.translate('__MSG__ACTIVITY_EMAIL_SUBJECT_INVITE_ACTOR_3+__', {
      actor1DisplayName: actors[0].displayName,
      numActorsMinus1: actors.length - 1
    });
  }

  const { emailPreference } = recipient;
  if (emailPreference === 'immediate') {
    // Determine if there was a single or multiple actors
    let isSingleActor = true;
    let actor = null;
    _.each(activities, (activity) => {
      if (actor !== null && actor['oae:id'] !== activity.originalActivity.actor['oae:id']) {
        isSingleActor = false;
      }

      actor = activity.originalActivity.actor;
    });

    if (isSingleActor && actor.objectType !== 'collection') {
      let key = '__MSG__ACTIVITY_EMAIL_SUMMARY_IMMEDIATE_SINGLE_ACTOR_SINGLE_ACTIVITY__';
      if (activities.length > 1) {
        key = '__MSG__ACTIVITY_EMAIL_SUMMARY_IMMEDIATE_SINGLE_ACTOR_MULTIPLE_ACTIVITIES__';
      }

      // If the profile path was set, it indicates that we have access to view the user, therefore
      // we should display a link. If not specified, we should show plain-text
      const url = util.url.ensureAbsoluteLink(actor['oae:profilePath'], baseUrl);
      let actorLink = '<span>' + util.html.encodeForHTML(actor.displayName) + '</span>';
      if (url) {
        actorLink = '<a href="' + url + '">' + util.html.encodeForHTML(actor.displayName) + '</a>';
      }

      const summary = util.i18n.translate(key, { actorLink });

      return util.url.ensureAbsoluteLinks(summary, baseUrl);
    }

    return '__MSG__ACTIVITY_EMAIL_SUMMARY_IMMEDIATE_MULTIPLE_ACTORS__';
  }

  if (emailPreference === 'daily') {
    return '__MSG__ACTIVITY_EMAIL_SUMMARY_DAILY__';
  }

  if (emailPreference === 'weekly') {
    return '__MSG__ACTIVITY_EMAIL_SUMMARY_WEEKLY__';
  }
};

/*!
 * Get all entities of the entity type associated to all given activities
 *
 * @param  {Activity[]}         activities  The activities from which to get the entities
 * @param  {String}             entityType  The entity type (actor, object or target)
 * @return {ActivityEntity[]}               All entities of the specified type for all activities
 */
const _getAllEntities = function (activities, entityType) {
  const seenIds = {};
  const entities = [];
  _.each(activities, (activity) => {
    const entity = activity.originalActivity[entityType];

    // If the entity is a collection, collect all of them
    if (entity.objectType === 'collection') {
      _.each(entity[ActivityConstants.properties.OAE_COLLECTION], (entity) => {
        const entityId = entity[ActivityConstants.properties.OAE_ID];
        if (!seenIds[entityId]) {
          seenIds[entityId] = true;
          entities.push(entity);
        }
      });
    } else {
      const entityId = entity[ActivityConstants.properties.OAE_ID];
      if (!seenIds[entityId]) {
        seenIds[entityId] = true;
        entities.push(entity);
      }
    }
  });

  return entities;
};

export { isEmail, getEmailSubject, getEmailSummary };