Intereting Posts
Расширенные пользовательские поля комментариев WP Meta Query на глубине 2 Как получить URI каталога темы? Лучший способ иметь индивидуальный пользовательский раздел для каждого пользователя Расширенные пользовательские поля из оператора категории – IF Обновление WordPress 4.7.5 до 4.8, вызванное ошибкой datepicker Встреча «Неправильный нон. Действие запретилось "при попытке изменить роль пользователя и не удалось отправить через WP Admin Отображение ссылок на все сообщения той же категории на странице сообщений Автогенерированная выдержка с коротким кодом и дополнительная кнопка / текстовая ссылка Множественная база базового слизняка на странице архива категорий Есть ли ограничение на приоритет? функции разбиения на страницы не работают Разрешить создание только новых подстраниц 3 модератора, чтобы одобрить комментарий Добавить строку в файл .pot с помощью Poedit

Предотвратить функцию comments_template () для загрузки comments.php

Я разрабатываю тему WordPress с помощью механизма шаблонов. Я хочу, чтобы мой код был максимально совместим с функциональными возможностями WP.

В некотором контексте

Моя первая проблема заключалась в том, чтобы найти способ разрешения шаблона, начиная с запроса WP. Я решил, что с помощью моей библиотеки Brain \ Hierarchy .

Что касается get_template_part() и других функций, которые загружают частичные get_header() такие как get_header() , get_footer() и т. Д., Было довольно легко написать оболочку для частичной функциональности шаблона.

Проблема

Моя проблема заключается в том, как загрузить шаблон комментариев.

Функция WordPress comments_template() – это функция, состоящая из 200 строк, которая делает много вещей, что я также хочу сделать для максимальной совместимости ядра.

Однако, как только я вызываю comments_template() , для файла require d, он является первым из:

  • файл в константе COMMENTS_TEMPLATE , если он определен
  • comments.php в папке темы, если найдено
  • /theme-compat/comments.php в WP включает папку в качестве последней резервной копии

Короче говоря, не существует способа предотвратить загрузку файла PHP, что нежелательно для меня, потому что мне нужно отображать мои шаблоны, а не просто использовать их.

Текущее решение

На данный момент я отправляю пустой файл comments.php и я использую 'comments_template' фильтра 'comments_template' , чтобы узнать, какой шаблон WordPress хочет загрузить, и использовать функцию из моего механизма шаблонов для загрузки шаблона.

Что-то вроде этого:

 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 ''; } 

Вопрос

Это работает, ядро ​​совместимо, но … есть ли способ заставить его работать, не отправляя пустые comments.php ?

Потому что мне это не нравится.

Solutions Collecting From Web of "Предотвратить функцию comments_template () для загрузки comments.php"

Не уверен, что следующее решение лучше, чем решение в OP, давайте просто скажем, это альтернативное, вероятно, более хакерское решение.

Я думаю, вы можете использовать исключение PHP, чтобы остановить выполнение WordPress, когда применяется фильтр 'comments_template' .

Вы можете использовать специальный класс исключений как DTO для переноса шаблона.

Это проект для исключения:

 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; } } 

С помощью этого класса исключений ваша функция будет выглядеть следующим образом:

 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); } } 

Для блока finally требуется PHP 5.5+.

Работает аналогичным образом и не требует пустого шаблона.

Я боролся с этим раньше, и мое решение было – оно может выбить себя из файла, если он ничего не делает .

Вот соответствующий код моего проекта :

 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'; } 

Я разрешаю comments_template() просматривать движения для создания глобальных и т. Д., Но кормить его пустым файлом PHP, чтобы require и перейти к моему фактическому шаблону Twig для вывода.

Обратите внимание, что для этого требуется перехват начального вызова comments_template() , который я могу сделать, так как мой шаблон Twig вызывает посредственную абстракцию, а не фактическую функцию PHP.

Хотя мне все еще приходится отправлять пустой файл для этого, я делаю это в библиотеке, и для реализации темы совсем не нужно ее заботиться.

Решение. Используйте временный файл – с уникальным именем файла

После множества прыжков и ползания в самых грязных уголках PHP я перефразировал вопрос:

Как можно заставить PHP возвращать TRUE для file_exists( $file ) ?

поскольку код в основном просто

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

Затем вопрос был решен быстрее:

 $template = tempnam( __DIR__, '' ); 

вот и все. Возможно, было бы лучше использовать wp_upload_dir() :

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

Другим вариантом может быть использование get_temp_dir() который обертывает WP_TEMP_DIR . Подсказка: он странно возвращается к /tmp/ поэтому файлы не будут сохраняться между перезагрузками, которые /var/tmp/ would. В конце можно провести простое сравнение строк и проверить возвращаемое значение, а затем исправить это, если это необходимо – это не так:

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

Теперь, чтобы быстро проверить, есть ли ошибки для временного файла без содержимого:

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

И: Нет ошибок → работает.

EDIT: Как отметил @toscho в комментариях, есть еще лучший способ сделать это:

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

Примечание. Согласно примечанию пользователя к документам sys_get_temp_dir() поведение sys_get_temp_dir() различается между системами. Таким образом, результат получает завершающую косую черту, а затем добавляется снова. Поскольку основная ошибка # 22267 исправлена, она также должна работать на серверах Win / IIS.

Ваша рефакторинговая функция (не тестировалась):

 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 ''; } 

Бонус Nr.1: tmpfile() вернет NULL . Да, действительно.

Бонус Nr.2: file_exists( __DIR__ ) вернет TRUE . Да, действительно … на случай, если ты забудешь.

^ Это приводит к фактической ошибке в ядре WP.


Чтобы помочь другим войти в режим проводника и найти их (плохо для недокументированных частей), я быстро подытожу то, что я пробовал:

Попытка 1: Временный файл в памяти

Первая попытка, которую я сделал, – создать поток во временный файл, используя php://temp . Из документов PHP:

Единственное различие между ними заключается в том, что php://memory всегда будет хранить свои данные в памяти, тогда как php://temp будет использовать временный файл, как только объем данных будет достигнут предопределенного предела (по умолчанию это 2 МБ). Местоположение этого временного файла определяется так же, как sys_get_temp_dir() .

Код:

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

Нахождение: Нет, не работает.

Попытка 2: использование временного файла

Там tmpfile() , так почему бы не использовать это ?!

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

Да, так много об этом ярлыке.

Попытка 3: Использовать настраиваемую обертку потока

Затем я подумал, что могу создать пользовательскую stream_wrapper_register() оболочку и зарегистрировать ее с помощью stream_wrapper_register() . Тогда я мог бы использовать виртуальный шаблон из этого потока, чтобы обмануть ядро, полагая, что у нас есть файл. Пример кода ниже (я уже удалил полный класс и история не имеет достаточных шагов …)

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

Опять же, это вернуло NULL в file_exists() .


Протестировано с помощью PHP 5.6.20

Поскольку @AlainSchlesser предложил следовать по маршруту (и как не работающие вещи всегда меня пугают ), я повторил попытку создания обтекателя потока для виртуальных файлов. Я не смог его решить (прочитал: чтение возвращаемых значений в документах) самостоятельно, но решил это с помощью @HPierce на 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(); } } 

Вам просто нужно зарегистрировать новый класс как новый протокол:

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

Это позволяет создать виртуальный (не существующий) файл:

 $template = fopen( "virtual://comments", '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() в core возвращает TRUE а require $file вызывает ошибок.

Я должен отметить, что я очень доволен тем, как это получилось, поскольку это может быть очень полезно с модульными тестами.