Plugin Directory

Changeset 3789736


Ignore:
Timestamp:
11/07/2026 02:25:46 PM (less than one hour ago)
Author:
joegomega
Message:

Update to version 2.34.0 from GitHub

Location:
mega-ai
Files:
12 edited
1 copied

Legend:

Unmodified
Added
Removed
  • mega-ai/tags/2.34.0/builder-css.php

    r3786786 r3789736  
    1515 *   CSS loading mode itself and rewrites that one file. Never call
    1616 *   `Assets_Files::regenerate_css_files()`: it rebuilds every post on the site.
     17 *   Bricks skips any element CSS it already generated in the same request
     18 *   (`Assets::$unique_inline_css`), and an undo's `wp_restore_post_revision` runs Bricks' own
     19 *   `save_post` first, so the rebuild after it wrote only the element-type defaults (an
     20 *   unstyled page). The tracking is reset first, as Bricks does for its own bulk rebuild.
    1721 * - Beaver keeps `uploads/bb-plugin/cache/<id>-layout*.css|js` and rebuilds a missing file on the
    1822 *   next view. `FLBuilderModel::delete_all_asset_cache(<id>)` unlinks only that post's files.
    … …  
    2428final class MegaBuilderCss {
    2529    const BRICKS_FILES_CLASS = 'Bricks\Assets_Files';
     30    const BRICKS_ASSETS_CLASS = 'Bricks\Assets';
    2631    const BEAVER_MODEL_CLASS = 'FLBuilderModel';
    2732
    … …  
    3742    }
    3843
     44    /** True when the post has a Bricks or Beaver Builder layout whose generated CSS it needs. */
     45    public static function has_layout($post_id) {
     46        $post_id = is_numeric($post_id) ? (int) $post_id : 0;
     47        if ($post_id <= 0) {
     48            return false;
     49        }
     50        $bricks = \get_post_meta($post_id, '_bricks_page_content_2', true);
     51        if (is_array($bricks) && $bricks !== array() && (string) \get_post_meta($post_id, '_bricks_editor_mode', true) !== 'wordpress') {
     52            return true;
     53        }
     54        return !empty(\get_post_meta($post_id, '_fl_builder_data', true)) || !empty(\get_post_meta($post_id, '_fl_builder_draft', true));
     55    }
     56
    3957    private static function bricks($post_id) {
    4058        try {
    … …  
    4664                return false;
    4765            }
     66            self::reset_bricks_duplicates();
    4867            $file = call_user_func(array(self::BRICKS_FILES_CLASS, 'generate_post_css_file'), $post_id, 'content', $elements);
    4968            return is_string($file) && $file !== '';
    5069        } catch (\Throwable $e) {
    5170            return false;
     71        }
     72    }
     73
     74    /**
     75     * Forget the element CSS Bricks generated earlier in this request, so the rebuild emits every
     76     * element's rules again. `Assets::reset_duplication_tracking()` (Bricks 2.0.1+) clears all of it;
     77     * older Bricks keeps the list in `Assets::$unique_inline_css` (1.8+).
     78     */
     79    private static function reset_bricks_duplicates() {
     80        $assets = self::BRICKS_ASSETS_CLASS;
     81        if (!class_exists($assets)) {
     82            return;
     83        }
     84        // A failed reset must not skip the rebuild that follows it.
     85        try {
     86            if (method_exists($assets, 'reset_duplication_tracking')) {
     87                call_user_func(array($assets, 'reset_duplication_tracking'));
     88            } elseif (property_exists($assets, 'unique_inline_css')) {
     89                $assets::$unique_inline_css = array();
     90            }
     91        } catch (\Throwable $e) {
    5292        }
    5393    }
  • mega-ai/tags/2.34.0/editor-blocks.php

    r3786786 r3789736  
    14001400
    14011401    /**
     1402     * Two decoded JSON values are the same: objects key by key in any order, lists in order. A
     1403     * client may re-encode an element with its object keys sorted (the mega CLI does), and Beaver
     1404     * stores maps such as a module's empty `connections` in its own order, so a value sent back
     1405     * unchanged must not count as an edit.
     1406     */
     1407    private static function same_value($a, $b) {
     1408        if (!is_array($a) || !is_array($b)) return $a === $b;
     1409        if (count($a) !== count($b) || self::is_list($a) !== self::is_list($b)) return false;
     1410        foreach ($a as $k => $v) {
     1411            if (!array_key_exists($k, $b) || !self::same_value($v, $b[$k])) return false;
     1412        }
     1413        return true;
     1414    }
     1415
     1416    /** A cleaned settings object with the stored keys first, in their stored order, then new keys. */
     1417    private static function in_stored_order($out, $norm) {
     1418        if (!is_array($norm) || $norm === array() || self::is_list($norm) || self::is_list($out)) return $out;
     1419        $ordered = array();
     1420        foreach (array_keys($norm) as $k) {
     1421            if (array_key_exists($k, $out)) $ordered[$k] = $out[$k];
     1422        }
     1423        foreach ($out as $k => $v) {
     1424            if (!array_key_exists($k, $ordered)) $ordered[$k] = $v;
     1425        }
     1426        return $ordered;
     1427    }
     1428
     1429    /**
    14021430     * Settings made safe: a value equal to what the node already stores ($orig, compared through
    14031431     * its normalized form $norm) is kept exactly as stored; anything else is checked. Refused keys
    … …  
    14081436     */
    14091437    private static function clean_node_settings($kind, $value, $orig, $norm, &$sanitized, $key = '') {
    1410         if ($norm !== null && $value === $norm) return $orig;
     1438        if ($norm !== null && self::same_value($value, $norm)) return $orig;
    14111439        if (is_array($value)) {
    14121440            $out = array();
    … …  
    14151443                $child_orig = $child_norm === null ? null : self::node_field($orig, (string) $k);
    14161444                if (is_array($orig) && $child_norm !== null && array_key_exists($k, $orig)) $child_orig = $orig[$k];
    1417                 $unchanged = $child_norm !== null && $v === $child_norm;
     1445                $unchanged = $child_norm !== null && self::same_value($v, $child_norm);
    14181446                if (!$unchanged && in_array((string) $k, self::NODE_REFUSED_KEYS, true) && !($v === array() || $v === '' || $v === null)) {
    14191447                    return self::error('INVALID_VALUE', sprintf('Setting %s is not accepted on an added or edited element.', (string) $k), 400);
    … …  
    14261454                $out[$k] = $clean;
    14271455            }
    1428             return $out;
     1456            return self::in_stored_order($out, $norm);
    14291457        }
    14301458        if (!is_string($value)) return $value;
  • mega-ai/tags/2.34.0/mega.php

    r3788788 r3789736  
    33Plugin Name: MEGA AI
    44Description: Connect your WordPress website to MEGA for automated SEO optimization and content management.
    5 Version: 2.33.0
     5Version: 2.34.0
    66Author: MEGA
    77Author URI: https://gomega.ai
    … …  
    102102class MegaPlugin {
    103103    private static $namespace = 'mega';
    104     private static $version = '2.33.0';
     104    private static $version = '2.34.0';
    105105
    106106    // Editor (widget) endpoints.
    … …  
    40004000                $draft_doc = MegaEditorBlocks::parse('nodes', $draft[0], $kind);
    40014001                $same_bytes = !\is_wp_error($draft_doc) && MegaEditorBlocks::encode($draft_doc['source']) === MegaEditorBlocks::encode($doc['source']);
    4002                 if (!$same_bytes && !$this->beaver_draft_matches_published($raw, $draft[0])) {
    4003                     return $this->editor_error('BUILDER_DRAFT_PENDING', 'This page has unpublished Beaver Builder changes (its draft differs from the published layout). Publish or discard them in Beaver first; nothing was written.', 409);
     4002                if (!$same_bytes) {
     4003                    $draft_check = $this->beaver_draft_comparison($raw, $draft[0]);
     4004                    if (!$draft_check['same']) {
     4005                        return $this->editor_error('BUILDER_DRAFT_PENDING', 'This page has unpublished Beaver Builder changes (its draft differs from the published layout). Publish or discard them in Beaver first; nothing was written.', 409, $draft_check['details']);
     4006                    }
    40044007                }
    40054008                $writes['_fl_builder_draft'] = $encoded;
    … …  
    40184021     * never refresh it, so a long-lived process would compare a stale layout. Without Beaver loaded
    40194022     * there is no way to ask, and the caller keeps refusing.
    4020      */
    4021     private function beaver_draft_matches_published($published_raw, $draft_raw) {
    4022         if (!\class_exists('FLBuilderModel') || !\is_callable(array('FLBuilderModel', 'clean_layout_data'))) {
    4023             return false;
    4024         }
    4025         $clean = static function ($raw) {
    4026             $data = \FLBuilderModel::clean_layout_data(\maybe_unserialize($raw));
    4027             if (\class_exists('FLBuilderSettingsCompat') && \is_callable(array('FLBuilderSettingsCompat', 'filter_layout_data'))) {
    4028                 $data = \FLBuilderSettingsCompat::filter_layout_data($data);
    4029             }
    4030             return $data;
     4023     *
     4024     * Returns array('same' => bool, 'details' => array) where the details say why a refusal was made
     4025     * without any page content: whether Beaver was loaded, both rows' byte length and sha256, and up
     4026     * to 10 nodes that differ with what differs (only in one, type, parent, position, or the names of
     4027     * up to 5 settings).
     4028     */
     4029    private function beaver_draft_comparison($published_raw, $draft_raw) {
     4030        $details = array(
     4031            'beaver_loaded' => \class_exists('FLBuilderModel') && \is_callable(array('FLBuilderModel', 'clean_layout_data')),
     4032            'published_bytes' => strlen((string) $published_raw),
     4033            'draft_bytes' => strlen((string) $draft_raw),
     4034            'published_sha256' => hash('sha256', (string) $published_raw),
     4035            'draft_sha256' => hash('sha256', (string) $draft_raw),
     4036            'clean_failed' => false,
     4037        );
     4038        $published = \maybe_unserialize($published_raw);
     4039        $draft = \maybe_unserialize($draft_raw);
     4040        $same = false;
     4041        if ($details['beaver_loaded']) {
     4042            $clean = static function ($data) {
     4043                $data = \FLBuilderModel::clean_layout_data($data);
     4044                if (\class_exists('FLBuilderSettingsCompat') && \is_callable(array('FLBuilderSettingsCompat', 'filter_layout_data'))) {
     4045                    $data = \FLBuilderSettingsCompat::filter_layout_data($data);
     4046                }
     4047                return $data;
     4048            };
     4049            try {
     4050                $published = $clean($published);
     4051                $draft = $clean($draft);
     4052                $same = !empty($published) && serialize($published) === serialize($draft);
     4053            } catch (\Throwable $e) {
     4054                $details['clean_failed'] = true;
     4055            }
     4056        }
     4057        if (!$same) {
     4058            $nodes = self::beaver_node_differences($published, $draft);
     4059            $details['differing_node_count'] = count($nodes);
     4060            $details['differing_nodes'] = array_slice($nodes, 0, 10);
     4061        }
     4062        return array('same' => $same, 'details' => $details);
     4063    }
     4064
     4065    /** "<node id>: <what differs>" for each node that is not the same in both Beaver layouts. */
     4066    private static function beaver_node_differences($published, $draft) {
     4067        $as_map = static function ($layout) {
     4068            $out = array();
     4069            if (is_array($layout) || is_object($layout)) {
     4070                foreach ((array) $layout as $id => $node) {
     4071                    $out[(string) $id] = is_object($node) ? (array) $node : (is_array($node) ? $node : array());
     4072                }
     4073            }
     4074            return $out;
    40314075        };
    4032         try {
    4033             $published = $clean($published_raw);
    4034             $draft = $clean($draft_raw);
    4035         } catch (\Throwable $e) {
    4036             return false;
    4037         }
    4038         return !empty($published) && serialize($published) === serialize($draft);
     4076        $a = $as_map($published);
     4077        $b = $as_map($draft);
     4078        $out = array();
     4079        foreach (array_unique(array_merge(array_keys($a), array_keys($b))) as $id) {
     4080            if (!isset($b[$id])) {
     4081                $out[] = $id . ': published only';
     4082                continue;
     4083            }
     4084            if (!isset($a[$id])) {
     4085                $out[] = $id . ': draft only';
     4086                continue;
     4087            }
     4088            $what = array();
     4089            foreach (array('type', 'parent', 'position') as $field) {
     4090                $x = isset($a[$id][$field]) ? $a[$id][$field] : null;
     4091                $y = isset($b[$id][$field]) ? $b[$id][$field] : null;
     4092                if ($x !== $y) $what[] = $field;
     4093            }
     4094            $sa = isset($a[$id]['settings']) ? (array) $a[$id]['settings'] : array();
     4095            $sb = isset($b[$id]['settings']) ? (array) $b[$id]['settings'] : array();
     4096            $keys = array();
     4097            foreach (array_unique(array_merge(array_keys($sa), array_keys($sb))) as $key) {
     4098                $x = array_key_exists($key, $sa) ? serialize($sa[$key]) : null;
     4099                $y = array_key_exists($key, $sb) ? serialize($sb[$key]) : null;
     4100                if ($x !== $y) $keys[] = (string) $key;
     4101            }
     4102            if ($keys !== array()) $what[] = 'settings ' . implode(',', array_slice($keys, 0, 5)) . (count($keys) > 5 ? ',...' : '');
     4103            $rest_a = $a[$id];
     4104            $rest_b = $b[$id];
     4105            unset($rest_a['type'], $rest_a['parent'], $rest_a['position'], $rest_a['settings'], $rest_b['type'], $rest_b['parent'], $rest_b['position'], $rest_b['settings']);
     4106            if (serialize($rest_a) !== serialize($rest_b)) $what[] = 'other fields';
     4107            if ($what !== array()) $out[] = $id . ': ' . implode('; ', $what);
     4108        }
     4109        return $out;
    40394110    }
    40404111
  • mega-ai/tags/2.34.0/page-duplicate.php

    r3788711 r3789736  
    110110        $copied = self::copy_meta((int) $source_id, (int) $copy_id);
    111111        self::copy_terms($source, (int) $copy_id);
    112         if (MegaSandbox::is_elementor_post($source_id)) {
     112        if (MegaSandbox::is_elementor_post($source_id) || (MegaModules::loaded('builder-css.php') && MegaBuilderCss::has_layout((int) $copy_id))) {
    113113            MegaPlugin::regenerate_post_css_static((int) $copy_id);
    114114        }
  • mega-ai/tags/2.34.0/readme.txt

    r3788788 r3789736  
    44Requires at least: 4.4
    55Tested up to: 6.8
    6 Stable tag: 2.33.0
     6Stable tag: 2.34.0
    77Requires PHP: 7.2
    88License: GPLv2 or later
    … …  
    149149
    150150== Changelog ==
     151= 2.34.0 =
     152* Fix: after an undo on a Bricks page, its generated CSS file again holds every element's styles. Bricks' own save, run by the revision restore, had marked them generated, so the rebuild wrote only the element defaults and the page lost its styling.
     153* Fix: a private test copy or draft duplicate of a Bricks or Beaver Builder page gets its own generated CSS, so it renders like the original.
     154* Fix: an edited Bricks element or Beaver Builder node keeps a setting sent back unchanged in any key order (such as a Beaver module's empty `connections`) instead of refusing it, and keeps the stored setting order.
     155* Improvement: the `BUILDER_DRAFT_PENDING` refusal reports why the Beaver draft differs (Beaver loaded or not, both layouts' size and hash, and the first differing nodes and fields), without page content.
     156
    151157= 2.33.0 =
    152158* Feature: `POST /mega/v1/cache/flush` also clears the Kinsta server cache and CDN, the Sucuri firewall cache (when the site saved its Sucuri API key), Cache Enabler, Nginx Helper, Hummingbird, WP-Optimize, the Pantheon edge and Endurance Page Cache. Each uses only that host's or plugin's own purge call, runs only when present, and cannot stop the others if it fails.
  • mega-ai/tags/2.34.0/sandbox.php

    r3788626 r3789736  
    101101        self::copy_meta((int) $source_id, (int) $sandbox_id);
    102102        self::copy_terms($source, (int) $sandbox_id);
    103         if (self::is_elementor_post($source_id)) {
    104             // Derived caches were not copied; build this copy's own CSS so the baseline preview renders.
     103        // Derived caches were not copied; build this copy's own CSS so the baseline preview renders.
     104        // Bricks writes a post's CSS file only on its own save, which ran before the layout was copied.
     105        if (self::is_elementor_post($source_id) || (MegaModules::loaded('builder-css.php') && MegaBuilderCss::has_layout((int) $sandbox_id))) {
    105106            MegaPlugin::regenerate_post_css_static((int) $sandbox_id);
    106107        }
  • mega-ai/trunk/builder-css.php

    r3786786 r3789736  
    1515 *   CSS loading mode itself and rewrites that one file. Never call
    1616 *   `Assets_Files::regenerate_css_files()`: it rebuilds every post on the site.
     17 *   Bricks skips any element CSS it already generated in the same request
     18 *   (`Assets::$unique_inline_css`), and an undo's `wp_restore_post_revision` runs Bricks' own
     19 *   `save_post` first, so the rebuild after it wrote only the element-type defaults (an
     20 *   unstyled page). The tracking is reset first, as Bricks does for its own bulk rebuild.
    1721 * - Beaver keeps `uploads/bb-plugin/cache/<id>-layout*.css|js` and rebuilds a missing file on the
    1822 *   next view. `FLBuilderModel::delete_all_asset_cache(<id>)` unlinks only that post's files.
    … …  
    2428final class MegaBuilderCss {
    2529    const BRICKS_FILES_CLASS = 'Bricks\Assets_Files';
     30    const BRICKS_ASSETS_CLASS = 'Bricks\Assets';
    2631    const BEAVER_MODEL_CLASS = 'FLBuilderModel';
    2732
    … …  
    3742    }
    3843
     44    /** True when the post has a Bricks or Beaver Builder layout whose generated CSS it needs. */
     45    public static function has_layout($post_id) {
     46        $post_id = is_numeric($post_id) ? (int) $post_id : 0;
     47        if ($post_id <= 0) {
     48            return false;
     49        }
     50        $bricks = \get_post_meta($post_id, '_bricks_page_content_2', true);
     51        if (is_array($bricks) && $bricks !== array() && (string) \get_post_meta($post_id, '_bricks_editor_mode', true) !== 'wordpress') {
     52            return true;
     53        }
     54        return !empty(\get_post_meta($post_id, '_fl_builder_data', true)) || !empty(\get_post_meta($post_id, '_fl_builder_draft', true));
     55    }
     56
    3957    private static function bricks($post_id) {
    4058        try {
    … …  
    4664                return false;
    4765            }
     66            self::reset_bricks_duplicates();
    4867            $file = call_user_func(array(self::BRICKS_FILES_CLASS, 'generate_post_css_file'), $post_id, 'content', $elements);
    4968            return is_string($file) && $file !== '';
    5069        } catch (\Throwable $e) {
    5170            return false;
     71        }
     72    }
     73
     74    /**
     75     * Forget the element CSS Bricks generated earlier in this request, so the rebuild emits every
     76     * element's rules again. `Assets::reset_duplication_tracking()` (Bricks 2.0.1+) clears all of it;
     77     * older Bricks keeps the list in `Assets::$unique_inline_css` (1.8+).
     78     */
     79    private static function reset_bricks_duplicates() {
     80        $assets = self::BRICKS_ASSETS_CLASS;
     81        if (!class_exists($assets)) {
     82            return;
     83        }
     84        // A failed reset must not skip the rebuild that follows it.
     85        try {
     86            if (method_exists($assets, 'reset_duplication_tracking')) {
     87                call_user_func(array($assets, 'reset_duplication_tracking'));
     88            } elseif (property_exists($assets, 'unique_inline_css')) {
     89                $assets::$unique_inline_css = array();
     90            }
     91        } catch (\Throwable $e) {
    5292        }
    5393    }
  • mega-ai/trunk/editor-blocks.php

    r3786786 r3789736  
    14001400
    14011401    /**
     1402     * Two decoded JSON values are the same: objects key by key in any order, lists in order. A
     1403     * client may re-encode an element with its object keys sorted (the mega CLI does), and Beaver
     1404     * stores maps such as a module's empty `connections` in its own order, so a value sent back
     1405     * unchanged must not count as an edit.
     1406     */
     1407    private static function same_value($a, $b) {
     1408        if (!is_array($a) || !is_array($b)) return $a === $b;
     1409        if (count($a) !== count($b) || self::is_list($a) !== self::is_list($b)) return false;
     1410        foreach ($a as $k => $v) {
     1411            if (!array_key_exists($k, $b) || !self::same_value($v, $b[$k])) return false;
     1412        }
     1413        return true;
     1414    }
     1415
     1416    /** A cleaned settings object with the stored keys first, in their stored order, then new keys. */
     1417    private static function in_stored_order($out, $norm) {
     1418        if (!is_array($norm) || $norm === array() || self::is_list($norm) || self::is_list($out)) return $out;
     1419        $ordered = array();
     1420        foreach (array_keys($norm) as $k) {
     1421            if (array_key_exists($k, $out)) $ordered[$k] = $out[$k];
     1422        }
     1423        foreach ($out as $k => $v) {
     1424            if (!array_key_exists($k, $ordered)) $ordered[$k] = $v;
     1425        }
     1426        return $ordered;
     1427    }
     1428
     1429    /**
    14021430     * Settings made safe: a value equal to what the node already stores ($orig, compared through
    14031431     * its normalized form $norm) is kept exactly as stored; anything else is checked. Refused keys
    … …  
    14081436     */
    14091437    private static function clean_node_settings($kind, $value, $orig, $norm, &$sanitized, $key = '') {
    1410         if ($norm !== null && $value === $norm) return $orig;
     1438        if ($norm !== null && self::same_value($value, $norm)) return $orig;
    14111439        if (is_array($value)) {
    14121440            $out = array();
    … …  
    14151443                $child_orig = $child_norm === null ? null : self::node_field($orig, (string) $k);
    14161444                if (is_array($orig) && $child_norm !== null && array_key_exists($k, $orig)) $child_orig = $orig[$k];
    1417                 $unchanged = $child_norm !== null && $v === $child_norm;
     1445                $unchanged = $child_norm !== null && self::same_value($v, $child_norm);
    14181446                if (!$unchanged && in_array((string) $k, self::NODE_REFUSED_KEYS, true) && !($v === array() || $v === '' || $v === null)) {
    14191447                    return self::error('INVALID_VALUE', sprintf('Setting %s is not accepted on an added or edited element.', (string) $k), 400);
    … …  
    14261454                $out[$k] = $clean;
    14271455            }
    1428             return $out;
     1456            return self::in_stored_order($out, $norm);
    14291457        }
    14301458        if (!is_string($value)) return $value;
  • mega-ai/trunk/mega.php

    r3788788 r3789736  
    33Plugin Name: MEGA AI
    44Description: Connect your WordPress website to MEGA for automated SEO optimization and content management.
    5 Version: 2.33.0
     5Version: 2.34.0
    66Author: MEGA
    77Author URI: https://gomega.ai
    … …  
    102102class MegaPlugin {
    103103    private static $namespace = 'mega';
    104     private static $version = '2.33.0';
     104    private static $version = '2.34.0';
    105105
    106106    // Editor (widget) endpoints.
    … …  
    40004000                $draft_doc = MegaEditorBlocks::parse('nodes', $draft[0], $kind);
    40014001                $same_bytes = !\is_wp_error($draft_doc) && MegaEditorBlocks::encode($draft_doc['source']) === MegaEditorBlocks::encode($doc['source']);
    4002                 if (!$same_bytes && !$this->beaver_draft_matches_published($raw, $draft[0])) {
    4003                     return $this->editor_error('BUILDER_DRAFT_PENDING', 'This page has unpublished Beaver Builder changes (its draft differs from the published layout). Publish or discard them in Beaver first; nothing was written.', 409);
     4002                if (!$same_bytes) {
     4003                    $draft_check = $this->beaver_draft_comparison($raw, $draft[0]);
     4004                    if (!$draft_check['same']) {
     4005                        return $this->editor_error('BUILDER_DRAFT_PENDING', 'This page has unpublished Beaver Builder changes (its draft differs from the published layout). Publish or discard them in Beaver first; nothing was written.', 409, $draft_check['details']);
     4006                    }
    40044007                }
    40054008                $writes['_fl_builder_draft'] = $encoded;
    … …  
    40184021     * never refresh it, so a long-lived process would compare a stale layout. Without Beaver loaded
    40194022     * there is no way to ask, and the caller keeps refusing.
    4020      */
    4021     private function beaver_draft_matches_published($published_raw, $draft_raw) {
    4022         if (!\class_exists('FLBuilderModel') || !\is_callable(array('FLBuilderModel', 'clean_layout_data'))) {
    4023             return false;
    4024         }
    4025         $clean = static function ($raw) {
    4026             $data = \FLBuilderModel::clean_layout_data(\maybe_unserialize($raw));
    4027             if (\class_exists('FLBuilderSettingsCompat') && \is_callable(array('FLBuilderSettingsCompat', 'filter_layout_data'))) {
    4028                 $data = \FLBuilderSettingsCompat::filter_layout_data($data);
    4029             }
    4030             return $data;
     4023     *
     4024     * Returns array('same' => bool, 'details' => array) where the details say why a refusal was made
     4025     * without any page content: whether Beaver was loaded, both rows' byte length and sha256, and up
     4026     * to 10 nodes that differ with what differs (only in one, type, parent, position, or the names of
     4027     * up to 5 settings).
     4028     */
     4029    private function beaver_draft_comparison($published_raw, $draft_raw) {
     4030        $details = array(
     4031            'beaver_loaded' => \class_exists('FLBuilderModel') && \is_callable(array('FLBuilderModel', 'clean_layout_data')),
     4032            'published_bytes' => strlen((string) $published_raw),
     4033            'draft_bytes' => strlen((string) $draft_raw),
     4034            'published_sha256' => hash('sha256', (string) $published_raw),
     4035            'draft_sha256' => hash('sha256', (string) $draft_raw),
     4036            'clean_failed' => false,
     4037        );
     4038        $published = \maybe_unserialize($published_raw);
     4039        $draft = \maybe_unserialize($draft_raw);
     4040        $same = false;
     4041        if ($details['beaver_loaded']) {
     4042            $clean = static function ($data) {
     4043                $data = \FLBuilderModel::clean_layout_data($data);
     4044                if (\class_exists('FLBuilderSettingsCompat') && \is_callable(array('FLBuilderSettingsCompat', 'filter_layout_data'))) {
     4045                    $data = \FLBuilderSettingsCompat::filter_layout_data($data);
     4046                }
     4047                return $data;
     4048            };
     4049            try {
     4050                $published = $clean($published);
     4051                $draft = $clean($draft);
     4052                $same = !empty($published) && serialize($published) === serialize($draft);
     4053            } catch (\Throwable $e) {
     4054                $details['clean_failed'] = true;
     4055            }
     4056        }
     4057        if (!$same) {
     4058            $nodes = self::beaver_node_differences($published, $draft);
     4059            $details['differing_node_count'] = count($nodes);
     4060            $details['differing_nodes'] = array_slice($nodes, 0, 10);
     4061        }
     4062        return array('same' => $same, 'details' => $details);
     4063    }
     4064
     4065    /** "<node id>: <what differs>" for each node that is not the same in both Beaver layouts. */
     4066    private static function beaver_node_differences($published, $draft) {
     4067        $as_map = static function ($layout) {
     4068            $out = array();
     4069            if (is_array($layout) || is_object($layout)) {
     4070                foreach ((array) $layout as $id => $node) {
     4071                    $out[(string) $id] = is_object($node) ? (array) $node : (is_array($node) ? $node : array());
     4072                }
     4073            }
     4074            return $out;
    40314075        };
    4032         try {
    4033             $published = $clean($published_raw);
    4034             $draft = $clean($draft_raw);
    4035         } catch (\Throwable $e) {
    4036             return false;
    4037         }
    4038         return !empty($published) && serialize($published) === serialize($draft);
     4076        $a = $as_map($published);
     4077        $b = $as_map($draft);
     4078        $out = array();
     4079        foreach (array_unique(array_merge(array_keys($a), array_keys($b))) as $id) {
     4080            if (!isset($b[$id])) {
     4081                $out[] = $id . ': published only';
     4082                continue;
     4083            }
     4084            if (!isset($a[$id])) {
     4085                $out[] = $id . ': draft only';
     4086                continue;
     4087            }
     4088            $what = array();
     4089            foreach (array('type', 'parent', 'position') as $field) {
     4090                $x = isset($a[$id][$field]) ? $a[$id][$field] : null;
     4091                $y = isset($b[$id][$field]) ? $b[$id][$field] : null;
     4092                if ($x !== $y) $what[] = $field;
     4093            }
     4094            $sa = isset($a[$id]['settings']) ? (array) $a[$id]['settings'] : array();
     4095            $sb = isset($b[$id]['settings']) ? (array) $b[$id]['settings'] : array();
     4096            $keys = array();
     4097            foreach (array_unique(array_merge(array_keys($sa), array_keys($sb))) as $key) {
     4098                $x = array_key_exists($key, $sa) ? serialize($sa[$key]) : null;
     4099                $y = array_key_exists($key, $sb) ? serialize($sb[$key]) : null;
     4100                if ($x !== $y) $keys[] = (string) $key;
     4101            }
     4102            if ($keys !== array()) $what[] = 'settings ' . implode(',', array_slice($keys, 0, 5)) . (count($keys) > 5 ? ',...' : '');
     4103            $rest_a = $a[$id];
     4104            $rest_b = $b[$id];
     4105            unset($rest_a['type'], $rest_a['parent'], $rest_a['position'], $rest_a['settings'], $rest_b['type'], $rest_b['parent'], $rest_b['position'], $rest_b['settings']);
     4106            if (serialize($rest_a) !== serialize($rest_b)) $what[] = 'other fields';
     4107            if ($what !== array()) $out[] = $id . ': ' . implode('; ', $what);
     4108        }
     4109        return $out;
    40394110    }
    40404111
  • mega-ai/trunk/page-duplicate.php

    r3788711 r3789736  
    110110        $copied = self::copy_meta((int) $source_id, (int) $copy_id);
    111111        self::copy_terms($source, (int) $copy_id);
    112         if (MegaSandbox::is_elementor_post($source_id)) {
     112        if (MegaSandbox::is_elementor_post($source_id) || (MegaModules::loaded('builder-css.php') && MegaBuilderCss::has_layout((int) $copy_id))) {
    113113            MegaPlugin::regenerate_post_css_static((int) $copy_id);
    114114        }
  • mega-ai/trunk/readme.txt

    r3788788 r3789736  
    44Requires at least: 4.4
    55Tested up to: 6.8
    6 Stable tag: 2.33.0
     6Stable tag: 2.34.0
    77Requires PHP: 7.2
    88License: GPLv2 or later
    … …  
    149149
    150150== Changelog ==
     151= 2.34.0 =
     152* Fix: after an undo on a Bricks page, its generated CSS file again holds every element's styles. Bricks' own save, run by the revision restore, had marked them generated, so the rebuild wrote only the element defaults and the page lost its styling.
     153* Fix: a private test copy or draft duplicate of a Bricks or Beaver Builder page gets its own generated CSS, so it renders like the original.
     154* Fix: an edited Bricks element or Beaver Builder node keeps a setting sent back unchanged in any key order (such as a Beaver module's empty `connections`) instead of refusing it, and keeps the stored setting order.
     155* Improvement: the `BUILDER_DRAFT_PENDING` refusal reports why the Beaver draft differs (Beaver loaded or not, both layouts' size and hash, and the first differing nodes and fields), without page content.
     156
    151157= 2.33.0 =
    152158* Feature: `POST /mega/v1/cache/flush` also clears the Kinsta server cache and CDN, the Sucuri firewall cache (when the site saved its Sucuri API key), Cache Enabler, Nginx Helper, Hummingbird, WP-Optimize, the Pantheon edge and Endurance Page Cache. Each uses only that host's or plugin's own purge call, runs only when present, and cannot stop the others if it fails.
  • mega-ai/trunk/sandbox.php

    r3788626 r3789736  
    101101        self::copy_meta((int) $source_id, (int) $sandbox_id);
    102102        self::copy_terms($source, (int) $sandbox_id);
    103         if (self::is_elementor_post($source_id)) {
    104             // Derived caches were not copied; build this copy's own CSS so the baseline preview renders.
     103        // Derived caches were not copied; build this copy's own CSS so the baseline preview renders.
     104        // Bricks writes a post's CSS file only on its own save, which ran before the layout was copied.
     105        if (self::is_elementor_post($source_id) || (MegaModules::loaded('builder-css.php') && MegaBuilderCss::has_layout((int) $sandbox_id))) {
    105106            MegaPlugin::regenerate_post_css_static((int) $sandbox_id);
    106107        }
Note: See TracChangeset for help on using the changeset viewer.