From 70a89b793973882b40b674987815c82ff1b4b34e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jakes=CC=8C?= Date: Thu, 3 Oct 2019 09:37:46 +0200 Subject: [PATCH] Move dynamic segments from Premium plugin [MAILPOET-2382] --- lib/API/JSON/v1/DynamicSegments.php | 219 ++++++++++++++++ lib/API/JSON/v1/NewsletterLinks.php | 20 ++ lib/DI/ContainerConfigurator.php | 2 + .../Exceptions/ErrorSavingException.php | 7 + .../InvalidSegmentTypeException.php | 15 ++ lib/DynamicSegments/Filters/EmailAction.php | 109 ++++++++ lib/DynamicSegments/Filters/Filter.php | 11 + lib/DynamicSegments/Filters/UserRole.php | 44 ++++ .../Filters/WooCommerceCategory.php | 74 ++++++ .../Filters/WooCommerceProduct.php | 56 ++++ .../AddToNewslettersSegments.php | 48 ++++ .../AddToSubscribersFilters.php | 67 +++++ .../SendingNewslettersSubscribersFinder.php | 56 ++++ .../SubscribersBulkActionHandler.php | 26 ++ .../SubscribersListingsHandlerFactory.php | 17 ++ lib/DynamicSegments/Mappers/DBMapper.php | 86 +++++++ .../Mappers/FormDataMapper.php | 120 +++++++++ .../Persistence/Loading/Loader.php | 35 +++ .../Loading/SingleSegmentLoader.php | 35 +++ .../Persistence/Loading/SubscribersCount.php | 23 ++ .../Persistence/Loading/SubscribersIds.php | 29 +++ lib/DynamicSegments/Persistence/Saver.php | 77 ++++++ lib/Models/DynamicSegment.php | 114 +++++++++ lib/Models/DynamicSegmentFilter.php | 55 ++++ lib/Models/SubscribersInDynamicSegment.php | 27 ++ .../API/JSON/v1/DynamicSegmentsTest.php | 239 ++++++++++++++++++ .../Filters/EmailActionTest.php | 103 ++++++++ .../DynamicSegments/Filters/UserRoleTest.php | 56 ++++ .../Filters/WooCommerceCategoryTest.php | 15 ++ .../AddToNewslettersSegmentsTest.php | 59 +++++ .../AddToSubscribersFiltersTest.php | 98 +++++++ ...endingNewslettersSubscribersFinderTest.php | 108 ++++++++ .../SubscribersBulkActionHandlerTest.php | 48 ++++ .../SubscribersListingsHandlerFactoryTest.php | 42 +++ .../Mappers/FormDataMapperTest.php | 114 +++++++++ .../Persistence/Loading/LoaderTest.php | 84 ++++++ .../Loading/SingleSegmentLoaderTest.php | 63 +++++ .../Loading/SubscribersCountTest.php | 59 +++++ .../Loading/SubscribersIdsTest.php | 66 +++++ .../DynamicSegments/Persistence/SaverTest.php | 65 +++++ .../SubscribersInDynamicSegmentTest.php | 83 ++++++ 41 files changed, 2674 insertions(+) create mode 100644 lib/API/JSON/v1/DynamicSegments.php create mode 100644 lib/API/JSON/v1/NewsletterLinks.php create mode 100644 lib/DynamicSegments/Exceptions/ErrorSavingException.php create mode 100644 lib/DynamicSegments/Exceptions/InvalidSegmentTypeException.php create mode 100644 lib/DynamicSegments/Filters/EmailAction.php create mode 100644 lib/DynamicSegments/Filters/Filter.php create mode 100644 lib/DynamicSegments/Filters/UserRole.php create mode 100644 lib/DynamicSegments/Filters/WooCommerceCategory.php create mode 100644 lib/DynamicSegments/Filters/WooCommerceProduct.php create mode 100644 lib/DynamicSegments/FreePluginConnectors/AddToNewslettersSegments.php create mode 100644 lib/DynamicSegments/FreePluginConnectors/AddToSubscribersFilters.php create mode 100644 lib/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinder.php create mode 100644 lib/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandler.php create mode 100644 lib/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactory.php create mode 100644 lib/DynamicSegments/Mappers/DBMapper.php create mode 100644 lib/DynamicSegments/Mappers/FormDataMapper.php create mode 100644 lib/DynamicSegments/Persistence/Loading/Loader.php create mode 100644 lib/DynamicSegments/Persistence/Loading/SingleSegmentLoader.php create mode 100644 lib/DynamicSegments/Persistence/Loading/SubscribersCount.php create mode 100644 lib/DynamicSegments/Persistence/Loading/SubscribersIds.php create mode 100644 lib/DynamicSegments/Persistence/Saver.php create mode 100644 lib/Models/DynamicSegment.php create mode 100644 lib/Models/DynamicSegmentFilter.php create mode 100644 lib/Models/SubscribersInDynamicSegment.php create mode 100644 tests/integration/API/JSON/v1/DynamicSegmentsTest.php create mode 100644 tests/integration/DynamicSegments/Filters/EmailActionTest.php create mode 100644 tests/integration/DynamicSegments/Filters/UserRoleTest.php create mode 100644 tests/integration/DynamicSegments/Filters/WooCommerceCategoryTest.php create mode 100644 tests/integration/DynamicSegments/FreePluginConnectors/AddToNewslettersSegmentsTest.php create mode 100644 tests/integration/DynamicSegments/FreePluginConnectors/AddToSubscribersFiltersTest.php create mode 100644 tests/integration/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinderTest.php create mode 100644 tests/integration/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandlerTest.php create mode 100644 tests/integration/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactoryTest.php create mode 100644 tests/integration/DynamicSegments/Mappers/FormDataMapperTest.php create mode 100644 tests/integration/DynamicSegments/Persistence/Loading/LoaderTest.php create mode 100644 tests/integration/DynamicSegments/Persistence/Loading/SingleSegmentLoaderTest.php create mode 100644 tests/integration/DynamicSegments/Persistence/Loading/SubscribersCountTest.php create mode 100644 tests/integration/DynamicSegments/Persistence/Loading/SubscribersIdsTest.php create mode 100644 tests/integration/DynamicSegments/Persistence/SaverTest.php create mode 100644 tests/integration/Models/SubscribersInDynamicSegmentTest.php diff --git a/lib/API/JSON/v1/DynamicSegments.php b/lib/API/JSON/v1/DynamicSegments.php new file mode 100644 index 0000000000..f909747206 --- /dev/null +++ b/lib/API/JSON/v1/DynamicSegments.php @@ -0,0 +1,219 @@ + AccessControl::PERMISSION_MANAGE_SEGMENTS, + ]; + + /** @var FormDataMapper */ + private $mapper; + + /** @var Saver */ + private $saver; + + /** @var SingleSegmentLoader */ + private $dynamic_segments_loader; + + /** @var SubscribersCount */ + private $subscribers_counts_loader; + + /** @var BulkActionController */ + private $bulk_action; + + /** @var Handler */ + private $listing_handler; + + public function __construct(BulkActionController $bulk_action, Handler $handler, $mapper = null, $saver = null, $dynamic_segments_loader = null, $subscribers_counts_loader = null) { + $this->bulk_action = $bulk_action; + $this->listing_handler = $handler; + $this->mapper = $mapper ?: new FormDataMapper(); + $this->saver = $saver ?: new Saver(); + $this->dynamic_segments_loader = $dynamic_segments_loader ?: new SingleSegmentLoader(new DBMapper()); + $this->subscribers_counts_loader = $subscribers_counts_loader ?: new SubscribersCount(); + } + + function get($data = []) { + if (isset($data['id'])) { + $id = (int)$data['id']; + } else { + return $this->errorResponse([ + Error::BAD_REQUEST => WPFunctions::get()->__('Missing mandatory argument `id`.', 'mailpoet-premium'), + ]); + } + + try { + $segment = $this->dynamic_segments_loader->load($id); + + $filters = $segment->getFilters(); + + return $this->successResponse(array_merge([ + 'name' => $segment->name, + 'description' => $segment->description, + 'id' => $segment->id, + ], $filters[0]->toArray())); + } catch (\InvalidArgumentException $e) { + return $this->errorResponse([ + Error::NOT_FOUND => WPFunctions::get()->__('This segment does not exist.', 'mailpoet-premium'), + ]); + } + } + + function save($data) { + try { + $dynamic_segment = $this->mapper->mapDataToDB($data); + $this->saver->save($dynamic_segment); + + return $this->successResponse($data); + } catch (InvalidSegmentTypeException $e) { + return $this->errorResponse([ + Error::BAD_REQUEST => $this->getErrorString($e), + ], [], Response::STATUS_BAD_REQUEST); + } catch (ErrorSavingException $e) { + $statusCode = Response::STATUS_UNKNOWN; + if ($e->getCode() === Model::DUPLICATE_RECORD) { + $statusCode = Response::STATUS_CONFLICT; + } + return $this->errorResponse([$statusCode => $e->getMessage()], [], $statusCode); + } + } + + private function getErrorString(InvalidSegmentTypeException $e) { + switch ($e->getCode()) { + case InvalidSegmentTypeException::MISSING_TYPE: + return WPFunctions::get()->__('Segment type is missing.', 'mailpoet-premium'); + case InvalidSegmentTypeException::INVALID_TYPE: + return WPFunctions::get()->__('Segment type is unknown.', 'mailpoet-premium'); + case InvalidSegmentTypeException::MISSING_ROLE: + return WPFunctions::get()->__('Please select user role.', 'mailpoet-premium'); + case InvalidSegmentTypeException::MISSING_ACTION: + return WPFunctions::get()->__('Please select email action.', 'mailpoet-premium'); + case InvalidSegmentTypeException::MISSING_NEWSLETTER_ID: + return WPFunctions::get()->__('Please select an email.', 'mailpoet-premium'); + case InvalidSegmentTypeException::MISSING_PRODUCT_ID: + return WPFunctions::get()->__('Please select category.', 'mailpoet-premium'); + case InvalidSegmentTypeException::MISSING_CATEGORY_ID: + return WPFunctions::get()->__('Please select product.', 'mailpoet-premium'); + default: + return WPFunctions::get()->__('An error occurred while saving data.', 'mailpoet-premium'); + } + } + + function trash($data = []) { + if (isset($data['id'])) { + $id = (int)$data['id']; + } else { + return $this->errorResponse([ + Error::BAD_REQUEST => WPFunctions::get()->__('Missing mandatory argument `id`.', 'mailpoet-premium'), + ]); + } + + try { + $segment = $this->dynamic_segments_loader->load($id); + $segment->trash(); + return $this->successResponse( + $segment->asArray(), + ['count' => 1] + ); + } catch (\InvalidArgumentException $e) { + return $this->errorResponse([ + Error::NOT_FOUND => WPFunctions::get()->__('This segment does not exist.', 'mailpoet-premium'), + ]); + } + } + + function restore($data = []) { + if (isset($data['id'])) { + $id = (int)$data['id']; + } else { + return $this->errorResponse([ + Error::BAD_REQUEST => WPFunctions::get()->__('Missing mandatory argument `id`.', 'mailpoet-premium'), + ]); + } + + try { + $segment = $this->dynamic_segments_loader->load($id); + $segment->restore(); + return $this->successResponse( + $segment->asArray(), + ['count' => 1] + ); + } catch (\InvalidArgumentException $e) { + return $this->errorResponse([ + Error::NOT_FOUND => WPFunctions::get()->__('This segment does not exist.', 'mailpoet-premium'), + ]); + } + } + + function delete($data = []) { + if (isset($data['id'])) { + $id = (int)$data['id']; + } else { + return $this->errorResponse([ + Error::BAD_REQUEST => WPFunctions::get()->__('Missing mandatory argument `id`.', 'mailpoet-premium'), + ]); + } + + try { + $segment = $this->dynamic_segments_loader->load($id); + $segment->delete(); + return $this->successResponse(null, ['count' => 1]); + } catch (\InvalidArgumentException $e) { + return $this->errorResponse([ + Error::NOT_FOUND => WPFunctions::get()->__('This segment does not exist.', 'mailpoet-premium'), + ]); + } + } + + function listing($data = []) { + $listing_data = $this->listing_handler->get('\MailPoet\Premium\Models\DynamicSegment', $data); + + $data = []; + foreach ($listing_data['items'] as $segment) { + $segment->subscribers_url = WPFunctions::get()->adminUrl( + 'admin.php?page=mailpoet-subscribers#/filter[segment=' . $segment->id . ']' + ); + + $row = $segment->asArray(); + $segment_with_filters = $this->dynamic_segments_loader->load($segment->id); + $row['count'] = $this->subscribers_counts_loader->getSubscribersCount($segment_with_filters); + $data[] = $row; + } + + return $this->successResponse($data, [ + 'count' => $listing_data['count'], + 'filters' => $listing_data['filters'], + 'groups' => $listing_data['groups'], + ]); + + } + + function bulkAction($data = []) { + try { + $meta = $this->bulk_action->apply('\MailPoet\Premium\Models\DynamicSegment', $data); + return $this->successResponse(null, $meta); + } catch (\Exception $e) { + return $this->errorResponse([ + $e->getCode() => $e->getMessage(), + ]); + } + } +} diff --git a/lib/API/JSON/v1/NewsletterLinks.php b/lib/API/JSON/v1/NewsletterLinks.php new file mode 100644 index 0000000000..1a199d4332 --- /dev/null +++ b/lib/API/JSON/v1/NewsletterLinks.php @@ -0,0 +1,20 @@ + AccessControl::PERMISSION_MANAGE_SEGMENTS, + ]; + + function get($data = []) { + $links = NewsletterLink::select(['id', 'url'])->where('newsletter_id', $data['newsletterId'])->findArray(); + return $this->successResponse($links); + } + +} \ No newline at end of file diff --git a/lib/DI/ContainerConfigurator.php b/lib/DI/ContainerConfigurator.php index 5217617db9..805707f224 100644 --- a/lib/DI/ContainerConfigurator.php +++ b/lib/DI/ContainerConfigurator.php @@ -66,12 +66,14 @@ class ContainerConfigurator implements IContainerConfigurator { $container->autowire(\MailPoet\API\JSON\v1\AutomatedLatestContent::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\AutomaticEmails::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\CustomFields::class)->setPublic(true); + $container->autowire(\MailPoet\API\JSON\v1\DynamicSegments::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\FeatureFlags::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\Forms::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\ImportExport::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\Mailer::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\MP2Migrator::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\Newsletters::class)->setPublic(true); + $container->autowire(\MailPoet\API\JSON\v1\NewsletterLinks::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\NewsletterTemplates::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\Segments::class)->setPublic(true); $container->autowire(\MailPoet\API\JSON\v1\SendingQueue::class)->setPublic(true); diff --git a/lib/DynamicSegments/Exceptions/ErrorSavingException.php b/lib/DynamicSegments/Exceptions/ErrorSavingException.php new file mode 100644 index 0000000000..2503203931 --- /dev/null +++ b/lib/DynamicSegments/Exceptions/ErrorSavingException.php @@ -0,0 +1,7 @@ +newsletter_id = (int)$newsletter_id; + if ($link_id) { + $this->link_id = (int)$link_id; + } + $this->setAction($action); + $this->connect = $connect; + } + + private function setAction($action) { + if (!in_array($action, EmailAction::$allowed_actions)) { + throw new \InvalidArgumentException("Unknown action " . $action); + } + $this->action = $action; + } + + function toSql(\ORM $orm) { + if (($this->action === EmailAction::ACTION_CLICKED) || ($this->action === EmailAction::ACTION_NOT_CLICKED)) { + $table = StatisticsClicks::$_table; + } else { + $table = StatisticsOpens::$_table; + } + + if (($this->action === EmailAction::ACTION_NOT_CLICKED) || ($this->action === EmailAction::ACTION_NOT_OPENED)) { + $orm->rawJoin( + 'INNER JOIN ' . StatisticsNewsletters::$_table, + 'statssent.subscriber_id = ' . Subscriber::$_table . '.id AND statssent.newsletter_id = ' . $this->newsletter_id, + 'statssent' + ); + $orm->rawJoin( + 'LEFT JOIN ' . $table, + $this->createNotStatsJoin(), + 'stats' + ); + $orm->whereNull('stats.id'); + } else { + $orm->rawJoin( + 'INNER JOIN ' . $table, + 'stats.subscriber_id = ' . Subscriber::$_table . '.id AND stats.newsletter_id = ' . $this->newsletter_id, + 'stats' + ); + } + if (($this->action === EmailAction::ACTION_CLICKED) && ($this->link_id)) { + $orm->where('stats.link_id', $this->link_id); + } + return $orm; + } + + private function createNotStatsJoin() { + $clause = 'statssent.subscriber_id = stats.subscriber_id AND stats.newsletter_id = ' . $this->newsletter_id; + if (($this->action === EmailAction::ACTION_NOT_CLICKED) && ($this->link_id)) { + $clause .= ' AND stats.link_id = ' . $this->link_id; + } + return $clause; + } + + function toArray() { + return [ + 'action' => $this->action, + 'newsletter_id' => $this->newsletter_id, + 'link_id' => $this->link_id, + 'connect' => $this->connect, + 'segmentType' => EmailAction::SEGMENT_TYPE, + ]; + } +} diff --git a/lib/DynamicSegments/Filters/Filter.php b/lib/DynamicSegments/Filters/Filter.php new file mode 100644 index 0000000000..351e0d4381 --- /dev/null +++ b/lib/DynamicSegments/Filters/Filter.php @@ -0,0 +1,11 @@ +role = $role; + $this->connect = $connect; + } + + function toSql(\ORM $orm) { + global $wpdb; + $orm->join($wpdb->users, ['wpusers.id', '=', MP_SUBSCRIBERS_TABLE . '.wp_user_id'], 'wpusers') + ->join($wpdb->usermeta, ['wpusers.ID', '=', 'wpusermeta.user_id'], 'wpusermeta') + ->whereEqual('wpusermeta.meta_key', $wpdb->prefix . 'capabilities') + ->whereLike('wpusermeta.meta_value', '%"' . $this->role . '"%'); + return $orm; + } + + public function toArray() { + return [ + 'wordpressRole' => $this->role, + 'connect' => $this->connect, + 'segmentType' => UserRole::SEGMENT_TYPE, + ]; + } + + function getRole() { + return $this->role; + } +} \ No newline at end of file diff --git a/lib/DynamicSegments/Filters/WooCommerceCategory.php b/lib/DynamicSegments/Filters/WooCommerceCategory.php new file mode 100644 index 0000000000..cfdc0819e4 --- /dev/null +++ b/lib/DynamicSegments/Filters/WooCommerceCategory.php @@ -0,0 +1,74 @@ +category_id = (int)$category_id; + $this->connect = $connect; + } + + function toSql(\ORM $orm) { + global $wpdb; + $orm->distinct(); + $orm->rawJoin( + 'INNER JOIN ' . $wpdb->postmeta, + "postmeta.meta_key = '_customer_user' AND " . Subscriber::$_table . '.wp_user_id=postmeta.meta_value', + 'postmeta' + ); + $orm->join($wpdb->prefix . 'woocommerce_order_items', ['postmeta.post_id', '=', 'items.order_id'], 'items'); + $orm->rawJoin( + 'INNER JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta', + "itemmeta.order_item_id = items.order_item_id AND itemmeta.meta_key = '_product_id'", + 'itemmeta' + ); + $orm->join($wpdb->term_relationships, ['itemmeta.meta_value', '=', 'term_relationships.object_id'], 'term_relationships'); + $orm->rawJoin( + 'INNER JOIN ' . $wpdb->term_taxonomy, + ' + term_taxonomy.term_taxonomy_id=term_relationships.term_taxonomy_id + AND + term_taxonomy.term_id IN (' . join(',', $this->getAllCategoryIds()) . ')', + 'term_taxonomy' + ); + $orm->where('status', Subscriber::STATUS_SUBSCRIBED); + return $orm; + } + + private function getAllCategoryIds() { + $subcategories = WPFunctions::get()->getTerms('product_cat', ['child_of' => $this->category_id]); + if (!is_array($subcategories)) return []; + $ids = array_map(function($category) { + return $category->term_id; + }, $subcategories); + $ids[] = $this->category_id; + return $ids; + } + + function toArray() { + return [ + 'action' => WooCommerceCategory::ACTION_CATEGORY, + 'category_id' => $this->category_id, + 'connect' => $this->connect, + 'segmentType' => WooCommerceCategory::SEGMENT_TYPE, + ]; + } +} diff --git a/lib/DynamicSegments/Filters/WooCommerceProduct.php b/lib/DynamicSegments/Filters/WooCommerceProduct.php new file mode 100644 index 0000000000..504d370016 --- /dev/null +++ b/lib/DynamicSegments/Filters/WooCommerceProduct.php @@ -0,0 +1,56 @@ +product_id = (int)$product_id; + $this->connect = $connect; + } + + function toSql(\ORM $orm) { + global $wpdb; + $orm->distinct(); + $orm->rawJoin( + 'INNER JOIN ' . $wpdb->postmeta, + "postmeta.meta_key = '_customer_user' AND " . Subscriber::$_table . '.wp_user_id=postmeta.meta_value', + 'postmeta' + ); + $orm->join($wpdb->prefix . 'woocommerce_order_items', ['postmeta.post_id', '=', 'items.order_id'], 'items'); + $orm->rawJoin( + 'INNER JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta', + "itemmeta.order_item_id=items.order_item_id + AND itemmeta.meta_key='_product_id' + AND itemmeta.meta_value=" . $this->product_id, + 'itemmeta' + ); + $orm->where('status', Subscriber::STATUS_SUBSCRIBED); + return $orm; + } + + function toArray() { + return [ + 'action' => WooCommerceProduct::ACTION_PRODUCT, + 'product_id' => $this->product_id, + 'connect' => $this->connect, + 'segmentType' => WooCommerceProduct::SEGMENT_TYPE, + ]; + } +} \ No newline at end of file diff --git a/lib/DynamicSegments/FreePluginConnectors/AddToNewslettersSegments.php b/lib/DynamicSegments/FreePluginConnectors/AddToNewslettersSegments.php new file mode 100644 index 0000000000..13568b2003 --- /dev/null +++ b/lib/DynamicSegments/FreePluginConnectors/AddToNewslettersSegments.php @@ -0,0 +1,48 @@ +loader = $loader; + $this->subscribersCountLoader = $subscribersCountLoader; + } + + /** + * @param array $initial_segments + * + * @return array + */ + function add(array $initial_segments) { + $dynamic_segments = $this->getListings(); + return array_merge($initial_segments, $dynamic_segments); + } + + private function getListings() { + $dynamic_segments = $this->loader->load(); + return $this->buildResult($dynamic_segments); + } + + private function buildResult($dynamic_segments) { + $result = []; + foreach ($dynamic_segments as $dynamic_segment) { + $result[] = [ + 'id' => $dynamic_segment->id, + 'name' => $dynamic_segment->name, + 'subscribers' => $this->subscribersCountLoader->getSubscribersCount($dynamic_segment), + 'deleted_at' => $dynamic_segment->deleted_at, + ]; + } + return $result; + } +} diff --git a/lib/DynamicSegments/FreePluginConnectors/AddToSubscribersFilters.php b/lib/DynamicSegments/FreePluginConnectors/AddToSubscribersFilters.php new file mode 100644 index 0000000000..e317b33629 --- /dev/null +++ b/lib/DynamicSegments/FreePluginConnectors/AddToSubscribersFilters.php @@ -0,0 +1,67 @@ +loader = $loader; + $this->subscribersCountLoader = $subscribersCountLoader; + } + + /** + * @param array $segment_filters + * + * @return array + */ + function add(array $segment_filters) { + $dynamic_segments = $this->getListings(); + return $this->sort(array_merge($segment_filters, $dynamic_segments)); + } + + private function getListings() { + $dynamic_segments = $this->loader->load(); + return $this->buildResult($dynamic_segments); + } + + private function buildResult($dynamic_segments) { + $result = []; + foreach ($dynamic_segments as $dynamic_segment) { + $result[] = [ + 'value' => $dynamic_segment->id, + 'label' => sprintf( + '%s (%s)', + $dynamic_segment->name, + number_format($this->subscribersCountLoader->getSubscribersCount($dynamic_segment)) + ), + ]; + } + return $result; + } + + private function sort($segment_filters) { + $special_segment_filters = []; + $segments = []; + foreach ($segment_filters as $segment_filter) { + if (is_numeric($segment_filter['value'])) { + $segments[] = $segment_filter; + } else { + $special_segment_filters[] = $segment_filter; + } + } + usort($segments, function ($a, $b) { + return strcasecmp($a["label"], $b["label"]); + }); + return array_merge($special_segment_filters, $segments); + } + +} diff --git a/lib/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinder.php b/lib/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinder.php new file mode 100644 index 0000000000..f4f2b0eadf --- /dev/null +++ b/lib/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinder.php @@ -0,0 +1,56 @@ +single_segment_loader = $single_segment_loader; + $this->subscribers_ids_loader = $subscribers_ids_loader; + } + + /** + * @param Segment $segment + * @param int[] $subscribers_to_process_ids + * + * @return Subscriber[] + */ + function findSubscribersInSegment(Segment $segment, array $subscribers_to_process_ids) { + if ($segment->type !== DynamicSegment::TYPE_DYNAMIC) return []; + $dynamic_segment = $this->single_segment_loader->load($segment->id); + return $this->subscribers_ids_loader->load($dynamic_segment, $subscribers_to_process_ids); + } + + /** + * @param Segment $segment + * + * @return array + */ + function getSubscriberIdsInSegment(Segment $segment) { + if ($segment->type !== DynamicSegment::TYPE_DYNAMIC) return []; + $dynamic_segment = $this->single_segment_loader->load($segment->id); + $result = $this->subscribers_ids_loader->load($dynamic_segment); + return $this->createResultArray($result); + } + + private function createResultArray($subscribers) { + $result = []; + foreach ($subscribers as $subscriber) { + $result[] = $subscriber->asArray(); + } + return $result; + } + +} diff --git a/lib/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandler.php b/lib/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandler.php new file mode 100644 index 0000000000..06cba8a5c0 --- /dev/null +++ b/lib/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandler.php @@ -0,0 +1,26 @@ +apply('\MailPoet\Premium\Models\SubscribersInDynamicSegment', $data); + } + } + +} diff --git a/lib/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactory.php b/lib/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactory.php new file mode 100644 index 0000000000..cda061456a --- /dev/null +++ b/lib/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactory.php @@ -0,0 +1,17 @@ +type === DynamicSegment::TYPE_DYNAMIC) { + $listing = new Handler(); + return $listing_data = $listing->get('\MailPoet\Premium\Models\SubscribersInDynamicSegment', $data); + } + } +} diff --git a/lib/DynamicSegments/Mappers/DBMapper.php b/lib/DynamicSegments/Mappers/DBMapper.php new file mode 100644 index 0000000000..e70fc16540 --- /dev/null +++ b/lib/DynamicSegments/Mappers/DBMapper.php @@ -0,0 +1,86 @@ +getFilters($segment_data->id, $filters_data); + $segment_data->setFilters($filters); + return $segment_data; + } + + /** + * @param \MailPoet\Premium\Models\DynamicSegment[] $segments_data + * @param DynamicSegmentFilter[] $filters_data + * + * @return DynamicSegment[] + */ + function mapSegments(array $segments_data, array $filters_data) { + $result = []; + foreach ($segments_data as $segment_data) { + $result[] = $this->mapSegment($segment_data, $filters_data); + } + return $result; + } + + private function getFilters($segment_id, $all_filters) { + $result = []; + foreach ($all_filters as $filter) { + if ($filter->segment_id === $segment_id) { + $result[] = $this->createFilter($filter->filter_data); + } + } + return $result; + } + + /** + * @param array $data + * @return Filter + * @throws InvalidSegmentTypeException + */ + private function createFilter(array $data) { + switch ($this->getSegmentType($data)) { + case 'userRole': + if (!$data['wordpressRole']) throw new InvalidSegmentTypeException('Missing role'); + return new UserRole($data['wordpressRole'], 'and'); + case 'email': + return new EmailAction($data['action'], $data['newsletter_id'], $data['link_id']); + case 'woocommerce': + if ($data['action'] === WooCommerceProduct::ACTION_PRODUCT) { + return new WooCommerceProduct($data['product_id']); + } + return new WooCommerceCategory($data['category_id']); + default: + throw new InvalidSegmentTypeException('Invalid type'); + } + } + + /** + * @param array $data + * @return string + * @throws InvalidSegmentTypeException + */ + private function getSegmentType(array $data) { + if (!isset($data['segmentType'])) { + throw new InvalidSegmentTypeException('Segment type is not set'); + } + return $data['segmentType']; + } + +} diff --git a/lib/DynamicSegments/Mappers/FormDataMapper.php b/lib/DynamicSegments/Mappers/FormDataMapper.php new file mode 100644 index 0000000000..4903b713f6 --- /dev/null +++ b/lib/DynamicSegments/Mappers/FormDataMapper.php @@ -0,0 +1,120 @@ +getFilters($data); + $dynamic_segment = $this->createDynamicSegment($data); + + $dynamic_segment->setFilters($filters); + + return $dynamic_segment; + } + + private function createDynamicSegment($data) { + $dataToSave = [ + 'name' => isset($data['name']) ? $data['name'] : '', + 'description' => isset($data['description']) ? $data['description'] : '', + ]; + $dynamic_segment = null; + if (isset($data['id'])) { + $dynamic_segment = DynamicSegment::findOne($data['id']); + } + if ($dynamic_segment instanceof DynamicSegment) { + $dynamic_segment->set($dataToSave); + } else { + $dynamic_segment = DynamicSegment::create(); + if ($dynamic_segment instanceof DynamicSegment) { + $dynamic_segment->hydrate($dataToSave); + } + } + return $dynamic_segment; + } + + /** + * @param array $data + * + * @return Filter[] + * @throws InvalidSegmentTypeException + */ + private function getFilters(array $data) { + switch ($this->getSegmentType($data)) { + case 'userRole': + if (!$data['wordpressRole']) throw new InvalidSegmentTypeException('Missing role', InvalidSegmentTypeException::MISSING_ROLE); + return [new UserRole($data['wordpressRole'])]; + case 'email': + return $this->createEmail($data); + case 'woocommerce': + return $this->createWooCommerce($data); + default: + throw new InvalidSegmentTypeException('Invalid type', InvalidSegmentTypeException::INVALID_TYPE); + } + } + + /** + * @param array $data + * + * @return string + * @throws InvalidSegmentTypeException + */ + private function getSegmentType(array $data) { + if (!isset($data['segmentType'])) { + throw new InvalidSegmentTypeException('Segment type is not set', InvalidSegmentTypeException::MISSING_TYPE); + } + return $data['segmentType']; + } + + /** + * @param array $data + * + * @return EmailAction[] + * @throws InvalidSegmentTypeException + */ + private function createEmail(array $data) { + if (empty($data['action'])) throw new InvalidSegmentTypeException('Missing action', InvalidSegmentTypeException::MISSING_ACTION); + if (empty($data['newsletter_id'])) throw new InvalidSegmentTypeException('Missing newsletter id', InvalidSegmentTypeException::MISSING_NEWSLETTER_ID); + if (isset($data['link_id'])) { + return [new EmailAction($data['action'], $data['newsletter_id'], $data['link_id'])]; + } else { + return [new EmailAction($data['action'], $data['newsletter_id'])]; + } + } + + /** + * @param array $data + * + * @return Filter[] + * @throws InvalidSegmentTypeException + */ + private function createWooCommerce($data) { + if (empty($data['action'])) throw new InvalidSegmentTypeException('Missing action', InvalidSegmentTypeException::MISSING_ACTION); + switch ($data['action']) { + case WooCommerceCategory::ACTION_CATEGORY: + if (!isset($data['category_id'])) throw new InvalidSegmentTypeException('Missing category', InvalidSegmentTypeException::MISSING_CATEGORY_ID); + return [new WooCommerceCategory($data['category_id'])]; + case WooCommerceProduct::ACTION_PRODUCT: + if (!isset($data['product_id'])) throw new InvalidSegmentTypeException('Missing product', InvalidSegmentTypeException::MISSING_PRODUCT_ID); + return [new WooCommerceProduct($data['product_id'])]; + default: + throw new \InvalidArgumentException("Unknown action " . $data['action']); + } + + } + +} diff --git a/lib/DynamicSegments/Persistence/Loading/Loader.php b/lib/DynamicSegments/Persistence/Loading/Loader.php new file mode 100644 index 0000000000..5b5c7d8dfa --- /dev/null +++ b/lib/DynamicSegments/Persistence/Loading/Loader.php @@ -0,0 +1,35 @@ +mapper = $mapper; + } + + /** + * @return DynamicSegment[] + */ + function load() { + $segments = DynamicSegment::findAll(); + return $this->loadFilters($segments); + } + + private function loadFilters(array $segments) { + $ids = array_map(function($segment) { + return $segment->id; + }, $segments); + $filters = DynamicSegmentFilter::getAllBySegmentIds($ids); + + return $this->mapper->mapSegments($segments, $filters); + } + +} \ No newline at end of file diff --git a/lib/DynamicSegments/Persistence/Loading/SingleSegmentLoader.php b/lib/DynamicSegments/Persistence/Loading/SingleSegmentLoader.php new file mode 100644 index 0000000000..666ccd4745 --- /dev/null +++ b/lib/DynamicSegments/Persistence/Loading/SingleSegmentLoader.php @@ -0,0 +1,35 @@ +mapper = $mapper; + } + + /** + * @param string|int $segment_id + * @return DynamicSegment + */ + function load($segment_id) { + + $segment = DynamicSegment::findOne($segment_id); + if (!$segment instanceof DynamicSegment) { + throw new \InvalidArgumentException('Segment not found'); + } + + $filters = $segment->dynamicSegmentFilters()->findMany(); + + return $this->mapper->mapSegment($segment, $filters); + } + + + +} diff --git a/lib/DynamicSegments/Persistence/Loading/SubscribersCount.php b/lib/DynamicSegments/Persistence/Loading/SubscribersCount.php new file mode 100644 index 0000000000..3197679825 --- /dev/null +++ b/lib/DynamicSegments/Persistence/Loading/SubscribersCount.php @@ -0,0 +1,23 @@ +getFilters() as $filter) { + $orm = $filter->toSql($orm); + } + return $orm->findOne()->cnt; + } + +} diff --git a/lib/DynamicSegments/Persistence/Loading/SubscribersIds.php b/lib/DynamicSegments/Persistence/Loading/SubscribersIds.php new file mode 100644 index 0000000000..4390f78c7b --- /dev/null +++ b/lib/DynamicSegments/Persistence/Loading/SubscribersIds.php @@ -0,0 +1,29 @@ +getFilters() as $filter) { + $orm = $filter->toSql($orm); + } + if ($limit_to_subscribers_ids) { + $orm->whereIn(Subscriber::$_table . '.id', $limit_to_subscribers_ids); + } + return $orm->findMany(); + } + +} diff --git a/lib/DynamicSegments/Persistence/Saver.php b/lib/DynamicSegments/Persistence/Saver.php new file mode 100644 index 0000000000..4f3d2522fe --- /dev/null +++ b/lib/DynamicSegments/Persistence/Saver.php @@ -0,0 +1,77 @@ +beginTransaction(); + + $data_segment = $this->saveSegment($db, $segment); + $this->saveFilters($db, $segment, $data_segment->id()); + $db->commit(); + return $data_segment->id(); + } + + /** + * @throws ErrorSavingException + */ + private function saveSegment(\PDO $db, DynamicSegment $segment) { + $segment->save(); + $this->checkErrors($db, $segment); + return $segment; + } + + /** + * @throws ErrorSavingException + */ + private function checkErrors(\PDO $db, Model $model) { + $errors = $model->getErrors(); + if ($errors) { + $code = null; + if (array_key_exists(Model::DUPLICATE_RECORD, $errors)) { + $code = Model::DUPLICATE_RECORD; + } + $db->rollBack(); + throw new ErrorSavingException(join(", ", $model->getErrors()), $code); + } + } + + /** + * @throws ErrorSavingException + */ + private function saveFilters(\PDO $db, DynamicSegment $segment, $saved_data_id) { + $this->deleteFilters($segment); + foreach ($segment->getFilters() as $filter) { + $data_filter = $this->saveFilter($filter, $saved_data_id); + $this->checkErrors($db, $data_filter); + } + } + + private function deleteFilters(DynamicSegment $segment) { + DynamicSegmentFilter::deleteAllBySegmentIds([$segment->id]); + } + + private function saveFilter(Filter $filter, $data_segment_id) { + $data_filter = DynamicSegmentFilter::create(); + if ($data_filter instanceof DynamicSegmentFilter) { + $data_filter->segment_id = $data_segment_id; + $data_filter->filter_data = $filter->toArray(); + $data_filter->save(); + } + return $data_filter; + } +} diff --git a/lib/Models/DynamicSegment.php b/lib/Models/DynamicSegment.php new file mode 100644 index 0000000000..1d660db793 --- /dev/null +++ b/lib/Models/DynamicSegment.php @@ -0,0 +1,114 @@ +filters; + } + + /** + * @param Filter[] $filters + */ + public function setFilters(array $filters) { + $this->filters = $filters; + } + + function save() { + $this->set('type', DynamicSegment::TYPE_DYNAMIC); + return parent::save(); + } + + function dynamicSegmentFilters() { + return $this->has_many(__NAMESPACE__ . '\DynamicSegmentFilter', 'segment_id'); + } + + static function findAll() { + $query = self::select('*'); + return $query->where('type', DynamicSegment::TYPE_DYNAMIC) + ->whereNull('deleted_at') + ->findMany(); + } + + static function listingQuery(array $data = []) { + $query = self::select('*'); + $query->where('type', DynamicSegment::TYPE_DYNAMIC); + if (isset($data['group'])) { + $query->filter('groupBy', $data['group']); + } + if (isset($data['search'])) { + $query->filter('search', $data['search']); + } + return $query; + } + + static function groups() { + return [ + [ + 'name' => 'all', + 'label' => WPFunctions::get()->__('All', 'mailpoet-premium'), + 'count' => DynamicSegment::getPublished()->where('type', DynamicSegment::TYPE_DYNAMIC)->count(), + ], + [ + 'name' => 'trash', + 'label' => WPFunctions::get()->__('Trash', 'mailpoet-premium'), + 'count' => parent::getTrashed()->where('type', DynamicSegment::TYPE_DYNAMIC)->count(), + ], + ]; + } + + function delete() { + DynamicSegmentFilter::where('segment_id', $this->id)->deleteMany(); + return parent::delete(); + } + + static function bulkTrash($orm) { + $count = parent::bulkAction($orm, function($ids) { + $placeholders = join(',', array_fill(0, count($ids), '?')); + DynamicSegment::rawExecute(join(' ', [ + 'UPDATE `' . DynamicSegment::$_table . '`', + 'SET `deleted_at` = NOW()', + 'WHERE `id` IN (' . $placeholders . ')', + ]), $ids); + }); + + return ['count' => $count]; + } + + static function bulkDelete($orm) { + $count = parent::bulkAction($orm, function($ids) { + $placeholders = join(',', array_fill(0, count($ids), '?')); + DynamicSegmentFilter::rawExecute(join(' ', [ + 'DELETE FROM `' . DynamicSegmentFilter::$_table . '`', + 'WHERE `segment_id` IN (' . $placeholders . ')', + ]), $ids); + DynamicSegment::rawExecute(join(' ', [ + 'DELETE FROM `' . DynamicSegment::$_table . '`', + 'WHERE `id` IN (' . $placeholders . ')', + ]), $ids); + }); + + return ['count' => $count]; + } + +} diff --git a/lib/Models/DynamicSegmentFilter.php b/lib/Models/DynamicSegmentFilter.php new file mode 100644 index 0000000000..232030490f --- /dev/null +++ b/lib/Models/DynamicSegmentFilter.php @@ -0,0 +1,55 @@ +filter_data)) { + $this->filter_data = []; + } + + if (!WPFunctions::get()->isSerialized($this->filter_data)) { + $this->filter_data = serialize($this->filter_data); + } + + return parent::save(); + } + + static function getAllBySegmentIds($segmentIds) { + if (empty($segmentIds)) return []; + $query = self::tableAlias('filters') + ->whereIn('filters.segment_id', $segmentIds); + + $query->findMany(); + return $query->findMany(); + } + + public function __get($name) { + $value = parent::__get($name); + if ($name === 'filter_data' && WPFunctions::get()->isSerialized($value)) { + return unserialize($value); + } + return $value; + } + + static function deleteAllBySegmentIds($segmentIds) { + if (empty($segmentIds)) return; + + $query = self::tableAlias('filters') + ->whereIn('segment_id', $segmentIds); + + $query->deleteMany(); + + } + +} diff --git a/lib/Models/SubscribersInDynamicSegment.php b/lib/Models/SubscribersInDynamicSegment.php new file mode 100644 index 0000000000..6062282df8 --- /dev/null +++ b/lib/Models/SubscribersInDynamicSegment.php @@ -0,0 +1,27 @@ +load($data['filter']['segment']); + foreach ($dynamic_segment->getFilters() as $filter) { + $query = $filter->toSql($query); + } + if (isset($data['group'])) { + $query->filter('groupBy', $data['group']); + } + if (isset($data['search']) && $data['search']) { + $query->filter('search', $data['search']); + } + return $query; + } + +} diff --git a/tests/integration/API/JSON/v1/DynamicSegmentsTest.php b/tests/integration/API/JSON/v1/DynamicSegmentsTest.php new file mode 100644 index 0000000000..350c45574a --- /dev/null +++ b/tests/integration/API/JSON/v1/DynamicSegmentsTest.php @@ -0,0 +1,239 @@ +bulk_action = ContainerWrapper::getInstance()->getPremiumContainer()->get(BulkActionController::class); + $this->listing_handler = ContainerWrapper::getInstance()->getPremiumContainer()->get(Handler::class); + } + + function testGetReturnsResponse() { + $loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader', [ + 'load' => function () { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 's1', + 'description' => '', + ]); + $dynamic_segment->setFilters([new UserRole('Editor', 'or')]); + return $dynamic_segment; + }, + ]); + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, $loader); + $response = $endpoint->get(['id' => 5]); + expect($response)->isInstanceOf('\MailPoet\API\JSON\SuccessResponse'); + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->data)->equals([ + 'id' => null, + 'name' => 's1', + 'description' => '', + 'segmentType' => 'userRole', + 'wordpressRole' => 'Editor', + 'connect' => 'or', + ]); + } + + function testGetReturnsError() { + $loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader', [ + 'load' => function () { + throw new \InvalidArgumentException('segment not found'); + }, + ]); + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, $loader); + $response = $endpoint->get(['id' => 5]); + expect($response)->isInstanceOf('\MailPoet\API\JSON\ErrorResponse'); + expect($response->status)->equals(self::SEGMENT_NOT_FOUND_RESPONSE_CODE); + } + + function testSaverSavesData() { + $mapper = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Mappers\FormDataMapper', ['mapDataToDB' => Expected::once(function () { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'name', + 'description' => 'description', + ]); + return $dynamic_segment; + })]); + $saver = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Saver', ['save' => Expected::once()]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, $mapper, $saver); + $response = $endpoint->save([]); + expect($response)->isInstanceOf('\MailPoet\API\JSON\SuccessResponse'); + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + } + + function testSaverReturnsErrorOnInvalidData() { + $mapper = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Mappers\FormDataMapper', ['mapDataToDB' => Expected::once(function () { + throw new InvalidSegmentTypeException(); + })]); + $saver = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Saver', ['save' => Expected::never()]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, $mapper, $saver); + $response = $endpoint->save([]); + expect($response)->isInstanceOf('\MailPoet\API\JSON\ErrorResponse'); + expect($response->status)->equals(self::INVALID_DATA_RESPONSE_CODE); + } + + function testSaverReturnsErrorOnSave() { + $mapper = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Mappers\FormDataMapper', ['mapDataToDB' => Expected::once(function () { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'name', + 'description' => 'description', + ]); + return $dynamic_segment; + })]); + $saver = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Saver', ['save' => Expected::once(function () { + throw new ErrorSavingException('Error saving data', Model::DUPLICATE_RECORD); + })]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, $mapper, $saver); + $response = $endpoint->save([]); + expect($response)->isInstanceOf('\MailPoet\API\JSON\ErrorResponse'); + expect($response->status)->equals(self::SERVER_ERROR_RESPONSE_CODE); + expect($response->errors[0]['message'])->equals('Error saving data'); + } + + function testItCanTrashASegment() { + DynamicSegment::deleteMany(); + $dynamic_segment = DynamicSegment::createOrUpdate([ + 'name' => 'Trash test', + 'description' => 'description', + ]); + $loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader', [ + 'load' => function () use($dynamic_segment) { + return $dynamic_segment; + }, + ]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, $loader); + $response = $endpoint->trash(['id' => $dynamic_segment->id]); + + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->data)->equals($dynamic_segment->asArray()); + expect($response->meta['count'])->equals(1); + + $dynamic_segment = DynamicSegment::findOne($dynamic_segment->id); + expect($dynamic_segment->deleted_at)->notNull(); + + $dynamic_segment->delete(); + } + + function testItCanRestoreASegment() { + DynamicSegment::deleteMany(); + $dynamic_segment = DynamicSegment::createOrUpdate([ + 'name' => 'Restore test', + 'description' => 'description', + ]); + $loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader', [ + 'load' => function () use($dynamic_segment) { + return $dynamic_segment; + }, + ]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, $loader); + $response = $endpoint->restore(['id' => $dynamic_segment->id]); + + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->data)->equals($dynamic_segment->asArray()); + expect($response->meta['count'])->equals(1); + + $dynamic_segment = DynamicSegment::findOne($dynamic_segment->id); + expect($dynamic_segment->deleted_at)->equals(null); + + $dynamic_segment->delete(); + } + + function testItCanDeleteASegment() { + DynamicSegment::deleteMany(); + $dynamic_segment = DynamicSegment::createOrUpdate([ + 'name' => 'Delete test', + 'description' => 'description', + ]); + $filter = DynamicSegmentFilter::createOrUpdate([ + 'segment_id' => $dynamic_segment->id, + ]); + $loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader', [ + 'load' => function () use($dynamic_segment) { + return $dynamic_segment; + }, + ]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, $loader); + $response = $endpoint->delete(['id' => $dynamic_segment->id]); + + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->data)->equals(null); + expect($response->meta['count'])->equals(1); + + expect(DynamicSegment::findOne($dynamic_segment->id))->equals(false); + expect(DynamicSegmentFilter::findOne($filter->id))->equals(false); + } + + function testItCanBulkDeleteSegments() { + DynamicSegment::deleteMany(); + $dynamic_segment_1 = DynamicSegment::createOrUpdate([ + 'name' => 'Test 1', + 'description' => 'description', + ]); + $dynamic_segment_2 = DynamicSegment::createOrUpdate([ + 'name' => 'Test 2', + 'description' => 'description', + ]); + $filter = DynamicSegmentFilter::createOrUpdate([ + 'segment_id' => $dynamic_segment_1->id, + ]); + + $endpoint = new DynamicSegments($this->bulk_action, $this->listing_handler, null, null, null); + $response = $endpoint->bulkAction([ + 'action' => 'trash', + 'listing' => ['group' => 'all'], + ]); + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->meta['count'])->equals(2); + + $response = $endpoint->bulkAction([ + 'action' => 'delete', + 'listing' => ['group' => 'trash'], + ]); + + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->meta['count'])->equals(2); + + $response = $endpoint->bulkAction([ + 'action' => 'delete', + 'listing' => ['group' => 'trash'], + ]); + expect($response->status)->equals(self::SUCCESS_RESPONSE_CODE); + expect($response->meta['count'])->equals(0); + + expect(DynamicSegment::count())->equals(0); + expect(DynamicSegmentFilter::findOne($filter->id))->equals(false); + } + +} diff --git a/tests/integration/DynamicSegments/Filters/EmailActionTest.php b/tests/integration/DynamicSegments/Filters/EmailActionTest.php new file mode 100644 index 0000000000..c8ff75c333 --- /dev/null +++ b/tests/integration/DynamicSegments/Filters/EmailActionTest.php @@ -0,0 +1,103 @@ +newsletter = Newsletter::createOrUpdate([ + 'subject' => 'newsletter 1', + 'status' => 'sent', + 'type' => Newsletter::TYPE_NOTIFICATION, + ]); + $this->subscriber_opened_clicked = Subscriber::createOrUpdate([ + 'email' => 'opened_clicked@example.com', + ]); + $this->subscriber_opened_not_clicked = Subscriber::createOrUpdate([ + 'email' => 'opened_not_clicked@example.com', + ]); + $this->subscriber_not_opened = Subscriber::createOrUpdate([ + 'email' => 'not_opened@example.com', + ]); + $this->subscriber_not_sent = Subscriber::createOrUpdate([ + 'email' => 'not_sent@example.com', + ]); + StatisticsNewsletters::createMultiple([ + ['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriber_opened_clicked->id, 'queue_id' => 1], + ['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriber_opened_not_clicked->id, 'queue_id' => 1], + ['newsletter_id' => $this->newsletter->id, 'subscriber_id' => $this->subscriber_not_opened->id, 'queue_id' => 1], + ]); + StatisticsOpens::getOrCreate($this->subscriber_opened_clicked->id, $this->newsletter->id, 1); + StatisticsOpens::getOrCreate($this->subscriber_opened_not_clicked->id, $this->newsletter->id, 1); + StatisticsClicks::createOrUpdateClickCount(1, $this->subscriber_opened_clicked->id, $this->newsletter->id, 1); + } + + function testGetOpened() { + $emailAction = new EmailAction(EmailAction::ACTION_OPENED, $this->newsletter->id); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(2); + } + + function testNotOpened() { + $emailAction = new EmailAction(EmailAction::ACTION_NOT_OPENED, $this->newsletter->id); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(1); + } + + function testGetClickedWithoutLink() { + $emailAction = new EmailAction(EmailAction::ACTION_CLICKED, $this->newsletter->id); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(1); + } + + function testGetClickedWithLink() { + $emailAction = new EmailAction(EmailAction::ACTION_CLICKED, $this->newsletter->id, 1); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(1); + } + + function testGetClickedWithWrongLink() { + $emailAction = new EmailAction(EmailAction::ACTION_CLICKED, $this->newsletter->id, 2); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(0); + } + + function testGetNotClickedWithLink() { + $emailAction = new EmailAction(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id, 1); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(2); + } + + function testGetNotClickedWithWrongLink() { + $emailAction = new EmailAction(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id, 2); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(3); + } + + function testGetNotClickedWithoutLink() { + $emailAction = new EmailAction(EmailAction::ACTION_NOT_CLICKED, $this->newsletter->id); + $sql = $emailAction->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(2); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + StatisticsClicks::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete(); + StatisticsNewsletters::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete(); + StatisticsOpens::where('newsletter_id', $this->newsletter->id)->findResultSet()->delete(); + $this->newsletter->delete(); + $this->subscriber_opened_clicked->delete(); + $this->subscriber_opened_not_clicked->delete(); + $this->subscriber_not_opened->delete(); + $this->subscriber_not_sent->delete(); + } +} diff --git a/tests/integration/DynamicSegments/Filters/UserRoleTest.php b/tests/integration/DynamicSegments/Filters/UserRoleTest.php new file mode 100644 index 0000000000..58d541433a --- /dev/null +++ b/tests/integration/DynamicSegments/Filters/UserRoleTest.php @@ -0,0 +1,56 @@ +cleanData(); + wp_insert_user([ + 'user_login' => 'user-role-test1', + 'user_email' => 'user-role-test1@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test2', + 'user_email' => 'user-role-test2@example.com', + 'role' => 'administrator', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test3', + 'user_email' => 'user-role-test3@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + } + + function testItConstructsQuery() { + $userRole = new UserRole('editor', 'and'); + $sql = $userRole->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(2); + } + + function testItDoesntGetSubString() { + $userRole = new UserRole('edit', 'and'); + $sql = $userRole->toSql(Subscriber::selectExpr('*')); + expect($sql->count())->equals(0); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + $emails = ['user-role-test1@example.com', 'user-role-test2@example.com', 'user-role-test3@example.com']; + foreach ($emails as $email) { + $user = get_user_by('email', $email); + if ($user) { + wp_delete_user($user->ID); + } + } + } +} diff --git a/tests/integration/DynamicSegments/Filters/WooCommerceCategoryTest.php b/tests/integration/DynamicSegments/Filters/WooCommerceCategoryTest.php new file mode 100644 index 0000000000..2a000aa8a4 --- /dev/null +++ b/tests/integration/DynamicSegments/Filters/WooCommerceCategoryTest.php @@ -0,0 +1,15 @@ +toArray(); + expect($data)->notEmpty(); + expect($data['segmentType'])->same('woocommerce'); + expect($data['action'])->same('purchasedCategory'); + expect($data['category_id'])->same(5); + } +} diff --git a/tests/integration/DynamicSegments/FreePluginConnectors/AddToNewslettersSegmentsTest.php b/tests/integration/DynamicSegments/FreePluginConnectors/AddToNewslettersSegmentsTest.php new file mode 100644 index 0000000000..328fe94f56 --- /dev/null +++ b/tests/integration/DynamicSegments/FreePluginConnectors/AddToNewslettersSegmentsTest.php @@ -0,0 +1,59 @@ +hydrate([ + 'name' => 'segment1', + 'description' => '', + ]); + + $segment_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\Loader', ['load' => Expected::once(function () { + return []; + })]); + + $subscribers_count_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersCount', ['getSubscribersCount' => Expected::never()]); + + $filter = new AddToNewslettersSegments($segment_loader, $subscribers_count_loader); + $result = $filter->add([$dynamic_segment]); + expect($result)->equals([$dynamic_segment]); + } + + function testItAddsDynamicSegments() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment2', + 'description' => '', + 'id' => 1, + ]); + + $segment_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\Loader', ['load' => Expected::once(function () use ($dynamic_segment) { + return [$dynamic_segment]; + })]); + + $subscribers_count_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersCount', ['getSubscribersCount']); + $subscribers_count_loader + ->expects($this->once()) + ->method('getSubscribersCount') + ->with($this->equalTo($dynamic_segment)) + ->will($this->returnValue(4)); + + $filter = new AddToNewslettersSegments($segment_loader, $subscribers_count_loader); + $result = $filter->add([]); + + expect($result)->count(1); + expect($result[0])->equals([ + 'id' => 1, + 'name' => 'segment2', + 'subscribers' => 4, + 'deleted_at' => null, + ]); + } +} diff --git a/tests/integration/DynamicSegments/FreePluginConnectors/AddToSubscribersFiltersTest.php b/tests/integration/DynamicSegments/FreePluginConnectors/AddToSubscribersFiltersTest.php new file mode 100644 index 0000000000..807802ef88 --- /dev/null +++ b/tests/integration/DynamicSegments/FreePluginConnectors/AddToSubscribersFiltersTest.php @@ -0,0 +1,98 @@ + 'segment1', + 'value' => '', + ]; + + $segment_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\Loader', ['load' => Expected::once(function () { + return []; + })]); + + $subscribers_count_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersCount', ['getSubscribersCount' => Expected::never()]); + + $filter = new AddToSubscribersFilters($segment_loader, $subscribers_count_loader); + $result = $filter->add([$original_segment]); + expect($result)->equals([$original_segment]); + } + + function testItAddsDynamicSegments() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment2', + 'description' => '', + 'id' => 1, + ]); + + $segment_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\Loader', ['load' => Expected::once(function () use ($dynamic_segment) { + return [$dynamic_segment]; + })]); + + $subscribers_count_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersCount', ['getSubscribersCount']); + $subscribers_count_loader + ->expects($this->once()) + ->method('getSubscribersCount') + ->with($this->equalTo($dynamic_segment)) + ->will($this->returnValue(4)); + + $filter = new AddToSubscribersFilters($segment_loader, $subscribers_count_loader); + $result = $filter->add([]); + + expect($result)->count(1); + expect($result[0])->equals([ + 'label' => 'segment2 (4)', + 'value' => 1, + ]); + } + + function testItSortsTheResult() { + $dynamic_segment1 = DynamicSegment::create(); + $dynamic_segment1->hydrate([ + 'name' => 'segment b', + 'description' => '', + 'id' => '1', + ]); + $dynamic_segment2 = DynamicSegment::create(); + $dynamic_segment2->hydrate([ + 'name' => 'segment a', + 'description' => '', + 'id' => '2', + ]); + + $segment_loader = Stub::makeEmpty( + '\MailPoet\Premium\DynamicSegments\Persistence\Loading\Loader', + [ + 'load' => Expected::once(function () use ($dynamic_segment1, $dynamic_segment2) { + return [$dynamic_segment1, $dynamic_segment2]; + }), + ] + ); + + $subscribers_count_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersCount', ['getSubscribersCount']); + $subscribers_count_loader + ->expects($this->exactly(2)) + ->method('getSubscribersCount') + ->will($this->returnValue(4)); + + $filter = new AddToSubscribersFilters($segment_loader, $subscribers_count_loader); + $result = $filter->add([ + ['value' => '', 'label' => 'Special segment filter'], + ['value' => '3', 'label' => 'segment c'], + ]); + + expect($result)->count(4); + expect($result[0]['value'])->equals(''); + expect($result[1]['value'])->equals('2'); + expect($result[2]['value'])->equals('1'); + expect($result[3]['value'])->equals('3'); + } +} diff --git a/tests/integration/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinderTest.php b/tests/integration/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinderTest.php new file mode 100644 index 0000000000..6f25aea4af --- /dev/null +++ b/tests/integration/DynamicSegments/FreePluginConnectors/SendingNewslettersSubscribersFinderTest.php @@ -0,0 +1,108 @@ +single_segment_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SingleSegmentLoader'); + $this->subscribers_ids_loader = Stub::makeEmpty('\MailPoet\Premium\DynamicSegments\Persistence\Loading\SubscribersIds'); + $this->subscribers_in_segments_finder = new SendingNewslettersSubscribersFinder($this->single_segment_loader, $this->subscribers_ids_loader); + } + + function testFindSubscribersInSegmentReturnsEmptyIfNotDynamic() { + $this->single_segment_loader + ->expects($this->never()) + ->method('load'); + $this->subscribers_ids_loader + ->expects($this->never()) + ->method('load'); + $segment = Segment::create(); + $segment->type = Segment::TYPE_DEFAULT; + $segment->id = 3; + $result = $this->subscribers_in_segments_finder->findSubscribersInSegment($segment, []); + expect($result)->count(0); + } + + function testFindSubscribersInSegmentReturnsSubscribers() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment 1', + 'description' => '', + ]); + $ids = [1, 2, 3]; + $this->single_segment_loader + ->expects($this->once()) + ->method('load') + ->with($this->equalTo(3)) + ->will($this->returnValue($dynamic_segment)); + $this->subscribers_ids_loader + ->expects($this->once()) + ->method('load') + ->with($this->equalTo($dynamic_segment), $ids) + ->will($this->returnValue([new Subscriber()])); + $segment = DynamicSegment::create(); + $segment->type = DynamicSegment::TYPE_DYNAMIC; + $segment->id = 3; + $result = $this->subscribers_in_segments_finder->findSubscribersInSegment($segment, $ids); + expect($result)->count(1); + } + + + function testGetSubscriberIdsInSegmentReturnsEmptyIfNotDynamic() { + $this->single_segment_loader + ->expects($this->never()) + ->method('load'); + $this->subscribers_ids_loader + ->expects($this->never()) + ->method('load'); + $segment = DynamicSegment::create(); + $segment->type = DynamicSegment::TYPE_DEFAULT; + $result = $this->subscribers_in_segments_finder->getSubscriberIdsInSegment($segment); + expect($result)->count(0); + } + + function testGetSubscriberIdsInSegmentReturnsSubscribers() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment 2', + 'description' => '', + ]); + $subscriber1 = Subscriber::create(); + $subscriber1->hydrate(['id' => 1]); + $subscriber2 = Subscriber::create(); + $subscriber2->hydrate(['id' => 2]); + $this->single_segment_loader + ->expects($this->once()) + ->method('load') + ->with($this->equalTo(3)) + ->will($this->returnValue($dynamic_segment)); + $this->subscribers_ids_loader + ->expects($this->once()) + ->method('load') + ->with($this->equalTo($dynamic_segment)) + ->will($this->returnValue([$subscriber1, $subscriber2])); + $segment = DynamicSegment::create(); + $segment->type = DynamicSegment::TYPE_DYNAMIC; + $segment->id = 3; + $result = $this->subscribers_in_segments_finder->getSubscriberIdsInSegment($segment); + expect($result)->count(2); + expect($result[0]['id'])->equals(1); + expect($result[1]['id'])->equals(2); + } + +} diff --git a/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandlerTest.php b/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandlerTest.php new file mode 100644 index 0000000000..6c731b07c5 --- /dev/null +++ b/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersBulkActionHandlerTest.php @@ -0,0 +1,48 @@ + 'name', + 'description' => 'desc', + 'type' => 'unknown', + ]; + $handler = new SubscribersBulkActionHandler(); + $result = $handler->apply($segment, [ + 'listing' => ['filter' => ['segment' => 5]], + 'action' => 'trash', + ]); + expect($result)->null(); + } + + function testItReturnsDataForDynamicSegment() { + $segment = DynamicSegment::createOrUpdate([ + 'name' => 'name', + 'description' => 'desc', + 'type' => DynamicSegment::TYPE_DYNAMIC, + ]); + $handler = new SubscribersBulkActionHandler(); + $result = $handler->apply($segment->asArray(), [ + 'listing' => ['filter' => ['segment' => $segment->id()]], + 'action' => 'trash', + ]); + expect($result)->notNull(); + } + + function _before() { + $this->cleanData(); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + } +} diff --git a/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactoryTest.php b/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactoryTest.php new file mode 100644 index 0000000000..21cbafb21f --- /dev/null +++ b/tests/integration/DynamicSegments/FreePluginConnectors/SubscribersListingsHandlerFactoryTest.php @@ -0,0 +1,42 @@ +id = 1; + $segment->name = 'name'; + $segment->type = 'unknown'; + $listings = new SubscribersListingsHandlerFactory(); + $result = $listings->get($segment, []); + expect($result)->null(); + } + + function testItReturnsDataForDynamicSegment() { + $segment = DynamicSegment::createOrUpdate([ + 'name' => 'name', + 'description' => 'desc', + 'type' => DynamicSegment::TYPE_DYNAMIC, + ]); + $listings = new SubscribersListingsHandlerFactory(); + $result = $listings->get($segment, []); + expect($result)->notNull(); + } + + function _before() { + $this->cleanData(); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + } + +} diff --git a/tests/integration/DynamicSegments/Mappers/FormDataMapperTest.php b/tests/integration/DynamicSegments/Mappers/FormDataMapperTest.php new file mode 100644 index 0000000000..a21bfdecb4 --- /dev/null +++ b/tests/integration/DynamicSegments/Mappers/FormDataMapperTest.php @@ -0,0 +1,114 @@ +mapper = new FormDataMapper(); + } + + public function testItThrowsForEmptyData() { + $data = [ + 'name' => '', + 'description' => '', + 'segmentType' => '', + ]; + $this->setExpectedException('\MailPoet\Premium\DynamicSegments\Exceptions\InvalidSegmentTypeException'); + $this->mapper->mapDataToDB($data); + } + + public function testItThrowsForInvalidType() { + $data = [ + 'name' => '', + 'description' => '', + 'segmentType' => 'invalid', + ]; + $this->setExpectedException('\MailPoet\Premium\DynamicSegments\Exceptions\InvalidSegmentTypeException'); + $this->mapper->mapDataToDB($data); + } + + public function testItCreatesUserRoleFilter() { + $data = [ + 'name' => 'Name', + 'description' => 'Description', + 'segmentType' => 'userRole', + 'wordpressRole' => 'administrator', + ]; + $segment = $this->mapper->mapDataToDB($data); + $this->assertInstanceOf('\MailPoet\Premium\Models\DynamicSegment', $segment); + $this->assertEquals('Name', $segment->name); + $this->assertEquals('Description', $segment->description); + $this->assertNull($segment->id); + $this->assertCount(1, $segment->getFilters()); + } + + public function testItFailsIfWooCommerceFilterDataIsMissing() { + $data = [ + 'name' => 'Name', + 'description' => 'Description', + 'segmentType' => 'woocommerce', + ]; + $this->setExpectedException('\MailPoet\Premium\DynamicSegments\Exceptions\InvalidSegmentTypeException'); + $this->mapper->mapDataToDB($data); + } + + public function testItCreatesWooCommerceCategoryFilter() { + $data = [ + 'name' => 'Name', + 'description' => 'Description', + 'segmentType' => 'woocommerce', + 'category_id' => '45', + 'action' => 'purchasedCategory', + ]; + $segment = $this->mapper->mapDataToDB($data); + $this->assertInstanceOf('\MailPoet\Premium\Models\DynamicSegment', $segment); + $this->assertEquals('Name', $segment->name); + $this->assertEquals('Description', $segment->description); + $this->assertNull($segment->id); + $filters = $segment->getFilters(); + $this->assertCount(1, $filters); + $this->assertInstanceOf('\MailPoet\Premium\DynamicSegments\Filters\WooCommerceCategory', $filters[0]); + } + + public function testItCreatesWooCommerceProductFilter() { + $data = [ + 'name' => 'Name', + 'description' => 'Description', + 'segmentType' => 'woocommerce', + 'product_id' => '45', + 'action' => 'purchasedProduct', + ]; + $segment = $this->mapper->mapDataToDB($data); + $this->assertInstanceOf('\MailPoet\Premium\Models\DynamicSegment', $segment); + $this->assertEquals('Name', $segment->name); + $this->assertEquals('Description', $segment->description); + $this->assertNull($segment->id); + $filters = $segment->getFilters(); + $this->assertCount(1, $filters); + $this->assertInstanceOf('\MailPoet\Premium\DynamicSegments\Filters\WooCommerceProduct', $filters[0]); + } + + public function testItSetsIdOnEdit() { + $dynamic_segment = DynamicSegment::createOrUpdate([ + 'name' => 'segment', + 'description' => 'description', + ]); + $data = [ + 'id' => (string)$dynamic_segment->id(), + 'name' => 'Name', + 'description' => 'Description', + 'segmentType' => 'userRole', + 'wordpressRole' => 'administrator', + ]; + $segment = $this->mapper->mapDataToDB($data); + $this->assertSame($dynamic_segment->id(), $segment->id); + + } + +} \ No newline at end of file diff --git a/tests/integration/DynamicSegments/Persistence/Loading/LoaderTest.php b/tests/integration/DynamicSegments/Persistence/Loading/LoaderTest.php new file mode 100644 index 0000000000..5132104deb --- /dev/null +++ b/tests/integration/DynamicSegments/Persistence/Loading/LoaderTest.php @@ -0,0 +1,84 @@ +loader = new Loader(new DBMapper()); + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + $this->segments[] = DynamicSegment::createOrUpdate([ + 'name' => 'segment 1', + 'description' => 'description', + ]); + $this->segments[] = DynamicSegment::createOrUpdate([ + 'name' => 'segment 2', + 'description' => 'description', + ]); + $filter = new UserRole('Administrator', 'and'); + $filter_data = DynamicSegmentFilter::create(); + $filter_data->hydrate([ + 'segment_id' => $this->segments[1]->id, + 'filter_data' => $filter->toArray(), + ]); + $filter_data->save(); + $filter = new UserRole('Editor', 'or'); + $filter_data = DynamicSegmentFilter::create(); + $filter_data->hydrate([ + 'segment_id' => $this->segments[0]->id, + 'filter_data' => $filter->toArray(), + ]); + $filter_data->save(); + } + + function testItLoadsSegments() { + $data = $this->loader->load(); + expect($data)->count(2); + expect($data[0])->isInstanceOf('\MailPoet\Premium\Models\DynamicSegment'); + expect($data[1])->isInstanceOf('\MailPoet\Premium\Models\DynamicSegment'); + } + + function testItDoesNotLoadTrashedSegments() { + $this->segments[0]->trash(); + $data = $this->loader->load(); + expect($data)->count(1); + expect($data[0])->isInstanceOf('\MailPoet\Premium\Models\DynamicSegment'); + expect($data[0]->name)->equals('segment 2'); + } + + function testItPopulatesCommonData() { + $data = $this->loader->load(); + expect($data[0]->name)->equals('segment 1'); + expect($data[1]->name)->equals('segment 2'); + expect($data[0]->description)->equals('description'); + expect($data[1]->description)->equals('description'); + } + + function testItPopulatesFilters() { + $data = $this->loader->load(); + $filters0 = $data[0]->getFilters(); + $filters1 = $data[1]->getFilters(); + expect($filters0)->count(1); + expect($filters1)->count(1); + expect($filters0[0])->isInstanceOf('\MailPoet\Premium\DynamicSegments\Filters\UserRole'); + expect($filters1[0])->isInstanceOf('\MailPoet\Premium\DynamicSegments\Filters\UserRole'); + expect($filters0[0]->getRole())->equals('Editor'); + expect($filters1[0]->getRole())->equals('Administrator'); + } + + function _after() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + } +} diff --git a/tests/integration/DynamicSegments/Persistence/Loading/SingleSegmentLoaderTest.php b/tests/integration/DynamicSegments/Persistence/Loading/SingleSegmentLoaderTest.php new file mode 100644 index 0000000000..be7b06d3e5 --- /dev/null +++ b/tests/integration/DynamicSegments/Persistence/Loading/SingleSegmentLoaderTest.php @@ -0,0 +1,63 @@ +loader = new SingleSegmentLoader(new DBMapper()); + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + $this->segment = DynamicSegment::createOrUpdate([ + 'name' => 'segment 1', + 'description' => 'description', + ]); + $filter = new UserRole('Administrator', 'and'); + $filter_data = DynamicSegmentFilter::create(); + $filter_data->hydrate([ + 'segment_id' => $this->segment->id, + 'filter_data' => $filter->toArray(), + ]); + $filter_data->save(); + } + + function testItLoadsSegments() { + $data = $this->loader->load($this->segment->id); + expect($data)->isInstanceOf('\MailPoet\Premium\Models\DynamicSegment'); + } + + function testItThrowsForUnknownSegment() { + $this->setExpectedException('InvalidArgumentException'); + $this->loader->load($this->segment->id + 11564564); + } + + function testItPopulatesCommonData() { + $data = $this->loader->load($this->segment->id); + expect($data->name)->equals('segment 1'); + expect($data->description)->equals('description'); + } + + function testItPopulatesFilters() { + $data = $this->loader->load($this->segment->id); + $filters0 = $data->getFilters(); + expect($filters0)->count(1); + expect($filters0[0])->isInstanceOf('\MailPoet\Premium\DynamicSegments\Filters\UserRole'); + expect($filters0[0]->getRole())->equals('Administrator'); + } + + function _after() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + } + +} diff --git a/tests/integration/DynamicSegments/Persistence/Loading/SubscribersCountTest.php b/tests/integration/DynamicSegments/Persistence/Loading/SubscribersCountTest.php new file mode 100644 index 0000000000..fa8b163a5b --- /dev/null +++ b/tests/integration/DynamicSegments/Persistence/Loading/SubscribersCountTest.php @@ -0,0 +1,59 @@ +cleanData(); + wp_insert_user([ + 'user_login' => 'user-role-test1', + 'user_email' => 'user-role-test1@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test2', + 'user_email' => 'user-role-test2@example.com', + 'role' => 'administrator', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test3', + 'user_email' => 'user-role-test3@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + } + + function testItConstructsQuery() { + $userRole = DynamicSegment::create(); + $userRole->hydrate([ + 'name' => 'segment', + 'description' => 'description', + ]); + $userRole->setFilters([new UserRole('editor', 'and')]); + + $loader = new SubscribersCount(); + $count = $loader->getSubscribersCount($userRole); + expect($count)->equals(2); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + $emails = ['user-role-test1@example.com', 'user-role-test2@example.com', 'user-role-test3@example.com']; + foreach ($emails as $email) { + $user = get_user_by('email', $email); + if ($user) { + wp_delete_user($user->ID); + } + } + } +} diff --git a/tests/integration/DynamicSegments/Persistence/Loading/SubscribersIdsTest.php b/tests/integration/DynamicSegments/Persistence/Loading/SubscribersIdsTest.php new file mode 100644 index 0000000000..376c2dc28e --- /dev/null +++ b/tests/integration/DynamicSegments/Persistence/Loading/SubscribersIdsTest.php @@ -0,0 +1,66 @@ +cleanData(); + $this->editors_wp_ids[] = wp_insert_user([ + 'user_login' => 'user-role-test1', + 'user_email' => 'user-role-test1@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test2', + 'user_email' => 'user-role-test2@example.com', + 'role' => 'administrator', + 'user_pass' => '12123154', + ]); + $this->editors_wp_ids[] = wp_insert_user([ + 'user_login' => 'user-role-test3', + 'user_email' => 'user-role-test3@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + } + + function testItConstructsSubscribersIdQueryForAnyDynamicSegment() { + $userRole = DynamicSegment::create(); + $userRole->hydrate([ + 'name' => 'segment', + 'description' => 'description', + ]); + $userRole->setFilters([new UserRole('editor', 'and')]); + $loader = new SubscribersIds(); + $result = $loader->load($userRole); + $wp_ids = [ + Subscriber::findOne($result[0]->id)->wp_user_id, + Subscriber::findOne($result[1]->id)->wp_user_id, + ]; + $this->assertEquals($wp_ids, $this->editors_wp_ids, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = true); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + $emails = ['user-role-test1@example.com', 'user-role-test2@example.com', 'user-role-test3@example.com']; + foreach ($emails as $email) { + $user = get_user_by('email', $email); + if ($user) { + wp_delete_user($user->ID); + } + } + } +} diff --git a/tests/integration/DynamicSegments/Persistence/SaverTest.php b/tests/integration/DynamicSegments/Persistence/SaverTest.php new file mode 100644 index 0000000000..f13b2aca6f --- /dev/null +++ b/tests/integration/DynamicSegments/Persistence/SaverTest.php @@ -0,0 +1,65 @@ +saver = new Saver(); + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + } + + function _after() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + } + + function testItSavesSegment() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment 1', + 'description' => 'desc', + ]); + $id = $this->saver->save($dynamic_segment); + $loaded = DynamicSegment::findOne($id); + expect($loaded->name)->equals('segment 1'); + expect($loaded->description)->equals('desc'); + } + + function testItThrowsOnDuplicateSegment() { + $dynamic_segment1 = DynamicSegment::createOrUpdate([ + 'name' => 'segment 1', + 'description' => 'description', + ]); + $dynamic_segment2 = DynamicSegment::create(); + $dynamic_segment2->hydrate([ + 'name' => 'segment 2', + 'description' => 'desc2', + 'id' => $dynamic_segment1->id, + ]); + $this->setExpectedException('\MailPoet\Premium\DynamicSegments\Exceptions\ErrorSavingException', 'Another record already exists. Please specify a different "PRIMARY".', Model::DUPLICATE_RECORD); + $this->saver->save($dynamic_segment2); + } + + function testItSavesFilters() { + $dynamic_segment = DynamicSegment::create(); + $dynamic_segment->hydrate([ + 'name' => 'segment 1', + 'description' => 'desc', + ]); + $dynamic_segment->setFilters([new UserRole('editor', 'and')]); + $id = $this->saver->save($dynamic_segment); + $loaded = DynamicSegmentFilter::select('*')->where('segment_id', $id)->findOne(); + expect($loaded)->isInstanceOf('\MailPoet\Premium\Models\DynamicSegmentFilter'); + } + +} diff --git a/tests/integration/Models/SubscribersInDynamicSegmentTest.php b/tests/integration/Models/SubscribersInDynamicSegmentTest.php new file mode 100644 index 0000000000..c573d2537c --- /dev/null +++ b/tests/integration/Models/SubscribersInDynamicSegmentTest.php @@ -0,0 +1,83 @@ +cleanData(); + $this->dynamic_segment = DynamicSegment::createOrUpdate([ + 'name' => 'name', + 'description' => 'desc', + ]); + $filter = new UserRole('editor', 'and'); + $data_filter = DynamicSegmentFilter::create(); + $data_filter->segment_id = $this->dynamic_segment->id; + $data_filter->filter_data = $filter->toArray(); + $data_filter->save(); + wp_insert_user([ + 'user_login' => 'user-role-test1', + 'user_email' => 'user-role-test1@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test2', + 'user_email' => 'user-role-test2@example.com', + 'role' => 'administrator', + 'user_pass' => '12123154', + ]); + wp_insert_user([ + 'user_login' => 'user-role-test3', + 'user_email' => 'user-role-test3@example.com', + 'role' => 'editor', + 'user_pass' => '12123154', + ]); + } + + function testListingQuery() { + $listing_data = [ + 'filter' => ['segment' => $this->dynamic_segment->id], + 'group' => 'all', + 'search' => '', + ]; + $query = SubscribersInDynamicSegment::listingQuery($listing_data); + $data = $query->orderByAsc('email')->findMany(); + expect($data)->count(2); + expect($data[0]->email)->equals('user-role-test1@example.com'); + expect($data[1]->email)->equals('user-role-test3@example.com'); + } + + function testListingQueryWithSearch() { + $listing_data = [ + 'filter' => ['segment' => $this->dynamic_segment->id], + 'group' => 'all', + 'search' => 'user-role-test1', + ]; + $query = SubscribersInDynamicSegment::listingQuery($listing_data); + $data = $query->findMany(); + expect($data)->count(1); + expect($data[0]->email)->equals('user-role-test1@example.com'); + } + + function _after() { + $this->cleanData(); + } + + private function cleanData() { + \ORM::raw_execute('TRUNCATE ' . DynamicSegment::$_table); + \ORM::raw_execute('TRUNCATE ' . DynamicSegmentFilter::$_table); + $emails = ['user-role-test1@example.com', 'user-role-test2@example.com', 'user-role-test3@example.com']; + foreach ($emails as $email) { + $user = get_user_by('email', $email); + if ($user) { + wp_delete_user($user->ID); + } + } + } + +}