¨/** * Kills WordPress execution and displays Ajax response with an error message. * * This is the handler for wp_die() when processing Ajax requests. * * @since 3.4.0 * @access private * * @param string $message Error message. * @param string $title Optional. Error title (unused). Default empty string. * @param string|array $args Optional. Arguments to control behavior. Default empty array. */ШñÑ£€ÐÀñÑ£òÑ£Ñ/** * Kills WordPress execution and displays XML response with an error message. * * This is the handler for wp_die() when processing XMLRPC requests. * * @since 3.2.0 * @access private * * @global wp_xmlrpc_server $wp_xmlrpc_server * * @param string $message Error message. * @param string $title Optional. Error title. Default empty string. * @param string|array $args Optional. Arguments to control behavior. Default empty array. */°óÑ£PôÑ£·/** * Deletes user interface settings. * * Deleting settings would reset them to the defaults. * * This function has to be used before any output has started as it calls `setcookie()`. * * @since 2.7.0 * * @param string $names The name or array of names of the setting to be deleted. * @return bool|null True if deleted successfully, false otherwise. * Null if the current user is not a member of the site. */sient name. */ to be saved. */·/** * Performs confidence checks on data that shall be encoded to JSON. * * @ignore * @since 4.1.0 * @access private * * @see wp_json_encode() * * @throws Exception If depth limit is reached. * * @param mixed $value Variable (usually an array or object) to encode as JSON. * @param int $depth Maximum depth to walk through $value. Must be greater than 0. * @return mixed The sanitized data that shall be encoded to JSON. */@põîAððÐ÷Ñ£€ð®/** * Prepares response data to be serialized to JSON. * * This supports the JsonSerializable interface for PHP 5.2-5.3 as well. * * @ignore * @since 4.4.0 * @deprecated 5.3.0 This function is no longer needed as support for PHP 5.2-5.3 * has been dropped. * @access private * * @param mixed $value Native representation. * @return bool|int|float|null|string|array Data ready for `json_encode()`. */¨ùÑ£úÑ£@ Ò£óÜ/** * Sends a JSON response back to an Ajax request, indicating success. * * @since 3.5.0 * @since 4.7.0 The `$status_code` parameter was added. * @since 5.6.0 The `$flags` parameter was added. * * @param mixed $value Optional. Data to encode as JSON, then print and die. Default null. * @param int $status_code Optional. The HTTP status code to output. Default null. * @param int $flags Optional. Options to be passed to json_encode(). Default 0. */ý´/** * Retrieves the WordPress site URL. * * If the constant named 'WP_SITEURL' is defined, then the value in that * constant will always be returned. This can be used for debugging a site * on your localhost while not having to change the database to your URL. * * @since 2.2.0 * @access private * * @see WP_SITEURL * * @param string $url URL to set the WordPress site location. * @return string The WordPress site URL. */ÿ¸ýÑ£€ÿÐýÑ£Û/** * Determines whether a site is the main site of the current network. * * @since 3.0.0 * @since 4.9.0 The `$network_id` parameter was added. * * @param int $site_id Optional. Site ID to test. Defaults to current site. * @param int $network_id Optional. Network ID of the network to check for. * Defaults to current network. * @return bool True if $site_id is the main site of the network, or if not * running Multisite. */::STATUS_UNSUBSCRIBED) ) { $currentUser = $this->wp->wpGetCurrentUser(); $this->unsubscribesTracker->track( (int)$oldSubscriber->getId(), StatisticsUnsubscribeEntity::SOURCE_ADMINISTRATOR, null, $currentUser->display_name // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps ); } if (isset($data['email']) && $this->isNewEmail($data['email'], $oldSubscriber)) { $this->verifyEmailIsUnique($data['email']); } $subscriber = $this->createOrUpdate($data, $oldSubscriber); $this->updateCustomFields($data, $subscriber); $this->updateTags($data, $subscriber); $segments = isset($data['segments']) ? $this->findSegments($data['segments']) : null; // check for status change if ( $oldStatus === SubscriberEntity::STATUS_SUBSCRIBED && $subscriber->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED ) { // make sure we unsubscribe the user from all segments $this->subscriberSegmentRepository->unsubscribeFromSegments($subscriber); } elseif ($segments !== null) { $this->subscriberSegmentRepository->resetSubscriptions($subscriber, $segments); } if (!empty($newSegments)) { $this->welcomeScheduler->scheduleSubscriberWelcomeNotification($subscriber->getId(), $newSegments); } // when global status changes to subscribed, fire subscribed hook for all subscribed segments if ( $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED && $oldStatus !== null // don't trigger for new subscribers (handled in subscriber segments repository) && $oldStatus !== SubscriberEntity::STATUS_SUBSCRIBED ) { $segments = $subscriber->getSubscriberSegments(); foreach ($segments as $subscriberSegment) { if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) { $this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment); } } } return $subscriber; } private function getNonDefaultSubscribedSegments(array $data): array { if (!isset($data['id']) || (int)$data['id'] <= 0) { return []; } $subscribedSegments = $this->subscriberSegmentRepository->getNonDefaultSubscribedSegments($data['id']); return array_filter(array_map(function(SubscriberSegmentEntity $subscriberSegment): int { $segment = $subscriberSegment->getSegment(); if (!$segment) { return 0; } return (int)$segment->getId(); }, $subscribedSegments)); } private function findSegments(array $segmentIds): array { return $this->segmentsRepository->findBy(['id' => $segmentIds]); } private function findNewSegments(array $data): array { $oldSegmentIds = []; if (isset($data['id']) && (int)$data['id'] > 0) { $subscribersSegments = $this->subscriberSegmentRepository->findBy(['subscriber' => $data['id']]); foreach ($subscribersSegments as $subscribersSegment) { $segment = $subscribersSegment->getSegment(); if (!$segment) { continue; } $oldSegmentIds[] = (int)$segment->getId(); } } return array_diff($data['segments'], $oldSegmentIds); } /** * @throws ValidationException */ public function createOrUpdate(array $data, ?SubscriberEntity $subscriber): SubscriberEntity { if (!$subscriber) { $subscriber = $this->createSubscriber(); if (!isset($data['source'])) $data['source'] = Source::ADMINISTRATOR; } if (isset($data['email'])) $subscriber->setEmail($data['email']); if (isset($data['first_name'])) $subscriber->setFirstName($data['first_name']); if (isset($data['last_name'])) $subscriber->setLastName($data['last_name']); if (isset($data['status'])) $subscriber->setStatus($data['status']); if (isset($data['source'])) $subscriber->setSource($data['source']); if (isset($data['wp_user_id'])) $subscriber->setWpUserId($data['wp_user_id']); if (isset($data['subscribed_ip'])) $subscriber->setSubscribedIp($data['subscribed_ip']); if (isset($data['confirmed_ip'])) $subscriber->setConfirmedIp($data['confirmed_ip']); if (isset($data['is_woocommerce_user'])) $subscriber->setIsWoocommerceUser((bool)$data['is_woocommerce_user']); $createdAt = isset($data['created_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['created_at']) : null; if ($createdAt) $subscriber->setCreatedAt($createdAt); $confirmedAt = isset($data['confirmed_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['confirmed_at']) : null; if ($confirmedAt) $subscriber->setConfirmedAt($confirmedAt); // wipe any unconfirmed data at this point $subscriber->setUnconfirmedData(null); // Validate the email (Saving group) + everything else (Default group) $subscriber->setValidationGroups(['Saving', 'Default']); try { $this->subscribersRepository->persist($subscriber); $this->subscribersRepository->flush(); } catch (ValidationException $exception) { // detach invalid entity because it can block another work with doctrine $this->subscribersRepository->detach($subscriber); throw $exception; } return $subscriber; } private function isNewEmail(string $email, ?SubscriberEntity $subscriber): bool { if ($subscriber && ($subscriber->getEmail() === $email)) return false; return true; } /** * @throws ConflictException */ private function verifyEmailIsUnique(string $email): void { $existingSubscriber = $this->subscribersRepository->findOneBy(['email' => $email]); if ($existingSubscriber) { // translators: %s is email address which already exists. $exceptionMessage = sprintf(__('A subscriber with E-mail "%s" already exists.', 'mailpoet'), $email); throw new ConflictException($exceptionMessage); } } private function createSubscriber(): SubscriberEntity { $subscriber = new SubscriberEntity(); $subscriber->setUnsubscribeToken($this->security->generateUnsubscribeTokenByEntity($subscriber)); $subscriber->setLinkToken(Security::generateHash(SubscriberEntity::LINK_TOKEN_LENGTH)); $subscriber->setStatus(!$this->settings->get('signup_confirmation.enabled') ? SubscriberEntity::STATUS_SUBSCRIBED : SubscriberEntity::STATUS_UNCONFIRMED); return $subscriber; } private function findSubscriber(array &$data): ?SubscriberEntity { $subscriber = null; if (isset($data['id']) && (int)$data['id'] > 0) { $subscriber = $this->subscribersRepository->findOneById(((int)$data['id'])); unset($data['id']); } if (!$subscriber && !empty($data['email'])) { $subscriber = $this->subscribersRepository->findOneBy(['email' => $data['email']]); if ($subscriber) { unset($data['email']); } } return $subscriber; } public function updateCustomFields(array $data, SubscriberEntity $subscriber): void { $customFieldsMap = []; foreach ($data as $key => $value) { if (strpos($key, 'cf_') === 0) { $customFieldsMap[(int)substr($key, 3)] = $value; } } if (empty($customFieldsMap)) { return; } $customFields = $this->customFieldsRepository->findBy(['id' => array_keys($customFieldsMap)]); foreach ($customFields as $customField) { $this->subscriberCustomFieldRepository->createOrUpdate($subscriber, $customField, $customFieldsMap[$customField->getId()]); } } private function updateTags(array $data, SubscriberEntity $subscriber): void { $removedTags = []; /** * $data['tags'] is either an array of arrays containing name, id etc. of the tag or an array of strings - the names * of the tag. * * Therefore we map it to be only an array of strings, containing the names of the tag. */ $tags = array_map( function($tag): string { if (is_array($tag)) { return array_key_exists('name', $tag) ? (string)$tag['name'] : ''; } return (string)$tag; }, (array)$data['tags'] ); foreach ($subscriber->getSubscriberTags() as $subscriberTag) { $tag = $subscriberTag->getTag(); if (!$tag || !in_array($tag->getName(), $tags, true)) { $subscriber->getSubscriberTags()->removeElement($subscriberTag); $removedTags[] = $subscriberTag; } } $newlyAddedTags = []; foreach ($tags as $tagName) { $tag = $this->tagRepository->createOrUpdate(['name' => $tagName]); $subscriberTag = $subscriber->getSubscriberTag($tag); if (!$subscriberTag) { $subscriberTag = new SubscriberTagEntity($tag, $subscriber); $subscriber->getSubscriberTags()->add($subscriberTag); $this->subscriberTagRepository->persist($subscriberTag); $newlyAddedTags[] = $subscriberTag; } } $this->subscriberTagRepository->flush(); foreach ($newlyAddedTags as $subscriberTag) { $this->wp->doAction('mailpoet_subscriber_tag_added', $subscriberTag); } foreach ($removedTags as $subscriberTag) { $this->wp->doAction('mailpoet_subscriber_tag_removed', $subscriberTag); } } }