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
. $post
przed ustawieniem pętli na pierwszy post w pętli i $post
ustawieniem na ostatni post w pętli po pętli, co jest oczekiwane. Potrzebne jest $post
ustawienie bieżącego obiektu strony, tj. Obiektu, którego dotyczy zapytanie.
Ponadto, $wp_the_query->post
i $wp_query->post
trzyma 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 $injectPageIntoLoop
true, 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 wp
mojej preGetPosts
metody
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 preGetPosts
metoda wewnętrzna
add_action( 'wp', [$this, 'wp'] );
Z tego $wp_the_query->post
, $wp_query->post
a $post
wszystko trzyma przedmiot stronę.
PO PĘTLI
To jest mój duży problem, po pętli. Po zhakowaniu globałów za pomocą wp
haka i metody,
$wp_the_query->post
i$wp_query->post
jest ustawiany z powrotem na pierwszy post w pętli, zgodnie z oczekiwaniami$post
jest 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ąć wp
metodę do loop_end
działania, co nie działa. Podłączenie wp
metody do get_sidebar
dział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->post
i $wp_query->post
wciąż są ustawione na pierwszy post i $post
ostatni post.
Mogę ręcznie dodać kod w wp
metodzie 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_posts
na prawdziwej stronie i stronie statycznej przedniej i nadal zachować integralność $wp_the_query->post
, $wp_query->post
oraz $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->post
a $post
po 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_posts
do 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 $post
czego 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
źródło
Odpowiedzi:
W końcu udało mi się go uruchomić, ale nie z kodem w moim pytaniu. Całkowicie zrzuciłem cały ten pomysł i zacząłem iść w nowym kierunku.
UWAGA:
Jeśli ktokolwiek jest w stanie rozwiązać problemy w moim pytaniu, prosimy o odpowiedź. Ponadto, jeśli masz jakieś inne rozwiązania, możesz opublikować odpowiedź.
ODNOWIONA KLASA I ROZWIĄZANIE:
To, co próbowałem tutaj zrobić, to użyć post zastrzyku, zamiast całkowicie zmienić główne zapytanie i utknąć we wszystkich powyższych kwestiach, w tym (a) bezpośrednio zmieniając globały, (b) napotykając problem globalnej wartości oraz (c) ponowne przypisywanie szablonów stron.
Za pomocą iniekcji, jestem w stanie utrzymać pełną integralność wpisu, więc
$wp_the_query->post
,$wp_query->post
,$posts
i$post
pobyt stały w całym szablonie. Każda z tych zmiennych odwołuje się do bieżącego obiektu strony (jak w przypadku prawdziwych stron). W ten sposób funkcje takie jak bułka tarta wiedzą, że bieżąca strona jest prawdziwą stroną, a nie jakimś archiwum.Musiałem jednak nieco zmienić główne zapytanie ( poprzez filtry i działania ), aby dostosować się do stronicowania, ale do tego dojdziemy.
ZAPYTANIE PO WTRYSKU
Aby wykonać post wstrzyknięcie, użyłem niestandardowego zapytania, aby zwrócić posty potrzebne do wstrzyknięcia. Użyłem również właściwości niestandardowego zapytania,
$found_pages
aby dostosować właściwość głównego zapytania, aby podział na strony działał z głównego zapytania. Posty są wstrzykiwane do głównego zapytania poprzezloop_end
akcję.Aby niestandardowe zapytanie było dostępne i użyteczne poza klasą, wprowadziłem kilka akcji.
Haczyki stronicowania w celu zaczepienia funkcji stronicowania:
pregetgostsforgages_before_loop_pagination
pregetgostsforgages_after_loop_pagination
Niestandardowy licznik zliczający posty w pętli. Tych działań można użyć do zmiany sposobu wyświetlania postów w pętli zgodnie z numerem postu.
pregetgostsforgages_counter_before_template_part
pregetgostsforgages_counter_after_template_part
Hak ogólny, aby uzyskać dostęp do obiektu zapytania i bieżącego obiektu postu
pregetgostsforgages_current_post_and_object
Te zaczepy zapewniają całkowitą swobodę rąk, ponieważ nie trzeba niczego zmieniać w samym szablonie strony, co było moją pierwotną intencją od samego początku. Stronę można całkowicie zmienić z wtyczki lub pliku funkcji, co czyni to rozwiązanie bardzo dynamicznym.
Użyłem również
get_template_part()
w celu załadowania części szablonu, która będzie używana do wyświetlania postów. Obecnie większość motywów korzysta z części szablonu, co czyni je bardzo przydatnymi w klasie. Jeśli Twoje zastosowań tematycznychcontent.php
, można po prostu przejśćcontent
się$templatePart
do obciążeniacontent.php
.Jeśli potrzebujesz obsługi formatu pocztowego dla części szablonu, jest to łatwe - możesz po prostu przejść
content
do$templatePart
i ustawić$postFormatSupport
natrue
. W rezultacie część szablonucontent-video.php
zostanie załadowana dla posta w formacie postuvideo
.GŁÓWNE ZAPYTANIE
W głównym zapytaniu wprowadzono następujące zmiany poprzez odpowiednie filtry i akcje:
Aby paginować główne zapytanie:
Wartość
$found_posts
właściwości zapytania iniektora jest przekazywana do właściwości głównego obiektu zapytania przezfound_posts
filtr.Wartość parametru przekazanego przez użytkownika
posts_per_page
jest ustawiana na zapytanie główne poprzezpre_get_posts
.$max_num_pages
jest obliczany na podstawie liczby postów w$found_posts
iposts_per_page
. Ponieważis_singular
jest to prawda na stronach, hamujeLIMIT
ustawianie klauzuli. Samo ustawienie wartościis_singular
false spowodowało kilka problemów, więc postanowiłem ustawićLIMIT
klauzulę przezpost_limits
filtr. Ciągleoffset
zLIMIT
zestawem klauzula0
uniknąć 404 jest na stronach z paginacji włączone.To zajmuje się paginacją i wszelkimi problemami, które mogą wyniknąć z wstrzyknięcia.
OBIEKT STRONY
Bieżący obiekt strony jest dostępny do wyświetlania jako post za pomocą domyślnej pętli na stronie, osobnej i na górze wstrzykniętych postów. Jeśli nie jest to potrzebne, możesz po prostu ustawić wartość
$removePageFromLoop
true, a to ukryje zawartość strony przed wyświetlaniem.Na tym etapie używam CSS do ukrywania obiektu strony poprzez akcje
loop_start
i,loop_end
ponieważ nie mogę znaleźć innego sposobu na zrobienie tego. Minusem tej metody jest to, że wszystko, co podpięte jest dothe_post
haka akcji w głównym zapytaniu, również zostanie ukryte.KLASA
PreGetPostsForPages
Klasa może zostać ulepszone i powinny być prawidłowo przestrzeni nazw, jak również. Chociaż możesz po prostu upuścić to w pliku funkcji motywu, lepiej byłoby upuścić to w niestandardowej wtyczce.Używaj, modyfikuj i nadużywaj według własnego uznania. Kod jest dobrze skomentowany, więc powinno być łatwe do naśladowania i dostosowania
STOSOWANIE
Możesz teraz zainicjować klasę ( także w pliku wtyczki lub pliku funkcji ) w następujący sposób, aby kierować reklamy na stronę o identyfikatorze 251, na której pokażemy 2 posty na stronę z danego
post
typu.DODAWANIE PAGINACJI I STYLIZACJI NIESTANDARDOWEJ
Jak wspomniałem wcześniej, w zapytaniu wtryskiwacza jest kilka akcji w celu dodania stronicowania i / lub niestandardowego stylu.
W poniższym przykładzie dodałem paginację po pętli, używając własnej funkcji paginacji z połączonej odpowiedzi . Ponadto, używając mojego niestandardowego licznika, dodałem
<div>
aby wyświetlić moje posty w dwóch kolumnach.Oto działania, których użyłem
Zauważ, że podział na strony jest ustawiany przez zapytanie główne, a nie zapytanie iniektora, więc wbudowane funkcje, takie jak
the_posts_pagination()
powinny również działać.To jest wynik końcowy
STATYCZNE STRONY PRZEDNIE
Wszystko działa zgodnie z oczekiwaniami na statycznych stronach głównych wraz z moją funkcją paginacji, bez konieczności jakiejkolwiek dalszej modyfikacji.
WNIOSEK
Może się to wydawać dużym obciążeniem i może być, ale profesjonaliści przeważają nad wielkim czasem oszustwa.
BIG PRO'S
Nie musisz w żaden sposób zmieniać szablonu strony dla konkretnej strony. To sprawia, że wszystko jest dynamiczne i może być łatwo przenoszone między motywami bez modyfikacji kodu, o ile wszystko odbywa się we wtyczce.
Co najwyżej musisz utworzyć
content.php
część szablonu w motywie, tylko jeśli Twój motyw jeszcze go nie ma.Każda podział na strony, który działa na głównym zapytaniu, będzie działał na stronie bez żadnych zmian ani żadnych dodatkowych informacji z zapytania przekazywanego do funkcji.
Jest więcej pro, o których nie mogę teraz myśleć, ale to są te ważne.
źródło