Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
themes
/
bricks
/
includes
:
breakpoints.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php namespace Bricks; if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly /** * Breakpoints * * Default behavior: From largest to smallest breakpoints via max-width rules. * * Mobile-first possible via custom breakpoints: * Set a small breakpoint as 'base' to use min-width rules. * * Custom breakpoints @since 1.5.1 */ class Breakpoints { public static $breakpoints = []; public static $base_key = 'desktop'; public static $base_width = 0; public static $is_mobile_first = false; public function __construct() { // To get localized strings for builder add_action( 'init', [ $this, 'init_breakpoints' ] ); add_action( 'admin_notices', [ $this, 'admin_notice_regenerate_bricks_css_files' ] ); add_action( 'wp_ajax_bricks_regenerate_bricks_css_files', [ $this, 'regenerate_bricks_css_files' ] ); add_action( 'wp_ajax_bricks_update_breakpoints', [ $this, 'update_breakpoints' ] ); } /** * Calculate the breakpoints on init to get the proper breakpoints translations * * @since 1.5.1 */ public static function init_breakpoints() { self::$breakpoints = self::get_breakpoints(); } /** * Automatically regenerate Bricks CSS files after theme update * * @since 1.5.1 */ public function admin_notice_regenerate_bricks_css_files() { // Return: Custom breakpoints not active if ( ! Database::get_setting( 'customBreakpoints', false ) ) { return; } // STEP: Return if Bricks CSS files have been generated for this version (meaning options entry matches installed version of Bricks) $breakpoint_css_files_last_generated_in_version = get_option( BRICKS_BREAKPOINTS_LAST_GENERATED ); if ( version_compare( BRICKS_VERSION, $breakpoint_css_files_last_generated_in_version, '==' ) ) { return; } // STEP: Regenerate Bricks CSS files for custom breakpoints (if db entry found) $custom_breakpoints = get_option( BRICKS_DB_BREAKPOINTS, false ); if ( $custom_breakpoints ) { self::regenerate_bricks_css_files( true ); } } /** * Regenerate Bricks CSS files (via Bricks > Settings > General) * * E.g. frontend.min.css, element & woo CSS files, etc. * * Manual trigger: "Regenerate CSS files" button * Auto trigger: After theme update (compare version number in db against current theme version) * * @since 1.5.1 */ public static function regenerate_bricks_css_files( $return = false ) { $all_breakpoints = self::$breakpoints; $default_breakpoints = self::get_default_breakpoints(); foreach ( $all_breakpoints as $breakpoint ) { foreach ( $default_breakpoints as $default_breakpoint ) { if ( $breakpoint['key'] === $default_breakpoint['key'] && $default_breakpoint['key'] !== 'desktop' ) { $css_files_updated = self::update_media_rule_width_in_css_files( $default_breakpoint['width'], $breakpoint['width'], $default_breakpoint['width'] ); } } } $updated = update_option( BRICKS_BREAKPOINTS_LAST_GENERATED, BRICKS_VERSION ); // Manually triggered via admin_notice_regenerate_bricks_css_files if ( $return ) { return; } if ( bricks_is_ajax_call() ) { wp_send_json_success( [ 'default_breakpoints' => $default_breakpoints, 'all_breakpoints' => $all_breakpoints, ] ); } } /** * Create breakpoint * * @since 1.5.1 */ public function update_breakpoints() { Ajax::verify_nonce(); // Only users with full access can update breakpoints (@since 1.5.4) if ( ! Capabilities::current_user_has_full_access() ) { wp_send_json_error( 'verify_request: Sorry, you are not allowed to perform this action.' ); } $do = ! empty( $_POST['do'] ) ? $_POST['do'] : false; $key = ! empty( $_POST['key'] ) ? $_POST['key'] : false; $label = ! empty( $_POST['label'] ) ? $_POST['label'] : false; $width = ! empty( $_POST['width'] ) ? intval( $_POST['width'] ) : false; $width_builder = ! empty( $_POST['widthBuilder'] ) ? intval( $_POST['widthBuilder'] ) : false; $icon = ! empty( $_POST['icon'] ) ? $_POST['icon'] : false; $base = ! empty( $_POST['base'] ) ? $_POST['base'] : false; // Format key: All lowercase letters + underscores $key = strtolower( $key ); $key = str_replace( '-', '_', $key ); $key = str_replace( ' ', '_', $key ); if ( $do !== 'configure' ) { if ( ! $key ) { wp_send_json_error( [ 'error' => esc_html__( 'Error', 'bricks' ) . ' (key)' ] ); } if ( ! $label ) { wp_send_json_error( [ 'error' => esc_html__( 'Error', 'bricks' ) . ' (label)' ] ); } } $breakpoints = self::get_breakpoints(); $index = array_search( $key, array_column( $breakpoints, 'key' ) ); $breakpoint = ! empty( $breakpoints[ $index ] ) ? $breakpoints[ $index ] : []; switch ( $do ) { // STEP: Create breakpoint case 'create': // Return: 'key' already exists if ( is_int( $index ) ) { wp_send_json_error( [ 'error' => esc_html__( 'Breakpoint already exists', 'bricks' ) . ' (' . $breakpoints[ $index ]['label'] . ')' ] ); } // Return: 'label' already exists $index = array_search( $label, array_column( $breakpoints, 'label' ) ); if ( is_int( $index ) ) { wp_send_json_error( [ 'error' => esc_html__( 'Breakpoint already exists', 'bricks' ) . ' (' . $breakpoints[ $index ]['label'] . ')' ] ); } // Return: 'width' already exists $index = array_search( $width, array_column( $breakpoints, 'width' ) ); if ( is_int( $index ) ) { wp_send_json_error( [ 'error' => esc_html__( 'Breakpoint already exists', 'bricks' ) . ' (' . $breakpoints[ $index ]['label'] . ')' ] ); } // Return: 'widthBuilder' already exists $index = array_search( $width_builder, array_column( $breakpoints, 'widthBuilder' ) ); if ( is_int( $index ) ) { wp_send_json_error( [ 'error' => esc_html__( 'Breakpoint already exists', 'bricks' ) . ' (' . $breakpoints[ $index ]['label'] . ')' ] ); } $new_breakpoint = [ 'key' => $key, 'label' => $label, 'width' => $width, 'icon' => $icon, 'custom' => true, // To show delete icon ]; // Optional: Default width builder (when switching to breakpoint) if ( $width_builder ) { $new_breakpoint['widthBuilder'] = $width_builder; } $breakpoints[] = $new_breakpoint; break; // STEP: Edit breakpoint case 'edit': if ( $label ) { $breakpoints[ $index ]['label'] = $label; } if ( $width ) { // STEP: Width of breakpoint changed $old_width = ! empty( $breakpoint['width'] ) ? intval( $breakpoint['width'] ) : 0; if ( $old_width && $old_width != $width ) { $default_width = ! isset( $breakpoint['custom'] ) && $old_width ? $old_width : 0; $css_files_updated = self::update_media_rule_width_in_css_files( $old_width, $width, $default_width ); } $breakpoints[ $index ]['width'] = $width; } if ( $width_builder ) { $breakpoints[ $index ]['widthBuilder'] = $width_builder; } else { unset( $breakpoints[ $index ]['widthBuilder'] ); } if ( $icon ) { $breakpoints[ $index ]['icon'] = $icon; } // Is default breakpoint: Mark as 'edited' (to show reset icon in breakpoint manager) if ( ! isset( $breakpoint['custom'] ) ) { $breakpoints[ $index ]['edited'] = true; } break; // STEP: Pause custom breakpoint (if it's not 'base' OR 'desktop' breakpoint as desktop holds all non-CSS settings) case 'pause': if ( $breakpoint && isset( $breakpoint['custom'] ) && ! isset( $breakpoint['base'] ) && $breakpoint['key'] !== 'desktop' ) { if ( isset( $breakpoint['paused'] ) ) { unset( $breakpoint['paused'] ); } else { $breakpoint['paused'] = true; } $breakpoints[ $index ] = $breakpoint; } break; // STEP: Reset (default) breakpoint case 'reset': $default_breakpoints = self::get_default_breakpoints(); $default_breakpoint_index = array_search( $key, array_column( $default_breakpoints, 'key' ) ); $default_breakpoint = $default_breakpoints[ $default_breakpoint_index ]; $css_files_updated = self::update_media_rule_width_in_css_files( $width, $default_breakpoint['width'] ); // Resetted base breakpoint if ( isset( $breakpoint['base'] ) ) { foreach ( $breakpoints as $i => $bp ) { if ( $bp['key'] === 'desktop' ) { $breakpoints[ $i ]['base'] = true; } } } $base = false; $breakpoints[ $index ] = $default_breakpoint; break; // STEP: Delete (custom) breakpoint case 'delete': unset( $breakpoints[ $index ] ); $breakpoints = array_values( $breakpoints ); break; // STEP: Configure breakpoints case 'configure': $default_breakpoints = self::get_default_breakpoints(); // Reset width for all default breakpoint @media rules (skip 'desktop' as its the default base breakpoint and has no CSS file rules) foreach ( $breakpoints as $breakpoint ) { foreach ( $default_breakpoints as $default_breakpoint ) { if ( $breakpoint['key'] === $default_breakpoint['key'] && $default_breakpoint['key'] !== 'desktop' ) { $css_files_updated = self::update_media_rule_width_in_css_files( $breakpoint['width'], $default_breakpoint['width'] ); } } } // Delete breakpoints from database: Delete custom & reset default breakpoints $updated = delete_option( BRICKS_DB_BREAKPOINTS ); $breakpoints = $default_breakpoints; break; } // STEP: Set breakpoint as 'base' if ( $base == 'true' ) { // Loop over breakpoints to unset old & set new 'base' breakpoint foreach ( $breakpoints as $i => $bp ) { if ( $bp['key'] === $key ) { $breakpoints[ $i ]['base'] = true; } else { unset( $breakpoints[ $i ]['base'] ); } } } // Sort breakpoints (descending by 'width') $widths = array_column( $breakpoints, 'width' ); array_multisort( $widths, SORT_DESC, $breakpoints ); // Update breakpoints in database if ( $do !== 'configure' ) { $updated = update_option( BRICKS_DB_BREAKPOINTS, $breakpoints ); } // Get fresh breakpoint data to update $is_mobile_first to return breakpoints in correct order $breakpoints = self::get_breakpoints(); $widths = array_column( $breakpoints, 'width' ); array_multisort( $widths, self::$is_mobile_first ? SORT_ASC : SORT_DESC, $breakpoints ); /** * STEP: Breakpoints update: Now regenerate external files! * * As certain element CSS files contain @media default breakpoint rules. * E.g.: layout element 'flex-wrap', .pricing-tables, etc. */ if ( Database::get_setting( 'cssLoading' ) === 'file' ) { $css_files_list = Assets_Files::get_css_files_list( true ); foreach ( $css_files_list as $css_file_index => $css_file ) { $file_name = Assets_Files::regenerate_css_file( $css_file, $css_file_index, true ); } } wp_send_json_success( [ 'key' => $key, 'label' => $label, 'width' => $width, 'widthBuilder' => $width_builder, 'icon' => $icon, 'base' => $base, 'updated' => $updated, 'breakpoints' => $breakpoints, 'is_mobile_first' => self::$is_mobile_first, 'post' => $_POST, ] ); } /** * Update @media rules for breakpoint in CSS files * * When changing default breakpoint width OR default breakpoint reset. */ public static function update_media_rule_width_in_css_files( $current_width = 0, $new_width = 0, $default_width = 0 ) { /** * STEP: Update @media rules in all Bricks CSS files * * frontend CSS, elements, integrations files */ if ( $current_width && $new_width ) { $css_file_paths = [ BRICKS_PATH_ASSETS . 'css/frontend.min.css', // CSS loading method: Inline styles BRICKS_PATH_ASSETS . 'css/frontend-light.min.css', // CSS loading method: External files ]; // Add element CSS files to list $elements_css_file_paths = glob( BRICKS_PATH_ASSETS . 'css/elements/*.min.css' ); $css_file_paths = array_merge( $css_file_paths, $elements_css_file_paths ); // Add integrations CSS files to list (WooCommerce, etc.) $integrations_css_file_paths = glob( BRICKS_PATH_ASSETS . 'css/integrations/*.min.css' ); $css_file_paths = array_merge( $css_file_paths, $integrations_css_file_paths ); foreach ( $css_file_paths as $css_file_path ) { $css_file_content = file_get_contents( $css_file_path ); $css_file_content = str_replace( "{$current_width}px)", "{$new_width}px)", $css_file_content ); // Also try updating from default width in case CSS file doesn't have breakpoint database width (e.g. after updating Bricks, etc.) if ( $default_width && $default_width != $new_width ) { $css_file_content = str_replace( "{$default_width}px)", "{$new_width}px)", $css_file_content ); } $css_file_updated = file_put_contents( $css_file_path, $css_file_content ); } } } /** * Default breakpoints * * - desktop (default base breakpoint) * - tablet_portrait * - mobile_landscape * - mobile_portrait * * @return Array */ public static function get_default_breakpoints() { return [ [ 'base' => true, // 'base' marks breakpoint as base breakpoint 'key' => 'desktop', 'label' => esc_html__( 'Desktop', 'bricks' ), 'width' => 1279, 'icon' => 'laptop', ], [ 'key' => 'tablet_portrait', 'label' => esc_html__( 'Tablet portrait', 'bricks' ), 'width' => 991, 'icon' => 'tablet-portrait', ], [ 'key' => 'mobile_landscape', 'label' => esc_html__( 'Mobile landscape', 'bricks' ), 'width' => 767, 'icon' => 'phone-landscape', ], [ 'key' => 'mobile_portrait', 'label' => esc_html__( 'Mobile portrait', 'bricks' ), 'width' => 478, 'icon' => 'phone-portrait', ], ]; } /** * Get all breakpoints (default & custom) */ public static function get_breakpoints() { $default_breakpoints = self::get_default_breakpoints(); // Get breakpoints from database (default & custom) $breakpoints = get_option( BRICKS_DB_BREAKPOINTS, false ); // Use default breakpoints (no breakpoints found in database OR custom breakpoints disabled) if ( empty( $breakpoints ) || ! is_array( $breakpoints ) || ! Database::get_setting( 'customBreakpoints', false ) ) { $breakpoints = $default_breakpoints; } // STEP: Get base width foreach ( $breakpoints as $index => $breakpoint ) { if ( isset( $breakpoint['base'] ) ) { self::$base_key = $breakpoint['key']; self::$base_width = intval( $breakpoint['width'] ); // Is mobile first: Smallest breakpoint is base breakpoint self::$is_mobile_first = $index === count( $breakpoints ) - 1; } // Fallback to desktop as base breakpoint elseif ( self::$base_width === 0 && $breakpoint['key'] === 'desktop' ) { self::$base_key = $breakpoint['key']; self::$base_width = intval( $breakpoint['width'] ); } } // Sort breakpoints by width $widths = array_column( $breakpoints, 'width' ); array_multisort( $widths, self::$is_mobile_first ? SORT_ASC : SORT_DESC, $breakpoints ); return $breakpoints; } /** * Get breakpoint by key */ public static function get_breakpoint_by( $key = 'key', $value = '' ) { $index = array_search( $value, array_column( self::$breakpoints, $key ) ); if ( is_int( $index ) ) { return self::$breakpoints[ $index ]; } } }