';
}
echo $message;
}
function showAdminNotice_ElementorMinimumVersion(): void
{
$message = sprintf(
/* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */
esc_html__('%1$s plugin requires %2$s plugin version %3$s or greater.', 'king-addons'),
esc_html__('King Addons', 'king-addons'),
esc_html__('Elementor', 'king-addons'),
'3.19.0'
);
echo '
' . esc_html($message) . '
';
}
public function initElementor(): void
{
add_action('elementor/widgets/register', [$this, 'registerWidgets']);
add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueEditorStyles']);
add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueEditorScripts']);
add_action('elementor/preview/enqueue_styles', [$this, 'enqueueEditorPreviewStyles']);
}
function enqueueEditorPreviewStyles(): void
{
wp_enqueue_style(
KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-preview',
KING_ADDONS_URL . 'includes/admin/css/elementor-preview.css',
[],
KING_ADDONS_VERSION
);
}
function addWidgetCategory(): void
{
$elements_manager = Plugin::instance()->elements_manager;
// Add our categories
$elements_manager->add_category(
'king-addons',
[
'title' => esc_html__('King Addons', 'king-addons'),
'icon' => 'fa fa-plug'
]
);
$elements_manager->add_category(
'king-addons-woo-builder',
[
'title' => esc_html__('King Addons Woo Builder', 'king-addons'),
'icon' => 'fa fa-shopping-cart'
]
);
// Move our categories to the top of the panel
$this->reorderWidgetCategories($elements_manager);
}
/**
* Reorder widget categories so King Addons categories appear after Layout and Basic.
*
* @param \Elementor\Elements_Manager $elements_manager
* @return void
*/
private function reorderWidgetCategories($elements_manager): void
{
try {
$reflection = new \ReflectionClass($elements_manager);
$categories_property = $reflection->getProperty('categories');
$categories_property->setAccessible(true);
$categories = $categories_property->getValue($elements_manager);
if (!is_array($categories)) {
return;
}
// Extract our categories
$our_categories = [];
if (isset($categories['king-addons'])) {
$our_categories['king-addons'] = $categories['king-addons'];
unset($categories['king-addons']);
}
if (isset($categories['king-addons-woo-builder'])) {
$our_categories['king-addons-woo-builder'] = $categories['king-addons-woo-builder'];
unset($categories['king-addons-woo-builder']);
}
// Insert our categories after Layout and Basic
$reordered = [];
$insert_after = ['layout', 'basic']; // Categories after which we insert ours
$inserted = false;
foreach ($categories as $key => $value) {
$reordered[$key] = $value;
// Insert our categories after the last target category
if (!$inserted && in_array($key, $insert_after, true)) {
// Check if next category is also in our target list
$keys = array_keys($categories);
$current_index = array_search($key, $keys, true);
$next_key = $keys[$current_index + 1] ?? null;
// Only insert if the next category is NOT in our target list
if ($next_key === null || !in_array($next_key, $insert_after, true)) {
$reordered = array_merge($reordered, $our_categories);
$inserted = true;
}
}
}
// If target categories weren't found, append at the end
if (!$inserted) {
$reordered = array_merge($reordered, $our_categories);
}
// Set back the reordered array
$categories_property->setValue($elements_manager, $reordered);
} catch (\ReflectionException $e) {
// Silently fail if reflection doesn't work (e.g., future Elementor changes)
}
}
/**
* Registers Elementor widgets with a mechanism to skip (and remember) broken widgets
* that caused a fatal error previously, and try them again if the plugin version is updated.
*
* @param Widgets_Manager $widgets_manager
* @return void
*/
function registerWidgets(Widgets_Manager $widgets_manager): void
{
// Used to track which widget is currently being loaded when a fatal error occurs
static $currentlyLoadingWidgetId = null;
$currentPluginVersion = KING_ADDONS_VERSION;
// Get plugin options to check if a widget is enabled
$options = get_option('king_addons_options');
$options = is_array($options) ? $options : [];
// Extension toggles (used to prevent loading dependent widgets when extension is disabled).
$wishlist_extension_enabled = !isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled';
if (defined('KING_ADDONS_EXT_WISHLIST') && KING_ADDONS_EXT_WISHLIST === false) {
$wishlist_extension_enabled = false;
}
// Ensure Woo Builder base class is available for single product widgets.
$abstract_single_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Single_Widget.php';
if (file_exists($abstract_single_widget)) {
require_once $abstract_single_widget;
}
// Ensure Woo Builder base class is available for archive widgets.
$abstract_archive_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Archive_Widget.php';
if (file_exists($abstract_archive_widget)) {
require_once $abstract_archive_widget;
}
// Ensure Woo Builder base class is available for cart widgets.
$abstract_cart_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Cart_Widget.php';
if (file_exists($abstract_cart_widget)) {
require_once $abstract_cart_widget;
}
// Ensure Woo Builder base class is available for checkout widgets.
$abstract_checkout_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Checkout_Widget.php';
if (file_exists($abstract_checkout_widget)) {
require_once $abstract_checkout_widget;
}
// Ensure Woo Builder base class is available for My Account widgets.
$abstract_my_account_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_My_Account_Widget.php';
if (file_exists($abstract_my_account_widget)) {
require_once $abstract_my_account_widget;
}
/**
* Retrieve the array of broken widgets from the WordPress options.
* The structure is expected to be something like:
*
* 'widget_id' => [
* 'version' => '1.2.0',
* 'error' => 'Some fatal error message'
* ],
* ...
*
*/
$brokenWidgets = get_option('king_addons_broken_widgets', []);
/**
* STEP 1: Clear out any "broken widgets" where the stored version is
* less than the current plugin version. This gives them a second chance
* after an update, assuming the issue may have been fixed.
*/
foreach ($brokenWidgets as $brokenId => $brokenData) {
if (
isset($brokenData['version'])
&& version_compare($currentPluginVersion, $brokenData['version'], '>')
) {
// If the plugin version is now higher, we remove the widget from the blacklist
unset($brokenWidgets[$brokenId]);
}
}
// Update the option after cleaning up
update_option('king_addons_broken_widgets', $brokenWidgets);
/**
* STEP 2: Use register_shutdown_function to detect any fatal errors (E_ERROR, E_PARSE, etc.)
* that might occur during the loading of a widget. If an error is detected, store that widget
* in the "broken" list with the current plugin version and the error message.
*/
register_shutdown_function(function () use (&$currentlyLoadingWidgetId, $currentPluginVersion) {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// If a fatal error occurred while loading a specific widget
if (!empty($currentlyLoadingWidgetId)) {
$brokenWidgetsLocal = get_option('king_addons_broken_widgets', []);
$brokenWidgetsLocal[$currentlyLoadingWidgetId] = [
'version' => $currentPluginVersion,
'error' => $error['message'] ?? ''
];
update_option('king_addons_broken_widgets', $brokenWidgetsLocal);
}
}
});
/**
* STEP 3: Now we iterate through all widgets in our modules map and try to load them.
* If a widget is in the broken list, we skip it to avoid repeated fatal errors.
*/
foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
// Hard-disable via constant (used to QA/rollout new widgets).
$widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
if (defined($widget_constant) && constant($widget_constant) === false) {
continue;
}
// Check if the widget is enabled in the options
if (!isset($options[$widget_id]) || $options[$widget_id] !== 'enabled') {
continue;
}
// Skip Wishlist widgets when Wishlist extension is disabled.
// This prevents fatals when wishlist classes aren't loaded.
if (!$wishlist_extension_enabled && strpos((string) $widget_id, 'wishlist-') === 0) {
continue;
}
// If this widget is listed as broken, skip it
if (array_key_exists($widget_id, $brokenWidgets)) {
// Log something here if needed:
// error_log("Skipping widget {$widget_id}, it previously caused a fatal error.");
continue;
}
// Track which widget we're loading
$currentlyLoadingWidgetId = $widget_id;
// Include the base widget class
$widget_class = $widget['php-class'];
$path_widget_class = "King_Addons\\" . $widget_class;
$widget_file = KING_ADDONS_PATH . 'includes/widgets/' . $widget_class . '/' . $widget_class . '.php';
if (!file_exists($widget_file)) {
// Skip missing widget files to avoid fatal errors if registry is ahead of implementation.
$currentlyLoadingWidgetId = null;
continue;
}
require_once $widget_file;
// Check if we can load the Pro version
if (
function_exists('king_addons_freemius')
&& king_addons_freemius()->can_use_premium_code__premium_only()
&& defined('KING_ADDONS_PRO_PATH')
) {
if (!empty($widget['has-pro'])) {
$pro_file_path = KING_ADDONS_PRO_PATH . 'includes/widgets/' . $widget_class . '_Pro/' . $widget_class . '_Pro.php';
if (file_exists($pro_file_path)) {
require_once($pro_file_path);
$path_widget_class_pro = "King_Addons\\" . $widget_class . '_Pro';
$widgets_manager->register(new $path_widget_class_pro);
} else {
// If Pro file doesn't exist, register the base widget
$widgets_manager->register(new $path_widget_class);
}
} else {
// No 'has-pro', register the base widget
$widgets_manager->register(new $path_widget_class);
}
} else {
// No Freemius Pro available, register the base widget
$widgets_manager->register(new $path_widget_class);
}
// Clear the tracking variable after successful load
$currentlyLoadingWidgetId = null;
}
}
function enableWidgetsByDefault(): void
{
$options = get_option('king_addons_options');
foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
// Hard-disable via constant (used to QA/rollout new widgets).
$widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
if (defined($widget_constant) && constant($widget_constant) === false) {
continue;
}
if (!($options[$widget_id] ?? null)) {
$options[$widget_id] = 'enabled';
update_option('king_addons_options', $options);
}
}
}
/**
* Enable and bootstrap registered features.
*
* Loads free feature classes and, when available and licensed, their Pro counterparts.
*
* @return void
*/
public function enableFeatures(): void
{
$options = get_option('king_addons_options');
foreach (ModulesMap::getModulesMapArray()['features'] as $feature_id => $feature) {
// Hard-disable via constant (used to QA/rollout new features).
$feature_constant = 'KING_ADDONS_FEAT_' . strtoupper(str_replace('-', '_', (string) $feature_id));
if (defined($feature_constant) && constant($feature_constant) === false) {
continue;
}
if (!($options[$feature_id] ?? null)) {
$options[$feature_id] = 'enabled';
update_option('king_addons_options', $options);
}
if ($options[$feature_id] !== 'enabled') {
continue;
}
$feature_class = $feature['php-class'];
$path_feature_class = "King_Addons\\" . $feature_class;
$feature_file = KING_ADDONS_PATH . 'includes/features/' . $feature_class . '/' . $feature_class . '.php';
if (file_exists($feature_file)) {
require_once $feature_file;
}
$pro_loaded = false;
if (
!empty($feature['has-pro'])
&& function_exists('king_addons_freemius')
&& king_addons_freemius()->can_use_premium_code__premium_only()
&& defined('KING_ADDONS_PRO_PATH')
) {
$pro_file_path = KING_ADDONS_PRO_PATH . 'includes/features/' . $feature_class . '_Pro/' . $feature_class . '_Pro.php';
if (file_exists($pro_file_path)) {
require_once $pro_file_path;
$path_feature_class_pro = "King_Addons\\" . $feature_class . '_Pro';
if (class_exists($path_feature_class_pro)) {
new $path_feature_class_pro();
$pro_loaded = true;
}
}
}
if (!$pro_loaded && class_exists($path_feature_class)) {
new $path_feature_class();
}
}
}
public function registerControls(Controls_Manager $controls_manager): void
{
$controls_manager->register(new AJAX_Select2\Ajax_Select2());
$controls_manager->register(new Animations\Animations());
$controls_manager->register(new Animations\Animations_Alternative());
$controls_manager->register(new Button_Animations\Button_Animations());
}
function enqueueEditorStyles(): void
{
wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/css/elementor-editor.css', '', KING_ADDONS_VERSION);
}
function enqueueEditorScripts(): void
{
wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/js/elementor-editor.js', '', KING_ADDONS_VERSION);
// Localize script with PRO status
wp_localize_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', 'kingAddonsEditor', [
'isPro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false
]);
wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-data-table-export', KING_ADDONS_URL . 'includes/widgets/Data_Table/preview-handler.js', '', KING_ADDONS_VERSION);
if (KING_ADDONS_WGT_FORM_BUILDER) {
wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-form-builder-editor-handler', KING_ADDONS_URL . 'includes/widgets/Form_Builder/editor-handler.js', '', KING_ADDONS_VERSION);
}
}
public static function renderProFeaturesSection($module, $section, $type, $widget_name, $features): void
{
if (king_addons_freemius()->can_use_premium_code__premium_only()) {
return;
}
$module->start_controls_section(
'king_addons_pro_features_section',
[
'label' => KING_ADDONS_ELEMENTOR_ICON_PRO . '' . esc_html__('Pro Features', 'king-addons') . '',
'tab' => $section ?: null,
]
);
$list_html = '