ป้องกัน comments_template () เพื่อโหลด comments.php


9

ฉันกำลังพัฒนาธีม WordPress โดยใช้เครื่องมือเทมเพลต ฉันต้องการให้โค้ดของฉันทำงานร่วมกันได้มากที่สุดกับฟังก์ชั่น WP core

บางบริบทก่อน

ปัญหาแรกของฉันคือการหาวิธีแก้ไขแม่แบบที่เริ่มต้นจากแบบสอบถาม WP ฉันจะแก้ไขที่หนึ่งใช้ห้องสมุดของฉันสมอง \ ลำดับชั้น

เกี่ยวget_template_part()และฟังก์ชั่นอื่น ๆ ที่โหลด partials ชอบget_header(), get_footer()และที่คล้ายกันก็เป็นคนน่ารักง่ายต่อการเขียนกระดาษห่อกับเครื่องยนต์แม่แบบการทำงานบางส่วน

ปัญหา

ปัญหาของฉันคือวิธีโหลดเทมเพลตความคิดเห็น

ฟังก์ชั่น WordPress comments_template()เป็นฟังก์ชั่น~ 200 บรรทัดที่ทำสิ่งต่างๆมากมายซึ่งฉันต้องการจะทำเช่นนั้นเพื่อความเข้ากันได้ของคอร์สูงสุด

อย่างไรก็ตามทันทีที่ฉันโทรcomments_template()ไฟล์จะเป็นrequired มันเป็นไฟล์แรกของ:

  • ไฟล์ในค่าคงที่COMMENTS_TEMPLATEหากกำหนดไว้
  • comments.php ในโฟลเดอร์ชุดรูปแบบหากพบ
  • /theme-compat/comments.php ใน WP รวมโฟลเดอร์เป็นทางเลือกสุดท้าย

ในระยะสั้นมีวิธีที่จะป้องกันไม่ให้ฟังก์ชั่นในการโหลดไฟล์ PHP ซึ่งไม่เป็นที่พึงประสงค์สำหรับฉันไม่เพราะฉันต้องทำให้แม่แบบและไม่เพียง requireแต่ใช้งานของฉัน

ทางออกปัจจุบัน

ในขณะนี้ฉันกำลังจัดส่งcomments.phpไฟล์ว่างเปล่าและฉันใช้'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หรือไม่?

เพราะฉันไม่ชอบ

คำตอบ:


4

ไม่แน่ใจว่าโซลูชันต่อไปนี้ดีกว่าโซลูชันใน 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+

ทำงานในลักษณะเดียวกันและไม่ต้องการเทมเพลตเปล่า


4

ฉันเคยต่อสู้กับเรื่องนี้มาก่อนและวิธีการแก้ปัญหาของฉันคือ - มันสามารถทำให้ตัวเองต้องใช้ไฟล์ตราบใดที่มันไม่ได้ทำอะไรเลย

นี่คือรหัสที่เกี่ยวข้องจากโครงการ templating Meadowของฉัน:

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()ผ่านการเคลื่อนไหวเพื่อตั้งค่า globals และเช่นนั้น แต่ป้อนไฟล์ PHP ว่างrequireและไปยังแม่แบบ Twig ที่แท้จริงของฉันสำหรับการส่งออก

โปรดทราบว่าสิ่งนี้จะต้องสามารถสกัดกั้นการcomments_template()โทรเริ่มต้นซึ่งฉันสามารถทำได้เนื่องจากแม่แบบ Twig ของฉันกำลังเรียกการลบล้างตัวกลางแทนฟังก์ชัน PHP จริง

ในขณะที่ฉันยังต้องจัดส่งไฟล์ว่างเปล่าสำหรับมันฉันทำเช่นนั้นในไลบรารี่และการใช้ธีมไม่จำเป็นต้องสนใจมันเลย


โหวตขึ้นขอบคุณ ฉันเห็นแนวทางของคุณแล้วตั้งแต่ฉันใช้ Meadow มาก่อน สิ่งที่ฉันไม่ชอบที่นี่คือความจริงที่ว่าเทมเพลตเปล่าต้องถูกส่งต่อไป ยิ่งไปกว่านั้นสิ่งนี้จะแบ่งความพยายามในการใช้comments_templateตัวกรองหรือCOMMENTS_TEMPLATEค่าคงที่เพื่อกำหนดแม่แบบเอง ซึ่งไม่ได้เป็นการพิจาณา แต่อย่างที่ฉันบอกว่าฉันต้องการที่จะอยู่กับแกนกลางให้มากที่สุดเท่าที่จะทำได้
gmazzap

@ gmazzap hmmm ... ไม่มีเหตุผลที่ฉันไม่สามารถเพิ่มการสนับสนุนสำหรับตัวกรองและค่าคงที่ใน wrapper ของฉัน แต่มันได้รับใน micromanaging
Rarst

3

วิธีแก้ไข: ใช้ไฟล์ชั่วคราว - ด้วยชื่อไฟล์ที่ไม่ซ้ำ

หลังจากกระโดดข้ามไปมาและคลานเข้าไปในมุมที่สกปรกที่สุดของ 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/จะเป็นเช่นนั้น เราสามารถทำการเปรียบเทียบสตริงอย่างง่าย ๆ ในตอนท้ายและตรวจสอบค่าส่งคืนแล้วแก้ไขในกรณีที่จำเป็น - ซึ่งไม่ได้อยู่ในกรณีนี้:

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

ตอนนี้เพื่อทดสอบอย่างรวดเร็วว่ามีข้อผิดพลาดเกิดขึ้นกับไฟล์ชั่วคราวที่ไม่มีเนื้อหาหรือไม่:

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

และ: ไม่มีข้อผิดพลาด →ทำงาน

แก้ไข:ตามที่@toschoชี้ให้เห็นในความคิดเห็นยังมีวิธีที่ดีกว่าในการทำ:

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

หมายเหตุ: ตามที่ผู้ใช้ทราบบนเอกสาร php.netที่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 core


เพื่อช่วยให้ผู้อื่นเข้าสู่โหมดสำรวจและค้นหาสิ่งเหล่านั้น (ไม่ดีต่อชิ้นส่วนที่ไม่มีเอกสาร) ฉันจะสรุปสิ่งที่ฉันลองอย่างรวดเร็ว:

ความพยายามที่ 1: ไฟล์ชั่วคราวในหน่วยความจำ

php://tempความพยายามครั้งแรกที่ผมทำคือการสร้างกระแสไปยังแฟ้มชั่วคราวโดยใช้ จากเอกสาร PHP:

ข้อแตกต่างระหว่างสองอย่างนี้คือphp://memoryจะเก็บข้อมูลไว้ในหน่วยความจำเสมอในขณะที่php://tempจะใช้ไฟล์ชั่วคราวเมื่อจำนวนข้อมูลที่เก็บไว้มีค่าถึงขีด จำกัด ที่กำหนดไว้ล่วงหน้า (ค่าเริ่มต้นคือ 2 MB) ตำแหน่งของไฟล์ชั่วคราวนี้ถูกกำหนดในลักษณะเดียวกับ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: ใช้ wrapper กระแสที่กำหนดเอง

ต่อไปฉันคิดว่าฉันสามารถสร้างกระแสเสื้อคลุมที่กำหนดเองและลงทะเบียนได้โดยใช้ stream_wrapper_register()จากนั้นฉันสามารถใช้เทมเพลตเสมือนจากสตรีมนั้นเพื่อหลอกล่อคอร์ให้เชื่อว่าเรามีไฟล์ โค้ดตัวอย่างด้านล่าง (ฉันลบคลาสเต็มแล้วและประวัติมีขั้นตอนไม่เพียงพอ ... )

class TemplateStreamWrapper
{
    public $context;

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

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

อีกครั้งนี้กลับมาในNULLfile_exists()


ทดสอบกับ PHP 5.6.20


ฉันคิดว่าความพยายาม 3 ของคุณควรทำงานในทางทฤษฎี ใน wrapper กระแสที่กำหนดเองของคุณคุณใช้stream_stat()หรือไม่ ฉันคิดว่านี่คือสิ่งที่file_exists()จะเรียกเพื่อให้ตรวจสอบ ... php.net/manual/en/streamwrapper.stream-stat.php
Alain Schlesser

เพิ่มขึ้นเพราะค่อนข้างดีและไม่ค่อยแฮ็ค อย่างไรก็ตามเนื่องจากรหัสของฉันมีวัตถุประสงค์เพื่อใช้ในการตั้งค่าที่แตกต่างกันฉันกลัวว่าการอนุญาตให้เขียนอาจเป็นปัญหา นอกจากนี้ไฟล์ชั่วคราวจำเป็นต้องลบซึ่งไม่ได้เป็นเรื่องง่ายในทันทีtempnam()เพราะมันไม่ง่ายที่จะตัดเส้นทางแบบเต็มกลับโดย การใช้งาน cron จะใช้งานได้ แต่จะมีค่าใช้จ่ายเพิ่มเติม ...
gmazzap

ฉันคิดว่าการเขียนไฟล์ชั่วคราวนั้นแย่กว่าการส่งเทมเพลตเปล่า เทมเพลตคงที่ว่างเปล่าจะถูกแคชไว้ใน opcode ไฟล์ชั่วคราวจะต้องถูกเขียนไปยังดิสก์แยกวิเคราะห์เย็น (ไม่มีรหัส) แล้วลบ มันจะดีกว่าเพื่อลดความนิยมของดิสก์โดยไม่มีเหตุผลที่ดี
Rarst

@Rarst คำถามไม่เคยเป็น"สิ่งที่ดีกว่า"ประสิทธิภาพการทำงานที่ชาญฉลาด คำถามต้มลงไปไม่ได้มีแฟ้มแม่แบบ :)
Kaiser

1
tempnam( sys_get_temp_dir(), 'comments.php' )ถูกเขียนครั้งเดียวคุณสามารถนำชื่อไฟล์มาใช้ใหม่และไฟล์นั้นว่างเปล่าดังนั้นจึงไม่ได้ใช้ทรัพยากรมากมาย นอกจากนี้ยังง่ายต่อการเข้าใจในรหัสของคุณ โดยวิธีที่ดีที่สุด imho
fuxia

3

ในฐานะที่เป็น@AlainSchlesserแนะนำให้ตามเส้นทาง (และเป็นสิ่งที่ไม่ได้ทำงานข้อผิดพลาดเสมอฉัน) ผมลองสร้างเสื้อคลุมสตรีมไฟล์เสมือน ฉันไม่สามารถแก้ปัญหาได้ (อ่าน: การอ่านค่าผลตอบแทนจากเอกสาร) บนของตัวเอง แต่แก้ไขได้ด้วยความช่วยเหลือของ@HPierce ในดังนั้น

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+' );

ฟังก์ชั่นของคุณสามารถรับการ refactored ไปที่:

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()แกนเช็คอินกลับมาTRUEและrequire $fileไม่มีข้อผิดพลาดเกิดขึ้น

ฉันต้องทราบว่าฉันมีความสุขมากที่สิ่งนี้เปิดออกเนื่องจากอาจเป็นประโยชน์กับการทดสอบหน่วย


1
ค้นพบที่ยอดเยี่ยม! ฉันชอบวิธีนี้ดีที่สุด ;-) ฉันแน่ใจว่ามีส่วนอื่น ๆ ของแกนที่สามารถนำไปใช้ได้
Birgire

1
โหวตขึ้นและขอขอบคุณ! สำหรับการทดสอบหน่วยมีgithub.com/mikey179/vfsStreamอยู่แล้วดังนั้นไม่จำเป็นต้องบูรณาการล้อ); Btw ฉันชอบวิธีการนี้ไม่แน่ใจว่าฉันจะใช้สิ่งนี้เพราะวิธีการยกเว้นทำให้ฉันรู้สึกชั่วร้ายอย่างมีความสุข: D
gmazzap

@gmazzap ฉันมากแน่ใจว่านี้เป็นวิธีที่คุณมองเมื่อคุณพบ
kaiser

@kaiser nah ฉันพบเพราะฉัน RTFM: P phpunit.de/manual/current/en/…
gmazzap
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.