Przeprowadziłem dość obszerne badania na temat używania pre_get_posts na prawdziwych stronach i statycznych stronach tytułowych i wydaje się, że nie ma metody głupiego dowodu.
Najlepsza opcja, jaką znalazłem do tej pory, to post opublikowany przez @birgire na Stackoverflow . Przepisałem go na klasę demonstracyjną i uczyniłem kod nieco bardziej dynamicznym
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();
Działa to dobrze i strona zgodnie z oczekiwaniami przy użyciu mojej własnej funkcji stronicowania .
PROBLEMY:
Z powodu tej funkcji tracę integralność strony, w której znajdują się inne funkcje zależne od przechowywanego obiektu strony $post. $postprzed ustawieniem pętli na pierwszy post w pętli i $postustawieniem na ostatni post w pętli po pętli, co jest oczekiwane. Potrzebne jest $postustawienie bieżącego obiektu strony, tj. Obiektu, którego dotyczy zapytanie.
Ponadto, $wp_the_query->posti $wp_query->posttrzyma pierwszy post w pętli, a nie obiekt, którego dotyczy zapytanie, jak na normalnej stronie
Korzystam z poniższych ( poza klasą ), aby sprawdzić moje globale przed i po pętli
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
}
PRZED PĘTLĄ:
Przed pętlą problem jest częściowo rozwiązany przez ustawienie wartości $injectPageIntoLooptrue, która wstrzykuje obiekt strony jako pierwszą stronę w pętli. Jest to bardzo przydatne, jeśli chcesz wyświetlić informacje o stronie przed żądanymi postami, ale jeśli tego nie chcesz, to jesteś wkręcony.
Mogę rozwiązać problem przed pętlą bezpośrednio hakując globały, co tak naprawdę nie lubię. Podczepiam następującą metodę do wpmojej preGetPostsmetody
public function wp()
{
$page = get_post( $this->pageID );
$GLOBALS['wp_the_query']->post = $page;
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
$GLOBALS['post'] = $page;
}
i preGetPostsmetoda wewnętrzna
add_action( 'wp', [$this, 'wp'] );
Z tego $wp_the_query->post, $wp_query->posta $postwszystko trzyma przedmiot stronę.
PO PĘTLI
To jest mój duży problem, po pętli. Po zhakowaniu globałów za pomocą wphaka i metody,
$wp_the_query->posti$wp_query->postjest ustawiany z powrotem na pierwszy post w pętli, zgodnie z oczekiwaniami$postjest ustawiony na ostatni post w pętli.
Potrzebuję tylko, aby wszystkie trzy zostały ustawione z powrotem na obiekt zapytany / bieżący obiekt strony.
Próbowałem podpiąć wpmetodę do loop_enddziałania, co nie działa. Podłączenie wpmetody do get_sidebardziałania działa, ale jest już za późno.
add_action( 'get_sidebar', [$this, 'wp'] );
Uruchomienie printGlobals()bezpośrednio po pętli w szablonie potwierdza, że jako $wp_the_query->posti $wp_query->postwciąż są ustawione na pierwszy post i $postostatni post.
Mogę ręcznie dodać kod w wpmetodzie po pętli w szablonie, ale pomysł nie polega na bezpośredniej zmianie plików szablonów, ponieważ klasa powinna być przenoszona we wtyczce między motywami.
Czy istnieje jakiś właściwy sposób rozwiązać ten problem, w którym jeden bieg pre_get_postsna prawdziwej stronie i stronie statycznej przedniej i nadal zachować integralność $wp_the_query->post, $wp_query->postoraz $post( posiadające wymienione na odpytywany obiektu ) przed i po pętli.
EDYTOWAĆ
Wydaje się, że istnieje zamieszanie co do tego, czego potrzebuję i dlaczego go potrzebuję
Czego potrzebuję
Muszę zachować wartości $wp_the_query->post, $wp_query->posta $postpo drugiej stronie szablonu niezależnie, a ta wartość powinna być poszukiwana przedmiot. Na tym etapie, z kodem, który opublikowałem, wartości tych trzech zmiennych nie przechowują obiektu strony, ale raczej publikują obiekty postów w pętli. Mam nadzieję, że to dość jasne.
Opublikowałem kod, którego można użyć do przetestowania tych zmiennych
Dlaczego tego potrzebuję
Potrzebuję niezawodnego sposobu dodawania postów pre_get_postsdo szablonów stron i statycznych stron głównych bez zmiany pełnej funkcjonalności strony. Na tym etapie, gdy kod, o którym mowa, łamie moją funkcję nawigacyjną i powiązaną funkcję strony po pętli, w wyniku $postczego przechowuje „zły” obiekt postu.
Przede wszystkim nie chcę bezpośrednio zmieniać szablonów stron. Chcę mieć możliwość dodawania postów do szablonu strony bez ŻADNEJ modyfikacji tego szablonu
