Show:

File: platform/plugins/Streams/classes/Streams/Invite.php

<?php
/**
 * @module Streams
 */
/**
 * Class representing 'Invite' rows in the 'Streams' database
 * You can create an object of this class either to
 * access its non-static methods, or to actually
 * represent a invite row in the Streams database.
 *
 * @class Streams_Invite
 * @extends Base_Streams_Invite
 */
class Streams_Invite extends Base_Streams_Invite
{
	/**
	 * The setUp() method is called the first time
	 * an object of this class is constructed.
	 * @method setUp
	 */
	function setUp()
	{
		parent::setUp();
	}
	
	/**
	 * Get the invites that have been left for one or more users in some stream.
	 * This is useful for auto-accepting them or presenting the user with a
	 * button to accept the invite when the stream is rendered on their client.
	 * @method forStream
	 * @static
	 * @param {string} $publisherId
	 * @param {string|array|Db_Expression} $streamName
	 * @param {string|array|Db_Expression} $userId
	 * @return {array|Streams_Invite|null} an array of Streams_Invite objects,
	 *  or if $streamName and $userId are strings, just returns Streams_Invite or null.
	 */
	static function forStream($publisherId, $streamName, $userId)
	{
		$rows = Streams_Invite::select()->where(
			compact('publisherId', 'streamName', 'userId')
		)->fetchDbRows();
		if (!is_string($streamName) || !is_string($userId)) {
			return $rows;
		}
		$row = reset($rows);
		return $row ? $row : null;
	}
	
	/**
	 * Accept the invite and set up user's access levels
	 * If invite was already accepted, this function simply returns null
	 * @method accept
	 * @param {array} $options Things to do with the logged-in user
	 * @param {boolean} [$options.subscribe=false]
	 *  Whether to auto-subscribe them to the stream if not already subscribed.
	 *  If the subscribe() call throws an exception, it is swallowed.
	 * @param {boolean} [$options.access=true]
	 *  Whether to upgrade their access to the stream
	 * @return {Streams_Participant|false|null}
	 * @throws {Users_Exception_NotLoggedIn}
	 *  If the $this->userId is false and user is not logged in
	 */
	function accept($options = array())
	{
		if (!isset($options['access'])) {
			$options['access'] = true;
		}
		
		$userId = $this->userId ? $this->userId : Users::loggedInUser(true)->id;
		
		if (!$this->userId) {
			$invited = new Streams_Invited();
			$invited->token = $this->token;
			$invited->userId = $userId;
			if (!$invited->retrieve() or $invited->state !== 'accepted') {
				$invited2 = new Streams_Invited();
				$invited2->token = $invited->token;
				$invited2->userId = $invited->userId;
				$invited2->state = 'accepted';
				$invited2->expireTime = $this->expireTime;
				$invited2->save();
			}
		}
		
		/**
		 * @event Streams/invite {before}
		 * @param {Streams_Invite} stream
		 * @param {Users_User} user
		 */
		$invite = $this;
		if (Q::event("Streams/invite/accept", compact('invite'), 'before') === false) {
			return false;
		}

		$this->state = 'accepted';
		if (!$this->save()) {
			return false;
		}
		
		$stream = Streams::fetchOne(
			$this->userId, $this->publisherId, $this->streamName, true
		);
	
		$participant = new Streams_Participant();
		$participant->publisherId = $this->publisherId; // shouldn't change
		$participant->streamName = $this->streamName; // shouldn't change
		$participant->streamType = $stream->type; // shouldn't change
		$participant->userId = $userId; // shouldn't change
		$participant->state = 'participating'; // since invite was accepted, user has begun participating in the stream
		$participant->save(true);
		
		if (!empty($options['access'])) {
			// Check if the users exist
			$invitedUser = Users_User::fetch($userId, true);
			$byUser = Users_User::fetch($this->invitingUserId, true);
			// Set up the objects
			$byStream = Streams::fetchOne(
				$this->invitingUserId, $this->publisherId, $this->streamName, true
			);
			$access = new Streams_Access();
			$access->publisherId = $byStream->publisherId;
			$access->streamName = $byStream->name;
			$access->ofUserId = $this->userId;
			// Check if we should update the access
			$shouldUpdateAccess = false;
			foreach (array('readLevel', 'writeLevel', 'adminLevel') as $level_type) {
				$access->$level_type = -1;
				if (empty($this->$level_type)) {
					continue;
				}
				// Give access level from the invite.
				// However, if inviting user has a lower access level now,
				// then give that level instead, unless it is lower than
				// what the invited user would have had otherwise.
				$min = min($this->$level_type, $byStream->get($level_type, 0));
				if ($min > $stream->get($level_type, 0)) {
					$access->$level_type = $min;
					$shouldUpdateAccess = true;
				}
			}
			if (!empty($access->permissions)) {
				// Grant permissions originally offered in the invite,
				// up to and including what the inviting user currently has.
				$permissions = Q::json_decode($access->permissions);
				$byPermissions = $byStream->get('permissions', array());
				foreach ($permissions as $permission) {
					if (in_array($permission, $byPermissions)) {
						$access->addPermission($permission);
					}
				}
			}
			if ($shouldUpdateAccess) {
				$access->save(true);
			}
		}
		
		if (!empty($options['subscribe']) and !$stream->subscription($userId)) {
			try {
				$stream->subscribe();
			} catch (Exception $e) {
				// Swallow this exception. If the caller wanted to catch
				// this exception, hey could have written this code block themselves.
			}
		}
		
		/**
		 * @event Streams/invite {after}
		 * @param {Streams_Invite} stream
		 * @param {Users_User} user
		 */
		Q::event("Streams/invite/accept", compact('invite', 'participant'), 'after');

		Streams_Message::post($userId, $this->publisherId, $this->streamName, array(
			'type' => 'Streams/invite/accept',
			'instructions' => Q::take($this->fields, array(
				'token', 'userId', 'invitingUserId', 'appUrl',
				'readLevel', 'writeLevel', 'adminLevel', 'permissions',
				'ofUserId', 'ofContactLabel'
			))
		), true);

		return true;
	}
	
	function decline()
	{
		$this->state = 'declined';
		$this->save();
	}

	/**
	 * Retrieves invite
	 * @method getInvite
	 * @static
	 * @param {string} $token
	 * @param {boolean} $throwIfMissing
	 * @return {Streams_Invite|null}
	 */

	static function fromToken ($token, $throwIfMissing = false) {
		if (empty($token)) {
			return null;
		}
		if (!empty(self::$cache['getInvite'][$token])) {
			return self::$cache['getInvite'][$token];
		}
		$invite = new Streams_Invite();
		$invite->token = $token;
		if (!$invite->retrieve()) {
			if ($throwIfMissing) {
				throw new Q_Exception_MissingRow(array(
					'table' => 'Invite',
					'criteria' => Q::json_encode(compact('token'))
				));
			}
			return null;
		}
		self::$cache['getInvite'][$token] = $invite;
		return $invite;
	}

	/**
	 * Assigns unique id to 'token' field if not set
	 * Saves corresponding row in Streams_Invited table
	 * Inserting a new invite affects corresponding row in Streams_Participant table
	 * @method beforeSave
	 * @param {array} $modifiedFields
	 *	The fields that have been modified
	 * @return {array}
	 */
	function beforeSave($modifiedFields)
	{
		if (!$this->retrieved) {
			if (!isset($modifiedFields['token'])) {
				$this->token = $modifiedFields['token'] = self::db()->uniqueId(
					self::table(),
					'token',
					null,
					array(
						'length' => Q_Config::get('Streams', 'invites', 'tokens', 'length', 16),
						'characters' => Q_Config::get('Streams', 'invites', 'tokens', 'characters', 'abcdefghijklmnopqrstuvwxyz')
					)
				);
			}
			if (!empty($modifiedFields['userId'])) {
				$p = new Streams_Participant();
				$p->publisherId = $modifiedFields['publisherId'];
				$p->streamName = $modifiedFields['streamName'];
				$p->userId = $modifiedFields['userId'];
				$p->state = 'invited';
				$p->save(true);
			}
		}

		if (!empty($modifiedFields['userId'])) {
			if (array_key_exists('state', $modifiedFields)
			or array_key_exists('expireTime', $modifiedFields)) {
				$invited = new Streams_Invited();
				$invited->userId = $this->userId; // shouldn't change
				$invited->token = $this->token; // shouldn't change
				if (array_key_exists('state', $modifiedFields)) {
					$invited->state = $modifiedFields['state'];
				}
				if (array_key_exists('expireTime', $modifiedFields)) {
					$invited->expireTime = $modifiedFields['expireTime'];
				}
				$invited->save(true);
			}
		}
		
		return parent::beforeSave($modifiedFields);
	}
	
	/**
	 * Also removes counterpart row in Streams_Invited table
	 * @method beforeSave
	 * @param {array} $pk
	 *	The primary key fields
	 * @return {boolean}
	 */
	function beforeRemove($pk)
	{
		if ($this->userId) {
			$invited = new Streams_Invited();
			$invited->userId = $this->userId;
			$invited->token = $this->token;
			$invited->remove();
		}
		return true;
	}
	
	static protected $cache = array();

	/* * * */
	/**
	 * Implements the __set_state method, so it can work with
	 * with var_export and be re-imported successfully.
	 * @method __set_state
	 * @param {array} $array
	 * @return {Streams_Invite} Class instance
	 */
	static function __set_state(array $array) {
		$result = new Streams_Invite();
		foreach($array as $k => $v)
			$result->$k = $v;
		return $result;
	}
};