class-redux-wordpress-data.php (18652B)
1 <?php 2 /** 3 * Redux WordPress Data Class 4 * 5 * @class Redux_WordPress_Data 6 * @version 3.0.0 7 * @package Redux Framework 8 */ 9 10 defined( 'ABSPATH' ) || exit; 11 12 if ( ! class_exists( 'Redux_WordPress_Data', false ) ) { 13 14 /** 15 * Class Redux_WordPress_Data 16 */ 17 class Redux_WordPress_Data extends Redux_Class { 18 19 /** 20 * Holds WordPress data. 21 * 22 * @var null 23 */ 24 private $wp_data = null; 25 26 /** 27 * Redux_WordPress_Data constructor. 28 * 29 * @param mixed $parent ReduxFramework pointer or opt_name. 30 */ 31 public function __construct( $parent = null ) { 32 if ( is_string( $parent ) ) { 33 $this->opt_name = $parent; 34 } else { 35 parent::__construct( $parent ); 36 } 37 } 38 39 /** 40 * Get the data. 41 * 42 * @param string|array $type Type. 43 * @param array|string $args Args. 44 * @param string $opt_name Opt name. 45 * @param string|int $current_value Current value. 46 * @param bool $ajax Tells if this is an AJAX call. 47 * 48 * @return array|mixed|string 49 */ 50 public function get( $type, $args = array(), string $opt_name = '', $current_value = '', bool $ajax = false ) { 51 $opt_name = $this->opt_name; 52 53 // We don't want to run this, it's not a string value. Send it back! 54 if ( is_array( $type ) ) { 55 return $type; 56 } 57 58 /** 59 * Filter 'redux/options/{opt_name}/pre_data/{type}' 60 * 61 * @param string $data 62 */ 63 $pre_data = apply_filters( "redux/options/$opt_name/pre_data/$type", null ); // phpcs:ignore WordPress.NamingConventions.ValidHookName 64 if ( null !== $pre_data || empty( $type ) ) { 65 return $pre_data; 66 } 67 68 if ( isset( $args['data_sortby'] ) && in_array( $args['data_sortby'], array( 'value', 'key' ), true ) ) { 69 $data_sort = $args['data_sortby']; 70 unset( $args['data_sortby'] ); 71 } else { 72 $data_sort = 'value'; 73 } 74 if ( isset( $args['data_order'] ) && in_array( $args['data_order'], array( 'asc', 'desc' ), true ) ) { 75 $data_order = $args['data_order']; 76 unset( $args['data_order'] ); 77 } else { 78 $data_order = 'asc'; 79 } 80 81 $this->maybe_get_translation( $type, $current_value, $args ); 82 83 $current_data = array(); 84 if ( empty( $current_value ) && ! Redux_Helpers::is_integer( $current_value ) ) { 85 $current_value = null; 86 } else { 87 // Get the args to grab the current data. 88 $current_data_args = $this->get_current_data_args( $type, $args, $current_value ); 89 90 // Let's make a unique key for this arg array. 91 $current_data_args_key = md5( maybe_serialize( $current_data_args ) ); 92 93 // Check to make sure we haven't already run this call before. 94 $current_data = $this->wp_data[ $type . $current_data_args_key ] ?? $this->get_data( $type, $current_data_args, $current_value ); 95 } 96 97 // If ajax is enabled AND $current_data is empty, set a dummy value for the init. 98 if ( $ajax && ! wp_doing_ajax() ) { 99 // Dummy is needed otherwise empty. 100 if ( empty( $current_data ) ) { 101 $current_data = array( 102 'dummy' => '', 103 ); 104 } 105 106 return $current_data; 107 } 108 109 // phpcs:ignore Squiz.PHP.CommentedOutCode 110 $args_key = md5( maybe_serialize( $args ) ); 111 112 // Data caching. 113 if ( isset( $this->wp_data[ $type . $args_key ] ) ) { 114 $data = $this->wp_data[ $type . $args_key ]; 115 } else { 116 /** 117 * Use data from WordPress to populate options array. 118 * */ 119 $data = $this->get_data( $type, $args, $current_value ); 120 } 121 122 if ( ! empty( $current_data ) ) { 123 $data += $current_data; 124 } 125 126 if ( ! empty( $data ) ) { 127 $data = $this->order_data( $data, $data_sort, $data_order ); 128 $this->wp_data[ $type . $args_key ] = $data; 129 } 130 131 /** 132 * Filter 'redux/options/{opt_name}/data/{type}' 133 * 134 * @param string $data 135 */ 136 137 // phpcs:ignore WordPress.NamingConventions.ValidHookName 138 return apply_filters( "redux/options/$opt_name/data/$type", $data ); 139 } 140 141 142 /** 143 * Process the results into a proper array, fetching the data elements needed for each data type. 144 * 145 * @param array|WP_Error $results Results to process in the data array. 146 * @param string|bool $id_key Key on object/array that represents the ID. 147 * @param string|bool $name_key Key on object/array that represents the name/text. 148 * @param bool $add_key If true, the display key will appear in the text. 149 * @param string|bool $secondary_key If a data type you'd rather display a different ID as the display key. 150 * 151 * @return array 152 */ 153 private function process_results( $results = array(), $id_key = '', $name_key = '', bool $add_key = true, $secondary_key = 'slug' ): array { 154 $data = array(); 155 if ( ! empty( $results ) && ! is_a( $results, 'WP_Error' ) ) { 156 foreach ( $results as $k => $v ) { 157 if ( empty( $id_key ) ) { 158 $key = $k; 159 } else { 160 if ( is_object( $v ) ) { 161 $key = $v->$id_key; 162 } elseif ( is_array( $v ) ) { 163 $key = $v[ $id_key ]; 164 } else { 165 $key = $k; 166 } 167 } 168 if ( empty( $name_key ) ) { 169 $value = $v; 170 } else { 171 if ( is_object( $v ) ) { 172 $value = $v->$name_key; 173 } elseif ( is_array( $v ) ) { 174 $value = $v[ $name_key ]; 175 } else { 176 $value = $v; 177 } 178 } 179 $display_key = $key; 180 if ( is_object( $v ) && isset( $v->$secondary_key ) ) { 181 $display_key = $v->$secondary_key; 182 } elseif ( ! is_object( $v ) && isset( $v[ $secondary_key ] ) ) { 183 $display_key = $v[ $secondary_key ]; 184 } 185 $data[ $key ] = $value; 186 if ( $display_key !== $value && $add_key ) { 187 $data[ $key ] = $data[ $key ] . ' [' . $display_key . ']'; 188 } 189 } 190 } 191 192 return $data; 193 } 194 195 /** 196 * Order / Sort the data. 197 * 198 * @param array $data Data to sort. 199 * @param string $sort Way to sort. Accepts: key|value. 200 * @param string $order Order of the sort. Accepts: asc|desc. 201 * 202 * @return array 203 */ 204 private function order_data( array $data = array(), string $sort = 'value', string $order = 'asc' ): array { 205 if ( 'key' === $sort ) { 206 if ( 'asc' === $order ) { 207 ksort( $data ); 208 } else { 209 krsort( $data ); 210 } 211 } elseif ( 'value' === $sort ) { 212 if ( 'asc' === $order ) { 213 asort( $data ); 214 } else { 215 arsort( $data ); 216 } 217 } 218 219 return $data; 220 } 221 222 /** 223 * Fetch the data for a given type. 224 * 225 * @param string $type The data type we're fetching. 226 * @param array|string $args Arguments to pass. 227 * @param mixed|array $current_value If a current value already set in the database. 228 * 229 * @return array|null|string 230 */ 231 private function get_data( string $type, $args, $current_value ) { 232 $args = $this->get_arg_defaults( $type, $args ); 233 234 $opt_name = $this->opt_name; 235 if ( empty( $args ) ) { 236 $args = array(); 237 } 238 239 $data = array(); 240 if ( isset( $args['args'] ) && empty( $args['args'] ) ) { 241 unset( $args['args'] ); 242 } 243 244 $display_keys = false; 245 if ( isset( $args['display_keys'] ) ) { 246 $display_keys = true; 247 unset( $args['display_keys'] ); 248 } 249 250 $secondary_key = 'slug'; 251 if ( isset( $args['secondary_key'] ) ) { 252 $display_key = $args['secondary_key']; 253 unset( $args['secondary_key'] ); 254 } 255 256 switch ( $type ) { 257 case 'categories': 258 case 'category': 259 case 'terms': 260 case 'term': 261 if ( isset( $args['taxonomies'] ) ) { 262 $args['taxonomy'] = $args['taxonomies']; 263 unset( $args['taxonomies'] ); 264 } 265 $results = get_terms( $args ); 266 $data = $this->process_results( $results, 'term_id', 'name', $display_keys, $secondary_key ); 267 break; 268 269 case 'pages': 270 case 'page': 271 $results = get_pages( $args ); 272 $data = $this->process_results( $results, 'ID', 'post_title', $display_keys, $secondary_key ); 273 break; 274 275 case 'tags': 276 case 'tag': 277 $results = get_tags( $args ); 278 $data = $this->process_results( $results, 'term_id', 'name', $display_keys, $secondary_key ); 279 break; 280 281 case 'menus': 282 case 'menu': 283 $results = wp_get_nav_menus( $args ); 284 $data = $this->process_results( $results, 'term_id', 'name', $display_keys, $secondary_key ); 285 break; 286 287 case 'posts': 288 case 'post': 289 $results = get_posts( $args ); 290 $data = $this->process_results( $results, 'ID', 'post_title', $display_keys, $secondary_key ); 291 break; 292 293 case 'users': 294 case 'user': 295 $results = get_users( $args ); 296 $data = $this->process_results( $results, 'ID', 'display_name', $display_keys, $secondary_key ); 297 break; 298 299 case 'sites': 300 case 'site': 301 // WP > 4.6. 302 if ( function_exists( 'get_sites' ) && class_exists( 'WP_Site_Query' ) ) { 303 $sites = get_sites(); 304 // WP < 4.6. 305 } elseif ( function_exists( 'wp_get_sites' ) ) { 306 $sites = wp_get_sites(); // phpcs:ignore WordPress.WP.DeprecatedFunctions 307 } 308 if ( isset( $sites ) ) { 309 $results = array(); 310 foreach ( $sites as $site ) { 311 $site = (array) $site; 312 $k = $site['blog_id']; 313 $v = $site['domain'] . $site['path']; 314 $name = get_blog_option( $k, 'blogname' ); 315 if ( ! empty( $name ) ) { 316 $v .= ' - [' . $name . ']'; 317 } 318 $results[ $k ] = $v; 319 } 320 $data = $this->process_results( $results, '', '', $display_keys, $secondary_key ); 321 } 322 323 break; 324 325 case 'taxonomies': 326 case 'taxonomy': 327 case 'tax': 328 $results = get_taxonomies( $args ); 329 $data = $this->process_results( $results, '', '', $display_keys, $secondary_key ); 330 break; 331 332 case 'post_types': 333 case 'post_type': 334 global $wp_post_types; 335 336 $output = $args['output']; 337 unset( $args['output'] ); 338 $operator = $args['operator']; 339 unset( $args['operator'] ); 340 341 $post_types = get_post_types( $args, $output, $operator ); 342 343 foreach ( $post_types as $name => $title ) { 344 if ( isset( $wp_post_types[ $name ]->labels->menu_name ) ) { 345 $data[ $name ] = $wp_post_types[ $name ]->labels->menu_name; 346 } else { 347 $data[ $name ] = ucfirst( $name ); 348 } 349 } 350 break; 351 352 case 'menu_locations': 353 case 'menu_location': 354 global $_wp_registered_nav_menus; 355 foreach ( $_wp_registered_nav_menus as $k => $v ) { 356 $data[ $k ] = $v; 357 if ( ! has_nav_menu( $k ) ) { 358 $data[ $k ] .= ' ' . __( '[unassigned]', 'redux-framework' ); 359 } 360 } 361 break; 362 363 case 'image_sizes': 364 case 'image_size': 365 global $_wp_additional_image_sizes; 366 $results = array(); 367 foreach ( $_wp_additional_image_sizes as $size_name => $size_attrs ) { 368 $results[ $size_name ] = $size_name . ' - ' . $size_attrs['width'] . ' x ' . $size_attrs['height']; 369 } 370 $data = $this->process_results( $results, '', '', $display_keys, $secondary_key ); 371 372 break; 373 374 case 'elusive-icons': 375 case 'elusive-icon': 376 case 'elusive': 377 case 'icons': 378 case 'font-icon': 379 case 'font-icons': 380 $fs = Redux_Filesystem::get_instance(); 381 $fonts = $fs->get_contents( Redux_Core::$dir . 'assets/css/vendor/elusive-icons.css' ); 382 if ( ! empty( $fonts ) ) { 383 preg_match_all( '@\.el-(\w+)::before@', $fonts, $matches ); 384 foreach ( $matches[1] as $item ) { 385 if ( 'before' === $item ) { 386 continue; 387 } 388 $data[ 'el el-' . $item ] = $item; 389 } 390 } 391 392 /** 393 * Filter 'redux/font-icons' 394 * 395 * @param array $font_icons array of elusive icon classes 396 * 397 * @deprecated 398 */ 399 400 // phpcs:ignore WordPress.NamingConventions.ValidHookName 401 $font_icons = apply_filters( 'redux/font-icons', $data ); 402 403 /** 404 * Filter 'redux/{opt_name}/field/font/icons' 405 * 406 * @param array $font_icons array of elusive icon classes 407 * 408 * @deprecated 409 */ 410 411 // phpcs:ignore WordPress.NamingConventions.ValidHookName 412 $font_icons = apply_filters( "redux/$opt_name/field/font/icons", $font_icons ); 413 414 break; 415 416 case 'dashicons': 417 case 'dashicon': 418 case 'dash': 419 $fs = Redux_Filesystem::get_instance(); 420 $fonts = $fs->get_contents( ABSPATH . WPINC . '/css/dashicons.css' ); 421 if ( ! empty( $fonts ) ) { 422 preg_match_all( '@\.dashicons-(\w+):before@', $fonts, $matches ); 423 foreach ( $matches[1] as $item ) { 424 if ( 'before' === $item ) { 425 continue; 426 } 427 $data[ 'dashicons dashicons-' . $item ] = $item; 428 } 429 } 430 break; 431 432 case 'roles': 433 case 'role': 434 global $wp_roles; 435 $results = $wp_roles->get_names(); 436 $data = $this->process_results( $results, '', '', $display_keys, $secondary_key ); 437 438 break; 439 440 case 'sidebars': 441 case 'sidebar': 442 global $wp_registered_sidebars; 443 $data = $this->process_results( $wp_registered_sidebars, '', 'name', $display_keys, $secondary_key ); 444 break; 445 case 'capabilities': 446 case 'capability': 447 global $wp_roles; 448 $results = array(); 449 foreach ( $wp_roles->roles as $role ) { 450 foreach ( $role['capabilities'] as $key => $cap ) { 451 $results[ $key ] = ucwords( str_replace( '_', ' ', $key ) ); 452 } 453 } 454 $data = $this->process_results( $results, '', '', $display_keys, $secondary_key ); 455 456 break; 457 458 case 'capabilities_grouped': 459 case 'capability_grouped': 460 case 'capabilities_group': 461 case 'capability_group': 462 global $wp_roles; 463 464 foreach ( $wp_roles->roles as $k => $role ) { 465 $caps = array(); 466 foreach ( $role['capabilities'] as $key => $cap ) { 467 $caps[ $key ] = ucwords( str_replace( '_', ' ', $key ) ); 468 } 469 asort( $caps ); 470 $data[ $role['name'] ] = $caps; 471 } 472 473 break; 474 475 case 'callback': 476 if ( ! empty( $args ) ) { 477 $data = call_user_func( $args, $current_value ); 478 } 479 break; 480 } 481 482 return $data; 483 } 484 485 486 /** 487 * Router for translation based on the given post type. 488 * 489 * @param string $type Type of data request. 490 * @param mixed|array $current_value Current value stored in DB. 491 * @param array|string $args Arguments for the call. 492 */ 493 private function maybe_get_translation( string $type, &$current_value = '', $args = array() ) { 494 switch ( $type ) { 495 case 'categories': 496 case 'category': 497 $this->maybe_translate( $current_value, 'category' ); 498 break; 499 500 case 'pages': 501 case 'page': 502 $this->maybe_translate( $current_value, 'page' ); 503 break; 504 505 case 'terms': 506 case 'term': 507 $this->maybe_translate( $current_value, $args['taxonomy'] ?? '' ); 508 break; 509 510 case 'tags': 511 case 'tag': 512 $this->maybe_translate( $current_value, 'post_tag' ); 513 break; 514 515 case 'menus': 516 case 'menu': 517 $this->maybe_translate( $current_value, 'nav_menu' ); 518 break; 519 520 case 'post': 521 case 'posts': 522 $this->maybe_translate( $current_value, 'post' ); 523 break; 524 default: 525 $this->maybe_translate( $current_value, '' ); 526 } 527 } 528 529 /** 530 * Maybe translate the values. 531 * 532 * @param mixed|array $value Value. 533 * @param mixed|array $post_type Post type. 534 */ 535 private function maybe_translate( &$value, $post_type ) { 536 537 // phpcs:ignore WordPress.NamingConventions.ValidHookName 538 $value = apply_filters( "redux/options/$this->opt_name/wordpress_data/translate/post_type_value", $value, $post_type ); 539 540 // WPML Integration, see https://wpml.org/documentation/support/creating-multilingual-wordpress-themes/language-dependent-ids/. 541 if ( function_exists( 'icl_object_id' ) ) { 542 if ( has_filter( 'wpml_object_id' ) ) { 543 if ( Redux_Helpers::is_integer( $value ) ) { 544 $value = apply_filters( 'wpml_object_id', $value, $post_type, true ); 545 } elseif ( is_array( $value ) ) { 546 $value = array_map( 547 function ( $val ) use ( $post_type ) { 548 return apply_filters( 'wpml_object_id', $val, $post_type, true ); 549 }, 550 $value 551 ); 552 } 553 } 554 } 555 } 556 557 /** 558 * Set the default arguments for a current query (existing data). 559 * 560 * @param string $type Type of data request. 561 * @param array|string $args Arguments for the call. 562 * @param mixed|array $current_value Current value stored in DB. 563 * 564 * @return array 565 */ 566 private function get_current_data_args( string $type, $args, $current_value ): array { 567 // In this section we set the default arguments for each data type. 568 switch ( $type ) { 569 case 'categories': 570 case 'category': 571 case 'pages': 572 case 'page': 573 case 'terms': 574 case 'term': 575 case 'users': 576 case 'user': 577 $args['include'] = $current_value; 578 break; 579 case 'tags': 580 case 'tag': 581 $args['get'] = 'all'; 582 break; 583 case 'menus': 584 case 'menu': 585 $args['object_ids'] = $current_value; 586 break; 587 case 'post': 588 case 'posts': 589 if ( ! empty( $current_value ) ) { 590 $args['post__in'] = is_array( $current_value ) ? $current_value : array( $current_value ); 591 } 592 break; 593 594 default: 595 $args = array(); 596 } 597 598 return $args; 599 } 600 601 602 /** 603 * Get default arguments for a given data type. 604 * 605 * @param string $type Type of data request. 606 * @param array|string $args Arguments for the call. 607 * 608 * @return array|string 609 */ 610 private function get_arg_defaults( string $type, $args = array() ) { 611 // In this section we set the default arguments for each data type. 612 switch ( $type ) { 613 case 'categories': 614 case 'category': 615 $args = wp_parse_args( 616 $args, 617 array( 618 'taxonomy' => 'category', 619 ) 620 ); 621 break; 622 623 case 'pages': 624 case 'page': 625 $args = wp_parse_args( 626 $args, 627 array( 628 'display_keys' => true, 629 'posts_per_page' => 20, 630 ) 631 ); 632 break; 633 634 case 'post_type': 635 case 'post_types': 636 $args = wp_parse_args( 637 $args, 638 array( 639 'public' => true, 640 'exclude_from_search' => false, 641 'output' => 'names', 642 'operator' => 'and', 643 ) 644 ); 645 646 break; 647 648 case 'tag': 649 case 'tags': 650 $args = wp_parse_args( 651 $args, 652 array( 653 'get' => 'all', 654 'display_keys' => true, 655 ) 656 ); 657 break; 658 659 case 'sidebars': 660 case 'sidebar': 661 case 'capabilities': 662 case 'capability': 663 $args = wp_parse_args( 664 $args, 665 array( 666 'display_keys' => true, 667 ) 668 ); 669 break; 670 671 case 'capabilities_grouped': 672 case 'capability_grouped': 673 case 'capabilities_group': 674 case 'capability_group': 675 $args = wp_parse_args( 676 $args, 677 array( 678 'data_sortby' => '', 679 ) 680 ); 681 break; 682 683 } 684 685 return $args; 686 } 687 } 688 }