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 Время жизни кеша в секундах
|
||||
* @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';
|
||||
|
||||
// Проверяем существование файла и его время жизни
|
||||
if ( file_exists($fcache) ){
|
||||
$cache_time = filemtime($fcache);
|
||||
$current_time = time();
|
||||
|
||||
// Если кеш не истек, отдаем его
|
||||
if ( ($current_time - $cache_time) < $ttl ) {
|
||||
echo file_get_contents($fcache);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$file_directory = dirname($fcache);
|
||||
|
||||
|
||||
ob_start();
|
||||
get_template_part($template, $name, $data );
|
||||
$content = ob_get_clean();
|
||||
|
||||
echo $content;
|
||||
|
||||
|
||||
if (!file_exists($file_directory)) {
|
||||
if (!wp_mkdir_p($file_directory)) {
|
||||
error_log('Не удалось создать директорию: ' . $file_directory);
|
||||
@@ -41,9 +46,9 @@ function cached_get_template_part( $template, $name = null, $data = [] ) {
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Функция очистки кеша
|
||||
function clear_template_cache ( $template ) {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
//ini_set('display_errors', 0);
|
||||
//error_reporting(E_ALL);
|
||||
//ini_set('display_errors', 1);
|
||||
//error_reporting(E_ERROR);
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
@@ -12,7 +12,11 @@ require "inc/cache-cleaner-home.php";
|
||||
require 'inc/article-rss-feed.php'; //rss ленты статей по датам
|
||||
require "inc/meta_keywords.php";
|
||||
require "inc/journal_issue.php"; //номера журналов
|
||||
require "inc/rank-description.php";
|
||||
require "inc/rank-description.php"; //подменяем дескрипшт от соцсетей
|
||||
require "inc/graphql.php"; //graphql
|
||||
|
||||
|
||||
require "inc/replace_old_copyright.php"; //подменяем фото для старых картинок с "плохим" копирайтом
|
||||
|
||||
//scheduler
|
||||
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/wp-cli-scheduler-commands.php";
|
||||
require "inc/adfox_on.php"; // управление adfox
|
||||
require "inc/generate_coauthors_cache.php"; // генерим кеш списка авторов
|
||||
}
|
||||
|
||||
include "inc/get_cached_alm.php";
|
||||
include "inc/graphql.php";
|
||||
|
||||
|
||||
if (is_admin()) {
|
||||
require "inc/add_adv_checked.php"; // рекламная статья
|
||||
require "inc/admin/auto_check.php"; // помечакм не отправлять в Дзен
|
||||
require "inc/admin/lock-terms.php"; // banned и keys
|
||||
require "inc/admin/coauthors_filter.php"; // фильтры авторов
|
||||
}
|
||||
|
||||
#api
|
||||
require "api/newstop.php"; // управление REST API
|
||||
require "api/coauthor.php"; // управление REST API
|
||||
|
||||
|
||||
|
||||
|
||||
//TODO: Убрать все эти is_yn, is_zen и прочее и внутри плагинов регулировать вывод
|
||||
@@ -291,8 +302,9 @@ function add_article_in_main_query( $query ) {
|
||||
//var_dump( get_option('layf_custom_url'));
|
||||
if (is_admin()){
|
||||
if (get_query_var('author_name')){
|
||||
$query->set('post_type', array('profile_article','anew','yellow'));
|
||||
//$query->set('post_type', array('profile_article','anew','yellow'));
|
||||
//echo '<!--'.$query->request.'-->';
|
||||
return $query;
|
||||
}else if (filter_input(INPUT_GET,'page') == 'wps_authors_page'){
|
||||
$query->set('post_type', array('profile_article','anew','yellow'));
|
||||
$query->set('posts_per_page', -1);
|
||||
@@ -1060,7 +1072,7 @@ add_action('wp_enqueue_scripts', function() {
|
||||
// Добавляем стиль
|
||||
|
||||
$css_url = get_template_directory_uri() . '/assets/css/profile-style.css';
|
||||
//$css_url = 'https://profile.ru//wp-content/themes/profile/assets/css/profile-style.css';
|
||||
$css_url = 'https://profile.ru//wp-content/themes/profile/assets/css/profile-style.css';
|
||||
|
||||
wp_enqueue_style(
|
||||
'profile-style', // уникальный идентификатор
|
||||
@@ -2348,7 +2360,7 @@ add_action('wp_head', function() {
|
||||
if (!is_singular()) return;
|
||||
|
||||
$post_id = get_the_ID();
|
||||
$default_image = 'https://cdn.profile.ru/wp-content/uploads/2023/07/profile_lazyload_hq.jpg'; // Заглушка в папке темы
|
||||
$default_image = 'https://cdn.profile.ru/wp-content/uploads/2026/01/zaglushka.jpg'; // Заглушка в папке темы
|
||||
|
||||
$og_image = esc_url($default_image);
|
||||
|
||||
@@ -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>
|
||||
<div class="footer__copyright">
|
||||
<p class="d-block d-md-flex">
|
||||
Информационное агентство "Деловой журнал "Профиль" зарегистрировано в Федеральной службе по надзору в сфере связи, информационных технологий и массовых коммуникаций. Свидетельство о государственной регистрации серии ИА № ФС 77 - 89668 от 23.06.2025
|
||||
Информационное агентство "Деловой журнал "Профиль" зарегистрировано в Федеральной службе по надзору в сфере связи, информационных технологий и массовых коммуникаций. Свидетельство о государственной регистрации серии ИА № ФС 77 - 89668 от 23.06.2025<br>
|
||||
Cвидетельство о регистрации электронного СМИ Эл NºФС77-73069 от 09 июня 2018 г.
|
||||
</p>
|
||||
<p>
|
||||
©<?= date("Y") ?> ИДР. Все права защищены.
|
||||
|
||||
@@ -51,13 +51,13 @@
|
||||
|
||||
<?php get_template_part("template-parts/header/breadcrumbs") ?>
|
||||
|
||||
|
||||
<!--[ zone 14 ]-->
|
||||
<?php if( !wp_is_mobile() and is_home() ) : ?>
|
||||
<div style="margin-bottom: 18px; text-align: center;">
|
||||
<?php get_template_part("template-parts/ad/revive/ad", "", [ "zone" => 12, "show_on_mobile" => false ]); ?>
|
||||
<?php get_template_part("template-parts/ad/revive/ad", "", [ "zone" => 14, "show_on_mobile" => false ]); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!--[/ zone 14 ]-->
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
<div class="col-12 col-md-8 col-xl-6 float-left">
|
||||
|
||||
<?php //get_template_part("template-parts/home/main-item");
|
||||
cached_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' );
|
||||
?>
|
||||
|
||||
<?php //get_template_part("template-parts/home/colon-item");
|
||||
cached_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' );
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -134,9 +134,13 @@
|
||||
|
||||
<ul class="news__list__top">
|
||||
|
||||
<?php if($top_query->have_posts()): ?>
|
||||
<?php
|
||||
$nntop = 0;
|
||||
if($top_query->have_posts()): ?>
|
||||
|
||||
<?php while ($top_query->have_posts()): ?>
|
||||
<?php while ($top_query->have_posts()):
|
||||
$nntop++;
|
||||
if ($nntop > 10){ break;}?>
|
||||
|
||||
<?php $top_query->the_post(); ?>
|
||||
|
||||
|
||||
503
utils/acf-category.php
Normal file
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