PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.7.0
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.7.0
2.7.0 2.6.0 trunk 1.1.0 1.1.4 1.2.1 1.2.2 1.2.3 1.2.5 1.2.9 1.3.1 1.3.2 1.5.0 1.5.1 1.5.4 1.5.7 1.5.8 1.5.9 1.6.2 1.6.5 1.6.8 1.7.2 1.7.4 1.7.5 1.7.9 All 43 releases
← All changes | includes/funnel/steps/thankyou.php +258 -19 1.6.52.7.0 View file →
@@ -3,16 +3,28 @@
3 3 namespace Sellkit\Funnel\Steps;
4 4
5 5 defined( 'ABSPATH' ) || die();
6 6
7 +use Sellkit\Global_Checkout\Checkout as Global_Checkout;
8 +use Elementor\Plugin as Elementor;
9 +
7 10 /**
8 11 * Class Sellkit_Thankyou.
9 12 *
13 + * @SuppressWarnings(PHPMD.NPathComplexity)
10 14 * @since 1.1.0
11 15 */
12 16 class Thankyou extends Base_Step {
13 17
14 18 /**
19 + * Post ID.
20 + *
21 + * @var int
22 + * @since 2.3.0
23 + */
24 + private $post_id;
25 +
26 + /**
15 27 * Thankyou constructor.
16 28 *
17 29 * @since 1.1.0
18 30 */
@@ -45,40 +57,144 @@
45 57 }
46 58
47 59 $order_key = filter_input( INPUT_GET, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
48 60
49 - if ( ! empty( $order_key ) ) {
50 - $order_id = wc_get_order_id_by_order_key( $order_key );
51 - $order = wc_get_order( $order_id );
52 - $next_step = $order->get_meta( 'sellkit_funnel_next_step_data' );
53 - $funnel_id = get_post_meta( $order_id, 'sellkit_funnel_id', true );
61 + if ( empty( $order_key ) ) {
62 + return;
63 + }
54 64
55 - if ( empty( $next_step ) ) {
56 - return;
57 - }
65 + $order_id = wc_get_order_id_by_order_key( $order_key );
66 + $order = wc_get_order( $order_id );
58 67
59 - if ( empty( $funnel_id ) ) {
60 - $step_id = intval( $next_step['page_id'] );
61 - $step_data = get_post_meta( $step_id, 'step_data', true );
62 - $funnel_id = (int) $step_data['funnel_id'];
68 + if ( ! is_a( $order, 'WC_Order' ) ) {
69 + return;
70 + }
71 +
72 + // Always run WooCommerce's native cart-clearing logic on a valid order-received
73 + // request, regardless of whether SellKit has its own next-step data to follow.
74 + // This covers express-checkout flows that bypass SellKit's hidden fields and
75 + // protects against SellKit's global-checkout template_redirect exit preventing
76 + // WooCommerce's own priority-20 cart-clearing hook from running.
77 + if ( function_exists( 'wc_clear_cart_after_payment' ) ) {
78 + wc_clear_cart_after_payment();
79 + }
80 +
81 + $next_step = $order->get_meta( 'sellkit_funnel_next_step_data' );
82 + $funnel_id = (int) $order->get_meta( 'sellkit_funnel_id' );
83 +
84 + if ( empty( $next_step ) ) {
85 + return;
86 + }
87 +
88 + if ( empty( $funnel_id ) ) {
89 + $step_id = intval( $next_step['page_id'] );
90 + $step_data = get_post_meta( $step_id, 'step_data', true );
91 + $funnel_id = (int) $step_data['funnel_id'];
92 + }
93 +
94 + // For the new method( upsell popup ) we show thakyou page right away after checkout step.
95 + $thankyou_id = $this->find_funnel_thankyou_page( $funnel_id );
96 + $next_step_link = add_query_arg( [ 'order-key' => $order_key ], get_permalink( $thankyou_id ) );
97 +
98 + if ( class_exists( 'SitePress' ) ) {
99 + $thankyou_data = $this->wpml_compatibility_get_thank_you_page_data( $thankyou_id );
100 + $thankyou_id = isset( $thankyou_data['id'] ) ? $thankyou_data['id'] : $thankyou_id;
101 +
102 + if ( isset( $thankyou_data['lang'] ) ) {
103 + $next_step_link = $this->wpml_compatibility_get_next_step_link( $thankyou_id, $order_key, $thankyou_data['lang'] );
63 104 }
105 + }
64 106
65 - // For the new method( upsell popup ) we show thakyou page right away after checkout step.
66 - $thankyou_id = $this->find_funnel_thankyou_page( $funnel_id );
67 - $next_step_link = add_query_arg( [ 'order-key' => $order_key ], get_permalink( $thankyou_id ) );
68 - $last_price = $order->get_total() - $order->get_total_discount() - $order->get_total_tax();
107 + $last_price = $order->get_total();
69 108
70 - $this->contacts->add_total_spent( $last_price, $funnel_id );
109 + $this->contacts->add_total_spent( $last_price, $funnel_id );
71 110
72 - wp_safe_redirect( $next_step_link );
111 + $global_funnel_id = (int) get_option( Global_Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION );
112 + if ( $global_funnel_id === $funnel_id ) {
113 + $this->global_thankyou( $thankyou_id );
73 114 exit();
74 115 }
116 +
117 + wp_safe_redirect( $next_step_link );
118 + exit();
75 119 }
76 120
77 121 /**
122 + * Retrieves the translated thank you page data for WPML compatibility.
123 + *
124 + * This function checks the referring page to determine the current language
125 + * and retrieves the translated thank you page ID for that language.
126 + *
127 + * @since 2.3.2
128 + *
129 + * @param int $thankyou_id The ID of the default thank you page.
130 + *
131 + * @return array {
132 + * An associative array containing the translated thank you page ID and language code.
133 + *
134 + * @type int $id The translated thank you page ID or the original if no translation is found.
135 + * @type string $lang The language code for the thank you page.
136 + * }
137 + */
138 + private function wpml_compatibility_get_thank_you_page_data( $thankyou_id ) {
139 + $referrer_url = wp_get_referer();
140 + $previous_page_id = 0;
141 +
142 + $data = [
143 + 'id' => $thankyou_id,
144 + 'lang' => '',
145 + ];
146 +
147 + if ( $referrer_url ) {
148 + $previous_page_id = url_to_postid( $referrer_url );
149 + }
150 +
151 + if ( $previous_page_id ) {
152 + $order_language_details = apply_filters( 'wpml_post_language_details', null, $previous_page_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
153 + $current_language = $order_language_details['language_code'];
154 +
155 + if ( ! empty( $current_language ) ) {
156 + $thankyou_id_translated = apply_filters( 'wpml_object_id', $thankyou_id, 'sellkit_step', true, $current_language ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
157 +
158 + if ( ! empty( $thankyou_id_translated ) ) {
159 + $data['id'] = $thankyou_id_translated;
160 + $data['lang'] = $current_language;
161 +
162 + return $data;
163 + }
164 + }
165 +
166 + return $data;
167 + }
168 +
169 + return $data;
170 + }
171 +
172 + /**
173 + * Generates the next step link with WPML compatibility.
174 + *
175 + * This function generates the next step link by retrieving the translated permalink
176 + * for the thank you page in the specified language and appending the order key as a query argument.
177 + *
178 + * @since 2.3.2
179 + *
180 + * @param int $thankyou_id The ID of the thank you page.
181 + * @param string $order_key The order key used for generating the next step link.
182 + * @param string $lang The language code for the thank you page.
183 + *
184 + * @return string The next step link URL, including the translated permalink and order key.
185 + */
186 + private function wpml_compatibility_get_next_step_link( $thankyou_id, $order_key, $lang ) {
187 + $permalink = apply_filters( 'wpml_permalink', get_permalink( $thankyou_id ), $lang ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
188 + $next_step_link = add_query_arg( [ 'order-key' => $order_key ], $permalink );
189 +
190 + return $next_step_link;
191 + }
192 +
193 + /**
78 194 * Find funnel thankyou page using one of steps data.
79 195 *
80 - * @param array $funnel_id funnel id.
196 + * @param int $funnel_id funnel id.
81 197 * @since 1.6.2
82 198 */
83 199 private function find_funnel_thankyou_page( $funnel_id ) {
84 200 $funnel_data = get_post_meta( $funnel_id, 'nodes', true );
@@ -84,8 +200,10 @@
84 200 $funnel_data = get_post_meta( $funnel_id, 'nodes', true );
85 201 $id = 0;
86 202
87 203 foreach ( $funnel_data as $step ) {
204 + $step['type'] = (array) $step['type'];
205 +
88 206 if ( 'thankyou' === $step['type']['key'] ) {
89 207 $id = $step['page_id'];
90 208 }
91 209 }
@@ -90,6 +208,127 @@
90 208 }
91 209 }
92 210
93 211 return $id;
212 + }
213 +
214 + /**
215 + * Show global thankyou page.
216 + *
217 + * @param int $thankyou_id thankyou page id.
218 + * @since 1.8.6
219 + */
220 + private function global_thankyou( $thankyou_id ) {
221 + // Remove previous content.
222 + remove_all_filters( 'the_content' );
223 +
224 + add_filter( 'sellkit_global_thankyou', '__return_true' );
225 +
226 + if ( 'gutenberg' === sellkit()->page_builder() ) {
227 + $thankyou_id = get_post( $thankyou_id );
228 +
229 + $this->load_order_cart_details_block_frontend();
230 +
231 + $thankyou_post = get_post( $thankyou_id );
232 +
233 + global $post;
234 + $post = $thankyou_post; // phpcs:ignore:WordPress.WP.GlobalVariablesOverride.OverrideProhibited
235 + setup_postdata( $post );
236 +
237 + $content = do_blocks( $post->post_content );
238 +
239 + $content = apply_filters( 'the_content', $content ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core filter.
240 +
241 + add_filter( 'the_content', function() use ( $content ) {
242 + ob_Start();
243 +
244 + echo $content; // phpcs:ignore:WordPress.Security.EscapeOutput.OutputNotEscaped
245 +
246 + return ob_get_clean();
247 + }, 5 );
248 +
249 + sellkit()->load_files( [
250 + 'templates/default-canvas'
251 + ] );
252 + }
253 +
254 + if ( defined( 'ELEMENTOR_VERSION' ) && 'elementor' === sellkit()->page_builder() ) {
255 + // Add new content.
256 + add_filter( 'the_content', function() use ( $thankyou_id ) {
257 + ob_Start();
258 + $content = Elementor::instance()->frontend->get_builder_content_for_display( (int) $thankyou_id, true );
259 + echo do_shortcode( $content );
260 + return ob_get_clean();
261 + }, 5 );
262 +
263 + sellkit()->load_files( [
264 + 'templates/canvas'
265 + ] );
266 + }
267 + }
268 +
269 + /**
270 + * Load order cart details block on frontend.
271 + *
272 + * @since 2.3.0
273 + */
274 + public function load_order_cart_details_block_frontend() {
275 + global $post;
276 +
277 + if ( empty( $post->post_content ) ) {
278 + return;
279 + }
280 +
281 + $this->post_id = $post->ID;
282 +
283 + $block = 'blocks/order-cart-details';
284 +
285 + $block_data = explode( '/', $block );
286 + $block_name = $block_data[1];
287 +
288 + $class_name = str_replace( '-', ' ', $block_name );
289 + $class_name = str_replace( ' ', '_', ucwords( $class_name ) );
290 + $class_name = "Sellkit\blocks\Render\\{$class_name}";
291 + $class_path = 'block-editor/' . $block . '/index';
292 +
293 + sellkit()->load_files( [
294 + $class_path,
295 + ] );
296 +
297 + $new_class = new $class_name( $this->post_id );
298 + $this->register_inner_blocks_by_parent( $new_class );
299 + $new_class->register_block_meta();
300 + }
301 +
302 + /**
303 + * Register inner blocks by parent.
304 + *
305 + * @param object $parent_class Parent class name.
306 + * @since 2.3.0
307 + * @return void
308 + */
309 + private function register_inner_blocks_by_parent( $parent_class ) {
310 + if ( ! method_exists( $parent_class, 'has_inner_blocks' ) ) {
311 + return;
312 + }
313 +
314 + $inner_blocks = $parent_class->get_inner_block();
315 +
316 + sellkit()->load_files( $inner_blocks );
317 +
318 + foreach ( $inner_blocks as $key => $value ) {
319 + if ( isset( $this->inner_blocks[ "blocks/{$key}" ] ) ) {
320 + continue;
321 + }
322 +
323 + $inner_block_class = 'Sellkit\Blocks\Inner_Block\\' . str_replace( '-', '_', ucwords( $key ) );
324 +
325 + if ( ! class_exists( $inner_block_class ) ) {
326 + continue;
327 + }
328 +
329 + $inner_block_instance = new $inner_block_class( $this->post_id );
330 +
331 + $inner_block_instance->register_block_meta();
332 + }
94 333 }
95 334 }