Verhindere, dass comments_template () comments.php lädt

Ich entwickle ein WordPress Theme mit einer Template Engine. Ich möchte, dass mein Code so gut wie möglich mit der WP-corefunktionalität kompatibel ist.

Etwas Kontext zuerst

Mein erstes Problem bestand darin, eine Möglichkeit zu finden , die Vorlage ausgehend von einer WP-Abfrage aufzulösen . Ich triggerse das mit einer meiner Bibliotheken, Brain \ Hierarchy .

In Bezug auf get_template_part() und andere functionen, die Partials wie get_header() , get_footer() und ähnliches get_footer() , war es ziemlich einfach, Wrapper in die get_footer() zu schreiben.

Das Thema

Mein Problem ist jetzt, wie man Kommentare Vorlage lädt.

WordPress function comments_template() ist eine ~ 200 Zeilen function, die viele Dinge tut, die ich auch für maximale corekompatibilität machen möchte.

Sobald ich jedoch comments_template() aufruft, ist eine Datei require d, es ist die erste von:

  • die Datei in der Konstanten COMMENTS_TEMPLATE , falls definiert
  • comments.php im Theme-Ordner, falls gefunden
  • /theme-compat/comments.php in WP enthält Ordner als letztes Ausweichsystem

Kurz gesagt, es gibt keine Möglichkeit zu verhindern, dass die function eine PHP-Datei lädt, was für mich nicht wünschenswert ist, weil ich meine Vorlagen rendern muss und nicht einfach require .

Aktuelle Lösung

Im Moment liefere ich eine leere comments.php Datei und ich benutze 'comments_template' Filter Hook, um zu wissen, welche WordPress Vorlage geladen werden soll, und benutze das Feature von meiner Template Engine, um die Vorlage zu laden.

Etwas wie das:

 function engineCommentsTemplate($myEngine) { $toLoad = null; // this will hold the template path $tmplGetter = function($tmpl) use(&$toLoad) { $toLoad = $tmpl; return $tmpl; }; // late priority to allow filters attached here to do their job add_filter('comments_template', $tmplGetter, PHP_INT_MAX); // this will load an empty comments.php file I ship in my theme comments_template(); remove_filter('comments_template', $tmplGetter, PHP_INT_MAX); if (is_file($toLoad) && is_readable($toLoad)) { return $myEngine->render($toLoad); } return ''; } 

Die Frage

Das funktioniert, ist Core-kompatibel, aber … gibt es eine Möglichkeit, es zum Laufen zu bringen, ohne eine leere comments.php ?

Weil ich es nicht mag.

Solutions Collecting From Web of "Verhindere, dass comments_template () comments.php lädt"

Nicht sicher, dass die folgende Lösung besser ist als die Lösung in OP, sagen wir einfach, ist eine alternative, wahrscheinlich mehr hackische Lösung.

Ich denke, dass Sie eine PHP-Ausnahme verwenden können, um die WordPress-Ausführung zu stoppen, wenn 'comments_template' Filter 'comments_template' angewendet wird.

Sie können eine benutzerdefinierte Ausnahmeklasse als DTO zum Übertragen der Vorlage verwenden.

Dies ist ein Entwurf für die Ausnahme:

 class CommentsTemplateException extends \Exception { protected $template; public static function forTemplate($template) { $instance = new static(); $instance->template = $template; return $instance; } public function template() { return $this->template; } } 

Wenn diese Ausnahmeklasse verfügbar ist, wird Ihre function zu:

 function engineCommentsTemplate($myEngine) { $filter = function($template) { throw CommentsTemplateException::forTemplate($template); }; try { add_filter('comments_template', $filter, PHP_INT_MAX); // this will throw the excption that makes `catch` block run comments_template(); } catch(CommentsTemplateException $e) { return $myEngine->render($e->template()); } finally { remove_filter('comments_template', $filter, PHP_INT_MAX); } } 

Der finally Block benötigt PHP 5.5+.

functioniert auf die gleiche Weise und erfordert keine leere Vorlage.

Ich habe schon früher damit gerungen und meine Lösung war – sie kann sich selbst herausfordern, wenn sie Datei benötigt, solange sie nichts tut .

Hier ist der relevante Code von meinem Meadow-Template-Projekt :

 public function comments_template( \Twig_Environment $env, $context, $file = 'comments.twig', $separate_comments = false ) { try { $env->loadTemplate( $file ); } catch ( \Twig_Error_Loader $e ) { ob_start(); comments_template( '/comments.php', $separate_comments ); return ob_get_clean(); } add_filter( 'comments_template', array( $this, 'return_blank_template' ) ); comments_template( '/comments.php', $separate_comments ); remove_filter( 'comments_template', array( $this, 'return_blank_template' ) ); return twig_include( $env, $context, $file ); } public function return_blank_template() { return __DIR__ . '/blank.php'; } 

Ich lasse comments_template() gehen durch die Bewegungen, um Globals und solche einzurichten, aber füttern Sie es leer PHP-Datei zu require und zu meiner tatsächlichen Twig Vorlage für die Ausgabe zu bewegen.

Beachten Sie, dass dies den anfänglichen comments_template() -Aufruf abfangen muss, was ich tun kann, da meine Twig-Vorlage die Zwischenabstraktion und nicht die eigentliche PHP-function aufruft.

Während ich noch leere Dateien dafür verschicken muss, mache ich das in der Bibliothek und das Implementieren des Themas muss sich überhaupt nicht darum kümmern.

Lösung: Verwenden Sie eine temporäre Datei mit einem eindeutigen Dateinamen

Nach vielen Sprüngen und Kriechen in die dreckigsten Ecken von PHP habe ich die Frage umformuliert:

Wie kann man PHP dazu bringen, TRUE für file_exists( $file ) ?

wie der Code im core gerade ist

 file_exists( apply_filters( 'comments_template', $template ) ) 

Dann wurde die Frage schneller getriggers:

 $template = tempnam( __DIR__, '' ); 

und das ist es. Vielleicht wäre es besser, stattdessen wp_upload_dir() verwenden:

 $uploads = wp_upload_dir(); $template = tempname( $uploads['basedir'], '' ); 

Eine andere Option könnte sein, get_temp_dir() zu verwenden, die get_temp_dir() WP_TEMP_DIR . Hinweis: Es fällt seltsamerweise auf /tmp/ so Dateien werden nicht zwischen Neustarts erhalten, die /var/tmp/ würde. Man kann am Ende einen einfachen Stringvergleich durchführen und den Rückgabewert überprüfen und dann beheben, falls es benötigt wird – was in diesem Fall nicht der Fall ist:

 $template = tempname( get_temp_dir(), '' ) 

Um nun schnell zu testen, ob Fehler für eine temporäre Datei ohne Inhalt ausgetriggers wurden:

 < ?php error_reporting( E_ALL ); $template = tempnam( __DIR__, '' ); var_dump( $template ); require $template; 

Und: Keine Fehler → arbeiten.

EDIT: Wie @toscho in den Kommentaren darauf hingewiesen hat, gibt es noch einen besseren Weg, es zu tun:

 $template = tempnam( trailingslashit( untrailingslashit( sys_get_temp_dir() ) ), 'comments.php' ); 

Hinweis: Laut einer Anmerkung eines Benutzers zu php.net-Dokumenten unterscheidet sich das Verhalten sys_get_temp_dir() zwischen den Systemen. Daher wird der nachfolgende Schrägstrich entfernt und dann erneut hinzugefügt. Da der coreerrors # 22267 behoben wurde, sollte dies jetzt auch auf Win / IIS Servern funktionieren.

Ihre refaktorierte function (nicht getestet):

 function engineCommentsTemplate( $engine ) { $template = null; $tmplGetter = function( $original ) use( &$template ) { $template = $original; return tempnam( trailingslashit( untrailingslashit( sys_get_temp_dir() ) ), 'comments.php' ); }; add_filter( 'comments_template', $tmplGetter, PHP_INT_MAX ); comments_template(); remove_filter( 'comments_template', $tmplGetter, PHP_INT_MAX ); if ( is_file( $template ) && is_readable( $template ) ) { return $engine->render( $template ); } return ''; } 

Bonus Nr.1: tmpfile() gibt NULL . Ja wirklich.

Bonus Nr.2: file_exists( __DIR__ ) gibt TRUE . Ja, wirklich ... falls du es vergessen hast.

^ Dies führt zu einem tatsächlichen Fehler im WP-core.


Um anderen zu helfen, in den Explorer-Modus zu gehen und diese (schlecht zu undokumentierten Stücke) zu finden, werde ich schnell zusammenfassen, was ich versucht habe:

Versuch 1: Temporäre Datei im Speicher

Der erste Versuch, den ich gemacht habe, war, einen Stream zu einer temporären Datei zu erstellen, mit php://temp . Aus den PHP-Dokumenten:

Der einzige Unterschied zwischen den beiden besteht darin, dass php://memory seine Daten immer im Speicher speichert, während php://temp eine temporäre Datei verwendet, sobald die Menge der gespeicherten Daten ein vordefiniertes Limit erreicht (der Standardwert ist 2 MB). Der Speicherort dieser temporären Datei wird auf die gleiche Weise wie die function sys_get_temp_dir() .

Der Code:

 $handle = fopen( 'php://temp', 'r+' ); fwrite( $handle, 'foo' ); rewind( $handle ); var_dump( file_exist( stream_get_contents( $handle, 5 ) ); 

Findet: Nein, funktioniert nicht.

Versuch 2: Verwenden Sie eine temporäre Datei

Da ist tmpfile() , also warum nicht benutzen ?!

 var_dump( file_exists( tmpfile() ) ); // boolean FALSE 

Ja, so viel zu dieser Abkürzung.

Versuch 3: Verwenden Sie einen benutzerdefinierten Stream-Wrapper

Als nächstes dachte ich, ich könnte einen eigenen Stream-Wrapper stream_wrapper_register() und ihn mit stream_wrapper_register() registrieren . Dann könnte ich eine virtuelle Vorlage aus diesem Stream verwenden, um Core zu glauben, dass wir eine Datei haben. Beispielcode unten (Ich habe bereits die gesamte class gelöscht und der Verlauf hat nicht genügend Schritte ...)

 class TemplateStreamWrapper { public $context; public function stream_open( $path, $mode, $options, &$opened ) { // return boolean } } stream_wrapper_register( 'vt://comments', 'TemplateStreamWrapper' ); // … etc. … 

Auch dies gab NULL für file_exists() .


Getestet mit PHP 5.6.20

Als @AlainSchlesser vorgeschlagen hat, der Route zu folgen (und weil mich nicht funktionierende Dinge immer stören), habe ich versucht, einen Stream Wrapper für virtuelle Dateien zu erstellen. Ich konnte es nicht lösen (lesen: lesen die Rückgabewerte auf den Dokumenten) allein, aber triggerse es mit Hilfe von @HPierce auf SO .

 class VirtualTemplateWrapper { public $context; public function stream_open( $path, $mode, $options, &$opened_path ) { return true; } public function stream_read( $count ) { return ''; } public function stream_eof() { return ''; } public function stream_stat() { # $user = posix_getpwuid( posix_geteuid() ); $data = [ 'dev' => 0, 'ino' => getmyinode(), 'mode' => 'r', 'nlink' => 0, 'uid' => getmyuid(), 'gid' => getmygid(), #'uid' => $user['uid'], #'gid' => $user['gid'], 'rdev' => 0, 'size' => 0, 'atime' => time(), 'mtime' => getlastmod(), 'ctime' => FALSE, 'blksize' => 0, 'blocks' => 0, ]; return array_merge( array_values( $data ), $data ); } public function url_stat( $path, $flags ) { return $this->stream_stat(); } } 

Sie müssen nur die neue class als neues Protokoll registrieren:

 add_action( 'template_redirect', function() { stream_wrapper_register( 'virtual', 'VirtualTemplateWrapper' ); }, 0 ); 

Dies erlaubt dann eine virtuelle (nicht existierende) Datei zu erstellen:

 $template = fopen( "virtual://comments", 'r+' ); 

Ihre function kann dann refaktorisiert werden für:

 function engineCommentsTemplate( $engine ) { $replacement = null; $virtual = fopen( "virtual://comments", 'r+' ); $tmplGetter = function( $original ) use( &$replacement, $virtual ) { $replacement = $original; return $virtual; }; add_filter( 'comments_template', $tmplGetter, PHP_INT_MAX ); comments_template(); remove_filter( 'comments_template', $tmplGetter, PHP_INT_MAX ); // As the PHP internals are quite unclear: Better safe then sorry unset( $virtual ); if ( is_file( $replacement ) && is_readable( $replacement ) ) { return $engine->render( $replacement ); } return ''; } 

file_exists() check-in-core file_exists() TRUE zurückgibt und die require $file keinen Fehler file_exists() .

Ich muss feststellen, dass ich ziemlich glücklich bin, wie sich das herauskristallisiert hat, da es bei Komponententests sehr hilfreich sein könnte.