Plugin Directory

Changeset 3492699


Ignore:
Timestamp:
03/27/2026 01:30:37 PM (21 hours ago)
Author:
dreamtheme
Message:

Release 1.4.0

Location:
better-block-editor
Files:
10 added
2 deleted
49 edited
2 copied

Legend:

Unmodified
Added
Removed
  • better-block-editor/tags/1.4.0/Base/ModuleBase.php

    r3449829 r3492699  
    113113    public static function get_settings_order() {
    114114        return static::SETTINGS_ORDER;
     115    }
     116
     117    /**
     118     * {inheritdocs}
     119     */
     120    public static function get_tab() {
     121        return Settings::TAB_FEATURES;
    115122    }
    116123
     
    291298    protected function get_option( string $key = null, $default = null ) {
    292299        $option_name = Settings::build_module_settings_name( $this->get_identifier() );
    293 
    294         $options = get_option( $option_name, array() );
    295 
    296         if ( null === $key ) {
    297             return $options;
    298         }
    299 
    300         return $options[ $key ] ?? $default;
     300        return Settings::get_setting($option_name, $key, $default );
    301301    }
    302302
  • better-block-editor/tags/1.4.0/Core/BundledAssetsManager.php

    r3386474 r3492699  
    3333    private $plugin_dist;
    3434
    35     public function __construct( string $plugin_id, string $plugin_dist, string $plugin_dist_url ) {
     35    /**
     36     * @var array Dependencies for each bundle (optional, can be empty).
     37     * Format: array( 'bundle_key' => array( 'dependency_handle1', 'dependency_handle2' ) )
     38     */
     39    private $dependencies;
     40
     41    public function __construct( string $plugin_id, string $plugin_dist, string $plugin_dist_url, array $dependencies = array() ) {
    3642        $this->plugin_id       = $plugin_id;
    3743        $this->plugin_dist     = $plugin_dist;
    3844        $this->plugin_dist_url = $plugin_dist_url;
     45        $this->dependencies = $dependencies;
    3946    }
    4047
     
    4451     * @return void
    4552     */
    46     public function process_editor_assets() {
    47         $this->register_assets( 'editor' );
    48         $this->enqueue_assets( 'enqueue_block_editor_assets', 'editor' );
     53    public function process_editor_assets(): void {
     54        $this->register_assets( self::EDITOR_BUNDLE );
     55        $this->enqueue_assets( self::EDITOR_BUNDLE );
    4956    }
    5057
     
    5461     * @return void
    5562     */
    56     public function process_editor_content_assets() {
    57         $this->register_assets( 'editor-content' );
    58         $this->enqueue_assets( 'enqueue_block_assets', 'editor-content' );
     63    public function process_editor_content_assets(): void {
     64        $this->register_assets( self::EDITOR_CONTENT_BUNDLE );
     65        $this->enqueue_assets( self::EDITOR_CONTENT_BUNDLE );
    5966    }
    6067
     
    6471     * @return void
    6572     */
    66     public function process_view_assets() {
    67         $this->register_assets( 'view' );
    68         $this->enqueue_assets( 'wp_enqueue_scripts', 'view' );
     73    public function process_view_assets(): void {
     74        $this->register_assets( self::VIEW_BUNDLE );
     75        $this->enqueue_assets( self::VIEW_BUNDLE );
    6976    }
    7077
    7178    /**
    7279     * Add inline JS code just before bundle code (see wp_add_inline_script())
     80     * Before mode does not affect "defer" script attribute
    7381     *
    7482     * @param string $bundle_name Bundle name(key) to add code to (see self::*_BUNDLE)
     
    7785     * @return bool
    7886     */
    79     public function add_inline_js_before_bundle( $bundle_name, $js ) {
     87    public function add_inline_js_before_bundle( $bundle_name, $js ): bool {
    8088        return wp_add_inline_script(
    8189            $this->build_script_handle( $bundle_name ),
     
    8694
    8795    /**
    88      * Add inline JS code just after bundle code (see wp_add_inline_script())
    89      *
    90      * @param string $bundle_name Bundle name(key) to add code to (see self::*_BUNDLE)
     96     * Add inline JS code to footer
     97     * We use fake handler to non existing script to add inline code to footer
     98     * Bundle name is required to add code with appropriate hook (editor, editor-content, view)
     99     *
     100     * @param string $bundle_name Bundle name (see self::*_BUNDLE) to add code with appropriate hook
    91101     * @param string $js JS code to be added as inline script
    92102     *
    93103     * @return bool
    94104     */
    95     public function add_inline_js_after_bundle( $bundle_name, $js ) {
    96         return wp_add_inline_script(
    97             $this->build_script_handle( $bundle_name ),
    98             $js,
    99             'after'
    100         );
     105    public function add_inline_js_to_footer($bundle_name, $js ): bool {
     106       
     107        $handle = $this->build_script_handle( 'footer-inline' ) . '__handler';
     108
     109        // register only once
     110        if ( ! wp_script_is( $handle, 'registered' ) ) {
     111            wp_register_script(
     112                $handle,
     113                false, // no source file
     114                array(),
     115                false,
     116                array( 'in_footer' => true ) // it won't be deferred because we add inline script to it
     117            );
     118
     119            $action = $this->get_action_for_bundle( $bundle_name );
     120           
     121            if ( null === $action ) {
     122                return false;
     123            }
     124
     125            add_action(
     126                $action,
     127                function () use ( $handle ) {
     128                    wp_enqueue_script( $handle );
     129                }
     130            );
     131        }
     132
     133        return wp_add_inline_script( $handle, $js, 'before' );
     134    }
     135
     136    /**
     137     * Build a handle name for a given plugin ID, bundle key and type (script or style).
     138     *
     139     * @param string $plugin_id   Plugin ID.
     140     * @param string $bundle_key  Bundle key (one of 'editor', 'editor-content', 'view').
     141     * @param string $type        Type of handle ('script' or 'style').
     142     *
     143     * @return string Handle name.
     144     */
     145    public static function build_handle( $plugin_id, $bundle_key, $type ): string {
     146        return $plugin_id . '__bundle__' . $bundle_key . '-' . $type;
     147    }
     148
     149    /**
     150     * Get the appropriate action hook for enqueuing assets based on the bundle name.
     151     *
     152     * @param string $bundle_name Bundle name(key) to get action for (see self::*_BUNDLE)
     153     *
     154     * @return string|null Action hook name or null if bundle name is invalid.
     155     */
     156    private function get_action_for_bundle( $bundle_name ): ?string {
     157        $map = array(
     158            self::EDITOR_BUNDLE => 'enqueue_block_editor_assets',
     159            self::EDITOR_CONTENT_BUNDLE => 'enqueue_block_assets',
     160            self::VIEW_BUNDLE => 'wp_enqueue_scripts',
     161        );
     162       
     163        return array_key_exists( $bundle_name, $map ) ? $map[ $bundle_name ] : null;
    101164    }
    102165
     
    105168     *
    106169     * @param string $key Bundle key (one of 'editor', 'editor-content', 'view').
    107      * @param array  $script_register_options Optional script registration options.
    108      *
    109      * @return void
    110      */
    111     private function register_assets( $key, $script_register_options = array() ) {
     170     *
     171     * @return void
     172     */
     173    private function register_assets( $key ): void {
    112174        if ( ! file_exists( $this->get_asset_filename( $key ) ) ) {
    113175            return;
     
    116178        $asset_file = require $this->get_asset_filename( $key );
    117179
    118         // script
    119         $default_options = array(
    120             'strategy'  => 'defer',
    121             'in_footer' => false,
    122         );
    123180        // it's safe to return here as css is added only using js import construction
    124181        if ( ! file_exists( $this->plugin_dist . self::BUNDLE_DIR . $key . '.js' ) ) {
     
    126183        }
    127184
    128         $res = wp_register_script(
     185        // merge "native" dependencies from asset file with any additional dependencies provided added manually
     186        $script_dependencies = array_merge( $asset_file['dependencies'], (array)($this->dependencies[ $key ] ?? array()) );
     187
     188
     189        wp_register_script(
    129190            $this->build_script_handle( $key ),
    130191            $this->plugin_dist_url . self::BUNDLE_DIR . $key . '.js',
    131             $asset_file['dependencies'],
     192            $script_dependencies,
    132193            $asset_file['version'],
    133             array_merge( $default_options, $script_register_options )
     194            // it's important to use defer for all scripts in the bundle
     195            // otherwise the order of execution will be broken and it may cause errors
     196            // we add to header as it's common practice
     197            array(
     198                'strategy'  => 'defer',
     199                'in_footer' => false,
     200            )
    134201        );
    135202
     
    148215
    149216    /**
    150      * Enqueue registered script and style assets for a given action and bundle key.
    151      *
    152      * @param string $action WordPress action hook.
    153      * @param string $key    Bundle key (one of 'editor', 'editor-content', 'view').
    154      *
    155      * @return void
    156      */
    157     private function enqueue_assets( $action, $key ) {
     217     * Enqueue registered script and style assets for a given supported bundle key.
     218     *
     219     * @param string $key   Bundle key (one of 'editor', 'editor-content', 'view').
     220     *
     221     * @return void
     222     */
     223    private function enqueue_assets( $key ): void {
    158224        $script_handle = $this->build_script_handle( $key );
    159225        $style_handle  = $this->build_style_handle( $key );
     226
     227        $action = $this->get_action_for_bundle( $key );
     228
     229        // if action is null it means that bundle key is invalid and we should not enqueue assets
     230        if ( null === $action ) {
     231            return;
     232        }
    160233
    161234        add_action(
     
    175248     * @return string Path to the asset metadata file.
    176249     */
    177     private function get_asset_filename( $key ) {
     250    private function get_asset_filename( $key ): string {
    178251        return $this->plugin_dist . self::BUNDLE_DIR . $key . '.asset.php';
    179252    }
     
    186259     * @return string Script handle name.
    187260     */
    188     private function build_script_handle( $key ) {
    189         return $this->plugin_id . '__' . 'bundle' . '__' . $key . '-script';
     261    public function build_script_handle( $key ): string {
     262        return self::build_handle( $this->plugin_id, $key, 'script' );
    190263    }
    191264
     
    197270     * @return string Style handle name.
    198271     */
    199     private function build_style_handle( $key ) {
    200         return $this->plugin_id . '__' . 'bundle' . '__' . $key . '-style';
     272    private function build_style_handle( $key ): string {
     273        return self::build_handle( $this->plugin_id, $key, 'style' );
    201274    }
    202275}
  • better-block-editor/tags/1.4.0/Core/ModulesManager.php

    r3449829 r3492699  
    131131                'label'          => $classname::get_label(),
    132132                'description'    => $classname::get_description(),
     133                'tab'            => $classname::get_tab(),
    133134                'settings_order' => $classname::get_settings_order(),
    134135                'enabled'        => self::is_module_enabled( $classname ),
    135                 'is_freemium'    => is_a( $classname, 'BetterBlockEditor\Base\ModuleInterface', true ),
     136                'is_freemium'    => !is_a( $classname, 'BbeProKit\Base\ModuleBasePro', true ),
    136137                'classname'      => $classname,
    137138                'active'         => isset( $this->modules[ $classname::get_identifier() ] ),
  • better-block-editor/tags/1.4.0/Core/Settings.php

    r3473824 r3492699  
    1616class Settings {
    1717
    18     protected static $allowed_breakpoint_units = array( 'px', 'em', 'rem', 'vw', 'vh' );
     18    public const TAB_FEATURES    = 'features';
     19    public const TAB_BLOCKS      = 'blocks';
     20    public const TAB_DESIGN      = 'design';
     21    public const TAB_BREAKPOINTS = 'breakpoints';
     22
     23    protected static $allowed_breakpoint_units = array( 'px', 'rem' );
    1924
    2025    // WP permission to open the Settings Page
     
    2631    const TEMPLATES_FOLDER_NAME = WPBBE_DIR . 'admin/templates/settings/';
    2732
     33    protected static $pro_separator_added = false;
    2834
    2935    /**
     
    6773
    6874        foreach ( $modules_data as $module_data ) {
    69             self::add_module_enable_checkbox(
    70                 $module_data['identifier'],
    71                 $module_data['title'],
     75            self::add_module_enable_checkbox( $module_data );
     76            // add module settings
     77            $classname = $module_data['classname'] ?? null;
     78            if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
     79                foreach ( $classname::get_settings() as $key => $field ) {
     80                    self::add_module_settigns( $module_data, $key, $field );
     81                }
     82            }
     83        }
     84
     85        self::add_user_defined_breakpoint_options();
     86
     87        add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_admin_assets' ) );
     88    }
     89
     90    /**
     91     * Enqueue admin assets for settings page.
     92     * * @param string $hook_suffix The current admin page hook suffix.
     93     */
     94    public static function enqueue_admin_assets( $hook_suffix ) {
     95        // if not on the settings page, do not enqueue the script
     96        if ( 'settings_page_' . self::MENU_PAGE_SLUG !== $hook_suffix ) {
     97            return;
     98        }
     99        // add js to the page
     100        $handle            = WPBBE_PLUGIN_ID . '__core-settings-script';
     101        wp_register_script(
     102            $handle,
     103            WPBBE_URL . 'admin/js/settings/settings.js',
     104            array(), // no dependencies for this script
     105            WPBBE_VERSION, // use plugin version as script version
     106            array(
     107                'in_footer' => true, // load script in footer as we need to access the DOM elements
     108            )
     109        );
     110
     111        $translations = array(
     112            'remove_breakpoint_confirm_message' => esc_js( __( 'Do you want to remove this breakpoint?', 'better-block-editor' ) ),
     113            'remove_breakpoint_button_title'    => esc_js( __( 'Remove breakpoint', 'better-block-editor' ) ),
     114        );
     115
     116        $inline_script = 'const WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS = ' . wp_json_encode(
    72117                array(
    73                     'label'       => $module_data['label'],
    74                     'description' => $module_data['description'],
    75                     'enabled'     => $module_data['enabled'],
     118                    'ALLOWED_SIZE_UNITS' => self::$allowed_breakpoint_units,
     119                    'WP_OPTION_NAME'     => self::build_user_defined_breakpoints_option_name(),
     120                    'I18N_TRANSLATIONS'  => $translations,
    76121                )
    77             );
     122            ) . ';';
     123
     124        // initialize the Map to store breakpoints
     125        $inline_script .= 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST = new Map();' . "\n";
     126        // we use Map to keep breakpoints order (otherwise it will be sorted by keys)
     127        foreach ( self::get_active_user_defined_breakpoints() as $key => $breakpoint ) {
     128            $inline_script .= sprintf(
     129                                  'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST.set(\'%s\', %s);',
     130                                  esc_js( (string) $key ),
     131                                  wp_json_encode( $breakpoint )
     132                              ) . "\n";
     133        }
     134
     135        wp_add_inline_script( $handle, $inline_script, 'before' );
     136
     137        wp_enqueue_script( $handle );
     138        wp_enqueue_style(
     139            WPBBE_PLUGIN_ID . '__core-settings-style',
     140            WPBBE_URL . 'admin/css/settings/settings.css',
     141            array(),
     142            WPBBE_VERSION
     143        );
     144    }
     145
     146    public static function rest_settings_init() {
     147        $modules_data = Plugin::instance()->modules_manager->get_managable_modules_data();
     148        foreach ( $modules_data as $module_data ) {
     149            self::add_module_enable_checkbox( $module_data,true );
    78150            // add module settings
    79151            if ( $module_data['enabled'] ) {
     
    81153                if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
    82154                    foreach ( $classname::get_settings() as $key => $field ) {
    83                         self::add_module_settigns( $module_data['identifier'], $key, $field );
    84                     }
    85                 }
    86             }
    87         }
    88 
    89         self::add_user_defined_breakpoint_options();
    90     }
    91 
    92     public static function rest_settings_init() {
    93         $modules_data = Plugin::instance()->modules_manager->get_managable_modules_data();
    94         foreach ( $modules_data as $module_data ) {
    95             self::add_module_enable_checkbox(
    96                 $module_data['identifier'],
    97                 $module_data['title'],
    98                 array(
    99                     'label'       => $module_data['label'],
    100                     'description' => $module_data['description'],
    101                     'enabled'     => $module_data['enabled'],
    102                 ),
    103                 true
    104             );
    105             // add module settings
    106             if ( $module_data['enabled'] ) {
    107                 $classname = $module_data['classname'] ?? null;
    108                 if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
    109                     foreach ( $classname::get_settings() as $key => $field ) {
    110                         self::add_module_settigns( $module_data['identifier'], $key, $field, true );
     155                        self::add_module_settigns( $module_data, $key, $field, true );
    111156                    }
    112157                }
     
    179224            __( 'Breakpoints', 'better-block-editor' ),
    180225            function () {
    181                 self::parse_template( 'breakpoints', array() );
     226                self::parse_template('_setting_wrapper', [
     227                    'tab'   => self::TAB_BREAKPOINTS,
     228                    'template' => 'breakpoints',
     229                ]);
    182230            },
    183231            self::MENU_PAGE_SLUG,
    184232            WPBBE_PLUGIN_ID . '_settings_section'
    185         );
    186 
    187         // add js to the page
    188         $relative_filename = 'admin/js/settings/breakpoints.js';
    189         $handle            = WPBBE_PLUGIN_ID . '__core-settings__breakpoints-script';
    190         wp_register_script(
    191             $handle,
    192             WPBBE_URL . $relative_filename,
    193             array(), // no dependencies for this script
    194             WPBBE_VERSION, // use plugin version as script version
    195             array(
    196                 'in_footer' => true, // load script in footer as we need to access the DOM elements
    197             )
    198         );
    199 
    200         $translations = array(
    201             'remove_breakpoint_confirm_message' => esc_js( __( 'Do you want to remove this breakpoint?', 'better-block-editor' ) ),
    202             'remove_breakpoint_button_title'    => esc_js( __( 'Remove breakpoint', 'better-block-editor' ) ),
    203         );
    204 
    205         $inline_script = 'const WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS = ' . wp_json_encode(
    206             array(
    207                 'ALLOWED_SIZE_UNITS' => self::$allowed_breakpoint_units,
    208                 'WP_OPTION_NAME'     => self::build_user_defined_breakpoints_option_name(),
    209                 'I18N_TRANSLATIONS'  => $translations,
    210             )
    211         ) . ';';
    212 
    213         // initialize the Map to store breakpoints
    214         $inline_script .= 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST = new Map();' . "\n";
    215         // we use Map to keep breakpoints order (otherwise it will be sorted by keys)
    216         foreach ( self::get_active_user_defined_breakpoints() as $key => $breakpoint ) {
    217             $inline_script .= sprintf(
    218                 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST.set(\'%s\', %s);',
    219                 esc_js( (string) $key ),
    220                 wp_json_encode( $breakpoint )
    221             ) . "\n";
    222         }
    223 
    224         wp_add_inline_script( $handle, $inline_script, 'before' );
    225 
    226         add_action(
    227             'admin_enqueue_scripts',
    228             function ( $hook_suffix ) use ( $handle ) {
    229                 // if not on the settings page, do not enqueue the script
    230                 if ( 'settings_page_' . self::MENU_PAGE_SLUG !== $hook_suffix ) {
    231                     return;
    232                 }
    233                 wp_enqueue_script( $handle );
    234             }
    235233        );
    236234    }
     
    279277    }
    280278
    281     private static function add_module_enable_checkbox( $module_identifier, $title, $args = array(), $rest = false ) {
     279    private static function add_module_enable_checkbox($args = array(), $rest = false ) {
     280        $args = array_merge(
     281            array(
     282                'identifier'  => '',
     283                'title'       => '',
     284                'label'       => null,
     285                'description' => null,
     286                'enabled'     => false,
     287                'tab'         => '',
     288            ),
     289            $args
     290        );
     291
     292        $module_identifier = $args['identifier'];
     293        $title = $args['title'];
    282294        $name = self::build_module_enabled_option_name( $module_identifier );
    283295        register_setting(
     
    300312            return;
    301313        }
    302         $args = array(
    303             'identifier'  => $name,
    304             'title'       => $title,
    305             'label'       => $args['label'] ?? null,
    306             'description' => $args['description'] ?? null,
    307             'value'     => $args['enabled'],
    308         );
    309 
     314        // dirty trick: add the separator before premium features as setting field so we don't need to create
     315        // separate section for premium features and can keep them separated in the UI with the separator element
     316        if (!self::$pro_separator_added  && !$args['is_freemium']) {
     317            add_settings_field(
     318                'wpbbe_pro_separator',
     319                self::parse_template('_pro_separator', [], true),
     320                function () { },
     321                self::MENU_PAGE_SLUG,
     322                WPBBE_PLUGIN_ID . '_settings_section',
     323                array( 'class' => 'wpbbe-separator-row wpbbe-plus-separator' )
     324            );
     325            self::$pro_separator_added = true;
     326        }
     327        $args['module_identifier'] = $module_identifier;
     328        $args['identifier'] = $name;
     329        $args['value'] = $args['enabled'];
     330        $args['class'] = $args['is_freemium'] ? '' : 'wpbbe-plus-feature';
    310331        add_settings_field(
    311332            $name,
    312333            $title,
    313334            function ( $args ) {
    314                 self::parse_template( '_checkbox', $args );
     335                self::parse_template('_setting_wrapper', [
     336                    'tab'   => $args['tab'],
     337                    'class' => 'wpbbe-module-enable',
     338                    'module' => $args['module_identifier'],
     339                    'template' => '_checkbox',
     340                    'template_args' => $args,
     341                ]);
    315342            },
    316343            self::MENU_PAGE_SLUG,
     
    330357     * @return void
    331358     */
    332     private static function parse_template( $template_name, $args ) {
     359    public static function parse_template( $template_name, $args, $return = false ) {
    333360        $template_full_name = self::TEMPLATES_FOLDER_NAME . $template_name . '.php';
    334361
    335362        if ( ! is_file( $template_full_name ) || ! is_readable( $template_full_name ) ) {
    336363            throw new Exception( 'Can not read template: ' . esc_html( $template_full_name ) );
     364        }
     365
     366        if ( $return ) {
     367            ob_start();
     368            include $template_full_name;
     369
     370            return ob_get_clean();
    337371        }
    338372
     
    410444     * @return void
    411445     */
    412     private static function add_module_settigns( $module_identifier, $option_key, $field, $rest = false ) {
     446    private static function add_module_settigns( $module_data, $option_key, $field, $rest = false ) {
     447        $module_identifier = $module_data['identifier'];
    413448        $option_name = self::build_module_settings_name( $module_identifier ); // store all sub options as array
    414449
     
    426461                    switch ( $field['type'] ?? 'text' ) {
    427462                        case 'multicheckbox':
    428                             $available             = $field['options'] ?? array();
     463                            $field_options             = $field['options'] ?? array();
    429464                            $output[ $option_key ] = $output[ $option_key ] ?? array();
    430                             foreach ( $available as $key => $_ ) {
    431                                 if ( ! empty( $input[ $option_key ][ $key ] ) ) {
    432                                     $output[ $option_key ][ $key ] = 1;
     465
     466                            foreach ( $field_options as $key => $_ ) {
     467                                $is_disabled = ! empty( $field_options[ $key ]['disabled'] );
     468                                //for disabled options we want keep the checkbox disabled and ignore changes from user (use default value)
     469                                if ( $is_disabled ) {
     470                                    // remove disabled options from the saved settings
     471                                    unset( $output[ $option_key ][ $key ] );
    433472                                } else {
    434                                     unset( $output[ $option_key ][ $key ] );
    435                                 }
    436                             }
     473                                    $output[ $option_key ][ $key ] = isset( $input[ $option_key ][ $key ] ) ? 1 : 0;
     474                                }}
    437475                            break;
    438476                        case 'checkbox':
     
    457495            return;
    458496        }
     497        $title = $field['title'] ?? '';
    459498        add_settings_field(
    460499            $option_name . '__' . $option_key,
    461             $field['label'],
    462             function () use ( $module_identifier, $option_key, $field, $option_name ) {
    463                 $stored_value = get_option( $option_name, array() );
    464                 $value        = $stored_value[ $option_key ] ?? $field['default'];
    465 
    466                 $template_name = '_' . $field['type'];
    467                 self::parse_template(
    468                     $template_name,
    469                     array_merge(
     500            $title,
     501            function () use ( $module_data, $option_key, $field, $option_name ) {
     502                $merged_value = self::get_setting(
     503                    $option_name,
     504                    $option_key,
     505                    $field['default'] ?? null
     506                );
     507
     508                self::parse_template('_setting_wrapper', [
     509                    'tab'   => $module_data['tab'],
     510                    'class' => 'wpbbe-module-setting',
     511                    'module' => $module_data['identifier'],
     512                    'template' => '_' . $field['type'],
     513                    'template_args' => array_merge(
    470514                        $field,
    471515                        array(
    472516                            'identifier' => $option_name . '[' . $option_key . ']',
    473                             'value'      => $value,
     517                            'value'      => $merged_value,
    474518                        )
    475                     )
    476                 );
     519                    ),
     520                ]);
    477521            },
    478522            self::MENU_PAGE_SLUG,
     
    480524        );
    481525    }
     526
     527
     528    /**
     529     * Retrieves a specific setting value for a module, with support for default values and array merging.
     530     *
     531     * @param string $option_name The name of the option in the database (usually built with build_module_settings_name).
     532     * @param string $option_key  The specific key of the setting within the module's settings array.
     533     * @param mixed  $default     The default value to return if the setting is not found. Can be an array for settings that are arrays.
     534     *
     535     * @return mixed The setting value, merged with defaults if it's an array, or the default value if not set.
     536     */
     537    public static function get_setting( $option_name, $key, $default = null ) {
     538        $stored = get_option( $option_name, null );
     539
     540        // If there is no stored value for the module settings, return the default value
     541        if ( $stored === null ) {
     542            return $default;
     543        }
     544
     545        if ( null === $key ) {
     546            return $stored;
     547        }
     548
     549        $value = $stored[ $key ] ?? null;
     550
     551        // array merge (add new defaults, keep user values)
     552        if ( is_array( $default ) && is_array( $value ) ) {
     553            return array_replace( $default, $value );
     554        }
     555
     556        // if value exists use it
     557        if ( $value !== null ) {
     558            return $value;
     559        }
     560        // if value does not exist return default
     561        return $default;
     562    }
    482563}
  • better-block-editor/tags/1.4.0/Modules/ButtonsResponsive/Module.php

    r3386474 r3492699  
    2828
    2929    public static function get_label() {
    30         return __( 'Add Responsive settings to Buttons block.', 'better-block-editor' );
     30        return __( 'Add Responsive Settings to Buttons block.', 'better-block-editor' );
    3131    }
    3232
  • better-block-editor/tags/1.4.0/Modules/ColumnsResponsive/Module.php

    r3386474 r3492699  
    201201
    202202    public static function get_label() {
    203         return __( 'Add responsiveness settings to Columns block.', 'better-block-editor' );
     203        return __( 'Add Responsive Settings to Columns block.', 'better-block-editor' );
    204204    }
    205205}
  • better-block-editor/tags/1.4.0/Modules/ContactForm7Block/Module.php

    r3449829 r3492699  
    1212use BetterBlockEditor\Core\BlockUtils;
    1313use BetterBlockEditor\Base\ModuleBase;
     14use BetterBlockEditor\Core\Settings;
    1415use BetterBlockEditor\Modules\StyleEngine\Module as StyleEngineModule;
    1516
     
    166167    }
    167168
     169    public static function get_tab() {
     170        return Settings::TAB_BLOCKS;
     171    }
     172
    168173    public static function get_title() {
    169         return __( 'Better Contact Form 7 Block', 'better-block-editor' );
     174        return __( 'BBE Contact Form 7', 'better-block-editor' );
    170175    }
    171176
    172177    public static function get_label() {
    173         return __( 'Enable Better Contact Form 7 block.', 'better-block-editor' );
    174     }
     178        return __( 'Enable BBE Contact Form 7 block.', 'better-block-editor' );
     179    }
     180
    175181    public function render( $attributes ) {
    176182        $id = intval( $attributes['id'] ?? 0 );
     
    235241            self::DESIGN_STYLES_OPTION => array(
    236242                'type'        => 'checkbox',
    237                 'label'       => __( 'Better Contact Form 7 Styles', 'better-block-editor' ),
     243                'label'       => __( 'Enable form styling.', 'better-block-editor' ),
    238244                'default'     => 1,
    239                 'description' => __( 'Enable configurable styles for Better Contact Form 7 block.', 'better-block-editor' ),
     245                'description' => __( 'When enabled, form styling settings become available in the BBE Contact Form 7 block. If disabled, default theme styles will be used.', 'better-block-editor' ),
    240246            ),
    241247        );
  • better-block-editor/tags/1.4.0/Modules/DemoContent/Module.php

    r3386474 r3492699  
    1010namespace BetterBlockEditor\Modules\DemoContent;
    1111
     12use BetterBlockEditor\Base\ManagableModuleInterface;
    1213use BetterBlockEditor\Base\ModuleBase;
    1314use BetterBlockEditor\Modules\DemoContent\AjaxHandlers\ImportContentAjaxHandler;
     
    2324 * Bootstraps Demo Content importer admin experience.
    2425 */
    25 class Module extends ModuleBase {
     26class Module extends ModuleBase implements ManagableModuleInterface {
    2627
    2728    const MODULE_IDENTIFIER = 'demo-content';
     
    4546     */
    4647    protected $page_hook = null;
     48
     49    public static function get_title() {
     50        return __( 'Site Templates', 'better-block-editor' );
     51    }
     52
     53    public static function get_label() {
     54        return __('Enable BBE pre-made Site Templates', 'better-block-editor' );
     55    }
     56
     57    public static function get_settings_order() {
     58        return 50;
     59    }
     60
    4761
    4862    /**
  • better-block-editor/tags/1.4.0/Modules/DesignSystemParts/Module.php

    r3386474 r3492699  
    1111use BetterBlockEditor\Base\ManagableModuleInterface;
    1212use BetterBlockEditor\Base\ConfigurableModuleInterface;
     13use BetterBlockEditor\Core\Settings;
    1314
    1415defined( 'ABSPATH' ) || exit;
     
    2324    public static function get_default_state() {
    2425        return false;
     26    }
     27
     28    public static function get_tab() {
     29        return Settings::TAB_DESIGN;
    2530    }
    2631
     
    3742        // add filter on very late stage to change theme data just before using it in FE
    3843        add_filter( 'wp_theme_json_data_theme', array( $this, 'modify_theme_json_values' ), 1000 );
    39         // exclude parts of design system, later add an option to settings page to control it
     44        // exclude parts of design system
    4045        add_filter( 'wpbbe_design_system_parts', array( $this, 'filter_design_system_parts' ), 10, 2 );
    4146        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
     
    4348
    4449    public function filter_design_system_parts( $parts, $context ) {
    45         $active_parts = $this->get_option( self::ACTIVE_PARTS, array() );
     50        $active_parts = $this->get_option( self::ACTIVE_PARTS, self::get_settings()[ self::ACTIVE_PARTS ]['default'] );
    4651        $map = array(
    4752            'color'      => array( 'colorPalette', 'colorGradients' ),
     
    197202            self::ACTIVE_PARTS => array(
    198203                'type'        => 'multicheckbox',
    199                 'label'       => __( 'Design System Parts', 'better-block-editor' ),
     204                'title'       => __( 'Design System Parts', 'better-block-editor' ),
    200205                'options'     => array(
    201206                    'color'      => __( 'Colors', 'better-block-editor' ),
    202207                    'typography' => __( 'Typography', 'better-block-editor' ),
     208                    'spacing'    => array( 'label' => __( 'Spacing', 'better-block-editor' ), 'disabled' => true )
    203209                ),
    204                 'default'     => array( '' ),
     210                'default'     => array(  'color'=> 1, 'typography' => 1, 'spacing' => 1 ),
    205211                'description' => __( 'Choose active parts of the design system.', 'better-block-editor' ),
    206212            ),
  • better-block-editor/tags/1.4.0/Modules/GridResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Grid block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Grid block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/tags/1.4.0/Modules/GroupResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Group block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Group block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/tags/1.4.0/Modules/NavigationResponsive/Module.php

    r3386474 r3492699  
    111111
    112112    public static function get_label() {
    113         return __( 'Add responsiveness settings to Navigation block.', 'better-block-editor' );
     113        return __( 'Add Responsive Settings to Navigation block.', 'better-block-editor' );
    114114    }
    115115}
  • better-block-editor/tags/1.4.0/Modules/PostTemplateResponsive/Module.php

    r3386474 r3492699  
    2828
    2929    public static function get_label() {
    30         return __( 'Add responsiveness settings to Post Template block.', 'better-block-editor' );
     30        return __( 'Add Responsive Settings to Post Template block when used in Grid view.', 'better-block-editor' );
    3131    }
    3232
  • better-block-editor/tags/1.4.0/Modules/RowResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Row block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Row block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/tags/1.4.0/Modules/TextResponsive/Module.php

    r3386474 r3492699  
    1919    const ASSETS_BUILD_PATH = 'editor/blocks/__all__/text-responsive/';
    2020
    21     const SETTINGS_ORDER = 1400;
     21    const SETTINGS_ORDER = 920;
    2222
    2323    const BLOCK_NAMES = array(
     
    3333
    3434    public static function get_label() {
    35         return __( 'Add responsive text alignment settings to Header, Paragraph, Post Title and Post Excerpt blocks.', 'better-block-editor' );
     35        return __( 'Add Responsive text alignment settings to Header, Paragraph, Post Title and Post Excerpt blocks.', 'better-block-editor' );
    3636    }
    3737
  • better-block-editor/tags/1.4.0/Modules/TextStyleFromElement/Module.php

    r3443250 r3492699  
    2020    const ASSETS_BUILD_PATH = 'editor/blocks/__all__/text-style-from-element/';
    2121
    22     const SETTINGS_ORDER = 1500;
     22    const SETTINGS_ORDER = 930;
    2323
    2424    const ATTRIBUTE_TEXT_STYLE   = 'wpbbeTextStyleFromElement';
     
    3434
    3535    const TEXT_STYLE_CLASSNAME_PREFIX = 'wpbbe-text-style-from-element-';
    36    
     36
    3737    public function init(): void {
    3838
     
    5757
    5858        // settings from the "All Headings" element
    59         $all_headings_styling_data = $this->get_text_style_to_borrow( 
    60             $theme_json_data['styles']['elements'][ self::ALL_HEADINGS_ELEMENT ] ?? array() 
     59        $all_headings_styling_data = $this->get_text_style_to_borrow(
     60            $theme_json_data['styles']['elements'][ self::ALL_HEADINGS_ELEMENT ] ?? array()
    6161        );
    6262
     
    6969                $all_headings_styling_data,
    7070                array(
    71                     'selector' =>  implode( ', ', 
    72                         array_map( 
    73                             function ( $heading_element ) { 
     71                    'selector' =>  implode( ', ',
     72                        array_map(
     73                            function ( $heading_element ) {
    7474                                return str_repeat('.' . self::TEXT_STYLE_CLASSNAME_PREFIX . $heading_element, 2);
    75                             }, 
    76                             self::HEADING_ELEMENTS 
    77                         ) 
     75                            },
     76                            self::HEADING_ELEMENTS
     77                        )
    7878                    ),
    7979                    'context'  => 'core',
     
    8181            );
    8282        }
    83        
     83
    8484
    8585        // individual heading (h1, h2, etc.) settings
     
    8787            if ( in_array( $element_name, self::HEADING_ELEMENTS, true ) ) {
    8888                $heading_element_styling_data = $this->get_text_style_to_borrow( $element_settings );
    89                
     89
    9090                if ( empty( $heading_element_styling_data ) ) {
    9191                    continue;
    92                 }   
     92                }
    9393
    9494                StyleEngineModule::get_styles(
     
    136136            // also add the core heading class if the style is taken from a heading element
    137137            $cssClasses[] = self::WP_CORE_HEADING_CLASSNAME;
    138         }   
     138        }
    139139
    140140        $block_content = BlockUtils::append_classes( $block_content, $cssClasses);
     
    144144
    145145    public static function get_title(): string {
    146         return __( 'Text Blocks Style', 'better-block-editor' );
     146        return __( 'Change Style for Text Blocks', 'better-block-editor' );
    147147    }
    148148
    149149    public static function get_label(): string {
    150         return __( 'Add Style setting for Heading, Paragraph, Post Title, and Post Excerpt blocks.', 'better-block-editor' );
     150        return __( 'Add a "Change style" setting for Heading, Paragraph, Post Title, and Post Excerpt blocks. Select a visual style (H1–H6, Paragraph) for text blocks without changing their HTML tag.', 'better-block-editor' );
    151151    }
    152152
  • better-block-editor/tags/1.4.0/Modules/UploadSVG/Module.php

    r3386474 r3492699  
    1010use BetterBlockEditor\Base\ModuleBase;
    1111use BetterBlockEditor\Base\ManagableModuleInterface;
     12use BetterBlockEditor\Core\Settings;
    1213use enshrined\svgSanitize\Sanitizer;
    1314
     
    493494
    494495    public static function get_title() {
    495         return __( 'Inline SVG', 'better-block-editor' );
     496        return __( 'BBE SVG Icon', 'better-block-editor' );
     497    }
     498
     499    public static function get_tab() {
     500        return Settings::TAB_BLOCKS;
    496501    }
    497502
    498503    public static function get_label() {
    499         return __( 'Allow to upload and display the SVG icon', 'better-block-editor' );
     504        return __( 'Allow uploading SVG images, and enable the BBE SVG Icon block.', 'better-block-editor' );
    500505    }
    501506}
  • better-block-editor/tags/1.4.0/Modules/Visibility/Module.php

    r3459110 r3492699  
    22/**
    33 * Adds responsive visibility settings to all blocks.
    4  * Standard approach with ResponsiveBlockModuleBase can not be used here 
     4 * Standard approach with ResponsiveBlockModuleBase can not be used here
    55 * as visibility='hidden' must work even without any responsive settings
    6  * 
     6 *
    77 * @package BetterBlockEditor
    88 */
     
    2424    const PLUGIN_ASSETS_BUILD_PATH = 'editor/plugins/visibility/';
    2525
    26     const SETTINGS_ORDER = 100;
     26    const SETTINGS_ORDER = 990;
    2727
    2828    const ATTRIBUTES = 'wpbbeVisibility';
     
    9090        return $block_content;
    9191    }
    92    
     92
    9393    /**
    9494     * Helper to get visibility settings from block attributes
    95      * 
     95     *
    9696     * @return array [ visibility, breakpoint, breakpointCustomValue ]
    9797     */
    9898    private function get_visibility_settings( $attributes ): array {
    99         return 
     99        return
    100100            array(
    101101                $attributes[ self::ATTRIBUTES ]['visibility'] ?? 'visible', // default to visible if not set
     
    112112        if ( null === $switch_width ) {
    113113            if ( $visibility === 'hidden' ) {
    114                 // if block is always hidden       
     114                // if block is always hidden
    115115                BlockUtils::add_styles_from_css_rules(
    116116                    array(
     
    129129            ".{$class_id}.{$class_id}",
    130130            array( 'display' => 'none !important' )
    131         );     
     131        );
    132132    }
    133133
     
    137137
    138138    public static function get_label() {
    139         return __( 'Add responsive Visibility settings to all blocks.', 'better-block-editor' );
     139        return __( 'Add Responsive Visibility Settings to all blocks.', 'better-block-editor' );
    140140    }
    141141}
  • better-block-editor/tags/1.4.0/admin/templates/settings/_multicheckbox.php

    r3386474 r3492699  
    66    <?php endif; ?>
    77
    8     <?php foreach ( $args['options'] as $key => $label ) :
     8    <?php foreach ( $args['options'] as $key => $value ) :
     9        $disabled = false;
     10        $label = '';
     11        if ( is_array( $value ) ) {
     12            $label = $value['label'] ?? $key;
     13            $disabled = ! empty( $value['disabled'] );
     14        } else {
     15            $label = $value;
     16        }
    917        $checked = !empty($args['value'][$key]);
    1018        ?>
     
    1422                name="<?php echo esc_attr( $args['identifier'] . '[' . $key . ']' ); ?>"
    1523                value="1"
     24                <?php disabled( $disabled ); ?>
    1625                <?php checked( $checked ); ?>
    1726            />
  • better-block-editor/tags/1.4.0/admin/templates/settings/breakpoints.php

    r3386474 r3492699  
    55    </div>
    66
    7     <button 
    8         type="button" 
    9         class="button button-secondary" 
     7    <button
     8        type="button"
     9        class="button button-secondary"
    1010        onclick="window.wpbbeSettingsAddBreakpoint(event)"
    1111    >
    12         <span 
    13             class="dashicons dashicons-plus" 
    14             style="width: auto; height: auto; font-size: 1.2em; vertical-align: middle;" 
     12        <span
     13            class="dashicons dashicons-plus"
     14            style="width: auto; height: auto; font-size: 1.2em; vertical-align: middle;"
    1515            title="<?php echo esc_attr( __( 'Add breakpoint', 'better-block-editor' ) ); ?>"
    1616        />
  • better-block-editor/tags/1.4.0/admin/templates/settings/page.php

    r3386474 r3492699  
    55defined( 'ABSPATH' ) || exit; ?>
    66
     7<?php
     8    $tabs  = array(
     9        Settings::TAB_FEATURES => array(
     10            'label' => __( 'Features', 'better-block-editor' ),
     11        ),
     12        Settings::TAB_BREAKPOINTS => array(
     13            'label' => __( 'Breakpoints', 'better-block-editor' ),
     14        ),
     15        Settings::TAB_DESIGN => array(
     16            'label' => __( 'Design System', 'better-block-editor' ),
     17        ),
     18        Settings::TAB_BLOCKS => array(
     19            'label' => __( 'Blocks', 'better-block-editor' ),
     20        ),
     21    );
     22    //Allow adding custom tabs
     23    $tabs = apply_filters('wpbbe-settings_tabs', $tabs);
     24?>
     25
    726<div class="wrap">
    8 
    927    <h1><?php echo esc_html( __( 'Better Block Editor', 'better-block-editor' ) ); ?></h1>
    10 
     28    <nav class="nav-tab-wrapper wpbbe-tabs">
     29        <?php foreach ( $tabs as $tab_slug => $tab ) : ?>
     30            <a href="#"
     31               class="nav-tab"
     32               data-tab="<?php echo esc_attr( $tab_slug ); ?>">
     33                <?php echo esc_html( $tab['label'] ); ?>
     34            </a>
     35        <?php endforeach; ?>
     36    </nav>
    1137    <form action="options.php" method="post">
    1238        <?php
    1339        settings_fields( WPBBE_PLUGIN_ID . '_settings' );
    14 
    1540        do_settings_sections( Settings::MENU_PAGE_SLUG );
    16 
    17 
    1841        submit_button( esc_attr( __( 'Save Settings', 'better-block-editor' ) ) );
    1942        ?>
    2043    </form>
    21 
    2244</div>
  • better-block-editor/tags/1.4.0/better-block-editor.php

    r3473824 r3492699  
    55 * Requires at least: 6.8
    66 * Requires PHP:      7.4
    7  * Version:           1.3.0
     7 * Version:           1.4.0
    88 * Author:            Dream-Theme
    99 * License:           GPLv2 or later
     
    2121require_once __DIR__ . '/plugin.php';
    2222
    23 define( 'WPBBE_VERSION', '1.3.0' );
     23define( 'WPBBE_VERSION', '1.4.0' );
    2424
    2525define( 'WPBBE_FILE', __FILE__ );
  • better-block-editor/tags/1.4.0/plugin.php

    r3386474 r3492699  
    7070            return $links;
    7171        }, 10, 2 );
    72 
    73         $this->bundled_assets_manager = new BundledAssetsManager( WPBBE_PLUGIN_ID, WPBBE_DIST, WPBBE_URL_DIST );
    7472    }
    7573
     
    10199        $this->modules_manager = new ModulesManager();
    102100        $this->modules_manager->setup_hooks();
     101
     102        // in case there is BBE Pro Kit we have to add BBE Pro Kit bundles as dependencies for our bundles
     103        // so they are loaded before our bundles (based on defer loading strategy).
     104        $dependencies =array(); 
     105
     106        if ( defined('BBE_PRO_KIT_PLUGIN_ID')) {
     107            $supported_bundles = array(
     108                BundledAssetsManager::EDITOR_BUNDLE,
     109                BundledAssetsManager::EDITOR_CONTENT_BUNDLE,
     110                BundledAssetsManager::VIEW_BUNDLE,
     111            );
     112
     113            foreach ( $supported_bundles as $bundle ) {
     114                $dependencies[ $bundle ] = array(
     115                    BundledAssetsManager::build_handle( BBE_PRO_KIT_PLUGIN_ID, $bundle, 'script' )
     116                );
     117            }
     118        }
     119       
     120        $this->bundled_assets_manager = new BundledAssetsManager(
     121            WPBBE_PLUGIN_ID,
     122            WPBBE_DIST,
     123            WPBBE_URL_DIST,
     124            $dependencies
     125        );
    103126
    104127        if ( $this->is_asset_bundle_mode() ) {
     
    130153            }
    131154        );
    132 
    133155        return empty( $disabled_modules );
    134156    }
  • better-block-editor/tags/1.4.0/readme.txt

    r3477947 r3492699  
    55Tested up to:      6.9
    66Requires PHP:      7.4
    7 Stable tag:        1.3.0
     7Stable tag:        1.4.0
    88License:           GPLv2 or later
    99License URI:       https://www.gnu.org/licenses/gpl-2.0.html
     
    1111== Description ==
    1212
    13 https://www.youtube.com/watch?v=WhWGnT5hi-8
     13https://www.youtube.com/watch?v=YwZZb_XIkJs
    1414
    1515Better Block Editor (BBE) adds responsive layout controls, hover effects, on-scroll animations, and ready-to-use site templates to Block Editor. It’s not another page builder. It doesn’t replace core blocks or add bloat — BBE just adds the missing settings where needed. It works with your current block theme and plugins, so your existing content stays intact.
     
    6969* User Guide — [https://docs.wpbbe.io/](https://docs.wpbbe.io/)
    7070== Changelog ==
     71= 1.4.0 (27-03-2026) =
     721. Improved Better Block Editor settings interface.
     732. Added option to remove Site Templates link from admin menu.
    7174= 1.3.0 (03-03-2026) =
    72751. Added accessibility enhancements for SVG Icon block.
  • better-block-editor/tags/1.4.0/vendor/composer/installed.php

    r3473824 r3492699  
    22    'root' => array(
    33        'name' => 'dream-theme/better-block-editor',
    4         'pretty_version' => 'v1.3.0',
    5         'version' => '1.3.0.0',
    6         'reference' => '151321e2b14e0e968a72c181b9d47a231657e33f',
     4        'pretty_version' => 'v1.4.0',
     5        'version' => '1.4.0.0',
     6        'reference' => '694e99ca742cbf09a53c056940dca8a28f513cd3',
    77        'type' => 'project',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'dream-theme/better-block-editor' => array(
    23             'pretty_version' => 'v1.3.0',
    24             'version' => '1.3.0.0',
    25             'reference' => '151321e2b14e0e968a72c181b9d47a231657e33f',
     23            'pretty_version' => 'v1.4.0',
     24            'version' => '1.4.0.0',
     25            'reference' => '694e99ca742cbf09a53c056940dca8a28f513cd3',
    2626            'type' => 'project',
    2727            'install_path' => __DIR__ . '/../../',
  • better-block-editor/trunk/Base/ModuleBase.php

    r3449829 r3492699  
    113113    public static function get_settings_order() {
    114114        return static::SETTINGS_ORDER;
     115    }
     116
     117    /**
     118     * {inheritdocs}
     119     */
     120    public static function get_tab() {
     121        return Settings::TAB_FEATURES;
    115122    }
    116123
     
    291298    protected function get_option( string $key = null, $default = null ) {
    292299        $option_name = Settings::build_module_settings_name( $this->get_identifier() );
    293 
    294         $options = get_option( $option_name, array() );
    295 
    296         if ( null === $key ) {
    297             return $options;
    298         }
    299 
    300         return $options[ $key ] ?? $default;
     300        return Settings::get_setting($option_name, $key, $default );
    301301    }
    302302
  • better-block-editor/trunk/Core/BundledAssetsManager.php

    r3386474 r3492699  
    3333    private $plugin_dist;
    3434
    35     public function __construct( string $plugin_id, string $plugin_dist, string $plugin_dist_url ) {
     35    /**
     36     * @var array Dependencies for each bundle (optional, can be empty).
     37     * Format: array( 'bundle_key' => array( 'dependency_handle1', 'dependency_handle2' ) )
     38     */
     39    private $dependencies;
     40
     41    public function __construct( string $plugin_id, string $plugin_dist, string $plugin_dist_url, array $dependencies = array() ) {
    3642        $this->plugin_id       = $plugin_id;
    3743        $this->plugin_dist     = $plugin_dist;
    3844        $this->plugin_dist_url = $plugin_dist_url;
     45        $this->dependencies = $dependencies;
    3946    }
    4047
     
    4451     * @return void
    4552     */
    46     public function process_editor_assets() {
    47         $this->register_assets( 'editor' );
    48         $this->enqueue_assets( 'enqueue_block_editor_assets', 'editor' );
     53    public function process_editor_assets(): void {
     54        $this->register_assets( self::EDITOR_BUNDLE );
     55        $this->enqueue_assets( self::EDITOR_BUNDLE );
    4956    }
    5057
     
    5461     * @return void
    5562     */
    56     public function process_editor_content_assets() {
    57         $this->register_assets( 'editor-content' );
    58         $this->enqueue_assets( 'enqueue_block_assets', 'editor-content' );
     63    public function process_editor_content_assets(): void {
     64        $this->register_assets( self::EDITOR_CONTENT_BUNDLE );
     65        $this->enqueue_assets( self::EDITOR_CONTENT_BUNDLE );
    5966    }
    6067
     
    6471     * @return void
    6572     */
    66     public function process_view_assets() {
    67         $this->register_assets( 'view' );
    68         $this->enqueue_assets( 'wp_enqueue_scripts', 'view' );
     73    public function process_view_assets(): void {
     74        $this->register_assets( self::VIEW_BUNDLE );
     75        $this->enqueue_assets( self::VIEW_BUNDLE );
    6976    }
    7077
    7178    /**
    7279     * Add inline JS code just before bundle code (see wp_add_inline_script())
     80     * Before mode does not affect "defer" script attribute
    7381     *
    7482     * @param string $bundle_name Bundle name(key) to add code to (see self::*_BUNDLE)
     
    7785     * @return bool
    7886     */
    79     public function add_inline_js_before_bundle( $bundle_name, $js ) {
     87    public function add_inline_js_before_bundle( $bundle_name, $js ): bool {
    8088        return wp_add_inline_script(
    8189            $this->build_script_handle( $bundle_name ),
     
    8694
    8795    /**
    88      * Add inline JS code just after bundle code (see wp_add_inline_script())
    89      *
    90      * @param string $bundle_name Bundle name(key) to add code to (see self::*_BUNDLE)
     96     * Add inline JS code to footer
     97     * We use fake handler to non existing script to add inline code to footer
     98     * Bundle name is required to add code with appropriate hook (editor, editor-content, view)
     99     *
     100     * @param string $bundle_name Bundle name (see self::*_BUNDLE) to add code with appropriate hook
    91101     * @param string $js JS code to be added as inline script
    92102     *
    93103     * @return bool
    94104     */
    95     public function add_inline_js_after_bundle( $bundle_name, $js ) {
    96         return wp_add_inline_script(
    97             $this->build_script_handle( $bundle_name ),
    98             $js,
    99             'after'
    100         );
     105    public function add_inline_js_to_footer($bundle_name, $js ): bool {
     106       
     107        $handle = $this->build_script_handle( 'footer-inline' ) . '__handler';
     108
     109        // register only once
     110        if ( ! wp_script_is( $handle, 'registered' ) ) {
     111            wp_register_script(
     112                $handle,
     113                false, // no source file
     114                array(),
     115                false,
     116                array( 'in_footer' => true ) // it won't be deferred because we add inline script to it
     117            );
     118
     119            $action = $this->get_action_for_bundle( $bundle_name );
     120           
     121            if ( null === $action ) {
     122                return false;
     123            }
     124
     125            add_action(
     126                $action,
     127                function () use ( $handle ) {
     128                    wp_enqueue_script( $handle );
     129                }
     130            );
     131        }
     132
     133        return wp_add_inline_script( $handle, $js, 'before' );
     134    }
     135
     136    /**
     137     * Build a handle name for a given plugin ID, bundle key and type (script or style).
     138     *
     139     * @param string $plugin_id   Plugin ID.
     140     * @param string $bundle_key  Bundle key (one of 'editor', 'editor-content', 'view').
     141     * @param string $type        Type of handle ('script' or 'style').
     142     *
     143     * @return string Handle name.
     144     */
     145    public static function build_handle( $plugin_id, $bundle_key, $type ): string {
     146        return $plugin_id . '__bundle__' . $bundle_key . '-' . $type;
     147    }
     148
     149    /**
     150     * Get the appropriate action hook for enqueuing assets based on the bundle name.
     151     *
     152     * @param string $bundle_name Bundle name(key) to get action for (see self::*_BUNDLE)
     153     *
     154     * @return string|null Action hook name or null if bundle name is invalid.
     155     */
     156    private function get_action_for_bundle( $bundle_name ): ?string {
     157        $map = array(
     158            self::EDITOR_BUNDLE => 'enqueue_block_editor_assets',
     159            self::EDITOR_CONTENT_BUNDLE => 'enqueue_block_assets',
     160            self::VIEW_BUNDLE => 'wp_enqueue_scripts',
     161        );
     162       
     163        return array_key_exists( $bundle_name, $map ) ? $map[ $bundle_name ] : null;
    101164    }
    102165
     
    105168     *
    106169     * @param string $key Bundle key (one of 'editor', 'editor-content', 'view').
    107      * @param array  $script_register_options Optional script registration options.
    108      *
    109      * @return void
    110      */
    111     private function register_assets( $key, $script_register_options = array() ) {
     170     *
     171     * @return void
     172     */
     173    private function register_assets( $key ): void {
    112174        if ( ! file_exists( $this->get_asset_filename( $key ) ) ) {
    113175            return;
     
    116178        $asset_file = require $this->get_asset_filename( $key );
    117179
    118         // script
    119         $default_options = array(
    120             'strategy'  => 'defer',
    121             'in_footer' => false,
    122         );
    123180        // it's safe to return here as css is added only using js import construction
    124181        if ( ! file_exists( $this->plugin_dist . self::BUNDLE_DIR . $key . '.js' ) ) {
     
    126183        }
    127184
    128         $res = wp_register_script(
     185        // merge "native" dependencies from asset file with any additional dependencies provided added manually
     186        $script_dependencies = array_merge( $asset_file['dependencies'], (array)($this->dependencies[ $key ] ?? array()) );
     187
     188
     189        wp_register_script(
    129190            $this->build_script_handle( $key ),
    130191            $this->plugin_dist_url . self::BUNDLE_DIR . $key . '.js',
    131             $asset_file['dependencies'],
     192            $script_dependencies,
    132193            $asset_file['version'],
    133             array_merge( $default_options, $script_register_options )
     194            // it's important to use defer for all scripts in the bundle
     195            // otherwise the order of execution will be broken and it may cause errors
     196            // we add to header as it's common practice
     197            array(
     198                'strategy'  => 'defer',
     199                'in_footer' => false,
     200            )
    134201        );
    135202
     
    148215
    149216    /**
    150      * Enqueue registered script and style assets for a given action and bundle key.
    151      *
    152      * @param string $action WordPress action hook.
    153      * @param string $key    Bundle key (one of 'editor', 'editor-content', 'view').
    154      *
    155      * @return void
    156      */
    157     private function enqueue_assets( $action, $key ) {
     217     * Enqueue registered script and style assets for a given supported bundle key.
     218     *
     219     * @param string $key   Bundle key (one of 'editor', 'editor-content', 'view').
     220     *
     221     * @return void
     222     */
     223    private function enqueue_assets( $key ): void {
    158224        $script_handle = $this->build_script_handle( $key );
    159225        $style_handle  = $this->build_style_handle( $key );
     226
     227        $action = $this->get_action_for_bundle( $key );
     228
     229        // if action is null it means that bundle key is invalid and we should not enqueue assets
     230        if ( null === $action ) {
     231            return;
     232        }
    160233
    161234        add_action(
     
    175248     * @return string Path to the asset metadata file.
    176249     */
    177     private function get_asset_filename( $key ) {
     250    private function get_asset_filename( $key ): string {
    178251        return $this->plugin_dist . self::BUNDLE_DIR . $key . '.asset.php';
    179252    }
     
    186259     * @return string Script handle name.
    187260     */
    188     private function build_script_handle( $key ) {
    189         return $this->plugin_id . '__' . 'bundle' . '__' . $key . '-script';
     261    public function build_script_handle( $key ): string {
     262        return self::build_handle( $this->plugin_id, $key, 'script' );
    190263    }
    191264
     
    197270     * @return string Style handle name.
    198271     */
    199     private function build_style_handle( $key ) {
    200         return $this->plugin_id . '__' . 'bundle' . '__' . $key . '-style';
     272    private function build_style_handle( $key ): string {
     273        return self::build_handle( $this->plugin_id, $key, 'style' );
    201274    }
    202275}
  • better-block-editor/trunk/Core/ModulesManager.php

    r3449829 r3492699  
    131131                'label'          => $classname::get_label(),
    132132                'description'    => $classname::get_description(),
     133                'tab'            => $classname::get_tab(),
    133134                'settings_order' => $classname::get_settings_order(),
    134135                'enabled'        => self::is_module_enabled( $classname ),
    135                 'is_freemium'    => is_a( $classname, 'BetterBlockEditor\Base\ModuleInterface', true ),
     136                'is_freemium'    => !is_a( $classname, 'BbeProKit\Base\ModuleBasePro', true ),
    136137                'classname'      => $classname,
    137138                'active'         => isset( $this->modules[ $classname::get_identifier() ] ),
  • better-block-editor/trunk/Core/Settings.php

    r3473824 r3492699  
    1616class Settings {
    1717
    18     protected static $allowed_breakpoint_units = array( 'px', 'em', 'rem', 'vw', 'vh' );
     18    public const TAB_FEATURES    = 'features';
     19    public const TAB_BLOCKS      = 'blocks';
     20    public const TAB_DESIGN      = 'design';
     21    public const TAB_BREAKPOINTS = 'breakpoints';
     22
     23    protected static $allowed_breakpoint_units = array( 'px', 'rem' );
    1924
    2025    // WP permission to open the Settings Page
     
    2631    const TEMPLATES_FOLDER_NAME = WPBBE_DIR . 'admin/templates/settings/';
    2732
     33    protected static $pro_separator_added = false;
    2834
    2935    /**
     
    6773
    6874        foreach ( $modules_data as $module_data ) {
    69             self::add_module_enable_checkbox(
    70                 $module_data['identifier'],
    71                 $module_data['title'],
     75            self::add_module_enable_checkbox( $module_data );
     76            // add module settings
     77            $classname = $module_data['classname'] ?? null;
     78            if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
     79                foreach ( $classname::get_settings() as $key => $field ) {
     80                    self::add_module_settigns( $module_data, $key, $field );
     81                }
     82            }
     83        }
     84
     85        self::add_user_defined_breakpoint_options();
     86
     87        add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_admin_assets' ) );
     88    }
     89
     90    /**
     91     * Enqueue admin assets for settings page.
     92     * * @param string $hook_suffix The current admin page hook suffix.
     93     */
     94    public static function enqueue_admin_assets( $hook_suffix ) {
     95        // if not on the settings page, do not enqueue the script
     96        if ( 'settings_page_' . self::MENU_PAGE_SLUG !== $hook_suffix ) {
     97            return;
     98        }
     99        // add js to the page
     100        $handle            = WPBBE_PLUGIN_ID . '__core-settings-script';
     101        wp_register_script(
     102            $handle,
     103            WPBBE_URL . 'admin/js/settings/settings.js',
     104            array(), // no dependencies for this script
     105            WPBBE_VERSION, // use plugin version as script version
     106            array(
     107                'in_footer' => true, // load script in footer as we need to access the DOM elements
     108            )
     109        );
     110
     111        $translations = array(
     112            'remove_breakpoint_confirm_message' => esc_js( __( 'Do you want to remove this breakpoint?', 'better-block-editor' ) ),
     113            'remove_breakpoint_button_title'    => esc_js( __( 'Remove breakpoint', 'better-block-editor' ) ),
     114        );
     115
     116        $inline_script = 'const WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS = ' . wp_json_encode(
    72117                array(
    73                     'label'       => $module_data['label'],
    74                     'description' => $module_data['description'],
    75                     'enabled'     => $module_data['enabled'],
     118                    'ALLOWED_SIZE_UNITS' => self::$allowed_breakpoint_units,
     119                    'WP_OPTION_NAME'     => self::build_user_defined_breakpoints_option_name(),
     120                    'I18N_TRANSLATIONS'  => $translations,
    76121                )
    77             );
     122            ) . ';';
     123
     124        // initialize the Map to store breakpoints
     125        $inline_script .= 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST = new Map();' . "\n";
     126        // we use Map to keep breakpoints order (otherwise it will be sorted by keys)
     127        foreach ( self::get_active_user_defined_breakpoints() as $key => $breakpoint ) {
     128            $inline_script .= sprintf(
     129                                  'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST.set(\'%s\', %s);',
     130                                  esc_js( (string) $key ),
     131                                  wp_json_encode( $breakpoint )
     132                              ) . "\n";
     133        }
     134
     135        wp_add_inline_script( $handle, $inline_script, 'before' );
     136
     137        wp_enqueue_script( $handle );
     138        wp_enqueue_style(
     139            WPBBE_PLUGIN_ID . '__core-settings-style',
     140            WPBBE_URL . 'admin/css/settings/settings.css',
     141            array(),
     142            WPBBE_VERSION
     143        );
     144    }
     145
     146    public static function rest_settings_init() {
     147        $modules_data = Plugin::instance()->modules_manager->get_managable_modules_data();
     148        foreach ( $modules_data as $module_data ) {
     149            self::add_module_enable_checkbox( $module_data,true );
    78150            // add module settings
    79151            if ( $module_data['enabled'] ) {
     
    81153                if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
    82154                    foreach ( $classname::get_settings() as $key => $field ) {
    83                         self::add_module_settigns( $module_data['identifier'], $key, $field );
    84                     }
    85                 }
    86             }
    87         }
    88 
    89         self::add_user_defined_breakpoint_options();
    90     }
    91 
    92     public static function rest_settings_init() {
    93         $modules_data = Plugin::instance()->modules_manager->get_managable_modules_data();
    94         foreach ( $modules_data as $module_data ) {
    95             self::add_module_enable_checkbox(
    96                 $module_data['identifier'],
    97                 $module_data['title'],
    98                 array(
    99                     'label'       => $module_data['label'],
    100                     'description' => $module_data['description'],
    101                     'enabled'     => $module_data['enabled'],
    102                 ),
    103                 true
    104             );
    105             // add module settings
    106             if ( $module_data['enabled'] ) {
    107                 $classname = $module_data['classname'] ?? null;
    108                 if ( $classname && is_a( $classname, ConfigurableModuleInterface::class, true ) ) {
    109                     foreach ( $classname::get_settings() as $key => $field ) {
    110                         self::add_module_settigns( $module_data['identifier'], $key, $field, true );
     155                        self::add_module_settigns( $module_data, $key, $field, true );
    111156                    }
    112157                }
     
    179224            __( 'Breakpoints', 'better-block-editor' ),
    180225            function () {
    181                 self::parse_template( 'breakpoints', array() );
     226                self::parse_template('_setting_wrapper', [
     227                    'tab'   => self::TAB_BREAKPOINTS,
     228                    'template' => 'breakpoints',
     229                ]);
    182230            },
    183231            self::MENU_PAGE_SLUG,
    184232            WPBBE_PLUGIN_ID . '_settings_section'
    185         );
    186 
    187         // add js to the page
    188         $relative_filename = 'admin/js/settings/breakpoints.js';
    189         $handle            = WPBBE_PLUGIN_ID . '__core-settings__breakpoints-script';
    190         wp_register_script(
    191             $handle,
    192             WPBBE_URL . $relative_filename,
    193             array(), // no dependencies for this script
    194             WPBBE_VERSION, // use plugin version as script version
    195             array(
    196                 'in_footer' => true, // load script in footer as we need to access the DOM elements
    197             )
    198         );
    199 
    200         $translations = array(
    201             'remove_breakpoint_confirm_message' => esc_js( __( 'Do you want to remove this breakpoint?', 'better-block-editor' ) ),
    202             'remove_breakpoint_button_title'    => esc_js( __( 'Remove breakpoint', 'better-block-editor' ) ),
    203         );
    204 
    205         $inline_script = 'const WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS = ' . wp_json_encode(
    206             array(
    207                 'ALLOWED_SIZE_UNITS' => self::$allowed_breakpoint_units,
    208                 'WP_OPTION_NAME'     => self::build_user_defined_breakpoints_option_name(),
    209                 'I18N_TRANSLATIONS'  => $translations,
    210             )
    211         ) . ';';
    212 
    213         // initialize the Map to store breakpoints
    214         $inline_script .= 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST = new Map();' . "\n";
    215         // we use Map to keep breakpoints order (otherwise it will be sorted by keys)
    216         foreach ( self::get_active_user_defined_breakpoints() as $key => $breakpoint ) {
    217             $inline_script .= sprintf(
    218                 'WPBBE_RESPONSIVE_BREAKPOINT_SETTINGS.BREAKPOINT_LIST.set(\'%s\', %s);',
    219                 esc_js( (string) $key ),
    220                 wp_json_encode( $breakpoint )
    221             ) . "\n";
    222         }
    223 
    224         wp_add_inline_script( $handle, $inline_script, 'before' );
    225 
    226         add_action(
    227             'admin_enqueue_scripts',
    228             function ( $hook_suffix ) use ( $handle ) {
    229                 // if not on the settings page, do not enqueue the script
    230                 if ( 'settings_page_' . self::MENU_PAGE_SLUG !== $hook_suffix ) {
    231                     return;
    232                 }
    233                 wp_enqueue_script( $handle );
    234             }
    235233        );
    236234    }
     
    279277    }
    280278
    281     private static function add_module_enable_checkbox( $module_identifier, $title, $args = array(), $rest = false ) {
     279    private static function add_module_enable_checkbox($args = array(), $rest = false ) {
     280        $args = array_merge(
     281            array(
     282                'identifier'  => '',
     283                'title'       => '',
     284                'label'       => null,
     285                'description' => null,
     286                'enabled'     => false,
     287                'tab'         => '',
     288            ),
     289            $args
     290        );
     291
     292        $module_identifier = $args['identifier'];
     293        $title = $args['title'];
    282294        $name = self::build_module_enabled_option_name( $module_identifier );
    283295        register_setting(
     
    300312            return;
    301313        }
    302         $args = array(
    303             'identifier'  => $name,
    304             'title'       => $title,
    305             'label'       => $args['label'] ?? null,
    306             'description' => $args['description'] ?? null,
    307             'value'     => $args['enabled'],
    308         );
    309 
     314        // dirty trick: add the separator before premium features as setting field so we don't need to create
     315        // separate section for premium features and can keep them separated in the UI with the separator element
     316        if (!self::$pro_separator_added  && !$args['is_freemium']) {
     317            add_settings_field(
     318                'wpbbe_pro_separator',
     319                self::parse_template('_pro_separator', [], true),
     320                function () { },
     321                self::MENU_PAGE_SLUG,
     322                WPBBE_PLUGIN_ID . '_settings_section',
     323                array( 'class' => 'wpbbe-separator-row wpbbe-plus-separator' )
     324            );
     325            self::$pro_separator_added = true;
     326        }
     327        $args['module_identifier'] = $module_identifier;
     328        $args['identifier'] = $name;
     329        $args['value'] = $args['enabled'];
     330        $args['class'] = $args['is_freemium'] ? '' : 'wpbbe-plus-feature';
    310331        add_settings_field(
    311332            $name,
    312333            $title,
    313334            function ( $args ) {
    314                 self::parse_template( '_checkbox', $args );
     335                self::parse_template('_setting_wrapper', [
     336                    'tab'   => $args['tab'],
     337                    'class' => 'wpbbe-module-enable',
     338                    'module' => $args['module_identifier'],
     339                    'template' => '_checkbox',
     340                    'template_args' => $args,
     341                ]);
    315342            },
    316343            self::MENU_PAGE_SLUG,
     
    330357     * @return void
    331358     */
    332     private static function parse_template( $template_name, $args ) {
     359    public static function parse_template( $template_name, $args, $return = false ) {
    333360        $template_full_name = self::TEMPLATES_FOLDER_NAME . $template_name . '.php';
    334361
    335362        if ( ! is_file( $template_full_name ) || ! is_readable( $template_full_name ) ) {
    336363            throw new Exception( 'Can not read template: ' . esc_html( $template_full_name ) );
     364        }
     365
     366        if ( $return ) {
     367            ob_start();
     368            include $template_full_name;
     369
     370            return ob_get_clean();
    337371        }
    338372
     
    410444     * @return void
    411445     */
    412     private static function add_module_settigns( $module_identifier, $option_key, $field, $rest = false ) {
     446    private static function add_module_settigns( $module_data, $option_key, $field, $rest = false ) {
     447        $module_identifier = $module_data['identifier'];
    413448        $option_name = self::build_module_settings_name( $module_identifier ); // store all sub options as array
    414449
     
    426461                    switch ( $field['type'] ?? 'text' ) {
    427462                        case 'multicheckbox':
    428                             $available             = $field['options'] ?? array();
     463                            $field_options             = $field['options'] ?? array();
    429464                            $output[ $option_key ] = $output[ $option_key ] ?? array();
    430                             foreach ( $available as $key => $_ ) {
    431                                 if ( ! empty( $input[ $option_key ][ $key ] ) ) {
    432                                     $output[ $option_key ][ $key ] = 1;
     465
     466                            foreach ( $field_options as $key => $_ ) {
     467                                $is_disabled = ! empty( $field_options[ $key ]['disabled'] );
     468                                //for disabled options we want keep the checkbox disabled and ignore changes from user (use default value)
     469                                if ( $is_disabled ) {
     470                                    // remove disabled options from the saved settings
     471                                    unset( $output[ $option_key ][ $key ] );
    433472                                } else {
    434                                     unset( $output[ $option_key ][ $key ] );
    435                                 }
    436                             }
     473                                    $output[ $option_key ][ $key ] = isset( $input[ $option_key ][ $key ] ) ? 1 : 0;
     474                                }}
    437475                            break;
    438476                        case 'checkbox':
     
    457495            return;
    458496        }
     497        $title = $field['title'] ?? '';
    459498        add_settings_field(
    460499            $option_name . '__' . $option_key,
    461             $field['label'],
    462             function () use ( $module_identifier, $option_key, $field, $option_name ) {
    463                 $stored_value = get_option( $option_name, array() );
    464                 $value        = $stored_value[ $option_key ] ?? $field['default'];
    465 
    466                 $template_name = '_' . $field['type'];
    467                 self::parse_template(
    468                     $template_name,
    469                     array_merge(
     500            $title,
     501            function () use ( $module_data, $option_key, $field, $option_name ) {
     502                $merged_value = self::get_setting(
     503                    $option_name,
     504                    $option_key,
     505                    $field['default'] ?? null
     506                );
     507
     508                self::parse_template('_setting_wrapper', [
     509                    'tab'   => $module_data['tab'],
     510                    'class' => 'wpbbe-module-setting',
     511                    'module' => $module_data['identifier'],
     512                    'template' => '_' . $field['type'],
     513                    'template_args' => array_merge(
    470514                        $field,
    471515                        array(
    472516                            'identifier' => $option_name . '[' . $option_key . ']',
    473                             'value'      => $value,
     517                            'value'      => $merged_value,
    474518                        )
    475                     )
    476                 );
     519                    ),
     520                ]);
    477521            },
    478522            self::MENU_PAGE_SLUG,
     
    480524        );
    481525    }
     526
     527
     528    /**
     529     * Retrieves a specific setting value for a module, with support for default values and array merging.
     530     *
     531     * @param string $option_name The name of the option in the database (usually built with build_module_settings_name).
     532     * @param string $option_key  The specific key of the setting within the module's settings array.
     533     * @param mixed  $default     The default value to return if the setting is not found. Can be an array for settings that are arrays.
     534     *
     535     * @return mixed The setting value, merged with defaults if it's an array, or the default value if not set.
     536     */
     537    public static function get_setting( $option_name, $key, $default = null ) {
     538        $stored = get_option( $option_name, null );
     539
     540        // If there is no stored value for the module settings, return the default value
     541        if ( $stored === null ) {
     542            return $default;
     543        }
     544
     545        if ( null === $key ) {
     546            return $stored;
     547        }
     548
     549        $value = $stored[ $key ] ?? null;
     550
     551        // array merge (add new defaults, keep user values)
     552        if ( is_array( $default ) && is_array( $value ) ) {
     553            return array_replace( $default, $value );
     554        }
     555
     556        // if value exists use it
     557        if ( $value !== null ) {
     558            return $value;
     559        }
     560        // if value does not exist return default
     561        return $default;
     562    }
    482563}
  • better-block-editor/trunk/Modules/ButtonsResponsive/Module.php

    r3386474 r3492699  
    2828
    2929    public static function get_label() {
    30         return __( 'Add Responsive settings to Buttons block.', 'better-block-editor' );
     30        return __( 'Add Responsive Settings to Buttons block.', 'better-block-editor' );
    3131    }
    3232
  • better-block-editor/trunk/Modules/ColumnsResponsive/Module.php

    r3386474 r3492699  
    201201
    202202    public static function get_label() {
    203         return __( 'Add responsiveness settings to Columns block.', 'better-block-editor' );
     203        return __( 'Add Responsive Settings to Columns block.', 'better-block-editor' );
    204204    }
    205205}
  • better-block-editor/trunk/Modules/ContactForm7Block/Module.php

    r3449829 r3492699  
    1212use BetterBlockEditor\Core\BlockUtils;
    1313use BetterBlockEditor\Base\ModuleBase;
     14use BetterBlockEditor\Core\Settings;
    1415use BetterBlockEditor\Modules\StyleEngine\Module as StyleEngineModule;
    1516
     
    166167    }
    167168
     169    public static function get_tab() {
     170        return Settings::TAB_BLOCKS;
     171    }
     172
    168173    public static function get_title() {
    169         return __( 'Better Contact Form 7 Block', 'better-block-editor' );
     174        return __( 'BBE Contact Form 7', 'better-block-editor' );
    170175    }
    171176
    172177    public static function get_label() {
    173         return __( 'Enable Better Contact Form 7 block.', 'better-block-editor' );
    174     }
     178        return __( 'Enable BBE Contact Form 7 block.', 'better-block-editor' );
     179    }
     180
    175181    public function render( $attributes ) {
    176182        $id = intval( $attributes['id'] ?? 0 );
     
    235241            self::DESIGN_STYLES_OPTION => array(
    236242                'type'        => 'checkbox',
    237                 'label'       => __( 'Better Contact Form 7 Styles', 'better-block-editor' ),
     243                'label'       => __( 'Enable form styling.', 'better-block-editor' ),
    238244                'default'     => 1,
    239                 'description' => __( 'Enable configurable styles for Better Contact Form 7 block.', 'better-block-editor' ),
     245                'description' => __( 'When enabled, form styling settings become available in the BBE Contact Form 7 block. If disabled, default theme styles will be used.', 'better-block-editor' ),
    240246            ),
    241247        );
  • better-block-editor/trunk/Modules/DemoContent/Module.php

    r3386474 r3492699  
    1010namespace BetterBlockEditor\Modules\DemoContent;
    1111
     12use BetterBlockEditor\Base\ManagableModuleInterface;
    1213use BetterBlockEditor\Base\ModuleBase;
    1314use BetterBlockEditor\Modules\DemoContent\AjaxHandlers\ImportContentAjaxHandler;
     
    2324 * Bootstraps Demo Content importer admin experience.
    2425 */
    25 class Module extends ModuleBase {
     26class Module extends ModuleBase implements ManagableModuleInterface {
    2627
    2728    const MODULE_IDENTIFIER = 'demo-content';
     
    4546     */
    4647    protected $page_hook = null;
     48
     49    public static function get_title() {
     50        return __( 'Site Templates', 'better-block-editor' );
     51    }
     52
     53    public static function get_label() {
     54        return __('Enable BBE pre-made Site Templates', 'better-block-editor' );
     55    }
     56
     57    public static function get_settings_order() {
     58        return 50;
     59    }
     60
    4761
    4862    /**
  • better-block-editor/trunk/Modules/DesignSystemParts/Module.php

    r3386474 r3492699  
    1111use BetterBlockEditor\Base\ManagableModuleInterface;
    1212use BetterBlockEditor\Base\ConfigurableModuleInterface;
     13use BetterBlockEditor\Core\Settings;
    1314
    1415defined( 'ABSPATH' ) || exit;
     
    2324    public static function get_default_state() {
    2425        return false;
     26    }
     27
     28    public static function get_tab() {
     29        return Settings::TAB_DESIGN;
    2530    }
    2631
     
    3742        // add filter on very late stage to change theme data just before using it in FE
    3843        add_filter( 'wp_theme_json_data_theme', array( $this, 'modify_theme_json_values' ), 1000 );
    39         // exclude parts of design system, later add an option to settings page to control it
     44        // exclude parts of design system
    4045        add_filter( 'wpbbe_design_system_parts', array( $this, 'filter_design_system_parts' ), 10, 2 );
    4146        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
     
    4348
    4449    public function filter_design_system_parts( $parts, $context ) {
    45         $active_parts = $this->get_option( self::ACTIVE_PARTS, array() );
     50        $active_parts = $this->get_option( self::ACTIVE_PARTS, self::get_settings()[ self::ACTIVE_PARTS ]['default'] );
    4651        $map = array(
    4752            'color'      => array( 'colorPalette', 'colorGradients' ),
     
    197202            self::ACTIVE_PARTS => array(
    198203                'type'        => 'multicheckbox',
    199                 'label'       => __( 'Design System Parts', 'better-block-editor' ),
     204                'title'       => __( 'Design System Parts', 'better-block-editor' ),
    200205                'options'     => array(
    201206                    'color'      => __( 'Colors', 'better-block-editor' ),
    202207                    'typography' => __( 'Typography', 'better-block-editor' ),
     208                    'spacing'    => array( 'label' => __( 'Spacing', 'better-block-editor' ), 'disabled' => true )
    203209                ),
    204                 'default'     => array( '' ),
     210                'default'     => array(  'color'=> 1, 'typography' => 1, 'spacing' => 1 ),
    205211                'description' => __( 'Choose active parts of the design system.', 'better-block-editor' ),
    206212            ),
  • better-block-editor/trunk/Modules/GridResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Grid block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Grid block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/trunk/Modules/GroupResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Group block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Group block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/trunk/Modules/NavigationResponsive/Module.php

    r3386474 r3492699  
    111111
    112112    public static function get_label() {
    113         return __( 'Add responsiveness settings to Navigation block.', 'better-block-editor' );
     113        return __( 'Add Responsive Settings to Navigation block.', 'better-block-editor' );
    114114    }
    115115}
  • better-block-editor/trunk/Modules/PostTemplateResponsive/Module.php

    r3386474 r3492699  
    2828
    2929    public static function get_label() {
    30         return __( 'Add responsiveness settings to Post Template block.', 'better-block-editor' );
     30        return __( 'Add Responsive Settings to Post Template block when used in Grid view.', 'better-block-editor' );
    3131    }
    3232
  • better-block-editor/trunk/Modules/RowResponsive/Module.php

    r3386474 r3492699  
    2929
    3030    public static function get_label() {
    31         return __( 'Add responsiveness settings to Row block.', 'better-block-editor' );
     31        return __( 'Add Responsive Settings to Row block.', 'better-block-editor' );
    3232    }
    3333
  • better-block-editor/trunk/Modules/TextResponsive/Module.php

    r3386474 r3492699  
    1919    const ASSETS_BUILD_PATH = 'editor/blocks/__all__/text-responsive/';
    2020
    21     const SETTINGS_ORDER = 1400;
     21    const SETTINGS_ORDER = 920;
    2222
    2323    const BLOCK_NAMES = array(
     
    3333
    3434    public static function get_label() {
    35         return __( 'Add responsive text alignment settings to Header, Paragraph, Post Title and Post Excerpt blocks.', 'better-block-editor' );
     35        return __( 'Add Responsive text alignment settings to Header, Paragraph, Post Title and Post Excerpt blocks.', 'better-block-editor' );
    3636    }
    3737
  • better-block-editor/trunk/Modules/TextStyleFromElement/Module.php

    r3443250 r3492699  
    2020    const ASSETS_BUILD_PATH = 'editor/blocks/__all__/text-style-from-element/';
    2121
    22     const SETTINGS_ORDER = 1500;
     22    const SETTINGS_ORDER = 930;
    2323
    2424    const ATTRIBUTE_TEXT_STYLE   = 'wpbbeTextStyleFromElement';
     
    3434
    3535    const TEXT_STYLE_CLASSNAME_PREFIX = 'wpbbe-text-style-from-element-';
    36    
     36
    3737    public function init(): void {
    3838
     
    5757
    5858        // settings from the "All Headings" element
    59         $all_headings_styling_data = $this->get_text_style_to_borrow( 
    60             $theme_json_data['styles']['elements'][ self::ALL_HEADINGS_ELEMENT ] ?? array() 
     59        $all_headings_styling_data = $this->get_text_style_to_borrow(
     60            $theme_json_data['styles']['elements'][ self::ALL_HEADINGS_ELEMENT ] ?? array()
    6161        );
    6262
     
    6969                $all_headings_styling_data,
    7070                array(
    71                     'selector' =>  implode( ', ', 
    72                         array_map( 
    73                             function ( $heading_element ) { 
     71                    'selector' =>  implode( ', ',
     72                        array_map(
     73                            function ( $heading_element ) {
    7474                                return str_repeat('.' . self::TEXT_STYLE_CLASSNAME_PREFIX . $heading_element, 2);
    75                             }, 
    76                             self::HEADING_ELEMENTS 
    77                         ) 
     75                            },
     76                            self::HEADING_ELEMENTS
     77                        )
    7878                    ),
    7979                    'context'  => 'core',
     
    8181            );
    8282        }
    83        
     83
    8484
    8585        // individual heading (h1, h2, etc.) settings
     
    8787            if ( in_array( $element_name, self::HEADING_ELEMENTS, true ) ) {
    8888                $heading_element_styling_data = $this->get_text_style_to_borrow( $element_settings );
    89                
     89
    9090                if ( empty( $heading_element_styling_data ) ) {
    9191                    continue;
    92                 }   
     92                }
    9393
    9494                StyleEngineModule::get_styles(
     
    136136            // also add the core heading class if the style is taken from a heading element
    137137            $cssClasses[] = self::WP_CORE_HEADING_CLASSNAME;
    138         }   
     138        }
    139139
    140140        $block_content = BlockUtils::append_classes( $block_content, $cssClasses);
     
    144144
    145145    public static function get_title(): string {
    146         return __( 'Text Blocks Style', 'better-block-editor' );
     146        return __( 'Change Style for Text Blocks', 'better-block-editor' );
    147147    }
    148148
    149149    public static function get_label(): string {
    150         return __( 'Add Style setting for Heading, Paragraph, Post Title, and Post Excerpt blocks.', 'better-block-editor' );
     150        return __( 'Add a "Change style" setting for Heading, Paragraph, Post Title, and Post Excerpt blocks. Select a visual style (H1–H6, Paragraph) for text blocks without changing their HTML tag.', 'better-block-editor' );
    151151    }
    152152
  • better-block-editor/trunk/Modules/UploadSVG/Module.php

    r3386474 r3492699  
    1010use BetterBlockEditor\Base\ModuleBase;
    1111use BetterBlockEditor\Base\ManagableModuleInterface;
     12use BetterBlockEditor\Core\Settings;
    1213use enshrined\svgSanitize\Sanitizer;
    1314
     
    493494
    494495    public static function get_title() {
    495         return __( 'Inline SVG', 'better-block-editor' );
     496        return __( 'BBE SVG Icon', 'better-block-editor' );
     497    }
     498
     499    public static function get_tab() {
     500        return Settings::TAB_BLOCKS;
    496501    }
    497502
    498503    public static function get_label() {
    499         return __( 'Allow to upload and display the SVG icon', 'better-block-editor' );
     504        return __( 'Allow uploading SVG images, and enable the BBE SVG Icon block.', 'better-block-editor' );
    500505    }
    501506}
  • better-block-editor/trunk/Modules/Visibility/Module.php

    r3459110 r3492699  
    22/**
    33 * Adds responsive visibility settings to all blocks.
    4  * Standard approach with ResponsiveBlockModuleBase can not be used here 
     4 * Standard approach with ResponsiveBlockModuleBase can not be used here
    55 * as visibility='hidden' must work even without any responsive settings
    6  * 
     6 *
    77 * @package BetterBlockEditor
    88 */
     
    2424    const PLUGIN_ASSETS_BUILD_PATH = 'editor/plugins/visibility/';
    2525
    26     const SETTINGS_ORDER = 100;
     26    const SETTINGS_ORDER = 990;
    2727
    2828    const ATTRIBUTES = 'wpbbeVisibility';
     
    9090        return $block_content;
    9191    }
    92    
     92
    9393    /**
    9494     * Helper to get visibility settings from block attributes
    95      * 
     95     *
    9696     * @return array [ visibility, breakpoint, breakpointCustomValue ]
    9797     */
    9898    private function get_visibility_settings( $attributes ): array {
    99         return 
     99        return
    100100            array(
    101101                $attributes[ self::ATTRIBUTES ]['visibility'] ?? 'visible', // default to visible if not set
     
    112112        if ( null === $switch_width ) {
    113113            if ( $visibility === 'hidden' ) {
    114                 // if block is always hidden       
     114                // if block is always hidden
    115115                BlockUtils::add_styles_from_css_rules(
    116116                    array(
     
    129129            ".{$class_id}.{$class_id}",
    130130            array( 'display' => 'none !important' )
    131         );     
     131        );
    132132    }
    133133
     
    137137
    138138    public static function get_label() {
    139         return __( 'Add responsive Visibility settings to all blocks.', 'better-block-editor' );
     139        return __( 'Add Responsive Visibility Settings to all blocks.', 'better-block-editor' );
    140140    }
    141141}
  • better-block-editor/trunk/admin/templates/settings/_multicheckbox.php

    r3386474 r3492699  
    66    <?php endif; ?>
    77
    8     <?php foreach ( $args['options'] as $key => $label ) :
     8    <?php foreach ( $args['options'] as $key => $value ) :
     9        $disabled = false;
     10        $label = '';
     11        if ( is_array( $value ) ) {
     12            $label = $value['label'] ?? $key;
     13            $disabled = ! empty( $value['disabled'] );
     14        } else {
     15            $label = $value;
     16        }
    917        $checked = !empty($args['value'][$key]);
    1018        ?>
     
    1422                name="<?php echo esc_attr( $args['identifier'] . '[' . $key . ']' ); ?>"
    1523                value="1"
     24                <?php disabled( $disabled ); ?>
    1625                <?php checked( $checked ); ?>
    1726            />
  • better-block-editor/trunk/admin/templates/settings/breakpoints.php

    r3386474 r3492699  
    55    </div>
    66
    7     <button 
    8         type="button" 
    9         class="button button-secondary" 
     7    <button
     8        type="button"
     9        class="button button-secondary"
    1010        onclick="window.wpbbeSettingsAddBreakpoint(event)"
    1111    >
    12         <span 
    13             class="dashicons dashicons-plus" 
    14             style="width: auto; height: auto; font-size: 1.2em; vertical-align: middle;" 
     12        <span
     13            class="dashicons dashicons-plus"
     14            style="width: auto; height: auto; font-size: 1.2em; vertical-align: middle;"
    1515            title="<?php echo esc_attr( __( 'Add breakpoint', 'better-block-editor' ) ); ?>"
    1616        />
  • better-block-editor/trunk/admin/templates/settings/page.php

    r3386474 r3492699  
    55defined( 'ABSPATH' ) || exit; ?>
    66
     7<?php
     8    $tabs  = array(
     9        Settings::TAB_FEATURES => array(
     10            'label' => __( 'Features', 'better-block-editor' ),
     11        ),
     12        Settings::TAB_BREAKPOINTS => array(
     13            'label' => __( 'Breakpoints', 'better-block-editor' ),
     14        ),
     15        Settings::TAB_DESIGN => array(
     16            'label' => __( 'Design System', 'better-block-editor' ),
     17        ),
     18        Settings::TAB_BLOCKS => array(
     19            'label' => __( 'Blocks', 'better-block-editor' ),
     20        ),
     21    );
     22    //Allow adding custom tabs
     23    $tabs = apply_filters('wpbbe-settings_tabs', $tabs);
     24?>
     25
    726<div class="wrap">
    8 
    927    <h1><?php echo esc_html( __( 'Better Block Editor', 'better-block-editor' ) ); ?></h1>
    10 
     28    <nav class="nav-tab-wrapper wpbbe-tabs">
     29        <?php foreach ( $tabs as $tab_slug => $tab ) : ?>
     30            <a href="#"
     31               class="nav-tab"
     32               data-tab="<?php echo esc_attr( $tab_slug ); ?>">
     33                <?php echo esc_html( $tab['label'] ); ?>
     34            </a>
     35        <?php endforeach; ?>
     36    </nav>
    1137    <form action="options.php" method="post">
    1238        <?php
    1339        settings_fields( WPBBE_PLUGIN_ID . '_settings' );
    14 
    1540        do_settings_sections( Settings::MENU_PAGE_SLUG );
    16 
    17 
    1841        submit_button( esc_attr( __( 'Save Settings', 'better-block-editor' ) ) );
    1942        ?>
    2043    </form>
    21 
    2244</div>
  • better-block-editor/trunk/better-block-editor.php

    r3473824 r3492699  
    55 * Requires at least: 6.8
    66 * Requires PHP:      7.4
    7  * Version:           1.3.0
     7 * Version:           1.4.0
    88 * Author:            Dream-Theme
    99 * License:           GPLv2 or later
     
    2121require_once __DIR__ . '/plugin.php';
    2222
    23 define( 'WPBBE_VERSION', '1.3.0' );
     23define( 'WPBBE_VERSION', '1.4.0' );
    2424
    2525define( 'WPBBE_FILE', __FILE__ );
  • better-block-editor/trunk/plugin.php

    r3386474 r3492699  
    7070            return $links;
    7171        }, 10, 2 );
    72 
    73         $this->bundled_assets_manager = new BundledAssetsManager( WPBBE_PLUGIN_ID, WPBBE_DIST, WPBBE_URL_DIST );
    7472    }
    7573
     
    10199        $this->modules_manager = new ModulesManager();
    102100        $this->modules_manager->setup_hooks();
     101
     102        // in case there is BBE Pro Kit we have to add BBE Pro Kit bundles as dependencies for our bundles
     103        // so they are loaded before our bundles (based on defer loading strategy).
     104        $dependencies =array(); 
     105
     106        if ( defined('BBE_PRO_KIT_PLUGIN_ID')) {
     107            $supported_bundles = array(
     108                BundledAssetsManager::EDITOR_BUNDLE,
     109                BundledAssetsManager::EDITOR_CONTENT_BUNDLE,
     110                BundledAssetsManager::VIEW_BUNDLE,
     111            );
     112
     113            foreach ( $supported_bundles as $bundle ) {
     114                $dependencies[ $bundle ] = array(
     115                    BundledAssetsManager::build_handle( BBE_PRO_KIT_PLUGIN_ID, $bundle, 'script' )
     116                );
     117            }
     118        }
     119       
     120        $this->bundled_assets_manager = new BundledAssetsManager(
     121            WPBBE_PLUGIN_ID,
     122            WPBBE_DIST,
     123            WPBBE_URL_DIST,
     124            $dependencies
     125        );
    103126
    104127        if ( $this->is_asset_bundle_mode() ) {
     
    130153            }
    131154        );
    132 
    133155        return empty( $disabled_modules );
    134156    }
  • better-block-editor/trunk/readme.txt

    r3477947 r3492699  
    55Tested up to:      6.9
    66Requires PHP:      7.4
    7 Stable tag:        1.3.0
     7Stable tag:        1.4.0
    88License:           GPLv2 or later
    99License URI:       https://www.gnu.org/licenses/gpl-2.0.html
     
    1111== Description ==
    1212
    13 https://www.youtube.com/watch?v=WhWGnT5hi-8
     13https://www.youtube.com/watch?v=YwZZb_XIkJs
    1414
    1515Better Block Editor (BBE) adds responsive layout controls, hover effects, on-scroll animations, and ready-to-use site templates to Block Editor. It’s not another page builder. It doesn’t replace core blocks or add bloat — BBE just adds the missing settings where needed. It works with your current block theme and plugins, so your existing content stays intact.
     
    6969* User Guide — [https://docs.wpbbe.io/](https://docs.wpbbe.io/)
    7070== Changelog ==
     71= 1.4.0 (27-03-2026) =
     721. Improved Better Block Editor settings interface.
     732. Added option to remove Site Templates link from admin menu.
    7174= 1.3.0 (03-03-2026) =
    72751. Added accessibility enhancements for SVG Icon block.
  • better-block-editor/trunk/vendor/composer/installed.php

    r3473824 r3492699  
    22    'root' => array(
    33        'name' => 'dream-theme/better-block-editor',
    4         'pretty_version' => 'v1.3.0',
    5         'version' => '1.3.0.0',
    6         'reference' => '151321e2b14e0e968a72c181b9d47a231657e33f',
     4        'pretty_version' => 'v1.4.0',
     5        'version' => '1.4.0.0',
     6        'reference' => '694e99ca742cbf09a53c056940dca8a28f513cd3',
    77        'type' => 'project',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'dream-theme/better-block-editor' => array(
    23             'pretty_version' => 'v1.3.0',
    24             'version' => '1.3.0.0',
    25             'reference' => '151321e2b14e0e968a72c181b9d47a231657e33f',
     23            'pretty_version' => 'v1.4.0',
     24            'version' => '1.4.0.0',
     25            'reference' => '694e99ca742cbf09a53c056940dca8a28f513cd3',
    2626            'type' => 'project',
    2727            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.