class-redux-output.php (15110B)
1 <?php 2 /** 3 * Redux Output Class 4 * 5 * @class Redux_Output 6 * @version 3.0.0 7 * @package Redux Framework/Classes 8 */ 9 10 defined( 'ABSPATH' ) || exit; 11 12 if ( ! class_exists( 'Redux_Output', false ) ) { 13 14 /** 15 * Class Redux_Output 16 */ 17 class Redux_Output extends Redux_Class { 18 19 /** 20 * Redux_Output constructor. 21 * 22 * @param object $parent ReduxFramework pointer. 23 */ 24 public function __construct( $parent ) { 25 parent::__construct( $parent ); 26 27 // Output dynamic CSS. 28 // Frontend: Maybe enqueue dynamic CSS and Google fonts. 29 if ( empty( $this->args['output_location'] ) || in_array( 'frontend', $this->args['output_location'], true ) ) { 30 add_action( 'wp_head', array( $this, 'output_css' ), 150 ); 31 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ), 150 ); 32 } 33 34 // Login page: Maybe enqueue dynamic CSS and Google fonts. 35 if ( in_array( 'login', $this->args['output_location'], true ) ) { 36 add_action( 'login_head', array( $this, 'output_css' ), 150 ); 37 add_action( 'login_enqueue_scripts', array( $this, 'enqueue' ), 150 ); 38 } 39 40 // Admin area: Maybe enqueue dynamic CSS and Google fonts. 41 if ( in_array( 'admin', $this->args['output_location'], true ) ) { 42 add_action( 'admin_head', array( $this, 'output_css' ), 150 ); 43 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), 150 ); 44 } 45 46 // phpcs:ignore WordPress.NamingConventions.ValidHookName 47 do_action( "redux/output/{$this->parent->args['opt_name']}/construct", $this ); 48 // Useful for adding different locations for CSS output. 49 } 50 51 /** 52 * Enqueue CSS and Google fonts for front end 53 * 54 * @return void 55 * @since 1.0.0 56 * @access public 57 */ 58 public function enqueue() { 59 $core = $this->core(); 60 61 if ( false === $core->args['output'] && false === $core->args['compiler'] ) { 62 return; 63 } 64 65 foreach ( $core->sections as $k => $section ) { 66 if ( isset( $section['type'] ) && ( 'divide' === $section['type'] ) ) { 67 continue; 68 } 69 70 if ( isset( $section['fields'] ) ) { 71 foreach ( $section['fields'] as $fieldk => $field ) { 72 if ( isset( $field['type'] ) && 'callback' !== $field['type'] ) { 73 $field_classes = array( 'Redux_' . $field['type'], 'ReduxFramework_' . $field['type'] ); 74 75 $field_class = Redux_Functions::class_exists_ex( $field_classes ); 76 77 if ( false === $field_class ) { 78 if ( ! isset( $field['compiler'] ) ) { 79 $field['compiler'] = ''; 80 } 81 82 /** 83 * Field class file 84 * filter 'redux/{opt_name}/field/class/{field.type} 85 * 86 * @param string field class file 87 * @param array $field field config data 88 */ 89 $field_type = str_replace( '_', '-', $field['type'] ); 90 $core_path = Redux_Core::$dir . "inc/fields/{$field['type']}/class-redux-$field_type.php"; 91 92 if ( ! file_exists( $core_path ) ) { 93 $core_path = Redux_Core::$dir . "inc/fields/{$field['type']}/field_{$field['type']}.php"; 94 } 95 96 if ( Redux_Core::$pro_loaded ) { 97 $pro_path = ''; 98 99 if ( class_exists( 'Redux_Pro' ) ) { 100 $pro_path = Redux_Pro::$dir . "core/inc/fields/{$field['type']}/class-redux-$field_type.php"; 101 } 102 103 if ( file_exists( $pro_path ) ) { 104 $filter_path = $pro_path; 105 } else { 106 $filter_path = $core_path; 107 } 108 } else { 109 $filter_path = $core_path; 110 } 111 112 // phpcs:ignore WordPress.NamingConventions.ValidHookName 113 $class_file = apply_filters( "redux/{$core->args['opt_name']}/field/class/{$field['type']}", $filter_path, $field ); 114 115 if ( $class_file && file_exists( $class_file ) && ( ! class_exists( $field_class ) ) ) { 116 require_once $class_file; 117 118 $field_class = Redux_Functions::class_exists_ex( $field_classes ); 119 } 120 } 121 122 $field['default'] = $field['default'] ?? ''; 123 $value = $core->options[ $field['id'] ] ?? $field['default']; 124 $style_data = ''; 125 $data = array( 126 'field' => $field, 127 'value' => $value, 128 'core' => $core, 129 'mode' => 'output', 130 ); 131 132 Redux_Functions::load_pro_field( $data ); 133 134 if ( empty( $field_class ) ) { 135 continue; 136 } 137 138 $field_object = new $field_class( $field, $value, $core ); 139 140 if ( ! empty( $core->options[ $field['id'] ] ) && class_exists( $field_class ) && method_exists( $field_class, 'output' ) && $this->can_output_css( $core, $field ) ) { 141 142 // phpcs:ignore WordPress.NamingConventions.ValidHookName 143 $field = apply_filters( "redux/field/{$core->args['opt_name']}/output_css", $field ); 144 145 if ( ! empty( $field['output'] ) && ! is_array( $field['output'] ) ) { 146 $field['output'] = array( $field['output'] ); 147 } 148 149 if ( ( ( isset( $field['output'] ) && ! empty( $field['output'] ) ) || ( isset( $field['compiler'] ) && ! empty( $field['compiler'] ) ) || isset( $field['media_query'] ) && ! empty( $field['media_query'] ) || 'typography' === $field['type'] || 'icon_select' === $field['type'] ) ) { 150 if ( method_exists( $field_class, 'css_style' ) ) { 151 $style_data = $field_object->css_style( $field_object->value ); 152 } 153 } 154 155 if ( null !== $style_data ) { 156 if ( ( ( isset( $field['output'] ) && ! empty( $field['output'] ) ) || ( isset( $field['compiler'] ) && ! empty( $field['compiler'] ) ) || 'typography' === $field['type'] || 'icon_select' === $field['type'] ) ) { 157 $field_object->output( $style_data ); 158 } 159 160 if ( isset( $field['media_query'] ) && ! empty( $field['media_query'] ) ) { 161 $field_object->media_query( $style_data ); 162 } 163 } 164 } 165 166 // phpcs:ignore WordPress.NamingConventions.ValidHookName 167 do_action( "redux/field/{$core->args['opt_name']}/output_loop", $core, $field, $value, $style_data ); 168 169 // phpcs:ignore WordPress.NamingConventions.ValidHookName 170 do_action( "redux/field/{$core->args['opt_name']}/output_loop/{$field['type']}", $core, $field, $value, $style_data ); 171 172 if ( method_exists( $field_class, 'output_variables' ) && $this->can_output_css( $core, $field ) ) { 173 $passed_style_data = $field_object->output_variables( $style_data ); 174 $this->output_variables( $core, $section, $field, $value, $passed_style_data ); 175 } 176 } 177 } 178 179 if ( ! empty( $core->outputCSS ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 180 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 181 $core->outputCSS = html_entity_decode( $core->outputCSS, ENT_QUOTES, 'UTF-8' ); 182 } 183 } 184 } 185 186 // For use like in the customizer. Stops the output, but passes the CSS in the variable for the compiler. 187 if ( isset( $core->no_output ) ) { 188 return; 189 } 190 191 if ( ! empty( $core->typography ) && filter_var( $core->args['output'], FILTER_VALIDATE_BOOLEAN ) ) { 192 $version = ! empty( $core->transients['last_save'] ) ? $core->transients['last_save'] : ''; 193 if ( ! class_exists( 'Redux_Typography' ) ) { 194 require_once Redux_Core::$dir . '/inc/fields/typography/class-redux-typography.php'; 195 } 196 $typography = new Redux_Typography( null, null, $core ); 197 198 if ( ! $core->args['disable_google_fonts_link'] ) { 199 $url = $typography->make_google_web_font_link( $core->typography ); 200 wp_enqueue_style( 'redux-google-fonts-' . $core->args['opt_name'], $url, array(), $version ); 201 add_filter( 'style_loader_tag', array( $this, 'add_style_attributes' ), 10, 4 ); 202 add_filter( 'wp_resource_hints', array( $this, 'google_fonts_preconnect' ), 10, 2 ); 203 } 204 } 205 } 206 207 /** 208 * Add Google Fonts preconnect link. 209 * 210 * @param array $urls HTML to be added. 211 * @param string $relationship_type Handle name. 212 * 213 * @return array 214 * @since 4.1.15 215 * @access public 216 */ 217 public function google_fonts_preconnect( array $urls, string $relationship_type ): array { 218 if ( 'preconnect' !== $relationship_type ) { 219 return $urls; 220 } 221 $urls[] = array( 222 'rel' => 'preconnect', 223 'href' => 'https://fonts.gstatic.com', 224 'crossorigin', 225 ); 226 return $urls; 227 } 228 229 /** 230 * Filter to enhance the google fonts enqueue. 231 * 232 * @param string $html HTML to be added. 233 * @param string $handle Handle name. 234 * @param string $href HREF URL of script. 235 * @param string $media Media type. 236 * 237 * @return string 238 * @since 4.1.15 239 * @access public 240 */ 241 public function add_style_attributes( string $html = '', string $handle = '', string $href = '', string $media = '' ): string { 242 if ( Redux_Functions_Ex::string_starts_with( $handle, 'redux-google-fonts-' ) ) { 243 // Revamp thanks to Harry: https://csswizardry.com/2020/05/the-fastest-google-fonts/. 244 $href = str_replace( array( '|', ' ' ), array( '%7C', '%20' ), urldecode( $href ) ); 245 $new_html = '<link rel="preload" as="style" href="' . esc_attr( $href ) . '" />'; 246 $new_html .= '<link rel="stylesheet" href="' . esc_attr( $href ) . '" media="print" onload="this.media=\'all\'">'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet 247 $new_html .= '<noscript><link rel="stylesheet" href="' . esc_attr( $href ) . '" /></noscript>'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet 248 $html = $new_html; 249 } 250 251 return $html; 252 } 253 254 /** 255 * Function to output output_variables to the dynamic output. 256 * 257 * @param object $core ReduxFramework core pointer. 258 * @param array $section Section containing this field. 259 * @param array $field Field object. 260 * @param array|string $value Current value of field. 261 * @param string|null $style_data CSS output string to append to the root output variable. 262 * 263 * @return void 264 * @since 4.0.3 265 * @access public 266 */ 267 private function output_variables( $core, array $section = array(), array $field = array(), $value = array(), ?string $style_data = '' ) { 268 // Let's allow section overrides please. 269 if ( isset( $section['output_variables'] ) && ! isset( $field['output_variables'] ) ) { 270 $field['output_variables'] = $section['output_variables']; 271 } 272 if ( isset( $section['output_variables_prefix'] ) && ! isset( $field['output_variables_prefix'] ) ) { 273 $field['output_variables_prefix'] = $section['output_variables_prefix']; 274 } 275 if ( isset( $field['output_variables'] ) && $field['output_variables'] ) { 276 $output_variables_prefix = $core->args['output_variables_prefix']; 277 if ( isset( $field['output_variables_prefix'] ) && ! empty( $field['output_variables_prefix'] ) ) { 278 $output_variables_prefix = $field['output_variables_prefix']; 279 } elseif ( isset( $section['output_variables_prefix'] ) && ! empty( $section['output_variables_prefix'] ) ) { 280 $output_variables_prefix = $section['output_variables_prefix']; 281 } 282 283 if ( is_array( $value ) ) { 284 $val_pieces = array_filter( $value, 'strlen' ); 285 // We don't need to show the Google boolean. 286 if ( 'typography' === $field['type'] && isset( $val_pieces['google'] ) ) { 287 unset( $val_pieces['google'] ); 288 } 289 290 foreach ( $val_pieces as $val_key => $val_val ) { 291 $val_key = $output_variables_prefix . sanitize_title_with_dashes( $field['id'] ) . '-' . $val_key; 292 $core->output_variables[ $val_key ] = $val_val; 293 if ( ! empty( $style_data ) ) { 294 $val_key = $output_variables_prefix . sanitize_title_with_dashes( $field['id'] ); 295 $core->output_variables[ $val_key ] = $style_data; 296 } 297 } 298 } else { 299 $val_key = $output_variables_prefix . sanitize_title_with_dashes( $field['id'] ); 300 301 if ( ! empty( $style_data ) ) { 302 $core->output_variables[ $val_key ] = $style_data; 303 } else { 304 $core->output_variables[ $val_key ] = $value; 305 } 306 } 307 } 308 } 309 310 /** 311 * Output dynamic CSS at bottom of HEAD 312 * 313 * @return void 314 * @since 3.2.8 315 * @access public 316 */ 317 public function output_css() { 318 $core = $this->core(); 319 320 if ( false === $core->args['output'] && false === $core->args['compiler'] && empty( $core->output_variables ) ) { 321 return; 322 } 323 324 if ( isset( $core->no_output ) ) { 325 return; 326 } 327 328 if ( ! empty( $core->output_variables ) ) { 329 $root_css = ':root{'; 330 foreach ( $core->output_variables as $key => $value ) { 331 $root_css .= "$key:$value;"; 332 } 333 $root_css .= '}'; 334 // phpcs:ignore WordPress.NamingConventions.ValidVariableName, WordPress.Security.EscapeOutput 335 $core->outputCSS = $root_css . $core->outputCSS; 336 } 337 338 // phpcs:ignore WordPress.NamingConventions.ValidVariableName 339 if ( ! empty( $core->outputCSS ) && ( true === $core->args['output_tag'] || ( isset( $_POST['customized'] ) && isset( $_POST['nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'preview-customize_' . wp_get_theme()->get_stylesheet() ) ) ) ) { 340 // phpcs:ignore WordPress.NamingConventions.ValidVariableName, WordPress.Security.EscapeOutput 341 echo '<style id="' . esc_attr( $core->args['opt_name'] ) . '-dynamic-css" title="dynamic-css" class="redux-options-output">' . $core->outputCSS . '</style>'; 342 } 343 } 344 345 /** 346 * Can Output CSS 347 * Check if a field meets its requirements before outputting to CSS 348 * 349 * @param object $core ReduxFramework core pointer. 350 * @param array $field Field array. 351 * 352 * @return bool 353 */ 354 private function can_output_css( $core, array $field ): ?bool { 355 $return = true; 356 357 // phpcs:ignore WordPress.NamingConventions.ValidHookName 358 $field = apply_filters( "redux/field/{$core->args['opt_name']}/_can_output_css", $field ); 359 360 if ( isset( $field['force_output'] ) && true === $field['force_output'] ) { 361 return $return; 362 } 363 364 if ( ! empty( $field['required'] ) ) { 365 if ( isset( $field['required'][0] ) ) { 366 if ( ! is_array( $field['required'][0] ) && 3 === count( $field['required'] ) ) { 367 $parent_value = $GLOBALS[ $core->args['global_variable'] ][ $field['required'][0] ] ?? ''; 368 $check_value = $field['required'][2]; 369 $operation = $field['required'][1]; 370 $return = $core->required_class->compare_value_dependencies( $parent_value, $check_value, $operation ); 371 } elseif ( is_array( $field['required'][0] ) ) { 372 foreach ( $field['required'] as $required ) { 373 if ( isset( $required[0] ) && ! is_array( $required[0] ) && 3 === count( $required ) ) { 374 $parent_value = $GLOBALS[ $core->args['global_variable'] ][ $required[0] ] ?? ''; 375 $check_value = $required[2]; 376 $operation = $required[1]; 377 $return = $core->required_class->compare_value_dependencies( $parent_value, $check_value, $operation ); 378 } 379 if ( ! $return ) { 380 return $return; 381 } 382 } 383 } 384 } 385 } 386 387 return $return; 388 } 389 390 } 391 392 }