Compare commits
10 Commits
ed66701325
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed4d79b706 | ||
|
|
83ca6c638a | ||
|
|
2a88b55732 | ||
|
|
485913ca23 | ||
|
|
141d117b96 | ||
|
|
61b86cdca5 | ||
|
|
b771da4504 | ||
|
|
d99f75aed7 | ||
|
|
ddf44fbfad | ||
|
|
9ed823e0af |
307
api/coauthor.php
Normal file
307
api/coauthor.php
Normal 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
93
api/newstop.php
Normal 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),
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -9,25 +9,30 @@
|
|||||||
* @param int $expire Время жизни кеша в секундах
|
* @param int $expire Время жизни кеша в секундах
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function cached_get_template_part( $template, $name = null, $data = [] ) {
|
function cached_get_template_part( $template, $name = null, $data = [], $ttl = 3600 ) {
|
||||||
|
|
||||||
$fcache = CACHED_TEMPLATE.$template.'.html';
|
$fcache = CACHED_TEMPLATE.$template.'.html';
|
||||||
|
|
||||||
|
// Проверяем существование файла и его время жизни
|
||||||
if ( file_exists($fcache) ){
|
if ( file_exists($fcache) ){
|
||||||
echo file_get_contents($fcache);
|
$cache_time = filemtime($fcache);
|
||||||
return true;
|
$current_time = time();
|
||||||
|
|
||||||
|
// Если кеш не истек, отдаем его
|
||||||
|
if ( ($current_time - $cache_time) < $ttl ) {
|
||||||
|
echo file_get_contents($fcache);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_directory = dirname($fcache);
|
$file_directory = dirname($fcache);
|
||||||
|
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
get_template_part($template, $name, $data );
|
get_template_part($template, $name, $data );
|
||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
|
|
||||||
echo $content;
|
echo $content;
|
||||||
|
|
||||||
|
|
||||||
if (!file_exists($file_directory)) {
|
if (!file_exists($file_directory)) {
|
||||||
if (!wp_mkdir_p($file_directory)) {
|
if (!wp_mkdir_p($file_directory)) {
|
||||||
error_log('Не удалось создать директорию: ' . $file_directory);
|
error_log('Не удалось создать директорию: ' . $file_directory);
|
||||||
@@ -41,9 +46,9 @@ function cached_get_template_part( $template, $name = null, $data = [] ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Функция очистки кеша
|
// Функция очистки кеша
|
||||||
function clear_template_cache ( $template ) {
|
function clear_template_cache ( $template ) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
//ini_set('display_errors', 0);
|
//ini_set('display_errors', 1);
|
||||||
//error_reporting(E_ALL);
|
//error_reporting(E_ERROR);
|
||||||
|
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
@@ -12,7 +12,11 @@ require "inc/cache-cleaner-home.php";
|
|||||||
require 'inc/article-rss-feed.php'; //rss ленты статей по датам
|
require 'inc/article-rss-feed.php'; //rss ленты статей по датам
|
||||||
require "inc/meta_keywords.php";
|
require "inc/meta_keywords.php";
|
||||||
require "inc/journal_issue.php"; //номера журналов
|
require "inc/journal_issue.php"; //номера журналов
|
||||||
require "inc/rank-description.php";
|
require "inc/rank-description.php"; //подменяем дескрипшт от соцсетей
|
||||||
|
require "inc/graphql.php"; //graphql
|
||||||
|
|
||||||
|
|
||||||
|
require "inc/replace_old_copyright.php"; //подменяем фото для старых картинок с "плохим" копирайтом
|
||||||
|
|
||||||
//scheduler
|
//scheduler
|
||||||
require "inc/schedule_async_post_processing.php";
|
require "inc/schedule_async_post_processing.php";
|
||||||
@@ -20,17 +24,24 @@ if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
|||||||
require "inc/action-scheduler-functions.php";
|
require "inc/action-scheduler-functions.php";
|
||||||
require "inc/wp-cli-scheduler-commands.php";
|
require "inc/wp-cli-scheduler-commands.php";
|
||||||
require "inc/adfox_on.php"; // управление adfox
|
require "inc/adfox_on.php"; // управление adfox
|
||||||
|
require "inc/generate_coauthors_cache.php"; // генерим кеш списка авторов
|
||||||
}
|
}
|
||||||
|
|
||||||
include "inc/get_cached_alm.php";
|
include "inc/get_cached_alm.php";
|
||||||
include "inc/graphql.php";
|
|
||||||
|
|
||||||
if (is_admin()) {
|
if (is_admin()) {
|
||||||
require "inc/add_adv_checked.php"; // рекламная статья
|
require "inc/add_adv_checked.php"; // рекламная статья
|
||||||
require "inc/admin/auto_check.php"; // помечакм не отправлять в Дзен
|
require "inc/admin/auto_check.php"; // помечакм не отправлять в Дзен
|
||||||
require "inc/admin/lock-terms.php"; // banned и keys
|
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 и прочее и внутри плагинов регулировать вывод
|
//TODO: Убрать все эти is_yn, is_zen и прочее и внутри плагинов регулировать вывод
|
||||||
@@ -291,8 +302,9 @@ function add_article_in_main_query( $query ) {
|
|||||||
//var_dump( get_option('layf_custom_url'));
|
//var_dump( get_option('layf_custom_url'));
|
||||||
if (is_admin()){
|
if (is_admin()){
|
||||||
if (get_query_var('author_name')){
|
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.'-->';
|
//echo '<!--'.$query->request.'-->';
|
||||||
|
return $query;
|
||||||
}else if (filter_input(INPUT_GET,'page') == 'wps_authors_page'){
|
}else if (filter_input(INPUT_GET,'page') == 'wps_authors_page'){
|
||||||
$query->set('post_type', array('profile_article','anew','yellow'));
|
$query->set('post_type', array('profile_article','anew','yellow'));
|
||||||
$query->set('posts_per_page', -1);
|
$query->set('posts_per_page', -1);
|
||||||
@@ -1060,7 +1072,7 @@ add_action('wp_enqueue_scripts', function() {
|
|||||||
// Добавляем стиль
|
// Добавляем стиль
|
||||||
|
|
||||||
$css_url = get_template_directory_uri() . '/assets/css/profile-style.css';
|
$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(
|
wp_enqueue_style(
|
||||||
'profile-style', // уникальный идентификатор
|
'profile-style', // уникальный идентификатор
|
||||||
@@ -2348,7 +2360,7 @@ add_action('wp_head', function() {
|
|||||||
if (!is_singular()) return;
|
if (!is_singular()) return;
|
||||||
|
|
||||||
$post_id = get_the_ID();
|
$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);
|
$og_image = esc_url($default_image);
|
||||||
|
|
||||||
@@ -6108,6 +6120,13 @@ add_filter( 'http_request_args',
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
166
inc/action-scheduler-functions.php
Normal file
166
inc/action-scheduler-functions.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
159
inc/action-scheduler-functions_15_10_25.php
Normal file
159
inc/action-scheduler-functions_15_10_25.php
Normal 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
124
inc/add_adv_checked.php
Normal 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>';
|
||||||
|
});
|
||||||
46
inc/add_adv_checked_advonly.php
Normal file
46
inc/add_adv_checked_advonly.php
Normal 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
74
inc/adfox_on.php
Normal 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
38
inc/admin/auto_check.php
Normal 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');
|
||||||
270
inc/admin/coauthors_filter.php
Normal file
270
inc/admin/coauthors_filter.php
Normal 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
807
inc/admin/lock-terms.php
Normal 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
271
inc/article-rss-feed.php
Normal 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(']]>', ']]>', $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']);
|
||||||
195
inc/generate_coauthors_cache.php
Normal file
195
inc/generate_coauthors_cache.php
Normal 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
363
inc/graphql.php
Normal 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
196
inc/journal_issue.php
Normal 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
20
inc/meta_keywords.php
Normal 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
0
inc/opensearch.php
Normal file
43
inc/popular-json.php
Normal file
43
inc/popular-json.php
Normal 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
222
inc/realtime-debug.php
Normal 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
511
inc/realtimesqllogger.php
Normal 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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
74
inc/replace_old_copyright.php
Normal file
74
inc/replace_old_copyright.php
Normal 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
24
inc/rossiya.php
Normal 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');
|
||||||
34
inc/schedule_async_post_processing.php
Normal file
34
inc/schedule_async_post_processing.php
Normal 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
221
inc/sql_result.php
Normal 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>
|
||||||
|
';
|
||||||
|
}
|
||||||
53
inc/test_action_scheduler.php
Normal file
53
inc/test_action_scheduler.php
Normal 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 );
|
||||||
|
|
||||||
159
inc/wp-cli-scheduler-commands.php
Normal file
159
inc/wp-cli-scheduler-commands.php
Normal 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' );
|
||||||
@@ -50,7 +50,8 @@
|
|||||||
<span class="footer__delimiter"></span>
|
<span class="footer__delimiter"></span>
|
||||||
<div class="footer__copyright">
|
<div class="footer__copyright">
|
||||||
<p class="d-block d-md-flex">
|
<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>
|
||||||
<p>
|
<p>
|
||||||
©<?= date("Y") ?> ИДР. Все права защищены.
|
©<?= date("Y") ?> ИДР. Все права защищены.
|
||||||
|
|||||||
@@ -51,13 +51,13 @@
|
|||||||
|
|
||||||
<?php get_template_part("template-parts/header/breadcrumbs") ?>
|
<?php get_template_part("template-parts/header/breadcrumbs") ?>
|
||||||
|
|
||||||
|
<!--[ zone 14 ]-->
|
||||||
<?php if( !wp_is_mobile() and is_home() ) : ?>
|
<?php if( !wp_is_mobile() and is_home() ) : ?>
|
||||||
<div style="margin-bottom: 18px; text-align: center;">
|
<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>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<!--[/ zone 14 ]-->
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
|
|
||||||
<?php //get_template_part("template-parts/home/news");
|
<?php //get_template_part("template-parts/home/news");
|
||||||
cached_get_template_part( 'template-parts/home/news' );?>
|
cached_get_template_part( 'template-parts/home/news' );?>
|
||||||
|
|
||||||
<div class="col-12 col-md-8 col-xl-6 float-left">
|
<div class="col-12 col-md-8 col-xl-6 float-left">
|
||||||
|
|
||||||
<?php //get_template_part("template-parts/home/main-item");
|
<?php get_template_part("template-parts/home/main-item");
|
||||||
cached_get_template_part( 'template-parts/home/main-item' );
|
//cached_get_template_part( 'template-parts/home/main-item' );
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php //get_template_part("template-parts/home/colon-item");
|
<?php get_template_part("template-parts/home/colon-item");
|
||||||
cached_get_template_part( 'template-parts/home/colon-item' );
|
//cached_get_template_part( 'template-parts/home/colon-item' );
|
||||||
?>
|
?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -134,9 +134,13 @@
|
|||||||
|
|
||||||
<ul class="news__list__top">
|
<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(); ?>
|
<?php $top_query->the_post(); ?>
|
||||||
|
|
||||||
|
|||||||
503
utils/acf-category.php
Normal file
503
utils/acf-category.php
Normal 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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user