Нет описания

MP2Migrator.php 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. <?php
  2. namespace MailPoet\Config;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Entities\FormEntity;
  5. use MailPoet\Form\FormsRepository;
  6. use MailPoet\Models\CustomField;
  7. use MailPoet\Models\MappingToExternalEntities;
  8. use MailPoet\Models\Segment;
  9. use MailPoet\Models\Subscriber;
  10. use MailPoet\Models\SubscriberCustomField;
  11. use MailPoet\Models\SubscriberSegment;
  12. use MailPoet\Settings\SettingsController;
  13. use MailPoet\Util\Notices\AfterMigrationNotice;
  14. use MailPoet\Util\ProgressBar;
  15. use MailPoet\WP\Functions as WPFunctions;
  16. use MailPoetVendor\Idiorm\ORM;
  17. class MP2Migrator {
  18. const IMPORT_TIMEOUT_IN_SECONDS = 7200; // Timeout = 2 hours
  19. const CHUNK_SIZE = 10; // To import the data by batch
  20. const MIGRATION_COMPLETE_SETTING_KEY = 'mailpoet_migration_complete';
  21. const MIGRATION_STARTED_SETTING_KEY = 'mailpoet_migration_started';
  22. /** @var SettingsController */
  23. private $settings;
  24. /** @var Activator */
  25. private $activator;
  26. /** @var FormsRepository */
  27. private $formsRepository;
  28. private $logFile;
  29. public $logFileUrl;
  30. public $progressbar;
  31. private $segmentsMapping = []; // Mapping between old and new segment IDs
  32. private $wpUsersSegment;
  33. private $doubleOptinEnabled = true;
  34. private $mp2CampaignTable;
  35. private $mp2CustomFieldTable;
  36. private $mp2EmailTable;
  37. private $mp2FormTable;
  38. private $mp2ListTable;
  39. private $mp2UserTable;
  40. private $mp2UserListTable;
  41. public function __construct(
  42. SettingsController $settings,
  43. FormsRepository $formsRepository,
  44. Activator $activator
  45. ) {
  46. $this->defineMP2Tables();
  47. $logFilename = 'mp2migration.log';
  48. $this->logFile = Env::$tempPath . '/' . $logFilename;
  49. $this->logFileUrl = Env::$tempUrl . '/' . $logFilename;
  50. $this->progressbar = new ProgressBar('mp2migration');
  51. $this->settings = $settings;
  52. $this->activator = $activator;
  53. $this->formsRepository = $formsRepository;
  54. }
  55. private function defineMP2Tables() {
  56. global $wpdb;
  57. $this->mp2CampaignTable = defined('MP2_CAMPAIGN_TABLE')
  58. ? MP2_CAMPAIGN_TABLE
  59. : $wpdb->prefix . 'wysija_campaign';
  60. $this->mp2CustomFieldTable = defined('MP2_CUSTOM_FIELD_TABLE')
  61. ? MP2_CUSTOM_FIELD_TABLE
  62. : $wpdb->prefix . 'wysija_custom_field';
  63. $this->mp2EmailTable = defined('MP2_EMAIL_TABLE')
  64. ? MP2_EMAIL_TABLE
  65. : $wpdb->prefix . 'wysija_email';
  66. $this->mp2FormTable = defined('MP2_FORM_TABLE')
  67. ? MP2_FORM_TABLE
  68. : $wpdb->prefix . 'wysija_form';
  69. $this->mp2ListTable = defined('MP2_LIST_TABLE')
  70. ? MP2_LIST_TABLE
  71. : $wpdb->prefix . 'wysija_list';
  72. $this->mp2UserTable = defined('MP2_USER_TABLE')
  73. ? MP2_USER_TABLE
  74. : $wpdb->prefix . 'wysija_user';
  75. $this->mp2UserListTable = defined('MP2_USER_LIST_TABLE')
  76. ? MP2_USER_LIST_TABLE
  77. : $wpdb->prefix . 'wysija_user_list';
  78. }
  79. /**
  80. * Test if the migration is already started but is not completed
  81. *
  82. * @return bool
  83. */
  84. public function isMigrationStartedAndNotCompleted() {
  85. return $this->settings->get(self::MIGRATION_STARTED_SETTING_KEY, false)
  86. && !$this->settings->get(self::MIGRATION_COMPLETE_SETTING_KEY, false);
  87. }
  88. /**
  89. * Test if the migration is needed
  90. *
  91. * @return bool
  92. */
  93. public function isMigrationNeeded() {
  94. if ($this->settings->get(self::MIGRATION_COMPLETE_SETTING_KEY)) {
  95. return false;
  96. } else {
  97. return $this->tableExists($this->mp2CampaignTable); // Check if the MailPoet 2 tables exist
  98. }
  99. }
  100. /**
  101. * Store the "Skip import" choice
  102. *
  103. */
  104. public function skipImport() {
  105. $this->settings->set(self::MIGRATION_COMPLETE_SETTING_KEY, true);
  106. }
  107. /**
  108. * Test if a table exists
  109. *
  110. * @param string $table Table name
  111. * @return bool
  112. */
  113. private function tableExists($table) {
  114. global $wpdb;
  115. try {
  116. $sql = "SHOW TABLES LIKE '{$table}'";
  117. $result = $wpdb->query($sql);
  118. return !empty($result);
  119. } catch (\Exception $e) {
  120. // Do nothing
  121. }
  122. return false;
  123. }
  124. /**
  125. * Initialize the migration page
  126. *
  127. */
  128. public function init() {
  129. if (!$this->settings->get(self::MIGRATION_STARTED_SETTING_KEY, false)) {
  130. $this->emptyLog();
  131. $this->progressbar->setTotalCount(0);
  132. }
  133. $this->enqueueScripts();
  134. }
  135. /**
  136. * Register the JavaScript for the admin area.
  137. *
  138. */
  139. private function enqueueScripts() {
  140. WPFunctions::get()->wpEnqueueScript('jquery-ui-progressbar');
  141. }
  142. /**
  143. * Write a message in the log file
  144. *
  145. * @param string $message
  146. */
  147. private function log($message) {
  148. file_put_contents($this->logFile, "$message\n", FILE_APPEND);
  149. }
  150. /**
  151. * Import the data from MailPoet 2
  152. *
  153. * @return string Result
  154. */
  155. public function import() {
  156. if (strpos((string)@ini_get('disable_functions'), 'set_time_limit') === false) {
  157. @set_time_limit(3600);
  158. }
  159. ob_start();
  160. $datetime = new \MailPoet\WP\DateTime();
  161. $this->log(sprintf('=== ' . mb_strtoupper(__('Start import', 'mailpoet'), 'UTF-8') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
  162. $this->settings->set('import_stopped', false); // Reset the stop import action
  163. if (!$this->settings->get(self::MIGRATION_STARTED_SETTING_KEY, false)) {
  164. $this->eraseMP3Data();
  165. $this->settings->set(self::MIGRATION_STARTED_SETTING_KEY, true);
  166. $this->displayDataToMigrate();
  167. }
  168. $this->loadDoubleOptinSettings();
  169. $this->importSegments();
  170. $this->importCustomFields();
  171. $this->importSubscribers();
  172. $this->importForms();
  173. $this->importSettings();
  174. if (!$this->importStopped()) {
  175. $this->settings->set(self::MIGRATION_COMPLETE_SETTING_KEY, true);
  176. $this->log(mb_strtoupper(__('Import complete', 'mailpoet'), 'UTF-8'));
  177. $afterMigrationNotice = new AfterMigrationNotice();
  178. $afterMigrationNotice->enable();
  179. }
  180. $this->log(sprintf('=== ' . mb_strtoupper(__('End import', 'mailpoet'), 'UTF-8') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
  181. $result = ob_get_contents();
  182. ob_clean();
  183. return (string)$result;
  184. }
  185. /**
  186. * Empty the log file
  187. *
  188. */
  189. private function emptyLog() {
  190. file_put_contents($this->logFile, '');
  191. }
  192. /**
  193. * Erase all the MailPoet 3 data
  194. *
  195. */
  196. private function eraseMP3Data() {
  197. $this->activator->deactivate();
  198. $this->activator->activate();
  199. $this->deleteSegments();
  200. $this->resetMigrationCounters();
  201. $this->log(__("MailPoet data erased", 'mailpoet'));
  202. }
  203. /**
  204. * Reset the migration counters
  205. *
  206. */
  207. private function resetMigrationCounters() {
  208. $this->settings->set('last_imported_user_id', 0);
  209. $this->settings->set('last_imported_list_id', 0);
  210. $this->settings->set('last_imported_form_id', 0);
  211. }
  212. private function loadDoubleOptinSettings() {
  213. $encodedOption = WPFunctions::get()->getOption('wysija');
  214. $values = unserialize(base64_decode($encodedOption));
  215. if (isset($values['confirm_dbleoptin']) && $values['confirm_dbleoptin'] === '0') {
  216. $this->doubleOptinEnabled = false;
  217. }
  218. }
  219. /**
  220. * Delete the existing segments except the wp_users and woocommerce_users segments
  221. *
  222. */
  223. private function deleteSegments() {
  224. global $wpdb;
  225. $table = MP_SEGMENTS_TABLE;
  226. $wpdb->query("DELETE FROM {$table} WHERE type != '" . Segment::TYPE_WP_USERS . "' AND type != '" . Segment::TYPE_WC_USERS . "'");
  227. }
  228. /**
  229. * Stop the import
  230. *
  231. */
  232. public function stopImport() {
  233. $this->settings->set('import_stopped', true);
  234. $this->log(mb_strtoupper(__('Import stopped by user', 'mailpoet'), 'UTF-8'));
  235. }
  236. /**
  237. * Test if the import must stop
  238. *
  239. * @return bool Import must stop or not
  240. */
  241. private function importStopped() {
  242. return $this->settings->get('import_stopped', false);
  243. }
  244. /**
  245. * Display the number of data to migrate
  246. *
  247. */
  248. private function displayDataToMigrate() {
  249. $data = $this->getDataToMigrateAndResetProgressBar();
  250. $this->log($data);
  251. }
  252. /**
  253. * Get the data to migrate
  254. *
  255. * @return string Data to migrate
  256. */
  257. private function getDataToMigrateAndResetProgressBar() {
  258. $result = '';
  259. $totalCount = 0;
  260. $this->progressbar->setTotalCount(0);
  261. $result .= WPFunctions::get()->__('MailPoet 2 data found:', 'mailpoet') . "\n";
  262. // User Lists
  263. $usersListsCount = ORM::for_table($this->mp2ListTable)->count();
  264. $totalCount += $usersListsCount;
  265. $result .= sprintf(_n('%d subscribers list', '%d subscribers lists', $usersListsCount, 'mailpoet'), $usersListsCount) . "\n";
  266. // Users
  267. $usersCount = ORM::for_table($this->mp2UserTable)->count();
  268. $totalCount += $usersCount;
  269. $result .= sprintf(_n('%d subscriber', '%d subscribers', $usersCount, 'mailpoet'), $usersCount) . "\n";
  270. // Forms
  271. $formsCount = ORM::for_table($this->mp2FormTable)->count();
  272. $totalCount += $formsCount;
  273. $result .= sprintf(_n('%d form', '%d forms', $formsCount, 'mailpoet'), $formsCount) . "\n";
  274. $this->progressbar->setTotalCount($totalCount);
  275. return $result;
  276. }
  277. /**
  278. * Import the subscribers segments
  279. *
  280. */
  281. private function importSegments() {
  282. $importedSegmentsCount = 0;
  283. if ($this->importStopped()) {
  284. $this->segmentsMapping = $this->getImportedMapping('segments');
  285. return;
  286. }
  287. $this->log(__("Importing segments...", 'mailpoet'));
  288. do {
  289. if ($this->importStopped()) {
  290. break;
  291. }
  292. $lists = $this->getLists(self::CHUNK_SIZE);
  293. $listsCount = count($lists);
  294. if (is_array($lists)) {
  295. foreach ($lists as $list) {
  296. $segment = $this->importSegment($list);
  297. if (!empty($segment)) {
  298. $importedSegmentsCount++;
  299. }
  300. }
  301. }
  302. $this->progressbar->incrementCurrentCount($listsCount);
  303. } while (($lists != null) && ($listsCount > 0));
  304. $this->segmentsMapping = $this->getImportedMapping('segments');
  305. $this->log(sprintf(_n("%d segment imported", "%d segments imported", $importedSegmentsCount, 'mailpoet'), $importedSegmentsCount));
  306. }
  307. /**
  308. * Get the Mailpoet 2 users lists
  309. *
  310. * @global object $wpdb
  311. * @param int $limit Number of users max
  312. * @return array Users Lists
  313. */
  314. private function getLists($limit) {
  315. global $wpdb;
  316. $lastId = intval($this->settings->get('last_imported_list_id', 0));
  317. $table = $this->mp2ListTable;
  318. $sql = "
  319. SELECT l.list_id, l.name, l.description, l.is_enabled, l.created_at
  320. FROM `$table` l
  321. WHERE l.list_id > '$lastId'
  322. ORDER BY l.list_id
  323. LIMIT $limit
  324. ";
  325. $lists = $wpdb->get_results($sql, ARRAY_A);
  326. return $lists;
  327. }
  328. /**
  329. * Import a segment
  330. *
  331. * @param array $listData List data
  332. * @return Segment
  333. */
  334. private function importSegment($listData) {
  335. $datetime = new \MailPoet\WP\DateTime();
  336. if ($listData['is_enabled']) {
  337. $segment = Segment::createOrUpdate([
  338. 'name' => $listData['name'],
  339. 'type' => 'default',
  340. 'description' => !empty($listData['description']) ? $listData['description'] : '',
  341. 'created_at' => $datetime->formatTime($listData['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
  342. ]);
  343. } else {
  344. $segment = Segment::getWPSegment();
  345. }
  346. if (!empty($segment)) {
  347. // Map the segment with its old ID
  348. $mapping = new MappingToExternalEntities();
  349. $mapping->create([
  350. 'old_id' => $listData['list_id'],
  351. 'type' => 'segments',
  352. 'new_id' => $segment->id,
  353. 'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
  354. ]);
  355. }
  356. $this->settings->set('last_imported_list_id', $listData['list_id']);
  357. return $segment;
  358. }
  359. /**
  360. * Import the custom fields
  361. *
  362. */
  363. private function importCustomFields() {
  364. $importedCustomFieldsCount = 0;
  365. if ($this->importStopped()) {
  366. return;
  367. }
  368. $this->log(__("Importing custom fields...", 'mailpoet'));
  369. $customFields = $this->getCustomFields();
  370. foreach ($customFields as $customField) {
  371. $result = $this->importCustomField($customField);
  372. if (!empty($result)) {
  373. $importedCustomFieldsCount++;
  374. }
  375. }
  376. $this->log(sprintf(_n("%d custom field imported", "%d custom fields imported", $importedCustomFieldsCount, 'mailpoet'), $importedCustomFieldsCount));
  377. }
  378. /**
  379. * Get the Mailpoet 2 custom fields
  380. *
  381. * @global object $wpdb
  382. * @return array Custom fields
  383. */
  384. private function getCustomFields() {
  385. global $wpdb;
  386. $customFields = [];
  387. $table = $this->mp2CustomFieldTable;
  388. $sql = "
  389. SELECT cf.id, cf.name, cf.type, cf.required, cf.settings
  390. FROM `$table` cf
  391. ";
  392. $customFields = $wpdb->get_results($sql, ARRAY_A);
  393. return $customFields;
  394. }
  395. /**
  396. * Import a custom field
  397. *
  398. * @param array $customField MP2 custom field
  399. * @return CustomField
  400. */
  401. private function importCustomField($customField) {
  402. $data = [
  403. 'id' => $customField['id'],
  404. 'name' => $customField['name'],
  405. 'type' => $this->mapCustomFieldType($customField['type']),
  406. 'params' => $this->mapCustomFieldParams($customField['name'], unserialize($customField['settings'])),
  407. ];
  408. $customField = new CustomField();
  409. $customField->createOrUpdate($data);
  410. return $customField;
  411. }
  412. /**
  413. * Map the MailPoet 2 custom field type with the MailPoet custom field type
  414. *
  415. * @param string $mp2Type MP2 custom field type
  416. * @return string MP3 custom field type
  417. */
  418. private function mapCustomFieldType($mp2Type) {
  419. $type = '';
  420. switch ($mp2Type) {
  421. case 'input':
  422. $type = 'text';
  423. break;
  424. case 'list':
  425. $type = 'segment';
  426. break;
  427. default:
  428. $type = $mp2Type;
  429. }
  430. return $type;
  431. }
  432. /**
  433. * Map the MailPoet 2 custom field settings with the MailPoet custom field params
  434. *
  435. * @param string $name Parameter name
  436. * @param array $params MP2 parameters
  437. * @return array serialized MP3 custom field params
  438. */
  439. private function mapCustomFieldParams($name, $params) {
  440. if (!isset($params['label'])) {
  441. $params['label'] = $name;
  442. }
  443. if (isset($params['required'])) {
  444. $params['required'] = (bool)$params['required'];
  445. }
  446. if (isset($params['validate'])) {
  447. $params['validate'] = $this->mapCustomFieldValidateValue($params['validate']);
  448. }
  449. if (isset($params['date_order'])) { // Convert the date_order field
  450. switch ($params['date_type']) {
  451. case 'year_month':
  452. if (preg_match('/y$/i', $params['date_order'])) {
  453. $params['date_format'] = 'MM/YYYY';
  454. } else {
  455. $params['date_format'] = 'YYYY/MM';
  456. }
  457. break;
  458. case 'month';
  459. $params['date_format'] = 'MM';
  460. break;
  461. case 'year';
  462. $params['date_format'] = 'YYYY';
  463. break;
  464. default:
  465. $params['date_format'] = mb_strtoupper($params['date_order'], 'UTF-8');
  466. }
  467. unset($params['date_order']);
  468. }
  469. return $params;
  470. }
  471. /**
  472. * Map the validate value
  473. *
  474. * @param string $mp2Value MP2 value
  475. * @return string MP3 value
  476. */
  477. private function mapCustomFieldValidateValue($mp2Value) {
  478. $value = '';
  479. switch ($mp2Value) {
  480. case 'onlyLetterSp':
  481. case 'onlyLetterNumber':
  482. $value = 'alphanum';
  483. break;
  484. case 'onlyNumberSp':
  485. $value = 'number';
  486. break;
  487. case 'phone':
  488. $value = 'phone';
  489. break;
  490. }
  491. return $value;
  492. }
  493. /**
  494. * Import the subscribers
  495. *
  496. */
  497. private function importSubscribers() {
  498. $importedSubscribersCount = 0;
  499. if ($this->importStopped()) {
  500. return;
  501. }
  502. $this->log(__("Importing subscribers...", 'mailpoet'));
  503. $this->wpUsersSegment = Segment::getWPSegment();
  504. do {
  505. if ($this->importStopped()) {
  506. break;
  507. }
  508. $users = $this->getUsers(self::CHUNK_SIZE);
  509. $usersCount = count($users);
  510. if (is_array($users)) {
  511. foreach ($users as $user) {
  512. $subscriber = $this->importSubscriber($user);
  513. if (!empty($subscriber)) {
  514. $importedSubscribersCount++;
  515. $this->importSubscriberSegments($subscriber, $user['user_id']);
  516. $this->importSubscriberCustomFields($subscriber, $user);
  517. }
  518. }
  519. }
  520. $this->progressbar->incrementCurrentCount($usersCount);
  521. } while (($users != null) && ($usersCount > 0));
  522. $this->log(sprintf(_n("%d subscriber imported", "%d subscribers imported", $importedSubscribersCount, 'mailpoet'), $importedSubscribersCount));
  523. }
  524. /**
  525. * Get the Mailpoet 2 users
  526. *
  527. * @global object $wpdb
  528. * @param int $limit Number of users max
  529. * @return array Users
  530. */
  531. private function getUsers($limit) {
  532. global $wpdb;
  533. $lastId = intval($this->settings->get('last_imported_user_id', 0));
  534. $table = $this->mp2UserTable;
  535. $sql = "
  536. SELECT u.*
  537. FROM `$table` u
  538. WHERE u.user_id > '$lastId'
  539. ORDER BY u.user_id
  540. LIMIT $limit
  541. ";
  542. $users = $wpdb->get_results($sql, ARRAY_A);
  543. return $users;
  544. }
  545. /**
  546. * Import a subscriber
  547. *
  548. * @param array $userData User data
  549. * @return Subscriber
  550. */
  551. private function importSubscriber($userData) {
  552. $datetime = new \MailPoet\WP\DateTime();
  553. $subscriber = Subscriber::createOrUpdate([
  554. 'wp_user_id' => !empty($userData['wpuser_id']) ? $userData['wpuser_id'] : null,
  555. 'email' => $userData['email'],
  556. 'first_name' => $userData['firstname'],
  557. 'last_name' => $userData['lastname'],
  558. 'status' => $this->mapUserStatus($userData['status']),
  559. 'created_at' => $datetime->formatTime($userData['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
  560. 'subscribed_ip' => !empty($userData['ip']) ? $userData['ip'] : null,
  561. 'confirmed_ip' => !empty($userData['confirmed_ip']) ? $userData['confirmed_ip'] : null,
  562. 'confirmed_at' => !empty($userData['confirmed_at']) ? $datetime->formatTime($userData['confirmed_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : null,
  563. ]);
  564. $this->settings->set('last_imported_user_id', $userData['user_id']);
  565. if (!empty($subscriber)) {
  566. // Map the subscriber with its old ID
  567. $mapping = new MappingToExternalEntities();
  568. $mapping->create([
  569. 'old_id' => $userData['user_id'],
  570. 'type' => 'subscribers',
  571. 'new_id' => $subscriber->id,
  572. 'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
  573. ]);
  574. }
  575. return $subscriber;
  576. }
  577. /**
  578. * Map the MailPoet 2 user status with MailPoet 3
  579. *
  580. * @param int $mp2UserStatus MP2 user status
  581. * @return string MP3 user status
  582. */
  583. private function mapUserStatus($mp2UserStatus) {
  584. switch ($mp2UserStatus) {
  585. case 1:
  586. $status = 'subscribed';
  587. break;
  588. case -1:
  589. $status = 'unsubscribed';
  590. break;
  591. case 0:
  592. default:
  593. //if MP2 double-optin is disabled, we change "unconfirmed" status in MP2 to "confirmed" status in MP3.
  594. if (!$this->doubleOptinEnabled) {
  595. $status = 'subscribed';
  596. } else {
  597. $status = 'unconfirmed';
  598. }
  599. }
  600. return $status;
  601. }
  602. /**
  603. * Import the segments for a subscriber
  604. *
  605. * @param Subscriber $subscriber MP3 subscriber
  606. * @param int $userId MP2 user ID
  607. */
  608. private function importSubscriberSegments($subscriber, $userId) {
  609. $userLists = $this->getUserLists($userId);
  610. foreach ($userLists as $userList) {
  611. $this->importSubscriberSegment($subscriber->id, $userList);
  612. }
  613. }
  614. /**
  615. * Get the lists for a user
  616. *
  617. * @global object $wpdb
  618. * @param int $userId User ID
  619. * @return array Users Lists
  620. */
  621. private function getUserLists($userId) {
  622. global $wpdb;
  623. $table = $this->mp2UserListTable;
  624. $sql = "
  625. SELECT ul.list_id, ul.sub_date, ul.unsub_date
  626. FROM `$table` ul
  627. WHERE ul.user_id = '$userId'
  628. ";
  629. $userLists = $wpdb->get_results($sql, ARRAY_A);
  630. return $userLists;
  631. }
  632. /**
  633. * Import a subscriber segment
  634. *
  635. * @param int $subscriberId
  636. * @param array $userList
  637. * @return SubscriberSegment|null
  638. */
  639. private function importSubscriberSegment($subscriberId, $userList) {
  640. $subscriberSegment = null;
  641. $datetime = new \MailPoet\WP\DateTime();
  642. if (isset($this->segmentsMapping[$userList['list_id']])) {
  643. $segmentId = $this->segmentsMapping[$userList['list_id']];
  644. $status = (($segmentId == $this->wpUsersSegment->id) || empty($userList['unsub_date'])) ? 'subscribed' : 'unsubscribed'; // the users belonging to the wp_users segment are always subscribed
  645. $data = [
  646. 'subscriber_id' => $subscriberId,
  647. 'segment_id' => $segmentId,
  648. 'status' => $status,
  649. 'created_at' => $datetime->formatTime($userList['sub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
  650. ];
  651. $data['updated_at'] = !empty($userList['unsub_date']) ? $datetime->formatTime($userList['unsub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : $data['created_at'];
  652. $subscriberSegment = new SubscriberSegment();
  653. $subscriberSegment->createOrUpdate($data);
  654. }
  655. return $subscriberSegment;
  656. }
  657. /**
  658. * Import the custom fields values for a subscriber
  659. *
  660. * @param Subscriber $subscriber MP3 subscriber
  661. * @param array $user MP2 user
  662. */
  663. private function importSubscriberCustomFields($subscriber, $user) {
  664. $importedCustomFields = $this->getImportedCustomFields();
  665. foreach ($importedCustomFields as $customField) {
  666. $customFieldColumn = 'cf_' . $customField['id'];
  667. $this->importSubscriberCustomField($subscriber->id, $customField, $user[$customFieldColumn]);
  668. }
  669. }
  670. /**
  671. * Get the imported custom fields
  672. *
  673. * @global object $wpdb
  674. * @return array Imported custom fields
  675. *
  676. */
  677. private function getImportedCustomFields() {
  678. global $wpdb;
  679. $table = MP_CUSTOM_FIELDS_TABLE;
  680. $sql = "
  681. SELECT cf.id, cf.name, cf.type
  682. FROM `$table` cf
  683. ";
  684. $customFields = $wpdb->get_results($sql, ARRAY_A);
  685. return $customFields;
  686. }
  687. /**
  688. * Import a subscriber custom field
  689. *
  690. * @param int $subscriberId Subscriber ID
  691. * @param array $customField Custom field
  692. * @param string $customFieldValue Custom field value
  693. * @return SubscriberCustomField
  694. */
  695. private function importSubscriberCustomField($subscriberId, $customField, $customFieldValue) {
  696. if ($customField['type'] == 'date') {
  697. $datetime = new \MailPoet\WP\DateTime();
  698. $value = $datetime->formatTime($customFieldValue, \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT); // Convert the date field
  699. } else {
  700. $value = $customFieldValue;
  701. }
  702. $data = [
  703. 'subscriber_id' => $subscriberId,
  704. 'custom_field_id' => $customField['id'],
  705. 'value' => isset($value) ? $value : '',
  706. ];
  707. $subscriberCustomField = new SubscriberCustomField();
  708. $subscriberCustomField->createOrUpdate($data);
  709. return $subscriberCustomField;
  710. }
  711. /**
  712. * Get the mapping between the MP2 and the imported MP3 IDs
  713. *
  714. * @param string $model Model (segment,...)
  715. * @return array Mapping
  716. */
  717. public function getImportedMapping($model) {
  718. $mappings = [];
  719. $mappingRelations = MappingToExternalEntities::where('type', $model)->findArray();
  720. foreach ($mappingRelations as $relation) {
  721. $mappings[$relation['old_id']] = $relation['new_id'];
  722. }
  723. return $mappings;
  724. }
  725. /**
  726. * Import the forms
  727. *
  728. */
  729. private function importForms() {
  730. $importedFormsCount = 0;
  731. if ($this->importStopped()) {
  732. return;
  733. }
  734. $this->log(__("Importing forms...", 'mailpoet'));
  735. do {
  736. if ($this->importStopped()) {
  737. break;
  738. }
  739. $forms = $this->getForms(self::CHUNK_SIZE);
  740. $formsCount = count($forms);
  741. if (is_array($forms)) {
  742. foreach ($forms as $form) {
  743. $this->importForm($form);
  744. $importedFormsCount++;
  745. }
  746. }
  747. $this->formsRepository->flush();
  748. $this->progressbar->incrementCurrentCount($formsCount);
  749. } while (($forms != null) && ($formsCount > 0));
  750. $this->log(sprintf(_n("%d form imported", "%d forms imported", $importedFormsCount, 'mailpoet'), $importedFormsCount));
  751. }
  752. /**
  753. * Get the Mailpoet 2 forms
  754. *
  755. * @global object $wpdb
  756. * @param int $limit Number of forms max
  757. * @return array Forms
  758. */
  759. private function getForms($limit) {
  760. global $wpdb;
  761. $lastId = intval($this->settings->get('last_imported_form_id', 0));
  762. $table = $this->mp2FormTable;
  763. $sql = "
  764. SELECT f.*
  765. FROM `$table` f
  766. WHERE f.form_id > '$lastId'
  767. ORDER BY f.form_id
  768. LIMIT $limit
  769. ";
  770. $forms = $wpdb->get_results($sql, ARRAY_A);
  771. return $forms;
  772. }
  773. /**
  774. * Import a form
  775. *
  776. * @param array $formData Form data
  777. */
  778. private function importForm($formData) {
  779. $serializedData = base64_decode($formData['data']);
  780. $data = unserialize($serializedData);
  781. $settings = $data['settings'];
  782. $body = $data['body'];
  783. $segments = $this->getMappedSegmentIds($settings['lists']);
  784. $mp3FormSettings = [
  785. 'on_success' => $settings['on_success'],
  786. 'success_message' => $settings['success_message'],
  787. 'segments_selected_by' => $settings['lists_selected_by'],
  788. 'segments' => $segments,
  789. ];
  790. $mp3FormBody = [];
  791. foreach ($body as $field) {
  792. $type = $this->mapCustomFieldType($field['type']);
  793. if ($type == 'segment') {
  794. $fieldId = 'segments';
  795. } else {
  796. switch ($field['field']) {
  797. case 'firstname':
  798. $fieldId = 'first_name';
  799. break;
  800. case 'lastname':
  801. $fieldId = 'last_name';
  802. break;
  803. default:
  804. $fieldId = $field['field'];
  805. }
  806. }
  807. $fieldId = preg_replace('/^cf_(\d+)$/', '$1', $fieldId);
  808. $params = $this->mapCustomFieldParams($field['name'], $field['params']);
  809. if (isset($params['text'])) {
  810. $params['text'] = $this->replaceMP2Shortcodes(html_entity_decode($params['text']));
  811. }
  812. if (isset($params['values'])) {
  813. $params['values'] = $this->replaceListIds($params['values']);
  814. }
  815. $mp3FormBody[] = [
  816. 'type' => $type,
  817. 'name' => $field['name'],
  818. 'id' => $fieldId,
  819. 'unique' => !in_array($field['type'], ['html', 'divider', 'email', 'submit']) ? "1" : "0",
  820. 'static' => in_array($fieldId, ['email', 'submit']) ? "1" : "0",
  821. 'params' => $params,
  822. 'position' => isset($field['position']) ? $field['position'] : '',
  823. ];
  824. }
  825. $form = new FormEntity($formData['name']);
  826. $form->setBody($mp3FormBody);
  827. $form->setSettings($mp3FormSettings);
  828. $this->formsRepository->persist($form);
  829. $this->settings->set('last_imported_form_id', $formData['form_id']);
  830. }
  831. /**
  832. * Get the MP3 segments IDs of the MP2 lists IDs
  833. *
  834. * @param array $mp2ListIds
  835. */
  836. private function getMappedSegmentIds($mp2ListIds) {
  837. $mp3SegmentIds = [];
  838. foreach ($mp2ListIds as $listId) {
  839. if (isset($this->segmentsMapping[$listId])) {
  840. $mp3SegmentIds[] = $this->segmentsMapping[$listId];
  841. }
  842. }
  843. return $mp3SegmentIds;
  844. }
  845. /**
  846. * Replace the MP2 shortcodes used in the textarea fields
  847. *
  848. * @param string $text Text
  849. * @return string|null Text
  850. */
  851. private function replaceMP2Shortcodes($text) {
  852. $text = str_replace('[total_subscribers]', '[mailpoet_subscribers_count]', $text);
  853. $text = preg_replace_callback(
  854. '/\[wysija_subscribers_count list_id="(.*)" \]/',
  855. function ($matches) {
  856. return $this->replaceMP2ShortcodesCallback($matches);
  857. },
  858. $text
  859. );
  860. return $text;
  861. }
  862. /**
  863. * Callback function for MP2 shortcodes replacement
  864. *
  865. * @param array $matches PREG matches
  866. * @return string Replacement
  867. */
  868. private function replaceMP2ShortcodesCallback($matches) {
  869. if (!empty($matches)) {
  870. $mp2Lists = explode(',', $matches[1]);
  871. $segments = $this->getMappedSegmentIds($mp2Lists);
  872. $segmentsIds = implode(',', $segments);
  873. return '[mailpoet_subscribers_count segments=' . $segmentsIds . ']';
  874. }
  875. return '';
  876. }
  877. /**
  878. * Replace the MP2 list IDs by MP3 segment IDs
  879. *
  880. * @param array $values Field values
  881. * @return array Field values
  882. */
  883. private function replaceListIds($values) {
  884. $mp3Values = [];
  885. foreach ($values as $value) {
  886. $mp3Value = [];
  887. foreach ($value as $item => $itemValue) {
  888. if (($item == 'list_id') && isset($this->segmentsMapping[$itemValue])) {
  889. $segmentId = $this->segmentsMapping[$itemValue];
  890. $mp3Value['id'] = $segmentId;
  891. $segment = Segment::findOne($segmentId);
  892. if ($segment instanceof Segment) {
  893. $mp3Value['name'] = $segment->get('name');
  894. }
  895. } else {
  896. $mp3Value[$item] = $itemValue;
  897. }
  898. }
  899. if (!empty($mp3Value)) {
  900. $mp3Values[] = $mp3Value;
  901. }
  902. }
  903. return $mp3Values;
  904. }
  905. /**
  906. * Import the settings
  907. *
  908. */
  909. private function importSettings() {
  910. $encodedOptions = WPFunctions::get()->getOption('wysija');
  911. $options = unserialize(base64_decode($encodedOptions));
  912. // Sender
  913. $sender = $this->settings->get('sender');
  914. $sender['name'] = isset($options['from_name']) ? $options['from_name'] : '';
  915. $sender['address'] = isset($options['from_email']) ? $options['from_email'] : '';
  916. $this->settings->set('sender', $sender);
  917. // Reply To
  918. $replyTo = $this->settings->get('reply_to');
  919. $replyTo['name'] = isset($options['replyto_name']) ? $options['replyto_name'] : '';
  920. $replyTo['address'] = isset($options['replyto_email']) ? $options['replyto_email'] : '';
  921. $this->settings->set('reply_to', $replyTo);
  922. // Bounce
  923. $bounce = $this->settings->get('bounce');
  924. $bounce['address'] = isset($options['bounce_email']) && WPFunctions::get()->isEmail($options['bounce_email']) ? $options['bounce_email'] : '';
  925. $this->settings->set('bounce', $bounce);
  926. // Notification
  927. $notification = $this->settings->get('notification');
  928. $notification['address'] = isset($options['emails_notified']) ? $options['emails_notified'] : '';
  929. $this->settings->set('notification', $notification);
  930. // Subscribe
  931. $subscribe = $this->settings->get('subscribe');
  932. $subscribe['on_comment']['enabled'] = isset($options['commentform']) ? $options['commentform'] : '0';
  933. $subscribe['on_comment']['label'] = isset($options['commentform_linkname']) ? $options['commentform_linkname'] : '';
  934. $subscribe['on_comment']['segments'] = isset($options['commentform_lists']) ? $this->getMappedSegmentIds($options['commentform_lists']) : [];
  935. $subscribe['on_register']['enabled'] = isset($options['registerform']) ? $options['registerform'] : '0';
  936. $subscribe['on_register']['label'] = isset($options['registerform_linkname']) ? $options['registerform_linkname'] : '';
  937. $subscribe['on_register']['segments'] = isset($options['registerform_lists']) ? $this->getMappedSegmentIds($options['registerform_lists']) : [];
  938. $this->settings->set('subscribe', $subscribe);
  939. // Subscription
  940. $subscription = $this->settings->get('subscription');
  941. $subscription['pages']['unsubscribe'] = isset($options['unsubscribe_page']) ? $options['unsubscribe_page'] : '';
  942. $subscription['pages']['confirmation'] = isset($options['confirmation_page']) ? $options['confirmation_page'] : '';
  943. $subscription['pages']['manage'] = isset($options['subscriptions_page']) ? $options['subscriptions_page'] : '';
  944. $subscription['segments'] = isset($options['manage_subscriptions_lists']) ? $this->getMappedSegmentIds($options['manage_subscriptions_lists']) : [];
  945. $this->settings->set('subscription', $subscription);
  946. // Confirmation email
  947. $signupConfirmation = $this->settings->get('signup_confirmation');
  948. $signupConfirmation['enabled'] = isset($options['confirm_dbleoptin']) && ($options['confirm_dbleoptin'] == 0) ? 0 : 1;
  949. if (isset($options['confirm_email_id'])) {
  950. $confirmEmailId = $options['confirm_email_id'];
  951. $confirmEmail = $this->getEmail($confirmEmailId);
  952. if (!empty($confirmEmail)) {
  953. $signupConfirmation['subject'] = isset($confirmEmail['subject']) ? $confirmEmail['subject'] : '';
  954. $signupConfirmation['body'] = isset($confirmEmail['body']) ? $confirmEmail['body'] : '';
  955. }
  956. }
  957. $this->settings->set('signup_confirmation', $signupConfirmation);
  958. // Analytics
  959. $analytics = $this->settings->get('analytics');
  960. $analytics['enabled'] = isset($options['analytics']) ? $options['analytics'] : '';
  961. $this->settings->set('analytics', $analytics);
  962. // MTA
  963. $mtaGroup = isset($options['sending_method']) && ($options['sending_method'] == 'smtp') ? 'smtp' : 'website';
  964. $this->settings->set('mta_group', $mtaGroup);
  965. $mta = $this->settings->get('mta');
  966. $mta['method'] = (isset($options['smtp_host']) && ($options['smtp_host'] == 'smtp.sendgrid.net')) ? 'SendGrid' : (isset($options['sending_method']) && ($options['sending_method'] == 'smtp') ? 'SMTP' : 'PHPMail');
  967. $sendingEmailsNumber = isset($options['sending_emails_number']) ? $options['sending_emails_number'] : '';
  968. $sendingEmailsEach = isset($options['sending_emails_each']) ? $options['sending_emails_each'] : '';
  969. $mta['frequency']['emails'] = $this->mapFrequencyEmails($sendingEmailsNumber, $sendingEmailsEach);
  970. $mta['frequency']['interval'] = $this->mapFrequencyInterval($sendingEmailsEach);
  971. $mta['host'] = isset($options['smtp_host']) ? $options['smtp_host'] : '';
  972. $mta['port'] = isset($options['smtp_port']) ? $options['smtp_port'] : '';
  973. $mta['login'] = isset($options['smtp_login']) ? $options['smtp_login'] : '';
  974. $mta['password'] = isset($options['smtp_password']) ? $options['smtp_password'] : '';
  975. $mta['encryption'] = isset($options['smtp_secure']) ? $options['smtp_secure'] : '';
  976. $mta['authentication'] = !isset($options['smtp_auth']) ? '1' : '-1';
  977. $this->settings->set('mta', $mta);
  978. // SMTP Provider
  979. if ($mta['method'] == 'SendGrid') {
  980. $this->settings->set('smtp_provider', 'SendGrid');
  981. }
  982. // Installation date
  983. if (isset($options['installed_time'])) {
  984. $datetime = new \MailPoet\WP\DateTime();
  985. $installedAt = $datetime->formatTime($options['installed_time'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT);
  986. $this->settings->set('installed_at', $installedAt);
  987. }
  988. $this->log(__("Settings imported", 'mailpoet'));
  989. }
  990. /**
  991. * Get an email
  992. *
  993. * @global object $wpdb
  994. * @param int $emailId
  995. * @return array Email
  996. */
  997. private function getEmail($emailId) {
  998. global $wpdb;
  999. $email = [];
  1000. $table = $this->mp2EmailTable;
  1001. $sql = "
  1002. SELECT e.*
  1003. FROM `$table` e
  1004. WHERE e.email_id = '$emailId'
  1005. ";
  1006. $email = $wpdb->get_row($sql, ARRAY_A);
  1007. return $email;
  1008. }
  1009. /**
  1010. * Map the Email frequency interval
  1011. *
  1012. * @param string $intervalStr Interval
  1013. * @return string Interval
  1014. */
  1015. private function mapFrequencyInterval($intervalStr) {
  1016. switch ($intervalStr) {
  1017. case 'one_min':
  1018. $interval = 1;
  1019. break;
  1020. case 'two_min':
  1021. $interval = 2;
  1022. break;
  1023. case 'five_min':
  1024. $interval = 5;
  1025. break;
  1026. case 'ten_min':
  1027. $interval = 10;
  1028. break;
  1029. default:
  1030. $interval = 15;
  1031. }
  1032. return (string)$interval;
  1033. }
  1034. /**
  1035. * Map the Email frequency number
  1036. *
  1037. * @param int $emailsNumber Emails number
  1038. * @param string $intervalStr Interval
  1039. * @return int Emails number
  1040. */
  1041. private function mapFrequencyEmails($emailsNumber, $intervalStr) {
  1042. if (empty($emailsNumber)) {
  1043. $emailsNumber = 70;
  1044. } else {
  1045. switch ($intervalStr) {
  1046. case 'thirty_min':
  1047. $emailsNumber /= 2;
  1048. break;
  1049. case 'hourly':
  1050. case '':
  1051. $emailsNumber /= 4;
  1052. break;
  1053. case 'two_hours':
  1054. $emailsNumber /= 8;
  1055. break;
  1056. }
  1057. $emailsNumber = (int)round($emailsNumber);
  1058. }
  1059. return $emailsNumber;
  1060. }
  1061. }