HEX
Server: nginx
System: Linux 167746b7b9c4 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
User: www-data (1000)
PHP: 8.4.3
Disabled: NONE
Upload Files
File: /www/sites/cbgdh_com/index/wp-content/themes/onenav/inc/functions/io-check-link.php
<?php

function get_http_status_codes(){
    $codes   = array(
		// [Informational 1xx]
		100 => 'Continue',
		101 => 'Switching Protocols',
		// [Successful 2xx]
		200 => 'OK',
		201 => 'Created',
		202 => 'Accepted',
		203 => 'Non-Authoritative Information',
		204 => 'No Content',
		205 => 'Reset Content',
		206 => 'Partial Content',
		// [Redirection 3xx]
		300 => 'Multiple Choices',
		301 => 'Moved Permanently',
		302 => 'Moved Temporarily',
		303 => 'See Other',
		304 => 'Not Modified',
		305 => 'Use Proxy',
		//306=>'(Unused)',
		307 => 'Temporary Redirect',
		// [Client Error 4xx]
		400 => 'Bad Request',
		401 => 'Unauthorized',
		402 => 'Payment Required',
		403 => 'Forbidden',
		404 => 'Not Found',
		405 => 'Method Not Allowed',
		406 => 'Not Acceptable',
		407 => 'Proxy Authentication Required',
		408 => 'Request Timeout',
		409 => 'Conflict',
		410 => 'Gone',
		411 => 'Length Required',
		412 => 'Precondition Failed',
		413 => 'Request Entity Too Large',
		414 => 'Request-URI Too Long',
		415 => 'Unsupported Media Type',
		416 => 'Requested Range Not Satisfiable',
		417 => 'Expectation Failed',
		// [Server Error 5xx]
		500 => 'Internal Server Error',
		501 => 'Not Implemented',
		502 => 'Bad Gateway',
		503 => 'Service Unavailable',
		504 => 'Gateway Timeout',
		505 => 'HTTP Version Not Supported',
		509 => 'Bandwidth Limit Exceeded',
		510 => 'Not Extended',
	);
    return $codes;
}
if(!function_exists('idn_to_ascii')){
require_once get_theme_file_path('/inc/classes/idna_convert.class.php'); 
function idn_to_ascii( $url, $charset = '' ) {
    $idn = new idna_convert();
    if ( null != $idn ) {
        if ( empty( $charset ) ) {
            $charset = get_bloginfo( 'charset' );
        }

        // 仅对 host 进行编码。
        if ( preg_match( '@(\w+:/*)?([^/:]+)(.*$)?@s', $url, $matches ) ) {
            $host = $matches[2];
            if ( ( strtoupper( $charset ) != 'UTF-8' ) && ( strtoupper( $charset ) != 'UTF8' ) ) {
                $host = io_encode_utf8( $host, $charset, true );
            }
            $host = $idn->encode( $host );
            $url  = $matches[1] . $host . $matches[3];
        }
    }

    return $url;
}
}
add_action( 'wp_ajax_io_current_load',  'io_ajax_current_load' );
function io_ajax_current_load() {
    $load = IOTOOLS::get_server_load();
    if ( empty( $load ) ) {
        die( "未知错误" );
    }

    $one_minute = reset( $load );
    printf( '%.2f', $one_minute );
    die();
}

add_action( 'wp_ajax_io_recheck_link',  'io_ajax_recheck' );
function io_ajax_recheck() {
    if ( ! current_user_can( 'edit_others_posts' ) ) {
        die( json_encode(array(
            'status' => 0,
            'error' => '你不允许这样做!',
            )) 
        );
    }

    if ( ! isset( $_POST['post_id'] ) || ! is_numeric( $_POST['post_id'] ) ) {
        die( json_encode(array(
            'status' => 0,
            'error' => '未指定 post_id',
            )) 
        );
    }

    $id   = intval( $_POST['post_id'] );
    $link = new IOLINK( $id );

    if ( ! $link->valid() ) {
        die(
            json_encode(
                array(
                    'status' => 0,
                    'error' => sprintf( "哎呀,找不到 id:%d 对应的链接!", $id ),
                )
            )
        );
    }

    //如果立即检查失败,这将确保在下一个work()运行期间检查该链接。
    $link->last_check_attempt  = 0;
    //$link->isOptionLinkChanged = true;

    $is_save = true;
    if (get_post_meta($id, '_affirm_dead_url', true) || get_post_meta($id, '_revive_url_m', true)) {
        $is_save = false;
    }
    if($is_save) $link->save();
    //检查链接并保存结果。
    $link->check($is_save);

    $status   = $link->analyse_status();
    $response = array(
        'status'         => 1,
        'status_text'    => $status['text'],
        'status_code'    => $status['code'],
        'http_code'      => empty( $link->http_code ) ? '' : $link->http_code,
        'redirect_count' => $link->redirect_count,
        'final_url'      => $link->final_url,
        'url'            => $link->url,
        'log'            => $link->log,
    );

    die( json_encode( $response ) );
}

if ( ! function_exists( 'microtime_float' ) ) {
	function microtime_float() {
		list( $usec, $sec ) = explode( ' ', microtime() );
		return ( (float) $usec + (float) $sec );
	}
}
/**
 * 做各种事情的主要功能。
 *
 * @return void
 */
function link_check_work() { 
    // 关闭会话以防止锁死。
    // PHP会话是阻塞的。 session_start()将等待所有使用同一会话的其他脚本完成。
    // 因此,一个长期运行的脚本如果无意中保持会话开放,会导致整个网站对当前用户/浏览器 "锁定"。
    // WordPress本身不使用会话,但有些插件使用,所以应该在启动之前明确地关闭会话(如果有的话)。
    if ( session_id() != '' ) {
        session_write_close();
    }

    if ( ! acquire_lock('io_nav_cron_links') ) {
        IOTOOLS::log("另一个检查实例已经在工作了。停止检查!", SAVE_CHECK_LOG);
        return;
    }

    if ( io_server_too_busy() ) {
        // LOG: 服务器负载过高,停止。
        IOTOOLS::log("服务器负载过高,停止检查!", SAVE_CHECK_LOG);
        return;
    }

    $execution_start_time = microtime_float();
    

    $options = io_get_option( "link_check_options", array('max_execution_time'=>420) );
    $max_execution_time = $options['max_execution_time'];
    IOTOOLS::log("====== 开始执行定时检查任务({$max_execution_time}秒) ======", SAVE_CHECK_LOG);

    /*****************************************
                        准备
    ******************************************/
    // 检查是否有安全模式
    if ( IOTOOLS::is_safe_mode() ) {
        // 以安全模式的方式进行--遵守现有的最大执行时间(max_execution_time)设置
        $t = ini_get( 'max_execution_time' );
        if ( $t && ( $t < $max_execution_time ) ) {
            $max_execution_time = $t - 1;
        }
    } else {
        // 按常规方法进行
        @set_time_limit( $max_execution_time * 2 ); //x2应该足够了,再跑下去就意味着出了问题。
    }

    //当连接被关闭时,不要停止脚本
    ignore_user_abort( true );

    //按以下规定关闭连接 http://www.php.net/manual/en/features.connection-handling.php#71172
    //这可以减少资源的使用。
    //(调试时禁用,否则无法获得FireHP输出)
    if ( ! headers_sent() && ( defined( 'DOING_AJAX' ) && constant( 'DOING_AJAX' ) ) && ( ! defined( 'WP_DEBUG' ) || ! constant( 'WP_DEBUG' ) ) ) {
        @ob_end_clean(); //丢弃现有的缓冲区,如果有的话。
        header( 'Connection: close' );
        ob_start();
        echo ( 'Connection closed' ); //这可以是任何东西
        $size = ob_get_length();
        header( "Content-Length: $size" );
        ob_end_flush(); // 奇怪的行为,将无法工作
        flush();        // 除非两者都被调用!
    }

    //目标使用率必须在 1% 到 100% 之间。
    $target_usage_fraction = 0.25;


    /*****************************************
                    解析新文章并检查
    ******************************************/
    IOTOOLS::log("开始解析新文章", SAVE_CHECK_LOG);
    $max_posts_per_query    = 50;

    $start                  = microtime( true );
    $posts                  = io_get_links_to_list( $max_posts_per_query );
    $get_posts_time         = microtime( true ) - $start;
    $is_analyse             = false;
    $options                = io_get_option( "link_check_options", array('check_threshold'=>72) );//默认每 72小时
    $check_threshold        = date( 'Y-m-d H:i:s', strtotime( '-' . $options['check_threshold'] . ' hours' ) );//多久检查一次

    while ( ! empty( $posts ) ) {
        io_sleep_to_maintain_ratio( $get_posts_time, $target_usage_fraction );

        IOTOOLS::log("解析到".count( $posts ) ."个新链接", SAVE_CHECK_LOG);
        $is_analyse = true;

        foreach ( $posts as $post ) {
            $synch_start_time = microtime( true );

            if( strtotime($post->last_check_attempt)<strtotime($check_threshold) ){
                // LOG: 这个链接需要被检查吗?被排除的链接不被检查,但它们的URL仍会被定期测试,看它们是否仍在排除名单上。
                if ( $post->valid() && !io_is_excluded( $post->url ) ) {
                    // LOG: 检查链接。
                    $post->check( true );
                } else {
                    $post->last_check_attempt = current_time( 'timestamp' );
                    $post->save();
                }
            }else{
                IOTOOLS::log("此新链接 ({$post->url}) 可能已经在列队中!", SAVE_CHECK_LOG);
            }
            // 标记为加入检查列队
            io_post_to_check_list( $post->post_id );
            
            $synch_elapsed_time = microtime( true ) - $synch_start_time;

            if ( (microtime_float() - $execution_start_time) > $max_execution_time ) {
                IOTOOLS::log("分配的执行时间已用完,停止解析新文章并检查的任务!", SAVE_CHECK_LOG);
                release_lock('io_nav_cron_links');
                return;
            }
            
            if ( io_server_too_busy() ) {
                IOTOOLS::log("服务器负载过高,停止解析新文章并检查的任务!", SAVE_CHECK_LOG);
                release_lock('io_nav_cron_links');
                return;
            }
            // 放慢解析速度,以减少服务器的负荷。基本上,我们在$target_usage_fraction时间内工作,其余时间则睡觉。
            io_sleep_to_maintain_ratio( $synch_elapsed_time, $target_usage_fraction );
        }
        $start          = microtime( true );
        $posts          = io_get_links_to_list( $max_posts_per_query );
        $get_posts_time = microtime( true ) - $start;
    }
    if($is_analyse){
        IOTOOLS::log("解析完成", SAVE_CHECK_LOG);
    }else{
        IOTOOLS::log("没有解析到新内容", SAVE_CHECK_LOG);
    }
    
    /*****************************************
                        检查链接
    ******************************************/
    IOTOOLS::log("准备检查链接", SAVE_CHECK_LOG);
    $max_links_per_query    = 30;

    $start                  = microtime( true );
    $links                  = io_get_links_to_check( $max_links_per_query );
    $get_links_time         = microtime( true ) - $start;
    $is_check               = false;

    while ( $links ) {
        io_sleep_to_maintain_ratio( $get_links_time, $target_usage_fraction );
        
        IOTOOLS::log("检查".count( $links ) ."个链接", SAVE_CHECK_LOG);
        $is_check = true;

        //将数组随机化可以减少在一排中得到几个指向同一域的链接的机会。
        shuffle( $links );

        foreach ( $links as $link ) {
            //这个链接需要被检查吗?被排除的链接不被检查,但它们的URL仍会被定期测试,看它们是否仍在排除名单上。
            if ( $link->valid() && !io_is_excluded( $link->url ) ) {
                //检查链接。
                $link->check( true );
            } else {
                $link->last_check_attempt = current_time( 'timestamp' );
                $link->save();
            }

            if ( (microtime_float() - $execution_start_time) > $max_execution_time ) {
                IOTOOLS::log("分配的执行时间已用完,停止检查任务!", SAVE_CHECK_LOG);
                release_lock('io_nav_cron_links');
                return;
            }

            if ( io_server_too_busy() ) {
                IOTOOLS::log("服务器负载过高,停止检查任务!", SAVE_CHECK_LOG);
                release_lock('io_nav_cron_links');
                return;
            }
        }

        $start          = microtime( true );
        $links          = io_get_links_to_check( $max_links_per_query );
        $get_links_time = microtime( true ) - $start;
    } 
    if($is_check){
        IOTOOLS::log("检查链接完成", SAVE_CHECK_LOG);
    }else{
        IOTOOLS::log("没有需要检查的内容", SAVE_CHECK_LOG);
    }

    release_lock('io_nav_cron_links');

    IOTOOLS::log("====== 完成此次检查任务! ======", SAVE_CHECK_LOG);
}
/**
 * 检查服务器当前是否过载
 */
function io_server_too_busy() {
    $options = io_get_option( "link_check_options", array('server_load_limit'=>4) );
    if ( ( isset( $options['server_load_limit'] ) && 0===$options['server_load_limit'] ) || ! isset( $options['server_load_limit'] ) ) {
        return false;
    }

    $loads = IOTOOLS::get_server_load();
    if ( empty( $loads ) ) {
        return false;
    }
    $one_minute = floatval( reset( $loads ) );

    return $one_minute > $options['server_load_limit'];
}
/**
 * 检查URL是否在排除列表。
 *
 * @param string $url 
 * @return bool
 */
function io_is_excluded( $url ) {
    $options = io_get_option( "link_check_options", array('exclusion_list'=>'') );
    $_list = preg_split("/,|,|\s|\n/", $options['exclusion_list']);
    if ( ! is_array( $_list ) ) {
        return false;
    }
    foreach ( $_list as $excluded_word ) {
        if ( stristr( $url, $excluded_word ) ) {
            return true;
        }
    }
    return false;
}
/**
 * 检索需要检查或重新检查的链接。
 *
 * @param integer $max_results 要返回的最大链接数。默认值为0=无限制。
 * @param bool $count_only 如果为true,则只返回找到的链接数,而不返回链接本身。
 * @return int|IOLINK[]|string
 */
function io_get_links_to_check( $max_results = 0, $count_only = false ) {
    global $wpdb; 

    $options = io_get_option( "link_check_options", array('check_threshold'=>72) );
    //每 72小时
    $check_threshold   = date( 'Y-m-d H:i:s', strtotime( '-' . $options['check_threshold'] . ' hours' ) );//多久检查一次
    $recheck_threshold = date( 'Y-m-d H:i:s', current_time( 'timestamp' ) - 1800 );//多少时间后重新检查  30 * 60  30分钟

    //Note : 这是一个缓慢的查询,但恐怕没有办法加快速度。
    if ( $count_only ) {
        $q = "SELECT COUNT(DISTINCT $wpdb->posts.ID)\n";
    } else {
        $q = "SELECT DISTINCT $wpdb->posts.ID\n";
    }
    //$q .= "FROM $wpdb->posts
    //    INNER JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
    //    INNER JOIN $wpdb->postmeta AS mt1 ON ( $wpdb->posts.ID = mt1.post_id )  
    //    INNER JOIN $wpdb->postmeta AS mt2 ON ( $wpdb->posts.ID = mt2.post_id )  
    //    INNER JOIN $wpdb->postmeta AS mt3 ON ( $wpdb->posts.ID = mt3.post_id )  
    //    WHERE 1=1
    //    AND (
    //        ( $wpdb->postmeta.meta_key = '_last_check' AND $wpdb->postmeta.meta_value < %s )
    //        OR ( 
    //            (  
    //                ($wpdb->postmeta.meta_key = '_dead_link' AND $wpdb->postmeta.meta_value = '1')
    //                OR ($wpdb->postmeta.meta_key = '_being_checked' AND $wpdb->postmeta.meta_value = '1')
    //            )
    //            AND (mt1.meta_key = '_may_recheck' AND mt1.meta_value = '1' )
    //            AND (mt2.meta_key = 'invalid' AND mt2.meta_value < '3' )
    //            AND (mt3.meta_key = '_last_check' AND mt3.meta_value < %s )
    //        ) 
    //    )
    //    AND $wpdb->posts.post_type = 'sites'
    //    AND (($wpdb->posts.post_status = 'publish'))";
        
    $q .= "FROM $wpdb->posts
        INNER JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
        WHERE 1=1
        AND (
            ( $wpdb->postmeta.meta_key = '_last_check' AND $wpdb->postmeta.meta_value < %s )
        )
        AND $wpdb->posts.post_type = 'sites'
        AND (($wpdb->posts.post_status = 'publish'))";
    if ( ! $count_only ) {
        if ( ! empty( $max_results ) ) {
            $q .= "\nLIMIT " . intval( $max_results );
        }
    }

    $link_q = $wpdb->prepare( $q, $check_threshold );//$recheck_threshold
    //LOG: 查找链接以进行检查 . $link_q;

    //如果只需要链接的数量,则检索它并返回
    if ( $count_only ) {
        return $wpdb->get_var( $link_q );
    }

    //获取链接数据
    $link_data = $wpdb->get_results( $link_q, ARRAY_A ); 
    if ( empty( $link_data ) ) {
        return array();
    }

    //为所有获取的链接实例化 IOLINK 对象
    $links = array();
    foreach ( $link_data as $data ) {
        $link = new IOLINK( $data['ID']);
        if($link->valid()){
            $links[] = $link;
        }
    }

    return $links;
}
/**
 * 解析链接加入检查对象。
 *
 * @param integer $max_results 要返回的最大链接数。默认值为0=无限制。
 * @param bool $count_only 如果为true,则只返回找到的链接数,而不返回链接本身。
 * @return int|IOLINK[]|string
 */
function io_get_links_to_list( $max_results = 0, $count_only = false ) {
    global $wpdb; 

    $options = get_option( "link_check_options", array('check_threshold'=>72) );

    if ( $count_only ) {
        $q = "SELECT COUNT( * )\n";
    } else {
        $q = "SELECT ID\n";
    }

    $q .= "FROM $wpdb->posts 
        WHERE 1=1
        AND `post_mime_type` = ''
        AND `post_type` = 'sites'
        AND `post_status` = 'publish'";
    if ( ! $count_only ) {
        if ( ! empty( $max_results ) ) {
            $q .= "\nLIMIT " . intval( $max_results );
        }
    }

    $link_q = $q;

    //如果只需要链接的数量,则检索它并返回
    if ( $count_only ) {
        return $wpdb->get_var( $link_q );
    }

    //获取链接数据
    $link_data = $wpdb->get_results( $link_q, ARRAY_A ); 
    if ( empty( $link_data ) ) {
        return array();
    }

    //为所有获取的链接实例化 IOLINK 对象
    $links = array();
    foreach ( $link_data as $data ) {
        $link = new IOLINK( $data['ID']);
        if($link->valid()){
            $links[] = $link;
        }
    }

    return $links;
}

/**
 * 标记网址已经加入检查列队
 * #TODO:占用 post 表中的 post_mime_type 字段,理论不影响wp功能
 * @return int|false 更新了多少行
 */
function io_post_to_check_list( $post_id) {
	global $wpdb;

	$post = get_post( $post_id );

	if ( ! $post ) {
		return false;
	}

	$return = $wpdb->update( $wpdb->posts, array( 'post_mime_type' => 1 ), array( 'ID' => $post->ID ) );
    //clean_post_cache( $post->ID );
	return $return;
}
/**
 * 睡眠时间足够长,以维持 $elapsed_time 和总运行时间之间所需的 $ratio。
 *
 * 例如,如果 $ratio 为 0.25 且 $elapsed_time 为 1 秒,则此方法将休眠 3 秒。
 * Total runtime(总运行时间) = 1 + 3 = 4, ratio(比例) = 1 / 4 = 0.25.
 *
 * @param float $elapsed_time 消耗的时间
 * @param float $ratio 比例
 */
function io_sleep_to_maintain_ratio( $elapsed_time, $ratio ) {
    if ( ( $ratio <= 0 ) || ( $ratio > 1 ) ) {
        return;
    }
    $sleep_time = $elapsed_time * ( ( 1 / $ratio ) - 1 );
    if ( $sleep_time > 0.0001 ) {
        usleep( $sleep_time * 1000000 );
    }
}
/**
 * 获取独占命名锁。
 */
function acquire_lock( $name, $timeout = 0 ) {
    global $wpdb; 
    $state = $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $name, $timeout ) );
    return 1 == $state;
}

/**
 * 释放一个命名锁。
 */
function release_lock( $name ) {
    global $wpdb; 
    $released = $wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $name ) );
    return 1 == $released;
}

function io_cron_check_links_events() {
    link_check_work();
}

/**
 * 获取网址状态
 *
 * @param int $post_id 文章 ID
 * @param int $show_level 显示等级
 * @param bool $display 直接输出 html
 * 
 * @return array|bool|null
 */
function get_sites_url_state($post_id, $show_level = 3, $display = false){
    $link = new IOLINK( $post_id );
    /*
		'broken'    死链
		'warning'   注意&超时
		'final_url' 重定向
    */
    $data = false;
    if( $link->broken && $show_level >=1 ){
        $data = array(
            'level' => 1,
            'code'  => 'error',
            'text'  => __('失效链接','i_theme'),
        );
    }elseif( $link->warning && $show_level >=2 ){
        $data = array(
            'level' => 2,
            'code'  => 'warning',
            'text'  => __('连接超时','i_theme'),
        );
    }elseif( '' !== $link->final_url && $show_level >=3 ){
        $data = array(
            'level' => 3,
            'code'  => 'info',
            'text'  => __('重定向&变更','i_theme'),
        );
    }
    if(is_array($data) && $display){
        echo $data['text'];
    }else{
        return $data;
    }
}