PluginProbe
Section Collection – Add Ready-made Sections to Design Modern Websites / 2.0.2
Section Collection – Add Ready-made Sections to Design Modern Websites v2.0.2
trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 All 43 releases
section-collection / section-collection.php

section-collection.php in Section Collection – Add Ready-made Sections to Design Modern Websites 2.0.2, at section-collection.php

407 lines 17.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Section Collection
4 * Description: A versatile section collection plugin featuring essential UI blocks to craft impactful, engaging, and user-centric web pages.
5 * Version: 2.0.2
6 * Author: bPlugins
7 * Author URI: https://bplugins.com
8 * License: GPLv3
9 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt
10 * Text Domain: section-collection
11 *
12 * Premium-only code. Freemius strips these paths when generating the free
13 * wordpress.org build, so no Pro section code ships to free users. The full
14 * Freemius SDK is premium-only too; the free build ships the lite SDK instead.
15 */
16
17 // ABS PATH
18 if ( !defined( 'ABSPATH' ) ) { exit; }
19
20 /*
21 * Free + Pro both active → bail to avoid a fatal, and keep Pro.
22 *
23 * The free (wordpress.org) and Pro (Freemius) builds ship the same constants,
24 * functions and classes. If both are active, the second one to load throws a
25 * fatal "cannot redeclare" error before Freemius can swap them — which is what
26 * happens when someone activates Pro while the free version is still active.
27 *
28 * Here, if another Section Collection copy already loaded this request, we bail
29 * before redeclaring anything (no fatal) and then deactivate the free copy so
30 * the Pro build takes over. If THIS copy is the free one, it deactivates itself.
31 */
32 if ( defined( 'BPSC_PLUGIN_VERSION' ) ) {
33 $bpsc_dup_file = __FILE__;
34 add_action( 'admin_init', function () use ( $bpsc_dup_file ) {
35 if ( ! function_exists( 'deactivate_plugins' ) ) {
36 require_once ABSPATH . 'wp-admin/includes/plugin.php';
37 }
38 $self = plugin_basename( $bpsc_dup_file );
39 $is_premium = file_exists( plugin_dir_path( $bpsc_dup_file ) . 'vendor/freemius/start.php' );
40 $main_file = '/section-collection.php';
41
42 if ( $is_premium ) {
43 // Pro wins — deactivate every OTHER (free) Section Collection copy.
44 foreach ( (array) get_option( 'active_plugins', array() ) as $plugin ) {
45 if ( $plugin !== $self && substr( $plugin, - strlen( $main_file ) ) === $main_file ) {
46 deactivate_plugins( $plugin );
47 }
48 }
49 } else {
50 // This free copy is the duplicate — deactivate itself so Pro stays.
51 deactivate_plugins( $self );
52 }
53 } );
54 return;
55 }
56
57 // Constant
58 $bpsc_host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
59 $bpsc_is_local = $bpsc_host && ( preg_match( '/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/', $bpsc_host ) || preg_match( '/\.(local|test)(:\d+)?$/', $bpsc_host ) );
60 // Real plugin version (for display). BPSC_VERSION is the asset cache-bust
61 // token — it becomes time() locally so dev builds always reload.
62 define( 'BPSC_PLUGIN_VERSION', '2.0.2' );
63 define( 'BPSC_VERSION', $bpsc_is_local ? (string) time() : BPSC_PLUGIN_VERSION );
64 define( 'BPSC_DIR_URL', plugin_dir_url( __FILE__ ) );
65 define( 'BPSC_DIR_PATH', plugin_dir_path( __FILE__ ) );
66
67 // True only when the premium build is installed. Freemius strips the full SDK
68 // (vendor/freemius) from the free wordpress.org build and ships vendor/freemius-lite
69 // instead, so the full SDK's presence is what distinguishes the two builds.
70 define( 'BPSC_HAS_PRO', file_exists( BPSC_DIR_PATH . 'vendor/freemius/start.php' ) );
71
72 // Freemius SDK. The init lives in includes/fs.php (full SDK, premium build) and
73 // includes/fs-lite.php (lite SDK, free wordpress.org build). Both define sc_fs().
74 if ( function_exists( 'sc_fs' ) ) {
75 sc_fs()->set_basename( BPSC_HAS_PRO, __FILE__ );
76 } else {
77 if ( BPSC_HAS_PRO ) {
78 require_once BPSC_DIR_PATH . 'includes/fs.php';
79 } else {
80 require_once BPSC_DIR_PATH . 'includes/fs-lite.php';
81 }
82
83 // Remove the Freemius "Add-Ons" item (admin.php?page=section-collection-addons)
84 // from the Section Collection sidebar. Freemius still registers the page even
85 // when its visibility filter returns false (and re-shows it whenever you're on
86 // that page), so we drop the menu link after Freemius builds the menu — it hooks
87 // admin_menu at WP_FS__LOWEST_PRIORITY (999999999) — and redirect direct hits.
88 add_action( 'admin_menu', function () {
89 remove_submenu_page( 'section-collection', 'section-collection-addons' );
90 }, PHP_INT_MAX );
91
92 add_action( 'admin_init', function () {
93 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only menu gate, no state change.
94 if ( isset( $_GET['page'] ) && 'section-collection-addons' === sanitize_text_field( wp_unslash( $_GET['page'] ) ) ) {
95 wp_safe_redirect( admin_url( 'admin.php?page=section-collection' ) );
96 exit;
97 }
98 } );
99 }
100
101 /**
102 * Whether the current site can use Pro sections.
103 *
104 * True when the user holds a valid premium license (the all-access Premium
105 * plan). Safe to call even if the SDK failed to load.
106 *
107 * @return bool
108 */
109 // sc_can_use_pro / sc_can_use_section (and their matching filters) are the
110 // plugin's documented public API — users hook `add_filter( 'sc_can_use_pro', … )`
111 // — and `sc_` is the plugin prefix. Renaming would break existing user code.
112 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals
113 if ( ! function_exists( 'sc_can_use_pro' ) ) {
114 function sc_can_use_pro() {
115 $can = function_exists( 'sc_fs' ) && sc_fs()->can_use_premium_code();
116
117 /**
118 * Filter whether the site can use Pro sections.
119 *
120 * Handy for testing the unlocked (paid) state without a live license, e.g.
121 * drop this in a snippet / wp-config:
122 * add_filter( 'sc_can_use_pro', '__return_true' );
123 *
124 * @param bool $can Whether Pro sections are unlocked.
125 */
126 return (bool) apply_filters( 'sc_can_use_pro', $can );
127 }
128 }
129
130 /**
131 * Whether the current site can use a specific Pro section.
132 *
133 * Unlocked when the site holds the all-access Premium license, OR has bought
134 * the section's individual Freemius add-on (à-la-carte).
135 *
136 * @param int|string $addon_id Freemius add-on product id for the section.
137 * @return bool
138 */
139 if ( ! function_exists( 'sc_can_use_section' ) ) {
140 function sc_can_use_section( $addon_id = 0 ) {
141 $can = false;
142
143 if ( function_exists( 'sc_fs' ) ) {
144 $fs = sc_fs();
145
146 // All-access Premium unlocks every section.
147 if ( $fs->can_use_premium_code() ) {
148 $can = true;
149 }
150
151 // Per-add-on (à-la-carte). Only meaningful once the site is connected.
152 // `is_registered()` exists only on the full Freemius SDK, not the Lite SDK
153 // in the free build — guard it so the free build never fatals here.
154 if ( ! $can && $addon_id && method_exists( $fs, 'is_registered' ) && $fs->is_registered() ) {
155 // If the add-on is installed as its own plugin, use its license.
156 if ( method_exists( $fs, 'get_addon_instance' ) ) {
157 $inst = $fs->get_addon_instance( $addon_id );
158 if ( $inst && $inst->can_use_premium_code() ) {
159 $can = true;
160 }
161 }
162
163 // Otherwise, recognise an add-on purchased against this account.
164 if ( ! $can && method_exists( $fs, 'get_account_addons' ) ) {
165 $owned = $fs->get_account_addons();
166 if ( is_array( $owned ) && in_array( (string) $addon_id, array_map( 'strval', $owned ), true ) ) {
167 $can = true;
168 }
169 }
170 }
171 }
172
173 /**
174 * Filter whether the current site can use a given Pro section.
175 *
176 * Handy for testing the unlock without a live purchase, e.g.:
177 * add_filter( 'sc_can_use_section', '__return_true' );
178 *
179 * @param bool $can Whether the section is unlocked.
180 * @param int|string $addon_id The section's Freemius add-on id.
181 */
182 return (bool) apply_filters( 'sc_can_use_section', $can, $addon_id );
183 }
184 }
185 // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals
186
187 // Admin dashboard (top-level menu + React SPA).
188 require_once BPSC_DIR_PATH . 'includes/Dashboard.php';
189
190 // Template Library (editor toolbar browser + bBlocks dependency manager).
191 require_once BPSC_DIR_PATH . 'includes/Templates/Templates.php';
192
193 // Premium-only files. These are stripped from the free wordpress.org build
194 // (see @fs_premium_only in the header), so guard each require with file_exists
195 // so the free build never fatals on a missing file.
196 if ( file_exists( BPSC_DIR_PATH . 'includes/pro/pro-blocks.php' ) ) {
197 require_once BPSC_DIR_PATH . 'includes/pro/pro-blocks.php';
198 }
199 // License activation (dashboard "Activation" page AJAX) — Pro only.
200 if ( file_exists( BPSC_DIR_PATH . 'includes/LicenseActivation.php' ) ) {
201 require_once BPSC_DIR_PATH . 'includes/LicenseActivation.php';
202 }
203
204
205
206 if( !class_exists( 'BPSCPlugin' ) ){
207 class BPSCPlugin{
208 function __construct(){
209 add_action( 'init', [ $this, 'onInit' ] );
210 add_filter( 'block_editor_settings_all', [ $this, 'addLeafletEditorIframeStyle' ] );
211 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueueAssets' ] );
212 add_action( 'wp_enqueue_scripts', [ $this, 'enqueueScripts' ] );
213 add_action( 'wp_enqueue_scripts', [ $this, 'enqueueLeaflet' ] );
214 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueueLeaflet' ] );
215 add_filter( 'block_categories_all', [$this, 'registerCategories'] );
216 }
217
218 function onInit(){
219 $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"];
220 // Sections turned off from the dashboard Blocks page are skipped.
221 $disabled = (array) get_option( 'bpscDisabledBlocks', [] );
222
223 foreach ( $blocks as $block ) {
224 $blockJson = __DIR__ . "/build/{$block}/block.json";
225
226 if ( $disabled && file_exists( $blockJson ) ) {
227 $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.
228 if ( isset( $meta['name'] ) && in_array( $meta['name'], $disabled, true ) ) {
229 continue;
230 }
231 }
232
233 register_block_type( __DIR__ . "/build/".$block );
234 }
235 }
236 function enqueueAssets(){
237 wp_enqueue_style( 'bndl-style', BPSC_DIR_URL . 'build/index.css', [], BPSC_VERSION );
238 wp_enqueue_script( 'bndl-script', BPSC_DIR_URL . 'build/index.js', ['wp-blocks'], BPSC_VERSION, true );
239
240 // Expose the disabled-blocks list to the editor. Every block bundle
241 // (free build/index.js and Pro build/pro/index.js) registers all of
242 // its blocks client-side, so the server-side register_block_type skip
243 // alone doesn't hide a disabled block in the editor — the bundles read
244 // this global and unregister the disabled ones. Attached to wp-blocks
245 // (a dependency of both bundles) so it's set before either runs.
246 $disabled = array_values( (array) get_option( 'bpscDisabledBlocks', [] ) );
247 wp_add_inline_script(
248 'wp-blocks',
249 'window.bpscDisabledBlocks = ' . wp_json_encode( $disabled ) . ';',
250 'after'
251 );
252
253 $this->enqueueTemplateLibrary();
254 }
255
256 // Template Library: editor toolbar browser + bBlocks dependency manager.
257 function enqueueTemplateLibrary(){
258 $bblocks = \SectionCollection\Inc\Templates\Templates::status_array();
259
260 // When bBlocks is active it renders its own Template Library button.
261 // Don't load ours too — that would show a duplicate button. (Our
262 // library exists mainly to bootstrap bBlocks when it's missing.)
263 if ( ! empty( $bblocks['active'] ) ) {
264 return;
265 }
266
267 $asset_file = BPSC_DIR_PATH . 'build/template-library.asset.php';
268 $asset = file_exists( $asset_file ) ? require $asset_file : [ 'dependencies' => [], 'version' => BPSC_VERSION ];
269
270 // wp.ajax (imperative AJAX) is a global, not an import, so webpack
271 // won't list wp-util/jquery — add the runtime globals explicitly.
272 $deps = array_values( array_unique( array_merge(
273 $asset['dependencies'],
274 [ 'wp-util', 'wp-data', 'wp-dom-ready', 'wp-blocks', 'wp-block-editor', 'wp-components', 'wp-i18n', 'jquery' ]
275 ) ) );
276
277 wp_enqueue_script( 'bpsc-template-library', BPSC_DIR_URL . 'build/template-library.js', $deps, $asset['version'], false );
278 wp_set_script_translations( 'bpsc-template-library', 'section-collection', BPSC_DIR_PATH . 'languages' );
279
280 if ( file_exists( BPSC_DIR_PATH . 'build/template-library.css' ) ) {
281 wp_enqueue_style( 'bpsc-template-library', BPSC_DIR_URL . 'build/template-library.css', [], $asset['version'] );
282 }
283
284 wp_localize_script( 'bpsc-template-library', 'scTemplateLibrary', [
285 'nonce' => wp_create_nonce( 'wp_ajax' ),
286 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
287 'isPremium' => function_exists( 'sc_can_use_pro' ) ? sc_can_use_pro() : false,
288 'pricingUrl' => admin_url( 'admin.php?page=section-collection#/pricing' ),
289 'logo' => BPSC_DIR_URL . 'assets/logo.svg',
290 'bblocks' => $bblocks,
291 ] );
292 }
293
294 function enqueueScripts(){
295 wp_enqueue_script( 'bndl-advanced-script', BPSC_DIR_URL . 'build/advanced.js', [], BPSC_VERSION, true );
296 }
297
298 function registerCategories( $categories ){
299 return array_merge( [ [
300 'slug' => 'bPlugins',
301 'title' => __( 'Section Collection', 'section-collection' ),
302 ] ], $categories );
303 }
304 function enqueueLeaflet() {
305 // is_admin() covers the block editor. On the frontend we also check
306 // the full template (FSE template parts / synced patterns), since
307 // has_block() on post content alone can miss blocks placed there.
308 $has_map = is_admin() || has_block( 'bpsc/map-section' );
309
310 if ( ! $has_map && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
311 global $_wp_current_template_content;
312 if ( ! empty( $_wp_current_template_content ) && has_block( 'bpsc/map-section', $_wp_current_template_content ) ) {
313 $has_map = true;
314 }
315 }
316
317 if ( $has_map ) {
318 $leaflet_js = BPSC_DIR_PATH . 'assets/leaflet/leaflet.js';
319 $leaflet_css = BPSC_DIR_PATH . 'assets/leaflet/leaflet.css';
320 $has_local = file_exists( $leaflet_js ) && file_exists( $leaflet_css );
321
322 if ( file_exists( $leaflet_css ) ) {
323 wp_enqueue_style(
324 'leaflet',
325 BPSC_DIR_URL . 'assets/leaflet/leaflet.css',
326 [],
327 '1.9.4'
328 );
329 }
330 if ( file_exists( $leaflet_js ) ) {
331 wp_enqueue_script(
332 'leaflet',
333 BPSC_DIR_URL . 'assets/leaflet/leaflet.js',
334 [],
335 '1.9.4',
336 true
337 );
338 }
339
340 // Safety net: if the locally bundled Leaflet is unavailable (so
341 // window.L never loaded), pull it from the public CDN at runtime so
342 // the Map block still renders. The URL lives in a JS string (not in
343 // a wp_enqueue_* call), so no asset is offloaded by the plugin.
344 if ( ! $has_local ) {
345 add_action( 'wp_footer', [ $this, 'printLeafletFallback' ], 5 );
346 add_action( 'admin_footer', [ $this, 'printLeafletFallback' ], 5 );
347 }
348 }
349 }
350
351 /**
352 * Runtime fallback that injects Leaflet from the CDN only when the locally
353 * bundled copy is missing. Kept out of the asset-enqueue system on purpose.
354 */
355 function printLeafletFallback() {
356 $css = esc_url( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
357 $js = esc_url( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' );
358 ?>
359 <script>
360 ( function () {
361 if ( window.L ) { return; }
362 var c = document.createElement( 'link' );
363 c.rel = 'stylesheet';
364 c.href = <?php echo wp_json_encode( $css ); ?>;
365 document.head.appendChild( c );
366 var s = document.createElement( 'script' );
367 s.src = <?php echo wp_json_encode( $js ); ?>;
368 document.head.appendChild( s );
369 } )();
370 </script>
371 <?php
372 }
373
374 /**
375 * The modern block editor renders the canvas inside an iframe with its own
376 * document. Styles enqueued via enqueue_block_editor_assets land in the
377 * OUTER page, not the iframe, so Leaflet's CSS never reaches the map and
378 * tiles render unstyled/scattered in the editor. Injecting the bundled
379 * Leaflet CSS through the editor settings 'styles' array places it inside
380 * the iframe canvas, which is where the block (and therefore the map)
381 * actually renders.
382 */
383 function addLeafletEditorIframeStyle( $settings ) {
384 $leaflet_css_path = BPSC_DIR_PATH . 'assets/leaflet/leaflet.css';
385 if ( ! isset( $settings['styles'] ) || ! is_array( $settings['styles'] ) ) {
386 $settings['styles'] = [];
387 }
388 if ( file_exists( $leaflet_css_path ) ) {
389 $settings['styles'][] = [
390 'css' => file_get_contents( $leaflet_css_path ), // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading a bundled local CSS file.
391 ];
392 } else {
393 // Fallback when the bundled CSS is unavailable. The URL lives in a
394 // CSS @import string, not in a wp_enqueue_* call.
395 $settings['styles'][] = [
396 'css' => "@import url('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css');",
397 ];
398 }
399 return $settings;
400 }
401 }
402
403
404 new BPSCPlugin();
405 }
406
407