Ngăn chặn bình luận_template () để tải bình luận.php


9

Tôi đang phát triển một chủ đề WordPress bằng cách sử dụng một công cụ mẫu. Tôi muốn mã của mình tương thích nhiều nhất có thể với chức năng lõi WP.

Một số bối cảnh đầu tiên

Vấn đề đầu tiên của tôi là tìm cách giải quyết mẫu bắt đầu từ truy vấn WP. Tôi đã giải quyết cái đó bằng thư viện của tôi, Brain \ HVELy .

Về get_template_part()các chức năng khác tải các phần như get_header(), get_footer()và tương tự, thật dễ dàng để viết trình bao bọc cho chức năng một phần của công cụ mẫu.

Vấn đề

Vấn đề của tôi bây giờ là làm thế nào để tải mẫu bình luận.

Chức năng WordPress comments_template()là một chức năng ~ 200 dòng thực hiện rất nhiều thứ, điều mà tôi muốn làm cũng như khả năng tương thích lõi tối đa.

Tuy nhiên, ngay sau khi tôi gọi comments_template(), một tệp là required, đó là tệp đầu tiên của:

  • các tập tin trong hằng COMMENTS_TEMPLATE, nếu được xác định
  • comments.php trong thư mục chủ đề, nếu tìm thấy
  • /theme-compat/comments.php trong WP bao gồm thư mục là dự phòng cuối cùng

Nói tóm lại, không có cách nào để ngăn chặn chức năng tải tệp PHP, điều mà tôi không mong muốn, bởi vì tôi cần kết xuất các mẫu của mình chứ không chỉ đơn giản là sử dụng require.

Giải pháp tạm thời

Hiện tại, tôi đang gửi một comments.phptệp trống và tôi đang sử dụng 'comments_template'bộ lọc hook, để biết WordPress muốn tải mẫu nào và sử dụng tính năng từ công cụ mẫu của tôi để tải mẫu.

Một cái gì đó như thế này:

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

Câu hỏi

Công việc này, tương thích cốt lõi, nhưng ... có cách nào để làm cho nó hoạt động mà không phải gửi một sản phẩm nào comments.phpkhông?

Bởi vì tôi không thích nó.

Câu trả lời:


4

Không chắc chắn giải pháp sau tốt hơn giải pháp trong OP, hãy nói là giải pháp thay thế, có thể là hackish hơn.

Tôi nghĩ bạn có thể sử dụng ngoại lệ PHP để dừng thực thi WordPress khi 'comments_template'áp dụng bộ lọc.

Bạn có thể sử dụng một lớp ngoại lệ tùy chỉnh làm DTO để mang mẫu.

Đây là một bản nháp cho sự miễn trừ:

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

Với lớp ngoại lệ này có sẵn, chức năng của bạn trở thành:

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

Các finallykhối đòi hỏi PHP 5.5+.

Hoạt động theo cùng một cách và không yêu cầu một mẫu trống.


4

Tôi đã vật lộn với điều này trước đây và giải pháp của tôi là - nó có thể tự loại bỏ yêu cầu tập tin, miễn là nó không làm gì cả.

Đây là mã có liên quan từ dự án khuôn mẫu đồng cỏ của tôi :

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

Tôi comments_template()chuyển qua các chuyển động để thiết lập toàn cầu và như vậy, nhưng cung cấp tệp PHP trống cho nó requirevà chuyển sang mẫu Twig thực tế của tôi để xuất ra.

Lưu ý rằng điều này đòi hỏi phải có khả năng chặn comments_template()cuộc gọi ban đầu , điều mà tôi có thể làm vì mẫu Twig của tôi đang gọi trừu tượng hóa trung gian thay vì chức năng PHP thực tế.

Mặc dù tôi vẫn phải gửi tệp trống cho nó, tôi làm như vậy trong thư viện và thực hiện chủ đề hoàn toàn không phải quan tâm đến nó.


Ủng hộ, cảm ơn. Tôi đã thấy cách tiếp cận của bạn kể từ khi tôi sử dụng đồng cỏ trước đây. Điều tôi không thích ở đây là thực tế là một mẫu trống cần phải được gửi đi. Hơn nữa, điều này phá vỡ mọi nỗ lực sử dụng comments_templatebộ lọc hoặc COMMENTS_TEMPLATEhằng số để tùy chỉnh mẫu. Đó không phải là mấu chốt, nhưng, như tôi đã nói, tôi muốn ở lại càng nhiều càng tốt tương thích với cốt lõi.
gmazzap

@gmazzap hmmm ... không có lý do gì tôi không thể thêm hỗ trợ cho bộ lọc & hằng số trong trình bao bọc của mình, nhưng nó được đưa vào vi mô.
Hết

3

Giải pháp: Sử dụng tệp tạm thời - với tên tệp duy nhất

Sau rất nhiều lần nhảy và bò vào những góc khuất nhất của PHP, tôi đã viết lại câu hỏi cho:

Làm thế nào người ta có thể đánh lừa PHP vào trở lại TRUEcho file_exists( $file )?

như mã trong lõi chỉ là

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

Sau đó, câu hỏi đã được giải quyết nhanh hơn:

$template = tempnam( __DIR__, '' );

và đó là nó. Có lẽ nó sẽ tốt hơn để sử dụng wp_upload_dir()thay thế:

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

Một lựa chọn khác có thể là sử dụng get_temp_dir()kết thúc tốt đẹp WP_TEMP_DIR. Gợi ý: Nó kỳ lạ rơi trở lại để /tmp/các tập tin sẽ không được bảo tồn giữa các lần khởi động lại /var/tmp/. Người ta có thể thực hiện so sánh chuỗi đơn giản ở cuối và kiểm tra giá trị trả về và sau đó sửa lỗi này trong trường hợp cần thiết - không phải trong trường hợp này:

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

Bây giờ để nhanh chóng kiểm tra nếu có lỗi ném cho một tệp tạm thời không có nội dung:

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

Và: Không có lỗi → làm việc.

EDIT: Như @toscho đã chỉ ra trong các bình luận, vẫn còn một cách tốt hơn để làm điều đó:

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

Lưu ý: Theo ghi chú của người dùng trên tài liệu php.net , sys_get_temp_dir()hành vi khác nhau giữa các hệ thống. Do đó, kết quả sẽ được xóa dấu gạch chéo, sau đó thêm lại. Vì lỗi cốt lõi # 22267 đã được sửa, nên nó cũng hoạt động trên các máy chủ Win / IIS.

Chức năng tái cấu trúc của bạn (không được kiểm tra):

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

Tiền thưởng Nr.1: tmpfile()sẽ trở lại NULL. Thật đấy.

Tiền thưởng Nr.2: file_exists( __DIR__ )sẽ trở lại TRUE. Vâng, thực sự rất khó trong trường hợp bạn quên.

^ Điều này dẫn đến một lỗi thực tế trong lõi WP.


Để giúp những người khác đi trong chế độ thám hiểm và tìm kiếm những thứ đó (xấu đến những mảnh không có giấy tờ), tôi sẽ nhanh chóng tóm tắt những gì tôi đã thử:

Nỗ lực 1: Tệp tạm thời trong bộ nhớ

Nỗ lực đầu tiên tôi thực hiện là tạo một luồng đến một tệp tạm thời, bằng cách sử dụng php://temp. Từ các tài liệu PHP:

Sự khác biệt duy nhất giữa hai là php://memorysẽ luôn lưu trữ dữ liệu của nó trong bộ nhớ, trong khi đó php://tempsẽ sử dụng một tệp tạm thời khi lượng dữ liệu được lưu trữ đạt đến giới hạn được xác định trước (mặc định là 2 MB). Vị trí của tệp tạm thời này được xác định theo cùng một cách với sys_get_temp_dir()chức năng.

Mật mã:

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

Tìm kiếm: Không, không hoạt động.

Nỗ lực 2: Sử dụng tệp tạm thời

tmpfile(), vậy tại sao không sử dụng?!

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

Vâng, rất nhiều về lối tắt này.

Cố gắng 3: Sử dụng trình bao bọc luồng tùy chỉnh

Tiếp theo tôi nghĩ rằng tôi có thể xây dựng một trình bao bọc luồng tùy chỉnhđăng ký nó bằng cách sử dụngstream_wrapper_register() . Sau đó, tôi có thể sử dụng một mẫu ảo từ luồng đó để lừa lõi tin rằng chúng tôi có một tệp. Mã ví dụ bên dưới (Tôi đã xóa toàn bộ lớp và lịch sử không có đủ các bước)

class TemplateStreamWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened )
    {
        // return boolean
    }
}

stream_wrapper_register( 'vt://comments', 'TemplateStreamWrapper' );
// … etc. …

Một lần nữa, điều này trở lại NULLvào file_exists().


Đã thử nghiệm với PHP 5.6.20


Tôi nghĩ rằng Nỗ lực 3 của bạn nên hoạt động trên lý thuyết. Trong trình bao bọc luồng tùy chỉnh của bạn, bạn đã thực hiện stream_stat()? Tôi nghĩ rằng đây là những gì file_exists()sẽ gọi để thực hiện kiểm tra ... php.net/manual/en/streamwrapper.stream-stat.php
Alain Schlesser

Upvote vì khá đẹp và không hackish. Tuy nhiên, vì mã của tôi dự định sẽ được sử dụng trong các thiết lập khác nhau, tôi sợ rằng quyền viết có thể là một vấn đề. Hơn nữa, các file tạm thời cần phải bị xóa, mà không phải là dễ dàng khi đang bay , bởi vì nó không phải dễ dàng để đánh chặn đường dẫn đầy đủ được trả về bởi tempnam(). Sử dụng một công việc định kỳ sẽ hoạt động, nhưng đó là chi phí bổ sung ...
gmazzap

Tôi nghĩ rằng viết tập tin tạm thời là tồi tệ hơn vận chuyển mẫu trống. Đã sửa mẫu trống sẽ được lưu vào bộ đệm. Tập tin tạm thời sẽ phải được ghi vào đĩa, phân tích cú pháp lạnh (không có opcode), sau đó xóa. Tốt hơn hết là giảm thiểu các lần truy cập đĩa mà không có lý do chính đáng.
Hết

@Rarst Câu hỏi không bao giờ là hiệu suất "cái gì tốt hơn" khôn ngoan. Câu hỏi được rút ra để không có tệp mẫu :)
kaiser

1
tempnam( sys_get_temp_dir(), 'comments.php' )được viết một lần , bạn có thể sử dụng lại tên tệp và tệp trống , vì vậy nó không sử dụng nhiều tài nguyên. Thêm vào đó là dễ hiểu trong mã của bạn. Cho đến nay giải pháp tốt nhất, imho.
fuxia

3

Như @AlainSchlesser đã đề xuất theo lộ trình (và vì những thứ không hoạt động luôn làm tôi khó chịu), tôi đã thử lại việc xây dựng một trình bao bọc luồng cho các tệp ảo. Tôi không thể tự mình giải quyết nó (đọc: đọc các giá trị trả về trên các tài liệu), nhưng đã giải quyết nó với sự trợ giúp của @HPierce trên 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();
    }
}

Bạn chỉ cần đăng ký lớp mới là giao thức mới:

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

Điều này sau đó cho phép tạo một tệp ảo (không tồn tại):

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

Chức năng của bạn sau đó có thể được tái cấu trúc thành:

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

khi file_exists()kiểm tra trong lõi trả về TRUErequire $fileném không có lỗi.

Tôi phải lưu ý rằng tôi khá vui khi điều này bật ra vì nó có thể thực sự hữu ích với các bài kiểm tra đơn vị.


1
Những phát hiện tuyệt vời! Tôi thích cách tiếp cận này nhất ;-) Tôi chắc chắn có những phần khác của cốt lõi mà điều này có thể được áp dụng.
bạch dương

1
Ủng hộ và cảm ơn! Đối với các bài kiểm tra đơn vị đã có github.com/mikey179/vfsStream vì vậy không cần phải phát minh lại bánh xe;) Btw, tôi thích cách tiếp cận này, không chắc chắn tôi sẽ sử dụng phương pháp này vì phương pháp ngoại lệ khiến tôi cảm thấy hạnh phúc xấu xa: D
gmazzap

@gmazzap Tôi rất chắc chắn đây là cách bạn nhìn khi bạn phát hiện ra .
kaiser

@kaiser nah, tôi đã tìm thấy nó bởi vì tôi RTFM: P phpunit.de/manual/civerse/en/ mẹo
gmazzap
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.