BxMassMailerModule.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup MassMailer Mass Mailer
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. class BxMassMailerModule extends BxBaseModGeneralModule
  12. {
  13. private $aSegments = array();
  14. function __construct(&$aModule)
  15. {
  16. parent::__construct($aModule);
  17. $CNF = &$this->_oConfig->CNF;
  18. $aRv = array('lvl:0' => _t('_bx_massmailer_segments_all'));
  19. $oAccountQuery = BxDolAclQuery::getInstance();
  20. $aTmp = array();
  21. $oAccountQuery->getLevels(array('type' => 'all_active_pair'), $aTmp);
  22. foreach ($aTmp as $sKey => $sTmp){
  23. if ($sKey != MEMBERSHIP_ID_NON_MEMBER && $sKey != MEMBERSHIP_ID_ACCOUNT)
  24. $aRv['lvl:' . $sKey] = _t($sTmp);
  25. };
  26. $aTmp = $this->_oDb->getSegments();
  27. foreach ($aTmp as $sKey => $sTmp){
  28. $aRv['cst:' . $sKey] = _t($sTmp);
  29. };
  30. $this->aSegments = $aRv;
  31. }
  32. /**
  33. * ACTION METHODS
  34. */
  35. public function actionTrack($sActionName, $sHash)
  36. {
  37. if (isset($sHash) && trim($sHash) != ""){
  38. switch ($sActionName) {
  39. case 'click':
  40. $sLink = $this->_oDb->updateDateClickForLink($sHash);
  41. if (strpos($sLink, '://') === false)
  42. $sLink = BX_DOL_URL_ROOT . $sLink;
  43. header('Location: ' . $sLink);
  44. break;
  45. case 'seen':
  46. header('Content-Type: image/png');
  47. $this->_oDb->updateDateSeenForLetter($sHash);
  48. break;
  49. }
  50. }
  51. }
  52. public function actionGetReportsData($sReportName, $sReportType, $sDateFrom, $sDateTo, $sSegment)
  53. {
  54. if($this->checkAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
  55. return '';
  56. header('Content-Type: application/json');
  57. echo json_encode($this->getReportsData($sReportName, $sReportType, $sDateFrom, $sDateTo, $sSegment));
  58. }
  59. /**
  60. * Chart.js 4 config for a report: type, data and options.
  61. */
  62. public function getReportsData($sReportName, $sReportType, $sDateFrom, $sDateTo, $sSegment)
  63. {
  64. $iDateFrom = strtotime($sDateFrom);
  65. $iDateTo = strtotime($sDateTo);
  66. $bIsTimeX = false;
  67. switch ($sReportName) {
  68. case "SUBSCRIBERS_INFO":
  69. $bIsTimeX = true;
  70. $aReport = $this->getReportSubscribers($sReportType == 'content_total', $iDateFrom, $iDateTo, $sSegment);
  71. break;
  72. case "CAMPAIGN_REPORT":
  73. $aReport = $this->getReportCampaign($sReportType);
  74. break;
  75. default:
  76. $aReport = array(
  77. 'labels' => array(),
  78. 'values' => array(array('legend' => '', 'data' => array())),
  79. 'strings' => array(_t('_bx_massmailer_txt_item'), _t('_bx_massmailer_txt_value')),
  80. 'max' => 0
  81. );
  82. }
  83. $iMinValueY = 0;
  84. $iMaxValueY = ceil($aReport['max'] * 1.1);
  85. return array(
  86. 'type' => $bIsTimeX ? 'line' : 'bar',
  87. 'data' => array(
  88. 'labels' => $aReport['labels'],
  89. 'datasets' => $this->getReportDatasets($aReport['values'])
  90. ),
  91. 'options' => array(
  92. 'plugins' => array(
  93. 'legend' => array(
  94. 'position' => 'bottom',
  95. 'display' => count($aReport['values']) > 1
  96. )
  97. ),
  98. 'scales' => array(
  99. 'y' => array(
  100. 'max' => $iMaxValueY,
  101. 'min' => $iMinValueY,
  102. 'ticks' => array(
  103. 'stepSize' => $this->getReportStep($iMinValueY, $iMaxValueY),
  104. 'autoSkip' => true
  105. )
  106. ),
  107. 'x' => $bIsTimeX ? $this->getReportTimeAxis($iDateFrom, $iDateTo) : array('ticks' => array('autoSkip' => false), 'display' => true)
  108. )
  109. ),
  110. 'links' => array(),
  111. 'strings' => $aReport['strings'],
  112. );
  113. }
  114. /**
  115. * Subscribed and unsubscribed accounts of a segment, a point per day.
  116. */
  117. private function getReportSubscribers($bTotal, $iDateFrom, $iDateTo, $sSegment)
  118. {
  119. $sSql = $this->getSqlBySegment($sSegment);
  120. $iMaxValueY = 0;
  121. $aSubscribed = $this->getReportSeries($this->_oDb->getAccountsByTermsStat($iDateFrom, $iDateTo, $sSql), (int)$this->_oDb->getAccountsByTermsStatInitValue($iDateFrom, $sSql), $bTotal, $iDateFrom, $iDateTo, $iMaxValueY);
  122. $aUnsubscribed = $this->getReportSeries($this->_oDb->getAccountsByTermsStatUnsubscribe($iDateFrom, $iDateTo, $sSql), (int)$this->_oDb->getAccountsByTermsStatInitValueUnsubscribe($iDateFrom, $sSql), $bTotal, $iDateFrom, $iDateTo, $iMaxValueY);
  123. return array(
  124. 'labels' => array(),
  125. 'values' => array(
  126. array('legend' => _t('_bx_massmailer_txt_subscribed'), 'data' => $aSubscribed),
  127. array('legend' => _t('_bx_massmailer_txt_unsubscribed'), 'data' => $aUnsubscribed)
  128. ),
  129. 'strings' => array(_t('_bx_massmailer_txt_date'), ''),
  130. 'max' => $iMaxValueY
  131. );
  132. }
  133. /**
  134. * Points from the per-day counts in $aRows: the running total before the day when $bTotal is set,
  135. * the day's count otherwise. $iMaxValueY is raised to the largest total or count.
  136. */
  137. private function getReportSeries($aRows, $iTotal, $bTotal, $iDateFrom, $iDateTo, &$iMaxValueY)
  138. {
  139. $aCounts = array_column($aRows, 'count', 'period');
  140. $aPoints = array();
  141. for ($i = $iDateFrom; $i < $iDateTo; $i += 86400) {
  142. $sX = date('Y-m-d', $i);
  143. $iCount = isset($aCounts[$sX]) ? (int)$aCounts[$sX] : 0;
  144. $aPoints[] = array('x' => $sX, 'y' => $bTotal ? $iTotal : $iCount);
  145. $iTotal += $iCount;
  146. $iMaxValueY = max($iMaxValueY, $bTotal ? $iTotal : $iCount);
  147. }
  148. return $aPoints;
  149. }
  150. private function getReportCampaign($iCampaignId)
  151. {
  152. $aStat = $this->_oDb->getStatByCampaignId($iCampaignId);
  153. $iUnopened = $aStat['total'] - $aStat['seen'];
  154. return array(
  155. // One category for all bars: a category axis without labels places no bars.
  156. 'labels' => array(''),
  157. 'values' => array(
  158. array('legend' => _t('_bx_massmailer_txt_sent_total_title', $aStat['total']), 'data' => array($aStat['total'])),
  159. array('legend' => _t('_bx_massmailer_txt_opened_total_title', $aStat['seen']), 'data' => array($aStat['seen'])),
  160. array('legend' => _t('_bx_massmailer_txt_unopened_total_title', $iUnopened), 'data' => array($iUnopened)),
  161. array('legend' => _t('_bx_massmailer_txt_clicked_total_title', $aStat['clicked']), 'data' => array($aStat['clicked'])),
  162. array('legend' => _t('_bx_massmailer_txt_unsubscribed_total_title', $aStat['unsubscribed']), 'data' => array($aStat['unsubscribed']))
  163. ),
  164. 'strings' => array(_t('_bx_massmailer_txt_item'), _t('_bx_massmailer_txt_value')),
  165. 'max' => $aStat['total']
  166. );
  167. }
  168. private function getReportTimeAxis($iDateFrom, $iDateTo)
  169. {
  170. $iDays = ($iDateTo - $iDateFrom) / 86400;
  171. $sUnit = 'day';
  172. if ($iDays > 100)
  173. $sUnit = 'month';
  174. else if ($iDays > 50)
  175. $sUnit = 'week';
  176. return array(
  177. 'type' => 'time',
  178. 'time' => array(
  179. 'tooltipFormat' => 'DD.MM.YYYY',
  180. 'unit' => $sUnit,
  181. ),
  182. 'display' => true,
  183. 'ticks' => array(
  184. 'display' => true,
  185. 'autoSkip' => true,
  186. )
  187. );
  188. }
  189. private function getReportDatasets($aValues)
  190. {
  191. $aColors = array('#3366CC', '#DC3912', '#FF9900', '#109618', '#990099');
  192. $aDatasets = array();
  193. foreach ($aValues as $i => $aValue)
  194. $aDatasets[] = array(
  195. 'label' => $aValue['legend'],
  196. 'fill' => false,
  197. 'backgroundColor' => $aColors[$i],
  198. 'borderColor' => $aColors[$i],
  199. 'borderWidth' => 1,
  200. 'data' => $aValue['data']
  201. );
  202. return $aDatasets;
  203. }
  204. /**
  205. * SERVICE METHODS
  206. */
  207. public function serviceGetSafeServices()
  208. {
  209. return array_merge(parent::serviceGetSafeServices(), [
  210. 'CampagnSubscribers' => ''
  211. ]);
  212. }
  213. /**
  214. * @page service Service Calls
  215. * @section bx_massmailer Mass mailer
  216. * @subsection bx_massmailer-page_blocks Page Blocks
  217. * @subsubsection bx_massmailer-campagn_subscribers campagn_subscribers
  218. *
  219. * @code bx_srv('bx_massmailer', 'campagn_subscribers', [...]); @endcode
  220. *
  221. * Get page block with the campagn's subscribers list
  222. *
  223. * @param $iContentId content ID.
  224. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  225. *
  226. * @see BxMassMailerModule::serviceCampagnSubscribers
  227. */
  228. /**
  229. * @ref bx_massmailer-campagn_subscribers "entity_view"
  230. */
  231. public function serviceCampagnSubscribers ($iContentId = 0, $iStart = 0, $iPerPage = 20)
  232. {
  233. if($this->checkAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
  234. return $this->_bIsApi ? [] : '';
  235. if(!$iContentId)
  236. $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
  237. if(!$iContentId)
  238. return $this->_bIsApi ? [] : '';
  239. $CNF = &$this->_oConfig->CNF;
  240. $sGrid = $CNF['OBJECT_GRID_LETTERS'];
  241. $oGrid = BxDolGrid::getObjectInstance($sGrid);
  242. if(!$oGrid)
  243. return $this->_bIsApi ? [] : '';
  244. if($iContentId)
  245. $oGrid->setContentId($iContentId);
  246. if($this->_bIsApi)
  247. return [
  248. bx_api_get_block('grid', $oGrid->getCodeAPI())
  249. ];
  250. return $oGrid->getCode();
  251. }
  252. /**
  253. * @page service Service Calls
  254. * @section bx_massmailer Mass mailer
  255. * @subsection bx_massmailer-page_blocks Page Blocks
  256. * @subsubsection bx_massmailer-campagn_info campagn_info
  257. *
  258. * @code bx_srv('bx_massmailer', 'campagn_info', [...]); @endcode
  259. *
  260. * Get page block with the campagn info
  261. *
  262. * @param $iContentId content ID.
  263. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  264. *
  265. * @see BxMassMailerModule::serviceCampagnInfo
  266. */
  267. /**
  268. * @ref bx_massmailer-campagn_info "campagn_info"
  269. */
  270. public function serviceCampagnInfo ($iContentId = 0)
  271. {
  272. if(!$iContentId)
  273. $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
  274. if(!$iContentId)
  275. return false;
  276. $mixedResult = $this->_oTemplate->getInfo($iContentId);
  277. if($this->_bIsApi)
  278. return [bx_api_get_block('simple_list', $mixedResult)];
  279. return $mixedResult;
  280. }
  281. /**
  282. * @page service Service Calls
  283. * @section bx_massmailer Mass mailer
  284. * @subsection bx_massmailer-page_blocks Page Blocks
  285. * @subsubsection bx_massmailer-campagn_links campagn_links
  286. *
  287. * @code bx_srv('bx_massmailer', 'campagn_links', [...]); @endcode
  288. *
  289. * Get page block with the campagn links list
  290. *
  291. * @param $iContentId content ID
  292. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  293. *
  294. * @see BxMassMailerModule::serviceCampagnLinks
  295. */
  296. /**
  297. * @ref bx_massmailer-campagn_links "campagn_links"
  298. */
  299. public function serviceCampagnLinks ($iContentId = 0)
  300. {
  301. if(!$iContentId)
  302. $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
  303. if(!$iContentId)
  304. return false;
  305. $aData = $this->_oDb->getClicksByCampaignId($iContentId);
  306. if(!$aData || !is_array($aData))
  307. return false;
  308. $mixedResult = $this->_oTemplate->getClicks($aData);
  309. if($this->_bIsApi)
  310. return [bx_api_get_block('table', $mixedResult)];
  311. return $mixedResult;
  312. }
  313. /**
  314. * @page service Service Calls
  315. * @section bx_massmailer Mass mailer
  316. * @subsection bx_massmailer-page_blocks Page Blocks
  317. * @subsubsection bx_massmailer-total_subscribers total_subscribers
  318. *
  319. * @code bx_srv('bx_massmailer', 'total_subscribers', [...]); @endcode
  320. *
  321. * Get page block with subscribers charts
  322. *
  323. * @param $aParams an array with search params.
  324. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  325. *
  326. * @see BxMassMailerModule::serviceTotalSubscribers
  327. */
  328. /**
  329. * @ref bx_massmailer-total_subscribers "total_subscribers"
  330. */
  331. public function serviceTotalSubscribers ()
  332. {
  333. if($this->checkAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
  334. return '';
  335. return $this->_oTemplate->getTotalSubscribers();
  336. }
  337. /**
  338. * @page service Service Calls
  339. * @section bx_massmailer Mass mailer
  340. * @subsection bx_massmailer-page_blocks Page Blocks
  341. * @subsubsection bx_massmailer-entity_breadcrumb entity_breadcrumb
  342. *
  343. * @code bx_srv('bx_massmailer', 'entity_breadcrumb', [...]); @endcode
  344. *
  345. * Get page block with page's breadcrumbs
  346. *
  347. * @param $iContentId content ID
  348. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  349. *
  350. * @see BxMassMailerModule::serviceEntityBreadcrumb
  351. */
  352. /**
  353. * @ref bx_massmailer-entity_breadcrumb "entity_breadcrumb"
  354. */
  355. public function serviceEntityBreadcrumb ($iContentId = 0)
  356. {
  357. $CNF = &$this->_oConfig->CNF;
  358. $oPermalink = BxDolPermalinks::getInstance();
  359. if (!$iContentId)
  360. $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
  361. if (!$iContentId){
  362. $aContentInfo[$CNF['FIELD_TITLE']] = _t('_bx_massmailer_txt_breadcrumb_title_new_campaign');
  363. $aContentInfo['url'] = $oPermalink->permalink('page.php?i=' . $CNF['URI_ADD_CAMPAIGN']);
  364. }
  365. else{
  366. $aContentInfo = $this->_oDb->getCampaignInfoById($iContentId);
  367. if (!$aContentInfo)
  368. return false;
  369. $aContentInfo['url'] = $oPermalink->permalink('page.php?i=' . $CNF['URI_VIEW_CAMPAIGN'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
  370. }
  371. return $this->_oTemplate->entryBreadcrumb($aContentInfo);
  372. }
  373. /**
  374. * @page service Service Calls
  375. * @section bx_massmailer Mass mailer
  376. * @subsection bx_massmailer-page_blocks Page Blocks
  377. * @subsubsection bx_massmailer-attributes attributes
  378. *
  379. * @code bx_srv('bx_massmailer', 'attributes', [...]); @endcode
  380. *
  381. * Get page block with attributes list
  382. *
  383. * @return HTML string with block content to display on the site. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  384. *
  385. * @see BxMassMailerModule::serviceAttributes
  386. */
  387. /**
  388. * @ref bx_massmailer-attributes "attributes"
  389. */
  390. public function serviceAttributes ()
  391. {
  392. $aAttributesParts = array();
  393. $aAttributesParts['account'] = array(
  394. _t('_bx_massmailer_txt_attribute_global'),
  395. array(
  396. 'email' => _t('_bx_massmailer_txt_attribute_email'),
  397. 'account_name' => _t('_bx_massmailer_txt_attribute_account_name'),
  398. 'account_id' => _t('_bx_massmailer_txt_attribute_account_id'),
  399. 'reset_password_url' => _t('_bx_massmailer_txt_attribute_reset_password_url'),
  400. 'unsubscribe_url' => _t('_bx_massmailer_txt_attribute_unsubscribe_url')
  401. )
  402. );
  403. $BxDolModuleQuery = BxDolModuleQuery::getInstance();
  404. $aModules = $BxDolModuleQuery->getModulesBy(array('type' => 'modules', 'active' => 1));
  405. foreach($aModules as $aModule){
  406. if(BxDolRequest::serviceExists($aModule['name'], 'act_as_profile') && BxDolService::call($aModule['name'], 'act_as_profile') == true){
  407. $oModule = BxDolModule::getInstance($aModule['name']);
  408. $aTmp2 = array();
  409. $aTmp = $oModule->serviceGetSearchableFieldsExtended('view');
  410. foreach($aTmp as $sKey => $aField){
  411. $aTmp2[$sKey] = _t($aField['caption']);
  412. }
  413. $aAttributesParts[$aModule['name']] = array($aModule['title'], $aTmp2);
  414. }
  415. }
  416. return $this->_oTemplate->getAttributes($aAttributesParts);
  417. }
  418. /**
  419. * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
  420. */
  421. public function checkAllowedAdd ($isPerformAction = false)
  422. {
  423. return $this->checkAllowed($isPerformAction);
  424. }
  425. /**
  426. * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
  427. */
  428. public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
  429. {
  430. return $this->checkAllowed($isPerformAction);
  431. }
  432. /**
  433. * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
  434. */
  435. public function checkAllowedEditAnyEntryForProfile ($isPerformAction = false, $iProfileId = false)
  436. {
  437. return $this->checkAllowed($isPerformAction);
  438. }
  439. /**
  440. * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
  441. */
  442. public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
  443. {
  444. return $this->checkAllowed($isPerformAction);
  445. }
  446. public function getSegments($sKey = "")
  447. {
  448. if ($sKey == ""){
  449. return $this->aSegments;
  450. }
  451. else{
  452. if (isset($this->aSegments[$sKey]))
  453. return $this->aSegments[$sKey];
  454. return false;
  455. }
  456. }
  457. public function sendTest($sEmail, $iCampaignId)
  458. {
  459. $aTmp = $this->getDataForCampaign($iCampaignId);
  460. $aTemplate = $aTmp[0];
  461. $aCustomHeaders = $aTmp[1];
  462. $aCampaign = $aTmp[2];
  463. if (!$aTemplate)
  464. return false;
  465. return $this->sendLetter($sEmail, $iCampaignId, $aCustomHeaders, getLoggedId(), $aTemplate, false, $aCampaign['is_track_links']);
  466. }
  467. public function sendAll($iCampaignId)
  468. {
  469. $aTmp = $this->getDataForCampaign($iCampaignId);
  470. $aTemplate = $aTmp[0];
  471. $aCustomHeaders = $aTmp[1];
  472. $aCampaign = $aTmp[2];
  473. if (!$aTemplate)
  474. return false;
  475. $GLOBALS['bx_profiler_disable'] = true;
  476. unset($GLOBALS['bx_profiler']);
  477. $this->_oDb->deleteCampaignData($iCampaignId);
  478. $aAccounts = $this->getEmailsBySegment($aCampaign['segments'], $aCampaign['is_one_per_account']);
  479. foreach ($aAccounts as $aAccountInfo){
  480. $this->sendLetter($aAccountInfo['email'], $iCampaignId, $aCustomHeaders, $aAccountInfo['account_id'], $aTemplate, true, $aCampaign['is_track_links']);
  481. $this->_oDb->addEmailToSentListForCampaign($iCampaignId, $aAccountInfo['email']);
  482. if (1 == rand(1, 300)) {
  483. BxDolDb::getInstance()->cleanMemoryAll();
  484. unset($GLOBALS['bxDolClasses']);
  485. }
  486. }
  487. $this->_oDb->sendCampaign($iCampaignId);
  488. return true;
  489. }
  490. private function sendLetter($sEmail, $iCampaignId, $aCustomHeaders, $iAccountId, $aTemplate, $bAddToQueue, $bTrackKinks)
  491. {
  492. $sLetterCode = $this->_oDb->addLetter($iCampaignId, $sEmail);
  493. $aMarkers = $this->addMarkers($iAccountId, $sLetterCode);
  494. bx_alert($this->_aModule['name'], 'user_fields', $iCampaignId, $iAccountId, array('email' => $sEmail, 'markers' => &$aMarkers, 'markers_ref' => &$aMarkers));
  495. if ($bTrackKinks){
  496. $regexp = "<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>";
  497. $aTemplate['Body'] = preg_replace_callback("/$regexp/siU",
  498. function ($aMatch) use ($iCampaignId, $sLetterCode, $aMarkers) {
  499. if ($aMatch[2] != '{unsubscribe_url}'){
  500. $sUrl = bx_replace_markers($aMatch[2], $aMarkers);
  501. $sLinkCode = $this->_oDb->addLink($sLetterCode, $sUrl, $aMatch[3], $iCampaignId);
  502. return str_replace($sUrl, BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'track/click/' . $sLinkCode . "/", $aMatch[0]);
  503. }
  504. else{
  505. return $aMatch[0];
  506. }
  507. },
  508. $aTemplate['Body']);
  509. }
  510. $aMarkersInLetter = [];
  511. preg_match_all("/\{[^\}]*\}/i", $aTemplate['Body'], $aMarkersInLetter);
  512. if ($aMarkersInLetter[0]>0){
  513. foreach ($aMarkersInLetter[0] as $sMarker){
  514. $sMarker = str_replace(['{', '}'], '', $sMarker);
  515. if (!array_key_exists($sMarker, $aMarkers)) {
  516. $aMarkers[$sMarker] = '';
  517. }
  518. }
  519. }
  520. return sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, $aMarkers, BX_EMAIL_MASS, 'html', false, $aCustomHeaders, $bAddToQueue);
  521. }
  522. public function getEmailCountInSegment($iCampaignId)
  523. {
  524. $aTmp = $this->getDataForCampaign($iCampaignId);
  525. $aCampaign = $aTmp[2];
  526. $aAccounts = $this->getEmailsBySegment($aCampaign['segments'], $aCampaign['is_one_per_account']);
  527. return count($aAccounts);
  528. }
  529. private function getEmailsBySegment($sSegment, $bIsOnePerAccount)
  530. {
  531. $sTerms = $this->getSqlBySegment($sSegment);
  532. if ($bIsOnePerAccount){
  533. $sTerms .= "AND `tp`.`id` = `ta`.`profile_id` ";
  534. }
  535. return $this->_oDb->getAccountsByTerms($sTerms);
  536. }
  537. public function checkAllowed($isPerformAction = false)
  538. {
  539. $aCheck = checkActionModule($this->_iProfileId, 'use massmailer', $this->getName(), $isPerformAction);
  540. if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
  541. return _t('_sys_txt_access_denied');
  542. return CHECK_ACTION_RESULT_ALLOWED;
  543. }
  544. private function getSqlBySegment($sSegment)
  545. {
  546. $sRv = '';
  547. $sJoin = ' INNER ';
  548. $aMatches = array();
  549. if (strpos($sSegment, 'lvl:') !== false){
  550. $sLvl = str_replace('lvl:', '', $sSegment);
  551. switch ($sLvl) {
  552. case 0:
  553. $sRv = '';
  554. $sJoin = ' LEFT ';
  555. break;
  556. case MEMBERSHIP_ID_UNCONFIRMED:
  557. $sRv = " AND `ta`.`email_confirmed` = 0 AND `ta`.`phone_confirmed` = 0 ";
  558. break;
  559. case MEMBERSHIP_ID_SUSPENDED:
  560. $sRv = " AND `tp`.`status` = 'suspended' ";
  561. break;
  562. case MEMBERSHIP_ID_PENDING:
  563. $sRv = " AND `tp`.`status` = 'pending' ";
  564. break;
  565. case MEMBERSHIP_ID_STANDARD:
  566. $sRv = " AND `tp`.`id` NOT IN (SELECT `IDMember` FROM `sys_acl_levels_members`) ";
  567. break;
  568. default:
  569. $sRv = $this->_oDb->prepareAsString(" AND `tp`.`id` IN (SELECT `IDMember` FROM `sys_acl_levels_members` WHERE IDLevel = ?) ", $sLvl);
  570. break;
  571. }
  572. $sRv .= $this->_oDb->getTermsByProfileTypes($this->getProfileModules());
  573. }
  574. else if (preg_match('/^cst:(\d+)$/', $sSegment, $aMatches)){
  575. //--- A custom segment is a list of emails: only the accounts with one of them.
  576. $sRv = $this->_oDb->getTermsByEmails($this->_oDb->getSegmentEmails((int)$aMatches[1]));
  577. $sRv .= $this->_oDb->getTermsByProfileTypes($this->getProfileModules());
  578. }
  579. return $sJoin . " JOIN `sys_profiles` AS `tp` ON `tp`.`account_id`=`ta`.`id` " . $sRv;
  580. }
  581. private function getProfileModules()
  582. {
  583. $aResult = array();
  584. $BxDolModuleQuery = BxDolModuleQuery::getInstance();
  585. $aModules = $BxDolModuleQuery->getModulesBy(array('type' => 'modules', 'active' => 1));
  586. foreach($aModules as $aModule){
  587. if(BxDolRequest::serviceExists($aModule['name'], 'act_as_profile') && BxDolService::call($aModule['name'], 'act_as_profile') == true){
  588. array_push($aResult, $aModule['name']);
  589. }
  590. }
  591. return $aResult;
  592. }
  593. private function getDataForCampaign($iCampaignId)
  594. {
  595. $aCampaign = $this->_oDb->getCampaignInfoById($iCampaignId);
  596. $oEmailTemplates = BxDolEmailTemplates::getInstance();
  597. $aTemplate = $oEmailTemplates->parseTemplate('bx_massmailer_email', array('content' => $aCampaign['body']));
  598. $aTemplate['Subject'] = $aCampaign['subject'];
  599. $aCustomHeaders = array();
  600. $sFrom = $aCampaign['from_name'] != '' ? $aCampaign['from_name'] : getParam('site_title');
  601. $aCustomHeaders['From'] = "=?UTF-8?B?" . base64_encode($sFrom) . "?= <" . getParam('site_email_notify') . ">" ;
  602. if ($aCampaign['reply_to'] != ''){
  603. $sFrom = $aCampaign['from_name'] != '' ? $aCampaign['from_name'] : getParam('site_title');
  604. $aCustomHeaders['Reply-To'] = "=?UTF-8?B?" . base64_encode($sFrom) . '?= <' . bx_process_output($aCampaign['reply_to']) . '>';
  605. $aCustomHeaders['X-Original-From'] = "=?UTF-8?B?" . base64_encode($sFrom) . '?= <' . bx_process_output($aCampaign['reply_to']) . '>';
  606. }
  607. return array($aTemplate, $aCustomHeaders, $aCampaign);
  608. }
  609. private function addMarkers($iAccountId, $sLetterCode)
  610. {
  611. $CNF = &$this->_oConfig->CNF;
  612. $aMarkers = array();
  613. $oAccount = BxDolAccount::getInstance($iAccountId);
  614. $aAccountInfo = $oAccount->getInfo();
  615. $sAccountEmail = $oAccount->getEmail();
  616. $aMarkers['email'] = $sAccountEmail;
  617. $aMarkers['account_name'] = $oAccount->getDisplayName();
  618. $aMarkers['account_id'] = $oAccount->id();
  619. if ($aAccountInfo['profile_id'] > 0){
  620. $oProfile = BxDolProfile::getInstance($aAccountInfo['profile_id']);
  621. $sModule = $oProfile->getModule();
  622. if (BxDolRequest::serviceExists($sModule, 'get_info')) {
  623. $aProfileInfo = bx_srv($sModule, 'get_info', array($oProfile->getContentId(), false));
  624. foreach ($aProfileInfo as $sKey => $sValue)
  625. if(!isset($aMarkers[$sKey]))
  626. $aMarkers[$sKey] = $sValue;
  627. }
  628. }
  629. $aMarkers['seen_image_url'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'track/seen/' . $sLetterCode . "/";
  630. $aMarkers['unsubscribe_url'] = BX_DOL_URL_ROOT . $oAccount->getUnsubscribeLink(BX_EMAIL_MASS) . "&lhash=" . $sLetterCode;
  631. $aMarkers['reset_password_url'] = '';
  632. if(($sResetPasswordUrl = bx_get_reset_password_link($sAccountEmail)) !== false)
  633. $aMarkers['reset_password_url'] = $sResetPasswordUrl . "&lhash=" . $sLetterCode;
  634. return $aMarkers;
  635. }
  636. private function getReportStep($iMin, $iMax)
  637. {
  638. $iCount = 10;
  639. return ceil(($iMax - $iMin) / $iCount);
  640. }
  641. public function checkAllowedDeleteAnyEntryForProfile ($isPerformAction = false, $iProfileId = false)
  642. {
  643. return _t('_sys_txt_access_denied');
  644. }
  645. }
  646. /** @} */