JezK
Edit File: section-collection.php
<?php /** * Plugin Name: Section Collection * Description: A versatile section collection plugin featuring essential UI blocks to craft impactful, engaging, and user-centric web pages. * Version: 2.0.7 * Author: bPlugins * Author URI: https://bplugins.com * License: GPLv3 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt * Text Domain: section-collection * * Premium-only code. Freemius strips these paths when generating the free * wordpress.org build, so no Pro section code ships to free users. The full * Freemius SDK is premium-only too; the free build ships the lite SDK instead. */ // ABS PATH if ( !defined( 'ABSPATH' ) ) { exit; } /* * Free + Pro both active → bail to avoid a fatal, and keep Pro. * * The free (wordpress.org) and Pro (Freemius) builds ship the same constants, * functions and classes. If both are active, the second one to load throws a * fatal "cannot redeclare" error before Freemius can swap them — which is what * happens when someone activates Pro while the free version is still active. * * Here, if another Section Collection copy already loaded this request, we bail * before redeclaring anything (no fatal) and then deactivate the free copy so * the Pro build takes over. If THIS copy is the free one, it deactivates itself. */ if ( defined( 'BPSC_PLUGIN_VERSION' ) ) { $bpsc_dup_file = __FILE__; add_action( 'admin_init', function () use ( $bpsc_dup_file ) { if ( ! function_exists( 'deactivate_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $self = plugin_basename( $bpsc_dup_file ); $is_premium = file_exists( plugin_dir_path( $bpsc_dup_file ) . 'vendor/freemius/start.php' ); $main_file = '/section-collection.php'; if ( $is_premium ) { // Pro wins — deactivate every OTHER (free) Section Collection copy. foreach ( (array) get_option( 'active_plugins', array() ) as $plugin ) { if ( $plugin !== $self && substr( $plugin, - strlen( $main_file ) ) === $main_file ) { deactivate_plugins( $plugin ); } } } else { // This free copy is the duplicate — deactivate itself so Pro stays. deactivate_plugins( $self ); } } ); return; } // Constant $bpsc_host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $bpsc_is_local = $bpsc_host && ( preg_match( '/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/', $bpsc_host ) || preg_match( '/\.(local|test)(:\d+)?$/', $bpsc_host ) ); // Real plugin version (for display). BPSC_VERSION is the asset cache-bust // token — it becomes time() locally so dev builds always reload. define( 'BPSC_PLUGIN_VERSION', '2.0.7' ); define( 'BPSC_VERSION', $bpsc_is_local ? (string) time() : BPSC_PLUGIN_VERSION ); define( 'BPSC_DIR_URL', plugin_dir_url( __FILE__ ) ); define( 'BPSC_DIR_PATH', plugin_dir_path( __FILE__ ) ); // True only when the premium build is installed. Freemius strips the full SDK // (vendor/freemius) from the free wordpress.org build and ships vendor/freemius-lite // instead, so the full SDK's presence is what distinguishes the two builds. define( 'BPSC_HAS_PRO', file_exists( BPSC_DIR_PATH . 'vendor/freemius/start.php' ) ); // Freemius SDK. The init lives in includes/fs.php (full SDK, premium build) and // includes/fs-lite.php (lite SDK, free wordpress.org build). Both define sc_fs(). if ( function_exists( 'sc_fs' ) ) { sc_fs()->set_basename( BPSC_HAS_PRO, __FILE__ ); } else { if ( BPSC_HAS_PRO ) { require_once BPSC_DIR_PATH . 'includes/fs.php'; } else { require_once BPSC_DIR_PATH . 'includes/fs-lite.php'; } // Remove the Freemius "Add-Ons" item (admin.php?page=section-collection-addons) // from the Section Collection sidebar. Freemius still registers the page even // when its visibility filter returns false (and re-shows it whenever you're on // that page), so we drop the menu link after Freemius builds the menu — it hooks // admin_menu at WP_FS__LOWEST_PRIORITY (999999999) — and redirect direct hits. add_action( 'admin_menu', function () { remove_submenu_page( 'section-collection', 'section-collection-addons' ); }, PHP_INT_MAX ); add_action( 'admin_init', function () { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only menu gate, no state change. if ( isset( $_GET['page'] ) && 'section-collection-addons' === sanitize_text_field( wp_unslash( $_GET['page'] ) ) ) { wp_safe_redirect( admin_url( 'admin.php?page=section-collection' ) ); exit; } } ); } /** * Whether the current site can use Pro sections. * * True when the user holds a valid premium license (the all-access Premium * plan). Safe to call even if the SDK failed to load. * * @return bool */ // sc_can_use_pro / sc_can_use_section (and their matching filters) are the // plugin's documented public API — users hook `add_filter( 'sc_can_use_pro', … )` // — and `sc_` is the plugin prefix. Renaming would break existing user code. // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals if ( ! function_exists( 'sc_can_use_pro' ) ) { function sc_can_use_pro() { $can = function_exists( 'sc_fs' ) && sc_fs()->can_use_premium_code(); /** * Filter whether the site can use Pro sections. * * Handy for testing the unlocked (paid) state without a live license, e.g. * drop this in a snippet / wp-config: * add_filter( 'sc_can_use_pro', '__return_true' ); * * @param bool $can Whether Pro sections are unlocked. */ return (bool) apply_filters( 'sc_can_use_pro', $can ); } } /** * Whether the current site can use a specific Pro section. * * Unlocked when the site holds the all-access Premium license, OR has bought * the section's individual Freemius add-on (à-la-carte). * * @param int|string $addon_id Freemius add-on product id for the section. * @return bool */ if ( ! function_exists( 'sc_can_use_section' ) ) { function sc_can_use_section( $addon_id = 0 ) { $can = false; if ( function_exists( 'sc_fs' ) ) { $fs = sc_fs(); // All-access Premium unlocks every section. if ( $fs->can_use_premium_code() ) { $can = true; } // Per-add-on (à-la-carte). Only meaningful once the site is connected. // `is_registered()` exists only on the full Freemius SDK, not the Lite SDK // in the free build — guard it so the free build never fatals here. if ( ! $can && $addon_id && method_exists( $fs, 'is_registered' ) && $fs->is_registered() ) { // If the add-on is installed as its own plugin, use its license. if ( method_exists( $fs, 'get_addon_instance' ) ) { $inst = $fs->get_addon_instance( $addon_id ); if ( $inst && $inst->can_use_premium_code() ) { $can = true; } } // Otherwise, recognise an add-on purchased against this account. if ( ! $can && method_exists( $fs, 'get_account_addons' ) ) { $owned = $fs->get_account_addons(); if ( is_array( $owned ) && in_array( (string) $addon_id, array_map( 'strval', $owned ), true ) ) { $can = true; } } } } /** * Filter whether the current site can use a given Pro section. * * Handy for testing the unlock without a live purchase, e.g.: * add_filter( 'sc_can_use_section', '__return_true' ); * * @param bool $can Whether the section is unlocked. * @param int|string $addon_id The section's Freemius add-on id. */ return (bool) apply_filters( 'sc_can_use_section', $can, $addon_id ); } } // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals // Admin dashboard (top-level menu + React SPA). require_once BPSC_DIR_PATH . 'includes/Dashboard.php'; // Template Library (editor toolbar browser + bBlocks dependency manager). require_once BPSC_DIR_PATH . 'includes/Templates/Templates.php'; // Premium-only files. These are stripped from the free wordpress.org build // (see // so the free build never fatals on a missing file. if ( file_exists( BPSC_DIR_PATH . 'includes/pro/pro-blocks.php' ) ) { require_once BPSC_DIR_PATH . 'includes/pro/pro-blocks.php'; } // License activation (dashboard "Activation" page AJAX) — Pro only. if ( file_exists( BPSC_DIR_PATH . 'includes/LicenseActivation.php' ) ) { require_once BPSC_DIR_PATH . 'includes/LicenseActivation.php'; } if( !class_exists( 'BPSCPlugin' ) ){ class BPSCPlugin{ function __construct(){ add_action( 'init', [ $this, 'onInit' ] ); add_filter( 'block_editor_settings_all', [ $this, 'addLeafletEditorIframeStyle' ] ); add_action( 'enqueue_block_editor_assets', [ $this, 'enqueueAssets' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueueScripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueueLeaflet' ] ); add_action( 'enqueue_block_editor_assets', [ $this, 'enqueueLeaflet' ] ); add_filter( 'block_categories_all', [$this, 'registerCategories'] ); } function onInit(){ $blocks =['testimonial_block', 'timeline_block', 'ticker', 'about-us', 'team-section', 'faq', 'call-to-actions', 'info-list-block', 'hero-section', 'pricing-table-section', 'feature-details', 'info-grid', 'feature-overview-section', 'modern-faq-section', "money-back-guarantee-card", "flip-box-section", "service-section", "counter-section", "fancy-image-box-section", "floating-image-section", "brand-sponsor-section", "slider-section", "mission-and-vision-section", "masonry-image-carousel", "multi-tab-timeline-section", "image-comparison-section", "video-showcase-section", "chart-section", "progress-section", "storytelling-section", "recommendation-section", "filterable-showcase-section", "carousel-testimonial-section", "resource-download-section","snippet-copy-section", "floating-cta-section", "orbiting-team-section", "map-section", "schedule-section", "how-it-works-section", "open-positions-section", "pricing-toggle-section", "social-proof-wall-section", "awards-recognition-section", "tabbed-feature-showcase-section", "food-menu-section", "roi-calculator-section", "press-mentions-section", "project-portfolio-section", "lead-quiz-section", "announcement-bar-section"]; // Sections turned off from the dashboard Blocks page are skipped. $disabled = (array) get_option( 'bpscDisabledBlocks', [] ); foreach ( $blocks as $block ) { $blockJson = __DIR__ . "/build/{$block}/block.json"; if ( $disabled && file_exists( $blockJson ) ) { $meta = json_decode( (string) file_get_contents( $blockJson ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading a bundled local block.json. if ( isset( $meta['name'] ) && in_array( $meta['name'], $disabled, true ) ) { continue; } } register_block_type( __DIR__ . "/build/".$block ); } } function enqueueAssets(){ wp_enqueue_style( 'bndl-style', BPSC_DIR_URL . 'build/index.css', [], BPSC_VERSION ); wp_enqueue_script( 'bndl-script', BPSC_DIR_URL . 'build/index.js', ['wp-blocks'], BPSC_VERSION, true ); // Expose the disabled-blocks list to the editor. Every block bundle // (free build/index.js and Pro build/pro/index.js) registers all of // its blocks client-side, so the server-side register_block_type skip // alone doesn't hide a disabled block in the editor — the bundles read // this global and unregister the disabled ones. Attached to wp-blocks // (a dependency of both bundles) so it's set before either runs. $disabled = array_values( (array) get_option( 'bpscDisabledBlocks', [] ) ); wp_add_inline_script( 'wp-blocks', 'window.bpscDisabledBlocks = ' . wp_json_encode( $disabled ) . ';', 'after' ); $this->enqueueTemplateLibrary(); } // Template Library: editor toolbar browser + bBlocks dependency manager. function enqueueTemplateLibrary(){ $bblocks = \SectionCollection\Inc\Templates\Templates::status_array(); // When bBlocks is active it renders its own Template Library button. // Don't load ours too — that would show a duplicate button. (Our // library exists mainly to bootstrap bBlocks when it's missing.) if ( ! empty( $bblocks['active'] ) ) { return; } $asset_file = BPSC_DIR_PATH . 'build/template-library.asset.php'; $asset = file_exists( $asset_file ) ? require $asset_file : [ 'dependencies' => [], 'version' => BPSC_VERSION ]; // wp.ajax (imperative AJAX) is a global, not an import, so webpack // won't list wp-util/jquery — add the runtime globals explicitly. $deps = array_values( array_unique( array_merge( $asset['dependencies'], [ 'wp-util', 'wp-data', 'wp-dom-ready', 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'jquery' ] ) ) ); wp_enqueue_script( 'bpsc-template-library', BPSC_DIR_URL . 'build/template-library.js', $deps, $asset['version'], false ); wp_set_script_translations( 'bpsc-template-library', 'section-collection', BPSC_DIR_PATH . 'languages' ); if ( file_exists( BPSC_DIR_PATH . 'build/template-library.css' ) ) { wp_enqueue_style( 'bpsc-template-library', BPSC_DIR_URL . 'build/template-library.css', [], $asset['version'] ); } wp_localize_script( 'bpsc-template-library', 'scTemplateLibrary', [ 'nonce' => wp_create_nonce( 'wp_ajax' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'isPremium' => function_exists( 'sc_can_use_pro' ) ? sc_can_use_pro() : false, 'pricingUrl' => admin_url( 'admin.php?page=section-collection#/pricing' ), 'logo' => BPSC_DIR_URL . 'assets/logo.svg', 'bblocks' => $bblocks, ] ); } function enqueueScripts(){ wp_enqueue_script( 'bndl-advanced-script', BPSC_DIR_URL . 'build/advanced.js', [], BPSC_VERSION, true ); } function registerCategories( $categories ){ return array_merge( [ [ 'slug' => 'section-collection', 'title' => __( 'Section Collection', 'section-collection' ), ] ], $categories ); } function enqueueLeaflet() { // is_admin() covers the block editor. On the frontend we also check // the full template (FSE template parts / synced patterns), since // has_block() on post content alone can miss blocks placed there. $has_map = is_admin() || has_block( 'bpsc/map-section' ); if ( ! $has_map && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { global $_wp_current_template_content; if ( ! empty( $_wp_current_template_content ) && has_block( 'bpsc/map-section', $_wp_current_template_content ) ) { $has_map = true; } } if ( $has_map ) { $leaflet_js = BPSC_DIR_PATH . 'assets/leaflet/leaflet.js'; $leaflet_css = BPSC_DIR_PATH . 'assets/leaflet/leaflet.css'; $has_local = file_exists( $leaflet_js ) && file_exists( $leaflet_css ); if ( file_exists( $leaflet_css ) ) { wp_enqueue_style( 'leaflet', BPSC_DIR_URL . 'assets/leaflet/leaflet.css', [], '1.9.4' ); } if ( file_exists( $leaflet_js ) ) { wp_enqueue_script( 'leaflet', BPSC_DIR_URL . 'assets/leaflet/leaflet.js', [], '1.9.4', true ); } // Safety net: if the locally bundled Leaflet is unavailable (so // window.L never loaded), pull it from the public CDN at runtime so // the Map block still renders. The URL lives in a JS string (not in // a wp_enqueue_* call), so no asset is offloaded by the plugin. if ( ! $has_local ) { add_action( 'wp_footer', [ $this, 'printLeafletFallback' ], 5 ); add_action( 'admin_footer', [ $this, 'printLeafletFallback' ], 5 ); } } } /** * Runtime fallback that injects Leaflet from the CDN only when the locally * bundled copy is missing. Kept out of the asset-enqueue system on purpose. */ function printLeafletFallback() { $css = esc_url( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' ); $js = esc_url( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' ); ?> <script> ( function () { if ( window.L ) { return; } var c = document.createElement( 'link' ); c.rel = 'stylesheet'; c.href = <?php echo wp_json_encode( $css ); ?>; document.head.appendChild( c ); var s = document.createElement( 'script' ); s.src = <?php echo wp_json_encode( $js ); ?>; document.head.appendChild( s ); } )(); </script> <?php } /** * The modern block editor renders the canvas inside an iframe with its own * document. Styles enqueued via enqueue_block_editor_assets land in the * OUTER page, not the iframe, so Leaflet's CSS never reaches the map and * tiles render unstyled/scattered in the editor. Injecting the bundled * Leaflet CSS through the editor settings 'styles' array places it inside * the iframe canvas, which is where the block (and therefore the map) * actually renders. */ function addLeafletEditorIframeStyle( $settings ) { $leaflet_css_path = BPSC_DIR_PATH . 'assets/leaflet/leaflet.css'; if ( ! isset( $settings['styles'] ) || ! is_array( $settings['styles'] ) ) { $settings['styles'] = []; } if ( file_exists( $leaflet_css_path ) ) { $settings['styles'][] = [ 'css' => file_get_contents( $leaflet_css_path ), // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading a bundled local CSS file. ]; } else { // Fallback when the bundled CSS is unavailable. The URL lives in a // CSS @import string, not in a wp_enqueue_* call. $settings['styles'][] = [ 'css' => "@import url('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css');", ]; } return $settings; } } new BPSCPlugin(); }