Я провел довольно обширное исследование о том, как использовать pre_get_posts
на настоящих страницах и статических титульных страницах, и кажется, что нет никакого метода защиты от дурака.
Лучший вариант, который я нашел на сегодняшний день, был из сообщения, написанного @birgire в Stackoverflow . Я переписал его в демонстрационный класс и сделал код немного более динамичным
class PreGeTPostsForPages
{
/**
* @var string|int $pageID
* @access protected
* @since 1.0.0
*/
protected $pageID;
/**
* @var bool $injectPageIntoLoop
* @access protected
* @since 1.0.0
*/
protected $injectPageIntoLoop;
/**
* @var array $args
* @access protected
* @since 1.0.0
*/
protected $args;
/**
* @var int $validatedPageID
* @access protected
* @since 1.0.0
*/
protected $validatedPageID = 0;
/**
* Constructor
*
* @param string|int $pageID = NULL
* @param bool $injectPageIntoLoop = false
* @param array| $args = []
* @since 1.0.0
*/
public function __construct(
$pageID = NULL,
$injectPageIntoLoop = true,
$args = []
) {
$this->pageID = $pageID;
$this->injectPageIntoLoop = $injectPageIntoLoop;
$this->args = $args;
}
/**
* Private method validatePageID()
*
* Validates the page ID passed
*
* @since 1.0.0
*/
private function validatePageID()
{
$validatedPageID = filter_var( $this->pageID, FILTER_VALIDATE_INT );
$this->validatedPageID = $validatedPageID;
}
/**
* Public method init()
*
* This method is used to initialize our pre_get_posts action
*
* @since 1.0.0
*/
public function init()
{
// Load the correct actions according to the value of $this->keepPageIntegrity
add_action( 'pre_get_posts', [$this, 'preGetPosts'] );
}
/**
* Protected method pageObject()
*
* Gets the queried object to use that as page object
*
* @since 1.0.0
*/
protected function pageObject()
{
global $wp_the_query;
return $wp_the_query->get_queried_object();
}
/**
* Public method preGetPosts()
*
* This is our call back method for the pre_get_posts action.
*
* The pre_get_posts action will only be used if the page integrity is
* not an issue, which means that the page will be altered to work like a
* normal archive page. Here you have the option to inject the page object as
* first post through the_posts filter when $this->injectPageIntoLoop === true
*
* @since 1.0.0
*/
public function preGetPosts( \WP_Query $q )
{
// Make sure that we are on the main query and the desired page
if ( is_admin() // Only run this on the front end
|| !$q->is_main_query() // Only target the main query
|| !is_page( $this->validatedPageID ) // Run this only on the page specified
)
return;
// Remove the filter to avoid infinte loops
remove_filter( current_filter(), [$this, __METHOD__] );
// METHODS:
$this->validatePageID();
$this->pageObject();
$queryArgs = $this->args;
// Set default arguments which cannot be changed
$queryArgs['pagename'] = NULL;
// We have reached this point, lets do what we need to do
foreach ( $queryArgs as $key=>$value )
$q->set(
filter_var( $key, FILTER_SANITIZE_STRING ),
$value // Let WP_Query handle the sanitation of the values accordingly
);
// Set $q->is_singular to 0 to get pagination to work
$q->is_singular = false;
// FILTERS:
add_filter( 'the_posts', [$this, 'addPageAsPost'], PHP_INT_MAX );
add_filter( 'template_include', [$this, 'templateInclude'], PHP_INT_MAX );
}
/**
* Public callback method hooked to 'the_posts' filter
* This will inject the queried object into the array of posts
* if $this->injectPageIntoLoop === true
*
* @since 1.0.0
*/
public function addPageAsPost( $posts )
{
// Inject the page object as a post if $this->injectPageIntoLoop == true
if ( true === $this->injectPageIntoLoop )
return array_merge( [$this->pageObject()], $posts );
return $posts;
}
/**
* Public call back method templateInclude() for the template_include filter
*
* @since 1.0.0
*/
public function templateInclude( $template )
{
// Remove the filter to avoid infinte loops
remove_filter( current_filter(), [$this, __METHOD__] );
// Get the page template saved in db
$pageTemplate = get_post_meta(
$this->validatedPageID,
'_wp_page_template',
true
);
// Make sure the template exists before we load it, but only if $template is not 'default'
if ( 'default' !== $pageTemplate ) {
$locateTemplate = locate_template( $pageTemplate );
if ( $locateTemplate )
return $template = $locateTemplate;
}
/**
* If $template returned 'default', or the template is not located for some reason,
* we need to get and load the template according to template hierarchy
*
* @uses get_page_template()
*/
return $template = get_page_template();
}
}
$init = new PreGeTPostsForPages(
251, // Page ID
false,
[
'posts_per_page' => 3,
'post_type' => 'post'
]
);
$init->init();
Это работает хорошо и страница, как и ожидалось, используя мою собственную функцию нумерации страниц .
ПРОБЛЕМЫ:
Из-за этой функции я теряю целостность страницы, в которую входят другие функции, зависящие от объекта страницы, хранящегося в $post
. $post
before цикл установлен на первое сообщение в цикле и $post
установлен на последний пост в цикле после цикла, что и ожидается. То, что мне нужно, $post
это установить объект текущей страницы, то есть запрашиваемый объект.
Также $wp_the_query->post
и $wp_query->post
содержит первый пост в цикле, а не запрашиваемый объект, как на обычной странице
Я использую следующее ( за пределами моего класса ), чтобы проверить мои глобальные переменные до и после цикла
add_action( 'wp_head', 'printGlobals' );
add_action( 'wp_footer', 'printGlobals' );
function printGlobals()
{
$global_test = 'QUERIED OBJECT: ' . $GLOBALS['wp_the_query']->queried_object_id . '</br>';
$global_test .= 'WP_THE_QUERY: ' . $GLOBALS['wp_the_query']->post->ID . '</br>';
$global_test .= 'WP_QUERY: ' . $GLOBALS['wp_query']->post->ID . '</br>';
$global_test .= 'POST: ' . $GLOBALS['post']->ID . '</br>';
$global_test .= 'FOUND_POSTS: ' . $GLOBALS['wp_query']->found_posts . '</br>';
$global_test .= 'MAX_NUM_PAGES: ' . $GLOBALS['wp_query']->max_num_pages . '</br>';
?><pre><?php var_dump( $global_test ); ?></pre><?php
}
ДО ПЕТЛИ:
Перед циклом проблема частично решается установкой значения $injectPageIntoLoop
true, которое вставляет объект страницы как первую страницу в цикл. Это очень полезно, если вам нужно показать информацию о странице перед запрошенными постами, но если вы этого не хотите, вы облажались.
Я могу решить проблему до начала цикла, напрямую взломав глобалы, что мне не очень нравится. Я подключаю следующий метод wp
внутрь моего preGetPosts
метода
public function wp()
{
$page = get_post( $this->pageID );
$GLOBALS['wp_the_query']->post = $page;
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
$GLOBALS['post'] = $page;
}
и внутри preGetPosts
метод
add_action( 'wp', [$this, 'wp'] );
Отсюда $wp_the_query->post
, $wp_query->post
и $post
все держит объект страницы.
ПОСЛЕ ПЕТЛИ
Это где моя большая проблема, после цикла. После взлома глобалов через wp
крючок и метод,
$wp_the_query->post
и$wp_query->post
возвращается к первому сообщению в цикле, как и ожидалось$post
устанавливается на последний пост в цикле.
Что мне нужно, так это то, что все три возвращаются к запрашиваемому объекту / объекту текущей страницы.
Я попытался подключить wp
метод к loop_end
действию, который не работает. Привязывание wp
метода к get_sidebar
действию работает, но уже слишком поздно.
add_action( 'get_sidebar', [$this, 'wp'] );
Запуск printGlobals()
непосредственно после цикла в шаблоне подтверждает, что as $wp_the_query->post
и $wp_query->post
все еще установлены для первого и $post
последнего сообщения.
Я могу вручную добавить код внутри wp
метода после цикла внутри шаблона, но идея не в том, чтобы изменять файлы шаблона напрямую, поскольку класс должен передаваться в плагине между темами.
Есть ли надлежащим образом решить эту проблему , когда один прогон pre_get_posts
на реальной странице и статической первой страницы и до сих пор сохранить целостность $wp_the_query->post
, $wp_query->post
и $post
( имея те , которые в запрашиваемом объекта ) до и после цикла.
РЕДАКТИРОВАТЬ
Кажется, есть путаница в том, что мне нужно и зачем мне это нужно
Что мне нужно
Мне нужно , чтобы сохранить значения $wp_the_query->post
, $wp_query->post
и $post
по шаблону , независимо, и это значение должно быть запрашиваемый объект. На этом этапе, с кодом, который я разместил, значения этих трех переменных не содержат объект страницы, а скорее размещают объекты сообщений в цикле. Я надеюсь, что это достаточно ясно.
Я разместил код, который вы можете использовать для проверки этих переменных
Зачем мне это нужно
Мне нужен надежный способ добавления сообщений через pre_get_posts
шаблоны страниц и статические титульные страницы без изменения полной функциональности страницы. На данном этапе, поскольку рассматриваемый код стоит, он нарушает мою функциональность хлебных крошек и связанную страницу после цикла, из-за $post
которого содержится «неправильный» объект post.
Больше всего я не хочу изменять шаблоны страниц напрямую. Я хочу , чтобы иметь возможность добавлять сообщения в шаблон страницы без ЛЮБЫХ изменений в шаблон
источник
Ответы:
Я, наконец, получил его работать, но не с кодом в моем вопросе. Я полностью отказался от всей этой идеи и возобновил движение в новом направлении.
НОТА:
Если кто-нибудь когда-нибудь сможет разобраться с вопросами в моем вопросе, не стесняйтесь отправить ответ. Кроме того, если у вас есть какие-либо другие решения, не стесняйтесь опубликовать ответ.
ПЕРЕРАБОТАННЫЙ КЛАСС И РЕШЕНИЕ:
То, что я пытался сделать здесь, это использовать постинжекцию, а не полностью изменять основной запрос и зацикливаться на всех вышеперечисленных проблемах, включая (а) прямое изменение глобальных переменных, (б) столкновение с проблемой глобальной ценности и (в) переназначение шаблонов страниц.
При использовании после инъекции я могу сохранить полную целостность поста, поэтому
$wp_the_query->post
,$wp_query->post
,$posts
и$post
остаются постоянными на протяжении всего шаблона. Каждая из этих переменных ссылается на текущий объект страницы (как в случае с истинными страницами). Таким образом, такие функции, как хлебные крошки, знают, что текущая страница является настоящей страницей, а не каким-то архивом.Мне пришлось немного изменить основной запрос ( через фильтры и действия ), чтобы скорректировать нумерацию страниц, но мы к этому придем.
ПОСЛЕ ИНЖЕКЦИОННОГО ЗАПРОСА
Чтобы выполнить публикацию сообщений, я использовал пользовательский запрос, чтобы вернуть сообщения, необходимые для внедрения. Я также использовал
$found_pages
свойство пользовательского запроса, чтобы настроить свойство основного запроса, чтобы обеспечить разбиение на страницы, работающее из основного запроса. Сообщения вводятся в основной запрос черезloop_end
действие.Чтобы сделать пользовательский запрос доступным и пригодным для использования вне класса, я ввел несколько действий.
Крючки для нумерации страниц, чтобы подключить функции нумерации страниц:
pregetgostsforgages_before_loop_pagination
pregetgostsforgages_after_loop_pagination
Пользовательский счетчик, который считает сообщения в цикле. Эти действия можно использовать для изменения способа отображения сообщений в цикле в соответствии с номером сообщения.
pregetgostsforgages_counter_before_template_part
pregetgostsforgages_counter_after_template_part
Общий хук для доступа к объекту запроса и текущему объекту публикации
pregetgostsforgages_current_post_and_object
Эти хуки дают вам полный опыт, так как вам не нужно ничего менять в самом шаблоне страницы, что было моим первоначальным намерением с самого начала. Страница может быть полностью изменена из плагина или файла функции, что делает это решение очень динамичным.
Я также использовал
get_template_part()
для того, чтобы загрузить часть шаблона, которая будет использоваться для отображения сообщений. Большинство тем сегодня используют части шаблона, что делает это очень полезным в классе. Если ваша тема использованиеcontent.php
, вы можете просто передатьcontent
в$templatePart
нагрузкуcontent.php
.Если вам нужна поддержка формата поста для частей шаблона, это просто - вы можете просто перейти
content
к$templatePart
и установить$postFormatSupport
наtrue
. В результате часть шаблонаcontent-video.php
будет загружена для сообщения с форматом сообщенияvideo
.ОСНОВНОЙ ВОПРОС
Следующие изменения были внесены в основной запрос с помощью соответствующих фильтров и действий:
Чтобы разбить основной запрос на страницы:
Значение
$found_posts
свойства запроса инжектора передается значению основного объекта запроса черезfound_posts
фильтр.Значение параметра, передаваемого пользователем
posts_per_page
, устанавливается через основной запросpre_get_posts
.$max_num_pages
рассчитывается с использованием количества постов в$found_posts
иposts_per_page
. Посколькуis_singular
это верно для страниц, оно запрещает установкуLIMIT
предложения. Просто установкаis_singular
в false привела к нескольким проблемам, поэтому я решил установитьLIMIT
предложение черезpost_limits
фильтр. Я держалoffset
вLIMIT
наборе положение , чтобы0
избежать 404 на страницах с пагинацией включена.Это заботится о нумерации страниц и любой проблеме, которая может возникнуть после пост-инъекции.
ОБЪЕКТ СТРАНИЦЫ
Текущий объект страницы доступен для отображения в виде сообщения с помощью цикла по умолчанию на странице, отдельно и поверх добавленных сообщений. Если вам это не нужно, вы можете просто установить
$removePageFromLoop
значение true, и это будет скрывать содержимое страницы от отображения.На данном этапе, я использую CSS , чтобы скрыть объект страницы через
loop_start
иloop_end
действия, я не могу найти другой способ сделать это. Недостатком этого метода является то, что все, что связано сthe_post
хуком действия внутри основного запроса, также будет скрыто.КЛАСС
PreGetPostsForPages
Класс может быть улучшен , и должны быть надлежащим образом в пространстве имен , а также. Хотя вы можете просто добавить это в файл функций вашей темы, было бы лучше добавить это в собственный плагин.Используйте, изменяйте и злоупотребляйте по своему усмотрению. Код хорошо прокомментирован, поэтому его легко отслеживать и корректировать.
ИСПОЛЬЗОВАНИЕ
Теперь вы можете инициировать класс ( также в вашем плагине или файле функций ), как показано ниже, чтобы настроить таргетинг на страницу с идентификатором 251, на котором мы будем показывать 2 поста на странице от
post
типа поста.ДОБАВЛЕНИЕ СТРАНИЦЫ И ТАМОЖЕННОГО СТИЛИРОВАНИЯ
Как я упоминал ранее, в запросе инжектора есть несколько действий для добавления нумерации страниц и / или пользовательских стилей.
В следующем примере я добавил разбиение на страницы после цикла, используя мою собственную функцию разбиения на страницы из связанного ответа . Кроме того, используя свой пользовательский счетчик, я добавил
<div>
к, чтобы отображать мои сообщения в двух столбцах.Вот действия, которые я использовал
Обратите внимание, что разбиение на страницы задается основным запросом, а не запросом инжектора, поэтому встроенные функции, такие как,
the_posts_pagination()
также должны работать.Это конечный результат
СТАТИЧЕСКИЕ ПЕРЕДНИЕ СТРАНИЦЫ
На статических титульных страницах все работает, как и ожидалось, вместе с моей функцией нумерации страниц, не требуя каких-либо дополнительных изменений
ВЫВОД
Это может показаться большим накладным расходом, и это может быть так, но профессионал перевешивает большое время мошенника.
BIG PRO'S
Вам не нужно каким-либо образом изменять шаблон страницы для конкретной страницы. Это делает все динамичным и может быть легко перенесено между темами без каких-либо изменений в коде, если все сделано в плагине.
Самое большее, вам нужно создать только
content.php
часть шаблона в вашей теме, если у вашей темы ее еще нет.Любая нумерация страниц, которая работает с основным запросом, будет работать на странице без каких-либо изменений или каких-либо дополнительных действий из запроса, передаваемого в функцию.
Есть больше профессионалов, о которых я не могу думать сейчас, но это важные.
источник