PluginProbe
Hester Core / 1.1.7
Hester Core v1.1.7
1.1.8 1.1.9 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 trunk 1.0.0 1.0.1 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2
hester-core / hester-core.php

hester-core.php in Hester Core 1.1.7, at hester-core.php

436 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Hester Core
4 * Description: The official companion plugin for Peregrine Themes. Adds widgets, customization options, Elementor widgets, and demo import features.
5 * Author: Peregrine Themes
6 * Author URI: https://peregrine-themes.com
7 * Version: 1.1.6
8 * Text Domain: hester-core
9 * Domain Path: /languages
10 * Requires at least: 5.9
11 * Requires PHP: 7.4
12 * Tested up to: 7.0
13 *
14 * Hester Core is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 2 of the License, or
17 * any later version.
18 *
19 * Hester Core is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Hester Core. If not, see <https://www.gnu.org/licenses/>.
26 *
27 * @category Plugin
28 * @package Hester_Core
29 * @link https://peregrine-themes.com
30 * @copyright 2022 Peregrine Themes
31 * @author Peregrine Themes <peregrinethemes@gmail.com>
32 * @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0-or-later
33 * @since 1.0.0
34 */
35
36 // Exit if accessed directly.
37 if ( ! defined( 'ABSPATH' ) ) {
38 exit;
39 }
40
41 /**
42 * Theme templates that receive full Hester Core support (widgets, admin, Elementor, etc.).
43 *
44 * @since 1.1.1
45 * @var string[]
46 */
47 define(
48 'HESTER_CORE_SUPPORTED_THEMES',
49 array(
50 'hester',
51 'hester-pro',
52 'blogun',
53 'blogun-pro',
54 'bloglo',
55 'bloglo-pro',
56 'bloghash',
57 'bloghash-pro',
58 'shopwell',
59 'blogsy',
60 )
61 );
62
63 /**
64 * Subset of supported themes that load the widgets component.
65 *
66 * 'shopwell' and 'blogsy' are intentionally excluded — they do not
67 * use the shared widgets provided by this plugin.
68 *
69 * @since 1.1.1
70 * @var string[]
71 */
72 define(
73 'HESTER_CORE_WIDGET_THEMES',
74 array(
75 'hester',
76 'hester-pro',
77 'blogun',
78 'blogun-pro',
79 'bloglo',
80 'bloglo-pro',
81 'bloghash',
82 'bloghash-pro',
83 )
84 );
85
86 /**
87 * Main Hester Core class.
88 *
89 * @package Hester_Core
90 * @since 1.0.0
91 */
92 final class Hester_Core {
93
94 /**
95 * Plugin version.
96 *
97 * @since 1.0.0
98 * @var string
99 */
100 public $version = '1.1.7';
101
102 /**
103 * Active theme template slug (e.g. "hester", "bloglo").
104 *
105 * @since 1.0.0
106 * @var string
107 */
108 public $theme_name = 'hester';
109
110 /**
111 * Singleton instance.
112 *
113 * @since 1.0.0
114 * @var Hester_Core|null
115 */
116 private static $instance = null;
117
118 /**
119 * Returns the single instance of the class, creating it on first call.
120 *
121 * @since 1.0.0
122 * @return Hester_Core
123 */
124 public static function instance() {
125 if ( null === self::$instance ) {
126 self::$instance = new self();
127 self::$instance->constants();
128 self::$instance->load_textdomain();
129 self::$instance->set_theme_name();
130 self::$instance->includes();
131
132 add_action( 'plugins_loaded', array( self::$instance, 'on_plugins_loaded' ), 10 );
133 }
134
135 return self::$instance;
136 }
137
138 /**
139 * Private constructor — use ::instance().
140 *
141 * @since 1.0.0
142 */
143 private function __construct() {
144 add_action( 'after_switch_theme', array( $this, 'after_theme_switch' ) );
145 }
146
147 /**
148 * After theme switch.
149 *
150 * @since 1.1.6
151 */
152 public function after_theme_switch() {
153 // Delete theme demos transient.
154 delete_transient( 'hester_core_demo_templates' );
155 }
156
157 /**
158 * Defines plugin constants.
159 *
160 * @since 1.0.0
161 */
162 private function constants() {
163 $constants = array(
164 'HESTER_CORE_VERSION' => $this->version,
165 'HESTER_CORE_PLUGIN_DIR' => plugin_dir_path( __FILE__ ),
166 'HESTER_CORE_PLUGIN_URL' => plugin_dir_url( __FILE__ ),
167 'HESTER_CORE_PLUGIN_FILE' => __FILE__,
168 'HESTER_CORE_ELEMENTOR_PATH' => plugin_dir_path( __FILE__ ) . 'core/elementor/',
169 'HESTER_CORE_ELEMENTOR_URL' => plugin_dir_url( __FILE__ ) . 'core/elementor/',
170 );
171
172 foreach ( $constants as $name => $value ) {
173 if ( ! defined( $name ) ) {
174 define( $name, $value );
175 }
176 }
177 }
178
179 /**
180 * Loads plugin text domain for translations.
181 *
182 * @since 1.0.0
183 */
184 public function load_textdomain() {
185 load_plugin_textdomain(
186 'hester-core',
187 false,
188 dirname( plugin_basename( __FILE__ ) ) . '/languages/'
189 );
190 }
191
192 /**
193 * Resolves and stores the active theme's base template slug.
194 *
195 * @since 1.0.0
196 */
197 private function set_theme_name() {
198 if ( preg_match( '/^([\w]+)/', wp_get_theme()->template, $match ) ) {
199 $this->theme_name = strtolower( $match[0] );
200 }
201 }
202
203 /**
204 * Includes required files based on the active theme and environment.
205 *
206 * @since 1.0.0
207 */
208 private function includes() {
209 $theme_template = wp_get_theme()->template;
210
211 // Widgets — only for themes that use the shared widget component.
212 // 'shopwell' and 'blogsy' are excluded intentionally.
213 if ( in_array( $theme_template, HESTER_CORE_WIDGET_THEMES, true ) ) {
214 require_once HESTER_CORE_PLUGIN_DIR . 'core/widgets/widgets.php';
215 }
216
217 // Admin class — always required.
218 require_once HESTER_CORE_PLUGIN_DIR . 'core/admin/class-hester-core-admin.php';
219
220 // Elementor integration — only when Elementor is active.
221 if ( did_action( 'elementor/loaded' ) ) {
222 require_once HESTER_CORE_ELEMENTOR_PATH . 'plugin.php';
223 \Hester_Core\Elementor\Plugin::instance();
224 }
225
226 // WP-CLI commands.
227 if ( defined( 'WP_CLI' ) && WP_CLI ) {
228 require_once HESTER_CORE_PLUGIN_DIR . 'core/cli/class-hester-core-cli.php';
229 }
230
231 // Theme-specific extras.
232 if ( in_array( $theme_template, array( 'hester' ), true ) ) {
233 require_once HESTER_CORE_PLUGIN_DIR . 'themes/hester/hester.php';
234 }
235 }
236
237 /**
238 * Fires the hester_core_loaded action once all dependencies are available.
239 *
240 * @since 1.0.0
241 */
242 public function on_plugins_loaded() {
243 /**
244 * Fires after Hester Core is fully loaded.
245 *
246 * @since 1.0.0
247 */
248 do_action( 'hester_core_loaded' );
249 }
250 }
251
252 /**
253 * Returns the single Hester_Core instance.
254 *
255 * Preferred usage:
256 * $hester_core = hester_core();
257 *
258 * @since 1.0.0
259 * @return Hester_Core
260 */
261 function hester_core() {
262 return Hester_Core::instance();
263 }
264
265 // Bootstrap the plugin only for supported themes; show an admin notice otherwise.
266 if ( hester_core_is_supported_theme() ) {
267 hester_core();
268 } else {
269 add_action( 'admin_notices', 'hester_core_unsupported_theme_notice' );
270 }
271
272 // -------------------------------------------------------------------------
273 // Helper functions
274 // -------------------------------------------------------------------------
275
276 /**
277 * Checks whether the currently active theme is supported by Hester Core.
278 *
279 * @since 1.1.1
280 * @return bool
281 */
282 function hester_core_is_supported_theme() {
283 $theme = wp_get_theme();
284 $parent_theme = $theme->parent() ? $theme->parent() : $theme;
285
286 // Check explicit list (for backwards compatibility).
287 if ( in_array( $parent_theme->template, HESTER_CORE_SUPPORTED_THEMES, true ) ) {
288 return true;
289 }
290
291 // Automatically support any theme authored by Peregrine Themes.
292 if ( strpos( $parent_theme->get( 'Author' ), 'Peregrine Themes' ) !== false ) {
293 return true;
294 }
295
296 // Allow filtering for external themes or special cases.
297 return apply_filters( 'hester_core_is_supported_theme', false, $theme );
298 }
299
300 // -------------------------------------------------------------------------
301 // Admin notices
302 // -------------------------------------------------------------------------
303
304 /**
305 * Displays an admin notice when an unsupported theme is active.
306 *
307 * @since 1.0.0
308 */
309 function hester_core_unsupported_theme_notice() {
310 ?>
311 <div class="notice notice-warning">
312 <p><?php esc_html_e( 'Please activate one of Peregrine Themes before activating Hester Core.', 'hester-core' ); ?></p>
313 </div>
314 <?php
315 }
316
317 /**
318 * Displays a dismissible welcome notice after the plugin is activated.
319 *
320 * @since 1.1.1
321 */
322 function hester_core_welcome_notice() {
323 if ( ! current_user_can( 'manage_options' ) ) {
324 return;
325 }
326
327 if ( ! get_option( 'hester_core_show_welcome_notice', true ) ) {
328 return;
329 }
330
331 if ( ! hester_core_is_supported_theme() ) {
332 return;
333 }
334
335 $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
336
337 $allowed_pages = array(
338 hester_core()->theme_name . '-dashboard',
339 hester_core()->theme_name . '-theme-library',
340 hester_core()->theme_name . '-demo-library',
341 hester_core()->theme_name . '-changelog',
342 hester_core()->theme_name . '-plugins',
343 hester_core()->theme_name . '-about',
344 );
345
346 if ( in_array( $page, $allowed_pages, true ) ) {
347 return;
348 }
349
350 $dashboard_url = admin_url( 'admin.php?page=' . hester_core()->theme_name . '-dashboard' );
351 $theme_library_url = admin_url( 'admin.php?page=' . hester_core()->theme_name . '-theme-library' );
352
353 // Enqueue AJAX script.
354 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
355 wp_enqueue_script( 'hester-core-admin', plugin_dir_url( __FILE__ ) . 'assets/js/admin' . $suffix . '.js', array( 'jquery' ), HESTER_CORE_VERSION, true );
356 wp_localize_script(
357 'hester-core-admin',
358 'hester_core_admin',
359 array(
360 'ajax_url' => admin_url( 'admin-ajax.php' ),
361 'nonce' => wp_create_nonce( 'hester_core_dismiss_welcome_notice' ),
362 'dismiss_text' => __( 'Dismissing...', 'hester-core' ),
363 'dismiss_button_text' => __( 'Dismiss', 'hester-core' ),
364 'error_message' => __( 'Failed to dismiss notice. Please try again.', 'hester-core' ),
365 )
366 );
367 ?>
368 <div class="notice notice-success hester-core-welcome-notice" id="hester-core-welcome-notice">
369 <style>
370 .hester-core-welcome-notice{position:relative;padding:20px;display:flex;align-items:center;justify-content:space-between}
371 .hester-core-welcome-notice .hester-core-welcome-text{flex:1;min-width:0}
372 .hester-core-welcome-notice .hester-core-welcome-text h2{margin:0 0 8px;font-size:18px}
373 .hester-core-welcome-notice .hester-core-welcome-text p{margin:0 0 12px;line-height:1.6}
374 .hester-core-welcome-notice .hester-core-welcome-actions{margin-top:12px}
375 .hester-core-welcome-notice .hester-core-welcome-actions .button{margin-right:8px}
376 </style>
377 <button type="button" class="hester-core-close notice-dismiss" aria-label="<?php esc_attr_e( 'Dismiss notice', 'hester-core' ); ?>" title="<?php esc_attr_e( 'Dismiss notice', 'hester-core' ); ?>">
378 <span class="screen-reader-text"><?php echo esc_html__( 'Dismiss notice.', 'hester-core' ); ?></span>
379 </button>
380 <div class="hester-core-welcome-text">
381 <h2><?php esc_html_e( 'Welcome to Hester Core!', 'hester-core' ); ?></h2>
382 <p>
383 <?php
384 echo wp_kses_post(
385 sprintf(
386 /* translators: %s is a link to the Peregrine Themes library page */
387 __( 'This plugin provides multiple Elementor widgets and other features for themes by %s.', 'hester-core' ),
388 '<a href="' . esc_url( $theme_library_url ) . '">' . esc_html__( 'Peregrine Themes', 'hester-core' ) . '</a>'
389 )
390 );
391 ?>
392 </p>
393 <div class="hester-core-welcome-actions">
394 <a href="<?php echo esc_url( $dashboard_url ); ?>" class="button button-primary">
395 <?php esc_html_e( 'Open Hester Dashboard', 'hester-core' ); ?>
396 </a>
397 <button type="button" class="button button-secondary" id="hester-core-dismiss-notice">
398 <?php esc_html_e( 'Dismiss', 'hester-core' ); ?>
399 </button>
400 </div>
401 </div>
402 </div>
403 <?php
404 }
405 add_action( 'admin_notices', 'hester_core_welcome_notice' );
406
407 // -------------------------------------------------------------------------
408 // Welcome notice dismiss handler (AJAX)
409 // -------------------------------------------------------------------------
410
411 /**
412 * Handles the AJAX request that dismisses the welcome notice.
413 *
414 * @since 1.1.1
415 */
416 function hester_core_ajax_dismiss_welcome_notice() {
417 // Check nonce.
418 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
419 if ( ! wp_verify_nonce( $nonce, 'hester_core_dismiss_welcome_notice' ) ) {
420 wp_send_json_error( array( 'message' => 'Invalid nonce' ) );
421 }
422
423 // Check permissions.
424 if ( ! current_user_can( 'manage_options' ) ) {
425 wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
426 }
427
428 // Create option if missing, otherwise update it.
429 if ( ! add_option( 'hester_core_show_welcome_notice', false ) ) {
430 update_option( 'hester_core_show_welcome_notice', false );
431 }
432
433 wp_send_json_success();
434 }
435 add_action( 'wp_ajax_hester_core_dismiss_welcome_notice', 'hester_core_ajax_dismiss_welcome_notice' );
436