0, 'finished' => 0, 'checked' => 0, 'items' => array(), 'queue' => array() ), $r ) : array( 'started' => 0, 'finished' => 0, 'checked' => 0, 'items' => array(), 'queue' => array() ); } private static function save( array $r ) { update_option( self::OPTION, $r, false ); } /* --------------------------------------------------------------------- * Проверка по шагам * ------------------------------------------------------------------- */ /** Начать проверку: очередь источников. */ public static function start() { YSNO_Checker::reset(); $queue = array(); $total = 0; foreach ( YSNO_Sources::all() as $id => $src ) { $n = (int) call_user_func( $src['count'] ); if ( $n > 0 ) { $queue[] = array( 'source' => $id, 'offset' => 0, 'total' => $n ); $total += $n; } } self::save( array( 'started' => time(), 'finished' => 0, 'checked' => 0, 'total' => $total, 'items' => array(), 'queue' => $queue, ) ); return $total; } /** Следующая порция. */ public static function step() { $r = self::results(); $sources = YSNO_Sources::all(); $budget = self::STEP; while ( $budget > 0 && $r['queue'] ) { $head = &$r['queue'][0]; if ( empty( $sources[ $head['source'] ] ) ) { array_shift( $r['queue'] ); continue; } $items = (array) call_user_func( $sources[ $head['source'] ]['fetch'], $head['offset'], $budget ); foreach ( $items as $item ) { self::store_item( $r, $item, $head['source'] ); } $head['offset'] += count( $items ); $r['checked'] += count( $items ); $budget -= count( $items ); if ( ! $items || $head['offset'] >= $head['total'] ) { array_shift( $r['queue'] ); } unset( $head ); } if ( ! $r['queue'] && ! $r['finished'] ) { $r['finished'] = time(); self::save( $r ); /** * Проверка сайта закончена. * * @param array $r Результаты. */ do_action( 'ysno/scan_finished', $r ); } else { self::save( $r ); } return $r; } /** Проверить элемент и записать результат. */ private static function store_item( array &$r, array $item, $source ) { $found = YSNO_Checker::check( $item['text'] ); if ( 0 === strpos( $item['key'], 'post:' ) ) { update_post_meta( (int) substr( $item['key'], 5 ), self::META, count( $found ) ); } if ( $found ) { $item['latin'] = YSNO_Checker::latin_share( $item['text'] ); unset( $item['text'] ); $item['source'] = $source; $item['found'] = $found; $r['items'][ $item['key'] ] = $item; } else { unset( $r['items'][ $item['key'] ] ); } } /** Перепроверка записи при сохранении. */ public static function on_save( $post_id, $post ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || ! in_array( $post->post_type, YSNO_Sources::post_types(), true ) ) { return; } if ( 'publish' !== $post->post_status ) { delete_post_meta( $post_id, self::META ); $r = self::results(); unset( $r['items'][ 'post:' . $post_id ] ); self::save( $r ); return; } $r = self::results(); self::store_item( $r, YSNO_Sources::post_item( $post ), 'posts' ); self::save( $r ); } /* --------------------------------------------------------------------- * Сводка * ------------------------------------------------------------------- */ /** * Слова по всему сайту: ключ => [word, type, suggest, note, count, items[]]. * * @return array */ public static function words() { $words = array(); foreach ( self::results()['items'] as $key => $item ) { foreach ( $item['found'] as $wkey => $f ) { if ( ! isset( $words[ $wkey ] ) ) { $words[ $wkey ] = array_merge( $f, array( 'count' => 0, 'items' => array() ) ); } $words[ $wkey ]['count'] += (int) $f['count']; $words[ $wkey ]['items'][] = $key; } } uasort( $words, static function ( $a, $b ) { $order = array( 'word' => 0, 'latin' => 1, 'abbr' => 2 ); return array( $order[ $a['type'] ] ?? 3, -count( $a['items'] ), -$a['count'] ) <=> array( $order[ $b['type'] ] ?? 3, -count( $b['items'] ), -$b['count'] ); } ); return $words; } /** Убрать слово из результатов (после добавления в исключения). */ public static function forget_word( $word ) { $r = self::results(); $low = YSNO_Checker::lower( $word ); foreach ( $r['items'] as $key => &$item ) { foreach ( array_keys( $item['found'] ) as $wkey ) { if ( $wkey === $low || 'w:' . $low === $wkey ) { unset( $item['found'][ $wkey ] ); } } if ( 0 === strpos( $key, 'post:' ) ) { update_post_meta( (int) substr( $key, 5 ), self::META, count( $item['found'] ) ); } if ( ! $item['found'] ) { unset( $r['items'][ $key ] ); } } unset( $item ); self::save( $r ); } /* --------------------------------------------------------------------- * AJAX * ------------------------------------------------------------------- */ private static function guard() { check_ajax_referer( 'ysno_ajax', 'nonce' ); if ( ! current_user_can( YSNO_Admin::CAP ) ) { wp_send_json_error( array( 'message' => __( 'Недостаточно прав', 'yasno' ) ), 403 ); } } public static function ajax_start() { self::guard(); wp_send_json_success( array( 'total' => self::start() ) ); } public static function ajax_step() { self::guard(); $r = self::step(); wp_send_json_success( array( 'checked' => (int) $r['checked'], 'total' => (int) ( $r['total'] ?? 0 ), 'finished' => (bool) $r['finished'], 'issues' => count( $r['items'] ), ) ); } public static function ajax_exception() { self::guard(); $word = isset( $_POST['word'] ) ? sanitize_text_field( wp_unslash( $_POST['word'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce проверен в guard(). if ( '' === $word ) { wp_send_json_error( array( 'message' => __( 'Пустое слово', 'yasno' ) ) ); } YSNO_Settings::add_exception( $word ); self::forget_word( $word ); wp_send_json_success( array( 'message' => __( 'Добавлено в исключения', 'yasno' ) ) ); } }