Make WordPress Core

Changeset 52721


Ignore:
Timestamp:
02/13/2022 05:07:09 PM (2 years ago)
Author:
SergeyBiryukov
Message:

Coding Standards: Fix WPCS issues in wp-admin/includes/misc.php.

  • Use strict comparison in various conditions.
  • Fix a Variable "$system_webServer_node" is not in valid snake_case format WPCS warning.

Includes minor code layout fixes for better readability.

Follow-up to [10607], [11350], [22253], [26137].

Props azouamauriac, SergeyBiryukov.
See #54728.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/misc.php

    r52406 r52721  
    7575
    7676    $state = false;
     77
    7778    foreach ( $markerdata as $markerline ) {
    7879        if ( false !== strpos( $markerline, '# END ' . $marker ) ) {
    7980            $state = false;
    8081        }
     82
    8183        if ( $state ) {
    8284            if ( '#' === substr( $markerline, 0, 1 ) ) {
    8385                continue;
    8486            }
     87
    8588            $result[] = $markerline;
    8689        }
     90
    8791        if ( false !== strpos( $markerline, '# BEGIN ' . $marker ) ) {
    8892            $state = true;
     
    119123        // Make sure the file is created with a minimum set of permissions.
    120124        $perms = fileperms( $filename );
     125
    121126        if ( $perms ) {
    122127            chmod( $filename, $perms | 0644 );
     
    143148
    144149    $instructions = explode( "\n", $instructions );
     150
    145151    foreach ( $instructions as $line => $text ) {
    146152        $instructions[ $line ] = '# ' . $text;
     
    167173
    168174    $fp = fopen( $filename, 'r+' );
     175
    169176    if ( ! $fp ) {
    170177        return false;
     
    175182
    176183    $lines = array();
     184
    177185    while ( ! feof( $fp ) ) {
    178186        $lines[] = rtrim( fgets( $fp ), "\r\n" );
     
    185193    $found_marker     = false;
    186194    $found_end_marker = false;
     195
    187196    foreach ( $lines as $line ) {
    188197        if ( ! $found_marker && false !== strpos( $line, $start_marker ) ) {
     
    193202            continue;
    194203        }
     204
    195205        if ( ! $found_marker ) {
    196206            $pre_lines[] = $line;
     
    225235    fseek( $fp, 0 );
    226236    $bytes = fwrite( $fp, $new_file_data );
     237
    227238    if ( $bytes ) {
    228239        ftruncate( $fp, ftell( $fp ) );
    229240    }
     241
    230242    fflush( $fp );
    231243    flock( $fp, LOCK_UN );
     
    248260 */
    249261function save_mod_rewrite_rules() {
     262    global $wp_rewrite;
     263
    250264    if ( is_multisite() ) {
    251265        return;
    252266    }
    253 
    254     global $wp_rewrite;
    255267
    256268    // Ensure get_home_path() is declared.
     
    264276     * and whether we have some rules. Else check for write access to the file.
    265277     */
    266     if ( ( ! file_exists( $htaccess_file ) && is_writable( $home_path ) && $wp_rewrite->using_mod_rewrite_permalinks() ) || is_writable( $htaccess_file ) ) {
     278    if ( ! file_exists( $htaccess_file ) && is_writable( $home_path ) && $wp_rewrite->using_mod_rewrite_permalinks()
     279        || is_writable( $htaccess_file )
     280    ) {
    267281        if ( got_mod_rewrite() ) {
    268282            $rules = explode( "\n", $wp_rewrite->mod_rewrite_rules() );
     283
    269284            return insert_with_markers( $htaccess_file, 'WordPress', $rules );
    270285        }
     
    285300 */
    286301function iis7_save_url_rewrite_rules() {
     302    global $wp_rewrite;
     303
    287304    if ( is_multisite() ) {
    288305        return;
    289306    }
    290307
    291     global $wp_rewrite;
    292 
    293308    // Ensure get_home_path() is declared.
    294309    require_once ABSPATH . 'wp-admin/includes/file.php';
     
    298313
    299314    // Using win_is_writable() instead of is_writable() because of a bug in Windows PHP.
    300     if ( iis7_supports_permalinks() && ( ( ! file_exists( $web_config_file ) && win_is_writable( $home_path ) && $wp_rewrite->using_mod_rewrite_permalinks() ) || win_is_writable( $web_config_file ) ) ) {
     315    if ( iis7_supports_permalinks()
     316        && ( ! file_exists( $web_config_file ) && win_is_writable( $home_path ) && $wp_rewrite->using_mod_rewrite_permalinks()
     317            || win_is_writable( $web_config_file ) )
     318    ) {
    301319        $rule = $wp_rewrite->iis7_url_rewrite_rules( false );
     320
    302321        if ( ! empty( $rule ) ) {
    303322            return iis7_add_rewrite_rule( $web_config_file, $rule );
     
    306325        }
    307326    }
     327
    308328    return false;
    309329}
     
    318338function update_recently_edited( $file ) {
    319339    $oldfiles = (array) get_option( 'recently_edited' );
     340
    320341    if ( $oldfiles ) {
    321342        $oldfiles   = array_reverse( $oldfiles );
     
    323344        $oldfiles   = array_reverse( $oldfiles );
    324345        $oldfiles   = array_unique( $oldfiles );
     346
    325347        if ( 5 < count( $oldfiles ) ) {
    326348            array_pop( $oldfiles );
     
    329351        $oldfiles[] = $file;
    330352    }
     353
    331354    update_option( 'recently_edited', $oldfiles );
    332355}
     
    343366function wp_make_theme_file_tree( $allowed_files ) {
    344367    $tree_list = array();
     368
    345369    foreach ( $allowed_files as $file_name => $absolute_filename ) {
    346370        $list     = explode( '/', $file_name );
    347371        $last_dir = &$tree_list;
     372
    348373        foreach ( $list as $dir ) {
    349374            $last_dir =& $last_dir[ $dir ];
    350375        }
     376
    351377        $last_dir = $file_name;
    352378    }
     379
    353380    return $tree_list;
    354381}
     
    375402        $index = 0;
    376403        $size  = count( $tree );
     404
    377405        foreach ( $tree as $label => $theme_file ) :
    378406            $index++;
     407
    379408            if ( ! is_array( $theme_file ) ) {
    380409                wp_print_theme_file_tree( $theme_file, $level, $index, $size );
     
    409438                <?php
    410439                $file_description = esc_html( get_file_description( $filename ) );
     440
    411441                if ( $file_description !== $filename && wp_basename( $filename ) !== $file_description ) {
    412442                    $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
     
    436466function wp_make_plugin_file_tree( $plugin_editable_files ) {
    437467    $tree_list = array();
     468
    438469    foreach ( $plugin_editable_files as $plugin_file ) {
    439470        $list     = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) );
    440471        $last_dir = &$tree_list;
     472
    441473        foreach ( $list as $dir ) {
    442474            $last_dir =& $last_dir[ $dir ];
    443475        }
     476
    444477        $last_dir = $plugin_file;
    445478    }
     479
    446480    return $tree_list;
    447481}
     
    461495function wp_print_plugin_file_tree( $tree, $label = '', $level = 2, $size = 1, $index = 1 ) {
    462496    global $file, $plugin;
     497
    463498    if ( is_array( $tree ) ) {
    464499        $index = 0;
    465500        $size  = count( $tree );
     501
    466502        foreach ( $tree as $label => $plugin_file ) :
    467503            $index++;
     504
    468505            if ( ! is_array( $plugin_file ) ) {
    469506                wp_print_plugin_file_tree( $plugin_file, $label, $level, $index, $size );
     
    569606        }
    570607    }
     608
    571609    echo "<p>$message</p>\n";
    572610    wp_ob_end_flush_all();
     
    593631    $functions        = array();
    594632    $ignore_functions = array();
     633
    595634    for ( $t = 0; $t < $count - 2; $t++ ) {
    596635        if ( ! is_array( $tokens[ $t ] ) ) {
     
    598637        }
    599638
    600         if ( T_STRING == $tokens[ $t ][0] && ( '(' === $tokens[ $t + 1 ] || '(' === $tokens[ $t + 2 ] ) ) {
     639        if ( T_STRING === $tokens[ $t ][0] && ( '(' === $tokens[ $t + 1 ] || '(' === $tokens[ $t + 2 ] ) ) {
    601640            // If it's a function or class defined locally, there's not going to be any docs available.
    602641            if ( ( isset( $tokens[ $t - 2 ][1] ) && in_array( $tokens[ $t - 2 ][1], array( 'function', 'class' ), true ) )
    603                 || ( isset( $tokens[ $t - 2 ][0] ) && T_OBJECT_OPERATOR == $tokens[ $t - 1 ][0] )
     642                || ( isset( $tokens[ $t - 2 ][0] ) && T_OBJECT_OPERATOR === $tokens[ $t - 1 ][0] )
    604643            ) {
    605644                $ignore_functions[] = $tokens[ $t ][1];
    606645            }
     646
    607647            // Add this to our stack of unique references.
    608648            $functions[] = $tokens[ $t ][1];
     
    625665
    626666    $out = array();
     667
    627668    foreach ( $functions as $function ) {
    628669        if ( in_array( $function, $ignore_functions, true ) ) {
    629670            continue;
    630671        }
     672
    631673        $out[] = $function;
    632674    }
     
    641683 */
    642684function set_screen_options() {
    643 
    644     if ( isset( $_POST['wp_screen_options'] ) && is_array( $_POST['wp_screen_options'] ) ) {
    645         check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
    646 
    647         $user = wp_get_current_user();
    648         if ( ! $user ) {
    649             return;
    650         }
    651         $option = $_POST['wp_screen_options']['option'];
    652         $value  = $_POST['wp_screen_options']['value'];
    653 
    654         if ( sanitize_key( $option ) != $option ) {
    655             return;
    656         }
    657 
    658         $map_option = $option;
    659         $type       = str_replace( 'edit_', '', $map_option );
    660         $type       = str_replace( '_per_page', '', $type );
    661         if ( in_array( $type, get_taxonomies(), true ) ) {
    662             $map_option = 'edit_tags_per_page';
    663         } elseif ( in_array( $type, get_post_types(), true ) ) {
    664             $map_option = 'edit_per_page';
    665         } else {
    666             $option = str_replace( '-', '_', $option );
    667         }
    668 
    669         switch ( $map_option ) {
    670             case 'edit_per_page':
    671             case 'users_per_page':
    672             case 'edit_comments_per_page':
    673             case 'upload_per_page':
    674             case 'edit_tags_per_page':
    675             case 'plugins_per_page':
    676             case 'export_personal_data_requests_per_page':
    677             case 'remove_personal_data_requests_per_page':
    678                 // Network admin.
    679             case 'sites_network_per_page':
    680             case 'users_network_per_page':
    681             case 'site_users_network_per_page':
    682             case 'plugins_network_per_page':
    683             case 'themes_network_per_page':
    684             case 'site_themes_network_per_page':
    685                 $value = (int) $value;
    686                 if ( $value < 1 || $value > 999 ) {
    687                     return;
    688                 }
    689                 break;
    690             default:
    691                 $screen_option = false;
    692 
    693                 if ( '_page' === substr( $option, -5 ) || 'layout_columns' === $option ) {
    694                     /**
    695                      * Filters a screen option value before it is set.
    696                      *
    697                      * The filter can also be used to modify non-standard [items]_per_page
    698                      * settings. See the parent function for a full list of standard options.
    699                      *
    700                      * Returning false from the filter will skip saving the current option.
    701                      *
    702                      * @since 2.8.0
    703                      * @since 5.4.2 Only applied to options ending with '_page',
    704                      *              or the 'layout_columns' option.
    705                      *
    706                      * @see set_screen_options()
    707                      *
    708                      * @param mixed  $screen_option The value to save instead of the option value.
    709                      *                              Default false (to skip saving the current option).
    710                      * @param string $option        The option name.
    711                      * @param int    $value         The option value.
    712                      */
    713                     $screen_option = apply_filters( 'set-screen-option', $screen_option, $option, $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
    714                 }
    715 
     685    if ( ! isset( $_POST['wp_screen_options'] ) || ! is_array( $_POST['wp_screen_options'] ) ) {
     686        return;
     687    }
     688
     689    check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
     690
     691    $user = wp_get_current_user();
     692
     693    if ( ! $user ) {
     694        return;
     695    }
     696
     697    $option = $_POST['wp_screen_options']['option'];
     698    $value  = $_POST['wp_screen_options']['value'];
     699
     700    if ( sanitize_key( $option ) !== $option ) {
     701        return;
     702    }
     703
     704    $map_option = $option;
     705    $type       = str_replace( 'edit_', '', $map_option );
     706    $type       = str_replace( '_per_page', '', $type );
     707
     708    if ( in_array( $type, get_taxonomies(), true ) ) {
     709        $map_option = 'edit_tags_per_page';
     710    } elseif ( in_array( $type, get_post_types(), true ) ) {
     711        $map_option = 'edit_per_page';
     712    } else {
     713        $option = str_replace( '-', '_', $option );
     714    }
     715
     716    switch ( $map_option ) {
     717        case 'edit_per_page':
     718        case 'users_per_page':
     719        case 'edit_comments_per_page':
     720        case 'upload_per_page':
     721        case 'edit_tags_per_page':
     722        case 'plugins_per_page':
     723        case 'export_personal_data_requests_per_page':
     724        case 'remove_personal_data_requests_per_page':
     725            // Network admin.
     726        case 'sites_network_per_page':
     727        case 'users_network_per_page':
     728        case 'site_users_network_per_page':
     729        case 'plugins_network_per_page':
     730        case 'themes_network_per_page':
     731        case 'site_themes_network_per_page':
     732            $value = (int) $value;
     733
     734            if ( $value < 1 || $value > 999 ) {
     735                return;
     736            }
     737
     738            break;
     739
     740        default:
     741            $screen_option = false;
     742
     743            if ( '_page' === substr( $option, -5 ) || 'layout_columns' === $option ) {
    716744                /**
    717745                 * Filters a screen option value before it is set.
    718746                 *
    719                  * The dynamic portion of the hook name, `$option`, refers to the option name.
     747                 * The filter can also be used to modify non-standard [items]_per_page
     748                 * settings. See the parent function for a full list of standard options.
    720749                 *
    721750                 * Returning false from the filter will skip saving the current option.
    722751                 *
    723                  * @since 5.4.2
     752                 * @since 2.8.0
     753                 * @since 5.4.2 Only applied to options ending with '_page',
     754                 *              or the 'layout_columns' option.
    724755                 *
    725756                 * @see set_screen_options()
    726757                 *
    727                  * @param mixed   $screen_option The value to save instead of the option value.
    728                  *                               Default false (to skip saving the current option).
    729                  * @param string  $option        The option name.
    730                  * @param int     $value         The option value.
     758                 * @param mixed  $screen_option The value to save instead of the option value.
     759                 *                              Default false (to skip saving the current option).
     760                 * @param string $option        The option name.
     761                 * @param int    $value         The option value.
    731762                 */
    732                 $value = apply_filters( "set_screen_option_{$option}", $screen_option, $option, $value );
    733 
    734                 if ( false === $value ) {
    735                     return;
    736                 }
    737                 break;
    738         }
    739 
    740         update_user_meta( $user->ID, $option, $value );
    741 
    742         $url = remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() );
    743         if ( isset( $_POST['mode'] ) ) {
    744             $url = add_query_arg( array( 'mode' => $_POST['mode'] ), $url );
    745         }
    746 
    747         wp_safe_redirect( $url );
    748         exit;
    749     }
     763                $screen_option = apply_filters( 'set-screen-option', $screen_option, $option, $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
     764            }
     765
     766            /**
     767             * Filters a screen option value before it is set.
     768             *
     769             * The dynamic portion of the hook name, `$option`, refers to the option name.
     770             *
     771             * Returning false from the filter will skip saving the current option.
     772             *
     773             * @since 5.4.2
     774             *
     775             * @see set_screen_options()
     776             *
     777             * @param mixed   $screen_option The value to save instead of the option value.
     778             *                               Default false (to skip saving the current option).
     779             * @param string  $option        The option name.
     780             * @param int     $value         The option value.
     781             */
     782            $value = apply_filters( "set_screen_option_{$option}", $screen_option, $option, $value );
     783
     784            if ( false === $value ) {
     785                return;
     786            }
     787
     788            break;
     789    }
     790
     791    update_user_meta( $user->ID, $option, $value );
     792
     793    $url = remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() );
     794
     795    if ( isset( $_POST['mode'] ) ) {
     796        $url = add_query_arg( array( 'mode' => $_POST['mode'] ), $url );
     797    }
     798
     799    wp_safe_redirect( $url );
     800    exit;
    750801}
    751802
     
    755806 * @since 2.8.0
    756807 *
     808 * @param string $filename The file path to the configuration file
    757809 * @return bool
    758  * @param string $filename The file path to the configuration file
    759810 */
    760811function iis7_rewrite_rule_exists( $filename ) {
     
    762813        return false;
    763814    }
     815
    764816    if ( ! class_exists( 'DOMDocument', false ) ) {
    765817        return false;
     
    767819
    768820    $doc = new DOMDocument();
     821
    769822    if ( $doc->load( $filename ) === false ) {
    770823        return false;
    771824    }
     825
    772826    $xpath = new DOMXPath( $doc );
    773827    $rules = $xpath->query( '/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]' );
    774     if ( 0 == $rules->length ) {
     828
     829    if ( 0 === $rules->length ) {
    775830        return false;
    776     } else {
    777         return true;
    778     }
     831    }
     832
     833    return true;
    779834}
    780835
     
    803858        return false;
    804859    }
     860
    805861    $xpath = new DOMXPath( $doc );
    806862    $rules = $xpath->query( '/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]' );
     863
    807864    if ( $rules->length > 0 ) {
    808865        $child  = $rules->item( 0 );
     
    812869        saveDomDocument( $doc, $filename );
    813870    }
     871
    814872    return true;
    815873}
     
    847905    // First check if the rule already exists as in that case there is no need to re-add it.
    848906    $wordpress_rules = $xpath->query( '/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]' );
     907
    849908    if ( $wordpress_rules->length > 0 ) {
    850909        return true;
     
    853912    // Check the XPath to the rewrite rule and create XML nodes if they do not exist.
    854913    $xmlnodes = $xpath->query( '/configuration/system.webServer/rewrite/rules' );
     914
    855915    if ( $xmlnodes->length > 0 ) {
    856916        $rules_node = $xmlnodes->item( 0 );
     
    859919
    860920        $xmlnodes = $xpath->query( '/configuration/system.webServer/rewrite' );
     921
    861922        if ( $xmlnodes->length > 0 ) {
    862923            $rewrite_node = $xmlnodes->item( 0 );
     
    867928
    868929            $xmlnodes = $xpath->query( '/configuration/system.webServer' );
     930
    869931            if ( $xmlnodes->length > 0 ) {
    870                 $system_webServer_node = $xmlnodes->item( 0 );
    871                 $system_webServer_node->appendChild( $rewrite_node );
     932                $system_webserver_node = $xmlnodes->item( 0 );
     933                $system_webserver_node->appendChild( $rewrite_node );
    872934            } else {
    873                 $system_webServer_node = $doc->createElement( 'system.webServer' );
    874                 $system_webServer_node->appendChild( $rewrite_node );
     935                $system_webserver_node = $doc->createElement( 'system.webServer' );
     936                $system_webserver_node->appendChild( $rewrite_node );
    875937
    876938                $xmlnodes = $xpath->query( '/configuration' );
     939
    877940                if ( $xmlnodes->length > 0 ) {
    878941                    $config_node = $xmlnodes->item( 0 );
    879                     $config_node->appendChild( $system_webServer_node );
     942                    $config_node->appendChild( $system_webserver_node );
    880943                } else {
    881944                    $config_node = $doc->createElement( 'configuration' );
    882945                    $doc->appendChild( $config_node );
    883                     $config_node->appendChild( $system_webServer_node );
     946                    $config_node->appendChild( $system_webserver_node );
    884947                }
    885948            }
     
    909972    $config = $doc->saveXML();
    910973    $config = preg_replace( "/([^\r])\n/", "$1\r\n", $config );
    911     $fp     = fopen( $filename, 'w' );
     974
     975    $fp = fopen( $filename, 'w' );
    912976    fwrite( $fp, $config );
    913977    fclose( $fp );
     
    9471011        $current_color = 'fresh';
    9481012    }
    949 
    9501013    ?>
    9511014    <fieldset id="color-picker" class="scheme-list">
     
    9561019
    9571020            ?>
    958             <div class="color-option <?php echo ( $color == $current_color ) ? 'selected' : ''; ?>">
     1021            <div class="color-option <?php echo ( $color === $current_color ) ? 'selected' : ''; ?>">
    9591022                <input name="admin_color" id="admin_color_<?php echo esc_attr( $color ); ?>" type="radio" value="<?php echo esc_attr( $color ); ?>" class="tog" <?php checked( $color, $current_color ); ?> />
    9601023                <input type="hidden" class="css_url" value="<?php echo esc_url( $color_info->url ); ?>" />
     
    9641027                    <tr>
    9651028                    <?php
    966 
    9671029                    foreach ( $color_info->colors as $html_color ) {
    9681030                        ?>
     
    9701032                        <?php
    9711033                    }
    972 
    9731034                    ?>
    9741035                    </tr>
     
    9781039
    9791040        endforeach;
    980 
    9811041        ?>
    9821042    </fieldset>
     
    10661126        foreach ( $data['wp-check-locked-posts'] as $key ) {
    10671127            $post_id = absint( substr( $key, 5 ) );
     1128
    10681129            if ( ! $post_id ) {
    10691130                continue;
     
    10711132
    10721133            $user_id = wp_check_post_lock( $post_id );
     1134
    10731135            if ( $user_id ) {
    10741136                $user = get_userdata( $user_id );
     1137
    10751138                if ( $user && current_user_can( 'edit_post', $post_id ) ) {
    10761139                    $send = array(
     
    11131176
    11141177        $post_id = absint( $received['post_id'] );
     1178
    11151179        if ( ! $post_id ) {
    11161180            return $response;
     
    11231187        $user_id = wp_check_post_lock( $post_id );
    11241188        $user    = get_userdata( $user_id );
     1189
    11251190        if ( $user ) {
    11261191            $error = array(
     
    11371202        } else {
    11381203            $new_lock = wp_set_post_lock( $post_id );
     1204
    11391205            if ( $new_lock ) {
    11401206                $send['new_lock'] = implode( ':', $new_lock );
     
    11601226function wp_refresh_post_nonces( $response, $data, $screen_id ) {
    11611227    if ( array_key_exists( 'wp-refresh-post-nonces', $data ) ) {
    1162         $received                           = $data['wp-refresh-post-nonces'];
     1228        $received = $data['wp-refresh-post-nonces'];
     1229
    11631230        $response['wp-refresh-post-nonces'] = array( 'check' => 1 );
    11641231
    11651232        $post_id = absint( $received['post_id'] );
     1233
    11661234        if ( ! $post_id ) {
    11671235            return $response;
     
    12001268    // Refresh the Heartbeat nonce.
    12011269    $response['heartbeat_nonce'] = wp_create_nonce( 'heartbeat-nonce' );
     1270
    12021271    return $response;
    12031272}
     
    14521521
    14531522    $response = get_site_transient( 'php_check_' . $key );
     1523
    14541524    if ( false === $response ) {
    14551525        $url = 'http://api.wordpress.org/core/serve-happy/1.0/';
     1526
    14561527        if ( wp_http_supports( array( 'ssl' ) ) ) {
    14571528            $url = set_url_scheme( $url, 'https' );
Note: See TracChangeset for help on using the changeset viewer.