mailchimpsubscribe.class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. <?php
  2. require_once dirname(dirname(dirname(__FILE__))) . '/libs/mailchimp-rest-api/src/VPS/MailChimp.php';
  3. class MailChimpSubscribe
  4. {
  5. const MC_ERROR_PH = 'mailchimp';
  6. /**
  7. * The modX object.
  8. *
  9. * @since 1.0.0
  10. * @access public
  11. * @var modX The modX object.
  12. */
  13. public $modx;
  14. /**
  15. * @var \VPS\MailChimp
  16. */
  17. public $mailchimp;
  18. /**
  19. * The namespace for this package.
  20. *
  21. * @since 1.0.0
  22. * @access public
  23. * @var string The package namespace.
  24. */
  25. public $namespace = 'mailchimpsubscribe';
  26. /**
  27. * Holds all configs values.
  28. *
  29. * @since 1.0.0
  30. * @access public
  31. * @var array Config value holder.
  32. */
  33. public $config = [];
  34. /**
  35. * @var Sterc\FormIt\Hook
  36. */
  37. public $hook;
  38. /**
  39. * Holds mailchimp message for subscribing a user.
  40. *
  41. * @since 1.0.0
  42. * @access public
  43. * @var string Mailchimp result message.
  44. */
  45. public $mcSubscribeMessage = '';
  46. /**
  47. * Holds the name or id of the MailChimp list TV.
  48. *
  49. * @var string
  50. */
  51. public $mcListTV = '';
  52. /**
  53. * Should a user be created or should an existing user be updated.
  54. *
  55. * @since 1.0.0
  56. * @access private
  57. * @var string Mailchimp subscribe mode.
  58. */
  59. private $mcSubscribeMode = 'create';
  60. /**
  61. * Array containing mergefields and form fields mapping.
  62. * @var array
  63. */
  64. private $mapping = [];
  65. /**
  66. * Array containing all mergefields and form values.
  67. *
  68. * @var array
  69. */
  70. private $values = [];
  71. /**
  72. * Name of field used for subscribing to mailchimp newsletter.
  73. * @var string
  74. */
  75. private $subscribeField = 'newsgroup';
  76. /**
  77. * Value that the subscribe field should have for subscribing a user to mailchimp.
  78. * @var string
  79. */
  80. private $subscribeFieldValue = 'yes';
  81. /**
  82. * Initialize the class.
  83. *
  84. * @since 1.0.0
  85. * @param modX $modx The modX object.
  86. * @param array $config Array with config values.
  87. */
  88. public function __construct(modX $modx, array $config = [])
  89. {
  90. $this->modx =& $modx;
  91. $this->namespace = $this->modx->getOption('namespace', $config, 'mailchimpsubscribe');
  92. $corePath = $this->modx->getOption(
  93. 'site.core_path',
  94. $config,
  95. $this->modx->getOption('core_path') . 'components/mailchimpsubscribe/'
  96. );
  97. $assetsUrl = $this->modx->getOption(
  98. 'site.assets_url',
  99. $config,
  100. $this->modx->getOption('assets_url') . 'components/mailchimpsubscribe/'
  101. );
  102. $assetsPath = $this->modx->getOption(
  103. 'site.assets_path',
  104. $config,
  105. $this->modx->getOption('assets_path') . 'components/mailchimpsubscribe/'
  106. );
  107. $this->config = array_merge([
  108. 'namespace' => $this->namespace,
  109. 'corePath' => $corePath,
  110. 'modelPath' => $corePath . 'model/',
  111. 'chunksPath' => $corePath . 'elements/chunks/',
  112. 'snippetsPath' => $corePath . 'elements/snippets/',
  113. 'templatesPath' => $corePath . 'templates/',
  114. 'assetsPath' => $assetsPath,
  115. 'assetsUrl' => $assetsUrl,
  116. 'jsUrl' => $assetsUrl . 'js/',
  117. 'cssUrl' => $assetsUrl . 'css/',
  118. 'connectorUrl' => $assetsUrl . 'connector.php'
  119. ], $config);
  120. $this->mcListTV = $this->modx->getOption('mailchimpsubscribe.list_tv');
  121. $this->mcListTV = is_numeric($this->mcListTV) ? (int) $this->mcListTV : (string) $this->mcListTV;
  122. $this->modx->addPackage('mailchimpsubscribe', $this->config['modelPath']);
  123. $this->modx->lexicon->load('mailchimpsubscribe:default');
  124. }
  125. /**
  126. * @param $hook
  127. */
  128. private function setHook($hook)
  129. {
  130. $this->hook = $hook;
  131. }
  132. private function setSubscribeFields()
  133. {
  134. if ($this->hook->config['mailchimpSubscribeField']) {
  135. $this->subscribeField = $this->hook->config['mailchimpSubscribeField'];
  136. }
  137. if ($this->hook->config['mailchimpSubscribeFieldValue']) {
  138. $this->subscribeFieldValue = $this->hook->config['mailchimpSubscribeFieldValue'];
  139. }
  140. }
  141. /**
  142. * Set the mailchimp merge tags configuration or add an error.
  143. *
  144. * @return bool
  145. */
  146. private function setMapping()
  147. {
  148. $config = $this->hook->config['mailchimpFields'];
  149. $fields = array_filter(explode(',', $config));
  150. if (!$fields) {
  151. $this->hook->addError(self::MC_ERROR_PH, $this->modx->lexicon('mailchimpsubscribe.error.missing_field_config_scriptproperty'));
  152. return false;
  153. }
  154. foreach ($fields as $field) {
  155. list($fieldName, $mergeTag) = explode('=', $field);
  156. $this->mapping[$mergeTag] = $fieldName;
  157. }
  158. if (!array_key_exists('EMAIL', $this->mapping)) {
  159. $this->hook->addError(self::MC_ERROR_PH, $this->modx->lexicon('mailchimpsubscribe.error.missing_required_config_field', ['tag' => 'EMAIL']));
  160. return false;
  161. }
  162. return true;
  163. }
  164. /**
  165. * Set the values array which contains all merge tags and form values which will be pushed to mailchimp.
  166. */
  167. private function setValues()
  168. {
  169. foreach ($this->mapping as $mergeTag => $fieldname) {
  170. if ($mergeTag !== 'EMAIL') {
  171. $this->values[$mergeTag] = $this->formatValue($this->hook->getValue($fieldname));
  172. }
  173. }
  174. }
  175. /**
  176. * Init MailChimp REST Enabled API 3.0 Wrapper Class.
  177. *
  178. * https://github.com/vatps/mailchimp-rest-api
  179. */
  180. private function initMailChimpApi()
  181. {
  182. $this->mailchimp = new \VPS\MailChimp($this->modx->getOption('mailchimpsubscribe.mailchimp_api_key'));
  183. }
  184. /**
  185. * Set mailchimp list id based on scriptproperty if it is set, else use tv value.
  186. *
  187. * @param $scriptProperties
  188. *
  189. * @return null
  190. */
  191. private function setListId($scriptProperties)
  192. {
  193. if (isset($scriptProperties['mailchimpListId']) && !empty($scriptProperties['mailchimpListId'])) {
  194. $listId = $scriptProperties['mailchimpListId'];
  195. } else {
  196. $listId = $this->modx->resource->getTVValue($this->mcListTV);
  197. }
  198. return $listId;
  199. }
  200. /**
  201. * Set mailchimp subscriber status on subscription.
  202. *
  203. * @param $scriptProperties
  204. *
  205. * @return null
  206. */
  207. private function setSubscriberStatus($scriptProperties)
  208. {
  209. if (isset($scriptProperties['mailchimpSubscribeStatus']) &&
  210. !empty($scriptProperties['mailchimpSubscribeStatus'])) {
  211. $status = $scriptProperties['mailchimpSubscribeStatus'];
  212. } else {
  213. $status = 'pending';
  214. }
  215. return $status;
  216. }
  217. /**
  218. * Retrieve all MailChimp lists as TV select options.
  219. *
  220. * @return string
  221. */
  222. public function getMailChimpLists()
  223. {
  224. $this->initMailChimpApi();
  225. $params = array('count' => 100);
  226. $result = $this->mailchimp->get('/lists/?' . http_build_query($params));
  227. $options = [];
  228. if (isset($result['lists']) && !empty($result['lists'])) {
  229. foreach ($result['lists'] as $list) {
  230. $options[$list['name']] = $list['name'] . '==' . $list['id'];
  231. }
  232. }
  233. asort($options, SORT_NATURAL);
  234. array_unshift($options, '- Select a mailchimp list - ==0');
  235. return implode('||', $options);
  236. }
  237. /**
  238. *
  239. * @param $hook
  240. * @param $scriptProperties
  241. * @return string
  242. */
  243. public function subscribeMailChimp($hook, $scriptProperties)
  244. {
  245. $this->modx->lexicon->load('mailchimpsubscribe:default');
  246. /* Set hook. */
  247. $this->setHook($hook);
  248. /* Initialize mailchimp api. */
  249. $this->initMailChimpApi();
  250. /* Set subscribe fields based on scriptproperties. */
  251. $this->setSubscribeFields();
  252. /* Fetch data from hook */
  253. $values = $this->hook->getValues();
  254. if ($values[$this->subscribeField] !== $this->subscribeFieldValue) {
  255. return true;
  256. }
  257. /* Validate the properties */
  258. $valid = $this->setMapping();
  259. if (!$valid) {
  260. return false;
  261. }
  262. $listId = $this->setListId($scriptProperties);
  263. $email = $this->formatValue($this->hook->getValue($this->mapping['EMAIL']));
  264. $this->setValues();
  265. /* No list id found. */
  266. if (empty($listId)) {
  267. $this->hook->addError(
  268. self::MC_ERROR_PH,
  269. $this->modx->lexicon('mailchimpsubscribe.error.no_list_found', [], $this->modx->cultureKey)
  270. );
  271. return false;
  272. }
  273. /* Check if user is allowed to be processed */
  274. $subscribeUser = $this->checkMCSubscriberStatus($listId, $email);
  275. if ($subscribeUser === false) {
  276. $this->hook->addError(self::MC_ERROR_PH, $this->mcSubscribeMessage);
  277. return false;
  278. }
  279. /**
  280. * If an existing user is not subscribed, update existing user and set status to pending.
  281. * Which will send the user an emailconfirmation for the subscribtion.
  282. *
  283. * Subscribe statusses:
  284. * - subscribed: To add an address right away.
  285. * - pending: To send a confirmation email.
  286. * - unsubscribed/cleaned to archive unused addresses.
  287. *
  288. * http://developer.mailchimp.com/documentation/mailchimp/guides/manage-subscribers-with-the-mailchimp-api/
  289. */
  290. $subscribeStatus = $this->setSubscriberStatus($scriptProperties);
  291. /* Check if tags are set and status is not subscribed */
  292. if ($subscribeStatus !== 'subscribed' &&
  293. !empty($scriptProperties['mailchimpTags']) &&
  294. isset($scriptProperties['mailchimpTags'])
  295. ) {
  296. $this->hook->addError(
  297. self::MC_ERROR_PH,
  298. $this->modx->lexicon(
  299. 'mailchimpsubscribe.error.incorrect_status',
  300. [],
  301. $this->modx->cultureKey
  302. )
  303. );
  304. return false;
  305. }
  306. if ($this->mcSubscribeMode === 'update') {
  307. $result = $this->mailchimp->PATCH(
  308. '/lists/' . $listId . '/members/' . md5($email),
  309. [
  310. 'email_address' => $email,
  311. 'merge_fields' => $this->values,
  312. 'status' => $subscribeStatus
  313. ]
  314. );
  315. } else {
  316. $result = $this->mailchimp->post(
  317. '/lists/' . $listId . '/members',
  318. [
  319. 'email_address' => $email,
  320. 'merge_fields' => $this->values,
  321. 'status' => $subscribeStatus
  322. ]
  323. );
  324. }
  325. if ($result['status'] !== $subscribeStatus) {
  326. $response = $result['title'] . ': ' . $result['detail'];
  327. $this->hook->addError(self::MC_ERROR_PH, $response);
  328. return false;
  329. }
  330. /* Add tags to subscription (Has no error handeling to prevent the process being killed) */
  331. if ($subscribeStatus === 'subscribed' &&
  332. !empty($scriptProperties['mailchimpTags']) &&
  333. isset($scriptProperties['mailchimpTags'])
  334. ) {
  335. $this->addTags($listId, $email, $scriptProperties['mailchimpTags']);
  336. }
  337. return true;
  338. }
  339. /**
  340. * Check mailchimp subscriber status by emailaddress.
  341. *
  342. * @param string $listId List id as specified in resource TV.
  343. * @param string $email Form value emailaddress.
  344. *
  345. * @return bool
  346. */
  347. public function checkMCSubscriberStatus($listId, $email)
  348. {
  349. $this->modx->lexicon->load('mailchimpsubscribe:default');
  350. $result = $this->mailchimp->get('/lists/' . $listId . '/members/' . md5($email));
  351. /* If status 404, the e-mailaddress is unknown and e-mailaddress can be subscribed. */
  352. if ($result['status'] === 404) {
  353. return true;
  354. }
  355. switch ($result['status']) {
  356. case 'subscribed':
  357. $this->mcSubscribeMessage = $this->modx->lexicon(
  358. 'mailchimpsubscribe.error.subscribed',
  359. [],
  360. $this->modx->cultureKey
  361. );
  362. break;
  363. case 'unsubscribed':
  364. /**
  365. * Update user and set status to pending.
  366. */
  367. $this->mcSubscribeMode = 'update';
  368. return true;
  369. break;
  370. case 'pending':
  371. $this->mcSubscribeMessage = $this->modx->lexicon(
  372. 'mailchimpsubscribe.error.pending',
  373. [],
  374. $this->modx->cultureKey
  375. );
  376. break;
  377. case 'cleaned':
  378. $this->mcSubscribeMessage = $this->modx->lexicon(
  379. 'mailchimpsubscribe.error.cleaned',
  380. [],
  381. $this->modx->cultureKey
  382. );
  383. break;
  384. }
  385. return false;
  386. }
  387. /**
  388. * Processes comma separated value to tags for subscribes
  389. *
  390. * @param $listId
  391. * @param $email
  392. * @param $tags
  393. * @return string
  394. */
  395. public function addTags($listId, $email, $tags)
  396. {
  397. $hashed = md5($email);
  398. $data = [];
  399. $tags = explode(',', $tags);
  400. /* Needs array and status active to create tag if not existing */
  401. foreach ($tags as $value) {
  402. $data[] = [
  403. 'name' => $value,
  404. 'status' => 'active'
  405. ];
  406. }
  407. /* Post data to mailchimp */
  408. $result = $this->mailchimp->post(
  409. '/lists/' . $listId . '/members/' . $hashed . '/tags',
  410. [
  411. 'tags' => $data
  412. ]
  413. );
  414. return $result;
  415. }
  416. /**
  417. * Format form values.
  418. *
  419. * @param $value
  420. * @return string
  421. */
  422. private function formatValue($value) {
  423. return trim($value);
  424. }
  425. }