settings = Settings::get_instance([ 'key' => 'notificationx', 'auto_commit' => true, 'debug' => false, 'store' => 'options', ]); $args = Settings::get_instance()->get_role_map(); new WPDRoleManagement($args); Upgrader::get_instance(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed for the NotificationX codebase: acceptable in this context. if (is_admin() || empty($_GET['frontend']) || $_GET['frontend'] != true) { Admin::get_instance(); } FrontEnd::get_instance(); add_action('admin_init', [$this, 'maybe_redirect'], 10); add_action('init', [$this, 'init'], 10); add_action('plugins_loaded', array($this, 'init_extension')); add_filter('nx_pro_alert_popup', array($this, 'pro_alert_popup')); add_action( 'init', [ $this, 'register_custom_image_size' ] ); /** * Register all REST Endpoint */ REST::get_instance(); // Country/user-role targeting for ALL notification types (decoupled from the bar module). Targeting::get_instance(); Cron::get_instance(); QuickBuild::get_instance(); ShortcodeInline::get_instance(); Blocks::get_instance(); CoreInstaller::get_instance(basename(NOTIFICATIONX_FILE, '.php')); // 3rd Party features. WPML::get_instance(); VisualPortfolio::get_instance(); if ( did_action( 'elementor/loaded' ) ) { ElementorManager::get_instance(); } else { add_action( 'elementor/loaded', [ ElementorManager::class, 'get_instance' ] ); } EntriesMailReceiver::get_instance(); // MCP server (AI-assistant integration). Progressive enhancement: // loads only on a capable PHP runtime and stays off until enabled. \NotificationX\MCP\Bootstrap::init(); } /** * The Plugin Activator * @return void */ public function activator(){ // nx_activated Database::get_instance()->Create_DB(); // A first-ever activation is the only moment the onboarding wizard may // be auto-launched; an abandoned wizard still counts as "already // activated", so re-activations never force it again. $is_first_activation = ! self::has_activated_before(); if ( $is_first_activation ) { update_option( self::FIRST_ACTIVATION_OPTION, time(), 'no' ); } if( ! static::$WP_CLI && current_user_can( 'delete_users' ) ) { set_transient( 'nx_activated', $is_first_activation ? 'first' : 'again', 30 ); } // Usage insights are normally wired up on `init`, which WordPress has // already fired by the time it activates a plugin — so the // register_activation_hook() callback registered inside the insights // constructor never runs and the tracking event is left unscheduled. // Build the instance and run its activation routine explicitly here so // data collection is live from activation onwards. Admin::get_instance()->plugin_usage_insights()->activate_this_plugin(); Upgrader::get_instance()->clear_transient(); } /** * Whether NotificationX has already gone through its initial activation on * this site — i.e. it has been activated before, regardless of whether the * Setup Wizard was ever opened or completed. * * @since 3.3.0 * @return bool */ public static function has_activated_before() { return (bool) get_option( self::FIRST_ACTIVATION_OPTION, false ); } public function maybe_redirect(){ if( static::$WP_CLI || wp_doing_ajax() ) { return; } // Bail if no activation transient is set. $activation = get_transient( 'nx_activated' ); if ( ! $activation ) { return; } // Delete the activation transient. delete_transient( 'nx_activated' ); if ( ! is_multisite() ) { // Only a genuine first-ever activation may launch the onboarding // Setup Wizard (and only while it has not been completed/skipped). // Every returning activation lands on the dashboard, as before. // Transients written by an older version hold `true`, which is // treated as a returning activation. $is_first_activation = ( 'first' === $activation ); $page = ( $is_first_activation && ! \NotificationX\Core\SetupWizard::is_completed() ) ? 'nx-setup-wizard' : 'nx-dashboard'; wp_safe_redirect( add_query_arg( array( 'page' => $page ), admin_url( 'admin.php' ) ) ); } } public function init_extension() { // TypeFactory::get_instance(); ExtensionFactory::get_instance(); } public function init() { /** * Run All Actions and Filters * for GOOD. */ // load_plugin_textdomain( 'notificationx', false, dirname( plugin_basename( NOTIFICATIONX_FILE ) ) . '/languages' ); // @todo remove if(defined('NX_DEBUG') && NX_DEBUG){ add_action('wp_ajax_nx', [$this, 'get_tabs']); // executed when logged in } add_action( 'plugin_action_links_' . NOTIFICATIONX_BASENAME, array($this, 'nx_action_links'), 10, 1); } public function pro_alert_popup($args) { if ( !empty($args)){ $args = wp_parse_args($args, [ "showConfirmButton"=> true, "showCloseButton"=>true, "title" => __('Opps! This is PRO Feature.', 'notificationx'), "customClass"=> [ "container"=> 'pro-video-popup', "closeButton"=> 'pro-video-close-button', "icon"=> 'pro-video-icon', "title"=> 'pro-video-title', "content"=> 'pro-video-content', "actions"=> 'nx-pro-alert-actions', "confirmButton"=> 'pro-video-confirm-button', "denyButton"=> 'pro-video-deny-button', ], ]); } return $args; } /** * This function is hooked * @hooked plugin_action_links_ * @param array $links * @return array * @since 1.2.4 */ public function nx_action_links( $links ) { $deactivate_link = isset( $links['deactivate'] ) ? $links['deactivate'] : ''; unset($links['deactivate']); $links['settings'] = '' . __('Settings','notificationx') .''; if( ! empty( $deactivate_link ) ) { $links['deactivate'] = $deactivate_link; } if( ! is_plugin_active('notificationx-pro/notificationx-pro.php' ) ) { $links['pro'] = '' . __('Go Pro','notificationx') .''; } return $links; } /** * Checks whether pro plugin is active. * * @return boolean */ public static function is_pro() { return class_exists('\NotificationXPro\NotificationX'); } /** * Get Tabs array in json. * * @return json */ public function get_tabs() { $tabs = GlobalFields::get_instance()->tabs(); // $tabs = Settings::get_instance()->get_form_data(); $fields = $this->get_field_names($tabs['tabs']); wp_send_json($tabs); } /** * Convert `fields` associative array to numeric array recursively. * @todo improve implementation. * * @param array $arr * @return array */ public function normalize($arr) { if (!empty($arr['fields'])) { $arr['fields'] = array_values($arr['fields']); } if (!empty($arr['options'])) { $arr['options'] = array_values($arr['options']); } if (!empty($arr['tabs'])) { $arr['tabs'] = array_values($arr['tabs']); } if (is_array($arr)) { foreach ($arr as $key => $value) { if (is_array($value)) { $arr[$key] = $this->normalize($value); } } } return $arr; } public function normalize_post($settings) { $fields = $this->get_field_names(); foreach ($fields as $key => $value) { if (!isset($settings[$key]) && isset($value['default'])) { $settings[$key] = $value['default']; } if (isset( $value['type'] ) && ($value['type'] == 'checkbox' || $value['type'] == 'toggle')) { $settings[$key] = (bool) (isset($settings[$key]) ? $settings[$key] : false); } if (isset( $value['type'] ) && $value['type'] == 'number') { $settings[$key] = isset($settings[$key]) ? $settings[$key] : 0; $settings[$key] = is_numeric($settings[$key]) ? $settings[$key] + 0 : 0; } } return $this->normalize_field_types($settings, $fields); } /** * Coerce stored values to the shape their consumers expect, without filling * defaults. Runs on every read (normalize_post()) and before every save * (PostType::save_post()), so MCP/REST callers that send the wrong JSON type * can neither store it nor fatal the front end with it. * * @param array $settings Notification settings. * @param array|null $fields Field list from get_field_names(); fetched when null. * @return array */ public function normalize_field_types($settings, $fields = null) { if (!is_array($settings)) { return $settings; } if (null === $fields) { $fields = $this->get_field_names(); } foreach ($fields as $key => $value) { if (!array_key_exists($key, $settings)) { continue; } if (!empty($value['multiple'])) { $settings[$key] = $this->normalize_multiple_value($settings[$key]); } elseif (isset($value['type']) && in_array($value['type'], self::STRING_FIELD_TYPES, true)) { $settings[$key] = $this->normalize_string_value($settings[$key]); } elseif (isset($value['type']) && in_array($value['type'], self::ROWS_FIELD_TYPES, true)) { $settings[$key] = $this->normalize_rows_value($settings[$key]); } } // The field list above comes from a cached builder config, so a field // whose module was off when it was built is missing from it. These keys // fatal the front end when they have the wrong type; always coerce them. foreach (self::GUARDED_FIELDS as $key => $shape) { if (!array_key_exists($key, $settings)) { continue; } if ('string' === $shape) { $settings[$key] = $this->normalize_string_value($settings[$key]); } elseif ('map' === $shape) { $settings[$key] = is_array($settings[$key]) ? $settings[$key] : (is_object($settings[$key]) ? (array) $settings[$key] : []); } elseif ('rows' === $shape) { $settings[$key] = $this->normalize_rows_value($settings[$key]); } } return $settings; } /** * Field types whose value every consumer treats as a string (explode(), * strtotime(), preg_match_all(), ...). */ const STRING_FIELD_TYPES = ['text', 'textarea', 'date', 'timepicker', 'advanced-codeviewer']; /** * Field types whose value is a list of rows, each row an array. */ const ROWS_FIELD_TYPES = ['advanced-repeater', 'repeater']; /** * Keys coerced even when they are missing from the cached field list. * `notification-template` is a `group` (param => tag map); `custom_contents` * is the Custom Notification repeater; `custom_ids` / `taxonomy_ids` are the * Pro "comma separated IDs" text fields that Locations explode(). */ const GUARDED_FIELDS = [ 'notification-template' => 'map', 'custom_contents' => 'rows', 'custom_ids' => 'string', 'taxonomy_ids' => 'string', ]; /** * MCP and REST saves store the JSON the caller sent, so a text field can * arrive as a list (e.g. custom_ids: [12, 34]). Join a list of scalars with * commas, the format these fields document; anything else becomes ''. * * @param mixed $value Stored value. * @return mixed The value unchanged when it is not an array/object. */ public function normalize_string_value($value) { if (is_object($value)) { $value = (array) $value; } if (!is_array($value)) { return $value; } foreach ($value as $item) { if (!is_scalar($item)) { return ''; } } return implode(',', $value); } /** * A repeater value must be a list of rows, each row an array. Drop anything * else, so row consumers (shuffle(), array_splice(), $row['key'] = ...) * never meet a string. * * @param mixed $value Stored value. * @return array */ public function normalize_rows_value($value) { if (is_object($value)) { $value = (array) $value; } if (!is_array($value)) { return []; } $rows = []; foreach ($value as $key => $row) { if (is_object($row)) { $row = (array) $row; } if (is_array($row)) { $rows[$key] = $row; } } return $rows; } /** * Multi-select fields (`'multiple' => true`) mostly declare `'default' => ''`, * and MCP / Quick Builder creates store that '' as-is. Consumers pass these * values to array_diff() / in_array(), which throw a TypeError on PHP 8 for a * string. Always hand out an array: '' and null become [], a single scalar * becomes [ scalar ]. * * @param mixed $value Stored value. * @return array */ public function normalize_multiple_value($value) { if (is_array($value)) { return $value; } if (is_object($value)) { return (array) $value; } if (null === $value || '' === $value || false === $value) { return []; } return [$value]; } public function get_tab(){ $tabs = get_transient('nx_builder_fields'); if(empty($tabs) || (defined('NX_DEBUG') && NX_DEBUG)){ $tabs = GlobalFields::get_instance()->tabs(); set_transient( 'nx_builder_fields', $tabs, DAY_IN_SECONDS ); } return $tabs; } public function get_field_names($tabs = null){ $fields = []; if(empty($tabs)){ $tabs = $this->get_tab(); } if(!empty($tabs['tabs'])){ $fields = $this->_get_field_names($tabs['tabs']); } return $fields; } public function get_field($field_name){ $fields = $this->get_field_names(); if(!empty($fields[$field_name])){ return $fields[$field_name]; } } // @todo maybe remove if not used in future. public function _get_field_names($fields, $names = []) { foreach ($fields as $key => $field) { if (empty($field['type'])) { $names = $this->_get_field_names($field['fields'], $names); } else if ($field['type'] == 'section' || $field['type'] == 'group') { // $_names = $this->_get_field_names($field['fields'], []); if ($field['type'] == 'section') $names = array_merge($names, $_names); else foreach ($_names as $key => $value) { $names[$field['name']][$key] = [ 'type' => $value['type'], 'default' => isset($value['default']) ? $value['default'] : '', 'help' => isset($value['help']) ? $value['help'] : '', 'label' => isset($value['label']) ? $value['label'] : '', 'parent_label' => isset($field['label']) ? $field['label'] : '', ]; } } elseif (!empty($field['name'])) { $names[$field['name']] = [ 'type' => $field['type'], 'default' => isset($field['default']) ? $field['default'] : '', 'help' => isset($field['help']) ? $field['help'] : '', 'label' => isset($field['label']) ? $field['label'] : '', 'multiple' => isset($field['multiple']) ? $field['multiple'] : '', ]; } } return $names; } function my_upgrade_function( $upgrader_object, $options ) { $current_plugin_path_name = plugin_basename( __FILE__ ); if ($options['action'] == 'update' && $options['type'] == 'plugin' ) { foreach($options['plugins'] as $each_plugin) { if ($each_plugin==$current_plugin_path_name) { // .......................... YOUR CODES ............. } } } } public function register_custom_image_size() { add_image_size('_nx_notification_thumb_100_100', 100, 100, true); add_image_size('_nx_notification_thumb_200_200', 200, 200, true); add_image_size('_nx_notification_thumb_300_300', 300, 300, true); add_image_size('_nx_notification_thumb_400_400', 400, 400, true); add_image_size('_nx_notification_thumb_500_500', 500, 500, true); } }