class-redux-framework-plugin.php (16438B)
1 <?php 2 /** 3 * Redux_Framework_Plugin main class 4 * 5 * @package Redux Framework 6 * @since 3.0.0 7 */ 8 9 // Exit if accessed directly. 10 defined( 'ABSPATH' ) || exit; 11 12 if ( ! class_exists( 'Redux_Framework_Plugin', false ) ) { 13 14 /** 15 * Main Redux_Framework_Plugin class 16 * 17 * @since 3.0.0 18 */ 19 class Redux_Framework_Plugin { 20 21 /** 22 * Option array for demo mode. 23 * 24 * @access protected 25 * @var array $options Array of config options, used to check for demo mode 26 * @since 3.0.0 27 */ 28 protected $options = array(); 29 30 /** 31 * Use this value as the text domain when translating strings from this plugin. It should match 32 * the Text Domain field set in the plugin header, as well as the directory name of the plugin. 33 * Additionally, text domains should only contain letters, number and hypens, not underscores 34 * or spaces. 35 * 36 * @access protected 37 * @var string $plugin_slug The unique ID (slug) of this plugin 38 * @since 3.0.0 39 */ 40 protected $plugin_slug = 'redux-framework'; 41 42 /** 43 * Set on network activate. 44 * 45 * @access protected 46 * @var string $plugin_network_activated Check for plugin network activation 47 * @since 3.0.0 48 */ 49 protected $plugin_network_activated = null; 50 51 /** 52 * Class instance. 53 * 54 * @access private 55 * @var \Redux_Framework_Plugin $instance The one true Redux_Framework_Plugin 56 * @since 3.0.0 57 */ 58 private static $instance; 59 60 /** 61 * Crash flag. 62 * 63 * @access private 64 * @var \Redux_Framework_Plugin $crash Crash flag if inside a crash. 65 * @since 4.1.15 66 */ 67 public static $crash = false; 68 69 /** 70 * Get active instance 71 * 72 * @access public 73 * @since 3.1.3 74 * @return self::$instance The one true Redux_Framework_Plugin 75 */ 76 public static function instance(): ?Redux_Framework_Plugin { 77 $path = REDUX_PLUGIN_FILE; 78 79 if ( function_exists( 'get_plugin_data' ) && file_exists( $path ) ) { 80 $data = get_plugin_data( $path ); 81 82 if ( isset( $data ) && isset( $data['Version'] ) && '' !== $data['Version'] ) { 83 $res = version_compare( $data['Version'], '4', '<' ); 84 } 85 86 if ( is_plugin_active( 'redux-framework/redux-framework.php' ) && true === $res ) { 87 echo '<div class="error"><p>' . esc_html__( 'Redux Framework version 4 is activated but not loaded. Redux Framework version 3 is still installed and activated. Please deactivate Redux Framework version 3.', 'redux-framework' ) . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput 88 return null; 89 } 90 } 91 92 if ( ! self::$instance ) { 93 self::$instance = new self(); 94 if ( class_exists( 'ReduxFramework' ) ) { 95 self::$instance->load_first(); 96 } else { 97 self::$instance->get_redux_options(); 98 self::$instance->includes(); 99 self::$instance->hooks(); 100 } 101 } 102 103 return self::$instance; 104 } 105 106 /** 107 * Shim for geting instance 108 * 109 * @access public 110 * @since 4.0.1 111 * @return self::$instance The one true Redux_Framework_Plugin 112 */ 113 public static function get_instance(): ?Redux_Framework_Plugin { 114 return self::instance(); 115 } 116 117 /** 118 * Get Redux options 119 * 120 * @access public 121 * @since 3.1.3 122 * @return void 123 */ 124 public function get_redux_options() { 125 126 // Setup defaults. 127 $defaults = array( 128 'demo' => false, 129 ); 130 131 // If multisite is enabled. 132 if ( is_multisite() ) { 133 134 // Get network activated plugins. 135 $plugins = get_site_option( 'active_sitewide_plugins' ); 136 137 foreach ( $plugins as $file => $plugin ) { 138 if ( strpos( $file, 'redux-framework.php' ) !== false ) { 139 $this->plugin_network_activated = true; 140 $this->options = get_site_option( 'ReduxFrameworkPlugin', $defaults ); 141 } 142 } 143 } 144 145 // If options aren't set, grab them now! 146 if ( empty( $this->options ) ) { 147 $this->options = get_option( 'ReduxFrameworkPlugin', $defaults ); 148 } 149 } 150 151 /** 152 * Include necessary files 153 * 154 * @access public 155 * @since 3.1.3 156 * @return void 157 */ 158 public function includes() { 159 160 // Include Redux_Core. 161 if ( file_exists( dirname( __FILE__ ) . '/redux-core/framework.php' ) ) { 162 require_once dirname( __FILE__ ) . '/redux-core/framework.php'; 163 } 164 165 // Including extendify sdk. 166 if ( true === (bool) get_option( 'use_extendify_templates', true ) ) { 167 if ( file_exists( plugin_dir_path( REDUX_PLUGIN_FILE ) . 'extendify-sdk/loader.php' ) ) { 168 $GLOBALS['extendifySdkSourcePlugin'] = 'Redux'; 169 require_once dirname( __FILE__ ) . '/extendify-sdk/loader.php'; 170 } 171 } 172 173 if ( file_exists( dirname( __FILE__ ) . '/redux-templates/redux-templates.php' ) ) { 174 require_once dirname( __FILE__ ) . '/redux-templates/redux-templates.php'; 175 } 176 177 if ( isset( Redux_Core::$as_plugin ) ) { 178 Redux_Core::$as_plugin = true; 179 } 180 181 add_action( 'setup_theme', array( $this, 'load_sample_config' ) ); 182 183 } 184 185 /** 186 * Loads the sample config after everything is loaded. 187 * 188 * @access public 189 * @since 4.0.2 190 * @return void 191 */ 192 public function load_sample_config() { 193 // Include demo config, if demo mode is active. 194 if ( $this->options['demo'] && file_exists( dirname( __FILE__ ) . '/sample/sample-config.php' ) ) { 195 require_once dirname( __FILE__ ) . '/sample/sample-config.php'; 196 } 197 } 198 199 /** 200 * Run action and filter hooks 201 * 202 * @access private 203 * @since 3.1.3 204 * @return void 205 */ 206 private function hooks() { 207 add_action( 'activated_plugin', array( $this, 'load_first' ) ); 208 add_action( 'wp_loaded', array( $this, 'options_toggle_check' ) ); 209 210 // Activate plugin when new blog is added. 211 add_action( 'wpmu_new_blog', array( $this, 'activate_new_site' ) ); 212 213 // Display admin notices. 214 add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 215 216 // Edit plugin metalinks. 217 add_filter( 'plugin_row_meta', array( $this, 'plugin_metalinks' ), null, 2 ); 218 add_filter( 'network_admin_plugin_action_links', array( $this, 'add_settings_link' ), 1, 2 ); 219 add_filter( 'plugin_action_links', array( $this, 'add_settings_link' ), 1, 2 ); 220 221 // phpcs:ignore WordPress.NamingConventions.ValidHookName 222 do_action( 'redux/plugin/hooks', $this ); 223 } 224 225 /** 226 * Pushes Redux to top of plugin load list, so it initializes before any plugin that may use it. 227 */ 228 public function load_first() { 229 if ( ! class_exists( 'Redux_Functions_Ex' ) ) { 230 require_once dirname( __FILE__ ) . '/redux-core/inc/classes/class-redux-functions-ex.php'; 231 } 232 233 $plugin_dir = Redux_Functions_Ex::wp_normalize_path( WP_PLUGIN_DIR ) . '/'; 234 $self_file = Redux_Functions_Ex::wp_normalize_path( __FILE__ ); 235 236 $path = str_replace( $plugin_dir, '', $self_file ); 237 $path = str_replace( 'class-redux-framework-plugin.php', 'redux-framework.php', $path ); 238 239 $plugins = get_option( 'active_plugins' ); 240 241 if ( $plugins ) { 242 $key = array_search( $path, $plugins, true ); 243 244 if ( false !== $key ) { 245 array_splice( $plugins, $key, 1 ); 246 array_unshift( $plugins, $path ); 247 update_option( 'active_plugins', $plugins ); 248 } 249 250 if ( class_exists( 'Redux_Pro' ) ) { 251 $self_file = Redux_Functions_Ex::wp_normalize_path( Redux_Pro::$dir ); 252 $path = str_replace( $plugin_dir, '', $self_file ); 253 254 // phpcs:ignore WordPress.NamingConventions.ValidHookName 255 $basename = apply_filters( 'redux/pro/basename', 'redux-pro.php' ); 256 257 $key = array_search( $path . '/' . $basename, $plugins, true ); 258 if ( false !== $key ) { 259 array_splice( $plugins, $key, 1 ); 260 array_unshift( $plugins, $path . '/' . $basename ); 261 update_option( 'active_plugins', $plugins ); 262 } 263 } 264 } 265 } 266 267 /** 268 * Fired on plugin activation 269 * 270 * @access public 271 * @since 3.0.0 272 * 273 * @param boolean $network_wide True if plugin is network activated, false otherwise. 274 * 275 * @return void 276 */ 277 public static function activate( bool $network_wide ) { 278 // phpcs:disable 279 //if ( function_exists( 'is_multisite' ) && is_multisite() ) { 280 // if ( $network_wide ) { 281 // // Get all blog IDs. 282 // $blog_ids = self::get_blog_ids(); 283 // 284 // foreach ( $blog_ids as $blog_id ) { 285 // switch_to_blog( $blog_id ); 286 // self::single_activate(); 287 // } 288 // restore_current_blog(); 289 // } else { 290 // self::single_activate(); 291 // } 292 //} else { 293 // self::single_activate(); 294 //} 295 // phpcs:enable 296 297 delete_site_transient( 'update_plugins' ); 298 } 299 300 /** 301 * Fired when plugin is deactivated 302 * 303 * @access public 304 * @since 3.0.0 305 * 306 * @param boolean $network_wide True if plugin is network activated, false otherwise. 307 * 308 * @return void 309 */ 310 public static function deactivate( bool $network_wide ) { 311 if ( function_exists( 'is_multisite' ) && is_multisite() ) { 312 if ( $network_wide ) { 313 // Get all blog IDs. 314 $blog_ids = self::get_blog_ids(); 315 316 foreach ( $blog_ids as $blog_id ) { 317 switch_to_blog( $blog_id ); 318 self::single_deactivate(); 319 } 320 restore_current_blog(); 321 } else { 322 self::single_deactivate(); 323 } 324 } else { 325 self::single_deactivate(); 326 } 327 328 delete_option( 'ReduxFrameworkPlugin' ); 329 Redux_Enable_Gutenberg::cleanup_options( 'redux-framework' ); // Auto disable Gutenberg and all that. 330 } 331 332 /** 333 * Fired when a new WPMU site is activated 334 * 335 * @access public 336 * 337 * @param int $blog_id The ID of the new blog. 338 * 339 * @return void 340 * @since 3.0.0 341 */ 342 public function activate_new_site( int $blog_id ) { 343 if ( 1 !== did_action( 'wpmu_new_blog' ) ) { 344 return; 345 } 346 347 switch_to_blog( $blog_id ); 348 self::single_activate(); 349 restore_current_blog(); 350 } 351 352 /** 353 * Get all IDs of blogs that are not activated, not spam, and not deleted 354 * 355 * @access private 356 * @since 3.0.0 357 * @global object $wpdb 358 * @return array|false Array of IDs or false if none are found 359 */ 360 private static function get_blog_ids() { 361 global $wpdb; 362 363 $var = '0'; 364 365 // Get an array of IDs (We have to do it this way because WordPress says so, however reduntant). 366 $result = wp_cache_get( 'redux-blog-ids' ); 367 if ( false === $result ) { 368 369 // WordPress asys get_col is discouraged? I found no alternative. So...ignore! - kp. 370 // phpcs:ignore WordPress.DB.DirectDatabaseQuery 371 $result = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE archived = %s AND spam = %s AND deleted = %s", $var, $var, $var ) ); 372 373 wp_cache_set( 'redux-blog-ids', $result ); 374 } 375 376 return $result; 377 } 378 379 /** 380 * Fired for each WPMS blog on plugin activation 381 * 382 * @access private 383 * @since 3.0.0 384 * @return void 385 */ 386 private static function single_activate() { 387 $notices = array(); 388 389 $nonce = wp_create_nonce( 'redux_framework_demo' ); 390 391 $notices = get_option( 'ReduxFrameworkPlugin_ACTIVATED_NOTICES', array() ); 392 $notices[] = esc_html__( 'Redux Framework has an embedded demo.', 'redux-framework' ) . ' <a href="./plugins.php?redux-framework-plugin=demo&nonce=' . $nonce . '">' . esc_html__( 'Click here to activate the sample config file.', 'redux-framework' ) . '</a>'; 393 394 update_option( 'ReduxFrameworkPlugin_ACTIVATED_NOTICES', $notices ); 395 } 396 397 /** 398 * Display admin notices 399 * 400 * @access public 401 * @since 3.0.0 402 * @return void 403 */ 404 public function admin_notices() { 405 do_action( 'redux_framework_plugin_admin_notice' ); 406 $notices = get_option( 'ReduxFrameworkPlugin_ACTIVATED_NOTICES', '' ); 407 if ( ! empty( $notices ) ) { 408 foreach ( $notices as $notice ) { 409 echo '<div class="updated notice is-dismissible"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput 410 } 411 412 delete_option( 'ReduxFrameworkPlugin_ACTIVATED_NOTICES' ); 413 } 414 } 415 416 /** 417 * Fired for each blog when the plugin is deactivated 418 * 419 * @access private 420 * @since 3.0.0 421 * @return void 422 */ 423 private static function single_deactivate() { 424 delete_option( 'ReduxFrameworkPlugin_ACTIVATED_NOTICES' ); 425 } 426 427 /** 428 * Turn on or off 429 * 430 * @access public 431 * @since 3.0.0 432 * @global string $pagenow The current page being displayed 433 * @return void 434 */ 435 public function options_toggle_check() { 436 global $pagenow; 437 438 if ( isset( $_GET['nonce'] ) && wp_verify_nonce( sanitize_key( $_GET['nonce'] ), 'redux_framework_demo' ) ) { 439 if ( isset( $_GET['redux-framework-plugin'] ) && 'demo' === $_GET['redux-framework-plugin'] ) { 440 $url = admin_url( add_query_arg( array( 'page' => 'redux-framework' ), 'options-general.php' ) ); 441 442 if ( false === $this->options['demo'] ) { 443 $this->options['demo'] = true; 444 $url = admin_url( add_query_arg( array( 'page' => 'redux_demo' ), 'admin.php' ) ); 445 } else { 446 $this->options['demo'] = false; 447 } 448 449 if ( is_multisite() && $this->plugin_network_activated ) { 450 update_site_option( 'ReduxFrameworkPlugin', $this->options ); 451 } else { 452 update_option( 'ReduxFrameworkPlugin', $this->options ); 453 } 454 455 wp_safe_redirect( esc_url( $url ) ); 456 457 exit(); 458 } 459 } 460 } 461 462 463 /** 464 * Add a settings link to the Redux entry in the plugin overview screen 465 * 466 * @param array $links Links array. 467 * @param string $file Plugin filename/slug. 468 * 469 * @return array 470 * @see filter:plugin_action_links 471 * @since 1.0 472 */ 473 public function add_settings_link( array $links, string $file ): array { 474 475 if ( strpos( REDUX_PLUGIN_FILE, $file ) === false ) { 476 return $links; 477 } 478 479 if ( ! class_exists( 'Redux_Pro' ) ) { 480 $links[] = sprintf( 481 '<a href="%s" target="_blank">%s</a>', 482 esc_url( $this->get_site_utm_url( '', 'upgrade' ) ), 483 sprintf( 484 '<span style="font-weight: bold;">%s</span>', 485 __( 'Go Pro', 'redux-framework' ) 486 ) 487 ); 488 } 489 490 return $links; 491 } 492 493 /** 494 * Get the url where the Admin Columns website is hosted 495 * 496 * @param string $path Path to add to url. 497 * 498 * @return string 499 */ 500 private function get_site_url( string $path = '' ): string { 501 $url = 'https://redux.io'; 502 503 if ( ! empty( $path ) ) { 504 $url .= '/' . trim( $path, '/' ) . '/'; 505 } 506 507 return $url; 508 } 509 510 /** 511 * Url with utm tags 512 * 513 * @param string $path Path on site. 514 * @param string $utm_medium Medium var. 515 * @param string|null $utm_content Content var. 516 * @param bool $utm_campaign Campaign var. 517 * 518 * @return string 519 */ 520 public function get_site_utm_url( string $path, string $utm_medium, string $utm_content = null, bool $utm_campaign = false ): string { 521 $url = self::get_site_url( $path ); 522 523 if ( ! $utm_campaign ) { 524 $utm_campaign = 'plugin-installation'; 525 } 526 527 $args = array( 528 // Referrer: plugin. 529 'utm_source' => 'plugin-installation', 530 531 // Specific promotions or sales. 532 'utm_campaign' => $utm_campaign, 533 534 // Marketing medium: banner, documentation or email. 535 'utm_medium' => $utm_medium, 536 537 // Used for differentiation of medium. 538 'utm_content' => $utm_content, 539 ); 540 541 $args = array_map( 'sanitize_key', array_filter( $args ) ); 542 543 return add_query_arg( $args, $url ); 544 } 545 546 /** 547 * Edit plugin metalinks 548 * 549 * @access public 550 * 551 * @param array $links The current array of links. 552 * @param string $file A specific plugin row. 553 * 554 * @return array The modified array of links 555 * @since 3.0.0 556 */ 557 public function plugin_metalinks( array $links, string $file ): array { 558 if ( strpos( $file, 'redux-framework.php' ) !== false && is_plugin_active( $file ) ) { 559 $links[] = '<a href="' . esc_url( admin_url( add_query_arg( array( 'page' => 'redux-framework' ), 'options-general.php' ) ) ) . '">' . esc_html__( 'What is this?', 'redux-framework' ) . '</a>'; 560 561 if ( true === Redux_Core::$redux_templates_enabled ) { 562 $links[] = '<a href="' . esc_url( admin_url( add_query_arg( array( 'post_type' => 'page' ), 'post-new.php' ) ) ) . '#redux_templates=1">' . esc_html__( 'Template Library', 'redux-framework' ) . '</a>'; 563 } 564 } 565 566 return $links; 567 } 568 } 569 if ( ! class_exists( 'ReduxFrameworkPlugin' ) ) { 570 class_alias( 'Redux_Framework_Plugin', 'ReduxFrameworkPlugin' ); 571 } 572 }