← All changes
|
includes/core/rest-api/Controllers/class-funnel-controller.php
+102
-4
3.13.3
→
3.13.4
View file →
| @@ -749,14 +749,85 @@ | ||
| 749 | 749 | $funnel_data = get_post_meta($funnel_id, 'funnel_data', true) ? get_post_meta($funnel_id, 'funnel_data', true) : get_post_meta($funnel_id, '_funnel_data', true); |
| 750 | 750 | $migration->update_funnel_data($funnel_id, $funnel_data); |
| 751 | 751 | update_post_meta($funnel_id, 'wpfnls_is_newui_migrated', 'yes'); |
| 752 | 752 | } |
| 753 | + $this->reconcile_derived_step_meta($funnel_id); | |
| 753 | 754 | $response = $this->prepare_funnel_data_response($funnel_id); |
| 755 | + // What saveFunnel checks before it overwrites the graph. | |
| 756 | + $response['revision'] = self::get_funnel_revision($funnel_id); | |
| 754 | 757 | return $this->prepare_item_for_response($response, $request); |
| 755 | 758 | } |
| 756 | 759 | |
| 757 | 760 | |
| 758 | 761 | /** |
| 762 | + * Revision of a funnel's stored graph: a hash of `_funnel_data`. | |
| 763 | + * | |
| 764 | + * Every save posts the whole graph, so two editors (two tabs, two people, | |
| 765 | + * or a canvas left open while an import, MCP call or A/B swap rewrites the | |
| 766 | + * graph) would overwrite each other's steps. A hash rather than a counter | |
| 767 | + * changes whoever writes the meta, so no writer has to remember to bump it. | |
| 768 | + * | |
| 769 | + * @param int $funnel_id Funnel ID. | |
| 770 | + * | |
| 771 | + * @return string Revision. | |
| 772 | + * @since 3.13.8 | |
| 773 | + */ | |
| 774 | + public static function get_funnel_revision($funnel_id) | |
| 775 | + { | |
| 776 | + return md5(maybe_serialize(get_post_meta($funnel_id, '_funnel_data', true))); | |
| 777 | + } | |
| 778 | + | |
| 779 | + | |
| 780 | + /** | |
| 781 | + * Rebuild `_steps`, `_steps_order` and the first step from the stored graph | |
| 782 | + * when they have drifted from it. | |
| 783 | + * | |
| 784 | + * The classic canvas saved the graph every time it opened, which rebuilt | |
| 785 | + * these; the React canvas only saves after an edit. Funnels changed by an | |
| 786 | + * import, MCP or an A/B swap would otherwise keep a stale `_steps_order`, | |
| 787 | + * which gateways and A/B testing read on the front end. Writes only when | |
| 788 | + * something differs, so an unchanged funnel costs no write. | |
| 789 | + * | |
| 790 | + * @param int $funnel_id Funnel ID. | |
| 791 | + * | |
| 792 | + * @return bool Whether anything was rewritten. | |
| 793 | + * @since 3.13.8 | |
| 794 | + */ | |
| 795 | + private function reconcile_derived_step_meta($funnel_id) | |
| 796 | + { | |
| 797 | + if ('yes' !== get_post_meta($funnel_id, 'wpfnls_is_newui_migrated', true)) { | |
| 798 | + return false; | |
| 799 | + } | |
| 800 | + | |
| 801 | + $funnel_data = get_post_meta($funnel_id, '_funnel_data', true); | |
| 802 | + | |
| 803 | + // An empty or scratch funnel has nothing to derive from; leave it alone. | |
| 804 | + if (empty($funnel_data['drawflow']['Home']['data']) || !is_array($funnel_data['drawflow']['Home']['data'])) { | |
| 805 | + return false; | |
| 806 | + } | |
| 807 | + | |
| 808 | + $changed = false; | |
| 809 | + $steps = $this->get_steps($funnel_data); | |
| 810 | + | |
| 811 | + if ($steps != get_post_meta($funnel_id, '_steps', true)) { | |
| 812 | + update_post_meta($funnel_id, '_steps', $steps); | |
| 813 | + Wpfnl_functions::generate_first_step($funnel_id, $steps); | |
| 814 | + $changed = true; | |
| 815 | + } | |
| 816 | + | |
| 817 | + // As save_funnel_data stores it: empty entries dropped. | |
| 818 | + $steps_order = array_values(array_filter($this->get_steps_order($funnel_data), 'count')); | |
| 819 | + | |
| 820 | + if (count($steps_order) && $steps_order != get_post_meta($funnel_id, '_steps_order', true)) { | |
| 821 | + update_post_meta($funnel_id, '_steps_order', $steps_order); | |
| 822 | + $changed = true; | |
| 823 | + } | |
| 824 | + | |
| 825 | + return $changed; | |
| 826 | + } | |
| 827 | + | |
| 828 | + | |
| 829 | + /** | |
| 759 | 830 | * Retrieves visit/conversion figures for a funnel's steps plus funnel totals. |
| 760 | 831 | * |
| 761 | 832 | * Deliberately kept out of get_funnel_data() so that opening a funnel on the |
| 762 | 833 | * canvas never blocks on analytics. On sites with large amounts of tracked |
| @@ -1180,8 +1251,10 @@ | ||
| 1180 | 1251 | } |
| 1181 | 1252 | } |
| 1182 | 1253 | |
| 1183 | 1254 | $_temp_step['should_assign_product'] = $should_assign_product; |
| 1255 | + // The Mail Mint automation linked to this step, so the canvas can open it. | |
| 1256 | + $_temp_step['mint_automation_id'] = (int) get_post_meta( $step_id, 'wpfnl_mint_automation_id', true ); | |
| 1184 | 1257 | |
| 1185 | 1258 | /** |
| 1186 | 1259 | * Fires to add Mail Mint automation data |
| 1187 | 1260 | * |
| @@ -1459,8 +1532,27 @@ | ||
| 1459 | 1532 | $funnel_data = []; |
| 1460 | 1533 | $_steps = []; |
| 1461 | 1534 | $response = ['success' => true, 'link' => home_url()]; |
| 1462 | 1535 | |
| 1536 | + /* | |
| 1537 | + * The revision the graph was read at (getFunnel / the last save). When | |
| 1538 | + * the stored graph has changed since, this save would drop whatever | |
| 1539 | + * changed it, so it is refused; the editor reloads and redoes its edit. | |
| 1540 | + * Without one (the classic canvas, older callers) nothing is checked. | |
| 1541 | + */ | |
| 1542 | + $base_revision = isset($request['revision']) ? sanitize_text_field($request['revision']) : ''; | |
| 1543 | + | |
| 1544 | + if ($funnel_id && '' !== $base_revision && self::get_funnel_revision($funnel_id) !== $base_revision) { | |
| 1545 | + return new WP_Error( | |
| 1546 | + 'wpfunnels_funnel_conflict', | |
| 1547 | + __('This funnel was changed somewhere else (another tab, another user or an import) since it was opened. The canvas now shows the latest version; please make your last change again.', 'wpfnl'), | |
| 1548 | + array( | |
| 1549 | + 'status' => 409, | |
| 1550 | + 'revision' => self::get_funnel_revision($funnel_id), | |
| 1551 | + ) | |
| 1552 | + ); | |
| 1553 | + } | |
| 1554 | + | |
| 1463 | 1555 | if ($funnel_json) { |
| 1464 | 1556 | $funnel_data = $funnel_json; |
| 1465 | 1557 | $steps = $funnel_data['drawflow']['Home']['data']; |
| 1466 | 1558 | |
| @@ -1562,8 +1654,11 @@ | ||
| 1562 | 1654 | Wpfnl_functions::generate_first_step($funnel_id); |
| 1563 | 1655 | |
| 1564 | 1656 | do_action( 'wpfunnels_canvas_saved', absint( $funnel_id ) ); |
| 1565 | 1657 | |
| 1658 | + // Read after the hooks above, which may rewrite the graph themselves. | |
| 1659 | + $response['revision'] = self::get_funnel_revision($funnel_id); | |
| 1660 | + | |
| 1566 | 1661 | return rest_ensure_response($response); |
| 1567 | 1662 | } |
| 1568 | 1663 | |
| 1569 | 1664 | |
| @@ -1841,12 +1936,12 @@ | ||
| 1841 | 1936 | ); |
| 1842 | 1937 | return rest_ensure_response($response); |
| 1843 | 1938 | } |
| 1844 | 1939 | |
| 1845 | - $is_gbf = get_post_meta($funnel_id, 'is_global_funnel', true); | |
| 1940 | + $is_gbf = get_post_meta($funnel_id, 'is_global_funnel', true); | |
| 1941 | + $step_ids = array(); | |
| 1846 | 1942 | if ('yes' === $is_gbf) { |
| 1847 | 1943 | $start_condition = get_post_meta($funnel_id, 'global_funnel_start_condition', true); |
| 1848 | - $step_ids = array(); | |
| 1849 | 1944 | foreach ($steps as $step) { |
| 1850 | 1945 | if ($step['step_type'] == 'checkout') { |
| 1851 | 1946 | if (!empty($start_condition)) { |
| 1852 | 1947 | array_push($step_ids, $step['id']); |
| @@ -1863,11 +1958,14 @@ | ||
| 1863 | 1958 | } |
| 1864 | 1959 | } |
| 1865 | 1960 | } |
| 1866 | 1961 | } |
| 1962 | + // The steps whose Global Funnel rules are set — the ones the canvas tags | |
| 1963 | + // as having products. Fixed: the ids were collected and then dropped, | |
| 1964 | + // so every Global Funnel card read "Add Product". | |
| 1867 | 1965 | $response = array( |
| 1868 | - 'success' => false, | |
| 1869 | - 'data' => [] | |
| 1966 | + 'success' => 'yes' === $is_gbf, | |
| 1967 | + 'data' => array_map('absint', $step_ids), | |
| 1870 | 1968 | ); |
| 1871 | 1969 | return rest_ensure_response($response); |
| 1872 | 1970 | } |
| 1873 | 1971 | |