/** * RunUpgrade * Runs the populate_user_unique_token upgrade * * @return Boolean Returns TRUE if successful, FALSE otherwise */ function RunUpgrade() { $result = $this->Db->Query('SELECT username, password FROM ' . SENDSTUDIO_TABLEPREFIX . 'users'); if ($result === false) { return false; } while ($row = $this->Db->Fetch($result)) { $new_token = API_USERS::generateUniqueToken($row['username']); $new_password = md5(md5($new_token) . $row['password']); $query = 'UPDATE ' . SENDSTUDIO_TABLEPREFIX . 'users '; $query .= " SET unique_token='" . $this->Db->Quote($new_token) . "'"; $query .= ", password='******'"; $query .= " WHERE username='******'username']) . "'"; $status = $this->Db->Query($query); if ($status === false) { return false; } } return true; }
/** * ProcessJob * * This function will process all records in the queues that are related to the trigger emails. * The queues only hold record of trigger emails that is ready to be sent out in the next 24 hours. * Another process will take care populating the queue table. * * @return Boolean Returns TRUE if successful, FALSE otherwise * * @see Jobs_TriggerEmails_API::ProcessPopulateJob() * @uses SENDSTUDIO_TABLEPREFIX * @uses Jobs_TriggerEmails_API::RECORDS_PER_PROCESS * @uses Jobs_TriggerEmails_API::_log() * @uses Jobs_TriggerEmails_API::_cacheTriggerRecordClean() * @uses Db::Query() * @uses Db::GetError() * @uses Db::Fetch() * @uses Db::FreeResult() * @uses Jobs_TriggerEmails_API::_cacheTriggerRecordGet() * @uses Jobs_TriggerEmails_API::_cacheNewsletterGet() * @uses Jobs_TriggerEmails_API::_send() * @uses Jobs_TriggerEmails_API::UpdateJob() * @uses Jobs_TriggerEmails_API::_markQueueRecordAsProcessed() */ public function ProcessJob() { $last_owner_id = null; $last_stat_id = null; $last_job_id = null; $current_available_credit = null; $credit_used = 0; $this->_log('Start processing Send Job'); // Clean up old caches $this->_cacheTriggerRecordClean(); // ----- Setup Stats API if it's not already available if (is_null($this->_statsAPIObject)) { require_once dirname(__FILE__) . '/stats.php'; $this->_statsAPIObject = new Stats_API(); } // ----- $queues = array(); do { // ----- Get all triggeremail queues that is due to be sent out $now = date('Y-m-d H:i:s', time()); $limit = self::RECORDS_PER_PROCESS; $query = " SELECT * FROM [|PREFIX|]queues WHERE queuetype='triggeremail' AND processed='0' AND processtime <= '{$now}' ORDER BY queueid, processtime ASC LIMIT {$limit} "; $resQueue = $this->Db->Query($query); if (!$resQueue) { list($msg, $errno) = $this->Db->GetError(); $this->_log('Cannot get queues: ' . $msg); trigger_error($msg, $errno); return false; } $queues = array(); while (($row = $this->Db->Fetch($resQueue))) { array_push($queues, $row); } $this->Db->FreeResult($resQueue); // ----- // If nothing to be done in the queues, then get out from the loop if (empty($queues)) { break; } // ----- Loop against the queue to process the triggers foreach ($queues as $queue) { $doactions = true; $error = false; $queueid = $queue['queueid']; $recipientid = $queue['recipient']; // Get trigger record, if there is an error fetching the trigger it will NOT send the trigger $trigger = $this->_cacheTriggerRecordGet($queueid); if ($trigger === false) { $error = true; $doactions = false; $this->_log('Cannot get trigger record for queueid: ' . $queueid); } // Update credit related procedure if (is_null($last_owner_id)) { $last_owner_id = $trigger['ownerid']; $last_stat_id = $trigger['statid']; $last_job_id = $trigger['jobid']; // Current available credit returns TRUE if credit is unlimited $current_available_credit = true; if (SENDSTUDIO_CREDIT_INCLUDE_TRIGGERS) { $current_available_credit = API_USERS::creditAvailableTotal($trigger['ownerid']); } } // Check if trigger is active if (!$trigger['active']) { $error = true; $this->_log('Trigger is marked as INACTIVE'); } // Have trigger exceeded it's running interval for this subscriber? // Or trigger has recently been actioned on. if (!$error && !$this->_checkInterval($trigger, $recipientid)) { $doactions = false; $this->_log('Do not proceed with trigger actions, as action interval has been exceeded for this subscriber.'); } // ----- Check whether the recipient exists or not $status = $this->Db->Query("SELECT subscriberid FROM [|PREFIX|]list_subscribers WHERE subscriberid = {$recipientid}"); if (!$status) { $this->_log('Cannot check database for particular subscriber'); return false; } $row = $this->Db->Fetch($status); $this->Db->FreeResult($status); if (empty($row)) { $doactions = false; $this->_log('Do not proceed with trigger actions... no subscribers found'); } // ----- if ($doactions && !$error) { if (!empty($trigger) && isset($trigger['triggeractions']) && is_array($trigger['triggeractions'])) { if (array_key_exists('send', $trigger['triggeractions'])) { // Send only when credit is available if (($current_available_credit === true) || ($current_available_credit > 0 && $current_available_credit > $credit_used)) { $status = $this->_ProcessJob_Send($queue, $trigger, $recipientid); if ($status['halt']) { return false; } if ($status['error']) { $error = true; } } else { $rs = $this->Db->Query("SELECT emailaddress FROM [|PREFIX|]list_subscribers WHERE subscriberid = {$recipientid}"); $emailaddress = $this->Db->FetchOne($rs, 'emailaddress'); // Log failed sending $this->RecordLogActions($trigger['triggeremailsid'], $recipientid, 'send_failed', $emailaddress); trigger_error('Insuficient credits! Cannot send newsletter with queueid:' . $queue['queueid'] . ', and recipient:' . $recipientid); $error = true; } // ----- Record sending so that it counted towards credit usage // Increment credit use if (!$error) { ++$credit_used; } // Record usage whenever the statid changed // NOTE: Each trigger is assigned a different statid and jobid if ($last_stat_id != $trigger['statid']) { if ($current_available_credit !== true) { $status = API_USERS::creditUse($last_owner_id, API_USERS::CREDIT_USAGETYPE_SENDTRIGGER, $credit_used, $last_job_id, $last_stat_id); if (!$status) { $this->_log("Cannot record usage -- userid = {$last_owner_id}, credit_used = {$credit_used}, job_id = {$last_job_id}, stat_id = {$last_stat_id}"); return false; } } $last_owner_id = $trigger['ownerid']; $last_stat_id = $trigger['statid']; $last_job_id = $trigger['jobid']; $current_available_credit = true; if (SENDSTUDIO_CREDIT_INCLUDE_TRIGGERS) { $current_available_credit = API_USERS::creditAvailableTotal($trigger['ownerid']); } $credit_used = 0; } // ----- } if (array_key_exists('addlist', $trigger['triggeractions'])) { $status = $this->_ProcessJob_AddList($queue, $trigger, $recipientid); if ($status['halt']) { return false; } if ($status['error']) { $error = true; } } if (array_key_exists('removelist', $trigger['triggeractions'])) { $status = $this->_ProcessJob_RemoveList($queue, $trigger, $recipientid); if ($status['halt']) { return false; } if ($status['error']) { $error = true; } } } } // Update log statistics if "removelist" is not enabled, otherwise it will be a wasteful exercise if ($doactions) { // Record summary if (!$this->RecordLogSummary($trigger['triggeremailsid'], $recipientid)) { $this->_log('Cannot write log summary to the database... exitting'); return false; } } // update lastupdatedtime so we can track what's going on. // This is used so we can see if the server has crashed or the cron job has been stopped in mid-send. if (!$this->UpdateJob('triggeremails_send', 'i', time())) { $this->_log('Cannot update job... Exitting'); return false; } // Mark queue record as processed, if cannot mark queue as processed, terminate if (!$this->_markQueueRecordAsProcessed($queueid, $recipientid)) { $this->_log('Cannot mark queue record qith queueueid:' . $queueid . ', and recipient:' . $recipientid . ' as processed'); return false; } } // ----- // ----- Clean up queue $status = $this->Db->Query("DELETE FROM [|PREFIX|]queues WHERE queuetype='triggeremail' AND processed='1'"); if (!$status) { list($msg, $errno) = $this->Db->GetError(); $this->_log('Cannot cleanup queue: ' . $msg); trigger_error($msg, $errno); return false; } // ----- } while (!empty($queues)); // ----- If there are leftover credits that haven't been recorded, then record it here if ($credit_used > 0) { $status = API_USERS::creditUse($last_owner_id, API_USERS::CREDIT_USAGETYPE_SENDTRIGGER, $credit_used, $last_job_id, $last_stat_id); if (!$status) { $this->_log("Cannot record usage -- userid = {$last_owner_id}, credit_used = {$credit_used}, job_id = {$last_job_id}, stat_id = {$last_stat_id}"); return false; } } // ----- return true; }
/** * IEM_Menu * This builds both the nav menu (with the dropdown items) and the text menu links at the top * It gets the main nav items from SendStudio_Functions::GenerateMenuLinks * It gets the text menu items from SendStudio_Functions::GenerateTextMenuLinks * * It will also see if test-mode is enabled (and display an appropriate message) * and also generate the right headers at the top (user is logged in as 'X', the current time is 'Y' etc). * * <b>Do *not* put any "ParseTemplate" calls inside IEM_Menu as you will cause an infinite loop.</b> * "ParseTemplate" calls "IEM_Menu" via IEM_DefaultVariables * Since the header menu has not yet finished building (ie the $menu variable is still null), * calling IEM_Menu at this stage will then call ParseTemplate (which then calls IEM_Menu). * * It returns an array: * - the first item is the main nav menu (contact lists, contacts, email campaigns etc) * - the second item is the text menu links at the top of the page (templates, users/manage account, logout etc) * * @uses SendStudio_Functions::GenerateMenuLinks * @uses SendStudio_Functions::GenerateTextMenuLinks * * @return Array Returns an array containing the main nav menu (the first item of the array) and the text menu items (the second item of the array). */ private function IEM_Menu() { static $menu = null; // we've already built the menu? just return it. if ($menu !== null) { return $menu; } // see if there is an upgrade required or problem with the lk. if (!isset($_GET['Page']) || strtolower($_GET['Page']) != 'upgradenx') { if (IEM::sessionGet('LicenseError')) { if (!isset($_GET['Page']) || strtolower($_GET['Page']) != 'settings') { header('Location: index.php?Page=Settings'); exit; } } } $user = IEM::getCurrentUser(); // we're not logged in? we don't have a menu so just return empty items. if (!$user) { $menu = array('', ''); return $menu; } $textlinks = SendStudio_Functions::GenerateTextMenuLinks(); $nav_menus = ''; if (!IEM::sessionGet('LicenseError')) { $nav_menus = SendStudio_Functions::GenerateMenuLinks(); } $GLOBALS['UsingWYSIWYG'] = '0'; if ($user->Get('usewysiwyg') == 1) { $GLOBALS['UsingWYSIWYG'] = '1'; } $adjustedtime = AdjustTime(); $GLOBALS['SystemDateTime'] = sprintf(GetLang('UserDateHeader'), AdjustTime($adjustedtime, false, GetLang('UserDateFormat'), true), $user->Get('usertimezone')); $name = $user->Get('username'); $fullname = $user->Get('fullname'); if ($fullname != '') { $name = $fullname; } $GLOBALS['UserLoggedInAs'] = sprintf(GetLang('LoggedInAs'), htmlentities($name, ENT_QUOTES, SENDSTUDIO_CHARSET)); $unlimited_total_emails = $user->hasUnlimitedTotalCredit(); if (!$unlimited_total_emails) { $creditUsed = API_USERS::getRecordById($user->userid)->getUsedCredit(); $creditLeft = (int) $user->group->limit_totalemailslimit - (int) $creditUsed; $GLOBALS['TotalEmailCredits'] = sprintf(GetLang('User_Total_CreditsLeft'), SendStudio_Functions::FormatNumber($creditLeft)); } $GLOBALS['MonthlyEmailCredits'] = ''; $unlimited_monthly_emails = $user->hasUnlimitedMonthlyCredit(); if (!$unlimited_monthly_emails) { $creditUsed = API_USERS::getRecordById($user->userid)->getUsedMonthlyCredit(); $creditLeft = (int) $user->group->limit_emailspermonth - (int) $creditUsed; $GLOBALS['MonthlyEmailCredits'] = sprintf(GetLang('User_Monthly_CreditsLeft'), SendStudio_Functions::FormatNumber($creditLeft), SendStudio_Functions::FormatNumber($user->group->limit_emailspermonth)); if (!$unlimited_total_emails) { $GLOBALS['MonthlyEmailCredits'] .= ' |'; } } $menu = array($nav_menus, $textlinks); return $menu; }
/** * CheckPermission * Check if user have access to subscribers * * @param Integer $userid User ID that we want to check the permission against * @param Mixed $subscribers Subscribers ID that needed to be checked against subscriber's permission * @return Boolean Returns TRUE if user have access, FALSE otherwise */ function CheckPermission($userid, $subscribers) { $userid = intval($userid); $user = API_USERS::getRecordById($userid); $checkedUser = &GetUser($userid); if ($checkedUser->Admin() || $checkedUser->ListAdmin() || $checkedUser->ListAdminType() == 'a') { return true; } $tablePrefix = SENDSTUDIO_TABLEPREFIX; if (!is_array($subscribers)) { $subscribers = array($subscribers); } $subscribers = $this->CheckIntVars($subscribers); $subscribers = array_unique($subscribers); if (empty($subscribers)) { return false; } $implodedSubscribers = implode(',', $subscribers); $query = trim(" SELECT l.ownerid AS ownerid, ac.groupid AS groupid FROM {$tablePrefix}list_subscribers AS ls JOIN {$tablePrefix}lists AS l ON ( ls.listid = l.listid AND ls.subscriberid IN ({$implodedSubscribers}) ) LEFT JOIN {$tablePrefix}usergroups_access AS ac ON ( l.listid = ac.resourceid AND ac.groupid = {$user->groupid} ) "); $result = $this->Db->Query($query); if (!$result) { list($msg, $errno) = $this->Db->GetError(); trigger_error($msg, $errno); return false; } $row_count = 0; while ($row = $this->Db->Fetch($result)) { if ($row['ownerid'] != $userid && $row['groupid'] != $user->groupid) { $row_count = 0; break; } ++$row_count; } $this->Db->FreeResult($result); return ($row_count >= count($subscribers)); }
/** * GetListByUserID * Get available lists for a particular user. * The function will caches it's result in the $GLOBAL variable, which will be refreshed for each request. * The cache should also be cleared when a list has been saved/created/deleted * * The cache is stored in $GLOBALS['Lists_API::GetListByUserID[listCache]'] * * The following functions in this class will delete the cache in $GLOBALS * - Create() * - Copy() * - Delete() * - Save() * - MergeList() * - DeleteAllSubscribers() * * @see Lists_API::Create() * @see Lists_API::Copy() * @see Lists_API::Delete() * @see Lists_API::Save() * @see Lists_API::DeleteAllSubscribers() * @see Lists_API::MergeLists() * * @param Integer $userid User ID, If user ID is not supplied, it will return all lists (OPTIONAL) * @param Boolean $getUnconfirmedCount Get unconfirmed count along with the query (OPTIONAL) * @param Boolean $getAutoresponderCount Get autoresponder count (OPTIONAL) * * @return Mixed Returns an array - list of listid's this user has created (or if the user is an admin/listadmin, returns everything), FALSE otherwise. */ function GetListByUserID($userid = 0, $getUnconfirmedCount = false, $getAutoresponderCount = true) { $userid = intval($userid); $user = API_USERS::getRecordById($userid); $key = '_' . $userid . '_' . ($getUnconfirmedCount? '1' : '0'); if (!array_key_exists('Lists_API::GetListByUserID[listCache]', $GLOBALS)) { $GLOBALS['Lists_API::GetListByUserID[listCache]'] = array(); } if (!array_key_exists($key, $GLOBALS['Lists_API::GetListByUserID[listCache]'])) { $tempSelects = array(); $tempTables = array(); $tempWhere = array(); // Add in "list" table $tempSelects[] = 'list.*'; $tempTables['list'] = "[|PREFIX|]lists AS list"; if ($userid != 0) { $tempTables['list'] .= " LEFT JOIN [|PREFIX|]usergroups_access AS access ON ( list.listid=access.resourceid AND access.resourcetype = 'lists' AND access.groupid = {$user->groupid} ) "; $tempWhere[] = "(list.ownerid = {$userid} OR access.groupid = {$user->groupid})"; } // Add "autoresponder" table if ($getAutoresponderCount) { $tempSelects[] = 'autoresponder.autorespondercount'; $tempTables['list'] .= " LEFT JOIN ( SELECT a.listid, COUNT(a.listid) AS autorespondercount FROM [|PREFIX|]autoresponders AS a GROUP BY a.listid ) AS autoresponder ON list.listid = autoresponder.listid "; } // If we need to get unconfirmed subscriber count, we also need to // join with list_subscribers table if ($getUnconfirmedCount) { $tempSelects[] = 'subscribers.unconfirmedsubscribercount'; $tempTables['list'] .= " LEFT JOIN ( SELECT listid, COUNT(1) AS unconfirmedsubscribercount FROM [|PREFIX|]list_subscribers WHERE confirmed <> '1' AND bounced = 0 AND unsubscribeconfirmed <> '1' GROUP BY listid ) AS subscribers ON list.listid = subscribers.listid "; } $tempQuery = 'SELECT ' . implode(', ', $tempSelects); $tempQuery .= ' FROM ' . implode(', ', $tempTables); if (!empty($tempWhere)) { $tempQuery .= ' WHERE ' . implode(' AND ', $tempWhere); } $tempQuery .= ' ORDER BY LOWER(list.name) ASC'; $tempResult = $this->Db->Query($tempQuery); if (!$tempResult) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } $tempLists = array(); while ($tempRow = $this->Db->Fetch($tempResult)) { $tempLists[$tempRow['listid']] = $tempRow; } $this->Db->FreeResult($tempResult); // Put list into cache (this will cache the list for the duration of this request) $GLOBALS['Lists_API::GetListByUserID[listCache]'][$key] = $tempLists; } return $GLOBALS['Lists_API::GetListByUserID[listCache]'][$key]; }
/** * GetAvailableEmailsThisMonth * Returns the number of emails you can send "this month" based on how many you have already sent. * * This is different to "permonth" because that number doesn't get adjusted as you send out (otherwise we'd need an extra db field to store the "original" info and the adjusted info). * * Returns -1 if the user hasn't been loaded, or if the per month limit is set to 0. * * @return Int Returns the number you can send left "this month" (and this month only). */ function GetAvailableEmailsThisMonth() { trigger_error(__CLASS__ . '::' . __METHOD__ . 'This function is deprecated. Please use API_USERS::* instead', E_USER_NOTICE); return API_USERS::creditAvailableThisMonth($this->userid); }
/** * GetRecordsByUserID * This method will return a list of trigger emails that are accessible by the specified user. * If the parameter $userID is omitted, all records will be returned * * The returned array will contains associated array, * whereby the array index is the triggeremails id * * @param Int $userID User ID (OPTIONAL, default NULL) * @param Array $sortinfo An array of sorting information - what to sort by and what direction (OPTIONAL) * @param Boolean $countonly Whether only to return the number of records available, rather than the whole records. * @param Int $start Where to start in the list. This is used in conjunction with perpage for paging. * @param Mixed $perpage How many results to return (Integer or String) (max). * * @return Mixed Returns false if it couldn't retrieve trigger emails information. Otherwise returns the count (if specified), or an array of trigger emails record. * * @uses SENDSTUDIO_TABLEPREFIX * @uses TriggerEmails_API::_fieldDefaultSort * @uses TriggerEmails_API::_fieldSortable * @uses Db::AddLimit() * @uses Db::Query() * @uses Db::GetError() * @uses Db::Fetch() * @uses Db::FreeResult() */ public function GetRecordsByUserID($userID = null, $sortinfo = array(), $countonly=false, $start=0, $perpage=10) { $query = 'SELECT ' . ($countonly? 'COUNT(1) AS count' : 't.*') . ' FROM [|PREFIX|]triggeremails AS t'; // Constraint by user's permission if user ID is specified if (!empty($userID)) { $userID = intval($userID); $user = API_USERS::getRecordById($userID); $query .= " WHERE t.ownerid = {$userID} OR t.triggeremailsid IN ( SELECT resourceid FROM [|PREFIX|]usergroups_access WHERE resourcetype = 'triggeremails' AND groupid = {$user->groupid} ) "; } if (!$countonly) { // add sorting to the query $sortField = $this->_fieldDefaultSort; $sortDirection = 'asc'; if (isset($sortinfo['SortBy']) && in_array($sortinfo['SortBy'], $this->_fieldSortable)) { $sortField = strtolower($sortinfo['SortBy']); } switch ($sortField) { case 'name': $sortField = 'LOWER(t.name)'; break; default: $sortField = 't.' . $sortField; break; } if (isset($sortinfo['Direction'])) { $sortDirection = strtolower(trim($sortinfo['Direction'])); } $sortDirection = ($sortDirection == 'up' || $sortDirection == 'asc')? ' ASC' : ' DESC'; $query .= ' ORDER BY ' . $sortField . $sortDirection; // Add limit to the query if ($perpage != 'all' && ($start || $perpage)) { $query .= $this->Db->AddLimit($start, $perpage); } // Query the database $lists = array(); $result = $this->Db->Query($query); if (!$result) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } while (($row = $this->Db->Fetch($result))) { $this->_processRecord($row); $lists[$row['triggeremailsid']] = $row; } $this->Db->FreeResult($result); return $lists; } else { $result = $this->Db->Query($query); if (!$result) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } $row = $this->Db->Fetch($result); $count = $row['count']; $this->Db->FreeResult($result); return $count; } }
/** * ActionJob * This actually processes the autoresponder job and sends it out. * It makes sure the autoresponder queue is present (if not, returns false) * It makes sure the autoresponder exists and is active (if not, returns false) * It makes sure the autoresponder has some content (if not, returns false) * Once that is done, it removes any newly banned subscribers * Then removes any newly unsubscribe recipients * It makes sure the recipient is valid, is on the list and matches the criteria set by the autoresponder * Then it gets to work, constructing the email to get sent to the final recipient * Once all recipients for this queue have been looked at, it will "UnProcess" the queue to make everyone active again so next time the job runs, it can start all over again. * The only recipients that are treated this way are the ones who are before the autoresponder's "hours after subscription" timeframe. * * @param Int $queueid The queue to process. * * @see IsQueue * @see Autoresponders_API::Load * @see Autoresponders_API::Active * @see SendStudio_Functions::GetAttachments * @see Lists_API::Load * @see RemoveBannedEmails * @see RemoveUnsubscribedEmails * @see Email_API * @see FetchFromQueue * @see Subscribers_API::LoadSubscriberList * @see RemoveFromQueue * @see MarkAsProcessed * @see MatchCriteria * * @return Boolean Returns false if the queue can't be processed for any reason, otherwise it gets processed and returns true. */ function ActionJob($queueid=0, $jobid=0) { $queueid = (int)$queueid; if (!$this->IsQueue($queueid, 'autoresponder')) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "queueid (" . $queueid . ") is not valid" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "found queueid (" . $queueid . ")" . "\n", 3, $this->LogFile); } $query = "SELECT autoresponderid FROM " . SENDSTUDIO_TABLEPREFIX . "autoresponders WHERE queueid='" . $queueid . "'"; $result = $this->Db->Query($query); if (!$result) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "unable to find autoresponder for queue" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "got autoresponder result" . "\n", 3, $this->LogFile); } $row = $this->Db->Fetch($result); if (empty($row)) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "unable to find autoresponder" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "found autoresponder (" . $row['autoresponderid'] . ")" . "\n", 3, $this->LogFile); } $this->autoresponder_api->Load($row['autoresponderid']); if ($this->autoresponder_api->autoresponderid <= 0) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "unable to find autoresponder" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "loaded autoresponder (" . $row['autoresponderid'] . ")" . "\n", 3, $this->LogFile); } // if the autoresponder isn't active, don't do anything. if (!$this->autoresponder_api->Active()) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "autoresponder not active" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "autoresponder is active" . "\n", 3, $this->LogFile); } // if the autoresponder is empty, don't do anything. if (empty($this->autoresponder_api->textbody) && empty($this->autoresponder_api->htmlbody)) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "autoresponder bodies are empty" . "\n", 3, $this->LogFile); error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "Returning" . "\n", 3, $this->LogFile); } return false; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "autoresponder has text &/or html body" . "\n", 3, $this->LogFile); } $this->autoresponder['Attachments'] = SendStudio_Functions::GetAttachments('autoresponders', $this->autoresponder_api->Get('autoresponderid'), true); $this->listid = $this->autoresponder_api->Get('listid'); $this->Lists_API->Load($this->listid); $listname = $this->Lists_API->Get('name'); $search_criteria = $this->autoresponder_api->Get('searchcriteria'); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "search_criteria: " . array_contents($search_criteria) . "\n", 3, $this->LogFile); } if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } // double check there are no duplicates in the autoresponder queue. $this->RemoveDuplicatesInQueue($queueid, 'autoresponder', $this->listid); // remove any that have been newly banned. $this->RemoveBannedEmails($this->listid, $queueid, 'autoresponder'); // remove any that have unsubscribed. $this->RemoveUnsubscribedEmails($this->listid, $queueid, 'autoresponder'); if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $this->Email_API->ForgetEmail(); $this->Email_API->Set('statid', $this->statid); $this->Email_API->Set('listids', array($this->listid)); $this->Email_API->SetSmtp(SENDSTUDIO_SMTP_SERVER, SENDSTUDIO_SMTP_USERNAME, @base64_decode(SENDSTUDIO_SMTP_PASSWORD), SENDSTUDIO_SMTP_PORT); if ($this->user->smtpserver) { $this->Email_API->SetSmtp($this->user->smtpserver, $this->user->smtpusername, $this->user->smtppassword, $this->user->smtpport); } if (is_null($this->userpause)) { $pause = $pausetime = 0; if ($this->user->perhour > 0) { $pause = $this->user->perhour; } // in case the system rate is less than the user rate, lower it. if (SENDSTUDIO_MAXHOURLYRATE > 0) { if ($pause == 0) { $pause = SENDSTUDIO_MAXHOURLYRATE; } else { $pause = min($pause, SENDSTUDIO_MAXHOURLYRATE); } } if ($pause > 0) { $pausetime = 3600 / $pause; } $this->userpause = $pausetime; if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "userpause is set to " . $this->userpause . "\n", 3, $this->LogFile); } } if ($this->autoresponder_api->Get('tracklinks')) { $this->Email_API->TrackLinks(true); } if ($this->autoresponder_api->Get('trackopens')) { $this->Email_API->TrackOpens(true); } if (SENDSTUDIO_FORCE_UNSUBLINK) { $this->Email_API->ForceLinkChecks(true); } $this->Email_API->Set('CharSet', $this->autoresponder_api->Get('charset')); if (!SENDSTUDIO_SAFE_MODE) { $this->Email_API->Set('imagedir', TEMP_DIRECTORY . '/autoresponder.' . $queueid); } else { $this->Email_API->Set('imagedir', TEMP_DIRECTORY . '/autoresponder'); } // clear out the attachments just to be safe. $this->Email_API->ClearAttachments(); if ($this->autoresponder['Attachments']) { $path = $this->autoresponder['Attachments']['path']; $files = $this->autoresponder['Attachments']['filelist']; foreach ($files as $p => $file) { $this->Email_API->AddAttachment($path . '/' . $file); } } $this->Email_API->Set('Subject', $this->autoresponder_api->Get('subject')); $this->Email_API->Set('FromName', $this->autoresponder_api->Get('sendfromname')); $this->Email_API->Set('FromAddress', $this->autoresponder_api->Get('sendfromemail')); $this->Email_API->Set('ReplyTo', $this->autoresponder_api->Get('replytoemail')); $this->Email_API->Set('BounceAddress', $this->autoresponder_api->Get('bounceemail')); $this->Email_API->Set('EmbedImages', $this->autoresponder_api->Get('embedimages')); $auto_format = $this->autoresponder_api->Get('format'); $this->Email_API->Set('Multipart', false); if ($auto_format == 'b' || $auto_format == 't') { if ($this->autoresponder_api->GetBody('text')) { $this->Email_API->AddBody('text', $this->autoresponder_api->GetBody('text')); $this->Email_API->AppendBody('text', $this->user->Get('textfooter')); $this->Email_API->AppendBody('text', stripslashes(SENDSTUDIO_TEXTFOOTER)); } } if ($auto_format == 'b' || $auto_format == 'h') { if ($this->autoresponder_api->GetBody('html')) { $this->Email_API->AddBody('html', $this->autoresponder_api->GetBody('html')); $this->Email_API->AppendBody('html', $this->user->Get('htmlfooter')); $this->Email_API->AppendBody('html', stripslashes(SENDSTUDIO_HTMLFOOTER)); } } if ($auto_format == 'b' && $this->autoresponder_api->Get('multipart')) { if ($this->autoresponder_api->GetBody('text') && $this->autoresponder_api->GetBody('html')) { $sent_format = 'm'; $this->Email_API->Set('Multipart', true); } else { $this->Email_API->Set('Multipart', false); } } $custom_fields_to_replace = $this->Email_API->GetCustomFields(); $personalize_customfields = array(); $firstname_field = $this->autoresponder_api->Get('to_firstname'); if ($firstname_field) { $personalize_customfields[] = $firstname_field; } $lastname_field = $this->autoresponder_api->Get('to_lastname'); if ($lastname_field) { $personalize_customfields[] = $lastname_field; } $personalize_customfields = array_unique($personalize_customfields); // Current available credit returns TRUE if credit is unlimited $credit_available = true; if (SENDSTUDIO_CREDIT_INCLUDE_AUTORESPONDERS) { $credit_available = API_USERS::creditAvailableTotal($this->autoresponder_api->Get('ownerid')); } $credit_used = 0; $emails_sent = 0; if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "checking queue for deleted recipients" . "\n", 3, $this->LogFile); } $recipient_to_check = array(); $query = "SELECT recipient FROM " . SENDSTUDIO_TABLEPREFIX . "queues WHERE queueid={$queueid}"; $result = $this->Db->Query($query); if (!$result) { trigger_error(mysql_error()); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . mysql_error() . "\n", 3, $this->LogFile); } } while ($row = $this->Db->Fetch($result)) { $recipient_to_check[] = $row['recipient']; } foreach($recipient_to_check as $val) { $query = "SELECT COUNT(subscriberid) FROM " . SENDSTUDIO_TABLEPREFIX . "list_subscribers WHERE subscriberid={$val}"; $exist = $this->Db->FetchOne($query); if((int)$exist == 0){ if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "subscriber id {$val} no longer exists" . "\n", 3, $this->LogFile); } $this->SaveAutoresponderSentStatus(false, $this->autoresponder_api->Get('autoresponderid'), $val, 'doesntexist'); $this->MarkAsSent($queueid, $val); } } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "fetching autoresponder recipients" . "\n", 3, $this->LogFile); } while ($recipients = $this->FetchFromQueue($queueid, 'autoresponder', 1, 500, $this->autoresponder_api->Get('hoursaftersubscription'))) { if (empty($recipients)) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "no more recipients" . "\n", 3, $this->LogFile); } break; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "found " . sizeof($recipients) . " recipients" . "\n", 3, $this->LogFile); } $tempCustomField = $this->SetupDynamicContentFields($recipients, $this->listid, true); $custom_fields_to_replace = array_unique(array_merge($tempCustomField, $custom_fields_to_replace)); $all_customfields = $this->SetupCustomFields($this->listid, $custom_fields_to_replace, $recipients, $personalize_customfields); foreach ($recipients as $p => $recipientid) { $subscriberinfo = $this->Subscriber_API->LoadSubscriberList($recipientid, $this->listid, true, false, false); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . "; subscriberinfo: " . array_contents($subscriberinfo) . "\n", 3, $this->LogFile); } // if they don't match the search criteria, remember it for later and don't sent it. if (empty($subscriberinfo) || !isset($subscriberinfo['subscribedate'])) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . "; subscriber info is empty or date is not set" . "\n", 3, $this->LogFile); } $this->SaveAutoresponderSentStatus(false, $this->autoresponder_api->Get('autoresponderid'), $recipientid, 'unsubscribed'); $this->MarkAsSent($queueid, $recipientid); continue; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . " has email address " . $subscriberinfo['emailaddress'] . "\n", 3, $this->LogFile); } // work out how long they have been subscribed for. $hours_subscribed = floor(($this->currenttime - $subscriberinfo['subscribedate']) / 3600); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . " subscribed for " . $hours_subscribed . "\n", 3, $this->LogFile); } // not long enough? Go to the next one. if ($hours_subscribed < $this->autoresponder_api->Get('hoursaftersubscription')) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . "; not time to send the autoresponder yet (subscribed for " . $hours_subscribed . "; hours set to: " . $this->autoresponder_api->Get('hoursaftersubscription') . ")" . "\n", 3, $this->LogFile); } $this->MarkAsProcessed($queueid, 'autoresponder', $recipientid); continue; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . " has been subscribed for long enough" . "\n", 3, $this->LogFile); } // if they don't match the search criteria, remember it for later and don't send it. if (!$this->MatchCriteria($search_criteria, $recipientid)) { $this->SaveAutoresponderSentStatus(false, $this->autoresponder_api->Get('autoresponderid'), $recipientid, 'search_criteria'); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . "; dont meet search criteria (" . array_contents($search_criteria) . ")" . "\n", 3, $this->LogFile); } $this->MarkAsSent($queueid, $recipientid); continue; } if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "does meet search criteria" . "\n", 3, $this->LogFile); } // If user don't have enough credit, discard queue record if ($credit_available !== true && ($credit_available <= 0 || $credit_available <= $credit_used)) { if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\tUser (userid: {$this->autoresponder->ownerid}) does not have enough credit... Will remove subscriber (id: {$recipientid}) from queue table.\n", 3, $this->LogFile); } $this->SaveAutoresponderSentStatus(false, $this->autoresponder_api->Get('autoresponderid'), $recipientid, 'icredit'); $this->MarkAsSent($queueid, $recipientid); continue; } $this->Email_API->ClearRecipients(); $subscriberinfo['ipaddress'] = $subscriberinfo['confirmip']; if (!$subscriberinfo['ipaddress']) { $subscriberinfo['ipaddress'] = $subscriberinfo['requestip']; } if (!$subscriberinfo['ipaddress']) { $subscriberinfo['ipaddress'] = ''; } $subscriberinfo['CustomFields'] = array(); if (!empty($all_customfields) && isset($all_customfields[$recipientid])) { $subscriberinfo['CustomFields'] = $all_customfields[$recipientid]; } else { /** * If the subscriber has no custom fields coming from the database, then set up blank placeholders. * If they have no custom fields in the database, they have no records in the 'all_customfields' array - so we need to fill it up with blank entries. */ foreach ($custom_fields_to_replace as $fieldid => $fieldname) { $subscriberinfo['CustomFields'][] = array( 'fieldid' => $fieldid, 'fieldname' => $fieldname, 'fieldtype' => 'text', 'defaultvalue' => '', 'fieldsettings' => '', 'subscriberid' => $recipientid, 'data' => '' ); } } $name = false; $firstname_field = $this->autoresponder_api->Get('to_firstname'); if ($firstname_field) { foreach ($subscriberinfo['CustomFields'] as $p => $details) { if ($details['fieldid'] == $firstname_field && $details['data'] != '') { $name = $details['data']; break; } } } $lastname_field = $this->autoresponder_api->Get('to_lastname'); if ($lastname_field) { foreach ($subscriberinfo['CustomFields'] as $p => $details) { if ($details['fieldid'] == $lastname_field && $details['data'] != '') { $name .= ' ' . $details['data']; break; } } } $this->Email_API->AddRecipient($subscriberinfo['emailaddress'], $name, $subscriberinfo['format'], $subscriberinfo['subscriberid']); $subscriberinfo['listid'] = $this->listid; $subscriberinfo['listname'] = $listname; $subscriberinfo['autoresponder'] = $this->autoresponder_api->Get('autoresponderid'); $subscriberinfo['statid'] = $this->statid; $subscriberinfo['companyname'] = $this->Lists_API->Get('companyname'); $subscriberinfo['companyaddress'] = $this->Lists_API->Get('companyaddress'); $subscriberinfo['companyphone'] = $this->Lists_API->Get('companyphone'); $this->Email_API->AddCustomFieldInfo($subscriberinfo['emailaddress'], $subscriberinfo); $this->Email_API->AddDynamicContentInfo($this->dynamic_content_replacement); $mail_result = $this->Email_API->Send(true, true); if ($this->Debug) { error_log(time() . "\t" . __FILE__ . "\t" . __LINE__ . "\t" . "recipientid: " . $recipientid . "; mail result: " . array_contents($mail_result) . "\n", 3, $this->LogFile); } if (!$this->Email_API->Get('Multipart')) { $sent_format = $subscriberinfo['format']; } $last_sent_error = false; if ($mail_result['success'] > 0) { $this->Stats_API->UpdateRecipient($this->statid, $sent_format, 'a'); $this->SaveAutoresponderSentStatus(true, $this->autoresponder_api->Get('autoresponderid'), $recipientid); } else { $last_sent_error = true; $error_reason = 'mail_error'; reset($mail_result['fail']); $error = current($mail_result['fail']); if ($error[1] == 'BlankEmail') { $error_reason = 'blankemail_' . $sent_format; } $this->SaveAutoresponderSentStatus(false, $this->autoresponder_api->Get('autoresponderid'), $recipientid, $error_reason); } /** * Trigger Event */ $tempEventData = new EventData_IEM_JOBSAUTORESPONDERAPI_ACTIONJOB(); $tempEventData->emailsent = ($mail_result['success'] > 0); $tempEventData->subscriberinfo = &$subscriberinfo; $tempEventData->autoresponder = &$this->autoresponder_api; $tempEventData->trigger(); unset($tempEventData); /** * ------ */ $emails_sent++; $this->MarkAsSent($queueid, $recipientid); /** * Work out whether we need to update the job 'lastupdatetime' * - If we pause between each email, we should update it * - If we are not pausing between each email, we should update it every 10 emails */ $update_job_time = false; if ($this->userpause > 0) { $update_job_time = true; } else { if (($emails_sent % 10) == 0) { $update_job_time = true; } } if ($update_job_time) { $timenow = $this->GetServerTime(); $query = "UPDATE [|PREFIX|]jobs SET lastupdatetime = {$timenow} WHERE jobid = {$jobid}"; $update_job_result = $this->Db->Query($query); $emails_sent = 0; } // ----- Record credit usage every 100 emails sent when there aren't any sending error if (!$last_sent_error && $credit_available !== true) { ++$credit_used; if ($credit_used >= 100) { $ownerid = $this->autoresponder_api->Get('ownerid'); $status = API_USERS::creditUse($ownerid, API_USERS::CREDIT_USAGETYPE_SENDAUTORESPONDER, $credit_used, $jobid, $this->statid); if (!$status) { trigger_error(__CLASS__ . '::' . __METHOD__ . " -- Cannot record usage -- userid = {$ownerid}, credit_used = {$credit_used}, job_id = {$jobid}, stat_id = {$this->statid}", E_USER_NOTICE); return false; } $credit_used = 0; } } // ----- // do we need to pause between each email? if ($this->userpause > 0) { if ($this->userpause > 0 && $this->userpause < 1) { $p = ceil($this->userpause * 1000000); usleep($p); } else { $p = ceil($this->userpause); sleep($p); } } // end if we need to pause. } // end foreach recipient } // end while loop (to go through each subscriber in the queue). // ----- If there are leftover credits that haven't been recorded, then record it here if ($credit_available !== true && $credit_used > 0) { $ownerid = $this->autoresponder_api->Get('ownerid'); $status = API_USERS::creditUse($ownerid, API_USERS::CREDIT_USAGETYPE_SENDAUTORESPONDER, $credit_used, $jobid, $this->statid); if (!$status) { trigger_error(__CLASS__ . '::' . __METHOD__ . " -- Cannot record usage -- userid = {$ownerid}, credit_used = {$credit_used}, job_id = {$jobid}, stat_id = {$this->statid}", E_USER_NOTICE); return false; } } // ----- $this->Email_API->CleanupImages(); // logout of the smtp server. the email class handles whether it's actually using an smtp server or not. $this->Email_API->SMTP_Logout(); // reset the 'pause' counter. $this->userpause = null; return true; }
/** * ProcessJob * Does most of the work setting up the job (creates the queue, removes duplicates and so on) to run. Once the job has been set up ready to run, it 'Actions' the job, then marks it as 'finished'. * * @param Int $jobid The job to run. * * @see Email_API * @see Subscriber_API * @see Lists_API * @see newsletter_api * @see GetUser * @see StartJob * @see GetJobQueue * @see CreateQueue * @see JobQueue * @see Subscribers_API::GetSubscribers * @see RemoveDuplicatesInQueue * @see RemoveBannedEmails * @see ActionJob * @see FinishJob * * @return Boolean Returns false if the job can't be started. Otherwise runs the job and returns true. */ function ProcessJob($jobid=0) { if (!$this->StartJob($jobid)) { $this->PauseJob($jobid); $this->jobstatus = 'p'; trigger_error("Unable to start send job {$jobid}"); return false; } $user = GetUser($this->jobowner); IEM::userLogin($this->jobowner, false); $queueid = false; // if there's no queue, start one up. if (!$queueid = $this->GetJobQueue($jobid)) { $sendqueue = $this->CreateQueue('send'); $queueok = $this->JobQueue($jobid, $sendqueue); $send_criteria = $this->jobdetails['SendCriteria']; $original_queuesize = $this->jobdetails['SendSize']; $queueinfo = array('queueid' => $sendqueue, 'queuetype' => 'send', 'ownerid' => $this->jobowner); if (isset($this->jobdetails['Segments']) && is_array($this->jobdetails['Segments'])) { $this->Subscriber_API->GetSubscribersFromSegment($this->jobdetails['Segments'], false, $queueinfo, 'nosort'); } else { $this->Subscriber_API->GetSubscribers($send_criteria, array(), false, $queueinfo, $sendqueue); } $this->Subscriber_API->RemoveDuplicatesInQueue($sendqueue, 'send', $this->jobdetails['Lists']); $this->Subscriber_API->RemoveBannedEmails($this->jobdetails['Lists'], $sendqueue, 'send'); $this->Subscriber_API->RemoveUnsubscribedEmails($this->jobdetails['Lists'], $sendqueue, 'send'); $queueid = $sendqueue; $newsletterstats = $this->jobdetails; $newsletterstats['Job'] = $jobid; $newsletterstats['Queue'] = $sendqueue; $newsletterstats['SentBy'] = $queueinfo['ownerid']; $real_queuesize = $this->Subscriber_API->QueueSize($queueid, 'send'); $newsletterstats['SendSize'] = $real_queuesize; $statid = $this->Stats_API->SaveNewsletterStats($newsletterstats); /** * Process tracker request where because cron was not enabled, we need to parse the option straight away * @todo Result for the call to module_Tracker::ParseOptionsForAllTracker() is not being processed and being ignored */ $tempAPIFile = dirname(__FILE__) . '/module_trackerfactory.php'; if (is_file($tempAPIFile)) { require_once($tempAPIFile); $temp = array_merge($this->jobdetails, array('statid' => $statid, 'stattype' => 'newsletter', 'newsletterid' => $this->jobdetails['Newsletter'])); $status = module_Tracker::ParseOptionsForAllTracker($temp); } /** * ----- */ /** * So we can link user stats to send stats, we need to update it. */ $this->Stats_API->UpdateUserStats($queueinfo['ownerid'], $jobid, $statid); /** * The 'queuesize' in the stats_users table is updated by MarkNewsletterFinished in send.php * so we don't need to worry about it while setting up the send. * That takes into account whether some recipients were skipped because a html-only email was sent etc. */ /** * We re-check the user stats in case a bunch of subscribers have joined, or the user has done something like: * - create a list * - added a few subscribers * - scheduled a send * - added more subscribers */ $check_stats = $this->Stats_API->ReCheckUserStats($user, $original_queuesize, $real_queuesize, AdjustTime()); list($ok_to_send, $not_ok_to_send_reason) = $check_stats; if (!$ok_to_send) { trigger_error(__CLASS__ . '::' . __METHOD__ . " -- " . GetLang($not_ok_to_send_reason), E_USER_WARNING); $this->PauseJob($jobid); $this->UnapproveJob($jobid); IEM::userLogout(); return false; } API_USERS::creditEvaluateWarnings($user->GetNewAPI()); } $this->statid = $this->LoadStats($jobid); if (empty($this->statid)) { trigger_error(__CLASS__ . '::' . __METHOD__ . " -- Cannot find statid. Previous preliminary job process did not get finalized (either CRON died, or it hasn't finished processing the job). Ignoring this job: jobid {$jobid}.", E_USER_NOTICE); IEM::userLogout(); return false; } $queuesize = $this->Subscriber_API->QueueSize($queueid, 'send'); // used by send.php::CleanUp $this->queuesize = $this->jobdetails['SendSize']; /** * There's nothing left? Just mark it as done. */ if ($queuesize == 0) { $this->jobstatus = 'c'; $this->FinishJob($jobid); IEM::userLogout(); return true; } $finished = $this->ActionJob($jobid, $queueid); if ($finished) { $this->jobstatus = 'c'; $this->FinishJob($jobid); } IEM::userLogout(); return true; }
/** * SaveDefaultSettings * Saves the default settings into the database. * Note that the database and required system settings must be set up before this is called. * * @return Array The first element is an error code indicating success (0) or failure (> 0). The second element is an error string. */ public function SaveDefaultSettings() { if (!$this->CheckRequiredFields()) { return array(self::SETTINGS_MISSING, 'All required settings must be loaded first.'); } if (!$this->_db) { return array(self::DB_MISSING, 'Database connection must be established first.'); } require_once SENDSTUDIO_API_DIRECTORY . '/settings.php'; $settings_api = new Settings_API(false); $settings = $this->_settings; $settings['DATABASE_UTF8PATCH'] = '1'; $settings['SERVERTIMEZONE'] = self::GetTimezone(); $settings['DEFAULTCHARSET'] = 'UTF-8'; $settings['SMTP_PORT'] = '25'; $settings['IPTRACKING'] = '1'; $settings['MAXHOURLYRATE'] = '0'; $settings['ALLOW_ATTACHMENTS'] = '1'; $settings['USEMULTIPLEUNSUBSCRIBE'] = '0'; $settings['CONTACTCANMODIFYEMAIL'] = '0'; $settings['FORCE_UNSUBLINK'] = '0'; $settings['MAXOVERSIZE'] = '0'; $settings['MAX_IMAGEWIDTH'] = '700'; $settings['MAX_IMAGEHEIGHT'] = '400'; $settings['BOUNCE_IMAP'] = '0'; $settings['ALLOW_EMBEDIMAGES'] = '1'; $settings['ATTACHMENT_SIZE'] = '2048'; $settings['CRON_ENABLED'] = '0'; $settings['CRON_SEND'] = '5'; $settings['CRON_AUTORESPONDER'] = '10'; $settings['CRON_BOUNCE'] = '60'; $settings['EMAILSIZE_WARNING'] = '500'; $settings['EMAILSIZE_MAXIMUM'] = '2048'; $settings['RESEND_MAXIMUM'] = '3'; $settings['CREDIT_INCLUDE_AUTORESPONDERS'] = '1'; $settings['CREDIT_INCLUDE_TRIGGERS'] = '1'; $settings['CREDIT_WARNINGS'] = '0'; $settings_api->Set('Settings', $settings); // set the table prefix constant for the API to work define('SENDSTUDIO_TABLEPREFIX', $this->_db->TablePrefix); $settings_api->Db =& $this->_db; $settings_api->Save(); $username = $_POST['admin_username']; $usernameToken = API_USERS::generateUniqueToken($username); $password = API_USERS::generatePasswordHash($_POST['admin_password'], $usernameToken); // Set the admin user's settings $query = 'UPDATE [|PREFIX|]users SET '; $query .= " usertimezone='" . $this->_db->Quote($settings['SERVERTIMEZONE']) . "', "; $query .= " emailaddress='" . $this->_db->Quote($settings['EMAIL_ADDRESS']) . "', "; $query .= " textfooter='" . $this->_db->Quote(GetLang('Default_Global_Text_Footer')) . "', "; $query .= " htmlfooter='" . $this->_db->Quote(GetLang('Default_Global_HTML_Footer')) . "', "; $query .= " unique_token='" . $this->_db->Quote($usernameToken) . "', "; $query .= " username='******', "; $query .= " password='******' "; $query .= ' WHERE userid=1'; $result = $this->_db->Query($query); if (!$result) { return array(self::DB_QUERY_ERROR, $this->_db->GetErrorMsg()); } return array(self::SUCCESS, null); }
/** * Record credit usage * This function will record credit usage for a particular user. * * @param record_Users|integer $user User record object or user ID * @param string $usagetype Usage type (see class constansts CREDIT_USAGETYPE_* for valid types) * @param integer $creditused The number of credits that are being used up * @param integer $jobid Associate job ID (OPTIONAL, default = 0) * @param integer $statid Associate statistic ID (OPTIONAL, default = 0) * @param integer $time Time of which the credit is being used (OPTIONAL, default = now) * * @return boolean Returns TRUE if successful, FALSE otherwise */ public static function creditUse($user, $usagetype, $creditused, $jobid = 0, $statid = 0, $time = 0, $evaluateWarnings = true) { $userid = 0; $usagetype = strtolower($usagetype); $creditused = intval($creditused); $jobid = intval($jobid); $statid = intval($statid); $time = intval($time); $db = IEM::getDatabase(); static $validTypes = null; if (is_null($validTypes)) { $validTypes = array(self::CREDIT_USAGETYPE_SENDAUTORESPONDER, self::CREDIT_USAGETYPE_SENDCAMPAIGN, self::CREDIT_USAGETYPE_SENDTRIGGER); } if (!$user instanceof record_Users) { $userid = intval($user); $user = API_USERS::getRecordByID($userid); } if (!$user) { trigger_error("API_USERS::creditUse -- Invalid user specified.", E_USER_NOTICE); return false; } if (!in_array($usagetype, $validTypes)) { trigger_error("API_USERS::creditUse -- Invalid credit type '{$usagetype}'.", E_USER_NOTICE); return false; } if ($creditused < 1) { trigger_error("API_USERS::creditUse -- Credit cannot be less than 1.", E_USER_NOTICE); return false; } if ($jobid < 0) { trigger_error("API_USERS::creditUse -- Invalid jobid specified.", E_USER_NOTICE); return false; } if ($statid < 0) { trigger_error("API_USERS::creditUse -- Invalid statid specified.", E_USER_NOTICE); return false; } if ($time < 0) { trigger_error("API_USERS::creditUse -- Time cannot be negative.", E_USER_NOTICE); return false; } // If user has unlimited emails credit, we don't need to record this $usersApi = new User_API($user->userid); if ($usersApi->hasUnlimitedCredit()) { return true; } // Check for cases (based on usage type) where credit does not need to be deducted switch ($usagetype) { case self::CREDIT_USAGETYPE_SENDTRIGGER: if (!SENDSTUDIO_CREDIT_INCLUDE_TRIGGERS) { return true; } break; case self::CREDIT_USAGETYPE_SENDAUTORESPONDER: if (!SENDSTUDIO_CREDIT_INCLUDE_AUTORESPONDERS) { return true; } break; } $time = $time == 0 ? time() : $time; $db->StartTransaction(); $tempStatus = $db->Query("\n\t\t\t\tINSERT INTO [|PREFIX|]user_credit (userid, transactiontype, transactiontime, credit, jobid, statid)\n\t\t\t\tVALUES ({$userid}, '{$usagetype}', {$time}, -{$creditused}, {$jobid}, {$statid})\n\t\t\t"); if (!$tempStatus) { $db->RollbackTransaction(); trigger_error("API_USERS::creditUse -- Unable to insert credit usage into database: " . $db->Error(), E_USER_NOTICE); return false; } /**@TODO REMOVE ALL REFERENCES TO OLD CREDIT SYSTEM /* // Record this in the credit summary table $tempTimeperiod = mktime(0, 0, 0, date('n'), 1, date('Y')); $tempQuery; // Since MySQL have a direct query which will insert/update in one go, we can utilzie this. if (SENDSTUDIO_DATABASE_TYPE == 'mysql') { $tempQuery = " INSERT INTO [|PREFIX|]user_credit_summary (userid, startperiod, credit_used) VALUES ({$userid}, {$tempTimeperiod}, {$creditused}) ON DUPLICATE KEY UPDATE credit_used = credit_used + {$creditused} "; // Do we need to do an INSERT or an UPDATE query ?? } else { $tempRS = $db->Query("SELECT usagesummaryid FROM [|PREFIX|]user_credit_summary WHERE userid = {$userid} AND startperiod = {$tempTimeperiod}"); if (!$tempRS) { $db->RollbackTransaction(); trigger_error("API_USERS::creditUse -- Cannot query user_credit_summary table: " . $db->Error(), E_USER_NOTICE); return false; } if ($db->CountResult($tempRS) == 0) { $tempQuery = " INSERT INTO [|PREFIX|]user_credit_summary (userid, startperiod, credit_used) VALUES ({$userid}, {$tempTimeperiod}, {$creditused}) "; } else { $tempSummaryID = $db->FetchOne($tempRS, 'usagesummaryid'); $tempQuery = " UPDATE [|PREFIX|]user_credit_summary SET credit_used = credit_used + {$creditused} WHERE usagesummaryid = {$tempSummaryID} "; } $db->FreeResult($tempRS); } $tempStatus = $db->Query($tempQuery); if (!$tempStatus) { $db->RollbackTransaction(); trigger_error("API_USERS::creditUse -- Unable to update/insert user_credit_summary table: " . $db->Error(), E_USER_NOTICE); return false; }*/ $db->CommitTransaction(); if ($evaluateWarnings) { return self::creditEvaluateWarnings($userid); } else { return true; } }
private static function _deleteUser($userid) { $user = API_USERS::getRecordById($userid); $status = array('status' => false, 'data' => array('segments' => false, 'templates' => false, 'usergroups_access' => false, 'usergroups_permissions' => false, 'user_activitylog' => false, 'autoresponders' => false, 'customfields' => false, 'folders' => false, 'triggers' => false, 'jobs_and_queues' => false, 'forms' => false, 'splittests' => false, 'newsletters' => false, 'lists' => false, 'stats' => false, 'users' => false)); // Delete "easy" data (ie. data that can be deleted without processing anything else) if (($temp = self::_deleteFromSimpleTable('segments', 'ownerid', $userid)) === false) { return $status; } else { $status['data']['segments'] = $temp; } if (($temp = self::_deleteFromSimpleTable('templates', 'ownerid', $userid)) === false) { return $status; } else { $status['data']['templates'] = $temp; } if (($temp = self::_deleteFromSimpleTable('user_activitylog', 'userid', $userid)) === false) { return $status; } else { $status['data']['user_activitylog'] = $temp; } // delete complex data if (($temp = self::_deleteComplexAutoresponder($userid)) === false) { return $status; } else { $status['data']['autoresponders'] = $temp; } if (($temp = self::_deleteComplexCustomFields($userid)) === false) { return $status; } else { $status['data']['customfields'] = $temp; } if (($temp = self::_deleteComplexFolders($userid)) === false) { return $status; } else { $status['data']['folders'] = $temp; } if (($temp = self::_deleteComplexTriggers($userid)) === false) { return $status; } else { $status['data']['triggers'] = $temp; } if (($temp = self::_deleteComplexJobsQueues($userid)) === false) { return $status; } else { $status['data']['jobs_and_queues'] = $temp; } if (($temp = self::_deleteComplexForms($userid)) === false) { return $status; } else { $status['data']['forms'] = $temp; } if (($temp = self::_deleteComplexSplittests($userid)) === false) { return $status; } else { $status['data']['splittests'] = $temp; } if (($temp = self::_deleteComplexNewsletters($userid)) === false) { return $status; } else { $status['data']['newsletters'] = $temp; } if (($temp = self::_deleteComplexLists($userid)) === false) { return $status; } else { $status['data']['lists'] = $temp; } if (($temp = self::_deleteComplexStats($userid)) === false) { return $status; } else { $status['data']['stats'] = $temp; } if (($temp = self::_deleteComplexUsers($userid)) === false) { return $status; } else { $status['data']['users'] = $temp; } $status['status'] = true; return $status; }
/** * _ProcessJob * This method does the "setup work" for a split test campaign. * * If a job is passed in that hasn't been started before, it will set everything up: * - create a "queue" of recipients * - clean the queue (remove banned/duplicate recipients etc) * - set up stats for each newsletter in the split test campaign * - save stats for the user sending the campaign to take off credits etc * * If a job is passed in that has been set up before, it just loads the data up. * * Once it has done either of those, it gives the details to the Splittest_Send_API class * and then calls _ActionJob. * Based on what that returns, it will either mark the job as complete or not. * * If the job is a percentage split test send (send to first X % of a list/segment), then * it sets the appropriate flags etc to make sure we only send to that first X%. * If the job hasn't been set up before, it works out the limit and sets the details in the jobdetails * which are then saved in the database in case the job dies or is paused etc. * * If the job is a percentage split test send and it's time to send to the rest of the list/segment, * then it works out the "best" one to send and sets the appropriate variables for that to happen. * * @param Int $jobid The specific job id we're going to process. * * @uses _jobid * @uses StartJob * @uses PauseJob * @uses LoadJob * @uses GetUser * @uses GetJobQueue * @uses CreateQueue * @uses JobQueue * @uses GetSubscribersFromSegment * @uses GetSubscribers * @uses RemoveDuplicatesInQueue * @uses RemoveBannedEmails * @uses RemoveUnsubscribedEmails * @uses QueueSize * @uses _CalculateBestNewsletter * @uses _FinishJob * @uses _ActionJob * @uses _CalculateBestNewsletter * * @return Boolean Returns whether the job was processed or not. If a job could not be processed, it returns false. Otherwise it returns true. */ private function _ProcessJob($jobid = 0) { if ($jobid <= 0) { return false; } $this->_jobid = $jobid; /** * Load the job, then start it. * We need to do this so when we call "StartJob" we can give it the splitid to "start" as well. */ $jobinfo = $this->LoadJob($jobid); $jobdetails = $jobinfo['jobdetails']; /** * Need to load the split campaign * before starting the job * so if we're in "t"imeout mode, * we can look at the stats * We also need the weighting's from the split campaign * to work it out. */ $this->splitcampaign_details = $this->_splittest_api->Load($jobdetails['splitid']); /** * If it's a "t"imeout, work out the best stat. */ if ($jobinfo['jobstatus'] == 't') { $jobdetails['newsletters'] = $this->_CalculateBestNewsletter($jobdetails['Stats']); /** * Also need to kill off the "percentage_send_maximum" element * as this would cause the job to go into "timeout" mode again. */ unset($jobdetails['percentage_send_maximum']); } if (!$this->StartJob($jobid, $jobdetails['splitid'])) { $this->PauseJob($jobid); return false; } // ----- "Login" to the system as the job's owner. $user = GetUser($jobinfo['ownerid']); IEM::userLogin($jobinfo['ownerid'], false); // ----- $queueid = false; // if there's no queue, start one up. if (!($queueid = $this->GetJobQueue($jobid))) { /** * Randomize the newsletters * It's probably already been done but can't hurt to do it again. */ shuffle($jobdetails['newsletters']); $sendqueue = $this->CreateQueue('splittest'); $queueok = $this->JobQueue($jobid, $sendqueue); $send_criteria = $jobdetails['SendCriteria']; $queueinfo = array('queueid' => $sendqueue, 'queuetype' => 'splittest', 'ownerid' => $jobinfo['ownerid']); if (isset($jobdetails['Segments']) && is_array($jobdetails['Segments'])) { $this->_subscribers_api->GetSubscribersFromSegment($jobdetails['Segments'], false, $queueinfo, 'nosort'); } else { $this->_subscribers_api->GetSubscribers($send_criteria, array(), false, $queueinfo, $sendqueue); } if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $this->_subscribers_api->RemoveDuplicatesInQueue($sendqueue, 'splittest', $jobdetails['Lists']); $this->_subscribers_api->RemoveBannedEmails($jobdetails['Lists'], $sendqueue, 'splittest'); $this->_subscribers_api->RemoveUnsubscribedEmails($jobdetails['Lists'], $sendqueue, 'splittest'); if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $jobdetails['SendSize'] = $this->_subscribers_api->QueueSize($sendqueue, 'splittest'); $jobdetails['Stats'] = array(); $jobdetails['SendQueue'] = $sendqueue; /** * Delete the old user stats allocations. * They were all allocated under one stat/job before so the user recorded their send info * so they couldn't set up split test sends and go over their send quota. * * Now, we need to re-allocate them per newsletter being sent. */ $this->_stats_api->DeleteUserStats($jobinfo['ownerid'], $this->_jobid); $statids = array(); foreach ($jobdetails['newsletters'] as $newsletterid) { $newsletterstats = $jobdetails; $newsletterstats['Job'] = $jobid; $newsletterstats['Queue'] = $sendqueue; $newsletterstats['SentBy'] = $queueinfo['ownerid']; $newsletterstats['SendType'] = 'splittest'; $newsletterstats['Newsletter'] = $newsletterid; $newsletterstats['Lists'] = $jobdetails['sendingto']['Lists']; $newsletterstats['SendCriteria'] = $jobdetails['SendCriteria']; $statid = $this->_stats_api->SaveNewsletterStats($newsletterstats); $statids[] = $statid; $jobdetails['Stats'][$newsletterid] = $statid; $this->_stats_api->RecordUserStats($jobinfo['ownerid'], $this->_jobid, $jobdetails['SendSize'], $jobdetails['SendStartTime'], $statid); } $this->SaveSplitStats($jobdetails['splitid'], $this->_jobid, $statids); /** * If it's a percentage send, * work out the number of emails to send for the first percentage * It gets stored in the jobdetails array so it can be saved in the database. */ if ($this->splitcampaign_details['splittype'] == 'percentage') { $max_to_email = ceil($this->splitcampaign_details['splitdetails']['percentage'] / 100 * $jobdetails['SendSize']); $jobdetails['percentage_send_maximum'] = $max_to_email; } /** * Save the job stat details. * Otherwise we could potentially end up with a 'start'ed queue but no stats. */ $this->Set('jobdetails', $jobdetails); $this->UpdateJobDetails(); /** * This is to process the 'queueid' later in the code. */ $queueid = $sendqueue; // This will make sure that the credit warning emails are also being send out from splittest API_USERS::creditEvaluateWarnings($user->GetNewAPI()); } $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); $queuesize = $this->_subscribers_api->QueueSize($queueid, 'splittest'); $this->_queueid = $queueid; /** * If there is a "percentage_send_maximum" variable in the jobdetails array, * we are sending to the first part of a 'percentage' split test. * * We have to send to the rest of the percentage maximum before we pause the job, * work out the "best" performing campaign and send to that. */ if (isset($jobdetails['percentage_send_maximum'])) { $this->_percentage_send_maximum = (int) $jobdetails['percentage_send_maximum']; } /** * If the _percentage_send_maximum is 0, then "timeout" the job. * We must have hit "pause" right at the end of the initial send process or something. */ if ($this->_percentage_send_maximum !== null && $this->_percentage_send_maximum <= 0) { $this->TimeoutJob($jobid, $this->splitcampaign_details['splitid'], $this->splitcampaign_details['splitdetails']['hoursafter']); IEM::userLogout(); return true; } $this->Set('statids', $jobdetails['Stats']); $this->Set('jobdetails', $jobdetails); $this->Set('jobowner', $jobinfo['ownerid']); /** * There's nothing left? Just mark it as done. */ if ($queuesize == 0) { $this->_FinishJob(); IEM::userLogout(); return true; } $finished = $this->_ActionJob($jobid, $queueid); if ($finished) { $this->_FinishJob(); } IEM::userLogout(); return true; }
/** * Install * Performs an installation based on the request in $xml * * @return Void Returns nothing, exits on error */ function Install() { $install = &$this->xml->install; // Required variables: $required = array( 'licenseKey','installPath', 'user' => array( 'email', 'username', 'password' ), 'database' => array( 'dbUser','dbPass','dbDatabase','dbServer' // ,'dbType' ) ); $errors = array(); foreach ($required as $node_name => $node) { if (is_array($node)) { foreach ($node as $variable) { if (!isset($install->$node_name->$variable)) { $errors[] = array('code' => 'missing' . ucfirst($node_name) . ucfirst($variable), 'message' => 'The ' . $node_name . ' ' . $variable . ' value was not supplied.'); } } } else { if (!isset($install->$node)) { $errors[] = array('code' => 'missing' . ucfirst($node), 'message' => 'The ' . $node . ' value was not supplied.'); } } } if (count($errors)) { $this->Error('Please fill out all mandatory fields to complete the installation.',$errors); } // Check if config file is writable $config_file = SENDSTUDIO_INCLUDES_DIRECTORY . "/config.php"; if (!is_writable($config_file)) { $this->Error('Before you can install Interspire Email Marketer make sure the following files are writable.',array(array('code' => 'filePermissions', 'message' => $config_file . ' is not writable.'))); } if (!is_writable(TEMP_DIRECTORY)) { $this->Error('Before you can install Interspire Email Marketer make sure the following files are writable.',array(array('code' => 'filePermissions', 'message' => TEMP_DIRECTORY . ' is not writable.'))); } $license_key = (string)$install->licenseKey; list($error, $msg) = sesion_start($license_key); if ($error) { $this->Error('A valid license key was not supplied.',array(array('code' => 'badLicenseKey','message' => $msg))); } /** * Connect to the database */ /** * Due to a problem with Plesk only mysql installations can be done * if ($install->database->dbType == 'postgresql') { require(dirname(__FILE__) . "/lib/database/pgsql.php"); $db_type = 'PGSQLDb'; $db_type_name = 'pgsql'; } elseif ($install->database->dbType == 'mysql') { */ require_once IEM_PATH . '/ext/database/mysql.php'; $db_type = 'MySQLDb'; $db_type_name = 'mysql'; defined('SENDSTUDIO_DATABASE_TYPE') or define('SENDSTUDIO_DATABASE_TYPE', $db_type_name); /** } else { $this->Error('The installer was not able to connect to the database.',array(array('code' => 'dbConnectError', 'message' => 'Unknown database type ' . $install->database->dbType))); } */ $db = new $db_type($install->database->dbServer, $install->database->dbUser, $install->database->dbPass, $install->database->dbDatabase); $db->TablePrefix = $install->database->tablePrefix; $db->ErrorCallback = array(&$this,'DatabaseError'); IEM::getDatabase($db); if (!$db->connection) { $this->Error('The installer was not able to connect to the database.', array(array('code' => 'dbConnectError', 'message' => "Unable to connect to the database: " . $db->GetError()))); } /** * Load the database schema file and create the database tables */ require_once(IEM_PATH . "/install/schema." . $db_type_name . ".php"); $tableprefix = ''; if (isset($install->database->tablePrefix)) { $tableprefix = (string)$install->database->tablePrefix; } foreach ($queries as $query) { $query = str_replace('%%TABLEPREFIX%%', $tableprefix, $query); $db->Query($query); } /** * Find the server timezone and write the configuration file */ $this->LoadLanguageFile('Timezones'); $timezone = date('O'); $timezone = preg_replace('/([+-])0/', '$1', $timezone); if ($timezone == '+000') { $timezone = 'GMT'; } $timez = 'GMT'; foreach ($GLOBALS['SendStudioTimeZones'] as $k => $tz) { // if we're using date('O') it doesn't include "GMT" or the ":" // see if we can match it up. $tz_trim = str_replace(array('GMT', ':'), '', $tz); if ($tz_trim == $timezone) { $timez = $tz; break; } } if (!defined('SENDSTUDIO_SERVERTIMEZONE')) { define('SENDSTUDIO_SERVERTIMEZONE', $timez); } define('SENDSTUDIO_TABLEPREFIX', $tableprefix); ob_start(); $settings_api = $this->GetApi('Settings'); $settings_details = array(); $settings_details['DATABASE_UTF8PATCH'] = '1'; $settings_details['DATABASE_TYPE'] = $db_type_name; $settings_details['DATABASE_USER'] = (string)$install->database->dbUser; $settings_details['DATABASE_PASS'] = (string)$install->database->dbPass; $settings_details['DATABASE_HOST'] = (string)$install->database->dbServer; $settings_details['DATABASE_NAME'] = (string)$install->database->dbDatabase; $settings_details['TABLEPREFIX'] = $tableprefix; $settings_details['LICENSEKEY'] = (string)$install->licenseKey; $settings_details['APPLICATION_URL'] = (string)$install->installPath; $settings_details['SERVERTIMEZONE'] = $timez; $settings_details['DEFAULTCHARSET'] = 'UTF-8'; $settings_details['EMAIL_ADDRESS'] = (string)$install->user->email; // now for the default settings. $settings_details['SMTP_PORT'] = '25'; $settings_details['IPTRACKING'] = '1'; $settings_details['MAX_IMAGEWIDTH'] = 700; $settings_details['MAX_IMAGEHEIGHT'] = 400; $settings_details['BOUNCE_IMAP'] = '0'; $settings_details['ALLOW_EMBEDIMAGES'] = '1'; $settings_details['ATTACHMENT_SIZE'] = '2048'; $settings_details['CRON_SEND'] = '5'; $settings_details['CRON_AUTORESPONDER'] = '10'; $settings_details['CRON_BOUNCE'] = '60'; $settings_details['EMAILSIZE_WARNING'] = '500'; $settings_details['EMAILSIZE_MAXIMUM'] = '2048'; $settings_details['RESEND_MAXIMUM'] = '3'; $settings_api->Set('Settings', $settings_details); $settings_api->Db = &$db; $settings_api->Save(); // ----- Update the default user account $username = $install->user->username; $unique_token = API_USERS::generateUniqueToken($username); $new_password = API_USERS::generatePasswordHash($install->user->password, $unique_token); $tempServerTimeZone = $db->Quote($settings_details['SERVERTIMEZONE']); $tempEmailAddress = $db->Quote(strval($install->user->email)); $tempUniqueToken = $db->Quote($unique_token); $tempUsername = $db->Quote($username); $tempPassword = $db->Quote($new_password); $tempHTMLFooter = $db->Quote(GetLang('Default_Global_HTML_Footer', '')); $tempTEXTFooter = $db->Quote(GetLang('Default_Global_Text_Footer', '')); $query = " UPDATE {$tableprefix}users SET unique_token = '{$tempUniqueToken}', usertimezone = '{$tempServerTimeZone}', emailaddress ='{$tempEmailAddress}', textfooter ='{$tempTEXTFooter}', htmlfooter ='{$tempHTMLFooter}', username = '******', password ='******' WHERE userid = 1 "; $db->Query($query); unset($tempTEXTFooter); unset($tempHTMLFooter); unset($tempPassword); unset($tempUniqueToken); unset($tempEmailAddress); unset($tempServerTimeZone); unset($new_password); unset($unique_token); // ----- ob_end_clean(); /** * Installation is finished */ $this->PrintHeader(); ?> <status>OK</status> <installPath><?php echo $install->installPath; ?></installPath> <user> <username>admin</username> <password><?php echo $install->user->password; ?></password> </user> <?php $this->PrintFooter(); return; }
/** * Checks whether this user is allowed to send these emails or not. * * @param object $user_object User Object to check. * @param int $queuesize The size of the queue to check. * @param int $queuetime The time when you are trying to send / schedule the queue. * * @return array Returns an array of status and a language variable describing why it can't be sent. This allows us to differentiate between whether it's a "maxemails" issue or a "per month" issue. */ public function CheckUserStats(User_API $user, $queueSize = 0, $queuetime=0) { // if they have no limits, then no need to do any other checks if ($user->hasUnlimitedCredit()) {return array(true, false);} $queueSize = (int) $queueSize; if (!$user->hasUnlimitedMonthlyCredit()){ $monthly = (int) API_USERS::creditAvailableThisMonth($user->userid, false, $queuetime); } if (!$user->hasUnlimitedTotalCredit()){ $total = (int) API_USERS::creditAvailableFixed($user->userid); } // do monthly credit check if (isset($monthly) && $queueSize > $monthly){return array(false, 'OverLimit_PerMonth');} // do total credit check if (isset($total) && $queueSize > $total) {return array(false, 'OverLimit_MaxEmails');} return array(true, false); }
/** * GetSegmentByUserID * This method will return a list of segments that are accessible by the specified user. * If the parameter $userID is omitted, all segments will be returned * * The returned array will contains associated array, * whereby the array index is the segment id * * @param Int $userID User ID (OPTIONAL, default NULL) * @param Array $sortinfo An array of sorting information - what to sort by and what direction (OPTIONAL) * @param Boolean $countonly Whether to only get a count of segments, rather than the information. * @param Int $start Where to start in the list. This is used in conjunction with perpage for paging. * @param Mixed $perpage How many results to return (Integer or String) (max). * * @return Mixed Returns false if it couldn't retrieve segment information. Otherwise returns the count (if specified), or an array of segments. * * @uses SENDSTUDIO_TABLEPREFIX * @uses API::_subQueryCapable() * @uses Segment_API::_fieldDefaultSort * @uses Segment_API::_FieldSortable * @uses Db::AddLimit() * @uses Db::Query() * @uses Db::GetError() * @uses Db::Fetch() * @uses Db::FreeResult() */ function GetSegmentByUserID($userID = null, $sortinfo = array(), $countonly = false, $start=0, $perpage=10) { $query = 'SELECT ' . ($countonly? 'COUNT(1) AS count' : '*') . ' FROM ' . SENDSTUDIO_TABLEPREFIX . 'segments'; // Constraint by user's permission if user ID is specified if (!is_null($userID)) { $userID = intval($userID); $user = API_USERS::getRecordById($userID); $query .= ' WHERE ownerid = ' . $userID; $subQuery = 'SELECT resourceid FROM ' . SENDSTUDIO_TABLEPREFIX . "usergroups_access WHERE resourcetype='segments' AND " . "groupid=" . $user->groupid; if ($this->_subqueryCapable()) { $query .= ' OR segmentid IN (' . $subQuery . ')'; } else { $tempResult = $this->Db->Query($subQuery); if (!$tempResult) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } $tempRow = array(); while (($row = $this->Db->Fetch($tempResult))) { array_push($tempRow, $row['resourceid']); } $this->Db->FreeResult($tempResult); if (count($tempRow) > 0) { $query .= ' OR segmentid IN (' . implode(',', $tempRow) . ')'; } } } if (!$countonly) { // Add sorting to the query $sortField = $this->_fieldDefaultSort; $sortDirection = 'asc'; if (isset($sortinfo['SortBy']) && in_array($sortinfo['SortBy'], $this->_fieldSortable)) { $sortField = strtolower($sortinfo['SortBy']); } if ($sortField == 'segmentname') { $sortField = 'LOWER(segmentname)'; } if (isset($sortinfo['Direction'])) { $sortDirection = strtolower(trim($sortinfo['Direction'])); } $sortDirection = ($sortDirection == 'up' || $sortDirection == 'asc')? ' ASC' : ' DESC'; $query .= ' ORDER BY ' . $sortField . $sortDirection; // Add limit to the query if ($perpage != 'all' && ($start || $perpage)) { $query .= $this->Db->AddLimit($start, $perpage); } // Query the database $lists = array(); $result = $this->Db->Query($query); if (!$result) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } while (($row = $this->Db->Fetch($result))) { $row['searchinfo'] = unserialize($row['searchinfo']); $lists[$row['segmentid']] = $row; } $this->Db->FreeResult($result); return $lists; } else { $result = $this->Db->Query($query); if (!$result) { list($error, $level) = $this->Db->GetError(); trigger_error($error, $level); return false; } $row = $this->Db->Fetch($result); $count = $row['count']; $this->Db->FreeResult($result); return $count; } }
/** * _authenticate * Return user record based on the username/password that is supplied. * If user does not exists, it will return an integer 0 (Zero). * * @param String $username Username to login user with * @param String $password Password to login user with * @param String $xmltoken XML Token to login user with * @return Mixed Returns an associative array of the user record if username/password match, 0 if record does not match, FALSE if error occured * * @uses Db::Quote() * @uses Db::Query() * @uses Db::GetError() * @uses Db::Fetch() * @uses Db::FreeResult() */ private function _authenticate($username, $password, $xmltoken) { $db = IEM::getDatabase(); $username = $db->Quote($username); if ($password === '' && $xmltoken === '') { return 0; } $query = "SELECT * FROM [|PREFIX|]users WHERE username = '******' AND status = '1'"; $result = $db->Query($query); if ($result == false) { list($error, $level) = $db->GetError(); trigger_error($error, $level); return false; } $details = $db->Fetch($result); $db->FreeResult($result); if (empty($details)) { return 0; } if (!empty($password)) { $tempPassword = $password; if (array_key_exists('unique_token', $details)) { $tempPassword = API_USERS::generatePasswordHash($password, $details['unique_token']); } else { $tempPassword = md5($password); } if ($details['password'] != $tempPassword) { return 0; } } elseif (!empty($xmltoken) && $details['xmltoken'] != $xmltoken) { return 0; } return $details; }
/** * DeleteUsers * Deletes a list of users from the database via the api. Each user is checked to make sure you're not going to accidentally delete your own account and that you're not going to delete the 'last' something (whether it's the last active user, admin user or other). * If you aren't an admin user, you can't do anything at all. * * @param integer[] $users An array of userid's to delete * @param boolean $deleteData Whether or not to delete data owned by user along * * @see GetUser * @see User_API::UserAdmin * @see DenyAccess * @see CheckUserSystem * @see PrintManageUsers * * @return Void Doesn't return anything. Works out the relevant message about who was/wasn't deleted and prints that out. Returns control to PrintManageUsers. */ function DeleteUsers($users = array(), $deleteData = false) { $thisuser = GetUser(); if (!$thisuser->UserAdmin()) { $this->DenyAccess(); return; } if (!is_array($users)) { $users = array($users); } $not_deleted_list = array(); $not_deleted = $deleted = 0; foreach ($users as $p => $userid) { if ($userid == $thisuser->Get('userid')) { $not_deleted++; $not_deleted_list[$userid] = array('username' => $thisuser->Get('username'), 'reason' => GetLang('User_CantDeleteOwn')); continue; } $error = $this->CheckUserSystem($userid); if (!$error) { $result = API_USERS::deleteRecordByID($userid, $deleteData); if ($result) { $deleted++; } else { $not_deleted++; $user = GetUser($userid); if ($user instanceof User_API) { $not_deleted_list[$userid] = array('username' => $user->Get('username'), 'reason' => ''); } else { $not_deleted_list[$userid] = array('username' => $userid, 'reason' => ''); } } } else { $not_deleted++; $user = GetUser($userid); if ($user instanceof User_API) { $not_deleted_list[$userid] = array('username' => $user->Get('username'), 'reason' => $error); } else { $not_deleted_list[$userid] = array('username' => $userid, 'reason' => $error); } } } if ($not_deleted > 0) { foreach ($not_deleted_list as $uid => $details) { FlashMessage(sprintf(GetLang('UserDeleteFail'), htmlspecialchars($details['username'], ENT_QUOTES, SENDSTUDIO_CHARSET), htmlspecialchars($details['reason'], ENT_QUOTES, SENDSTUDIO_CHARSET)), SS_FLASH_MSG_ERROR); } } if ($deleted > 0) { if ($deleted == 1) { FlashMessage(GetLang('UserDeleteSuccess_One'), SS_FLASH_MSG_SUCCESS, IEM::urlFor('Users')); } else { FlashMessage(sprintf(GetLang('UserDeleteSuccess_Many'), $this->FormatNumber($deleted)), SS_FLASH_MSG_SUCCESS, IEM::urlFor('Users')); } } IEM::redirectTo('Users'); }
/** * Process * This works out where you are up to in the send process and takes the appropriate action. Most is passed off to other methods in this class for processing and displaying the right forms. * * @return Void Doesn't return anything. */ function Process() { $action = (isset($_GET['Action'])) ? strtolower($_GET['Action']) : null; $user = IEM::userGetCurrent(); $access = $user->HasAccess('Newsletters', 'send'); $popup = (in_array($action, $this->PopupWindows)) ? true : false; $this->PrintHeader($popup); if (!$access) { $this->DenyAccess(); return; } if ($action == 'processpaging') { $this->SetPerPage($_GET['PerPageDisplay']); $action = ''; } switch ($action) { case 'viewsenderrors': $job = (isset($_GET['Job'])) ? (int)$_GET['Job'] : 0; if (!$this->CanAccessJobs($job)) { $this->DenyAccess(); return; } echo $this->PrintSendFailureReport($job); break; case 'view_report': $queueid = IEM::sessionGet('ReportQueue'); $report_type = (isset($_GET['ReportType'])) ? strtolower($_GET['ReportType']) : null; switch ($report_type) { case '1': $GLOBALS['Heading'] = GetLang('SendProblem_Report_Subscriber_Problem_Heading'); $GLOBALS['Intro'] = GetLang('SendProblem_Report_Subscriber_Problem_Intro'); break; case '10': $GLOBALS['Heading'] = GetLang('SendProblem_Report_Email_Problem_Heading'); $GLOBALS['Intro'] = GetLang('SendProblem_Report_Email_Problem_Intro'); break; case '20': $GLOBALS['Heading'] = GetLang('SendProblem_Report_MailServer_Problem_Heading'); $GLOBALS['Intro'] = GetLang('SendProblem_Report_MailServer_Problem_Intro'); break; case '30': $GLOBALS['Heading'] = GetLang('SendProblem_Report_SMTPMailServer_Problem_Heading'); $GLOBALS['Intro'] = GetLang('SendProblem_Report_SMTPMailServer_Problem_Intro'); break; default: $GLOBALS['Heading'] = GetLang('SendProblem_Report_Invalid_Heading'); $GLOBALS['Intro'] = GetLang('SendProblem_Report_Invalid_Intro'); $GLOBALS['EmailList'] = GetLang('SendProblem_InvalidReportURL'); $this->ParseTemplate('SendProblem_Report_Results_View'); break 2; } $api = $this->GetApi('Subscribers'); $email_list = ''; $problem_email_addresses = $api->GetUnsentSubscribers($queueid, $report_type); foreach ($problem_email_addresses as $emailaddress) { $email_list .= htmlspecialchars($emailaddress, ENT_QUOTES, SENDSTUDIO_CHARSET) . "\n"; } $GLOBALS['EmailList'] = $email_list; $this->ParseTemplate('SendProblem_Report_Results_View'); break; case 'pausesend': $job = (int)$_GET['Job']; if (!$this->CanAccessJobs($job)) { $this->DenyAccess(); return; } $api = $this->GetApi('Jobs'); $paused = $api->PauseJob($job); if ($paused) { $GLOBALS['Message'] = $this->PrintSuccess('Send_Paused_Success'); } else { $GLOBALS['Error'] = GetLang('Send_Paused_Failure'); $GLOBALS['Message'] = $this->ParseTemplate('ErrorMsg', true, false); } $this->ParseTemplate('Send_Step5_Paused'); break; case 'sendfinished': $job = (int)$_GET['Job']; if (!$this->CanAccessJobs($job)) { $this->DenyAccess(); return; } $send_details = IEM::sessionGet('SendDetails'); $statsapi = $this->GetApi('Stats'); $statsapi->MarkNewsletterFinished($send_details['StatID'], $send_details['SendSize']); $timetaken = $send_details['SendEndTime'] - $send_details['SendStartTime']; $timedifference = $this->TimeDifference($timetaken); $GLOBALS['SendReport_Intro'] = sprintf(GetLang('SendReport_Intro'), $timedifference); $sendreport = ''; if ($send_details['EmailResults']['success'] > 0) { if ($send_details['EmailResults']['success'] == 1) { $sendreport .= $this->PrintSuccess('SendReport_Success_One'); } else { $sendreport .= $this->PrintSuccess('SendReport_Success_Many', $this->FormatNumber($send_details['EmailResults']['success'])); } } $this->PrintSendFailureReport($job, $sendreport); $api = $this->GetApi('Jobs'); $api->FinishJob($job); $api->ClearQueue($send_details['SendQueue'], 'send'); break; case 'send': IEM::sessionRemove('ApproveJob'); API_USERS::creditEvaluateWarnings($user->GetNewAPI()); $jobid = (int)$_GET['Job']; if (!$this->CanAccessJobs($jobid)) { $this->DenyAccess(); return; } $subscriberApi = $this->GetApi('Subscribers'); $jobApi = $this->GetApi('Jobs'); if (!isset($_GET['Started'])) { $jobApi->StartJob($jobid); } $sendqueue = $jobApi->GetJobQueue($jobid); $send_api = $this->GetApi('Send'); $job = $jobApi->LoadJob($jobid); $send_api->Set('statid', $send_api->LoadStats($jobid)); $send_api->Set('jobdetails', $job['jobdetails']); $send_api->Set('jobowner', $job['ownerid']); if (isset($_GET['Resend'])) { // this function handles moving everyone onto the 'live' queue etc so we don't need to worry about any of that. $send_api->ResendJob_Setup($jobid); } $queuesize = $jobApi->QueueSize($sendqueue, 'send'); $send_details = IEM::sessionGet('SendDetails'); $send_details['SendQueue'] = $sendqueue; $timenow = AdjustTime(0, true, null, true); $timediff = ($timenow - $send_details['SendStartTime']); $time_so_far = $this->TimeDifference($timediff); $num_left_to_send = $send_details['SendSize'] - $queuesize; if ($num_left_to_send > 0) { $timeunits = $timediff / ($num_left_to_send); $timediff = ($timeunits * $queuesize); } else { $timediff = 0; } $timewaiting = $this->TimeDifference($timediff); $GLOBALS['SendTimeSoFar'] = sprintf(GetLang('Send_TimeSoFar'), $time_so_far); $GLOBALS['SendTimeLeft'] = sprintf(GetLang('Send_TimeLeft'), $timewaiting); if ($queuesize <= 0) { $email = $this->GetApi('Email'); if (SENDSTUDIO_SAFE_MODE) { $email->Set('imagedir', TEMP_DIRECTORY . '/send'); } else { $email->Set('imagedir', TEMP_DIRECTORY . '/send.' . $jobid . '.' . $sendqueue); } $email->CleanupImages(); $send_details['SendEndTime'] = AdjustTime(0, true, null, true); IEM::sessionSet('SendDetails', $send_details); $GLOBALS['Send_NumberLeft'] = GetLang('SendFinished'); $this->ParseTemplate('Send_Step5'); ?> <script> window.opener.focus(); window.opener.document.location = 'index.php?Page=Send&Action=SendFinished&Job=<?php echo $jobid; ?>&r=<?php echo time(); ?>'; window.close(); </script> <?php break; } if ($queuesize == 1) { $GLOBALS['Send_NumberLeft'] = GetLang('Send_NumberLeft_One'); } else { $GLOBALS['Send_NumberLeft'] = sprintf(GetLang('Send_NumberLeft_Many'), $this->FormatNumber($queuesize)); } if ($num_left_to_send == 1) { $GLOBALS['Send_NumberAlreadySent'] = GetLang('Send_NumberSent_One'); } else { $GLOBALS['Send_NumberAlreadySent'] = sprintf(GetLang('Send_NumberSent_Many'), $this->FormatNumber($num_left_to_send)); } $send_api->SetupJob($jobid, $sendqueue); $recipients = $send_api->FetchFromQueue($sendqueue, 'send', 1, 1); $send_api->SetupDynamicContentFields($recipients); $send_api->SetupCustomFields($recipients); $sent_ok = false; foreach ($recipients as $p => $recipientid) { $send_results = $send_api->SendToRecipient($recipientid, $sendqueue); // save the info in the session, then see if we need to pause between each email. if ($send_results['success'] > 0) { $sent_ok = true; $send_details['EmailResults']['success']++; } else { $send_details['EmailResults']['failure']++; } $send_details['EmailResults']['total']++; IEM::sessionSet('SendDetails', $send_details); } $GLOBALS['JobID'] = $jobid; $template = $this->ParseTemplate('Send_Step5', true); $template .= $this->PrintFooter(true, true); echo $template; // we should only need to pause if we successfully sent. if ($sent_ok) { $send_api->Pause(); } exit; break; case 'step4': $newsletter_chosen = $_POST['newsletter']; if ($newsletter_chosen == 0) { $this->SelectNewsletter(GetLang('Send_Step4_ChooseNewsletter')); break; } if (!$this->CanAccessNewsletter($newsletter_chosen)) { $this->DenyAccess(); break; } $send_details = IEM::sessionGet('SendDetails'); $send_details['Multipart'] = (isset($_POST['sendmultipart'])) ? 1 : 0; $send_details['TrackOpens'] = (isset($_POST['trackopens'])) ? 1 : 0; $send_details['TrackLinks'] = (isset($_POST['tracklinks'])) ? 1 : 0; $send_details['EmbedImages'] = (isset($_POST['embedimages'])) ? 1 : 0; $send_details['Newsletter'] = $_POST['newsletter']; $send_details['SendFromName'] = $_POST['sendfromname']; $send_details['SendFromEmail'] = $_POST['sendfromemail']; $send_details['ReplyToEmail'] = (isset($_POST['replytoemail'])) ? $_POST['replytoemail'] : $send_details['SendFromEmail']; $send_details['BounceEmail'] = (isset($_POST['bounceemail'])) ? $_POST['bounceemail'] : $send_details['SendFromEmail']; $newsletterapi = $this->GetApi('Newsletters'); $newsletterapi->Load($send_details['Newsletter']); $archive = $newsletterapi->Archive(); if(empty($archive)) { $GLOBALS['Messages'] = $this->PrintWarning('SendNewsletterArchive_DeactivatedWarning'); } $to_firstname = false; if (isset($_POST['to_firstname']) && (int)$_POST['to_firstname'] > 0) { $to_firstname = (int)$_POST['to_firstname']; } $send_details['To_FirstName'] = $to_firstname; $to_lastname = false; if (isset($_POST['to_lastname']) && (int)$_POST['to_lastname'] > 0) { $to_lastname = (int)$_POST['to_lastname']; } $send_details['To_LastName'] = $to_lastname; $send_details['Charset'] = SENDSTUDIO_CHARSET; $send_details['NotifyOwner'] = (isset($_POST['notifyowner'])) ? 1 : 0; $send_details['SendStartTime'] = AdjustTime(0, true, null, true); $send_details['EmailResults']['success'] = 0; $send_details['EmailResults']['total'] = 0; $send_details['EmailResults']['failure'] = 0; $jobapi = $this->GetApi('Jobs'); $scheduletime = AdjustTime(0, true, null, true); $statsapi = $this->GetApi('Stats'); IEM::sessionSet('SendDetails', $send_details); $subscriber_count = $send_details['SendSize']; $approved = $user->Get('userid'); $newslettername = ''; $newsletterApi = $this->GetApi('Newsletters'); $newsletterApi->Load($send_details['Newsletter']); $newslettername = $newsletterApi->Get('name'); $newslettersubject = $newsletterApi->Get('subject'); $newsletter_size = 0; $html_size = utf8_strlen($newsletterApi->Get('htmlbody')); $text_size = utf8_strlen($newsletterApi->Get('textbody')); // if you are sending multipart, then put both parts together to work out an approximate size. if ($send_details['Multipart']) { $newsletter_size += $html_size + $text_size; } else { // if you are not sending multipart, then try to work out the html part (as a guide for maximum size). if ($html_size > 0) { $newsletter_size += $html_size; } else { $newsletter_size += $text_size; } } $attachments = $this->GetAttachments('newsletters', $send_details['Newsletter'], true); if (isset($attachments['filelist'])) { foreach ($attachments['filelist'] as $p => $attachment) { $file = $attachments['path'] . '/' . $attachment; // base64 encoding adds about 30% overhead so we need to add it here. $newsletter_size += 1.3 * filesize($file); } } $email_api = $this->GetApi('Email'); $problem_images = array(); // we'll do a quick check for the images in the html content to make sure they all work. $email_api->Set('EmbedImages', true); $email_api->AddBody('html', $newsletterApi->Get('htmlbody')); $images = $email_api->GetImages(); if (is_array($images) && !empty($images)) { $max_image_count = $this->max_image_count; $counter = 0; $total_image_size = 0; $image_exceed_threshold = (count($images) > $max_image_count); foreach ($images as $md5 => $image_url) { list($img, $error) = $email_api->GetImage($image_url); $image_size = 1.3 * strlen($img); if ($img) { if ($send_details['EmbedImages']) { // base64 encoding adds about 30% overhead so we need to add it here. $newsletter_size += $image_size; } } else { $problem_images[] = array('img' => $image_url, 'error' => $error); } // Images exceed "max image count" threshold.... // We will need to do something about it so that the application doesn't timeout if ($image_exceed_threshold) { $total_image_size += $image_size; if (++$counter >= $max_image_count) { $temp = sprintf(GetLang('CannotVerifyAllImages_ExceedThreshold'), $max_image_count); $temp .= sprintf(GetLang('CannotVerifyAllImages_OnlyThresholdImagesVerified'), $max_image_count); $problem_images[] = $temp; if ($send_details['EmbedImages']) { $problem_images[] = sprintf(GetLang('CannotVerifyAllImages_SendSizeEstimated')); $average_image_size = ($total_image_size / $max_image_count); $total_image_count = count($images); $newsletter_size += abs($average_image_size * ($total_image_count - $max_image_count)); } break; } } } } $img_warning = ''; if (!empty($problem_images)) { foreach ($problem_images as $problem_details) { if (is_array($problem_images)) { $img_warning .= sprintf(GetLang('UnableToLoadImage'), $problem_details['img'], $problem_details['img'], $problem_details['error']); } else { $img_warning .= "- {$problem_details}<br/>"; } } } if ($img_warning) { if ($send_details['EmbedImages']) { $warning_var = 'UnableToLoadImage_Newsletter_List_Embed'; } else { $warning_var = 'UnableToLoadImage_Newsletter_List'; } $GLOBALS['ImageWarning'] = $this->PrintWarning($warning_var, $img_warning); } if (SENDSTUDIO_EMAILSIZE_MAXIMUM > 0) { if ($newsletter_size >= (SENDSTUDIO_EMAILSIZE_MAXIMUM*1024)) { $this->SelectNewsletter(sprintf(GetLang('Newsletter_Size_Over_EmailSize_Maximum'), $this->EasySize(SENDSTUDIO_EMAILSIZE_MAXIMUM*1024, 0))); break; } } if (($subcount = IEM::sessionGet('SendSize_Many_Extra', false)) === false) { $subcount = $subscriber_count; } $GLOBALS['ApproximateSendSize'] = sprintf(GetLang('Newsletter_SendSize_Approximate'), $this->EasySize($newsletter_size, 0), $this->EasySize($newsletter_size * $subcount, 1)); if (SENDSTUDIO_EMAILSIZE_WARNING > 0) { if ($newsletter_size > (SENDSTUDIO_EMAILSIZE_WARNING*1024)) { $GLOBALS['EmailSizeWarning'] = $this->PrintWarning('Newsletter_Size_Over_EmailSize_Warning_Send', $this->EasySize((SENDSTUDIO_EMAILSIZE_WARNING*1024), 0)); } } if (SENDSTUDIO_CRON_ENABLED && SENDSTUDIO_CRON_SEND > 0) { $sendtime = $_POST['sendtime']; if (isset($_POST['sendimmediately']) && $_POST['sendimmediately'] == '1') { /* * Set the date/time to now if sendimmediately was ticked */ $time_now = AdjustTime(0, true, null, true); $sendtime = AdjustTime($time_now, false, 'h:iA'); $_POST['datetime']['year'] = AdjustTime($time_now,false,'Y'); $_POST['datetime']['month'] = AdjustTime($time_now,false,'n'); $_POST['datetime']['day'] = AdjustTime($time_now,false,'j'); } /* * the sendtime is in this format: * hr:minAM * so we need to look at the character positions rather than exploding on the separator. */ $hr = substr($sendtime, 0, 2); $minute = substr($sendtime, 3, 2); $ampm = substr($sendtime, -2); if (strtolower($ampm) == 'pm') { if ($hr != 12) { $hr = $hr + 12; } } if (strtolower($ampm) == 'am' && $hr == 12) { $hr = 0; } if ($hr > 23) { $hr = $hr - 24; } $check_schedule_time = AdjustTime(array($hr, $minute, 0, (int)$_POST['datetime']['month'], (int)$_POST['datetime']['day'], (int)$_POST['datetime']['year']), true); $five_mins_ago = $statsapi->GetServerTime() - (5*60); if ($check_schedule_time < $five_mins_ago) { $this->SelectNewsletter(GetLang('Send_Step4_CannotSendInPast')); break; } $send_criteria = $send_details['SendCriteria']; $server_schedule_time = array($hr, $minute, 0, (int)$_POST['datetime']['month'], (int)$_POST['datetime']['day'], (int)$_POST['datetime']['year']); $scheduletime = AdjustTime($server_schedule_time, true); /** * Since we're using scheduled sending, we need to check user stats for when this is scheduled to send. */ $check_stats = $statsapi->CheckUserStats($user, $subscriber_count, $scheduletime); list($ok_to_send, $not_ok_to_send_reason) = $check_stats; if (!$ok_to_send) { echo $this->PrintError($not_ok_to_send_reason); // Please refer to Mitch about why I comment this out //$this->FilterRecipients($send_details['Lists'], GetLang($not_ok_to_send_reason)); break; } $send_details['SendStartTime'] = $scheduletime; /** * Store required tracker variables in send details */ if (check($this, 'mailTrack', true)) { if ($this->GetApi('module_TrackerFactory', false)) { $list = module_Tracker::GetRequestOptionNamesForAllTracker(); foreach ($list as $each) { if (isset($_POST[$each])) { $send_details[$each] = $_POST[$each]; } } } } } /** * see if they have hit refresh on this last step. * if they have, then there will already be an approvejob session variable. * * If there is one there already, clean it up. * Give the user back their email credits and delete the stats etc. */ $job_already_started = IEM::sessionGet('ApproveJob'); if ($job_already_started) { $send_size = IEM::sessionGet('JobSendSize'); $statsapi = $this->GetApi('Stats'); $jobapi = $this->GetApi('Jobs'); // we need to start the job // then get the queue // then we can get the stats // so a user can get their credits back // if they cancel a send before doing anything. $jobapi->StartJob($job_already_started); $queueid = $jobapi->GetJobQueue($job_already_started); $statid = $statsapi->GetStatsByQueue($queueid); $statsapi->Delete($statid, 'n'); $jobapi->PauseJob($job_already_started); $jobapi->Delete($job_already_started); IEM::sessionRemove('JobSendSize'); IEM::sessionRemove('ApproveJob'); } $jobcreated = $jobapi->Create('send', $scheduletime, $user->userid, $send_details, 'newsletter', $send_details['Newsletter'], $send_details['Lists'], $approved); IEM::sessionSet('ApproveJob', $jobcreated); IEM::sessionSet('JobSendSize', $subscriber_count); // if we're not using scheduled sending, create the queue and start 'er up! if (!SENDSTUDIO_CRON_ENABLED || SENDSTUDIO_CRON_SEND <= 0) { /** * Record the user stats for this send. * We have to do it here so you can't schedule multiple sends and then it records everything. */ $statsapi->RecordUserStats($user->userid, $jobcreated, $subscriber_count, $scheduletime); $subscriberApi = $this->GetApi('Subscribers'); $sendqueue = $subscriberApi->CreateQueue('Send'); $jobapi->StartJob($jobcreated); $queuedok = $jobapi->JobQueue($jobcreated, $sendqueue); $send_criteria = $send_details['SendCriteria']; $queueinfo = array('queueid' => $sendqueue, 'queuetype' => 'send', 'ownerid' => $user->userid); if (isset($send_details['Segments']) && is_array($send_details['Segments'])) { $subscriberApi->GetSubscribersFromSegment($send_details['Segments'], false, $queueinfo, 'nosort'); } else { $subscriberApi->GetSubscribers($send_criteria, array(), false, $queueinfo, $user->userid); } if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $subscriberApi->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $subscriberApi->RemoveDuplicatesInQueue($sendqueue, 'send', $send_details['Lists']); $subscriberApi->RemoveBannedEmails($send_details['Lists'], $sendqueue, 'send'); $subscriberApi->RemoveUnsubscribedEmails($send_details['Lists'], $sendqueue, 'send'); if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $subscriberApi->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $send_details['SendSize'] = $subscriberApi->QueueSize($sendqueue, 'send'); $newsletterstats = $send_details; $newsletterstats['Job'] = $jobcreated; $newsletterstats['Queue'] = $sendqueue; $newsletterstats['SentBy'] = $queueinfo['ownerid']; $statid = $statsapi->SaveNewsletterStats($newsletterstats); /** * Process tracker request hwere because cron was not enabled * @todo Result for the call to module_Tracker::ParseOptionsForAllTracker() is not being processed and being ignored */ if (check($this, 'TrackAllLinks', true)) { if ($this->GetApi('module_TrackerFactory', false)) { $temp = array_merge($_POST, array( 'statid' => $statid, 'stattype' => 'newsletter', 'newsletterid' => $send_details['Newsletter'])); $status = module_Tracker::ParseOptionsForAllTracker($temp); } } $send_details['StatID'] = $statid; /** * So we can link user stats to send stats, we need to update it. */ $statsapi->UpdateUserStats($user->userid, $jobcreated, $statid); $jobapi->PauseJob($jobcreated); IEM::sessionSet('SendDetails', $send_details); $GLOBALS['JobID'] = $jobcreated; } $listdetails = array(); $listApi = $this->GetApi('Lists'); foreach ($send_details['Lists'] as $l => $listid) { $listApi->Load($listid); $listdetails[] = $listApi->Get('name'); } $listnames = implode(', ', $listdetails); $GLOBALS['Send_NewsletterName'] = sprintf(GetLang('Send_NewsletterName'), htmlspecialchars($newslettername, ENT_QUOTES, SENDSTUDIO_CHARSET)); $GLOBALS['Send_NewsletterSubject'] = sprintf(GetLang('Send_NewsletterSubject'), htmlspecialchars($newslettersubject, ENT_QUOTES, SENDSTUDIO_CHARSET)); $GLOBALS['Send_SubscriberList'] = sprintf(GetLang('Send_SubscriberList'), htmlspecialchars($listnames, ENT_QUOTES, SENDSTUDIO_CHARSET)); $last_sent_details = $newsletterApi->GetLastSent($send_details['Newsletter']); $last_sent = $last_sent_details['starttime']; if ($last_sent <= 0 && $send_details['SendSize'] > 5) { $GLOBALS['SentToTestListWarning'] = $this->PrintWarning('SendToTestListWarning'); } $SendInfo = IEM::sessionGet('SendInfoDetails'); if (SENDSTUDIO_CRON_ENABLED && SENDSTUDIO_CRON_SEND > 0) { /** * Record the user stats for this send. * We have to do it here so you can't schedule multiple sends and then it records everything. */ $statsapi->RecordUserStats($user->userid, $jobcreated, $subscriber_count, $scheduletime); $GLOBALS['Send_ScheduleTime'] = sprintf(GetLang('JobScheduled'), $this->PrintTime($scheduletime)); $GLOBALS['Send_TotalRecipients'] = sprintf(GetLang('Send_TotalRecipients_Cron'), $this->FormatNumber($SendInfo['Count'])); $this->ParseTemplate('Send_Step4_Cron'); break; } $GLOBALS['Send_TotalRecipients'] = sprintf(GetLang('Send_TotalRecipients'), $this->FormatNumber($newsletterApi->QueueSize($sendqueue, 'send'))); $this->ParseTemplate('Send_Step4'); break; case 'step3': $this->Step3(); break; case 'step2': $filteringOption = 0; $lists = array(); $segments = array(); if (isset($_POST['ShowFilteringOptions'])) { $filteringOption = intval($_POST['ShowFilteringOptions']); if ($filteringOption != 0) { $user->SetSettings('ShowFilteringOptions', $filteringOption); } } if ($filteringOption == 3 && !$user->HasAccess('Segments', 'Send')) { $filteringOption = 1; } switch ($filteringOption) { // This is when a list is selected case 1: case 2: if (isset($_POST['lists'])) { $lists = $_POST['lists']; } break; // This is when a segment is selected case 3: if (isset($_POST['segments']) && is_array($_POST['segments'])) { $segments = $_POST['segments']; } break; // A list/segment can be selected using "GET" request default: if (isset($_GET['list'])) { $lists = array((int)$_GET['list']); $filteringOption = 1; $user->SetSettings('ShowFilteringOptions', 1); } elseif (isset($_GET['segment'])) { $segments = array(intval($_GET['segment'])); $filteringOption = 3; $user->SetSettings('ShowFilteringOptions', 3); } break; } if ($filteringOption == 1 || $filteringOption == 2) { if (empty($lists)) { $GLOBALS['Error'] = GetLang('Send_Step1_ChooseListToSendTo'); $GLOBALS['Message'] = $this->ParseTemplate('ErrorMsg', true, false); $this->ChooseList('Send', 'step2', false); break; } if (!$user->Admin()) { $availabeLists = $user->GetLists(); if (is_array($availabeLists)) { $availabeLists = array_keys($availabeLists); } else { $this->ChooseList('Send', 'step2', false); break; } $intersects = array_intersect($lists, $availabeLists); if (count($lists) != count($intersects)) { $this->ChooseList('Send', 'step2', false); break; } } } if ($filteringOption == 1) { $this->FilterRecipients($lists); } elseif ($filteringOption == 2) { $send_details = IEM::sessionGet('SendDetails'); $send_details['Lists'] = $lists; $send_details['SendCriteria'] = array('Confirmed' => 1); $send_details['SendSize'] = null; $send_details['BackStep'] = 1; IEM::sessionSet('SendDetails', $send_details); $this->Step3(); } else { if (empty($segments)) { $GLOBALS['Error'] = GetLang('Send_Step1_ChooseSegmentToSendTo'); $GLOBALS['Message'] = $this->ParseTemplate('ErrorMsg', true, false); $this->ChooseList('Send', 'step2', false); break; } if (!$user->Admin()) { $availableSegments = $user->GetSegmentList(); if (!empty($availableSegments)) { $availableSegments = array_keys($availableSegments); } foreach ($segments as $segment) { if (!in_array($segment, $availableSegments)) { $this->DenyAccess(); exit(); } } } $send_details = IEM::sessionGet('SendDetails'); $send_details['Lists'] = null; $send_details['SendCriteria'] = array(); $send_details['SendSize'] = null; $send_details['Segments'] = $segments; IEM::sessionSet('SendDetails', $send_details); $this->Step3(); } break; case 'resumesend': $this->ResumeSend(); break; case 'resend': $this->ResendJob(); break; default: IEM::sessionRemove('SendDetails'); $id = (isset($_GET['id'])) ? (int)$_GET['id'] : 0; if (!$this->CanAccessNewsletter($id)) { $id = 0; } $senddetails['NewsletterChosen'] = $id; IEM::sessionSet('SendDetails', $senddetails); $newsletterapi = $this->GetApi('Newsletters'); $newsletterowner = ($user->Admin() ? 0 : $user->userid); $newsletters = $newsletterapi->GetLiveNewsletters($newsletterowner); if (empty($newsletters)) { $all_newsletters = $newsletterapi->GetNewsletters($newsletterowner, array(), true); if ($all_newsletters < 1) { if ($user->HasAccess('Newsletters', 'Create')) { $GLOBALS['Message'] = $this->PrintSuccess('NoNewsletters', GetLang('NoNewsletters_HasAccess')); $GLOBALS['Newsletters_AddButton'] = $this->ParseTemplate('Newsletter_Create_Button', true, false); } else { $GLOBALS['Message'] = $this->PrintSuccess('NoNewsletters', ''); } } else { if ($user->HasAccess('Newsletters', 'Approve')) { $GLOBALS['Message'] = $this->PrintSuccess('NoLiveNewsletters', GetLang('NoLiveNewsletters_HasAccess')); } else { $GLOBALS['Message'] = $this->PrintSuccess('NoLiveNewsletters', ''); } } $this->ParseTemplate('Newsletters_Send_Empty'); break; } $this->ChooseList('Send', 'step2', false); break; } $this->PrintFooter($popup); }