enable_rest_api(); $this->enable_wp_cli(); add_action( 'init', function () { Event_Manager::register_model_events('membership', __('Membership', 'wp-ultimo'), ['created', 'updated']); } ); add_action('wu_async_transfer_membership', [$this, 'async_transfer_membership'], 10, 2); add_action('wu_async_delete_membership', [$this, 'async_delete_membership'], 10); /* * Transitions */ add_action('wu_transition_membership_status', [$this, 'mark_cancelled_date'], 10, 3); add_action('wu_transition_membership_status', [$this, 'transition_membership_status'], 10, 3); /* * Deal with delayed/schedule swaps */ add_action('wu_async_membership_swap', [$this, 'async_membership_swap'], 10); /* * Deal with pending sites creation */ add_action('wp_ajax_wu_publish_pending_site', [$this, 'publish_pending_site']); add_action('wp_ajax_wu_check_pending_site_created', [$this, 'check_pending_site_created']); add_action('wu_async_publish_pending_site', [$this, 'async_publish_pending_site'], 10); } /** * Processes a delayed site publish action. * * @since 2.0.11 */ public function publish_pending_site(): void { check_ajax_referer('wu_publish_pending_site'); ignore_user_abort(true); // Don't make the request block till we finish, if possible. if ( function_exists('fastcgi_finish_request') && version_compare(phpversion(), '7.0.16', '>=') ) { wp_send_json(['status' => 'creating-site']); fastcgi_finish_request(); } $membership_id = wu_request('membership_id'); $this->async_publish_pending_site($membership_id); exit; // Just exit the request } /** * Processes a delayed site publish action. * * @since 2.0.0 * * @param int $membership_id The membership id. * @return bool|\WP_Error */ public function async_publish_pending_site($membership_id) { $membership = wu_get_membership($membership_id); if ( ! $membership) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $status = $membership->publish_pending_site(); if (is_wp_error($status)) { wu_log_add('site-errors', $status, LogLevel::ERROR); } return $status; } /** * Processes a delayed site publish action. * * @since 2.0.11 */ public function check_pending_site_created() { $membership_id = wu_request('membership_hash'); $membership = wu_get_membership_by_hash($membership_id); if ( ! $membership) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $pending_site = $membership->get_pending_site(); if ( ! $pending_site) { /** * We do not have a pending site, so we can assume the site was created. */ wp_send_json(['publish_status' => 'completed']); exit; } wp_send_json(['publish_status' => $pending_site->is_publishing() ? 'running' : 'stopped']); exit; } /** * Processes a membership swap. * * @since 2.0.0 * * @param int $membership_id The membership id. * @return bool|\WP_Error */ public function async_membership_swap($membership_id) { global $wpdb; $membership = wu_get_membership($membership_id); if ( ! $membership) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $scheduled_swap = $membership->get_scheduled_swap(); if (empty($scheduled_swap)) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $order = $scheduled_swap->order; $wpdb->query('START TRANSACTION'); try { $membership->swap($order); $status = $membership->save(); if (is_wp_error($status)) { $wpdb->query('ROLLBACK'); return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } } catch (\Throwable $exception) { $wpdb->query('ROLLBACK'); return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } /* * Clean up the membership swap order. */ $membership->delete_scheduled_swap(); $wpdb->query('COMMIT'); return true; } /** * Watches the change in payment status to take action when needed. * * @todo Publishing sites should be done in async. * * @since 2.0.0 * * @param string $old_status The old status of the membership. * @param string $new_status The new status of the membership. * @param integer $membership_id Payment ID. * @return void */ public function transition_membership_status($old_status, $new_status, $membership_id): void { $allowed_previous_status = [ Membership_Status::PENDING, Membership_Status::ON_HOLD, ]; if ( ! in_array($old_status, $allowed_previous_status, true)) { return; } $allowed_status = [ Membership_Status::ACTIVE, Membership_Status::TRIALING, ]; if ( ! in_array($new_status, $allowed_status, true)) { return; } /* * Create pending sites. */ $membership = wu_get_membership($membership_id); $status = $membership->publish_pending_site_async(); if (is_wp_error($status)) { wu_log_add('site-errors', $status, LogLevel::ERROR); } } /** * Mark the membership date of cancellation. * * @since 2.0.0 * * @param string $old_value Old status value. * @param string $new_value New status value. * @param int $item_id The membership id. * @return void */ public function mark_cancelled_date($old_value, $new_value, $item_id): void { if ('cancelled' === $new_value && $new_value !== $old_value) { $membership = wu_get_membership($item_id); $membership->set_date_cancellation(wu_get_current_time('mysql', true)); $membership->save(); } } /** * Transfer a membership from a user to another. * * @since 2.0.0 * * @param int $membership_id The ID of the membership being transferred. * @param int $target_customer_id The new owner. * @return mixed */ public function async_transfer_membership($membership_id, $target_customer_id) { global $wpdb; $membership = wu_get_membership($membership_id); $target_customer = wu_get_customer($target_customer_id); if ( ! $membership || ! $target_customer || absint($membership->get_customer_id()) === absint($target_customer->get_id())) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $wpdb->query('START TRANSACTION'); try { /* * Get Sites and move them over. */ $sites = wu_get_sites( [ 'meta_query' => [ 'membership_id' => [ 'key' => 'wu_membership_id', 'value' => $membership->get_id(), ], ], ] ); foreach ($sites as $site) { $site->set_customer_id($target_customer_id); $saved = $site->save(); if (is_wp_error($saved)) { $wpdb->query('ROLLBACK'); return $saved; } } /* * Change the membership */ $membership->set_customer_id($target_customer_id); $saved = $membership->save(); if (is_wp_error($saved)) { $wpdb->query('ROLLBACK'); return $saved; } } catch (\Throwable $e) { $wpdb->query('ROLLBACK'); return new \WP_Error('exception', $e->getMessage()); } $wpdb->query('COMMIT'); $membership->unlock(); return true; } /** * Delete a membership. * * @since 2.0.0 * * @param int $membership_id The ID of the membership being deleted. * @return mixed */ public function async_delete_membership($membership_id) { global $wpdb; $membership = wu_get_membership($membership_id); if ( ! $membership) { return new \WP_Error('error', __('An unexpected error happened.', 'wp-ultimo')); } $wpdb->query('START TRANSACTION'); try { /* * Get Sites and delete them. */ $sites = wu_get_sites( [ 'meta_query' => [ 'membership_id' => [ 'key' => 'wu_membership_id', 'value' => $membership->get_id(), ], ], ] ); foreach ($sites as $site) { $saved = $site->delete(); if (is_wp_error($saved)) { $wpdb->query('ROLLBACK'); return $saved; } } /* * Delete the membership */ $saved = $membership->delete(); if (is_wp_error($saved)) { $wpdb->query('ROLLBACK'); return $saved; } } catch (\Throwable $e) { $wpdb->query('ROLLBACK'); return new \WP_Error('exception', $e->getMessage()); } $wpdb->query('COMMIT'); return true; } }