Compare commits

...

8 Commits

Author SHA1 Message Date
Profile Profile
ed4d79b706 add files inc 2026-03-09 20:51:08 +03:00
Profile Profile
83ca6c638a add logic coauthors 2026-03-05 00:25:12 +03:00
Profile Profile
2a88b55732 add coauthor endpoint 2026-03-02 17:25:21 +03:00
Profile Profile
485913ca23 add endpoints 2026-02-15 23:06:39 +03:00
Profile Profile
141d117b96 add coauthors_filter 2026-02-03 22:56:39 +03:00
Profile Profile
61b86cdca5 new zaglushka 2026-01-30 12:55:59 +03:00
Profile Profile
b771da4504 add support mainitem and colonitem in GraphQL 2026-01-29 01:31:22 +03:00
Profile Profile
d99f75aed7 add utils 2026-01-25 21:15:07 +03:00
30 changed files with 5072 additions and 78 deletions

307
api/coauthor.php Normal file
View File

@@ -0,0 +1,307 @@
<?php
add_action('rest_api_init', function() {
register_rest_route('my/v1', '/author/(?P<slug>[a-zA-Z0-9-]+)', [
'methods' => 'GET',
'callback' => function($request) {
$slug = $request['slug'];
if (!function_exists('get_coauthors')) {
return new WP_Error('no_plugin', 'Co-Authors Plus not active', ['status' => 404]);
}
global $coauthors_plus;
if (isset($coauthors_plus) && method_exists($coauthors_plus, 'get_coauthor_by')) {
$coauthor = $coauthors_plus->get_coauthor_by('user_nicename', $slug);
if ($coauthor) {
// Получаем ID записи гостевого автора
$author_post_id = $coauthor->ID;
// ПОЛУЧАЕМ ФОТО ИЗ МИНИАТЮРЫ ЗАПИСИ (post thumbnail)
$photo = null;
$photo_sizes = [];
if (has_post_thumbnail($author_post_id)) {
$photo_id = get_post_thumbnail_id($author_post_id);
// Основные размеры
$photo_sizes = [
'thumbnail' => wp_get_attachment_image_url($photo_id, 'thumbnail'),
'medium' => wp_get_attachment_image_url($photo_id, 'medium'),
'large' => wp_get_attachment_image_url($photo_id, 'large'),
'full' => wp_get_attachment_image_url($photo_id, 'full'),
];
$photo = $photo_sizes['medium'];
}
return [
'id' => $coauthor->user_nicename,
'name' => $coauthor->display_name,
'firstName' => $coauthor->first_name ?? '',
'lastName' => $coauthor->last_name ?? '',
'description' => $coauthor->description ?? '',
// Фото из миниатюры поста (это то, что вам нужно)
'photo' => $photo,
'photo_sizes' => $photo_sizes,
// Для обратной совместимости
'avatar' => $photo ?: '',
'url' => get_author_posts_url($coauthor->ID, $coauthor->user_nicename),
'type' => $coauthor->type ?? 'guest-author'
];
}
}
return new WP_Error('not_found', 'Author not found', ['status' => 404]);
},
'permission_callback' => '__return_true'
]);
});
add_action('rest_api_init', function() {
register_rest_route('my/v1', '/author/(?P<slug>[a-zA-Z0-9-]+)/posts', [
'methods' => 'GET',
'callback' => function($request) {
$slug = $request['slug'];
$per_page = $request->get_param('per_page') ?: 10;
$cursor = $request->get_param('cursor');
$before = $request->get_param('before');
$after = $request->get_param('after');
// Устанавливаем нужные типы постов
$post_type = ['profile_article', 'anew', 'yellow'];
if (!function_exists('get_coauthors')) {
return new WP_Error('no_plugin', 'Co-Authors Plus not active', ['status' => 404]);
}
global $coauthors_plus;
// Получаем автора по slug
$coauthor = null;
if (isset($coauthors_plus) && method_exists($coauthors_plus, 'get_coauthor_by')) {
$coauthor = $coauthors_plus->get_coauthor_by('user_nicename', $slug);
}
if (!$coauthor) {
return new WP_Error('not_found', 'Author not found', ['status' => 404]);
}
// Определяем тип автора и получаем термин таксономии author
$author_term = null;
if ($coauthor->type === 'guest-author') {
$author_term = get_term_by('slug', 'cap-' . $coauthor->user_nicename, 'author');
} else {
$author_term = get_term_by('slug', $coauthor->user_nicename, 'author');
}
if (!$author_term) {
return new WP_Error('not_found', 'Author term not found', ['status' => 404]);
}
// Базовые аргументы запроса
$args = [
'post_type' => $post_type,
'posts_per_page' => $per_page,
'post_status' => 'publish',
'tax_query' => [
[
'taxonomy' => 'author',
'field' => 'term_id',
'terms' => $author_term->term_id,
]
],
'orderby' => 'date',
'order' => 'DESC',
];
// Применяем курсорную пагинацию
if ($after) {
$args['date_query'] = [
[
'column' => 'post_date',
'before' => $after,
'inclusive' => false,
]
];
} elseif ($before) {
$args['date_query'] = [
[
'column' => 'post_date',
'after' => $before,
'inclusive' => false,
]
];
$args['order'] = 'ASC';
} elseif ($cursor) {
$cursor_post = get_post($cursor);
if ($cursor_post) {
$args['date_query'] = [
[
'column' => 'post_date',
'before' => $cursor_post->post_date,
'inclusive' => false,
]
];
}
}
$query = new WP_Query($args);
$posts = $query->posts;
if ($before) {
$posts = array_reverse($posts);
}
// Форматируем посты - ТОЛЬКО нужные поля
$formatted_posts = [];
foreach ($posts as $post) {
setup_postdata($post);
// Получаем соавторов
$coauthors = [];
if (function_exists('get_coauthors')) {
$post_coauthors = get_coauthors($post->ID);
foreach ($post_coauthors as $post_coauthor) {
$coauthors[] = [
'id' => $post_coauthor->user_nicename,
'name' => $post_coauthor->display_name,
];
}
}
// Получаем рубрики/категории
$categories = [];
$taxonomies = ['category', 'rubric'];
foreach ($taxonomies as $taxonomy) {
$terms = get_the_terms($post->ID, $taxonomy);
if ($terms && !is_wp_error($terms)) {
foreach ($terms as $term) {
$categories[] = [
'id' => $term->term_id,
'name' => $term->name,
'slug' => $term->slug,
'taxonomy' => $taxonomy,
];
}
}
}
$formatted_posts[] = [
'id' => $post->ID,
'title' => $post->post_title,
'slug' => $post->post_name,
'type' => $post->post_type,
'date' => $post->post_date,
'link' => get_permalink($post->ID),
// Только картинка (разные размеры)
'featured_image' => [
'full' => get_the_post_thumbnail_url($post->ID, 'full'),
'large' => get_the_post_thumbnail_url($post->ID, 'large'),
'medium' => get_the_post_thumbnail_url($post->ID, 'medium'),
'thumbnail' => get_the_post_thumbnail_url($post->ID, 'thumbnail'),
],
// Рубрики
'categories' => $categories,
// Соавторы
'coauthors' => $coauthors,
// Курсор для пагинации
'cursor' => $post->post_date,
];
}
wp_reset_postdata();
// ИСПРАВЛЕННАЯ ПАГИНАЦИЯ
$has_next = false;
$next_cursor = null;
$has_previous = false;
$previous_cursor = null;
$first_cursor = null;
$last_cursor = null;
if (!empty($posts)) {
$first_post = reset($posts);
$last_post = end($posts);
$first_cursor = $first_post->post_date;
$last_cursor = $last_post->post_date;
// Простая проверка: если количество найденных постов больше, чем количество на странице
// ИЛИ если есть больше страниц
$has_next = $query->max_num_pages > 1;
// Если есть следующая страница, курсор - дата последнего поста
if ($has_next) {
$next_cursor = $last_post->post_date;
}
// Проверяем предыдущие посты (если это не первая страница)
if ($cursor || $after || $before) {
// Если есть курсор, значит это не первая страница
$has_previous = true;
$previous_cursor = $first_post->post_date;
}
}
// Добавим отладку (можно убрать после тестирования)
error_log('=== AUTHOR POSTS DEBUG ===');
error_log('Slug: ' . $slug);
error_log('Posts found: ' . count($posts));
error_log('Total posts: ' . $query->found_posts);
error_log('Max num pages: ' . $query->max_num_pages);
error_log('Has next: ' . ($has_next ? 'true' : 'false'));
error_log('Next cursor: ' . ($next_cursor ?? 'null'));
error_log('========================');
return [
'data' => $formatted_posts,
'pagination' => [
'total' => $query->found_posts,
'per_page' => (int)$per_page,
'has_next' => $has_next,
'has_previous' => $has_previous,
'next_cursor' => $next_cursor,
'previous_cursor' => $previous_cursor,
'first_cursor' => $first_cursor,
'last_cursor' => $last_cursor,
],
'author' => [
'id' => $coauthor->user_nicename,
'name' => $coauthor->display_name,
]
];
},
'args' => [
'per_page' => [
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 100,
],
'cursor' => [
'type' => 'string',
'description' => 'Cursor for pagination',
],
'after' => [
'type' => 'string',
'description' => 'Get posts after this date',
],
'before' => [
'type' => 'string',
'description' => 'Get posts before this date',
],
],
'permission_callback' => '__return_true'
]);
});

93
api/newstop.php Normal file
View File

@@ -0,0 +1,93 @@
<?php
add_action('rest_api_init', function () {
register_rest_route('my/v1', '/news-top', [
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_api_news_top',
'permission_callback' => '__return_true', // при необходимости замените на проверку доступа
'args' => [
'per_page' => [
'required' => false,
'default' => 20,
'sanitize_callback' => 'absint',
],
],
]);
});
function my_api_news_top(WP_REST_Request $request) {
$per_page = (int) $request->get_param('per_page');
if ($per_page <= 0) {
$per_page = 20;
}
if ($per_page > 100) {
$per_page = 100; // защита от слишком тяжелых запросов
}
// 1) news_query
$news_query = new WP_Query([
'post_type' => ['anew', 'yellow'],
'ignore_sticky_posts' => true,
'posts_per_page' => $per_page,
'order' => 'DESC',
'orderby' => 'post_date',
]);
// 2) top_query (ids берём из опции ppp_options)
$top_raw = get_option('ppp_options');
$top = json_decode($top_raw);
$ids = [];
if (is_array($top)) {
$ids = array_values(array_filter(array_map(static function ($item) {
return isset($item->id) ? absint($item->id) : 0;
}, $top)));
}
$top_query = null;
$top_items = [];
if (!empty($ids)) {
$top_query = new WP_Query([
'post_type' => ['anew', 'yellow'],
'ignore_sticky_posts' => true,
'post__in' => $ids,
'posts_per_page' => -1,
'order' => 'DESC',
'orderby' => 'post_date',
]);
$top_items = array_map('my_api_map_post', $top_query->posts);
}
$news_items = array_map('my_api_map_post', $news_query->posts);
return rest_ensure_response([
'news' => [
'found' => (int) $news_query->found_posts,
'items' => $news_items,
],
'top' => [
'ids' => $ids,
'found' => $top_query ? (int) $top_query->found_posts : 0,
'items' => $top_items,
],
]);
}
/**
* Приводим WP_Post к простому массиву.
*/
function my_api_map_post($post) {
return [
'id' => (int) $post->ID,
'type' => $post->post_type,
'title' => get_the_title($post),
'date' => get_the_date('c', $post),
'uri' => wp_parse_url(get_permalink($post), PHP_URL_PATH),
'link' => get_permalink($post),
];
}

View File

@@ -1,6 +1,6 @@
<?php
//ini_set('display_errors', 0);
//error_reporting(E_ALL);
//ini_set('display_errors', 1);
//error_reporting(E_ERROR);
header('Content-Type: text/html; charset=utf-8');
@@ -12,10 +12,11 @@ require "inc/cache-cleaner-home.php";
require 'inc/article-rss-feed.php'; //rss ленты статей по датам
require "inc/meta_keywords.php";
require "inc/journal_issue.php"; //номера журналов
require "inc/rank-description.php";
require "inc/rank-description.php"; //подменяем дескрипшт от соцсетей
require "inc/graphql.php"; //graphql
//utils
require "utils/acf-category.php";
require "inc/replace_old_copyright.php"; //подменяем фото для старых картинок с "плохим" копирайтом
//scheduler
require "inc/schedule_async_post_processing.php";
@@ -23,17 +24,24 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) {
require "inc/action-scheduler-functions.php";
require "inc/wp-cli-scheduler-commands.php";
require "inc/adfox_on.php"; // управление adfox
require "inc/generate_coauthors_cache.php"; // генерим кеш списка авторов
}
include "inc/get_cached_alm.php";
include "inc/graphql.php";
if (is_admin()) {
require "inc/add_adv_checked.php"; // рекламная статья
require "inc/admin/auto_check.php"; // помечакм не отправлять в Дзен
require "inc/admin/lock-terms.php"; // banned и keys
require "inc/admin/coauthors_filter.php"; // фильтры авторов
}
#api
require "api/newstop.php"; // управление REST API
require "api/coauthor.php"; // управление REST API
//TODO: Убрать все эти is_yn, is_zen и прочее и внутри плагинов регулировать вывод
@@ -294,8 +302,9 @@ function add_article_in_main_query( $query ) {
//var_dump( get_option('layf_custom_url'));
if (is_admin()){
if (get_query_var('author_name')){
$query->set('post_type', array('profile_article','anew','yellow'));
//$query->set('post_type', array('profile_article','anew','yellow'));
//echo '<!--'.$query->request.'-->';
return $query;
}else if (filter_input(INPUT_GET,'page') == 'wps_authors_page'){
$query->set('post_type', array('profile_article','anew','yellow'));
$query->set('posts_per_page', -1);
@@ -1063,7 +1072,7 @@ add_action('wp_enqueue_scripts', function() {
// Добавляем стиль
$css_url = get_template_directory_uri() . '/assets/css/profile-style.css';
//$css_url = 'https://profile.ru//wp-content/themes/profile/assets/css/profile-style.css';
$css_url = 'https://profile.ru//wp-content/themes/profile/assets/css/profile-style.css';
wp_enqueue_style(
'profile-style', // уникальный идентификатор
@@ -2351,7 +2360,7 @@ add_action('wp_head', function() {
if (!is_singular()) return;
$post_id = get_the_ID();
$default_image = 'https://cdn.profile.ru/wp-content/uploads/2023/07/profile_lazyload_hq.jpg'; // Заглушка в папке темы
$default_image = 'https://cdn.profile.ru/wp-content/uploads/2026/01/zaglushka.jpg'; // Заглушка в папке темы
$og_image = esc_url($default_image);
@@ -6111,6 +6120,13 @@ add_filter( 'http_request_args',

View File

@@ -0,0 +1,166 @@
<?php
// action-scheduler-functions.php (загружается только для воркера)
add_action( 'async_post_processing_trigger', 'handle_async_post_processing', 10, 1 );
function handle_async_post_processing( $post_id ) {
// Сохраняем оригинальное состояние кеширования
//$original_cache_addition = wp_suspend_cache_addition( true );
//$original_cache_invalidation = wp_suspend_cache_invalidation( true );
try {
// Очищаем кеш поста
//clean_post_cache( $post_id );
// Очищаем кеш таксономий
/** $taxonomies = get_object_taxonomies( get_post_type( $post_id ) );
foreach ( $taxonomies as $taxonomy ) {
wp_cache_delete( 'all_ids', $taxonomy );
wp_cache_delete( 'get', $taxonomy );
delete_option( "{$taxonomy}_children" );
wp_cache_delete( 'last_changed', 'terms' );
}*/
// Получаем свежие данные
$post = get_post( $post_id );
log_scheduler_activity( 'текущий пост:', $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return false;
}
$post_url = get_permalink( $post_id );
$post_title = $post->post_title;
$delay = 0;
//google news
as_schedule_single_action(
time() + $delay,
'process_sitemap_submission',
[],
'sitemap_generation'
);
//IndexNow
as_schedule_single_action(
time() + 5,
'process_indexnow_submission',
[ $post_id, $post_url, $post_title ],
'post_publish_processing'
);
return true;
} catch ( Exception $e ) {
// Логируем ошибку
error_log( "failed for post {$post_id}: " . $e->getMessage() );
log_scheduler_activity( "failed for post {$post_id}: " . $e->getMessage() );
// Пробрасываем исключение для перевода задачи в Failed
throw $e;
}
}
function log_scheduler_activity( $message, $post_id = null ) {
$log_file = ABSPATH . 'scheduler.log';
$log_file = '/var/www/profile/html/scheduler.log';
$log_entry = sprintf(
"[%s] %s%s\n",
current_time( 'Y-m-d H:i:s' ),
$post_id ? "Post {$post_id}: " : "",
$message
);
file_put_contents( $log_file, $log_entry, FILE_APPEND | LOCK_EX );
}
// generation google news php82 /usr/bin/wp eval-file test-theme-load.php --path=/var/www/profile/html
add_action('process_sitemap_submission', 'handle_sitemap_submission');
function handle_sitemap_submission() {
$generator = new AK_Sitemap_Generator();
$result = $generator->generate_news_sitemap([ 'profile_article', 'anew', 'yellow' ]);
if ($result) {
error_log('News Sitemap generated successfully');
} else {
error_log('News Sitemap generation failed');
}
return $result;
}
//index now
function process_indexnow_submission( $post_id, $post_url, $post_title ) {
try {
// Проверяем флаг отправки
if ( get_post_meta( $post_id, '_indexnow_sent', true ) ) {
return;
}
// Ваша логика отправки в IndexNow
$api_key = 'b1a2g3d4i8f6g7h8i9juyg0k11l12';
//$domain = parse_url( home_url(), PHP_URL_HOST );
$domain = 'https://profile.ru';
$body = array(
'host' => $domain,
'key' => $api_key,
// 'keyLocation' => esc_url( home_url( '/' . $api_key . '.txt' ) ), // ❌ НЕ НУЖНО
'urlList' => array( $post_url )
);
$response = wp_remote_post( 'https://api.indexnow.org/IndexNow', array(
'body' => wp_json_encode( $body ),
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
'timeout' => 30,
));
// ПРАВИЛЬНАЯ ПРОВЕРКА ОТВЕТА
if ( is_wp_error( $response ) ) {
log_scheduler_activity( "IndexNow WP Error for post {$post_id}: " . $response->get_error_message(), $post_id );
return false;
}
$response_code = wp_remote_retrieve_response_code( $response );
$response_body = wp_remote_retrieve_body( $response );
// IndexNow возвращает 200/201 при успехе
if ( in_array( $response_code, array( 200, 201, 202 ) ) ) {
// Помечаем как успешно отправленный
update_post_meta( $post_id, '_indexnow_sent', current_time( 'mysql' ) );
log_scheduler_activity( "IndexNow success {$post_url}", $post_id );
return true;
} else {
log_scheduler_activity( "IndexNow failed {$post_url}. Status: {$response_code} | Response: {$response_body}", $post_id );
return false;
}
} catch ( Exception $e ) {
// Логируем ошибку
error_log( "IndexNow failed for post {$post_id}: " . $e->getMessage() );
echo $e->getMessage();
// Пробрасываем исключение для перевода задачи в Failed
throw $e;
}
}

View File

@@ -0,0 +1,159 @@
<?php
// action-scheduler-functions.php (загружается только для воркера)
add_action( 'async_post_processing_trigger', 'handle_async_post_processing', 10, 1 );
function handle_async_post_processing( $post_id ) {
// Сохраняем оригинальное состояние кеширования
$original_cache_addition = wp_suspend_cache_addition( true );
$original_cache_invalidation = wp_suspend_cache_invalidation( true );
try {
// Очищаем кеш поста
clean_post_cache( $post_id );
// Очищаем кеш таксономий
$taxonomies = get_object_taxonomies( get_post_type( $post_id ) );
foreach ( $taxonomies as $taxonomy ) {
wp_cache_delete( 'all_ids', $taxonomy );
wp_cache_delete( 'get', $taxonomy );
delete_option( "{$taxonomy}_children" );
wp_cache_delete( 'last_changed', 'terms' );
}
// Получаем свежие данные
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return false;
}
$post_url = get_permalink( $post_id );
$post_title = $post->post_title;
//google news
as_schedule_single_action(
time() + $delay,
'process_sitemap_submission',
[],
'sitemap_generation'
);
//IndexNow
as_schedule_single_action(
time() + 5,
'process_indexnow_submission',
[ $post_id, $post_url, $post_title ],
'post_publish_processing'
);
return true;
} catch ( Exception $e ) {
// Логируем ошибку
error_log( "failed for post {$post_id}: " . $e->getMessage() );
log_scheduler_activity( "failed for post {$post_id}: " . $e->getMessage() );
// Пробрасываем исключение для перевода задачи в Failed
throw $e;
}
}
function log_scheduler_activity( $message, $post_id = null ) {
$log_file = ABSPATH . 'scheduler.log';
$log_entry = sprintf(
"[%s] %s%s\n",
current_time( 'Y-m-d H:i:s' ),
$post_id ? "Post {$post_id}: " : "",
$message
);
file_put_contents( $log_file, $log_entry, FILE_APPEND | LOCK_EX );
}
// generation google news
add_action('process_sitemap_submission', 'handle_sitemap_submission');
function handle_sitemap_submission() {
$generator = new AK_Sitemap_Generator();
$result = $generator->generate_news_sitemap([ 'profile_article', 'anew', 'yellow' ]);
if ($result) {
error_log('News Sitemap generated successfully');
} else {
error_log('News Sitemap generation failed');
}
return $result;
}
//index now
function process_indexnow_submission( $post_id, $post_url, $post_title ) {
try {
// Проверяем флаг отправки
if ( get_post_meta( $post_id, '_indexnow_sent', true ) ) {
return;
}
// Ваша логика отправки в IndexNow
$api_key = 'b1a2g3d4i8f6g7h8i9juyg0k11l12';
//$domain = parse_url( home_url(), PHP_URL_HOST );
$domain = 'https://profile.ru';
$body = array(
'host' => $domain,
'key' => $api_key,
// 'keyLocation' => esc_url( home_url( '/' . $api_key . '.txt' ) ), // ❌ НЕ НУЖНО
'urlList' => array( $post_url )
);
$response = wp_remote_post( 'https://api.indexnow.org/IndexNow', array(
'body' => wp_json_encode( $body ),
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
'timeout' => 30,
));
// ПРАВИЛЬНАЯ ПРОВЕРКА ОТВЕТА
if ( is_wp_error( $response ) ) {
log_scheduler_activity( "IndexNow WP Error for post {$post_id}: " . $response->get_error_message(), $post_id );
return false;
}
$response_code = wp_remote_retrieve_response_code( $response );
$response_body = wp_remote_retrieve_body( $response );
// IndexNow возвращает 200/201 при успехе
if ( in_array( $response_code, array( 200, 201, 202 ) ) ) {
// Помечаем как успешно отправленный
update_post_meta( $post_id, '_indexnow_sent', current_time( 'mysql' ) );
log_scheduler_activity( "IndexNow success {$post_url}", $post_id );
return true;
} else {
log_scheduler_activity( "IndexNow failed {$post_url}. Status: {$response_code} | Response: {$response_body}", $post_id );
return false;
}
} catch ( Exception $e ) {
// Логируем ошибку
error_log( "IndexNow failed for post {$post_id}: " . $e->getMessage() );
echo $e->getMessage();
// Пробрасываем исключение для перевода задачи в Failed
throw $e;
}
}

124
inc/add_adv_checked.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
// Регистрация метабокса и обработка сохранения
add_action('add_meta_boxes', function() {
// Метабокс для рекламного материала
add_meta_box(
'advertisement_meta_box',
'Рекламный текст',
function($post) {
$is_advertisement = get_post_meta($post->ID, '_is_advertisement', true);
wp_nonce_field('advertisement_nonce', 'advertisement_nonce_field');
echo '<label><input type="checkbox" name="is_advertisement" value="1" ' . checked($is_advertisement, '1', false) . ' /> Это рекламная публикация</label>';
},
['anew', 'yellow', 'profile_article'],
'side',
'low'
);
// Метабокс для Аэрофлота (только для profile_article)
add_meta_box(
'aeroflot_meta_box',
'Лента Аэрофлота',
function($post) {
$no_aeroflot = get_post_meta($post->ID, '_no_aeroflot', true);
wp_nonce_field('aeroflot_nonce', 'aeroflot_nonce_field');
echo '<label><input type="checkbox" name="no_aeroflot" value="1" ' . checked($no_aeroflot, '1', false) . ' /> Не отправлять в Аэрофлот</label>';
},
['profile_article'], // Только для этого типа записей
'side',
'low'
);
});
add_action('save_post', function($post_id) {
// Сохранение рекламного материала
if (isset($_POST['advertisement_nonce_field']) && wp_verify_nonce($_POST['advertisement_nonce_field'], 'advertisement_nonce')) {
if (!defined('DOING_AUTOSAVE') || !DOING_AUTOSAVE) {
if (current_user_can('edit_post', $post_id)) {
update_post_meta($post_id, '_is_advertisement', isset($_POST['is_advertisement']) ? '1' : '0');
}
}
}
// Сохранение настройки Аэрофлот (только для profile_article)
if (isset($_POST['aeroflot_nonce_field']) && wp_verify_nonce($_POST['aeroflot_nonce_field'], 'aeroflot_nonce')) {
if (!defined('DOING_AUTOSAVE') || !DOING_AUTOSAVE) {
if (current_user_can('edit_post', $post_id)) {
update_post_meta($post_id, '_no_aeroflot', isset($_POST['no_aeroflot']) ? '1' : '0');
}
}
}
});
add_action('admin_head', function() {
echo '<style>
/* Стили для рекламного метабокса - теплые тона */
#advertisement_meta_box {
background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
border: 2px solid #ffb300;
border-left: 4px solid #ffb300;
}
#advertisement_meta_box .hndle {
background: #ffb300;
color: white;
border-bottom: 2px solid #ff8f00;
font-weight: 600;
}
#advertisement_meta_box .hndle:before {
content: "📢";
margin-right: 8px;
font-size: 16px;
}
#advertisement_meta_box label {
display: block;
padding: 12px 0;
font-weight: 600;
color: #e65100;
}
/* Стили для Аэрофлота - синие тона как в бренде */
#aeroflot_meta_box {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border: 2px solid #1565c0;
border-left: 4px solid #1565c0;
}
#aeroflot_meta_box .hndle {
background: #1565c0;
color: white;
border-bottom: 2px solid #0d47a1;
font-weight: 600;
}
#aeroflot_meta_box .hndle:before {
content: "✈️";
margin-right: 8px;
font-size: 16px;
}
#aeroflot_meta_box label {
display: block;
padding: 12px 0;
font-weight: 600;
color: #0d47a1;
}
/* Стили для чекбоксов */
#advertisement_meta_box input[type="checkbox"],
#aeroflot_meta_box input[type="checkbox"] {
margin-right: 8px;
transform: scale(1.2);
}
#advertisement_meta_box input[type="checkbox"]:checked {
accent-color: #ffb300;
}
#aeroflot_meta_box input[type="checkbox"]:checked {
accent-color: #1565c0;
}
/* Выравнивание иконок */
#advertisement_meta_box .hndle,
#aeroflot_meta_box .hndle {
display: flex;
align-items: center;
}
</style>';
});

View File

@@ -0,0 +1,46 @@
<?php
// Регистрация метабокса и обработка сохранения
add_action('add_meta_boxes', function() {
add_meta_box(
'advertisement_meta_box',
'Рекламный материал',
function($post) {
$is_advertisement = get_post_meta($post->ID, '_is_advertisement', true);
wp_nonce_field('advertisement_nonce', 'advertisement_nonce_field');
echo '<label><input type="checkbox" name="is_advertisement" value="1" ' . checked($is_advertisement, '1', false) . ' /> Это рекламная публикация</label>';
},
['anew', 'yellow', 'profile_article'],
'side',
'low'
);
});
add_action('save_post', function($post_id) {
if (!isset($_POST['advertisement_nonce_field']) || !wp_verify_nonce($_POST['advertisement_nonce_field'], 'advertisement_nonce')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
update_post_meta($post_id, '_is_advertisement', isset($_POST['is_advertisement']) ? '1' : '0');
});
add_action('admin_head', function() {
echo '<style>
#advertisement_meta_box {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border: 2px solid #2196f3;
border-left: 4px solid #2196f3;
}
#advertisement_meta_box .hndle {
background: #2196f3;
color: white;
border-bottom: 2px solid #1976d2;
}
#advertisement_meta_box label {
display: block;
padding: 12px 0;
font-weight: 600;
color: #1565c0;
}
</style>';
});

74
inc/adfox_on.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
/**
* WP-CLI команды для управления рекламой
*/
if (!defined('ABSPATH') && !defined('WP_CLI')) {
exit;
}
class Ad_Manager_Commands {
/**
* Управление настройкой рекламы
*/
public function __invoke($args, $assoc_args) {
if (empty($args)) {
WP_CLI::error('Укажите действие: status, on, off, toggle');
}
list($action) = $args;
switch ($action) {
case 'status':
$this->show_status();
break;
case 'on':
$this->turn_on();
break;
case 'off':
$this->turn_off();
break;
case 'toggle':
$this->toggle();
break;
default:
WP_CLI::error("Неизвестная команда: {$action}. Используйте: status, on, off, toggle");
}
}
private function show_status() {
$show_ad = get_option('show_ad', 0);
$status = ((int)$show_ad === 1) ? '✅ включена' : '❌ выключена';
WP_CLI::line("Текущий статус рекламы: {$status}");
WP_CLI::line("Значение в базе: {$show_ad}");
}
private function turn_on() {
update_option('show_ad', 1);
WP_CLI::success('✅ Реклама включена');
$this->show_status();
}
private function turn_off() {
update_option('show_ad', 0);
WP_CLI::success('❌ Реклама выключена');
$this->show_status();
}
private function toggle() {
$current = get_option('show_ad', 0);
$new_value = ((int)$current === 1) ? 0 : 1;
$action = ($new_value === 1) ? '✅ включена' : '❌ выключена';
update_option('show_ad', $new_value);
WP_CLI::success("Реклама {$action}");
$this->show_status();
}
}
// Регистрируем команду
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('ad', 'Ad_Manager_Commands');
}

38
inc/admin/auto_check.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
function add_auto_check_script_to_post_edit() {
$screen = get_current_screen();
if ($screen && $screen->base === 'post') {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Проверяем, что это новый пост
if (window.location.href.indexOf('post-new.php') > -1) {
// Ждем загрузки DOM
setTimeout(function() {
// Устанавливаем атрибут checked="checked" для нужных чекбоксов
const checkboxes = [
'input[name="yzrssenabled_meta_value"][type="checkbox"]',
'input[name="ynrss_exclude"][type="checkbox"]',
'input[name="_send_telegram"][type="checkbox"]'
//'input[name="_hide_on_mainpage"][type="checkbox"]',
// Добавьте другие чекбоксы здесь:
// 'input[name="_another_field"][type="checkbox"]',
];
checkboxes.forEach(function(selector) {
const $checkbox = $(selector);
if ($checkbox.length) {
$checkbox.attr('checked', 'checked');
$checkbox.prop('checked', true);
//console.log('Установлен checked для:', selector);
}
});
}, 100);
}
});
</script>
<?php
}
}
add_action('admin_footer', 'add_auto_check_script_to_post_edit');

View File

@@ -0,0 +1,270 @@
<?php
// Убираем стандартный фильтр авторов для profile_article
add_action('admin_head-edit.php', 'cap_hide_default_author_filter');
function cap_hide_default_author_filter() {
global $typenow;
if ($typenow === 'profile_article') {
echo '<style>
select[name="author"] { display: none !important; }
/* Фиксируем расположение фильтров в одну строку */
.tablenav .actions {
display: flex !important;
align-items: center !important;
flex-wrap: nowrap !important;
gap: 5px !important;
}
.tablenav .actions > * {
flex-shrink: 0 !important;
margin: 0 !important;
}
#author-filter-wrapper {
display: inline-flex !important;
align-items: center !important;
gap: 3px !important;
flex-shrink: 0 !important;
}
#author_name_filter {
width: 140px !important;
}
.select2-container {
flex-shrink: 0 !important;
}
#cap-filter-submit {
flex-shrink: 0 !important;
white-space: nowrap !important;
}
</style>';
}
}
// Добавляем наш фильтр по Co-Authors Plus
add_action('restrict_manage_posts', 'cap_filter_by_coauthor');
function cap_filter_by_coauthor($post_type) {
if ($post_type !== 'profile_article') {
return;
}
global $wpdb;
$authors = $wpdb->get_results("
SELECT DISTINCT t.term_id, t.name, t.slug
FROM {$wpdb->terms} t
INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
INNER JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID
WHERE tt.taxonomy = 'author'
AND p.post_type = 'profile_article'
AND p.post_status = 'publish'
ORDER BY t.name ASC
");
if (!empty($authors)) {
$current = isset($_GET['author_name']) ? $_GET['author_name'] : '';
// Обертка для фильтра и кнопки
echo '<div id="author-filter-wrapper">';
// Добавляем скрытое поле для сохранения post_type
echo '<input type="hidden" name="post_type" value="profile_article">';
echo '<select name="author_name" id="author_name_filter">';
echo '<option value="">Все авторы</option>';
foreach ($authors as $author) {
$coauthor = get_user_by('slug', $author->slug);
if ($coauthor) {
$display_name = $coauthor->display_name;
} else {
global $coauthors_plus;
if ($coauthors_plus) {
$guest = $coauthors_plus->get_coauthor_by('user_nicename', $author->slug);
$display_name = $guest && !empty($guest->display_name) ? $guest->display_name : $author->name;
} else {
$display_name = $author->name;
}
}
printf(
'<option value="%s" %s>%s</option>',
esc_attr($author->slug),
selected($current, $author->slug, false),
esc_html($display_name)
);
}
echo '</select>';
// Кнопка внутри обертки
submit_button('Фильтр', '', 'filter_action', false, ['id' => 'cap-filter-submit']);
echo '</div>';
}
}
// Модифицируем запрос для фильтрации
add_filter('pre_get_posts', 'cap_filter_posts_by_coauthor', 999);
function cap_filter_posts_by_coauthor($query) {
// Проверяем, что это админка и главный запрос
if (!is_admin() || !$query->is_main_query()) {
return;
}
global $pagenow;
if ($pagenow !== 'edit.php') {
return;
}
// Получаем тип поста (может быть строкой или массивом)
$current_post_type = $query->get('post_type');
// Если массив - берем первый элемент
if (is_array($current_post_type)) {
$current_post_type = reset($current_post_type);
}
// Если пустой - берем из GET
if (empty($current_post_type) && isset($_GET['post_type'])) {
$current_post_type = is_array($_GET['post_type']) ? reset($_GET['post_type']) : $_GET['post_type'];
}
if ($current_post_type !== 'profile_article') {
return;
}
// Убираем пустой параметр s (поиск)
if (isset($_GET['s']) && (empty($_GET['s']) || $_GET['s'] === '')) {
$query->set('s', '');
unset($_GET['s']);
}
// Если выбран автор в фильтре
if (isset($_GET['author_name']) && !empty($_GET['author_name'])) {
$tax_query = $query->get('tax_query');
if (!is_array($tax_query)) {
$tax_query = [];
}
$tax_query[] = [
'taxonomy' => 'author',
'field' => 'slug',
'terms' => sanitize_text_field($_GET['author_name'])
];
$query->set('tax_query', $tax_query);
}
}
// Подключаем Select2 и стили
add_action('admin_enqueue_scripts', 'cap_enqueue_select2');
function cap_enqueue_select2($hook) {
if ($hook !== 'edit.php') {
return;
}
global $typenow;
if ($typenow !== 'profile_article') {
return;
}
wp_enqueue_style('select2', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css');
wp_enqueue_script('select2', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js', ['jquery'], null, true);
wp_add_inline_script('select2', '
jQuery(document).ready(function($) {
// Удаляем дублированные обертки
$("#author-filter-wrapper").not(":first").remove();
var $select = $("#author_name_filter").first();
$select.select2({
placeholder: "Все авторы",
allowClear: true,
width: "140px"
});
// Получаем текущий URL для сброса
var baseUrl = "' . admin_url('edit.php?post_type=profile_article') . '";
// Обработка очистки - сохраняем post_type
$select.on("select2:clear", function(e) {
window.location.href = baseUrl;
});
// Функция для удаления пустых полей формы
function removeEmptyFields($form) {
$form.find("input[type=text], input[type=search]").each(function() {
if ($(this).val() === "" || $(this).val() === null) {
$(this).remove();
}
});
}
// Обработка выбора
$select.on("select2:select", function(e) {
var $form = $(this).closest("form");
removeEmptyFields($form);
$form.submit();
});
// Enter в поиске
$(document).on("keypress", ".select2-search__field", function(e) {
if (e.which === 13) {
e.preventDefault();
var $form = $select.closest("form");
removeEmptyFields($form);
$form.submit();
}
});
// Обработка клика по кнопке Фильтр
$("#cap-filter-submit").on("click", function(e) {
var $form = $(this).closest("form");
removeEmptyFields($form);
});
// Глобальная обработка отправки формы фильтров
$("#posts-filter").on("submit", function(e) {
removeEmptyFields($(this));
});
});
');
wp_add_inline_style('select2', '
.select2-container--default .select2-selection--single {
height: 32px;
border-color: #8c8f94;
border-radius: 3px;
background: #fff;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
line-height: 30px;
color: #50575e;
padding-left: 8px;
font-size: 13px;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 30px;
}
.select2-container--default.select2-container--focus .select2-selection--single {
border-color: #2271b1;
box-shadow: 0 0 0 1px #2271b1;
}
.select2-dropdown {
border-color: #8c8f94;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #2271b1;
}
.select2-container--default .select2-search--dropdown .select2-search__field {
border-color: #8c8f94;
font-size: 13px;
}
');
}

807
inc/admin/lock-terms.php Normal file
View File

@@ -0,0 +1,807 @@
<?php
add_action('admin_enqueue_scripts', function($hook) {
if ($hook !== 'post.php' && $hook !== 'post-new.php') return;
wp_enqueue_script('select2');
wp_enqueue_style('select2');
});
add_action('admin_footer', function () {
global $post;
// Массив с настройками для каждой таксономии
$taxonomies_settings = [
'banned' => [
'background' => '#9e5a63', // фон для banned
'color' => '#ffffff', // белый цвет для рамки
'placeholder_color' => '#ffffff', // цвет placeholder для banned
'allow_new_tags' => false // запрещаем новые теги для banned
],
'keys' => [
'background' => '#e8f2ff', // синий фон для keys
'color' => '#2271b1', // синий цвет для рамки
'placeholder_color' => '#2271b1', // цвет placeholder для keys
'allow_new_tags' => true // разрешаем новые теги для keys
],
'post_tag' => [
'background' => '#31708e', // светлый фон для post_tag
'color' => '#ffffff', // серый цвет для рамки
'placeholder_color' => '#ffffff', // цвет placeholder для post_tag
'allow_new_tags' => false // запрещаем новые теги для post_tag
]
];
?>
<script>
jQuery(function ($) {
const taxonomiesSettings = <?php echo json_encode($taxonomies_settings); ?>;
Object.keys(taxonomiesSettings).forEach(tax => {
const settings = taxonomiesSettings[tax];
// Для post_tag используется другой ID контейнера
let box;
if (tax === 'post_tag') {
box = $('#tagsdiv-post_tag');
// Если не нашли по стандартному ID, пробуем найти по классу
if (!box.length) {
box = $('.tagsdiv-post_tag');
}
} else {
box = $('#tagsdiv-' + tax);
}
if (!box.length) {
console.log('Container not found for taxonomy:', tax);
return;
}
const originalInput = box.find('.taghint').parent().find('input');
const tagList = box.find('.tagchecklist');
const tagCloudLink = box.find('.tagcloud-link');
// Получаем классы с оригинального input
const originalClasses = originalInput.attr('class') || '';
// Скрываем стандартные элементы
originalInput.hide();
box.find('.ajaxtag').hide();
tagList.hide();
// Создаем новый элемент select с классами оригинала
const select = $('<select multiple="multiple" class="' + originalClasses + '"></select>');
select.insertBefore(tagList);
// Создаем скрытое поле для хранения ID терминов
const hiddenIdsInput = $('<input type="hidden" name="' + tax + '_ids" />');
hiddenIdsInput.insertAfter(select);
// Переменная для отслеживания последнего введенного текста
let lastInputText = '';
// Функция для обновления скрытых inputs
const updateHiddenInputs = function() {
let selectedNames = [];
let selectedIds = [];
// Получаем все выбранные значения из Select2
const selectedData = select.select2('data');
selectedData.forEach(item => {
const value = item.id;
const text = item.text;
selectedNames.push(text);
// Для новых тегов используем название вместо ID
if (value.toString().startsWith('NEW_')) {
// Сохраняем название тега (без префикса NEW_)
selectedIds.push(text);
} else {
// Существующий ID термина
selectedIds.push(value);
}
});
// Для совместимости со стандартным input WordPress
originalInput.val(selectedNames.join(','));
// Для нашего обработчика сохранения
hiddenIdsInput.val(selectedIds.join(','));
console.log('Updated hidden inputs for', tax, ':', {
names: selectedNames.join(','),
ids: selectedIds.join(',')
});
};
// Функция для принудительного добавления тега
const forceAddTag = function(tagText) {
if (!tagText || tagText.trim() === '') return false;
const trimmedText = tagText.trim();
const newTagId = 'NEW_' + trimmedText;
// Проверяем, нет ли уже такого тега в выбранных
const existingOptions = select.find('option');
let alreadyExists = false;
existingOptions.each(function() {
if ($(this).val() === newTagId || $(this).text().toLowerCase() === trimmedText.toLowerCase()) {
alreadyExists = true;
return false;
}
});
if (!alreadyExists && settings.allow_new_tags) {
const option = new Option(trimmedText, newTagId, true, true);
select.append(option);
select.trigger('change');
console.log('Force added tag:', trimmedText);
return true;
}
return false;
};
// Настройки Select2 в зависимости от таксономии
const select2Options = {
ajax: {
url: ajaxurl,
dataType: 'json',
delay: 250,
data: (params) => ({
action: 'get_existing_terms',
taxonomy: tax,
search: params.term || ''
}),
processResults: function (data) {
return {
results: data
};
}
},
placeholder: '',
allowClear: true,
width: '100%',
minimumInputLength: 1, // Минимальная длина для поиска
matcher: function(params, data) {
// Если нет поискового запроса, показываем все результаты
if ($.trim(params.term) === '') {
return data;
}
// Проверяем, содержит ли текст данные поискового запроса
if (data.text.toLowerCase().indexOf(params.term.toLowerCase()) > -1) {
return data;
}
// Если не нашли совпадение, скрываем результат
return null;
}
};
// Для banned и post_tag - только выбор из существующих
// Для keys - разрешаем создание новых тегов
if (settings.allow_new_tags) {
select2Options.tags = true;
select2Options.createTag = function (params) {
var term = $.trim(params.term);
if (term === '') {
return null;
}
// Проверяем, есть ли уже такой термин в результатах поиска
// Если есть - не создаем новый тег
return {
id: 'NEW_' + term,
text: term,
newTag: true
};
};
// Добавляем валидацию для новых тегов
select2Options.insertTag = function (data, tag) {
// Проверяем, не существует ли уже такого тега в данных
const existingTag = data.find(item =>
item.text.toLowerCase() === tag.text.toLowerCase()
);
if (existingTag) {
// Если тег уже существует, не добавляем новый
return data;
}
// Добавляем новый тег в начало списка
data.unshift(tag);
return data;
};
} else {
select2Options.tags = false;
// Для таксономий без создания новых тегов запрещаем любые пользовательские вводы
select2Options.createTag = function (params) {
return null;
};
}
// Инициализация select2
select.select2(select2Options);
// Обработчики для гарантированного добавления тегов
if (settings.allow_new_tags) {
// Обработчик ввода - сохраняем последний введенный текст
select.on('input', '.select2-search__field', function(e) {
lastInputText = $(this).val();
});
// Обработчик keydown на самом select элементе
select.on('keydown', function(e) {
if (e.which === 13) { // Enter
const select2Container = select.next('.select2-container');
const searchInput = select2Container.find('.select2-search__field');
const currentText = searchInput.val().trim();
if (currentText !== '') {
e.preventDefault();
e.stopImmediatePropagation();
// Даем немного времени Select2 на обработку
setTimeout(() => {
if (forceAddTag(currentText)) {
searchInput.val('');
updateHiddenInputs();
}
}, 50);
}
}
});
// Дополнительный обработчик на document для перехвата всех Enter
$(document).on('keydown', function(e) {
if (e.which === 13) {
const activeElement = document.activeElement;
if ($(activeElement).hasClass('select2-search__field')) {
const select2Container = $(activeElement).closest('.select2-container');
const relatedSelect = $('select').filter(function() {
return $(this).next('.select2-container').is(select2Container);
});
if (relatedSelect.length && settings.allow_new_tags) {
const currentText = $(activeElement).val().trim();
if (currentText !== '') {
e.preventDefault();
e.stopImmediatePropagation();
setTimeout(() => {
if (forceAddTag(currentText)) {
$(activeElement).val('');
updateHiddenInputs();
}
}, 50);
}
}
}
}
});
// Обработчик для случаев, когда Select2 не успевает обработать тег
select.on('select2:closing', function(e) {
const select2Container = select.next('.select2-container');
const searchInput = select2Container.find('.select2-search__field');
const currentText = searchInput.val().trim();
if (currentText !== '') {
setTimeout(() => {
if (forceAddTag(currentText)) {
searchInput.val('');
updateHiddenInputs();
}
}, 100);
}
});
}
// Обработчики событий Select2
select.on('change', function () {
console.log('Select2 changed for', tax, 'selected values:', $(this).val());
updateHiddenInputs();
});
select.on('select2:select', function (e) {
console.log('Select2 select for', tax, 'selected item:', e.params.data);
updateHiddenInputs();
});
select.on('select2:unselect', function (e) {
console.log('Select2 unselect for', tax, 'unselected item:', e.params.data);
updateHiddenInputs();
});
// Загружаем выбранные термы в select2
$.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'get_post_terms',
post_id: <?php echo (int)$post->ID; ?>,
taxonomy: tax
}
}).done(function (terms) {
terms.forEach(t => {
let option = new Option(t.text, t.id, true, true);
select.append(option);
});
select.trigger('change');
// Обновляем скрытые inputs
updateHiddenInputs();
});
// Переменная для хранения текущего облака
let currentTagCloud = null;
// Обработчик клика по ссылке "Выбрать из часто используемых меток"
tagCloudLink.off('click').on('click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
// Если облако уже открыто - закрываем его
if (currentTagCloud && currentTagCloud.is(':visible')) {
currentTagCloud.remove();
currentTagCloud = null;
return;
}
// Удаляем предыдущее облако если есть
if (currentTagCloud) {
currentTagCloud.remove();
}
// Загружаем популярные теги через AJAX
$.ajax({
url: ajaxurl,
type: 'GET',
dataType: 'json',
data: {
action: 'get_popular_terms',
taxonomy: tax
}
}).done(function(terms) {
// Создаем свое облако тегов в стандартном стиле WordPress
if (terms.length > 0) {
currentTagCloud = $('<div class="the-tagcloud" style="margin-top: 10px;"></div>');
// Находим минимальное и максимальное количество использования
let minCount = Math.min(...terms.map(term => term.count));
let maxCount = Math.max(...terms.map(term => term.count));
terms.forEach(term => {
const tagLink = $('<a href="#" class="tag-cloud-link"></a>');
tagLink.text(term.name);
tagLink.attr('data-id', term.id);
// Рассчитываем размер шрифта на основе популярности
const fontSize = calculateFontSize(term.count, minCount, maxCount);
tagLink.css('font-size', fontSize + 'px');
currentTagCloud.append(tagLink);
currentTagCloud.append(' ');
});
box.append(currentTagCloud);
// Обработчик клика по тегам в нашем облаке
currentTagCloud.on('click', 'a', function(e) {
e.preventDefault();
const tagName = $(this).text();
const tagId = $(this).data('id');
// Добавляем тег в Select2
if (!select.find('option[value="' + tagId + '"]').length) {
let option = new Option(tagName, tagId, true, true);
select.append(option);
select.trigger('change');
updateHiddenInputs();
}
// Закрываем облако после выбора
currentTagCloud.remove();
currentTagCloud = null;
});
}
});
});
// Функция для расчета размера шрифта на основе популярности
function calculateFontSize(count, minCount, maxCount) {
// Минимальный и максимальный размер шрифта в px
const minSize = 11;
const maxSize = 18;
// Если все теги имеют одинаковую популярность
if (minCount === maxCount) {
return (minSize + maxSize) / 2;
}
// Рассчитываем размер пропорционально популярности
const scale = (count - minCount) / (maxCount - minCount);
return minSize + (scale * (maxSize - minSize));
}
// Закрываем облако при клике вне его
$(document).on('click', function(e) {
if (currentTagCloud && !$(e.target).closest('.the-tagcloud').length && !$(e.target).closest('.tagcloud-link').length) {
currentTagCloud.remove();
currentTagCloud = null;
}
});
});
$('.tagcloud-link').filter(function() {
return $(this).text().trim() === 'Выбрать из часто используемых меток';
}).text('Выбрать из часто используемых');
});
</script>
<!-- Стили с фоном select, цветными рамками и placeholder -->
<style>
.select2-container {
margin-bottom: 8px;
width: 100% !important;
}
.select2-selection {
min-height: 30px;
}
/* Стили для banned */
#tagsdiv-banned .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
}
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
color: #ffffff !important;
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-banned .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
/* Placeholder для banned */
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['banned']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для banned */
#tagsdiv-banned .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['banned']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
color: #ffffff !important;
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
/* Стили для keys */
#tagsdiv-keys .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
color: #2271b1 !important;
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
/* Placeholder для keys */
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['keys']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для keys */
#tagsdiv-keys .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['keys']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
color: #2271b1 !important;
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
/* Стили для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
} !impor
#tagsdiv-post_tag .select2-container--default.select2-container--focus .select2-selection--multiple,
.tagsdiv-post_tag .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
/* Placeholder для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__placeholder,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['post_tag']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-search--inline .select2-search__field,
.tagsdiv-post_tag .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['post_tag']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
/* Общие стили для кнопок удаления */
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: inherit !important;
opacity: 0.7;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
opacity: 1;
color: inherit !important;
}
/* Стили для текста в поле ввода (для всех таксономий) */
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent !important;
margin-top: 0 !important;
padding: 0 !important;
}
/* Стили для облака тегов в стандартном стиле WordPress */
.the-tagcloud {
background: #fff;
border: 1px solid #ccd0d4;
padding: 10px;
border-radius: 4px;
line-height: 2;
}
.the-tagcloud a {
color: #2271b1 !important;
text-decoration: none;
line-height: 1.4;
display: inline-block;
margin: 2px 5px 2px 0;
}
.the-tagcloud a:hover {
color: #135e96 !important;
text-decoration: underline;
}
/* Стили для обводки в выпадающем списке */
#tagsdiv-banned .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['banned']['color']; ?> !important;
outline-offset: 0px !important;
}
#tagsdiv-keys .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['keys']['color']; ?> !important;
outline-offset: 0px !important;
}
#tagsdiv-post_tag .select2-container--default .select2-search__field:focus,
.tagsdiv-post_tag .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
outline-offset: 0px !important;
}
/* Дополнительно для браузеров которые используют box-shadow для фокуса */
#tagsdiv-banned .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-search__field:focus,
.tagsdiv-post_tag .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
.tagsdiv .howto {
display: none !important;
}
</style>
<?php
});
// AJAX обработчик для получения существующих терминов с сортировкой по релевантности
add_action('wp_ajax_get_existing_terms', function () {
$taxonomy = sanitize_key($_GET['taxonomy']);
$search = sanitize_text_field($_GET['search'] ?? '');
$args = [
'taxonomy' => $taxonomy,
'hide_empty' => false,
'number' => 100,
];
// Если есть поисковый запрос, добавляем поиск и сортировку по релевантности
if (!empty($search)) {
$args['search'] = $search;
$args['orderby'] = 'name';
$args['order'] = 'ASC';
}
$terms = get_terms($args);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'text' => $term->name
];
}
// Сортируем по релевантности, если есть поисковый запрос
if (!empty($search)) {
usort($result, function($a, $b) use ($search) {
$a_text = $a['text'];
$b_text = $b['text'];
$search_lower = strtolower($search);
// Приоритет: точное совпадение
if (strtolower($a_text) === $search_lower && strtolower($b_text) !== $search_lower) {
return -1;
}
if (strtolower($b_text) === $search_lower && strtolower($a_text) !== $search_lower) {
return 1;
}
// Приоритет: начинается с поискового запроса
$a_starts = stripos($a_text, $search) === 0;
$b_starts = stripos($b_text, $search) === 0;
if ($a_starts && !$b_starts) {
return -1;
}
if ($b_starts && !$a_starts) {
return 1;
}
// Приоритет: содержит поисковый запрос
$a_contains = stripos($a_text, $search) !== false;
$b_contains = stripos($b_text, $search) !== false;
if ($a_contains && !$b_contains) {
return -1;
}
if ($b_contains && !$a_contains) {
return 1;
}
// Если одинаковый приоритет - сортируем по алфавиту
return strcasecmp($a_text, $b_text);
});
}
wp_send_json($result);
});
// AJAX обработчик для получения терминов поста
add_action('wp_ajax_get_post_terms', function () {
$taxonomy = sanitize_key($_POST['taxonomy']);
$post_id = (int)$_POST['post_id'];
$terms = wp_get_post_terms($post_id, $taxonomy);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'text' => $term->name
];
}
wp_send_json($result);
});
// AJAX обработчик для получения популярных тегов
add_action('wp_ajax_get_popular_terms', function () {
$taxonomy = sanitize_key($_GET['taxonomy']);
// Получаем популярные теги (с наибольшим количеством постов)
$terms = get_terms([
'taxonomy' => $taxonomy,
'orderby' => 'count',
'order' => 'DESC',
'number' => 20,
'hide_empty' => false,
]);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'name' => $term->name,
'count' => $term->count // Добавляем количество использований
];
}
wp_send_json($result);
});
// Обработчик сохранения с исправленной логикой для новых тегов
add_action('save_post', function($post_id) {
// Проверяем права пользователя
if (!current_user_can('edit_post', $post_id)) return;
// Убираем автосохранение
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
// Отладочная информация
error_log('Save post hook called for post: ' . $post_id);
error_log('POST data for keys: ' . ($_POST['keys_ids'] ?? 'not set'));
error_log('POST data for banned: ' . ($_POST['banned_ids'] ?? 'not set'));
error_log('POST data for post_tag: ' . ($_POST['post_tag_ids'] ?? 'not set'));
$taxonomies = ['banned', 'keys', 'post_tag'];
foreach ($taxonomies as $taxonomy) {
if (isset($_POST[$taxonomy . '_ids'])) {
$values = explode(',', $_POST[$taxonomy . '_ids']);
$term_ids = [];
foreach ($values as $value) {
$value = trim($value);
if (empty($value)) continue;
// Если значение НЕ число, это новый тег (только для keys)
if (!is_numeric($value) && $taxonomy === 'keys') {
// Это название нового тега
$term_name = $value;
if (!empty($term_name)) {
// Создаем новый терм
$new_term = wp_insert_term($term_name, $taxonomy);
if (!is_wp_error($new_term)) {
$term_ids[] = $new_term['term_id'];
error_log('Created new term: ' . $term_name . ' with ID: ' . $new_term['term_id']);
} else if ($new_term->get_error_code() === 'term_exists') {
// Если терм уже существует, получаем его ID
$existing_term = get_term_by('name', $term_name, $taxonomy);
if ($existing_term) {
$term_ids[] = $existing_term->term_id;
error_log('Term already exists: ' . $term_name . ' with ID: ' . $existing_term->term_id);
}
} else {
error_log('Error creating term: ' . $term_name . ' - ' . $new_term->get_error_message());
}
}
} else {
// Существующий ID термина
$term_ids[] = intval($value);
}
}
$term_ids = array_filter($term_ids);
if (!empty($term_ids)) {
wp_set_object_terms($post_id, $term_ids, $taxonomy, false);
error_log('Set terms for ' . $taxonomy . ': ' . implode(', ', $term_ids));
} else {
// Если нет терминов, очищаем
wp_set_object_terms($post_id, [], $taxonomy, false);
error_log('Cleared terms for ' . $taxonomy);
}
}
}
});

271
inc/article-rss-feed.php Normal file
View File

@@ -0,0 +1,271 @@
<?php
/**
* Custom RSS Feed for profile_article posts
*/
// Защита от прямого доступа
if (!defined('ABSPATH')) {
exit;
}
class CustomProfileArticleRSS {
public function __construct() {
add_action('init', [$this, 'add_rss_endpoint']);
add_action('template_redirect', [$this, 'generate_rss_feed']);
}
/**
* Добавляем endpoint для RSS ленты
*/
public function add_rss_endpoint() {
add_rewrite_rule(
'^rss-feed/?$',
'index.php?custom_rss_feed=1',
'top'
);
add_rewrite_tag('%custom_rss_feed%', '([^&]+)');
add_rewrite_tag('%rss_date%', '([^&]+)');
}
/**
* Генерируем RSS ленту
*/
public function generate_rss_feed() {
if (get_query_var('custom_rss_feed') || isset($_GET['rss-feed'])) {
$date = isset($_GET['date']) ? sanitize_text_field($_GET['date']) : date('Y-m-d');
// Валидация даты
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
$date = date('Y-m-d');
}
$this->output_rss_feed($date);
exit;
}
}
/**
* Получаем URL иконки сайта
*/
private function get_site_icon_url() {
$site_icon_id = get_option('site_icon');
if ($site_icon_id) {
return wp_get_attachment_image_url($site_icon_id, 'full');
}
// Fallback на логотип или дефолтную иконку
$custom_logo_id = get_theme_mod('custom_logo');
if ($custom_logo_id) {
return wp_get_attachment_image_url($custom_logo_id, 'full');
}
return home_url('/wp-admin/images/wordpress-logo.png');
}
private function get_clean_post_content($post) {
$content = apply_filters('the_content', $post->post_content);
// Удаляем запрещенные для Турбо элементы
$content = preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $content);
$content = preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $content);
$content = preg_replace('/<iframe\b[^>]*>.*?<\/iframe>/is', '', $content);
$content = preg_replace('/<form\b[^>]*>.*?<\/form>/is', '', $content);
$content = preg_replace('/<input[^>]*>/is', '', $content);
$content = preg_replace('/<button[^>]*>.*?<\/button>/is', '', $content);
$content = preg_replace('/<select[^>]*>.*?<\/select>/is', '', $content);
$content = preg_replace('/<textarea[^>]*>.*?<\/textarea>/is', '', $content);
$content = str_replace('<p></p>', '', $content);
// Удаляем определенные классы
$content = preg_replace('/<div[^>]*class="[^"]*(ads|advert|banner|social|share|widget|comments)[^"]*"[^>]*>.*?<\/div>/is', '', $content);
// Разрешаем только турбо-совместимые теги
$allowed_tags = [
'p' => ['class' => [], 'style' => []],
'br' => [],
'strong' => [],
'em' => [],
'b' => [],
'i' => [],
'ul' => [],
'ol' => [],
'li' => [],
'h1' => [],
'h2' => [],
'h3' => [],
'h4' => [],
'h5' => [],
'h6' => [],
'a' => ['href' => [], 'title' => []],
'img' => ['src' => [], 'alt' => [], 'title' => [], 'width' => [], 'height' => []],
'blockquote' => [],
'figure' => [],
'header' => [],
];
$content = wp_kses($content, $allowed_tags);
return $content;
}
/**
* Формируем XML ленту
*/
private function output_rss_feed($date) {
global $wpdb;
// Определяем диапазон дат: 7 дней до указанной даты
$end_date = $date; // указанная дата
$start_date = date('Y-m-d', strtotime($date . ' -6 days')); // 7 дней назад (включая указанную дату)
//такая лента формируется на каждую дату и содержит все статьи за 7 дней до указанной даты
$posts = $wpdb->get_results($wpdb->prepare("
SELECT *
FROM {$wpdb->posts}
WHERE post_type = 'profile_article'
AND post_status = 'publish'
AND DATE(post_date) BETWEEN %s AND %s
AND post_password = ''
AND ID NOT IN (
SELECT post_id
FROM {$wpdb->postmeta}
WHERE meta_key IN ('_no_aeroflot', '_is_advertisement', '_only_link_access', '_hide_on_website', '_hide_on_mainpage')
AND meta_value = '1'
)
ORDER BY post_date DESC
", $start_date, $end_date));
// Конвертируем в объекты WP_Post
$posts = array_map(function($post) {
return new WP_Post($post);
}, $posts);
// Устанавливаем заголовки
header('Content-Type: application/rss+xml; charset=' . get_option('blog_charset'), true);
// Начинаем вывод XML
echo '<?xml version="1.0" encoding="' . get_option('blog_charset') . '"?>';
?>
<rss version="2.0"
xmlns:yandex="http://news.yandex.ru"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:turbo="http://turbo.yandex.ru">
<channel>
<title><?php echo esc_xml(get_bloginfo('name')); ?></title>
<link><?php echo esc_url(home_url()); ?></link>
<description><?php echo esc_xml(get_bloginfo('description')); ?></description>
<language><?php echo esc_xml(get_bloginfo('language')); ?></language>
<lastBuildDate><?php echo esc_xml(gmdate(DATE_RSS)); ?></lastBuildDate>
<pubDate><?php echo esc_xml(gmdate(DATE_RSS)); ?></pubDate>
<guid>https://profile.ru</guid>
<generator>Информационное агенство Деловой журнал Профиль</generator>
<image>
<url><?php echo esc_url($this->get_site_icon_url()); ?></url>
<title><?php echo esc_xml(get_bloginfo('name')); ?></title>
<link><?php echo esc_url(home_url()); ?></link>
</image>
<?php if (empty($posts)): ?>
<item>
<title>No posts for period <?php echo esc_xml($start_date); ?> to <?php echo esc_xml($end_date); ?></title>
<link><?php echo esc_url(home_url()); ?></link>
<description>No posts found for this period</description>
<pubDate><?php echo esc_xml(gmdate(DATE_RSS)); ?></pubDate>
<guid isPermaLink="false">no-posts-<?php echo esc_xml($start_date . '-to-' . $end_date); ?></guid>
</item>
<?php else: ?>
<?php foreach ($posts as $post):
$post = new WP_Post($post);
$post_url = get_permalink($post->ID);
$post_date = gmdate(DATE_RSS, strtotime($post->post_date_gmt));
$excerpt = get_the_excerpt($post->ID);
$content = $this->get_clean_post_content($post);
$thumbnail = get_the_post_thumbnail_url($post->ID, 'large');
// Получаем авторов из плагина Co-Authors
$authors = get_coauthors($post->ID);
?>
<item>
<title><?php echo esc_xml(get_the_title($post->ID)); ?></title>
<link><?php echo esc_url($post_url); ?></link>
<description><?php echo esc_xml($excerpt); ?></description>
<pubDate><?php echo esc_xml($post_date); ?></pubDate>
<?php if (!empty($authors)): ?>
<?php
$author_names = array();
foreach ($authors as $author) {
$author_names[] = $author->display_name;
}
?>
<author><?php echo esc_xml(implode(', ', $author_names)); ?></author>
<?php else: ?>
<?php $default_author = get_the_author_meta('display_name', $post->post_author); ?>
<author><?php echo esc_xml($default_author); ?></author>
<?php endif; ?>
<guid><?php echo esc_url($post_url); ?></guid>
<?php if ($thumbnail): ?>
<imageAnnounce>
<url><?php echo esc_url($thumbnail); ?></url>
<title><?php echo esc_xml(get_the_title($post->ID)); ?></title>
<description><?php echo esc_xml($excerpt); ?></description>
</imageAnnounce>
<?php endif; ?>
<?php if ($thumbnail): ?>
<image>
<url><?php echo esc_url($thumbnail); ?></url>
<title><?php echo esc_xml(get_the_title($post->ID)); ?></title>
<description><?php echo esc_xml($excerpt); ?></description>
<link><?php echo esc_url($post_url); ?></link>
</image>
<?php endif; ?>
<?php
$categories = get_the_category($post->ID);
foreach ($categories as $category) {
echo '<category>' . esc_xml($category->name) . '</category>';
}
?>
<content:encoded>
<![CDATA[<?php echo $content; ?>]]>
</content:encoded>
</item>
<?php endforeach; ?>
<?php endif; ?>
</channel>
</rss>
<?php
}
/**
* Получаем контент поста
*/
private function get_post_content($post) {
$content = apply_filters('the_content', $post->post_content);
$content = str_replace(']]>', ']]&gt;', $content);
return $content;
}
/**
* Активация - сбрасываем rewrite rules
*/
public static function activate() {
flush_rewrite_rules();
}
/**
* Деактивация - чистим rewrite rules
*/
public static function deactivate() {
flush_rewrite_rules();
}
}
// Инициализация
new CustomProfileArticleRSS();
// Хуки активации/деактивации
register_activation_hook(__FILE__, ['CustomProfileArticleRSS', 'activate']);
register_deactivation_hook(__FILE__, ['CustomProfileArticleRSS', 'deactivate']);

View File

@@ -0,0 +1,195 @@
<?php
/**
* CLI команда для генерации кеша соавторов
*
* Запуск: wp coauthors generate-cache
* Или: wp eval "generate_coauthors_cache();"
*/
if (defined('WP_CLI') && WP_CLI) {
/**
* Генерирует кеш всех соавторов
*/
function generate_coauthors_cache_cli() {
WP_CLI::line('Начинаю генерацию кеша соавторов...');
$result = generate_coauthors_cache();
if ($result['success']) {
WP_CLI::success($result['message']);
WP_CLI::line("Всего соавторов: {$result['total']}");
WP_CLI::line("Файл сохранен: {$result['file']}");
} else {
WP_CLI::error($result['message']);
}
}
WP_CLI::add_command('coauthors generate-cache', 'generate_coauthors_cache_cli');
}
/**
* Генерирует кеш всех соавторов и сохраняет в JSON файл
*
* @return array Результат операции
*/
function generate_coauthors_cache() {
if (!function_exists('get_coauthors')) {
return array(
'success' => false,
'message' => 'Плагин CoAuthors Plus не активен'
);
}
global $wpdb, $coauthors_plus;
$cache_file = get_coauthors_cache_file_path();
$cache_data = array(
'generated' => current_time('mysql'),
'total' => 0,
'authors' => array(),
'by_display_name' => array(), // Индекс по display_name
'by_last_name' => array(), // Индекс по фамилии
'by_login' => array(), // Индекс по логину
'by_nicename' => array() // Индекс по nicename
);
// Получаем всех соавторов через термины таксономии 'author'
$terms = get_terms(array(
'taxonomy' => 'author',
'hide_empty' => false,
'number' => 0 // Получаем все
));
$authors = array();
$processed_ids = array();
WP_CLI::line('Получение соавторов из терминов...');
$progress = \WP_CLI\Utils\make_progress_bar('Обработка соавторов', count($terms));
foreach ($terms as $term) {
$coauthor = $coauthors_plus->get_coauthor_by('user_nicename', $term->slug);
if ($coauthor && !in_array($coauthor->ID, $processed_ids)) {
$authors[] = $coauthor;
$processed_ids[] = $coauthor->ID;
}
$progress->tick();
}
$progress->finish();
// Дополнительно: получаем авторов из постов за последние 2 года (на случай если в терминах не все)
WP_CLI::line('Проверка авторов в постах...');
$two_years_ago = date('Y-m-d H:i:s', strtotime('-2 years'));
$post_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT ID FROM {$wpdb->posts}
WHERE post_type IN ('post', 'profile_article')
AND post_status = 'publish'
AND post_date >= %s
ORDER BY post_date DESC
LIMIT 5000",
$two_years_ago
));
$post_progress = \WP_CLI\Utils\make_progress_bar('Поиск авторов в постах', count($post_ids));
foreach ($post_ids as $pid) {
foreach (get_coauthors($pid) as $ca) {
if (!in_array($ca->ID, $processed_ids)) {
$authors[] = $ca;
$processed_ids[] = $ca->ID;
}
}
$post_progress->tick();
}
$post_progress->finish();
// Формируем структуру данных для кеша
WP_CLI::line('Формирование структуры кеша...');
$total = count($authors);
foreach ($authors as $author) {
$display_name = $author->display_name;
$last_name = extract_last_name($display_name);
$author_data = array(
'ID' => $author->ID,
'display_name' => $display_name,
'last_name' => $last_name,
'user_login' => $author->user_login,
'user_nicename' => $author->user_nicename,
'type' => isset($author->type) ? $author->type : 'wpuser',
'description' => isset($author->description) ? $author->description : '',
'show_only_articles' => get_user_meta($author->ID, 'show_only_articles', true)
);
// Добавляем в основной массив
$cache_data['authors'][] = $author_data;
// Индексируем по display_name (полное имя)
$display_name_key = sanitize_key_for_cache($display_name);
$cache_data['by_display_name'][$display_name_key] = $author_data;
// Индексируем по фамилии
if (!empty($last_name)) {
$last_name_key = sanitize_key_for_cache($last_name);
if (!isset($cache_data['by_last_name'][$last_name_key])) {
$cache_data['by_last_name'][$last_name_key] = array();
}
$cache_data['by_last_name'][$last_name_key][] = $author_data;
}
// Индексируем по логину
if (!empty($author->user_login)) {
$login_key = sanitize_key_for_cache($author->user_login);
$cache_data['by_login'][$login_key] = $author_data;
}
// Индексируем по nicename
if (!empty($author->user_nicename)) {
$nicename_key = sanitize_key_for_cache($author->user_nicename);
$cache_data['by_nicename'][$nicename_key] = $author_data;
}
}
$cache_data['total'] = $total;
// Сохраняем в файл
$written = file_put_contents($cache_file, json_encode($cache_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
if ($written === false) {
return array(
'success' => false,
'message' => "Ошибка при записи файла кеша: {$cache_file}"
);
}
return array(
'success' => true,
'message' => "Кеш соавторов успешно сгенерирован",
'total' => $total,
'file' => $cache_file
);
}
/**
* Извлекает фамилию из полного имени
* Предполагает, что фамилия - последнее слово
*/
function extract_last_name($full_name) {
$parts = explode(' ', trim($full_name));
return end($parts);
}
/**
* Очищает строку для использования в качестве ключа кеша
*/
function sanitize_key_for_cache($string) {
return mb_strtolower(trim(preg_replace('/\s+/', ' ', $string)));
}
/**
* Возвращает путь к файлу кеша
*/
function get_coauthors_cache_file_path() {
$upload_dir = wp_upload_dir();
return $upload_dir['basedir'] . '/coauthors-cache.json';
}

363
inc/graphql.php Normal file
View File

@@ -0,0 +1,363 @@
<?php
// Общая функция для получения соавторов
function get_coauthors_for_graphql($post_id) {
if (!function_exists('get_coauthors')) {
return [];
}
$coauthors = get_coauthors($post_id);
$users = [];
foreach ($coauthors as $coauthor) {
// Пропускаем если это основной автор (он уже в поле author)
// if ($coauthor->ID == $author_id) continue;
if (isset($coauthor->type) && $coauthor->type === 'guest-author') {
// Для гостевых авторов
$users[] = [
'__typename' => 'GuestAuthor',
'id' => $coauthor->user_nicename,
'name' => $coauthor->display_name,
'firstName' => $coauthor->first_name,
'lastName' => $coauthor->last_name,
'description' => $coauthor->description,
'avatar' => get_avatar_url($coauthor->user_email, ['size' => 96]),
'url' => get_author_posts_url($coauthor->ID, $coauthor->user_nicename),
'type' => 'guest-author'
];
} else {
// Для обычных пользователей
$user = get_user_by('id', $coauthor->ID);
if ($user) {
$avatar_url = '';
if (function_exists('get_avatar_url')) {
$avatar_url = get_avatar_url($user->ID, ['size' => 96]);
}
$users[] = [
'__typename' => 'User',
'id' => 'user-' . $user->ID,
'name' => $user->display_name,
'firstName' => $user->first_name,
'lastName' => $user->last_name,
'description' => $user->description,
'avatar' => $avatar_url,
'url' => get_author_posts_url($user->ID),
'databaseId' => $user->ID
];
}
}
}
return $users;
}
add_filter( 'register_post_type_args', function( $args, $post_type ) {
if ( 'profile_article' === $post_type ) {
$args['show_in_graphql'] = true;
$args['graphql_single_name'] = 'ProfileArticle';
$args['graphql_plural_name'] = 'ProfileArticles';
}
if ( 'anew' === $post_type ) {
$args['show_in_graphql'] = true;
$args['graphql_single_name'] = 'ANew'; // или 'Anew' - смотрите комментарий ниже
$args['graphql_plural_name'] = 'ANews'; // или 'Anews' - смотрите комментарий ниже
}
return $args;
}, 10, 2 );
//цвет меню
add_action('graphql_register_types', function() {
// Поле цвета для пунктов меню
register_graphql_field('MenuItem', 'menuItemColor', [
'type' => 'String',
'description' => __('Custom color for menu item', 'your-textdomain'),
'resolve' => function($menu_item) {
$color = get_post_meta($menu_item->databaseId, '_menu_item_color', true);
return !empty($color) ? $color : null;
}
]);
// Поле цвета для категорий
register_graphql_field( 'Category', 'color', [
'type' => 'String',
'description' => __( 'Background color class for category badge', 'your-textdomain' ),
'resolve' => function( $term ) {
$color = get_field( 'color', 'category_' . $term->term_id );
return ! empty( $color ) ? $color : 'bg-blue';
}
] );
// Соавторы
$post_types_with_coauthors = ['ProfileArticle', 'ANew']; // Исправлено: ANew
foreach ($post_types_with_coauthors as $post_type) {
register_graphql_field($post_type, 'coauthors', [
'type' => ['list_of' => 'User'],
'description' => sprintf(__('Co-authors of the %s', 'your-textdomain'), $post_type),
'resolve' => function($post_object) use ($post_type) {
$post_id = $post_object->databaseId;
return get_coauthors_for_graphql($post_id);
}
]);
}
});
//вторчиный заг
add_action('graphql_register_types', function() {
// Для ProfileArticle
register_graphql_field('ProfileArticle', 'secondaryTitle', [
'type' => 'String',
'description' => __('Secondary title from Secondary Title plugin', 'your-textdomain'),
'resolve' => function($post_object) {
$post_id = $post_object->databaseId;
// Прямое получение поля, которое использует плагин
$secondary_title = get_post_meta($post_id, 'secondary_post_title', true);
// Если пусто, проверяем через функцию плагина (если она существует)
if (empty($secondary_title) && function_exists('get_secondary_title')) {
$secondary_title = get_secondary_title($post_id);
}
return !empty($secondary_title) ? $secondary_title : null;
}
]);
// Для Anews
register_graphql_field('Anews', 'secondaryTitle', [
'type' => 'String',
'description' => __('Secondary title from Secondary Title plugin', 'your-textdomain'),
'resolve' => function($post_object) {
$post_id = $post_object->databaseId;
$secondary_title = get_post_meta($post_id, 'secondary_post_title', true);
if (empty($secondary_title) && function_exists('get_secondary_title')) {
$secondary_title = get_secondary_title($post_id);
}
return !empty($secondary_title) ? $secondary_title : null;
}
]);
});
/**
* Регистрация colonItem для ACF Checkbox
*/
add_action('graphql_register_types', function() {
// Регистрируем поле как Boolean
register_graphql_field('ProfileArticle', 'colonItem', [
'type' => 'Boolean',
'description' => 'Флаг поста колонки',
'resolve' => function($post) {
$value = get_field('colon_item', $post->ID);
// ACF Checkbox возвращает массив или false
// Если checkbox отмечен, get_field вернет массив: ['true']
// Если не отмечен - false или пустой массив
if (is_array($value) && in_array('true', $value)) {
return true;
}
return false;
}
]);
// Для типа Anew (если нужно)
register_graphql_field('Anew', 'colonItem', [
'type' => 'Boolean',
'description' => 'Флаг поста колонки',
'resolve' => function($post) {
$value = get_field('colon_item', $post->ID);
if (is_array($value) && in_array('true', $value)) {
return true;
}
return false;
}
]);
// Регистрируем where аргумент
register_graphql_field('RootQueryToProfileArticleConnectionWhereArgs', 'colonItemEquals', [
'type' => 'Boolean',
'description' => 'Фильтровать по полю colonItem',
]);
// Для Anew
register_graphql_field('RootQueryToAnewConnectionWhereArgs', 'colonItemEquals', [
'type' => 'Boolean',
'description' => 'Фильтровать по полю colonItem',
]);
});
/**
* Фильтрация для ACF Checkbox
*/
add_filter('graphql_post_object_connection_query_args', function($query_args, $source, $args, $context, $info) {
if (isset($args['where']['colonItemEquals'])) {
$colon_value = $args['where']['colonItemEquals'];
if (!isset($query_args['meta_query'])) {
$query_args['meta_query'] = [];
}
if ($colon_value === true) {
// Для ACF Checkbox используем LIKE с сериализованным значением
$query_args['meta_query'][] = [
'key' => 'colon_item',
'value' => '"true"', // Ищем "true" внутри сериализованного массива
'compare' => 'LIKE'
];
} else {
// Ищем посты без этого значения
$query_args['meta_query'][] = [
'relation' => 'OR',
[
'key' => 'colon_item',
'value' => '"true"',
'compare' => 'NOT LIKE'
],
[
'key' => 'colon_item',
'compare' => 'NOT EXISTS'
]
];
}
}
return $query_args;
}, 10, 5);
/**
* Добавление поддержки фильтрации по mainItem для ProfileArticle
*/
add_action('graphql_register_types', function() {
// Регистрируем ACF поле mainItem в GraphQL
register_graphql_field('ProfileArticle', 'mainItem', [
'type' => 'Boolean',
'description' => 'Флаг главного поста',
'resolve' => function($post) {
$value = get_field('main_item', $post->ID);
// ACF Checkbox возвращает массив или false
// Если checkbox отмечен, get_field вернет массив: ['true']
// Если не отмечен - false или пустой массив
if (is_array($value) && in_array('true', $value)) {
return true;
}
return false;
}
]);
// Регистрируем кастомный where аргумент для ProfileArticle
register_graphql_field('RootQueryToProfileArticleConnectionWhereArgs', 'mainItemEquals', [
'type' => 'Boolean',
'description' => 'Фильтровать статьи профиля по полю mainItem',
]);
});
/**
* Применяем фильтрацию через meta_query для mainItem
*/
add_filter('graphql_post_object_connection_query_args', function($query_args, $source, $args, $context, $info) {
// Проверяем наличие аргумента mainItemEquals
if (isset($args['where']['mainItemEquals'])) {
$main_value = $args['where']['mainItemEquals'];
if (!isset($query_args['meta_query'])) {
$query_args['meta_query'] = [];
}
if ($main_value === true) {
// Для ACF Checkbox используем LIKE с сериализованным значением
$query_args['meta_query'][] = [
'key' => 'main_item',
'value' => '"true"', // Ищем "true" внутри сериализованного массива
'compare' => 'LIKE'
];
} else {
// Ищем посты без этого значения
$query_args['meta_query'][] = [
'relation' => 'OR',
[
'key' => 'main_item',
'value' => '"true"',
'compare' => 'NOT LIKE'
],
[
'key' => 'main_item',
'compare' => 'NOT EXISTS'
]
];
}
}
return $query_args;
}, 10, 5);
// авторы
add_action('graphql_register_types', function() {
// Добавляем аргумент coauthorLogin
register_graphql_field('RootQueryToContentNodeConnectionWhereArgs', 'coauthorLogin', [
'type' => 'String',
'description' => __('Filter by coauthor login (nicename)', 'textdomain'),
]);
});
add_filter('graphql_post_object_connection_query_args', function($query_args, $source, $args, $context, $info) {
// Фильтр по логину
if (isset($args['where']['coauthorLogin']) && !empty($args['where']['coauthorLogin'])) {
$login = sanitize_user($args['where']['coauthorLogin']);
// Находим пользователя по логину
$user = get_user_by('login', $login);
if ($user) {
$query_args['author'] = $user->ID;
}
}
// Фильтр по имени (если нужен)
if (isset($args['where']['coauthorName']) && !empty($args['where']['coauthorName'])) {
$query_args['author_name'] = sanitize_title($args['where']['coauthorName']);
}
return $query_args;
}, 10, 5);

196
inc/journal_issue.php Normal file
View File

@@ -0,0 +1,196 @@
<?php
// Создаем Custom Post Type для номеров изданий
function create_journal_issue_cpt() {
$labels = array(
'name' => 'Номера изданий',
'singular_name' => 'Номер издания',
'menu_name' => 'Номера изданий',
'all_items' => 'Все номера',
'add_new' => 'Добавить новый',
'add_new_item' => 'Добавить новый номер',
'edit_item' => 'Редактировать номер',
'new_item' => 'Новый номер',
'view_item' => 'Просмотреть номер',
'search_items' => 'Поиск номеров',
);
$args = array(
'label' => 'journal_issue',
'labels' => $labels,
'description' => 'Архивы номеров журналов',
'public' => true,
'show_in_menu' => true,
'menu_icon' => 'dashicons-book-alt',
'supports' => array('title', 'thumbnail', 'editor'),
'has_archive' => false,
);
register_post_type('journal_issue', $args);
// Включаем поддержку миниатюр (на всякий случай)
add_post_type_support('journal_issue', 'thumbnail');
}
add_action('init', 'create_journal_issue_cpt');
// Создаем скрытую Таксономию только для связи (без пункта меню)
add_action('init', function() {
$labels = array(
'name' => 'Номера изданий',
'singular_name' => 'Номер издания',
);
$args = array(
'labels' => $labels,
'hierarchical' => true,
'public' => false,
'show_ui' => false, // Полностью скрываем из админки
'show_admin_column' => true,
'show_in_rest' => false,
'capabilities' => array(
'manage_terms' => 'manage_options',
'edit_terms' => 'manage_options',
'delete_terms' => 'manage_options',
'assign_terms' => 'edit_posts',
),
'meta_box_cb' => false,
);
register_taxonomy('article_journal_issue', array('profile_article'), $args);
});
// Заменяем стандартный метабокс на выпадающий список
add_action('admin_menu', function() {
add_meta_box('journal_issue_dropdown', 'Номер издания', 'custom_journal_issue_dropdown', 'profile_article', 'side', 'default');
});
// Выпадающий список для выбора номера
function custom_journal_issue_dropdown($post) {
// Получаем номера изданий из CPT journal_issue
$journal_issues = get_posts(array(
'post_type' => 'journal_issue',
'numberposts' => -1,
'orderby' => 'title',
'order' => 'DESC',
'post_status' => 'publish'
));
// Получаем выбранный термин
$selected_terms = wp_get_object_terms($post->ID, 'article_journal_issue', array('fields' => 'names'));
$selected_name = !empty($selected_terms) ? $selected_terms[0] : '';
echo '<select name="journal_issue_selected" style="width:100%;">';
echo '<option value="">— Не выбран —</option>';
foreach ($journal_issues as $issue) {
$selected = selected($selected_name, $issue->post_title, false);
echo '<option value="' . esc_attr($issue->post_title) . '" ' . $selected . '>' . esc_html($issue->post_title) . '</option>';
}
echo '</select>';
echo '<p class="description" style="margin-top:5px;">Выберите один номер издания</p>';
echo '<p class="description"><small><a href="' . admin_url('edit.php?post_type=journal_issue') . '">Управление номерами изданий</a></small></p>';
}
// Обрабатываем сохранение выбранного номера
add_action('save_post_profile_article', function($post_id) {
// Проверяем автосохранение
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
// Проверяем права пользователя
if (!current_user_can('edit_post', $post_id)) return;
// Обрабатываем выбор из выпадающего списка
if (isset($_POST['journal_issue_selected'])) {
$selected_title = sanitize_text_field($_POST['journal_issue_selected']);
if (empty($selected_title)) {
// Если выбран пустой вариант - удаляем все термины
wp_set_object_terms($post_id, array(), 'article_journal_issue');
} else {
// Создаем или получаем термин на основе выбранного номера
$term = term_exists($selected_title, 'article_journal_issue');
if (!$term) {
$term = wp_insert_term($selected_title, 'article_journal_issue');
}
if (!is_wp_error($term)) {
wp_set_object_terms($post_id, array($term['term_id']), 'article_journal_issue');
}
}
}
});
// Добавляем быстрый выбор в таблице постов (колонка)
add_filter('manage_profile_article_posts_columns', function($columns) {
$columns['journal_issue'] = 'Номер издания';
return $columns;
});
add_action('manage_profile_article_posts_custom_column', function($column, $post_id) {
if ($column === 'journal_issue') {
$terms = get_the_terms($post_id, 'article_journal_issue');
if ($terms && !is_wp_error($terms)) {
foreach ($terms as $term) {
echo '<span style="background:#f0f0f0; padding:2px 6px; border-radius:3px; font-size:12px;">' . $term->name . '</span>';
}
} else {
echo '<span style="color:#ccc;">—</span>';
}
}
}, 10, 2);
// Функция для автоматической синхронизации терминов при создании/изменении номера
function sync_journal_issue_term($post_id) {
// Проверяем, что это номер издания
if (get_post_type($post_id) !== 'journal_issue') return;
// Пропускаем автосохранение
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
$issue = get_post($post_id);
if ($issue && $issue->post_status === 'publish') {
// Создаем или обновляем термин
$term = term_exists($issue->post_title, 'article_journal_issue');
if (!$term) {
wp_insert_term($issue->post_title, 'article_journal_issue');
} else {
// Обновляем название термина, если изменилось название номера
wp_update_term($term['term_id'], 'article_journal_issue', array(
'name' => $issue->post_title
));
}
}
}
// Синхронизируем при сохранении номера издания
add_action('save_post_journal_issue', 'sync_journal_issue_term');
// Удаляем термин при удалении номера издания
function delete_journal_issue_term($post_id) {
if (get_post_type($post_id) !== 'journal_issue') return;
$issue = get_post($post_id);
if ($issue) {
$term = term_exists($issue->post_title, 'article_journal_issue');
if ($term) {
wp_delete_term($term['term_id'], 'article_journal_issue');
}
}
}
add_action('before_delete_post', 'delete_journal_issue_term');
// Инициализация существующих номеров при активации
function init_existing_journal_issues() {
$journal_issues = get_posts(array(
'post_type' => 'journal_issue',
'numberposts' => -1,
'post_status' => 'publish'
));
foreach ($journal_issues as $issue) {
$term = term_exists($issue->post_title, 'article_journal_issue');
if (!$term) {
wp_insert_term($issue->post_title, 'article_journal_issue');
}
}
}
register_activation_hook(__FILE__, 'init_existing_journal_issues');

20
inc/meta_keywords.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
add_action('wp_head', function() {
// Для записей и страниц
if (is_single() || is_page()) {
$post_id = get_queried_object_id();
// Получаем термины из таксономии 'keys'
$terms = get_the_terms($post_id, 'keys');
if ($terms && !is_wp_error($terms)) {
$keywords = array();
foreach ($terms as $term) {
$keywords[] = $term->name;
}
echo '<meta name="keywords" content="' . esc_attr(implode(', ', $keywords)) . '">' . "\n";
}
}
});

0
inc/opensearch.php Normal file
View File

43
inc/popular-json.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
$json_path = CACHED_TEMPLATE.'json';
$ids = array_map(
function ($item) { return $item->id; },
json_decode(get_option("ppp_options") ?: '[]')
);
$posts = get_posts([
"post_type" => ["anew", "yellow"],
"post_status" => "publish",
"posts_per_page" => 20,
"ignore_sticky_posts" => true,
"post__in" => $ids,
"orderby" => "post__in",
"meta_query" => [[
"key" => "_thumbnail_id",
"compare" => "EXISTS"
]]
]);
$data = array_map(function($post) {
$thumbnail = get_the_post_thumbnail_url($post->ID, 'thumb-264');
return [
'id' => $post->ID,
'title' => get_the_title($post->ID),
'link' => get_permalink($post->ID),
'thumbnail' => $thumbnail ?: wp_get_attachment_image_url(1357368, 'thumb-264'),
'alt' => get_the_title($post->ID)
];
}, $posts);
// Создаем директорию если не существует
if (!file_exists($json_path)) {
wp_mkdir_p($json_path);
}
$json_file = $json_path.'popular';
file_put_contents($json_file, json_encode($data, JSON_UNESCAPED_UNICODE));

222
inc/realtime-debug.php Normal file
View File

@@ -0,0 +1,222 @@
<?php
/**
* Real-time Execution Logger for WordPress
* Логирует выполнение страницы в реальном времени
*/
class RealtimePageLogger {
private static $instance;
private $log_file;
private $request_id;
private $start_time;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->log_file = WP_CONTENT_DIR . '/realtime-debug.log';
$this->request_id = uniqid('req_', true);
$this->start_time = microtime(true);
// Очищаем файл при инициализации
$this->clean_log_file();
$this->init();
}
/**
* Очистка лог-файла с проверками
*/
private function clean_log_file() {
// Если файл не существует, создаем его
if (!file_exists($this->log_file)) {
if (touch($this->log_file)) {
chmod($this->log_file, 0644);
$this->log_message("Создан новый лог-файл", 'SYSTEM');
} else {
error_log('Не удалось создать файл realtime-debug.log');
return;
}
}
// Проверяем доступность для записи
if (!is_writable($this->log_file)) {
error_log('Файл realtime-debug.log недоступен для записи');
return;
}
// Очищаем файл
if (file_put_contents($this->log_file, '') === false) {
error_log('Не удалось очистить файл realtime-debug.log');
} else {
$this->log_message("Лог-файл очищен", 'SYSTEM');
}
}
private function init() {
// Начинаем логирование как можно раньше
add_action('plugins_loaded', [$this, 'log_plugins_loaded'], 1);
add_action('setup_theme', [$this, 'log_setup_theme'], 1);
add_action('after_setup_theme', [$this, 'log_after_setup_theme'], 1);
add_action('init', [$this, 'log_init'], 1);
// Логируем все основные этапы
add_action('wp_loaded', [$this, 'log_wp_loaded']);
add_action('parse_query', [$this, 'log_parse_query']);
add_action('pre_get_posts', [$this, 'log_pre_get_posts']);
add_action('wp', [$this, 'log_wp']);
// Логируем SQL запросы
add_filter('query', [$this, 'log_sql_query']);
// Логируем шаблоны
add_filter('template_include', [$this, 'log_template_include'], 9999);
// Логируем завершение
add_action('shutdown', [$this, 'log_shutdown'], 9999);
// Логируем хуки в реальном времени
$this->setup_hook_logging();
}
public function setup_hook_logging() {
$important_hooks = [
'template_redirect',
'get_header',
'wp_head',
'the_post',
'loop_start',
'loop_end',
'get_sidebar',
'get_footer',
'wp_footer',
'admin_bar_menu',
'wp_enqueue_scripts',
'wp_print_styles',
'wp_print_scripts'
];
foreach ($important_hooks as $hook) {
add_action($hook, function() use ($hook) {
$this->log_hook($hook);
}, 1);
}
}
private function log_message($message, $level = 'INFO') {
$timestamp = microtime(true);
$elapsed = round(($timestamp - $this->start_time) * 1000, 2);
$memory = memory_get_usage(true);
$log = sprintf(
"[%s] %s | %6.2fms | %8s | %s | %s\n",
date('H:i:s'),
$this->request_id,
$elapsed,
size_format($memory, 0),
$level,
$message
);
file_put_contents($this->log_file, $log, FILE_APPEND);
}
public function log_plugins_loaded() {
$this->log_message('PLUGINS_LOADED - Плагины загружены');
}
public function log_setup_theme() {
$this->log_message('SETUP_THEME - Начинается загрузка темы');
}
public function log_after_setup_theme() {
$this->log_message('AFTER_SETUP_THEME - Тема загружена');
}
public function log_init() {
$this->log_message('INIT - WordPress инициализирован');
}
public function log_wp_loaded() {
$this->log_message('WP_LOADED - WordPress полностью загружен');
}
public function log_parse_query() {
global $wp_query;
$this->log_message(sprintf('PARSE_QUERY - Запрос: %s', $wp_query->query_vars['pagename'] ?? 'main'));
}
public function log_pre_get_posts($query) {
if ($query->is_main_query()) {
$this->log_message('PRE_GET_POSTS - Основной запрос к posts');
}
}
public function log_wp() {
$this->log_message('WP - Запрос обработан, готовим данные для шаблона');
}
public function log_sql_query($query) {
$trimmed = trim($query);
if (!empty($trimmed) && !str_starts_with($trimmed, '/*')) {
//$short_query = substr($trimmed, 0, 150);
//if (strlen($trimmed) > 150) {
// $short_query .= '...';
// }
$this->log_message("SQL: {$query}", 'SQL');
}
return $query;
}
public function log_hook($hook) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$caller = $this->get_caller_info($backtrace);
$this->log_message("HOOK: {$hook}{$caller}", 'HOOK');
}
public function log_template_include($template) {
$template_name = basename($template);
$this->log_message("TEMPLATE: {$template_name}", 'TEMPLATE');
return $template;
}
public function log_shutdown() {
$total_time = round((microtime(true) - $this->start_time) * 1000, 2);
$peak_memory = memory_get_peak_usage(true);
$this->log_message("SHUTDOWN - Завершение работы ({$total_time}ms, " .
size_format($peak_memory) . ")", 'FINISH');
}
private function get_caller_info($backtrace) {
foreach ($backtrace as $trace) {
if (isset($trace['file']) &&
!str_contains($trace['file'], 'wp-includes') &&
!str_contains($trace['file'], 'wp-admin')) {
$file = basename($trace['file']);
$line = $trace['line'] ?? 'unknown';
return "{$file}:{$line}";
}
}
return 'unknown';
}
}
// Инициализируем логгер только если включен дебаг
//if (defined('WP_DEBUG') && WP_DEBUG) {
RealtimePageLogger::get_instance();
//}
// Функция для очистки лога
add_action('wp_ajax_clear_realtime_log', 'clear_realtime_debug_log');
function clear_realtime_debug_log() {
if (current_user_can('administrator')) {
file_put_contents(WP_CONTENT_DIR . '/realtime-debug.log', '');
echo "Realtime log cleared!";
}
wp_die();
}

511
inc/realtimesqllogger.php Normal file
View File

@@ -0,0 +1,511 @@
<?php
/**
* Real-time SQL Query Logger with PRE-execution tracking
*/
class RealtimeSqlLogger {
private static $instance;
private $log_file;
private $request_id;
private $start_time;
private $pending_queries = [];
private $log_enabled = true;
private $current_location = 'unknown';
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->log_file = WP_CONTENT_DIR . '/sql-debug.log';
$this->request_id = uniqid('req_', true);
$this->start_time = microtime(true);
$this->init_log_file();
$this->init();
}
private function init_log_file() {
if (!is_writable(WP_CONTENT_DIR)) {
$this->log_enabled = false;
error_log('Директория wp-content недоступна для записи');
return;
}
if (!file_exists($this->log_file)) {
if (@touch($this->log_file)) {
@chmod($this->log_file, 0644);
}
}
// Очищаем файл при каждой инициализации
@file_put_contents($this->log_file, '');
}
private function init() {
if (!$this->log_enabled) return;
// Трекинг местоположения в WordPress
$this->setup_location_tracking();
// Перехватываем запрос ДО выполнения
add_filter('query', [$this, 'log_query_before_execution'], 1, 1);
// Логируем результат выполнения
add_filter('query', [$this, 'log_query_after_execution'], 9999, 2);
// Логируем ошибки запросов
add_action('wpdb_error', [$this, 'log_query_error'], 10, 2);
add_action('shutdown', [$this, 'log_pending_queries'], 9999);
}
/**
* Настраиваем трекинг местоположения в WordPress
*/
private function setup_location_tracking() {
// Основные хуки WordPress для определения местоположения
$location_hooks = [
'plugins_loaded' => 'PLUGINS_LOADED',
'setup_theme' => 'SETUP_THEME',
'after_setup_theme' => 'AFTER_SETUP_THEME',
'init' => 'INIT',
'wp_loaded' => 'WP_LOADED',
'parse_query' => 'PARSE_QUERY',
'pre_get_posts' => 'PRE_GET_POSTS',
'wp' => 'WP_MAIN_QUERY',
'template_redirect' => 'TEMPLATE_REDIRECT',
'get_header' => 'HEADER',
'wp_head' => 'WP_HEAD',
'loop_start' => 'LOOP_START',
'the_post' => 'THE_POST',
'loop_end' => 'LOOP_END',
'get_sidebar' => 'SIDEBAR',
'get_footer' => 'FOOTER',
'wp_footer' => 'WP_FOOTER',
'shutdown' => 'SHUTDOWN'
];
foreach ($location_hooks as $hook => $location) {
add_action($hook, function() use ($location) {
$this->current_location = $location;
$this->log_message("📍 LOCATION: {$location}", 'LOCATION', false);
}, 1);
}
// Специальные хуки для контента
add_action('the_content', function($content) {
$this->current_location = 'THE_CONTENT';
return $content;
});
add_action('the_title', function($title) {
$this->current_location = 'THE_TITLE';
return $title;
});
add_action('the_excerpt', function($excerpt) {
$this->current_location = 'THE_EXCERPT';
return $excerpt;
});
}
/**
* Перехватываем запрос ДО выполнения
*/
public function log_query_before_execution($query) {
if (!$this->log_enabled) return $query;
$trimmed = trim($query);
if (empty($trimmed)) return $query;
$query_hash = md5($query . microtime(true));
$backtrace_info = $this->get_detailed_caller_info();
// Сохраняем запрос как pending
$this->pending_queries[$query_hash] = [
'query' => $query,
'start_time' => microtime(true),
'backtrace' => $backtrace_info['short'],
'detailed_backtrace' => $backtrace_info['full'],
'component' => $this->get_component_info($backtrace_info['file']),
'location' => $this->current_location,
'status' => 'PENDING'
];
$this->log_message(
"🚦 QUERY QUEUED: " . $this->shorten_query($query),
'SQL-QUEUE',
false
);
$this->log_message(
" 📍 Source: " . $backtrace_info['short'],
'SQL-QUEUE',
false
);
$this->log_message(
" 🏷️ Component: " . $this->pending_queries[$query_hash]['component'],
'SQL-QUEUE',
false
);
$this->log_message(
" 🏠 Location: " . $this->current_location,
'SQL-QUEUE',
false
);
return $query;
}
/**
* Логируем запрос ПОСЛЕ выполнения
*/
public function log_query_after_execution($query, $result = null) {
if (!$this->log_enabled) return $query;
$trimmed = trim($query);
if (empty($trimmed)) return $query;
$query_hash = md5($query . microtime(true));
$execution_time = 0;
$caller_info = '';
$component_info = '';
$location_info = '';
// Находим соответствующий pending запрос
foreach ($this->pending_queries as $hash => $pending) {
if ($pending['query'] === $query) {
$execution_time = microtime(true) - $pending['start_time'];
$this->pending_queries[$hash]['status'] = 'COMPLETED';
$this->pending_queries[$hash]['execution_time'] = $execution_time;
$caller_info = $pending['backtrace'];
$component_info = $pending['component'];
$location_info = $pending['location'];
break;
}
}
$status = $result === false ? 'FAILED' : 'COMPLETED';
$time_ms = round($execution_time * 1000, 2);
$this->log_message(
"✅ QUERY {$status}: {$time_ms}ms - " . $this->shorten_query($query),
$status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE',
false
);
$this->log_message(
" 📍 Source: " . $caller_info,
$status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE',
false
);
$this->log_message(
" 🏷️ Component: " . $component_info,
$status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE',
false
);
$this->log_message(
" 🏠 Location: " . $location_info,
$status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE',
false
);
// Для медленных запросов добавляем дополнительную информацию
if ($time_ms > 100 && $status !== 'FAILED') {
$this->log_message(
" ⚠️ SLOW QUERY: {$time_ms}ms - consider optimization",
'SQL-SLOW',
false
);
}
return $query;
}
/**
* Логируем ошибки запросов
*/
public function log_query_error($error, $query) {
if (!$this->log_enabled) return;
$this->log_message(
"❌ QUERY ERROR: " . $error,
'SQL-ERROR',
false
);
$this->log_message(
" 💥 Failed query: " . $this->shorten_query($query),
'SQL-ERROR',
false
);
// Находим информацию о вызывающем коде для ошибочного запроса
foreach ($this->pending_queries as $pending) {
if ($pending['query'] === $query) {
$this->log_message(
" 📍 Source: " . $pending['backtrace'],
'SQL-ERROR',
false
);
$this->log_message(
" 🏷️ Component: " . $pending['component'],
'SQL-ERROR',
false
);
$this->log_message(
" 🏠 Location: " . $pending['location'],
'SQL-ERROR',
false
);
break;
}
}
}
/**
* Логируем зависшие запросы
*/
public function log_pending_queries() {
if (!$this->log_enabled || empty($this->pending_queries)) return;
$current_time = microtime(true);
$hung_queries = 0;
foreach ($this->pending_queries as $hash => $query_info) {
if ($query_info['status'] === 'PENDING') {
$hang_time = round(($current_time - $query_info['start_time']) * 1000, 2);
$hung_queries++;
$this->log_message(
"⚠️ HUNG QUERY: {$hang_time}ms - " . $this->shorten_query($query_info['query']),
'SQL-HUNG',
false
);
$this->log_message(
" 📍 Source: " . $query_info['backtrace'],
'SQL-HUNG',
false
);
$this->log_message(
" 🏷️ Component: " . $query_info['component'],
'SQL-HUNG',
false
);
$this->log_message(
" 🏠 Location: " . $query_info['location'],
'SQL-HUNG',
false
);
$this->log_message(
" 🔍 Full backtrace:\n" . $query_info['detailed_backtrace'],
'SQL-HUNG',
false
);
}
}
if ($hung_queries > 0) {
$this->log_message(
"🔴 Found {$hung_queries} hung queries!",
'SQL-HUNG',
false
);
}
// Логируем статистику по всем запросам
$this->log_query_statistics();
}
/**
* Логируем статистику выполнения запросов
*/
private function log_query_statistics() {
$total_queries = count($this->pending_queries);
$completed_queries = 0;
$failed_queries = 0;
$total_time = 0;
$queries_by_location = [];
$queries_by_component = [];
foreach ($this->pending_queries as $query) {
// Статистика по местоположению
$location = $query['location'];
if (!isset($queries_by_location[$location])) {
$queries_by_location[$location] = 0;
}
$queries_by_location[$location]++;
// Статистика по компонентам
$component = $query['component'];
if (!isset($queries_by_component[$component])) {
$queries_by_component[$component] = 0;
}
$queries_by_component[$component]++;
if ($query['status'] === 'COMPLETED') {
$completed_queries++;
$total_time += $query['execution_time'] ?? 0;
} elseif ($query['status'] === 'FAILED') {
$failed_queries++;
}
}
$avg_time = $completed_queries > 0 ? round(($total_time / $completed_queries) * 1000, 2) : 0;
$this->log_message(
"📊 STATS: Total: {$total_queries} | Completed: {$completed_queries} | " .
"Failed: {$failed_queries} | Avg: {$avg_time}ms",
'STATS',
false
);
// Статистика по местоположению
if (!empty($queries_by_location)) {
$this->log_message("📊 QUERIES BY LOCATION:", 'STATS', false);
arsort($queries_by_location);
foreach ($queries_by_location as $location => $count) {
$this->log_message(" {$location}: {$count} queries", 'STATS', false);
}
}
// Статистика по компонентам
if (!empty($queries_by_component)) {
$this->log_message("📊 QUERIES BY COMPONENT:", 'STATS', false);
arsort($queries_by_component);
foreach ($queries_by_component as $component => $count) {
$this->log_message(" {$component}: {$count} queries", 'STATS', false);
}
}
}
private function log_message($message, $level = 'INFO', $check_enabled = true) {
if ($check_enabled && !$this->log_enabled) return;
$timestamp = microtime(true);
$elapsed = round(($timestamp - $this->start_time) * 1000, 2);
$log = sprintf(
"[%s] %s | %6.2fms | %-12s | %s\n",
date('H:i:s'),
substr($this->request_id, -6),
$elapsed,
$level,
$message
);
@file_put_contents($this->log_file, $log, FILE_APPEND | LOCK_EX);
}
/**
* Получаем детальную информацию о вызывающем коде
*/
private function get_detailed_caller_info() {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15);
$short_info = 'unknown';
$full_info = '';
$file_path = '';
foreach ($backtrace as $index => $trace) {
if (isset($trace['file']) &&
!str_contains($trace['file'], 'wp-includes') &&
!str_contains($trace['file'], 'wp-admin')) {
$file = basename($trace['file']);
$line = $trace['line'] ?? 'unknown';
$function = $trace['function'] ?? 'unknown';
if ($short_info === 'unknown') {
$short_info = "{$file}:{$line} ({$function})";
$file_path = $trace['file'];
}
$full_info .= sprintf("#%d %s(%d): %s()\n",
$index,
$trace['file'],
$trace['line'] ?? 0,
$function
);
}
}
return [
'short' => $short_info,
'full' => $full_info ?: 'No backtrace available',
'file' => $file_path
];
}
/**
* Определяем компонент (плагин/тема) по пути файла
*/
private function get_component_info($file_path) {
if (empty($file_path)) return 'unknown';
$abspath = ABSPATH;
// Определяем тип компонента
if (str_contains($file_path, WP_PLUGIN_DIR)) {
$relative_path = str_replace(WP_PLUGIN_DIR . '/', '', $file_path);
$parts = explode('/', $relative_path);
return 'Plugin: ' . $parts[0];
} elseif (str_contains($file_path, get_template_directory())) {
$theme = wp_get_theme();
return 'Theme: ' . $theme->get('Name');
} elseif (str_contains($file_path, get_stylesheet_directory())) {
$theme = wp_get_theme();
return 'Child Theme: ' . $theme->get('Name');
} elseif (str_contains($file_path, $abspath . 'wp-content/mu-plugins')) {
return 'MU-Plugin';
} elseif (str_contains($file_path, $abspath . 'wp-content')) {
return 'Other (wp-content)';
} elseif (str_contains($file_path, $abspath)) {
return 'WordPress Core';
}
return 'External';
}
private function shorten_query($query, $length = 120) {
$trimmed = trim($query);
if (strlen($trimmed) <= $length) return $trimmed;
return substr($trimmed, 0, $length) . '...';
}
}
// Инициализируем логгер
//if (defined('WP_DEBUG') && WP_DEBUG) {
RealtimeSqlLogger::get_instance();
//}
// Функция для просмотра текущего состояния
add_action('wp_footer', 'show_sql_debug_info', 9999);
function show_sql_debug_info() {
if (!current_user_can('administrator')) return;
$log_file = WP_CONTENT_DIR . '/sql-debug.log';
if (file_exists($log_file)) {
$log_content = file_get_contents($log_file);
echo '<div style="background:#1e1e1e;padding:20px;margin:20px;border:1px solid #444;font-family:monospace;font-size:11px;max-height:400px;overflow:auto;color:#fff;">';
echo '<h3 style="color:#fff;">🎯 SQL Debug Log (Live)</h3>';
echo '<pre style="color:#ccc;line-height:1.4;">' . htmlspecialchars($log_content) . '</pre>';
echo '</div>';
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Фильтр для удаления заглавных изображений с влиянием на OpenGraph
*/
// Глобальный массив для хранения ID постов где удалили изображение
$GLOBALS['removed_featured_images'] = array();
/**
* Основной фильтр - удаление изображения
*/
add_filter('post_thumbnail_id', 'filter_featured_image_if_old_and_copyright', 999, 2);
function filter_featured_image_if_old_and_copyright($thumb_id, $post) {
// Быстрый выход если нет ID
if (!$thumb_id) {
return $thumb_id;
}
// Определяем ID поста
$post_id = is_object($post) ? $post->ID : (is_numeric($post) ? $post : 0);
if (!$post_id) return $thumb_id;
// Проверяем тип записи
$post_type = get_post_type($post_id);
if (!in_array($post_type, array('profile_article', 'anew', 'yellow'))) {
return $thumb_id;
}
// Получаем дату загрузки изображения
$attachment = get_post($thumb_id);
if (!$attachment) return $thumb_id;
// Проверяем дату (изображения до 2024 года)
if (strtotime($attachment->post_date) >= strtotime('2024-01-01')) {
return $thumb_id;
}
// Проверяем на наличие копирайтов
$text = strtolower($attachment->post_excerpt . ' ' .
$attachment->post_content . ' ' .
$attachment->post_title);
if (stripos($text, 'shutterstock') !== false ||
stripos($text, 'fotodom') !== false) {
// Запоминаем что для этого поста удалили изображение
$GLOBALS['removed_featured_images'][$post_id] = true;
// Возвращаем false - изображение удалено
return false;
}
return $thumb_id;
}
/**
* 4. Фильтр для Rank Math OpenGraph
*/
add_filter('rank_math/opengraph/facebook/image', 'filter_rankmath_opengraph', 10, 1);
add_filter('rank_math/opengraph/twitter/image', 'filter_rankmath_opengraph', 10, 1);
function filter_rankmath_opengraph($image_url) {
global $post;
if (!is_object($post) || !isset($post->ID)) {
return $image_url;
}
if (isset($GLOBALS['removed_featured_images'][$post->ID])) {
return ''; // Удаляем изображение
}
return $image_url;
}

24
inc/rossiya.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
function replace_category_with_tag($query) {
// Проверяем, что это главный запрос и не в админке
if (!$query->is_main_query() || is_admin()) {
return;
}
// Проверяем, что это архив рубрики с ID 3347
if ($query->is_category(3347)) {
// Убираем фильтр по категории
$query->set('cat', '');
$query->set('category__in', '');
// Устанавливаем фильтр по тегу
$query->set('tag', 'rossiya');
// Сбрасываем флаги, чтобы WordPress думал, что это архив тега
$query->is_category = false;
$query->is_tag = true;
$query->is_archive = true;
}
}
add_action('pre_get_posts', 'replace_category_with_tag');

View File

@@ -0,0 +1,34 @@
<?php
function schedule_async_post_processing( $post_id, $post, $update ) {
if ( wp_is_post_revision( $post_id ) ||
wp_is_post_autosave( $post_id ) ||
defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! in_array( $post->post_type, array( 'profile_article', 'anew', 'yellow' ), true ) ) {
return;
}
if ( 'publish' !== $post->post_status ) {
return;
}
// НЕМЕДЛЕННО ставим одну быструю задачу
as_schedule_single_action(
time() + 2, // Минимальная задержка
'async_post_processing_trigger',
array( $post_id ),
'async_processing'
);
}
add_action( 'save_post', 'schedule_async_post_processing', 99, 3 ); // Низкий приоритет

221
inc/sql_result.php Normal file
View File

@@ -0,0 +1,221 @@
<?php
add_action('wp_footer', 'display_sql_queries_in_footer', 9999);
function display_sql_queries_in_footer() {
if (!current_user_can('administrator')) {
return;
}
global $wpdb;
if (empty($wpdb->queries)) {
echo '<div style="background: #fff; padding: 20px; margin: 20px; border: 1px solid #ccc; font-family: monospace; font-size: 14px; position: relative; z-index: 99999;">';
echo '<p>Нет данных о запросах. Убедитесь, что в wp-config.php добавлена строка:</p>';
echo '<code style="background: #f4f4f4; padding: 5px; display: block;">define( \'SAVEQUERIES\', true );</code>';
echo '</div>';
return;
}
$queries = $wpdb->queries;
$total_time = 0;
foreach ($queries as $query) {
$total_time += $query[1];
}
echo '<div id="sql-debug-panel" style="background: #fff; padding: 20px; margin: 20px; border: 1px solid #ccc; font-family: monospace; font-size: 14px; position: relative; z-index: 99999;">';
// Панель управления сортировкой
echo '<div style="margin-bottom: 20px; padding: 10px; background: #f5f5f5; border-radius: 5px;">';
echo '<h3 style="margin-top: 0; display: inline-block; margin-right: 20px;">SQL Запросы</h3>';
echo '<button onclick="sortQueries(\'time-desc\')" style="margin-right: 10px; padding: 5px 10px; background: #e74c3c; color: white; border: none; border-radius: 3px; cursor: pointer;">Самые медленные</button>';
echo '<button onclick="sortQueries(\'time-asc\')" style="margin-right: 10px; padding: 5px 10px; background: #27ae60; color: white; border: none; border-radius: 3px; cursor: pointer;">Самые быстрые</button>';
echo '<button onclick="sortQueries(\'default\')" style="margin-right: 10px; padding: 5px 10px; background: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer;">По умолчанию</button>';
echo '<span style="margin-left: 20px; font-weight: bold;">Всего запросов: ' . count($queries) . '</span>';
echo '</div>';
echo '<table id="sql-queries-table" style="width: 100%; border-collapse: collapse; margin-top: 15px;">';
echo '<thead>';
echo '<tr style="background: #2c3e50; color: #fff; cursor: pointer;">';
echo '<th style="padding: 8px; border: 1px solid #ddd; text-align: left;" onclick="sortQueries(\'time-desc\')">Время ⬇</th>';
echo '<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Запрос</th>';
echo '<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Вызвавший код</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
foreach ($queries as $query) {
$time = number_format($query[1] * 1000, 2);
$time_seconds = $query[1];
$color = '#27ae60';
if ($query[1] > 0.1) $color = '#f39c12';
if ($query[1] > 0.5) $color = '#e74c3c';
echo '<tr class="sql-query-row">';
echo '<td style="padding: 8px; border: 1px solid #ddd; color: ' . $color . '; font-weight: bold;" data-time="' . $time_seconds . '">' . $time . ' ms</td>';
echo '<td style="padding: 8px; border: 1px solid #ddd; word-break: break-all;">' . htmlspecialchars($query[0]) . '</td>';
echo '<td style="padding: 8px; border: 1px solid #ddd; font-size: 12px; color: #666;">' . $query[2] . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '<div style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-left: 4px solid #3498db;">';
echo 'Общее время SQL запросов: <strong>' . number_format($total_time * 1000, 2) . ' ms</strong><br>';
echo 'Среднее время на запрос: <strong>' . number_format(($total_time / count($queries)) * 1000, 2) . ' ms</strong>';
echo '</div>';
echo '</div>';
// Добавляем JavaScript для сортировки
echo '
<script>
function sortQueries(sortType) {
const table = document.getElementById("sql-queries-table");
const tbody = table.querySelector("tbody");
const rows = Array.from(tbody.querySelectorAll("tr.sql-query-row"));
// Сбрасываем классы активной сортировки у заголовков
const headers = table.querySelectorAll("th");
headers.forEach(header => {
header.innerHTML = header.innerHTML.replace(" ⬇", "").replace(" ⬆", "");
});
// Добавляем индикатор сортировки
if (sortType === "time-desc") {
headers[0].innerHTML = "Время ⬇";
} else if (sortType === "time-asc") {
headers[0].innerHTML = "Время ⬆";
}
rows.sort((a, b) => {
const timeA = parseFloat(a.querySelector("td[data-time]").getAttribute("data-time"));
const timeB = parseFloat(b.querySelector("td[data-time]").getAttribute("data-time"));
switch(sortType) {
case "time-desc":
return timeB - timeA; // Самые медленные first
case "time-asc":
return timeA - timeB; // Самые быстрые first
case "default":
return 0; // Оригинальный порядок
default:
return 0;
}
});
// Очищаем и перезаполняем tbody
while (tbody.firstChild) {
tbody.removeChild(tbody.firstChild);
}
rows.forEach(row => {
tbody.appendChild(row);
});
}
// Добавляем поиск по запросам
function addSearchFeature() {
const panel = document.getElementById("sql-debug-panel");
const searchHtml = \`
<div style="margin-bottom: 15px;">
<input type="text" id="sql-search" placeholder="Поиск по запросам..."
style="padding: 8px; width: 300px; border: 1px solid #ddd; border-radius: 3px;">
<button onclick="filterQueries()" style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer;">Поиск</button>
<button onclick="clearSearch()" style="padding: 8px 15px; background: #95a5a6; color: white; border: none; border-radius: 3px; cursor: pointer;">Очистить</button>
</div>
\`;
const controlsDiv = panel.querySelector("div");
controlsDiv.insertAdjacentHTML("afterend", searchHtml);
}
function filterQueries() {
const searchTerm = document.getElementById("sql-search").value.toLowerCase();
const rows = document.querySelectorAll("tr.sql-query-row");
rows.forEach(row => {
const queryText = row.querySelector("td:nth-child(2)").textContent.toLowerCase();
const callerText = row.querySelector("td:nth-child(3)").textContent.toLowerCase();
if (queryText.includes(searchTerm) || callerText.includes(searchTerm)) {
row.style.display = "";
} else {
row.style.display = "none";
}
});
}
function clearSearch() {
document.getElementById("sql-search").value = "";
const rows = document.querySelectorAll("tr.sql-query-row");
rows.forEach(row => {
row.style.display = "";
});
}
// Инициализируем поиск при загрузке
document.addEventListener("DOMContentLoaded", function() {
addSearchFeature();
});
</script>
';
}
// Добавляем стили и скрипты
add_action('wp_enqueue_scripts', 'add_sql_debug_styles');
function add_sql_debug_styles() {
if (!current_user_can('administrator')) {
return;
}
wp_enqueue_style('sql-debug-style', false);
echo '
<style>
#sql-debug-panel {
max-height: 600px;
overflow-y: auto;
position: fixed;
bottom: 20px;
right: 20px;
width: 90%;
max-width: 1200px;
box-shadow: 0 0 20px rgba(0,0,0,0.3);
border-radius: 8px;
}
.sql-query-row:hover {
background-color: #f8f9fa !important;
}
.sort-btn {
margin-right: 10px;
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.sort-btn:hover {
opacity: 0.9;
}
.sort-desc { background: #e74c3c; color: white; }
.sort-asc { background: #27ae60; color: white; }
.sort-default { background: #3498db; color: white; }
#sql-search {
padding: 8px;
width: 300px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
</style>
';
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* ТЕСТИРОВАНИЕ ACTION SCHEDULER
* Код для проверки работы фоновых задач
*/
// Регистрируем обработчик
add_action( 'test_action_scheduler_task', 'test_action_scheduler_log_to_file', 10, 2 );
function test_action_scheduler_log_to_file( $post_id, $post_title ) {
$log_file = ABSPATH . 'action-scheduler-test.log';
$log_entry = sprintf(
"[%s] Задача выполнена | Post ID: %d | Title: %s\n",
current_time( 'Y-m-d H:i:s' ),
$post_id,
$post_title
);
file_put_contents( $log_file, $log_entry, FILE_APPEND | LOCK_EX );
//error_log( 'Action Scheduler Test: ' . trim( $log_entry ) );
}
function test_schedule_action_on_save_post( $post_id, $post, $update ) {
// Проверки
if ( wp_is_post_revision( $post_id ) ||
wp_is_post_autosave( $post_id ) ||
defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! in_array( $post->post_type, array( 'profile_article','anew','yellow' ), true ) ) {
return;
}
if ( 'publish' !== $post->post_status ) {
return;
}
// Планируем задачу
as_schedule_single_action(
time() + 60,
'test_action_scheduler_task',
array( $post_id, $post->post_title ),
'test'
);
//error_log( "Задача запланирована для поста ID: {$post_id}" );
}
add_action( 'save_post', 'test_schedule_action_on_save_post', 10, 3 );

View File

@@ -0,0 +1,159 @@
<?php
class Scheduler_CLI_Commands extends WP_CLI_Command {
/**
* Принудительно запустить задачу Action Scheduler, даже если она failed
*
* ## OPTIONS
*
* <action_id>
* : ID задачи Action Scheduler
*
* [--retry]
* : Сбросить статус failed перед запуском
*
* ## EXAMPLES
*
* wp scheduler force-run-action 11843340
* wp scheduler force-run-action 456 --retry
*
* @param array $args
* @param array $assoc_args
*/
public function force_run_action( $args, $assoc_args ) {
list( $action_id ) = $args;
$retry = isset( $assoc_args['retry'] );
WP_CLI::log( "Принудительный запуск задачи ID: {$action_id}" );
global $wpdb;
// Получаем информацию о задаче
$action = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}actionscheduler_actions WHERE action_id = %d",
$action_id
) );
if ( ! $action ) {
WP_CLI::error( "Задача с ID {$action_id} не найдена." );
return;
}
WP_CLI::log( "Статус: " . $action->status );
WP_CLI::log( "Хук: " . $action->hook );
// Если нужно сбросить статус failed
if ( $retry && 'failed' === $action->status ) {
$updated = $wpdb->update(
"{$wpdb->prefix}actionscheduler_actions",
array( 'status' => 'pending' ),
array( 'action_id' => $action_id ),
array( '%s' ),
array( '%d' )
);
if ( $updated ) {
WP_CLI::log( "Статус сброшен с failed на pending" );
}
}
// Запускаем задачу через Action Scheduler
try {
// Создаем экземпляр задачи
$store = ActionScheduler::store();
$action_obj = $store->fetch_action( $action_id );
if ( ! $action_obj ) {
WP_CLI::error( "Не удалось создать объект задачи." );
return;
}
WP_CLI::log( "Запуск задачи..." );
// Выполняем задачу
$start_time = microtime( true );
$action_obj->execute();
$execution_time = microtime( true ) - $start_time;
// Проверяем новый статус
$new_status = $wpdb->get_var( $wpdb->prepare(
"SELECT status FROM {$wpdb->prefix}actionscheduler_actions WHERE action_id = %d",
$action_id
) );
WP_CLI::success( sprintf(
"Задача выполнена за %.2f секунд. Новый статус: %s",
$execution_time,
$new_status
) );
} catch ( Exception $e ) {
WP_CLI::error( "Ошибка выполнения: " . $e->getMessage() );
}
}
/**
* Сбросить статус failed задачи на pending
*
* ## OPTIONS
*
* <action_id>
* : ID задачи Action Scheduler
*
* ## EXAMPLES
*
* wp scheduler reset-failed-action 123
*/
public function reset_failed_action( $args, $assoc_args ) {
list( $action_id ) = $args;
global $wpdb;
$action = $wpdb->get_row( $wpdb->prepare(
"SELECT status FROM {$wpdb->prefix}actionscheduler_actions WHERE action_id = %d",
$action_id
) );
if ( ! $action ) {
WP_CLI::error( "Задача с ID {$action_id} не найдена." );
return;
}
if ( 'failed' !== $action->status ) {
WP_CLI::warning( "Статус задачи не 'failed', а '{$action->status}'. Сброс не требуется." );
return;
}
$updated = $wpdb->update(
"{$wpdb->prefix}actionscheduler_actions",
array( 'status' => 'pending' ),
array( 'action_id' => $action_id ),
array( '%s' ),
array( '%d' )
);
if ( $updated ) {
WP_CLI::success( "Статус задачи сброшен с failed на pending." );
} else {
WP_CLI::error( "Не удалось сбросить статус задачи." );
}
}
}
// Регистрируем команды - ЭТО ОБЯЗАТЕЛЬНО!
WP_CLI::add_command( 'scheduler', 'Scheduler_CLI_Commands' );

View File

@@ -50,7 +50,8 @@
<span class="footer__delimiter"></span>
<div class="footer__copyright">
<p class="d-block d-md-flex">
Информационное агентство "Деловой журнал "Профиль" зарегистрировано в Федеральной службе по надзору в сфере связи, информационных технологий и массовых коммуникаций. Свидетельство о государственной регистрации серии ИА № ФС 77 - 89668 от 23.06.2025
Информационное агентство "Деловой журнал "Профиль" зарегистрировано в Федеральной службе по надзору в сфере связи, информационных технологий и массовых коммуникаций. Свидетельство о государственной регистрации серии ИА № ФС 77 - 89668 от 23.06.2025<br>
Cвидетельство о регистрации электронного СМИ Эл NºФС77-73069 от 09 июня 2018 г.
</p>
<p>
©<?= date("Y") ?> ИДР. Все права защищены.

View File

@@ -51,13 +51,13 @@
<?php get_template_part("template-parts/header/breadcrumbs") ?>
<!--[ zone 14 ]-->
<?php if( !wp_is_mobile() and is_home() ) : ?>
<div style="margin-bottom: 18px; text-align: center;">
<?php get_template_part("template-parts/ad/revive/ad", "", [ "zone" => 12, "show_on_mobile" => false ]); ?>
<?php get_template_part("template-parts/ad/revive/ad", "", [ "zone" => 14, "show_on_mobile" => false ]); ?>
</div>
<?php endif; ?>
<!--[/ zone 14 ]-->
<div class="container-fluid">

View File

@@ -134,9 +134,13 @@
<ul class="news__list__top">
<?php if($top_query->have_posts()): ?>
<?php
$nntop = 0;
if($top_query->have_posts()): ?>
<?php while ($top_query->have_posts()): ?>
<?php while ($top_query->have_posts()):
$nntop++;
if ($nntop > 10){ break;}?>
<?php $top_query->the_post(); ?>

503
utils/acf-category.php Normal file
View File

@@ -0,0 +1,503 @@
<?php
/*
Plugin Name: ACF Fields Viewer by Post Type
Description: Показывает все ACF поля для типов постов и таксономий
Version: 2.0
Author: Your Name
*/
// Добавляем пункт в главное меню админки
add_action('admin_menu', 'acf_viewer_add_menu');
function acf_viewer_add_menu() {
add_menu_page(
'ACF Fields Viewer',
'ACF Viewer',
'manage_options',
'acf-fields-viewer',
'acf_viewer_main_page',
'dashicons-list-view',
30
);
}
function acf_viewer_main_page() {
if (!current_user_can('manage_options')) {
wp_die('Недостаточно прав для доступа к этой странице.');
}
// Получаем текущую вкладку
$current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'post_types';
?>
<div class="wrap">
<h1>ACF Fields Viewer</h1>
<!-- Вкладки навигации -->
<h2 class="nav-tab-wrapper">
<a href="?page=acf-fields-viewer&tab=post_types" class="nav-tab <?php echo $current_tab === 'post_types' ? 'nav-tab-active' : ''; ?>">
Типы постов
</a>
<a href="?page=acf-fields-viewer&tab=taxonomies" class="nav-tab <?php echo $current_tab === 'taxonomies' ? 'nav-tab-active' : ''; ?>">
Таксономии
</a>
<a href="?page=acf-fields-viewer&tab=options" class="nav-tab <?php echo $current_tab === 'options' ? 'nav-tab-active' : ''; ?>">
Options Pages
</a>
</h2>
<div class="tab-content">
<?php
if ($current_tab === 'post_types') {
acf_viewer_show_post_types();
} elseif ($current_tab === 'taxonomies') {
acf_viewer_show_taxonomies();
} elseif ($current_tab === 'options') {
acf_viewer_show_options_pages();
}
?>
</div>
<style>
.acf-viewer-container {
background: #fff;
padding: 20px;
margin: 20px 0;
border: 1px solid #ccd0d4;
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
}
.acf-viewer-container h2 {
color: #1d2327;
margin: 0 0 15px 0;
padding-bottom: 10px;
border-bottom: 2px solid #2271b1;
}
.acf-viewer-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-bottom: 20px;
padding: 15px;
background: #f6f7f7;
border-radius: 4px;
}
.acf-viewer-info-item {
padding: 5px 0;
}
.acf-viewer-info-item strong {
color: #2271b1;
}
.acf-viewer-table {
margin-top: 15px;
}
.acf-viewer-table pre {
max-height: 200px;
overflow: auto;
background: #f6f7f7;
padding: 10px;
margin: 0;
font-size: 12px;
line-height: 1.4;
border-radius: 3px;
}
.acf-empty {
color: #999;
font-style: italic;
}
.post-type-section {
margin-bottom: 30px;
padding: 20px;
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
}
.post-type-section > h2 {
margin-top: 0;
color: #2271b1;
font-size: 24px;
}
.tab-content {
margin-top: 20px;
}
code.field-key {
background: #f0f0f1;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
}
.field-group-box {
background: #f9f9f9;
border-left: 4px solid #2271b1;
padding: 15px;
margin: 15px 0;
}
.field-group-box h3 {
margin-top: 0;
color: #2271b1;
}
.field-item {
padding: 10px;
background: #fff;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 3px;
}
.field-item:hover {
background: #f0f0f1;
}
.badge {
display: inline-block;
padding: 3px 8px;
background: #2271b1;
color: #fff;
border-radius: 3px;
font-size: 11px;
margin-left: 5px;
}
.badge.required {
background: #d63638;
}
.location-rules {
background: #fff3cd;
border: 1px solid #ffc107;
padding: 10px;
margin: 10px 0;
border-radius: 3px;
}
</style>
</div>
<?php
}
// Функция для отображения типов постов
function acf_viewer_show_post_types() {
// Получаем все зарегистрированные типы постов
$all_post_types = get_post_types(array(), 'objects');
if (empty($all_post_types)) {
echo '<div class="notice notice-warning"><p>Типы постов не найдены.</p></div>';
return;
}
foreach ($all_post_types as $post_type_slug => $post_type) {
// Пропускаем служебные типы
if (in_array($post_type_slug, array('revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation', 'acf-field-group', 'acf-field'))) {
continue;
}
echo '<div class="post-type-section">';
echo '<h2>' . esc_html($post_type->labels->name) . ' <span style="font-weight: normal; font-size: 18px; color: #666;">(' . esc_html($post_type_slug) . ')</span></h2>';
// Информация о типе поста
echo '<div class="acf-viewer-info">';
echo '<div class="acf-viewer-info-item"><strong>Slug:</strong> ' . esc_html($post_type_slug) . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Публичный:</strong> ' . ($post_type->public ? 'Да' : 'Нет') . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Иерархический:</strong> ' . ($post_type->hierarchical ? 'Да' : 'Нет') . '</div>';
// Подсчет постов
$count = wp_count_posts($post_type_slug);
$total = 0;
foreach ($count as $status => $number) {
$total += $number;
}
echo '<div class="acf-viewer-info-item"><strong>Всего постов:</strong> ' . $total . '</div>';
echo '</div>';
// Получаем группы полей для этого типа постов
$field_groups = acf_get_field_groups(array(
'post_type' => $post_type_slug
));
if (empty($field_groups)) {
echo '<div class="notice notice-info inline"><p>ACF группы полей не найдены для этого типа постов.</p></div>';
} else {
echo '<p><strong>Найдено групп полей:</strong> ' . count($field_groups) . '</p>';
foreach ($field_groups as $field_group) {
echo '<div class="field-group-box">';
echo '<h3>' . esc_html($field_group['title']) . '</h3>';
// Информация о группе
echo '<div style="margin-bottom: 15px;">';
echo '<div><strong>Key:</strong> <code>' . esc_html($field_group['key']) . '</code></div>';
// Показываем правила размещения
if (!empty($field_group['location'])) {
echo '<div class="location-rules">';
echo '<strong>Правила размещения:</strong><br>';
foreach ($field_group['location'] as $group_index => $rules) {
if ($group_index > 0) echo '<em style="color: #666;">или</em><br>';
foreach ($rules as $rule_index => $rule) {
if ($rule_index > 0) echo '<em style="color: #666;">и</em> ';
$operator_text = ($rule['operator'] == '==') ? 'равно' : 'не равно';
echo esc_html($rule['param']) . ' ' . $operator_text . ' <strong>' . esc_html($rule['value']) . '</strong><br>';
}
}
echo '</div>';
}
if (!empty($field_group['description'])) {
echo '<div><strong>Описание:</strong> ' . esc_html($field_group['description']) . '</div>';
}
echo '</div>';
// Получаем поля группы
$fields = acf_get_fields($field_group['key']);
if ($fields) {
echo '<table class="wp-list-table widefat fixed striped acf-viewer-table">';
echo '<thead><tr>';
echo '<th width="25%">Название поля</th>';
echo '<th width="20%">Ключ (Name)</th>';
echo '<th width="15%">Тип</th>';
echo '<th width="40%">Настройки</th>';
echo '</tr></thead>';
echo '<tbody>';
acf_viewer_display_fields($fields, 0);
echo '</tbody></table>';
} else {
echo '<p><em>Поля не найдены в этой группе.</em></p>';
}
echo '</div>';
}
}
echo '</div>';
}
}
// Рекурсивная функция для отображения полей (включая вложенные)
function acf_viewer_display_fields($fields, $level = 0) {
foreach ($fields as $field) {
$indent = str_repeat('—', $level);
echo '<tr>';
echo '<td>';
echo $indent . ' <strong>' . esc_html($field['label']) . '</strong>';
if (!empty($field['required'])) {
echo '<span class="badge required">обязательное</span>';
}
echo '</td>';
echo '<td><code class="field-key">' . esc_html($field['name']) . '</code></td>';
echo '<td>' . esc_html($field['type']) . '</td>';
echo '<td>';
// Показываем ключевые настройки в зависимости от типа
$settings = array();
if (!empty($field['instructions'])) {
$settings[] = '<strong>Инструкции:</strong> ' . esc_html($field['instructions']);
}
if (!empty($field['default_value'])) {
$settings[] = '<strong>По умолчанию:</strong> ' . esc_html($field['default_value']);
}
// Специфичные настройки для разных типов
switch ($field['type']) {
case 'text':
case 'textarea':
if (!empty($field['placeholder'])) {
$settings[] = '<strong>Placeholder:</strong> ' . esc_html($field['placeholder']);
}
if (!empty($field['maxlength'])) {
$settings[] = '<strong>Макс. длина:</strong> ' . esc_html($field['maxlength']);
}
break;
case 'select':
case 'checkbox':
case 'radio':
if (!empty($field['choices'])) {
$choices = is_array($field['choices']) ? implode(', ', array_keys($field['choices'])) : $field['choices'];
$settings[] = '<strong>Варианты:</strong> ' . esc_html($choices);
}
break;
case 'image':
case 'file':
if (!empty($field['return_format'])) {
$settings[] = '<strong>Формат возврата:</strong> ' . esc_html($field['return_format']);
}
if (!empty($field['mime_types'])) {
$settings[] = '<strong>Типы файлов:</strong> ' . esc_html($field['mime_types']);
}
break;
case 'post_object':
case 'relationship':
if (!empty($field['post_type'])) {
$pt = is_array($field['post_type']) ? implode(', ', $field['post_type']) : $field['post_type'];
$settings[] = '<strong>Типы постов:</strong> ' . esc_html($pt);
}
break;
case 'taxonomy':
if (!empty($field['taxonomy'])) {
$settings[] = '<strong>Таксономия:</strong> ' . esc_html($field['taxonomy']);
}
break;
}
if (!empty($settings)) {
echo implode('<br>', $settings);
} else {
echo '<em class="acf-empty">—</em>';
}
echo '</td>';
echo '</tr>';
// Рекурсивно обрабатываем вложенные поля (для repeater, group, flexible content)
if (in_array($field['type'], array('repeater', 'group', 'flexible_content')) && !empty($field['sub_fields'])) {
acf_viewer_display_fields($field['sub_fields'], $level + 1);
}
}
}
// Функция для отображения таксономий
function acf_viewer_show_taxonomies() {
// Получаем все таксономии
$taxonomies = get_taxonomies(array(), 'objects');
if (empty($taxonomies)) {
echo '<div class="notice notice-warning"><p>Таксономии не найдены.</p></div>';
return;
}
foreach ($taxonomies as $taxonomy_slug => $taxonomy) {
// Пропускаем служебные таксономии
if (in_array($taxonomy_slug, array('nav_menu', 'link_category', 'post_format', 'wp_theme', 'wp_template_part_area'))) {
continue;
}
echo '<div class="post-type-section">';
echo '<h2>' . esc_html($taxonomy->labels->name) . ' <span style="font-weight: normal; font-size: 18px; color: #666;">(' . esc_html($taxonomy_slug) . ')</span></h2>';
// Информация о таксономии
echo '<div class="acf-viewer-info">';
echo '<div class="acf-viewer-info-item"><strong>Slug:</strong> ' . esc_html($taxonomy_slug) . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Публичная:</strong> ' . ($taxonomy->public ? 'Да' : 'Нет') . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Иерархическая:</strong> ' . ($taxonomy->hierarchical ? 'Да' : 'Нет') . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Типы постов:</strong> ' . implode(', ', $taxonomy->object_type) . '</div>';
// Подсчет терминов
$terms_count = wp_count_terms(array('taxonomy' => $taxonomy_slug, 'hide_empty' => false));
echo '<div class="acf-viewer-info-item"><strong>Всего терминов:</strong> ' . (is_wp_error($terms_count) ? 0 : $terms_count) . '</div>';
echo '</div>';
// Получаем группы полей для этой таксономии
$field_groups = acf_get_field_groups(array(
'taxonomy' => $taxonomy_slug
));
if (empty($field_groups)) {
echo '<div class="notice notice-info inline"><p>ACF группы полей не найдены для этой таксономии.</p></div>';
} else {
echo '<p><strong>Найдено групп полей:</strong> ' . count($field_groups) . '</p>';
foreach ($field_groups as $field_group) {
echo '<div class="field-group-box">';
echo '<h3>' . esc_html($field_group['title']) . '</h3>';
echo '<div style="margin-bottom: 15px;">';
echo '<div><strong>Key:</strong> <code>' . esc_html($field_group['key']) . '</code></div>';
if (!empty($field_group['description'])) {
echo '<div><strong>Описание:</strong> ' . esc_html($field_group['description']) . '</div>';
}
echo '</div>';
// Получаем поля группы
$fields = acf_get_fields($field_group['key']);
if ($fields) {
echo '<table class="wp-list-table widefat fixed striped acf-viewer-table">';
echo '<thead><tr>';
echo '<th width="25%">Название поля</th>';
echo '<th width="20%">Ключ (Name)</th>';
echo '<th width="15%">Тип</th>';
echo '<th width="40%">Настройки</th>';
echo '</tr></thead>';
echo '<tbody>';
acf_viewer_display_fields($fields, 0);
echo '</tbody></table>';
} else {
echo '<p><em>Поля не найдены в этой группе.</em></p>';
}
echo '</div>';
}
}
echo '</div>';
}
}
// Функция для отображения Options Pages
function acf_viewer_show_options_pages() {
// Проверяем, есть ли функция acf_get_options_pages
if (!function_exists('acf_get_options_pages')) {
echo '<div class="notice notice-info"><p>ACF Options Pages не зарегистрированы или недоступны.</p></div>';
return;
}
$options_pages = acf_get_options_pages();
if (empty($options_pages)) {
echo '<div class="notice notice-info"><p>ACF Options Pages не найдены.</p></div>';
return;
}
foreach ($options_pages as $options_page) {
echo '<div class="post-type-section">';
echo '<h2>' . esc_html($options_page['page_title']) . '</h2>';
echo '<div class="acf-viewer-info">';
echo '<div class="acf-viewer-info-item"><strong>Menu Slug:</strong> ' . esc_html($options_page['menu_slug']) . '</div>';
echo '<div class="acf-viewer-info-item"><strong>Post ID:</strong> ' . esc_html($options_page['post_id']) . '</div>';
echo '</div>';
// Получаем группы полей для этой options page
$field_groups = acf_get_field_groups(array(
'options_page' => $options_page['menu_slug']
));
if (empty($field_groups)) {
echo '<div class="notice notice-info inline"><p>ACF группы полей не найдены для этой страницы опций.</p></div>';
} else {
echo '<p><strong>Найдено групп полей:</strong> ' . count($field_groups) . '</p>';
foreach ($field_groups as $field_group) {
echo '<div class="field-group-box">';
echo '<h3>' . esc_html($field_group['title']) . '</h3>';
$fields = acf_get_fields($field_group['key']);
if ($fields) {
echo '<table class="wp-list-table widefat fixed striped acf-viewer-table">';
echo '<thead><tr>';
echo '<th width="25%">Название поля</th>';
echo '<th width="20%">Ключ (Name)</th>';
echo '<th width="15%">Тип</th>';
echo '<th width="40%">Настройки</th>';
echo '</tr></thead>';
echo '<tbody>';
acf_viewer_display_fields($fields, 0);
echo '</tbody></table>';
}
echo '</div>';
}
}
echo '</div>';
}
}