settings = $settings; $this->wp = $wp; $this->captcha = $captcha; $this->referralDetector = $referralDetector; $this->prefix = Env::$dbPrefix; $this->models = [ 'newsletter_option_fields', 'newsletter_templates', ]; $this->templates = [ 'WelcomeBlank1Column', 'WelcomeBlank12Column', 'GiftWelcome', 'Minimal', 'Phone', 'Sunglasses', 'RealEstate', 'AppWelcome', 'FoodBox', 'Poet', 'PostNotificationsBlank1Column', 'ModularStyleStories', 'RssSimpleNews', 'NotSoMedium', 'WideStoryLayout', 'IndustryConference', 'ScienceWeekly', 'NewspaperTraditional', 'ClearNews', 'DogFood', 'KidsClothing', 'RockBand', 'WineCity', 'Fitness', 'Motor', 'Avocado', 'BookStoreWithCoupon', 'FlowersWithCoupon', 'NewsletterBlank1Column', 'NewsletterBlank12Column', 'NewsletterBlank121Column', 'NewsletterBlank13Column', 'SimpleText', 'TakeAHike', 'NewsDay', 'WorldCup', 'FestivalEvent', 'RetroComputingMagazine', 'Shoes', 'Music', 'Hotels', 'PieceOfCake', 'BuddhistTemple', 'Mosque', 'Synagogue', 'Faith', 'College', 'RenewableEnergy', 'PrimarySchool', 'ComputerRepair', 'YogaStudio', 'Retro', 'Charity', 'CityLocalNews', 'Coffee', 'Vlogger', 'Birds', 'Engineering', 'BrandingAgencyNews', 'WordPressTheme', 'Drone', 'FashionBlog', 'FashionStore', 'FashionBlogA', 'Photography', 'JazzClub', 'Guitarist', 'HealthyFoodBlog', 'Software', 'LifestyleBlogA', 'FashionShop', 'LifestyleBlogB', 'Painter', 'FarmersMarket', ]; $this->flagsController = $flagsController; } public function up() { $localizer = new Localizer(); $localizer->forceLoadWebsiteLocaleText(); array_map([$this, 'populate'], $this->models); $defaultSegment = $this->createDefaultSegment(); $this->createDefaultForm($defaultSegment); $this->createDefaultSettings(); $this->createDefaultUsersFlags(); $this->createMailPoetPage(); $this->createSourceForSubscribers(); $this->updateMetaFields(); $this->scheduleInitialInactiveSubscribersCheck(); $this->scheduleAuthorizedSendingEmailsCheck(); $this->scheduleBeamer(); $this->updateLastSubscribedAt(); $this->enableStatsNotificationsForAutomatedEmails(); $this->scheduleUnsubscribeTokens(); $this->scheduleSubscriberLinkTokens(); $this->detectReferral(); $this->updateFormsSuccessMessages(); $this->moveGoogleAnalyticsFromPremium(); } private function createMailPoetPage() { $pages = $this->wp->getPosts([ 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC', 'post_type' => 'mailpoet_page', ]); $page = null; if (!empty($pages)) { $page = array_shift($pages); if (strpos($page->post_content, '[mailpoet_page]') === false) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.NotCamelCaps $page = null; } } if ($page === null) { $mailpoetPageId = Pages::createMailPoetPage(); } else { $mailpoetPageId = (int)$page->ID; } $subscription = $this->settings->get('subscription.pages', []); if (empty($subscription)) { $this->settings->set('subscription.pages', [ 'unsubscribe' => $mailpoetPageId, 'manage' => $mailpoetPageId, 'confirmation' => $mailpoetPageId, 'captcha' => $mailpoetPageId, ]); } elseif (empty($subscription['captcha']) || $subscription['captcha'] !== $mailpoetPageId) { // For existing installations $this->settings->set('subscription.pages', array_merge($subscription, ['captcha' => $mailpoetPageId])); } } private function createDefaultSettings() { $currentUser = $this->wp->wpGetCurrentUser(); $settingsDbVersion = $this->settings->fetch('db_version'); // set cron trigger option to default method if (!$this->settings->fetch(CronTrigger::SETTING_NAME)) { $this->settings->set(CronTrigger::SETTING_NAME, [ 'method' => CronTrigger::DEFAULT_METHOD, ]); } // set default sender info based on current user $sender = [ 'name' => $currentUser->display_name, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.NotCamelCaps 'address' => $currentUser->user_email, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.NotCamelCaps ]; // set default from name & address if (!$this->settings->fetch('sender')) { $this->settings->set('sender', $sender); } // enable signup confirmation by default if (!$this->settings->fetch('signup_confirmation')) { $this->settings->set('signup_confirmation', [ 'enabled' => true, ]); } // set installation date if (!$this->settings->fetch('installed_at')) { $this->settings->set('installed_at', date("Y-m-d H:i:s")); } // set captcha settings $captcha = $this->settings->fetch('captcha'); $reCaptcha = $this->settings->fetch('re_captcha'); if (empty($captcha)) { $captchaType = Captcha::TYPE_DISABLED; if (!empty($reCaptcha['enabled'])) { $captchaType = Captcha::TYPE_RECAPTCHA; } elseif ($this->captcha->isSupported()) { $captchaType = Captcha::TYPE_BUILTIN; } $this->settings->set('captcha', [ 'type' => $captchaType, 'recaptcha_site_token' => !empty($reCaptcha['site_token']) ? $reCaptcha['site_token'] : '', 'recaptcha_secret_token' => !empty($reCaptcha['secret_token']) ? $reCaptcha['secret_token'] : '', ]); } $subscriberEmailNotification = $this->settings->fetch(NewSubscriberNotificationMailer::SETTINGS_KEY); if (empty($subscriberEmailNotification)) { $sender = $this->settings->fetch('sender', []); $this->settings->set('subscriber_email_notification', [ 'enabled' => true, 'automated' => true, 'address' => isset($sender['address']) ? $sender['address'] : null, ]); } $statsNotifications = $this->settings->fetch(Worker::SETTINGS_KEY); if (empty($statsNotifications)) { $sender = $this->settings->fetch('sender', []); $this->settings->set(Worker::SETTINGS_KEY, [ 'enabled' => true, 'address' => isset($sender['address']) ? $sender['address'] : null, ]); } $woocommerceOptinOnCheckout = $this->settings->fetch('woocommerce.optin_on_checkout'); if (empty($woocommerceOptinOnCheckout)) { $this->settings->set('woocommerce.optin_on_checkout', [ 'enabled' => empty($settingsDbVersion), // enable on new installs only 'message' => $this->wp->_x('Yes, I would like to be added to your mailing list', "default email opt-in message displayed on checkout page for ecommerce websites"), ]); // this is here only for translators to pick it up, after it is translated we will replace the other message with this one $this->wp->_x('I would like to receive exclusive emails with discounts and product information', "default email opt-in message displayed on checkout page for ecommerce websites"); } // reset mailer log MailerLog::resetMailerLog(); } private function createDefaultUsersFlags() { $lastAnnouncementSeen = $this->settings->fetch('last_announcement_seen'); if (!empty($lastAnnouncementSeen)) { foreach ($lastAnnouncementSeen as $userId => $value) { $this->createOrUpdateUserFlag($userId, 'last_announcement_seen', $value); } $this->settings->delete('last_announcement_seen'); } $prefix = 'user_seen_editor_tutorial'; $prefixLength = strlen($prefix); foreach ($this->settings->getAll() as $name => $value) { if (substr($name, 0, $prefixLength) === $prefix) { $userId = substr($name, $prefixLength); $this->createOrUpdateUserFlag($userId, 'editor_tutorial_seen', $value); $this->settings->delete($name); } } } private function createOrUpdateUserFlag($userId, $name, $value) { $userFlagsRepository = \MailPoet\DI\ContainerWrapper::getInstance(WP_DEBUG)->get(UserFlagsRepository::class); $flag = $userFlagsRepository->findOneBy([ 'userId' => $userId, 'name' => $name, ]); if (!$flag) { $flag = new UserFlagEntity(); $flag->setUserId($userId); $flag->setName($name); $userFlagsRepository->persist($flag); } $flag->setValue($value); $userFlagsRepository->flush(); } private function createDefaultSegment() { // WP Users segment Segment::getWPSegment(); // WooCommerce customers segment Segment::getWooCommerceSegment(); // Synchronize WP Users WP::synchronizeUsers(); // Default segment if (Segment::where('type', 'default')->count() === 0) { $defaultSegment = Segment::create(); $newList = [ 'name' => $this->wp->__('My First List', 'mailpoet'), 'description' => $this->wp->__('This list is automatically created when you install MailPoet.', 'mailpoet'), ]; if ($this->flagsController->isSupported(FeaturesController::NEW_DEFAULT_LIST_NAME)) { $newList['name'] = $this->wp->__('Newsletter mailing list', 'mailpoet'); } $defaultSegment->hydrate($newList); $defaultSegment->save(); } } private function createDefaultForm($defaultSegment) { if (Form::count() === 0) { $factory = new DefaultForm(new Styles()); if (!$defaultSegment) { $defaultSegment = Segment::where('type', 'default')->orderByAsc('id')->limit(1)->findOne(); } Form::createOrUpdate([ 'name' => $factory->getName(), 'body' => serialize($factory->getBody()), 'settings' => serialize($factory->getSettings($defaultSegment)), 'styles' => $factory->getStyles(), ]); } } protected function newsletterOptionFields() { $optionFields = [ [ 'name' => 'isScheduled', 'newsletter_type' => 'standard', ], [ 'name' => 'scheduledAt', 'newsletter_type' => 'standard', ], [ 'name' => 'event', 'newsletter_type' => 'welcome', ], [ 'name' => 'segment', 'newsletter_type' => 'welcome', ], [ 'name' => 'role', 'newsletter_type' => 'welcome', ], [ 'name' => 'afterTimeNumber', 'newsletter_type' => 'welcome', ], [ 'name' => 'afterTimeType', 'newsletter_type' => 'welcome', ], [ 'name' => 'intervalType', 'newsletter_type' => 'notification', ], [ 'name' => 'timeOfDay', 'newsletter_type' => 'notification', ], [ 'name' => 'weekDay', 'newsletter_type' => 'notification', ], [ 'name' => 'monthDay', 'newsletter_type' => 'notification', ], [ 'name' => 'nthWeekDay', 'newsletter_type' => 'notification', ], [ 'name' => 'schedule', 'newsletter_type' => 'notification', ], [ 'name' => 'group', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'event', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'sendTo', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'segment', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'afterTimeNumber', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'afterTimeType', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], [ 'name' => 'meta', 'newsletter_type' => Newsletter::TYPE_AUTOMATIC, ], ]; return [ 'rows' => $optionFields, 'identification_columns' => [ 'name', 'newsletter_type', ], ]; } protected function newsletterTemplates() { $templates = []; foreach ($this->templates as $template) { $template = self::TEMPLATES_NAMESPACE . $template; $template = new $template(Env::$assetsUrl); $templates[] = $template->get(); } return [ 'rows' => $templates, 'identification_columns' => [ 'name', ], 'remove_duplicates' => true, ]; } protected function populate($model) { $modelMethod = Helpers::underscoreToCamelCase($model); $table = $this->prefix . $model; $dataDescriptor = $this->$modelMethod(); $rows = $dataDescriptor['rows']; $identificationColumns = array_fill_keys( $dataDescriptor['identification_columns'], '' ); $removeDuplicates = isset($dataDescriptor['remove_duplicates']) && $dataDescriptor['remove_duplicates']; foreach ($rows as $row) { $existenceComparisonFields = array_intersect_key( $row, $identificationColumns ); if (!$this->rowExists($table, $existenceComparisonFields)) { $this->insertRow($table, $row); } else { if ($removeDuplicates) { $this->removeDuplicates($table, $row, $existenceComparisonFields); } $this->updateRow($table, $row, $existenceComparisonFields); } } } private function rowExists($table, $columns) { global $wpdb; $conditions = array_map(function($key) { return $key . '=%s'; }, array_keys($columns)); return $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE " . implode(' AND ', $conditions), array_values($columns) )) > 0; } private function insertRow($table, $row) { global $wpdb; return $wpdb->insert( $table, $row ); } private function updateRow($table, $row, $where) { global $wpdb; return $wpdb->update( $table, $row, $where ); } private function removeDuplicates($table, $row, $where) { global $wpdb; $conditions = ['1=1']; $values = []; foreach ($where as $field => $value) { $conditions[] = "`t1`.`$field` = `t2`.`$field`"; $conditions[] = "`t1`.`$field` = %s"; $values[] = $value; } $conditions = implode(' AND ', $conditions); $sql = "DELETE FROM `$table` WHERE $conditions"; return $wpdb->query( $wpdb->prepare( "DELETE t1 FROM $table t1, $table t2 WHERE t1.id < t2.id AND $conditions", $values ) ); } private function createSourceForSubscribers() { Subscriber::rawExecute( ' UPDATE LOW_PRIORITY `' . Subscriber::$_table . '` subscriber ' . ' JOIN `' . StatisticsForms::$_table . '` stats ON stats.subscriber_id=subscriber.id ' . ' SET `source` = "' . Source::FORM . '"' . ' WHERE `source` = "' . Source::UNKNOWN . '"' ); Subscriber::rawExecute( 'UPDATE LOW_PRIORITY `' . Subscriber::$_table . '`' . ' SET `source` = "' . Source::WORDPRESS_USER . '"' . ' WHERE `source` = "' . Source::UNKNOWN . '"' . ' AND `wp_user_id` IS NOT NULL' ); Subscriber::rawExecute( 'UPDATE LOW_PRIORITY `' . Subscriber::$_table . '`' . ' SET `source` = "' . Source::WOOCOMMERCE_USER . '"' . ' WHERE `source` = "' . Source::UNKNOWN . '"' . ' AND `is_woocommerce_user` = 1' ); } private function updateMetaFields() { global $wpdb; // perform once for versions below or equal to 3.26.0 if (version_compare($this->settings->get('db_version', '3.26.1'), '3.26.0', '>')) { return false; } $tables = [ScheduledTask::$_table, SendingQueue::$_table]; foreach ($tables as $table) { $query = "UPDATE `%s` SET meta = NULL WHERE meta = 'null'"; $wpdb->query(sprintf($query, $table)); } return true; } private function scheduleInitialInactiveSubscribersCheck() { $this->scheduleTask( InactiveSubscribers::TASK_TYPE, Carbon::createFromTimestamp($this->wp->currentTime('timestamp'))->addHour() ); } private function scheduleAuthorizedSendingEmailsCheck() { if (!Bridge::isMPSendingServiceEnabled()) { return; } $this->scheduleTask( AuthorizedSendingEmailsCheck::TASK_TYPE, Carbon::createFromTimestamp($this->wp->currentTime('timestamp')) ); } private function scheduleBeamer() { if (!$this->settings->get('last_announcement_date')) { $this->scheduleTask( Beamer::TASK_TYPE, Carbon::createFromTimestamp($this->wp->currentTime('timestamp')) ); } } private function updateLastSubscribedAt() { global $wpdb; // perform once for versions below or equal to 3.42.0 if (version_compare($this->settings->get('db_version', '3.42.1'), '3.42.0', '>')) { return false; } $query = "UPDATE `%s` SET last_subscribed_at = GREATEST(COALESCE(confirmed_at, 0), COALESCE(created_at, 0)) WHERE status != '%s' AND last_subscribed_at IS NULL;"; $wpdb->query(sprintf( $query, Subscriber::$_table, Subscriber::STATUS_UNCONFIRMED )); return true; } private function scheduleUnsubscribeTokens() { $this->scheduleTask( UnsubscribeTokens::TASK_TYPE, Carbon::createFromTimestamp($this->wp->currentTime('timestamp')) ); } private function scheduleSubscriberLinkTokens() { $this->scheduleTask( SubscriberLinkTokens::TASK_TYPE, Carbon::createFromTimestamp($this->wp->currentTime('timestamp')) ); } private function scheduleTask($type, $datetime) { $task = ScheduledTask::where('type', $type) ->whereRaw('status = ? OR status IS NULL', [ScheduledTask::STATUS_SCHEDULED]) ->findOne(); if ($task) { return true; } $task = ScheduledTask::create(); $task->type = $type; $task->status = ScheduledTask::STATUS_SCHEDULED; $task->scheduledAt = $datetime; $task->save(); } /** * Remove this comment when this private function is actually used * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements */ private function updateFormsSuccessMessages() { if (version_compare($this->settings->get('db_version', '3.23.2'), '3.23.1', '>')) { return; } Form::updateSuccessMessages(); } private function enableStatsNotificationsForAutomatedEmails() { if (version_compare($this->settings->get('db_version', '3.31.2'), '3.31.1', '>')) { return; } $settings = $this->settings->get(Worker::SETTINGS_KEY); $settings['automated'] = true; $this->settings->set(Worker::SETTINGS_KEY, $settings); } private function moveGoogleAnalyticsFromPremium() { global $wpdb; if (version_compare($this->settings->get('db_version', '3.38.2'), '3.38.1', '>')) { return; } $premiumTableName = $wpdb->prefix . 'mailpoet_premium_newsletter_extra_data'; $premiumTableExists = (int)$wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM information_schema.tables WHERE table_schema=%s AND table_name=%s;", $wpdb->dbname, $premiumTableName ) ); if ($premiumTableExists) { $query = " UPDATE `%s` as n JOIN %s as ped ON n.id=ped.newsletter_id SET n.ga_campaign = ped.ga_campaign "; $wpdb->query( sprintf( $query, Newsletter::$_table, $premiumTableName ) ); } return true; } private function detectReferral() { $this->referralDetector->detect(); } }