/** * Track when an email is viewed, a link is clicked, or the recipient unsubscribes * * Campaign emails include an img tag to a blank image to track when the message was opened, * an unsubscribe link, and converted links to track when a recipient clicks a link. * All those links are handled by this action. * * @param integer $uid The unique id of the recipient * @param string $type 'open', 'click', or 'unsub' * @param string $url For click types, this is the urlencoded URL to redirect to * @param string $email For unsub types, this is the urlencoded email address * of the person unsubscribing */ public function actionClick($uid, $type, $url = null, $email = null) { $now = time(); $item = CActiveRecord::model('X2ListItem')->with('contact', 'list')->findByAttributes(array('uniqueId' => $uid)); // It should never happen that we have a list item without a campaign, // but it WILL happen on any old db where x2_list_items does not cascade on delete // we can't track anything if the listitem was deleted, but at least prevent breaking links if ($item === null || $item->list->campaign === null) { if ($type == 'click') { // campaign redirect link click $this->redirect(urldecode($url)); } elseif ($type == 'open') { //return a one pixel transparent gif header('Content-Type: image/gif'); echo base64_decode('R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); } elseif ($type == 'unsub' && !empty($email)) { Contacts::model()->updateAll(array('doNotEmail' => true), 'email=:email', array(':email' => $email)); X2ListItem::model()->updateAll(array('unsubscribed' => time()), 'emailAddress=:email AND unsubscribed=0', array('email' => $email)); $message = Yii::t('marketing', 'You have been unsubscribed'); echo '<html><head><title>' . $message . '</title></head><body>' . $message . '</body></html>'; } return; } $contact = $item->contact; $list = $item->list; $event = new Events(); $notif = new Notification(); $action = new Actions(); $action->completeDate = $now; $action->complete = 'Yes'; $action->updatedBy = 'API'; $skipActionEvent = true; if ($contact !== null) { $skipActionEvent = false; if ($email === null) { $email = $contact->email; } $action->associationType = 'contacts'; $action->associationId = $contact->id; $action->associationName = $contact->name; $action->visibility = $contact->visibility; $action->assignedTo = $contact->assignedTo; $event->associationId = $action->associationId; $event->associationType = 'Contacts'; if ($action->assignedTo !== '' && $action->assignedTo !== 'Anyone') { $notif->user = $contact->assignedTo; $notif->modelType = 'Contacts'; $notif->modelId = $contact->id; $notif->createDate = $now; $notif->value = $item->list->campaign->getLink(); } } elseif ($list !== null) { $action = new Actions(); $action->type = 'note'; $action->createDate = $now; $action->lastUpdated = $now; $action->completeDate = $now; $action->complete = 'Yes'; $action->updatedBy = 'admin'; $action->associationType = 'X2List'; $action->associationId = $list->id; $action->associationName = $list->name; $action->visibility = $list->visibility; $action->assignedTo = $list->assignedTo; } if ($type == 'unsub') { $item->unsubscribe(); // find any weblists associated with the email address and create unsubscribe actions // for each of them $sql = 'SELECT t.* FROM x2_lists as t JOIN x2_list_items as li ON t.id=li.listId WHERE li.emailAddress=:email AND t.type="weblist";'; $weblists = Yii::app()->db->createCommand($sql)->queryAll(true, array('email' => $email)); foreach ($weblists as $weblist) { $weblistAction = new Actions(); $weblistAction->disableBehavior('changelog'); //$weblistAction->id = 0; // this causes primary key contraint violation errors $weblistAction->isNewRecord = true; $weblistAction->type = 'email_unsubscribed'; $weblistAction->associationType = 'X2List'; $weblistAction->associationId = $weblist['id']; $weblistAction->associationName = $weblist['name']; $weblistAction->visibility = $weblist['visibility']; $weblistAction->assignedTo = $weblist['assignedTo']; $weblistAction->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . $email . " " . Yii::t('marketing', 'has unsubscribed') . "."; $weblistAction->save(); } $action->type = 'email_unsubscribed'; $notif->type = 'email_unsubscribed'; if ($contact === null) { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . $item->emailAddress . ' ' . Yii::t('marketing', 'has unsubscribed') . "."; } else { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . Yii::t('marketing', 'Contact has unsubscribed') . ".\n" . Yii::t('marketing', '\'Do Not Email\' has been set') . "."; } $message = Yii::t('marketing', 'You have been unsubscribed'); echo '<html><head><title>' . $message . '</title></head><body>' . $message . '</body></html>'; } elseif ($type == 'open') { //return a one pixel transparent gif header('Content-Type: image/gif'); echo base64_decode('R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); // Check if it has been marked as opened already, or if the contact // no longer exists. If so, exit; nothing more need be done. if ($item->opened != 0) { Yii::app()->end(); } // This needs to happen before the skip option to accomodate the case of newsletters $item->markOpened(); if ($skipActionEvent) { Yii::app()->end(); } $action->disableBehavior('changelog'); $action->type = 'campaignEmailOpened'; $event->type = 'email_opened'; $notif->type = 'email_opened'; $event->save(); if ($contact === null) { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . $item->emailAddress . ' ' . Yii::t('marketing', 'has opened the email') . "."; } else { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . Yii::t('marketing', 'Contact has opened the email') . "."; } } elseif ($type == 'click') { // redirect link click $item->markClicked($url); $action->type = 'email_clicked'; $notif->type = 'email_clicked'; if ($contact === null) { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . Yii::t('marketing', 'Contact has clicked a link') . ":\n" . urldecode($url); } else { $action->actionDescription = Yii::t('marketing', 'Campaign') . ': ' . $item->list->campaign->name . "\n\n" . $item->emailAddress . ' ' . Yii::t('marketing', 'has clicked a link') . ":\n" . urldecode($url); } $this->redirect(urldecode($url)); } $action->save(); // if any of these hasn't been fully configured $notif->save(); // it will simply not validate and not be saved }
/** * Starts a workflow stage * @param int $workflowId * @param int $stageNumber the stage to start * @param object $model model associated with workflow */ public static function startStage($workflowId, $stageNumber, $model, $workflowStatus = null) { //AuxLib::debugLogR ('starting stage '.$stageNumber); $modelId = $model->id; $type = lcfirst(X2Model::getModuleName(get_class($model))); if (!$workflowStatus) { $workflowStatus = Workflow::getWorkflowStatus($workflowId, $modelId, $type); } //AuxLib::debugLogR ($workflowStatus); //assert ($model !== null); $started = false; // if stage has not yet been started or completed if ($model !== null && self::checkStageRequirement($stageNumber, $workflowStatus) && !self::isStarted($workflowStatus, $stageNumber)) { $action = new Actions('workflow'); // don't genererate normal action changelog/triggers/events $action->disableBehavior('changelog'); $action->disableBehavior('tags'); // no tags up in here $action->associationId = $modelId; $action->associationType = $type; $action->assignedTo = Yii::app()->user->getName(); $action->updatedBy = Yii::app()->user->getName(); $action->complete = 'No'; $action->type = 'workflow'; $action->visibility = 1; $action->createDate = time(); $action->lastUpdated = time(); $action->workflowId = (int) $workflowId; $action->stageNumber = (int) $stageNumber; $action->save(); $model->updateLastActivity(); X2Flow::trigger('WorkflowStartStageTrigger', array('workflow' => $action->workflow, 'model' => $model, 'workflowId' => $action->workflow->id, 'stageNumber' => $stageNumber)); if (!$workflowStatus['started']) { X2Flow::trigger('WorkflowStartTrigger', array('workflow' => $action->workflow, 'model' => $model, 'workflowId' => $action->workflow->id)); } self::updateWorkflowChangelog($action, 'start', $model); $workflowStatus = Workflow::getWorkflowStatus($workflowId, $modelId, $type); $started = true; } //AuxLib::debugLogR ((int) $started); return array($started, $workflowStatus); }
/** * Make records of the email in every shape and form. * * This method is to be called only once the email has been sent. * * The basic principle behind what all is happening here: emails are getting * sent to people. Since the "To:" field in the inline email form is not * read-only, the emails could be sent to completely different people. Thus, * creating action and event records must be based exclusively on who the * email is addressed to and not the model from whose view the inline email * form (if that's how this model is being used) is submitted. */ public function recordEmailSent($makeEvent = true) { // The email record, with action header for display purposes: $emailRecordBody = $this->insertInBody(self::insertedPattern('ah', $this->actionHeader), 1, 1); $now = time(); $recipientContacts = array_filter($this->recipientContacts); if (!empty($this->targetModel)) { $model = $this->targetModel; if ((bool) $model) { if ($model->hasAttribute('lastActivity')) { $model->lastActivity = $now; $model->save(); } } $action = new Actions(); // These attributes will be the same regardless of the type of // email being sent: $action->completedBy = $this->userProfile->username; $action->createDate = $now; $action->dueDate = $now; $action->subject = $this->subject; $action->completeDate = $now; $action->complete = 'Yes'; $action->actionDescription = $emailRecordBody; // These attributes are context-sensitive and subject to change: $action->associationId = $model->id; $action->associationType = $model->module; $action->type = 'email'; $action->visibility = isset($model->visibility) ? $model->visibility : 1; $action->assignedTo = $this->userProfile->username; if ($this->modelName == 'Quote') { // Is an email being sent to the primary // contact on the quote? If so, the user is "issuing" the quote or // invoice, and it should have a special type. if (!empty($this->targetModel->contact)) { if (array_key_exists($this->targetModel->contact->email, $recipientContacts)) { $action->associationType = lcfirst(get_class($model)); $action->associationId = $model->id; $action->type .= '_' . ($model->type == 'invoice' ? 'invoice' : 'quote'); $action->visibility = 1; $action->assignedTo = $model->assignedTo; } } } if ($makeEvent && $action->save()) { $this->trackEmail($action->id); // Create a corresponding event record. Note that special cases // may have to be written in the method Events->getText to // accommodate special association types apart from contacts, // in addition to special-case-handling here. if ($makeEvent) { $event = new Events(); $event->type = 'email_sent'; $event->subtype = 'email'; $event->associationType = $model->myModelName; $event->associationId = $model->id; $event->user = $this->userProfile->username; if ($this->modelName == 'Quote') { // Special "quote issued" or "invoice issued" event: $event->subtype = 'quote'; if ($this->targetModel->type == 'invoice') { $event->subtype = 'invoice'; } $event->associationType = $this->modelName; $event->associationId = $this->modelId; } $event->save(); } } } // Create action history events and event feed events for all contacts that were in the // recipient list: if ($this->contactFlag) { foreach ($recipientContacts as $email => $contact) { $contact->lastActivity = $now; $contact->update(array('lastActivity')); $skip = false; $skipEvent = false; if ($this->targetModel && get_class($this->targetModel) === 'Contacts') { $skip = $this->targetModel->id == $contact->id; } else { if ($this->modelName == 'Quote') { // An event has already been made for issuing the quote and // so another event would be redundant. $skipEvent = $this->targetModel->associatedContacts == $contact->nameId; } } if ($skip) { // Only save the action history item/event if this hasn't // already been done. continue; } // These attributes will be the same regardless of the type of // email being sent: $action = new Actions(); $action->completedBy = $this->userProfile->username; $action->createDate = $now; $action->dueDate = $now; $action->completeDate = $now; $action->complete = 'Yes'; // These attributes are context-sensitive and subject to change: $action->associationId = $contact->id; $action->associationType = 'contacts'; $action->type = 'email'; $action->visibility = isset($contact->visibility) ? $contact->visibility : 1; $action->assignedTo = $this->userProfile->username; // Set the action's text to the modified email body $action->actionDescription = $emailRecordBody; // We don't really care about changelog events for emails; they're // set in stone anyways. $action->disableBehavior('changelog'); if ($action->save()) { // Now create an event for it: if ($makeEvent && !$skipEvent) { $event = new Events(); $event->type = 'email_sent'; $event->subtype = 'email'; $event->associationType = $contact->myModelName; $event->associationId = $contact->id; $event->user = $this->userProfile->username; $event->save(); } } } // Loop over contacts } // Conditional statement: do all this only if the flag to perform action history creation for all contacts has been set // At this stage, if email tracking is to take place, "$action" should // refer to the action history item of the one and only recipient contact, // because there has been only one element in the recipient array to loop // over. If the target model is a contact, and the one recipient is the // contact itself, the action will be as declared before the above loop, // and it will thus still be properly associated with that contact. }
public function getAction($new = false) { if (!isset($this->_action) || $new) { if (!Yii::app()->user->isGuest) { $model = new Actions(); } else { $model = new Actions('guestCreate'); } if (!empty($this->type)) { $model->disableBehavior('changelog'); } $this->_action = $model; } return $this->_action; }