/** * Post a Tweet with link to this * question */ protected function tweet() { try { $reward = \Lampcms\Points::SHARED_CONTENT; $User = $this->Registry->Viewer; $oTweet = new Tweet(); $oBitly = new Bitly($this->Registry->Ini->getSection('BITLY')); $oTwitter = new Twitter($this->Registry); $Resource = $this->obj; $Mongo = $this->Registry->Mongo; d('cp'); } catch (\Exception $e) { d('Unable to post tweet because of this exception: ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); return; } $func = function () use($oTweet, $oBitly, $oTwitter, $Resource, $User, $reward, $Mongo) { $result = $oTweet->post($oTwitter, $oBitly, $Resource); if (!empty($result) && is_array($result)) { /** * If status is OK (Tweet was posted) * then reward the user with points! */ if (!empty($result['id_str']) && '200' == $result['http_code']) { $User->setReputation($reward); /** * Now need to also record Tweet Status data * to TWEETS collection */ try { $coll = $Mongo->TWEETS; $coll->ensureIndex(array('i_uid' => 1)); $tw_uid = !empty($result['user']) && !empty($result['user']['id_str']) ? $result['user']['id_str'] : null; $tw_username = !empty($result['user']) && !empty($result['user']['screen_name']) ? $result['user']['screen_name'] : null; /** * Record Tweet status to TWEETS collection. * Later can query Twitter to find * replies to these Tweets and add them * as "comments" to this Question or Answer * * HINT: if i_rid !== i_qid then it's an ANSWER * if these are the same then it's a Question * @var array */ $aData = array('_id' => $result['id_str'], 'i_uid' => $User->getUid(), 'i_rid' => $Resource->getResourceId(), 'i_qid' => $Resource->getQuestionId(), 'tw_user_id_str' => $tw_uid, 'tw_username' => $tw_username, 'i_ts' => time(), 'h_ts' => date('r')); $coll->save($aData); } catch (\Exception $e) { if (function_exists('e')) { e('Unable to save data to TWEETS collection because of ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); } } } } }; d('cp'); \Lampcms\runLater($func); }
/** * Post to blogger blog * */ protected function post() { d('cp'); try { d('cp'); $oBlogger = new ApiClient($this->Registry); d('cp'); $User = $this->Registry->Viewer; d('cp'); if (false === $oBlogger->setUser($User)) { d('User does not have Blogger Oauth credentials'); return; } d('cp'); $reward = $this->Registry->Ini->POINTS->SHARED_CONTENT; $Resource = $this->obj; d('cp'); $oAdapter = new BloggerPostAdapter($this->Registry); d('cp'); } catch (\Exception $e) { d('Unable to post to blogger because of this exception: ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); return; } d('cp'); $func = function () use($oBlogger, $oAdapter, $Resource, $User, $reward) { $result = null; try { $result = $oBlogger->add($oAdapter->makeEntry($Resource)); } catch (\Exception $e) { return; } if (!empty($result) && is_numeric($result)) { $User->setReputation($reward); /** * Also save blogger post id to QUESTIONS or ANSWERS * collection. * This way later on (maybe way later...) * We can add a function so that if user edits * Post on the site we can also edit it * on blogger via API * Can also delete from blogger if Resource * id deleted * */ $Resource['blogger_id'] = $result; $Resource->save(); } }; //$func(); \Lampcms\runLater($func); }
/** * Update ONLINE_USERS collection * @todo exit if useragent is of known Crawler * * @todo make logging guests online configurable option via Ini * */ protected function run() { $Viewer = $this->Registry->Viewer; $ip = Request::getIP(); $uid = $Viewer->getUid(); d('uid: ' . $uid); $aData = array('ip' => $ip, 'i_ts' => time(), 'ua' => Request::getUserAgent(), 'action' => 'request_' . $this->Registry->Request->get('a', 's', 'home'), 'uri' => $_SERVER['REQUEST_URI'], 'title' => $this->title, 'category' => $this->category, 'a_kw' => !empty($this->aInfo['keywords']) ? explode(', ', $this->aInfo['keywords']) : array()); if ($uid > 0) { $aData['i_uid'] = $uid; $aData['username'] = $Viewer->getDisplayName(); $aData['avtr'] = $Viewer->getAvatarSrc(); $aData['profile'] = $Viewer->getProfileUrl(); $aData['role'] = $Viewer->getRoleId(); $aData['i_pp'] = $Viewer->getProfitPoint(); } $Mongo = $this->Registry->Mongo->getDb(); $Geo = $this->Registry->Geo; $func = function () use($aData, $Mongo, $Geo) { $aGeo = $Geo->getLocation($aData['ip'])->toArray(); $aData = $aData + $aGeo; /** * Need unique index uid * */ if (array_key_exists('i_uid', $aData)) { $coll = $Mongo->ONLINE; $coll->ensureIndex(array('i_uid' => 1), array('unique' => true)); $coll->ensureIndex(array('i_ts' => 1)); $coll->update(array('i_uid' => $aData['i_uid']), $aData, array('upsert' => true)); } else { /** * For guests the value of ip2long (int) * will be used as uid */ $aData['i_uid'] = ip2long($aData['ip']); $coll = $Mongo->GUESTS; $coll->ensureIndex(array('i_uid' => 1), array('unique' => true)); $coll->ensureIndex(array('i_ts' => 1)); $coll->update(array('ip' => $aData['ip']), $aData, array('upsert' => true)); } /** * Remove old records * Cleanup runs 10% of requests * removes records older than 24 hours */ if (1 === rand(0, 10)) { $offset = time() - 60 * 60 * 24; $coll->remove(array('i_ts' => array('$lt' => $offset))); } }; \Lampcms\runLater($func); }
/** * Post to Tumblr blog * */ protected function post() { d('begin post to Tumblr'); try { $oTumblr = new ApiClient($this->Registry); $User = $this->Registry->Viewer; if (false === $oTumblr->setUser($User)) { d('User does not have Tumblr Oauth credentials'); return; } $reward = $this->Registry->Ini->POINTS->SHARED_CONTENT; $Resource = $this->obj; d('cp'); $oAdapter = new TumblrPostAdapter($this->Registry); d('cp'); } catch (\Exception $e) { d('Unable to post to Tumblr because of this exception: ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); return; } $func = function () use($oTumblr, $oAdapter, $Resource, $User, $reward) { $result = null; try { $result = $oTumblr->add($oAdapter->get($Resource)); } catch (\Exception $e) { if (function_exists('d')) { d('Unable to post to Tumblr: ' . $e->getMessage()); } return; } if (!empty($result) && is_numeric($result)) { $User->setReputation($reward); /** * Also save Tumblr status id to QUESTIONS or ANSWERS * collection. * This way later on (maybe way later...) * We can add a function so that if user edits * Post on the site we can also edit it * on Tumblr via API * Can also delete from Tumblr if Resource * id deleted * */ $Resource['i_tumblr'] = (int) $result; $Resource->save(); } }; \Lampcms\runLater($func); }
/** * Notify all who follows the question * But exclude the Viewer - whoever just added * the new answer or whatever * * * and exclude all who follows the Viewer because all who * follows the Viewer will be notified via * the nofityUserFollowers * * @param int qid id of question * * @param int excludeUid UserID of user that should NOT * be notified. Usually this is in a special case of when * the answer or comment owner has already been notified * so now we just have to exclude the same user in case same user * is also the question author. * * @return object $this */ protected function notifyQuestionFollowers($qid = null, $excludeUid = 0) { $viewerID = $this->Registry->Viewer->getUid(); d('$viewerID: ' . $viewerID); $routerCallback = $this->Registry->Router->getCallback(); /** * $qid can be passed here * OR in can be extracted from $this->Question */ if ($qid) { $Question = new \Lampcms\Question($this->Registry); try { $Question->by_id((int) $qid); } catch (\Exception $e) { e('Unable to create Question by _id. Exception: ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); $Question = null; } } else { $Question = $this->Question; } if (null === $Question) { d('no $Question'); return $this; } $subj = 'onNewAnswer' === $this->eventName || 'onApprovedAnswer' === $this->eventName ? 'email.subject.question_answer' : 'email.subject.question_comment'; $body = 'onNewAnswer' === $this->eventName || 'onApprovedAnswer' === $this->eventName ? 'email.body.question_answer' : 'email.body.question_comment'; $siteUrl = $this->Registry->Ini->SITE_URL; $updateType = 'onNewAnswer' === $this->eventName || 'onApprovedAnswer' === $this->eventName ? 'answer' : 'comment'; $username = '******' === $this->eventName || 'onApprovedAnswer' === $this->eventName ? $this->obj['username'] : $this->aInfo['username']; if ('onNewAnswer' === $this->eventName || 'onApprovedAnswer' === $this->eventName) { $url = $this->obj->getUrl(); } else { $url = $siteUrl . '{_WEB_ROOT_}/{_viewquestion_}/{_QID_PREFIX_}' . $this->aInfo['i_qid'] . '/#c' . $this->aInfo['_id']; $url = $routerCallback($url); } d('url: ' . $url); $content = 'onNewAnswer' !== $this->eventName ? "\n____\n" . \strip_tags($this->aInfo['b']) . "\n" : ''; $varsBody = array('{username}' => $username, '{title}' => $this->Question['title'], '{body}' => $content, '{link}' => $url, '{site_title}' => $this->Registry->Ini->SITE_NAME, '{home}' => $this->Registry->Router->getHomePageUrl()); $body = new TranslatableBody($body, $varsBody); $subj = new TranslatableSubject($subj); $Mailer = $this->Registry->Mailer; $Mailer->setCache($this->Registry->Cache); /** * MongoCollection USERS * * @var object MongoCollection */ $coll = $this->collUsers; d('before shutdown function for question followers'); /** * Get array of followers for this question */ $aFollowers = $Question['a_flwrs']; if (!empty($aFollowers)) { $func = function () use($updateType, $viewerID, $aFollowers, $subj, $body, $coll, $Mailer, $excludeUid) { /** * Remove $viewerID from aFollowers * Remove excludeID from aFollowers * Removing these userIDs from * the find $in condition is guaranteed to not * have these IDs in result and is much better * than adding extra $ne or $nin conditions * on these uids to find() * */ if (false !== ($key = array_search($viewerID, $aFollowers))) { array_splice($aFollowers, $key, 1); } if (!empty($excludeUid)) { if (false !== ($key = array_search($excludeUid, $aFollowers))) { array_splice($aFollowers, $key, 1); } } array_unique($aFollowers); /** * Find all users who follow this question * and * are not also following the Viewer * * In case of comment we should not exclude * Viewer followers because Viewer followers are NOT * notified on user comment * */ if ('comment' !== $updateType) { /** * Condition: Find all users with _id in array of $aFollowers * AND NOT FOLLOWING viewer (who is the author of this answer) * because they will also be notified of this answer * in a separate email that is sent to all followers of Viewer */ $cur = $coll->find(array('_id' => array('$in' => $aFollowers), 'a_f_u' => array('$nin' => array(0 => $viewerID)), 'ne_fq' => array('$ne' => true)), array('email' => true, 'locale' => true)); } else { $cur = $coll->find(array('_id' => array('$in' => $aFollowers), 'ne_fq' => array('$ne' => true)), array('email' => true, 'locale' => true)); } $count = $cur->count(); if ($count > 0) { $Mailer->mailFromCursor($cur, $subj, $body); } }; \Lampcms\runLater($func); } return $this; }
protected function post() { $url = $this->obj->getUrl(); $label = $this->obj->getTitle(); $comment = 'I asked this on ' . $this->Registry->Ini->SITE_NAME; if ($this->obj instanceof \Lampcms\Answer) { $comment = 'My answer to "' . $label . '"'; } d('$comment: ' . $comment . ' label: ' . $label . ' $url: ' . $url); $reward = \Lampcms\Points::SHARED_CONTENT; $User = $this->Registry->Viewer; $token = $User->getLinkedinToken(); $secret = $User->getlinkedinSecret(); try { $oLI = new ApiClient($this->aConfig['OAUTH_KEY'], $this->aConfig['OAUTH_SECRET']); $oLI->setUserToken($token, $secret); } catch (\Exception $e) { e('Error during setup of Linkedin ApiClient object ' . $e->getMessage() . ' in ' . $e->getFile() . ' on ' . $e->getLine()); return $this; } $func = function () use($oLI, $User, $comment, $label, $url, $reward) { try { $oLI->share($comment, $label, $url); } catch (\Exception $e) { return; } $User->setProfitPoint($reward); }; \Lampcms\runLater($func); }
/** * Import user's contacts from Google account * We will be able to use contacts to match new users * with existing members they may already know * * @param int $uid _id of newly created user */ protected function addContacts($uid) { if (\in_array('https://www.google.com/m8/feeds/', $this->scopes)) { $Curl = new \Lampcms\Curl(); $ContactParser = new \Lampcms\Modules\Google\Contacts($this->Registry->Mongo, $Curl); $accessToken = $this->tokenObject->access_token; $func = function () use($ContactParser, $uid, $accessToken) { $ContactParser->import($uid, $accessToken); }; \Lampcms\runLater($func); } }
/** * Post a Link to this question or answer * To User's Facebook Wall * */ protected function post() { try { $reward = \Lampcms\Points::SHARED_CONTENT; $User = $this->Registry->Viewer; $oFB = $this->Registry->Facebook; $Resource = $this->obj; $Mongo = $this->Registry->Mongo; $logo = !empty($this->aFB['SITE_LOGO']) ? $this->aFB['SITE_LOGO'] : null; /** * @todo Translate String(s) of caption * It appears on Facebook Wall under the link * @var string */ $caption = $this->obj instanceof \Lampcms\Question ? 'Please click if you can answer this question' : 'I answered this question'; $description = Utf8String::factory($this->obj['b'], 'utf-8', true)->asPlainText()->valueOf(); } catch (\Exception $e) { d('Unable to post to facebook because of this exception: ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); return; } $func = function () use($oFB, $Resource, $User, $reward, $Mongo, $logo, $caption, $description) { $result = null; $aData = array('link' => $Resource->getUrl(), 'name' => $Resource['title'], 'message' => $caption, 'description' => $description); if (!empty($logo) && 'http' == substr($logo, 0, 4)) { $aData['picture'] = $logo; } try { $result = $oFB->postUpdate($User, $aData); } catch (\Exception $e) { // does not matter } if (!empty($result) && false !== ($decoded = json_decode($result, true))) { /** * If status is OK * then reward the user with points! */ if (!empty($decoded['id'])) { $status_id = (string) $decoded['id']; $User->setProfitPoint($reward); /** * Now need to also record Facebook id * to FB_STATUSES collection */ try { /** * Also save fb_status to QUESTIONS or ANSWERS * collection. * This way later on (maybe way later...) * We can add a function so that if user edits * Post on the site we can also edit it * on Tumblr via API * */ $Resource['fb_status'] = $status_id; $Resource->save(); } catch (\Exception $e) { if (function_exists('e')) { e('Unable to save data to FB_STATUSES collection because of ' . $e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); } } } } }; \Lampcms\runLater($func); }
/** * Notify all who follows the question * But exclude the Viewer - whoever just added * the new answer or whatever * * * and exclude all who follows the Viewer because all who * follows the Viewer will be notified via * the nofityUserFollowers * * @param int qid id of question * * @param int excludeUid UserID of user that should NOT * be notified. Usually this is in a special case of when * the answer or comment owner has already been notified * so now we just have to exclude the same user in case same user * is also the question author. * * @return object $this */ protected function notifyQuestionFollowers($qid = null, $excludeUid = 0) { $viewerID = $this->Registry->Viewer->getUid(); d('$viewerID: ' . $viewerID); /** * * $qid can be passed here * OR in can be extracted from $this->Question * */ if ($qid) { $Question = new \Lampcms\Question($this->Registry); try { $Question->by_id((int) $qid); } catch (\Exception $e) { e($e->getMessage() . ' in file: ' . $e->getFile() . ' on line: ' . $e->getLine()); $Question = null; } } else { $Question = $this->Question; } if (null === $Question) { return $this; } $updateType = 'onNewAnswer' === $this->eventName ? 'answer' : 'comment'; $subj = sprintf(static::$QUESTION_FOLLOW_SUBJ, $updateType); d('cp'); $siteUrl = $this->Registry->Ini->SITE_URL; $username = '******' === $updateType ? $this->obj['username'] : $this->aInfo['username']; $url = 'answer' === $updateType ? $this->obj->getUrl() : $siteUrl . '/q' . $this->aInfo['i_qid'] . '/#c' . $this->aInfo['_id']; d('url: ' . $url); $content = 'comment' === $updateType ? "\n____\n" . \strip_tags($this->aInfo['b']) . "\n" : ''; $body = vsprintf(static::$QUESTION_FOLLOW_BODY, array($username, $updateType, $this->Question['title'], $url, $siteUrl, $content)); d('$body: ' . $body); $oMailer = $this->Registry->Mailer; d('cp'); /** * MongoCollection USERS * @var object MongoCollection */ $coll = $this->collUsers; d('before shutdown function for question followers'); /** * Get array of followers for this question */ $aFollowers = $Question['a_flwrs']; if (!empty($aFollowers)) { $func = function () use($updateType, $viewerID, $aFollowers, $updateType, $subj, $body, $coll, $oMailer, $excludeUid) { /** * Remove $viewerID from aFollowers * Remove excludeID from aFollowers * Removing these userIDs from * the find $in condition is guaranteed to not * have these IDs in result and is much better * than adding extra $ne or $nin conditions * on these uids to find() * */ if (false !== ($key = array_search($viewerID, $aFollowers))) { array_splice($aFollowers, $key, 1); } if (!empty($excludeUid)) { if (false !== ($key = array_search($excludeUid, $aFollowers))) { array_splice($aFollowers, $key, 1); } } array_unique($aFollowers); /** * Find all users who follow this question * and * are not themselves the viewer (a viewer may reply to * own question and we don't want to notify viewer of that) * * In case of comment we should not exclude * user followers because user followers are NOT * notified on user comment * */ if ('comment' !== $updateType) { $cur = $coll->find(array('_id' => array('$in' => $aFollowers), 'a_f_u' => array('$nin' => array(0 => $viewerID)), 'ne_fq' => array('$ne' => true)), array('email')); } else { $cur = $coll->find(array('_id' => array('$in' => $aFollowers), 'ne_fq' => array('$ne' => true)), array('email')); } $count = $cur->count(); if ($count > 0) { /** * Passing callback function * to exclude mailing to those who * opted out on Email On Followed Question */ $oMailer->mailFromCursor($cur, $subj, $body); /** * , function($a){ if(!empty($a['email']) && (!array_key_exists('e_fq', $a) || false !== $a['e_fq'])){ return $a['email']; } return null; } */ } }; \Lampcms\runLater($func); } return $this; }
/** * This is a main mail function to send * email to individual user or to array or recipients * or even by passing Iterator object to it such * as MongoCursor * * The better more fine-tuned method mailFromCursor * should be used when sending mass email and when we know * that cursor contains several hundreds of records * * Sends our emails using the mail() * but can be sent from inside the runLater * This way function returns immediately * * Also accept Iterator so we can use the * cursor in place of $to * * Has the ability to pass in * callback function and that function * would return email address or false * if email should not be sent * This will be used if $to is iterator or array * and contains fields like e_ft = null or does not exist * So we can check if (empty($a['e_ft']){ * return false // skip this one * } else { * return $a['email']; * } * * @todo for best result we should actually fork another php * script as regular cgi script (not fastcgi, just cgi php) * and just pass arguments to it and let it run as long as necessary, * looping over the array of recipients and sending out emails * The problem is that we cannot just pass cursor or even an array * of recipients to a cgi like this, we need to somehow pass data * differently like not the actual data but the conditions and then * let the cgi find that data and use it. We can do this via a cache * just put array into cache and pass cache key to cgi script * that would work for up to 4MB of data * And then let cgi delete that key and just in case set the expiration * on that key too so it will be deleted eventually even if cgi * dies for some reason * * @param mixed $to * @param string $subject * @param string $body * * @param bool $sendLater if true (default) then will run * via the runLater, otherwise * will run immediately. So if this method itself is * invoked from some registered shutdown function then it * makes sense to not use the 'later' feature. * * @param function $func callback function to be applied * to each record of the $to array. * * @return bool * @throws DevException */ public function mail($to, $subject, $body = null, $func = null, $sendLater = true) { if (!is_string($to) && !is_array($to) && !is_callable($to) && (!is_object($to) || !$to instanceof \Iterator)) { $class = \is_object($to) ? \get_class($to) : 'not an object'; throw new DevException('$to can be only string or array or object implementing Iterator. Was: ' . \gettype($to) . ' class: ' . $class); } if (null === $body && (!is_object($subject) || !$subject instanceof \Swift_Message)) { throw new DevException('$body cannot be null if $subject is not instance of \\Swift_Message'); } if (\is_string($to)) { $aTo = array($to); } elseif (\is_array($to)) { $aTo = $to; } else { d('$to is callable function'); } $fromEmail = $this->adminEmail; $fromName = $this->siteName; $Swift = $this->SwiftMailer; $Cache = $this->Cache; $callable = function () use($subject, $body, $fromEmail, $fromName, $aTo, $func, $Swift, $Cache) { $translatedMessages = array(); $translators = array(); if (!is_array($aTo) && is_callable($aTo)) { $aTo = $aTo(); } $total = is_array($aTo) ? count($aTo) : $aTo->count(); /** * @todo deal with breaking up * the long array/cursor into * smaller chunks and send emails * in groups on N then wait N seconds * This is handled differently for cursors and for arrays */ if ($total > 0) { $failedRecipients = array(); $numSent = 0; try { $message = is_object($subject) && $subject instanceof \Swift_Message ? $subject : \Swift_Message::newInstance($subject)->setFrom(array($fromEmail => $fromName))->setBody($body)->setCharset('utf-8'); } catch (\Exception $e) { /** * Do not use error-level logging because error log e($message) * sends out email to admin. * if we failed to send out email we don't want to attempt to notify * admin by email - it can create infinite loop */ d('Unable to create email message: ' . $e->getMessage() . ' Exception: ' . get_class($e)); return; } foreach ($aTo as $to) { /** * Deal with format of array when array * is result of iterator_to_array($cur) * where $cur is MongoCursor - result of * find() */ if (is_array($to)) { /** * If callback function is passed to mail() * then it must accept array or * one user record as argument and must * return either email address (string) * or false if record should be skipped * For example, if array contains something like this * 'email' => user@email.com, * 'e_ft' => null * * Which indicates that user does not want * to receive emails on followed tag * * This is a way to pass callback to serve * as a filter - to users who opted out * or receiving emails on specific events * will not receive them * * Since each opt-out flag is different, the * each callback for specific type of mailing * will also be different. * */ if (is_callable($func)) { $to = $func($to); } elseif (!empty($to['email'])) { $to = $to['email']; } } /** * Now it is possible that callback * function returned null or false so we * must now check that $to is not * empty * Also if array did not contain * the 'email' key then nothing will * be sent at this point because * the $to will be !is_string() at * this time - it will still be array */ if (empty($to) || !is_string($to)) { continue; } /** * If user has locale and subject or body * is Translatable then translate message * and create locale-specific message object * */ $ER = error_reporting(0); $message->setTo($to); try { $numSent += $Swift->send($message, $failedRecipients); } catch (\Exception $e) { /** * Do not user error-level loggin because error log e($message) * sends out email to admin. * if we failed to send out email we don't want to attempt to notify * admin by email - it can create infinite loop */ d('Unable to send email: ' . $e->getMessage() . ' Exception: ' . get_class($e)); } if (!empty($failedRecipients)) { d('Failed recipients: ' . json_encode($failedRecipients)); } error_reporting($ER); } d('sent ' . $numSent . ' emails'); } }; if ($sendLater) { \Lampcms\runLater($callable); } else { $callable(); } return true; }
/** * This is a main mail function to send * email to individual user or to array or receipients * or even by passing Iterator object to it such * as MongoCursor * * The better more fine-tuned method mailFromCursor * should be used when sending mass email and when we know * that cursor contains several hundreds of records * * Sends our emails using the mail() * but can be sent from inside the runLater * This way function returns immediately * * Also accept Iteractor so we can use the * cursor in place of $to * * Has the ability to pass in * callback function and that function * would return email address or false * if email should not be sent * This will be used if $to is iterator or array * and contains fields like e_ft = null or does not exist * So we can check if (empty($a['e_ft']){ * return flase // skip this one * } else { * return $a['email']; * } * *@todo for best result we should actually fork another php *script as regular cgi script (not fastcgi, just cgi php) *and just pass arguments to it and let it run as long as necessary, *looping over the array of recepients and sending out emails *The problem is that we cannot just pass cursor or even an array *of receipients to a cgi like this, we need to somehow pass data *differently like not the actual data but the conditions and then *let the cgi find that data and use it. We can do this via a cache *just put array into cache and pass cache key to cgi script *that would work for up to 4MB of data *And then let cgi delete that key and just in case set the expiration *on that key too so it will be deleted eventually even if cgi *dies for some reason * * @param mixed $to * @param string $subject * @param string $body * * @param bool $sendLater if true (default) then will run * via the runLater, otherwise * will run immediately. So if this method itself is * invoked from some registered shutdown function then it * makes sense to not use the 'later' feature. * * @param function $func callback function to be applied * to each record of the array. * * @throws DevException */ public function mail($to, $subject, $body, $func = null, $sendLater = true) { if (!is_string($to) && !is_array($to) && (!is_object($to) || !$to instanceof \Iterator)) { $class = is_object($to) ? get_class($to) : 'not an object'; throw new DevException('$to can be only string or array or object implementing Iterator. Was: ' . gettype($to) . ' class: ' . $class); } $aTo = is_string($to) ? (array) $to : $to; $aHeaders = array(); $aHeaders['From'] = $this->from; $aHeaders['Reply-To'] = $this->adminEmail; $headers = \Lampcms\prepareHeaders($aHeaders); d('$subject: ' . $subject . ' body: ' . $body . ' headers: ' . $headers . ' $aTo: ' . print_r($aTo, 1)); $callable = function () use($subject, $body, $headers, $aTo, $func) { $total = is_array($aTo) ? count($aTo) : $aTo->count(); /** * @todo deal with breaking up * the long array/cursor into * smaller chunks and send emails * in groups on N then wait N seconds * This is handled differently for cursors and for arrays */ if ($total > 0) { foreach ($aTo as $to) { /** * Deal with format of array when array * is result of iterator_to_array($cur) * where $cur is MongoCursor - result of * find() */ if (is_array($to)) { /** * If callback function is passed to mail() * then it must accept array or * one user record as argument and must * return either email address (string) * or false if record should be skipped * For example, if array contains something like this * 'email' => user@email.com, * 'e_ft' => null * * Which indicates that user does not want * to receive emails on followed tag * * This is a way to pass callback to serve * as a filter - to users who opted out * or receiving emails on specific events * will not receive them * * Since each opt-out flag is different, the * each callback for specific type of mailing * will also be different. * */ if (is_callable($func)) { $to = $func($to); } elseif (!empty($to['email'])) { $to = $to['email']; } } /** * Now it is possible that callback * function returned null or false so we * must now check that $to is not * empty * Also if array did not contain * the 'email' key then nothing will * be sent at this point because * the $to will be !is_string() at * this time - it will still be array */ if (empty($to) || !is_string($to)) { continue; } $ER = error_reporting(0); if (true !== @\mail($to, $subject, $body, $headers)) { // was unable to send out email if (function_exists('e')) { e('unable to send email to ' . $to . ' subject: ' . $subj . ' body: ' . $body); } } error_reporting($ER); } } }; if ($sendLater) { \Lampcms\runLater($callable); } else { $callable(); } return true; }
protected function notifyModerators() { /** * Proceed ONLY when Question or Answer has * a PENDING status */ d('need to notify moderators of pending item'); $coll = $this->Registry->Mongo->USERS; $Mailer = $this->Registry->Mailer; $Mailer->setCache($this->Registry->Cache); if ('onNewPendingQuestion' == $this->eventName) { $subj = new TranslatableSubject('email.subject.new_question_moderation'); $tpl = 'email.body.new_question_moderation'; $vars = array('{username}' => $this->obj['username'], '{title}' => $this->obj['title'], '{body}' => $this->obj['intro'], '{link}' => $this->obj->getUrl()); $body = new TranslatableBody($tpl, $vars); } else { $subj = new TranslatableSubject('email.subject.new_answer_moderation'); $tpl = 'email.body.new_answer_moderation'; $vars = array('{username}' => $this->obj['username'], '{title}' => $this->obj->getTitle(), '{body}' => \strip_tags($this->obj['b']), '{link}' => $this->obj->getUrl()); $body = new TranslatableBody($tpl, $vars); } $func = function () use($subj, $body, $coll, $Mailer) { $cur = $coll->find(array('role' => array('$in' => array('moderator', 'administrator'))), array('email' => true, 'locale' => true)); $count = $cur->count(); if ($count > 0) { $Mailer->mailFromCursor($cur, $subj, $body); } }; \Lampcms\runLater($func); return $this; }
/** * Update LOGIN_LOG collection * */ protected function run() { $Viewer = $this->Registry->Viewer; if (!is_object($Viewer)) { d('Could not get Viewer object'); return; } $ip = Request::getIP(); $uid = $Viewer->getUid(); d('uid: ' . $uid); if ($uid > 0) { $aData = array('ip' => $ip, 'i_uid' => $uid, 'i_ts' => time(), 'ua' => Request::getUserAgent(), 'login_method' => $this->loginMethod); $Mongo = $this->Registry->Mongo->getDb(); //$Geo = $this->Registry->Geo; $func = function () use($aData, $Mongo) { //$aGeo = $Geo->getLocation($aData['ip'])->toArray(); //$aData = $aData + $aGeo; $coll = $Mongo->LOGIN_LOG; $coll->ensureIndex(array('i_uid' => 1)); $coll->ensureIndex(array('ip' => 1)); $coll->insert($aData); }; \Lampcms\runLater($func); } }