/** * Add contact with one or more labels * @method addContact * @static * @param {string} $userId * The id of the user whose contact will be added * @param {string|array} $label * The label of the contact. This can be a string or an array of strings, in which case * multiple contact rows are saved. * @param {string} $contactUserId * The id of the user who is the contact * @param {string} [$nickname=''] * Optional nickname to assign to the contact * @param {string} [$asUserId=null] The user to do this operation as. * Defaults to the logged-in user. Pass false to skip access checks. * @throws {Q_Exception_RequiredField} * if $label is missing * @return {array} Array of contacts that are saved */ static function addContact($userId, $label, $contactUserId, $nickname = '', $asUserId = null) { foreach (array('userId', 'label', 'contactUserId') as $field) { if (empty(${$field})) { throw new Q_Exception_RequiredField($field); } } Users::canManageContacts($asUserId, $userId, $label, true); Users_User::fetch($userId, true); Users_User::fetch($contactUserId, true); $labels = is_array($label) ? $label : array($label); // Insert the contacts one by one $contacts = array(); foreach ($labels as $l) { $contact = new Users_Contact(); $contact->userId = $userId; $contact->label = $l; $contact->contactUserId = $contactUserId; if (isset($nickname)) { $contact->nickname = $nickname; } $contact->save(true); $contacts[] = $contact; } /** * @event Users/Contact/addContact {after} * @param {string} contactUserId * @param {string} label * @param {array} contacts */ Q::event('Users/Contact/addContact', compact('contactUserId', 'label', 'contacts'), 'after'); return $contacts; }
/** * This tool renders a user avatar * * @param {array} $options An associative array of parameters, containing: * @param {boolean} [$options.userId] * "userId" => The user's id. Defaults to id of the logged-in user, if any. * @param {boolean} [$options.icon] * "icon" => Optional. Render icon before the username. * @param {boolean} [$options.iconAttributes] * "iconAttributes" => Optional. Array of attributes to render for the icon. * @param {boolean} [$options.editable] * "editable" => Optional. Whether to provide an interface for editing the user's info. Can be array containing "icon", "name". * @param {array} [$options.inplaces] Additional fields to pass to the child Streams/inplace tools, if any * @param {boolean} [$options.renderOnClient] * If true, only the html container is rendered, so the client will do the rest. */ function Users_avatar_tool($options) { $defaults = array('icon' => false, 'editable' => false); $options = array_merge($defaults, $options); if (empty($options['userId'])) { $user = Users::loggedInUser(); $options['userId'] = $user->id; } else { $user = Users_User::fetch($options['userId']); } Q_Response::addStylesheet('plugins/Q/css/Q.css'); Q_Response::setToolOptions($options); if (!empty($options['renderOnClient'])) { return ''; } if (!$user) { return ''; } $user->addPreloaded(); $p = $options; $p['userId'] = $user->id; Q_Response::setToolOptions($p); $result = ''; $icon = $options['icon']; if ($icon) { if ($icon === true) { $icon = Q_Config::get('Users', 'icon', 'defaultSize', 40); } $attributes = isset($options['iconAttributes']) ? $options['iconAttributes'] : array(); $attributes['class'] = isset($attributes['class']) ? $attributes['class'] . ' Users_avatar_icon' : 'Users_avatar_icon'; $result .= Q_Html::img($user->iconUrl($icon), 'user icon', $attributes); } $result .= '<span class="Users_avatar_name">' . $user->username . '</span>'; return $result; }
function Users_identifier_post() { $userId = Q::ifset($_REQUEST, 'userId', null); if (isset($userId)) { $user = Users_User::fetch($userId, true); if ($user->emailAddress or $user->mobileNumber) { throw new Q_Exception("This user is already able to log in and set their own email and mobile number."); } } else { $user = Users::loggedInUser(true); } $app = Q_Config::expect('Q', 'app'); $fields = array(); $identifier = Users::requestedIdentifier($type); if (!$type) { throw new Q_Exception("a valid email address or mobile number is required", array('identifier', 'mobileNumber', 'emailAddress')); } if ($type === 'email') { $subject = Q_Config::get('Users', 'transactional', 'identifier', 'subject', "Welcome! Verify your email address."); $view = Q_Config::get('Users', 'transactional', 'identifier', 'body', 'Users/email/addEmail.php'); $user->addEmail($identifier, $subject, $view, array(), array('html' => true)); } else { if ($type === 'mobile') { $view = Q_Config::get('Users', 'transactional', 'identifier', 'sms', 'Users/sms/addMobile.php'); $user->addMobile($identifier, $view); } } }
function Streams_0_8_8_Streams_mysql() { $communityId = Users::communityId(); $user = Users_User::fetch($communityId, true); Streams::create($communityId, $communityId, 'Streams/resource', array('name' => 'Streams/invitations', 'readLevel' => 0, 'writeLevel' => 0, 'adminLevel' => 0)); Streams_Access::insert(array('publisherId' => $communityId, 'streamName' => "Streams/invitations", 'ofUserId' => '', 'grantedByUserId' => null, 'ofContactLabel' => "{$app}/admins", 'readLevel' => Streams::$READ_LEVEL['messages'], 'writeLevel' => Streams::$WRITE_LEVEL['close'], 'adminLevel' => Streams::$ADMIN_LEVEL['invite']))->execute(); }
function Streams_after_Q_objects() { $user = Users::loggedInUser(); if (!$user) { return; } $invite = Streams::$followedInvite; if (!$invite) { return; } $displayName = $user->displayName(); if ($displayName) { return; } $stream = new Streams_Stream(); $stream->publisherId = $invite->publisherId; $stream->name = $invite->streamName; if (!$stream->retrieve()) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } // Prepare the complete invite dialog $invitingUser = Users_User::fetch($invite->invitingUserId); list($relations, $related) = Streams::related($user->id, $stream->publisherId, $stream->name, false); $params = array('displayName' => null, 'action' => 'Streams/basic', 'icon' => $user->iconUrl(), 'token' => $invite->token, 'user' => array('icon' => $invitingUser->iconUrl(), 'displayName' => $invitingUser->displayName(array('fullAccess' => true))), 'stream' => $stream->exportArray(), 'relations' => Db::exportArray($relations), 'related' => Db::exportArray($related)); $config = Streams_Stream::getConfigField($stream->type, 'invite', array()); $defaults = Q::ifset($config, 'dialog', array()); $tree = new Q_Tree($defaults); if ($tree->merge($params)) { $dialogData = $tree->getAll(); if ($dialogData) { Q_Response::setScriptData('Q.plugins.Streams.invite.dialog', $dialogData); Q_Response::addTemplate('Streams/invite/complete'); } } }
function Streams_0_8_4_Streams_mysql() { $app = Q_Config::expect('Q', 'app'); $communityId = Users::communityId(); $user = Users_User::fetch($communityId); // avatar for the App user $avatar = new Streams_Avatar(); $avatar->toUserId = $communityId; $avatar->publisherId = $communityId; $avatar->username = $user->username; $avatar->firstName = Users::communityName(); $avatar->lastName = Users::communitySuffix(); $avatar->icon = $user->icon; $avatar->save(); $avatar2 = new Streams_Avatar(); $avatar2->copyFrom($avatar, null, false, true); $avatar->toUserId = ''; $avatar->save(); // access stream for managing app roles $stream = new Streams_Stream(); $stream->publisherId = Users::communityId(); $stream->name = 'Streams/contacts'; $stream->type = 'Streams/resource'; $stream->title = "Contacts"; $stream->setAttribute('prefixes', array("Users/", "{$app}/")); $stream->save(); // access stream for managing app roles $stream = new Streams_Stream(); $stream->publisherId = $app; $stream->name = 'Streams/labels'; $stream->type = 'Streams/resource'; $stream->title = "Labels"; $stream->setAttribute('prefixes', array("Users/", "{$app}/")); $stream->save(); // access for managing app contacts $access = new Streams_Access(); $access->publisherId = $communityId; $access->streamName = 'Streams/contacts'; $access->ofUserId = ''; $access->ofContactLabel = "{$app}/admins"; $access->readLevel = Streams::$READ_LEVEL['messages']; $access->writeLevel = Streams::$WRITE_LEVEL['edit']; $access->adminLevel = Streams::$ADMIN_LEVEL['manage']; $access->save(); // access for managing app roles $access = new Streams_Access(); $access->publisherId = $communityId; $access->streamName = 'Streams/labels'; $access->ofUserId = ''; $access->ofContactLabel = "{$app}/admins"; $access->readLevel = Streams::$READ_LEVEL['messages']; $access->writeLevel = Streams::$WRITE_LEVEL['edit']; $access->adminLevel = Streams::$ADMIN_LEVEL['manage']; $access->save(); }
/** * We are going to implement a subset of the OAuth 1.0a functionality for now, * and later we can expand it to match the full OAuth specification. */ function Users_authorize_response() { if (Q_Response::getErrors()) { Q_Dispatcher::showErrors(); } $response_type = 'token'; $token_type = 'bearer'; $client_id = $_REQUEST['client_id']; $state = $_REQUEST['state']; $skip = Q::ifset($_REQUEST, 'skip', false); $scope = Users_OAuth::requestedScope(true, $scopes); $client = Users_User::fetch($client_id, true); if (!$client) { throw new Q_Exception_MissingRow(array('table' => 'client user', 'criteria' => "id = '{$client_id}'"), 'client_id'); } if (empty($client->url)) { throw new Q_Exception("Client app needs to register url", 'client_id'); } $redirect_uri = Q::ifset($_REQUEST, 'redirect_uri', $client->url); $user = Users::loggedInUser(); $oa = null; if (isset(Users::$cache['oAuth'])) { $oa = Users::$cache['oAuth']; } else { if ($user) { $oa = new Users_OAuth(); $oa->client_id = $client_id; $oa->userId = $user->id; $oa->state = $state; $oa = $oa->retrieve(); } } $remaining = $scope; if ($oa and $oa->wasRetrieved()) { // User is logged in and already has a token for this client_id and state $paths = Q_Config::get('Users', 'authorize', 'clients', Q::app(), 'redirectPaths', false); $path = substr($redirect_uri, strlen($client->url) + 1); $p = array('response_type' => $response_type, 'token_type' => $token_type, 'access_token' => $oa->access_token, 'expires_in' => $oa->token_expires_seconds, 'scope' => implode(' ', $scope), 'state' => $oa->state); $p = Q_Utils::sign($p, 'Q.Users.oAuth'); // the redirect uri could be a native app url scheme $s = strpos($redirect_uri, '#') === false ? '#' : '&'; $redirect_uri = Q_Uri::from($redirect_uri . $s . http_build_query($p), false)->toUrl(); if (!Q::startsWith($redirect_uri, $client->url) or is_array($paths) and !in_array($path, $paths)) { throw new Users_Exception_Redirect(array('uri' => $redirect_uri)); } Q_Response::redirect($redirect_uri); return false; } $terms_label = Users::termsLabel('authorize'); Q_Response::setScriptData('Q.Users.authorize', compact('client_id', 'redirect_uri', 'scope', 'scopes', 'remaining', 'state', 'response_type', 'skip')); $content = Q::view('Users/content/authorize.php', compact('client', 'user', 'redirect_uri', 'scope', 'scopes', 'remaining', 'state', 'terms_label', 'response_type', 'skip')); Q_Response::setSlot('content', $content); Q_Response::setSlot('column0', $content); return true; }
function Streams_invited_response() { if (!($token = Q_Dispatcher::uri()->token)) { throw new Q_Exception_RequiredField(array('field' => 'token'), 'token'); } if (!($invite = Streams_Invite::fromToken($token))) { throw new Q_Exception_MissingRow(array('table' => 'invite', 'criteria' => "token: {$token}"), 'token'); } Users_User::fetch($invite->userId, true)->setVerified(); Q_Response::redirect($invite->appUrl . "?" . http_build_query(array('Q.Streams.token' => $token), null, '&')); }
/** * Get a summary of streams related to the specified user's * "Streams/user/interests" stream * * @param {array} $_REQUEST * @param {string} [$_REQUEST.userId=loggedInUserId] userId * @return {void} */ function Streams_interest_response_interests() { $user = Users::loggedInUser(); $userId = Q::ifset($_REQUEST, 'userId', null); if ($user and $userId and $userId != $user->id and Q_Config::get('Streams', 'interests', 'allowClientQueries', false)) { throw new Q_Exception("Client queries are restricted, as per Streams/interests/allowClientQueries"); } if ($userId) { $user = Users_User::fetch($userId); } if (!$user) { throw new Users_Exception_NotLoggedIn(); } return Streams_Category::getRelatedTo($user->id, 'Streams/user/interests', 'Streams/interests'); }
/** * This tool renders ways to get in touch * * @param array [$options] An associative array of options, containing: * @param {string|Users_User} [$options.user] Required. The user object or id of the user exposing their primary identifiers for getting in touch. * @param {boolean|string} [$options.email] Pass true here to use the primary verified email address, if any. Or pass the string label for this button. * @param {string} [$options.emailSubject] Fill this if you want the email subject to be automatically filled in * @param {string} [$options.emailBody] Fill this if you want the email body to be automatically filled in * @param {boolean|string} [$options.sms] Pass true here to allow texting the primary verified mobile number, if any. Or pass the string label for this button. * @param {boolean|string} [$options.call] Pass true here to allow calling the primary verified mobile number, if any. Or pass the string label for this button. * @param {string} [$options.tag] The type of tag to use, defaults to "button" * @param {string} [$options.class] Any classes to add to the tags * @param {string} [$options.between] Any HTML to put between the elements */ function Users_getintouch_tool($options) { $tag = 'button'; $class = null; $between = ''; $user = null; $emailSubject = ''; $emailBody = ''; extract($options, EXTR_IF_EXISTS); if (!$user) { throw new Q_Exception_RequiredField(array('field' => 'user')); } if (is_string($user)) { $userId = $user; $user = Users_User::fetch($userId); if (!$user) { throw new Q_Exception_MissingRow(array('table' => 'user', 'criteria' => "id={$userId}")); } } $ways = array(); $email = $sms = $call = false; if (!empty($options['email']) and $user->emailAddress) { $email = is_string($options['email']) ? $options['email'] : "Email me"; $email = Q_Html::img("plugins/Users/img/email.png") . $email; $ways['email'] = Q_Html::tag($tag, array('id' => 'email', 'class' => $class), $email); Q_Response::setToolOptions(array('emailAddress' => Q_Utils::obfuscate($user->emailAddress), 'emailSubject' => Q_Utils::obfuscate($emailSubject), 'emailBody' => Q_Utils::obfuscate($emailBody))); } if (Q_Request::isMobile()) { $obfuscated_mobileNumber = Q_Utils::obfuscate($user->mobileNumber); if (!empty($options['sms']) and $user->mobileNumber) { $sms = is_string($options['sms']) ? $options['sms'] : "Text me"; $sms = Q_Html::img("plugins/Users/img/sms.png") . $sms; $ways['sms'] = Q_Html::tag($tag, array('id' => 'sms', 'class' => $class), $sms); Q_Response::setToolOptions(array('mobileNumber' => $obfuscated_mobileNumber)); } if (!empty($options['call']) and $user->mobileNumber) { $call = is_string($options['call']) ? $options['call'] : "Call me"; $call = Q_Html::img("plugins/Users/img/call.png") . $call; $ways['call'] = Q_Html::tag($tag, array('id' => 'call', 'class' => $class), $call); Q_Response::setToolOptions(array('mobileNumber' => $obfuscated_mobileNumber)); } } return implode($between, $ways); }
/** * We are going to implement a subset of the OAuth 1.0a functionality for now, * and later we can expand it to match the full OAuth specification. */ function Users_authorize_response() { if (Q_Response::getErrors()) { Q_Dispatcher::showErrors(); } $client_id = $_REQUEST['client_id']; $redirect_url = $_REQUEST['redirect_uri']; $state = $_REQUEST['state']; $client = Users_User::fetch($client_id); if (!$client) { throw new Q_Exception_MissingRow(array('table' => 'user', 'criteria' => "id = '{$client_id}'"), 'client_id'); } if (empty($client->url)) { throw new Q_Exception("Client app needs to register url", 'client_id'); } if (substr($redirect_url, 0, strlen($client->url)) !== $client->url) { throw new Q_Exception_WrongValue(array('field' => 'redirect_uri', 'range' => "a url prefixed by client user's url")); } $user = Users::loggedInUser(); $oa = null; if (isset(Users::$cache['oAuth'])) { $oa = Users::$cache['oAuth']; } else { if ($user) { $oa = new Users_OAuth(); $oa->client_id = $client_id; $oa->userId = $user->id; $oa->state = $state; $oa->retrieve(); } } if ($oa and $oa->wasRetrieved()) { // User is logged in and already has a token for this client_id and state $separator = strpos($redirect_url, '?') === false ? '?' : '&'; $url = $redirect_url . $separator . http_build_query(array('access_token' => $oa->access_token, 'token_type' => 'bearer', 'expires_in' => $oa->token_expires_seconds, 'scope' => 'user', 'state' => $oa->state)); Q_Response::redirect(Q_Uri::from($url, false)); return false; } $terms_label = Users::termsLabel('authorize'); $content = Q::view('Users/content/authorize.php', compact('client', 'redirect_url', 'user', 'state', 'terms_label')); Q_Response::setSlot('content', $content); Q_Response::setSlot('column0', $content); return true; }
/** * Get the logged-in user's credits stream * @method userStream * @param {string} [$userId=null] * The id of the user for which the stream is obtained. Defaults to logged-in user. * @param {string} [$asUserId=null] * The id of the user who is trying to obtain it. Defaults to logged-in user. * @param {boolean} [$throwIfNotLoggedIn=false] * Whether to throw a Users_Exception_NotLoggedIn if no user is logged in. * @return {Streams_Stream|null} * @throws {Users_Exception_NotLoggedIn} If user is not logged in and * $throwIfNotLoggedIn is true */ static function userStream($userId = null, $asUserId = null, $throwIfNotLoggedIn = false) { if (!isset($userId)) { $user = Users::loggedInUser($throwIfNotLoggedIn); if (!$user) { return null; } } else { $user = Users_User::fetch($userId, true); } $userId = $user->id; $streamName = 'Awards/user/credits'; $stream = Streams::fetchOne($asUserId, $userId, $streamName); if (!$stream) { $amount = Q_Config::get('Awards', 'credits', 'amounts', 'Users/insertUser', self::DEFAULT_AMOUNT); $stream = Streams::create($userId, $userId, 'Awards/credits', array('name' => 'Awards/user/credits', 'title' => "Credits", 'icon' => 'plugins/Awards/img/credits.png', 'content' => '', 'attributes' => Q::json_encode(compact('amount')))); } return $stream; }
function Streams_0_8_7_Streams_mysql() { $app = Q_Config::expect('Q', 'app'); $user = Users_User::fetch($app, true); $simulated = array('row' => $user, 'inserted' => true, 'modifiedFields' => $user->fields); Q::event('Db/Row/Users_User/saveExecute', $simulated, 'after'); $stream = array('publisherId' => '', 'name' => "Streams/images/", 'type' => 'Streams/template', 'title' => 'Image Gallery', 'icon' => 'default', 'content' => '', 'attributes' => null, 'readLevel' => Streams::$READ_LEVEL['messages'], 'writeLevel' => Streams::$WRITE_LEVEL['close'], 'adminLevel' => Streams::$ADMIN_LEVEL['invite']); $access = array('publisherId' => '', 'streamName' => "Streams/images/", 'ofUserId' => '', 'grantedByUserId' => null, 'ofContactLabel' => "{$app}/admins", 'readLevel' => Streams::$READ_LEVEL['messages'], 'writeLevel' => Streams::$WRITE_LEVEL['close'], 'adminLevel' => Streams::$ADMIN_LEVEL['invite']); Streams_Stream::insert($stream)->execute(); Streams_Access::insert($access)->execute(); $stream['name'] = $access['streamName'] = 'Streams/image/'; $stream['icon'] = 'Streams/image'; $stream['title'] = 'Untitled Image'; Streams_Stream::insert($stream)->execute(); Streams_Access::insert($access)->execute(); $stream['name'] = $access['streamName'] = 'Streams/file/'; $stream['icon'] = 'files/_blank'; $stream['title'] = 'Untitled File'; Streams_Stream::insert($stream)->execute(); Streams_Access::insert($access)->execute(); }
/** * Check if user "owns" a stream template for a publisher * @method isOwner * @static * @param {string} $publisherId * @param {string} $type * @param {string|Users_User} [$user=null] * @return {boolean} */ static function isOwner($publisherId, $type, $user = null) { if (!isset($user)) { $user = Users::loggedInUser(); } else { if (is_string($user)) { $user = Users_User::fetch($user); } } if (!isset($user)) { return false; } // check if user is owner of stream template $stream = new Streams_Stream(); $stream->publisherId = $publisherId; $stream->name = $type . '/'; if (!$stream->retrieve()) { return false; } $stream->calculateAccess($user->id); return $stream->testAdminLevel('own'); }
/** * Adds contacts to the system. Fills the "contacts" slot. * @param {array} $_REQUEST * @param {string} $_REQUEST.label The label of the contact * @param {string} $_REQUEST.contactUserId The contactUserId of the contact * @param {string} [$_REQUEST.nickname] The nickname of the contact * @param {string} [$_REQUEST.userId=Users::loggedInUser(true)->id] You can override the user id, if another plugin adds a hook that allows you to do this */ function Users_contact_post($params = array()) { $req = array_merge($_REQUEST, $params); Q_Request::requireFields(array('label', 'contactUserId'), $req, true); $loggedInUserId = Users::loggedInUser(true)->id; $userId = Q::ifset($req, 'userId', $loggedInUserId); $contactUserId = $req['contactUserId']; $nickname = Q::ifset($req, 'nickname', null); $l = $req['label']; if ($userId !== $loggedInUserId) { Users_User::fetch($userId, true); } Users_User::fetch($contactUserId, true); Users::canManageContacts($loggedInUserId, $userId, $l, true); $label = new Users_Label(); $label->userId = $userId; $label->label = $l; if (!$label->retrieve()) { throw new Q_Exception_MissingRow(array('table' => 'Users_Label', 'criteria' => json_encode($label->fields))); } $contacts = Users_Contact::addContact($userId, $l, $contactUserId, $nickname); Q_Response::setSlot('contacts', Db::exportArray($contacts)); }
/** * Invites a user (or a future user) to a stream . * @method invite * @static * @param {string} $publisherId The id of the stream publisher * @param {string} $streamName The name of the stream the user will be invited to * @param {array} $who Array that can contain the following keys: * @param {string|array} [$who.userId] user id or an array of user ids * @param {string|array} [$who.fb_uid] fb user id or array of fb user ids * @param {string|array} [$who.label] label or an array of labels, or tab-delimited string * @param {string|array} [$who.identifier] identifier or an array of identifiers, or tab-delimited string * @param {integer} [$who.newFutureUsers] the number of new Users_User objects to create via Users::futureUser in order to invite them to this stream. This typically is used in conjunction with passing the "html" option to this function. * @param {array} [$options=array()] * @param {string|array} [$options.label] label or an array of labels for adding publisher's contacts * @param {string|array} [$options.myLabel] label or an array of labels for adding logged-in user's contacts * @param {integer} [$options.readLevel] => the read level to grant those who are invited * @param {integer} [$options.writeLevel] => the write level to grant those who are invited * @param {integer} [$options.adminLevel] => the admin level to grant those who are invited * @param {string} [$options.displayName] => the display name to use to represent the inviting user * @param {string} [$options.appUrl] => Can be used to override the URL to which the invited user will be redirected and receive "Q.Streams.token" in the querystring. * @param {array} [$options.html] => an array of ($template, $batchName) such as ("MyApp/foo.handlebars", "foo") for generating html snippets which can then be viewed from and printed via the action Streams/invitations?batchName=$batchName * @param {array} [$options.asUserId=null] Invite as this user id * @see Users::addLink() * @return {array} returns array with keys "success", "invited", "statuses", "identifierTypes", "alreadyParticipating" */ static function invite($publisherId, $streamName, $who, $options = array()) { if (isset($options['asUserId'])) { $asUserId = $options['asUserId']; $asUser = Users_User::fetch($asUserId); } else { $asUser = Users::loggedInUser(true); $asUserId = $asUser->id; } // Fetch the stream as the logged-in user $stream = Streams::fetch($asUserId, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } $stream = reset($stream); // Do we have enough admin rights to invite others to this stream? if (!$stream->testAdminLevel('invite') || !$stream->testWriteLevel('join')) { throw new Users_Exception_NotAuthorized(); } if (isset($options['html'])) { $html = $options['html']; if (!is_array($html) or count($html) < 2) { throw new Q_Exception_WrongType(array('field' => "options.html", 'type' => 'array of 2 strings')); } list($template, $batchName) = $html; // validate these paths $filename = APP_VIEWS_DIR . DS . $template; if (!Q::realPath($filename)) { throw new Q_Exception_MissingFile(compact('filename')); } $ext = $pathinfo = pathinfo($template, PATHINFO_EXTENSION); if ($ext !== 'handlebars') { throw new Q_Exception_WrongValue(array('field' => 'options.html[0]', 'range' => 'a filename with extension .handlebars')); } $path = Streams::invitationsPath($asUserId) . DS . $batchName; Q_Utils::canWriteToPath($path, true, true); } // get user ids if any to array, throw if user not found $raw_userIds = isset($who['userId']) ? Users_User::verifyUserIds($who['userId'], true) : array(); // merge labels if any if (isset($who['label'])) { $label = $who['label']; if (is_string($label)) { $label = array_map('trim', explode("\t", $labels)); } $raw_userIds = array_merge($raw_userIds, Users_User::labelsToIds($asUserId, $label)); } // merge identifiers if any $identifierType = null; $statuses = null; if (isset($who['identifier'])) { $identifier = $who['identifier']; if (is_string($identifier)) { if (Q_Valid::email($who['identifier'])) { $identifierType = 'email'; } else { if (Q_Valid::phone($who['identifier'])) { $identifierType = 'mobile'; } } $identifier = array_map('trim', explode("\t", $identifier)); } $statuses = array(); $identifier_ids = Users_User::idsFromIdentifiers($identifier, $statuses); $raw_userIds = array_merge($raw_userIds, $identifier_ids); } // merge fb uids if any if (isset($who['fb_uid'])) { $fb_uids = $who['fb_uid']; if (is_string($fb_uids)) { $fb_uids = array_map('trim', explode("\t", $fb_uids)); } $raw_userIds = array_merge($raw_userIds, Users_User::idsFromFacebook($fb_uids)); } if (!empty($who['newFutureUsers'])) { $nfu = $who['newFutureUsers']; for ($i = 0; $i < $nfu; ++$i) { $raw_userIds[] = Users::futureUser('none', null)->id; } } // ensure that each userId is included only once // and remove already participating users $raw_userIds = array_unique($raw_userIds); $total = count($raw_userIds); $userIds = Streams_Participant::filter($raw_userIds, $stream); $to_invite = count($userIds); $appUrl = !empty($options['appUrl']) ? $options['appUrl'] : Q_Request::baseUrl() . '/' . Q_Config::get("Streams", "types", $stream->type, "invite", "url", "plugins/Streams/stream"); // now check and define levels for invited user $readLevel = isset($options['readLevel']) ? $options['readLevel'] : null; if (isset($readLevel)) { if (!$stream->testReadLevel($readLevel)) { // We can't assign greater read level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $writeLevel = isset($options['writeLevel']) ? $options['writeLevel'] : null; if (isset($writeLevel)) { if (!$stream->testWriteLevel($writeLevel)) { // We can't assign greater write level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $adminLevel = isset($options['adminLevel']) ? $options['adminLevel'] : null; if (isset($adminLevel)) { if (!$stream->testAdminLevel($adminLevel + 1)) { // We can't assign an admin level greater, or equal, to our own! // A stream's publisher can assign owners. Owners can assign admins. // Admins can confer powers to invite others, to some people. // Those people can confer the privilege to publish a message re this stream. // But admins can't assign other admins, and even stream owners // can't assign other owners. throw new Users_Exception_NotAuthorized(); } } // calculate expiry time $duration = Q_Config::get("Streams", "types", $stream->type, "invite", "duration", false); $expiry = $duration ? strtotime($duration) : null; // let node handle the rest, and get the result $params = array("Q/method" => "Streams/Stream/invite", "invitingUserId" => $asUserId, "username" => $asUser->username, "userIds" => Q::json_encode($userIds), "stream" => Q::json_encode($stream->toArray()), "appUrl" => $appUrl, "label" => Q::ifset($options, 'label', null), "myLabel" => Q::ifset($options, 'myLabel', null), "readLevel" => $readLevel, "writeLevel" => $writeLevel, "adminLevel" => $adminLevel, "displayName" => isset($options['displayName']) ? $options['displayName'] : Streams::displayName($asUser), "expiry" => $expiry); if ($template) { $params['template'] = $template; $params['batchName'] = $batchName; } $result = Q_Utils::queryInternal('Q/node', $params); return array('success' => $result, 'invited' => $userIds, 'statuses' => $statuses, 'identifierType' => $identifierType, 'alreadyParticipating' => $total - $to_invite); }
function Streams_before_Q_objects() { $token = Q_Request::special('Streams.token', null); if ($token === null) { return; } $invite = Streams_Invite::fromToken($token); if (!$invite) { throw new Q_Exception_MissingRow(array('table' => 'invite', 'criteria' => "token = '{$token}"), 'token'); } // did invite expire? $ts = Streams_Invite::db()->select("CURRENT_TIMESTAMP")->fetchAll(PDO::FETCH_NUM); if (isset($invite->expireTime) and $invite->expireTime < $ts[0][0]) { $invite->state = 'expired'; $invite->save(); } // is invite still pending? if ($invite->state !== 'pending') { switch ($invite->state) { case 'expired': $exception = new Streams_Exception_AlreadyExpired(null, 'token'); break; case 'accepted': $exception = new Streams_Exception_AlreadyAccepted(null, 'token'); break; case 'declined': $exception = new Streams_Exception_AlreadyDeclined(null, 'token'); break; case 'forwarded': $exception = new Streams_Exception_AlreadyForwarded(null, 'token'); break; default: $exception = new Q_Exception("This invite has already been " . $invite->state, 'token'); break; } $shouldThrow = Q::event('Streams/objects/inviteException', compact('invite', 'exception'), 'before'); if ($shouldThrow === null) { Q_Response::setNotice('Streams/objects', $exception->getMessage(), true); } else { if ($shouldThrow === true) { throw $exception; } } } // now process the invite $invitedUser = Users_User::fetch($invite->userId, true); $stream = Streams::fetchOne($invitedUser->id, $invite->publisherId, $invite->streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $byUser = Users_User::fetch($invite->invitingUserId, true); $byStream = Streams::fetchOne($byUser->id, $invite->publisherId, $invite->streamName); if (!$byStream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => "publisherId = '{$invite->publisherId}', name = '{$invite->streamName}'")); } $access = new Streams_Access(); $access->publisherId = $byStream->publisherId; $access->streamName = $byStream->name; $access->ofUserId = $invite->userId; $specified_access = false; foreach (array('readLevel', 'writeLevel', 'adminLevel') as $level_type) { $access->{$level_type} = -1; if (empty($invite->{$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($invite->{$level_type}, $byStream->get($level_type, 0)); if ($min > $stream->get($level_type, 0)) { $access->{$level_type} = $min; $specified_access = true; } } if ($specified_access) { $access->save(true); } // now log invited user in $user = Users::loggedInUser(); if (empty($user) or $user->id !== $invite->userId) { $user = new Users_User(); $user->id = $invite->userId; if (!$user->retrieve()) { // The user who was invited doesn't exist // This shouldn't happen. We just silently log it and return. Q::log("Sanity check failed: invite with {$invite->token} pointed to nonexistent user"); return; } Users::setLoggedInUser($user); } // accept invite and autosubscribe if first time if ($invite->accept() and !$stream->subscription($user->id)) { $stream->subscribe(); } // retain the invite object for further processing Streams::$followedInvite = $invite; }
/** * Use with caution! This bypasses authentication. * This functionality should not be exposed externally. * @method setLoggedInUser * @static * @param {Users_User|string} $user The user object or user id */ static function setLoggedInUser($user = null) { if (!$user) { return Users::logout(); } if (is_string($user)) { $user = Users_User::fetch($user); } if (isset($_SESSION['Users']['loggedInUser']['id'])) { if ($user->id == $_SESSION['Users']['loggedInUser']['id']) { // This user is already the logged-in user. return; } } if ($sessionId = Q_Session::id()) { // Change the session id to prevent session fixation attacks $sessionId = Q_Session::regenerateId(true); } // Store the new information in the session $snf = Q_Config::get('Q', 'session', 'nonceField', 'nonce'); $_SESSION['Users']['loggedInUser']['id'] = $user->id; Q_Session::setNonce(true); $user->sessionCount = isset($user->sessionCount) ? $user->sessionCount + 1 : 1; // Do we need to update it? if (Q_Config::get('Users', 'setLoggedInUser', 'updateSessionKey', true)) { /** * @event Users/setLoggedInUser/updateSessionKey {before} * @param {Users_User} user */ Q::event('Users/setLoggedInUser/updateSessionKey', compact('user'), 'before'); $user->sessionId = $sessionId; $user->save(); // update sessionId in user /** * @event Users/setLoggedInUser/updateSessionKey {after} * @param {Users_User} user */ Q::event('Users/setLoggedInUser/updateSessionKey', compact('user'), 'after'); } $votes = Users_Vote::select('*')->where(array('userId' => $user->id, 'forType' => 'Users/hinted'))->fetchDbRows(null, null, 'forId'); // Cache already shown hints in the session. // The consistency of this mechanism across sessions is not perfect, i.e. // the same hint may repeat in multiple concurrent sessions, but it's ok. $_SESSION['Users']['hinted'] = array_keys($votes); /** * @event Users/setLoggedInUser {after} * @param {Users_User} user */ Q::event('Users/setLoggedInUser', compact('user'), 'after'); self::$loggedOut = false; }
/** * Starts a recurring subscription * @param {Streams_Stream} $plan The subscription plan stream * @param {string} [$payments=null] The type of payments processor, could be "authnet" or "stripe". If omitted, the subscription proceeds without any payments. * @param {array} [$options=array()] Options for the subscription * @param {date} [$options.startDate=today] The start date of the subscription * @param {date} [$options.endDate=today+year] The end date of the subscription * @param {Users_User} [$options.user=Users::loggedInUser()] Allows us to set the user to subscribe * @param {Users_User} [$options.publisherId=Users::communityId()] Allows us to override the publisher to subscribe to * @param {string} [$options.description=null] description of the charge, to be sent to customer * @param {string} [$options.metadata=null] any additional metadata to store with the charge * @param {string} [$options.subscription=null] if this charge is related to a subscription stream * @param {string} [$options.subscription.publisherId] * @param {string} [$options.subscription.streamName] * @throws Assets_Exception_DuplicateTransaction * @throws Assets_Exception_HeldForReview * @throws Assets_Exception_ChargeFailed * @return {Streams_Stream} A stream of type 'Assets/subscription' representing this subscription */ static function startSubscription($plan, $payments = null, $options = array()) { if (!isset($options['user'])) { $options['user'] = Users::loggedInUser(true); } $app = Q_Config::expect('Q', 'app'); $user = Q::ifset($options, 'user', Users::loggedInUser(true)); $currency = 'USD'; // TODO: may want to implement support for currency conversion $startDate = Q::ifset($options, 'startDate', date("Y-m-d")); $startDate = date('Y-m-d', strtotime($startDate)); $months = $plan->getAttribute('months', 12); $amount = $plan->getAttribute('amount'); $endDate = date("Y-m-d", strtotime("-1 day", strtotime("+{$months} month", strtotime($startDate)))); $endDate = date('Y-m-d', strtotime($endDate)); $publisherId = Q::ifset($options, 'publisherId', Users::communityId()); $publisher = Users_User::fetch($publisherId); $streamName = "Assets/subscription/{$user->id}/{$plan->name}"; if ($subscription = Streams::fetchOne($publisherId, $publisherId, $streamName)) { return $subscription; // it already started } $attributes = Q::json_encode(array('payments' => $payments, 'planPublisherId' => $plan->publisherId, 'planStreamName' => $plan->name, 'startDate' => $startDate, 'endDate' => $endDate, 'months' => $months, 'amount' => $amount, 'currency' => $currency)); $stream = Streams::create($publisherId, $publisherId, "Assets/subscription", array('name' => $streamName, 'title' => $plan->title, 'readLevel' => Streams::$READ_LEVEL['none'], 'writeLevel' => Streams::$WRITE_LEVEL['none'], 'adminLevel' => Streams::$ADMIN_LEVEL['none'], 'attributes' => $attributes)); $access = new Streams_Access(array('publisherId' => $publisherId, 'streamName' => $streamName, 'ofUserId' => $user->id, 'grantedByUserId' => $app, 'readLevel' => Streams::$READ_LEVEL['max'], 'writeLevel' => -1, 'adminLevel' => -1)); $access->save(); $amount = $plan->getAttribute('amount', null); if (!is_numeric($amount)) { throw new Q_Exception_WrongValue(array('field' => 'amount', 'range' => 'an integer')); } $options['stream'] = $stream; if ($payments) { Assets::charge($payments, $amount, $currency, $options); } /** * @event Assets/startSubscription {before} * @param {Streams_Stream} plan * @param {Streams_Stream} subscription * @param {string} startDate * @param {string} endDate * @return {Users_User} */ Q::event('Assets/startSubscription', compact('plan', 'user', 'publisher', 'stream', 'startDate', 'endDate', 'months', 'currency'), 'after'); return $stream; }
/** * Send credits, as the logged-in user, to another user * @method send * @static * @param {integer} $amount The amount of credits to send. * @param {string} $toUserId The id of the user to whom you will send the credits * @param {array} $more An array supplying more info, including * "reason" => Identifies the reason for sending, if any */ static function send($amount, $toUserId, $more = array()) { if (!is_int($amount) or $amount <= 0) { throw new Q_Exception_WrongType(array('field' => 'amount', 'type' => 'integer')); } $user = Users::loggedInUser(true); $from_stream = new Streams_Stream(); $from_stream->publisherId = $user->id; $from_stream->name = 'Awards/credits'; if (!$from_stream->retrieve()) { $from_stream = self::createStream($user); } $existing_amount = $from_stream->getAttribute('amount'); if ($existing_amount < $amount) { throw new Awards_Exception_NotEnoughCredits(array('missing' => $amount - $existing_amount)); } $to_user = Users_User::fetch($toUserId, true); $to_stream = new Streams_Stream(); $to_stream->publisherId = $toUserId; $to_stream->name = 'Awards/credits'; if (!$to_stream->retrieve()) { $to_stream = self::createStream($to_user); } $to_stream->setAttribute('amount', $to_stream->getAttribute('amount') - $amount); $to_stream->save(); // NOTE: we are not doing transactions here mainly because of sharding. // If if we reached this point without exceptions, that means everything worked. // But if the following statement fails, then someone will get free credits. $from_stream->setAttribute('amount', $from_stream->getAttribute('amount') - $amount); $from_stream->save(); $instructions_json = Q::json_encode(array_merge(array('app' => Q_Config::expect('Q', 'app')), $more)); Streams_Message::post($user->id, $userId, array('type' => 'Awards/credits/sent', 'content' => $amount, 'instructions' => $instructions_json)); Streams_Message::post($user->id, $toUserId, array('type' => 'Awards/credits/received', 'content' => $amount, 'instructions' => $instructions_json)); }
protected function _verifyUser($options, &$user = null) { if (isset($options['userId'])) { $user = Users_User::fetch($options['userId'], true); } else { $user = Users::loggedInUser(true); } $stream = Streams::fetchOne($user->id, $this->publisherId, $this->name, true, array('refetch' => true)); return $user->id; }
protected function fetchAsUser($options, &$userId, &$user = null) { if (isset($options['userId'])) { $user = Users_User::fetch($options['userId']); if (!$user) { throw new Q_Exception_MissingRow(array('table' => 'user', 'criteria' => "id = " . $options['userId'])); } } else { $user = Users::loggedInUser(true); } $userId = $user->id; if (!empty($options['skipAccess']) or $userId === $this->get('asUserId', null)) { return $this; } $stream = Streams::fetchOne($userId, $this->publisherId, $this->name, '*', array('refetch' => true)); if (!$stream) { // this should never happen throw new Q_Exception("Error getting {$this->name} stream published by {$this->publisherId} for user '{$userId}'"); } return $stream; }
function Assets_after_Assets_charge($params) { $user = $payments = $amount = $currency = $charge = $adapter = $options = null; extract($params, EXTR_OVERWRITE); $description = 'a product or service'; $stream = Q::ifset($options, 'stream', null); if ($stream) { $publisherId = $stream->publisherId; $publisher = Users_User::fetch($publisherId, true); if ($stream->type === 'Assets/subscription') { $plan = Streams::fetchOne($stream->getAttribute('planPublisherId'), $stream->getAttribute('planPublisherId'), $stream->getAttribute('planStreamName'), true); $months = $stream->getAttribute('months'); $startDate = $stream->getAttribute('startDate'); $endDate = $stream->getAttribute('endDate'); } $description = $stream->title; } else { $publisherId = Users::communityId(); $publisher = Users_User::fetch($publisherId, true); } if (isset($options['description'])) { $description = $options['description']; } $currencies = Q::json_decode(file_get_contents(ASSETS_PLUGIN_CONFIG_DIR . DS . 'currencies.json'), true); if (!isset($currencies['symbols'][$currency])) { throw new Q_Exception_BadValue(array('internal' => 'currency', 'problem' => 'no symbol found'), 'currency'); } if (!isset($currencies['names'][$currency])) { throw new Q_Exception_BadValue(array('internal' => 'currency', 'problem' => 'no name found'), 'currency'); } $symbol = $currencies['symbols'][$currency]; $currencyName = $currencies['names'][$currency]; $communityId = Users::communityId(); $communityName = Users::communityName(); $communitySuffix = Users::communitySuffix(); $link = Q_Request::baseUrl('action.php') . "/Assets/payment?publisherId={$publisherId}&userId=" . $user->id; $fields = compact('user', 'publisher', 'publisherId', 'communityId', 'communityName', 'communitySuffix', 'description', 'subscription', 'stream', 'plan', 'currency', 'name', 'symbol', 'currencyName', 'amount', 'months', 'startDate', 'endDate', 'link'); if ($user->emailAddress) { $email = new Users_Email(); $email->address = $user->emailAddress; $email->retrieve(true); $emailSubject = Q_Config::get('Assets', 'transactional', 'charged', 'subject', false); $emailView = Q_Config::get('Assets', 'transactional', 'charged', 'body', false); if ($emailSubject !== false and $emailView) { $email->sendMessage($emailSubject, $emailView, $fields); } } else { if ($user->mobileNumber) { $mobile = new Users_Mobile(); $mobile->number = $user->mobileNumber; $mobile->retrieve(true); if ($mobileView = Q_Config::get('Assets', 'transactional', 'charged', 'sms', false)) { $mobile->sendMessage($mobileView, $fields); } } } if ($publisher->emailAddress) { $email = new Users_Email(); $email->address = $publisher->emailAddress; $email->retrieve(true); $emailSubject = Q_Config::get('Assets', 'transactional', 'charge', 'subject', false); $emailView = Q_Config::get('Assets', 'transactional', 'charge', 'body', false); if ($emailSubject !== false and $emailView) { $email->sendMessage($emailSubject, $emailView, $fields); } } else { if ($publisher->mobileNumber) { $mobile = new Users_Mobile(); $mobile->number = $publisher->mobileNumber; $mobile->retrieve(true); if ($mobileView = Q_Config::get('Assets', 'transactional', 'charge', 'sms', false)) { $mobile->sendMessage($mobileView, $fields); } } } }