TL;TR? 扰流板警报-将鼠标悬停在下一个blockquote上以显示它
构建一个可通过当前输入的密码轻松过滤的档案绝非易事,也绝对不可能优雅地构建。解决方案包括一个辅助查询和一个小插件,但它可以工作。还会显示所有错误和错误路径,以帮助您避免犯相同的错误。
DB结构密码是主$wpdb->posts
桌子它保存在纯文本中(只是为了说明这一点。不要害怕。它没有连接到任何实际的用户登录,所以你不必担心,当有人每次意外发现它时,你正在打开一个安全漏洞。
构建/更改主查询
为了将结果减少到受密码保护的帖子,我们正在拦截
WHERE
条款适用于:
一个页面,任何公共页面,所有页面都有一个文件名为的模板password-protected.php
这里是插件的实际代码。请注意,它会not 工作二者都is_page_template()
和is_page()
使用slug
参数将从get_queried_object()
. 当我们已经检索到最终查询结果时,查询的对象就存在了。
<?php
/** Plugin Name: (#135443) Password Protected Posts Page Template Query */
add_action( \'pre_get_posts\', \'wpse135443ExcludeProtectedAction\' );
function wpse135443ExcludeProtectedAction( $query )
{
if (
is_page( \'slug-of-protected-page\' )
AND is_page_template( \'password-protected.php\' )
AND ! is_admin()
)
add_filter( \'posts_where\', \'wpse135443ExcludeProtected\' );
}
// Callback for the posts WHERE clause that only queries for password protected posts
function wpse135443ExcludeProtected( $where )
{
return $where .= " AND NOT {$GLOBALS[\'wpdb\']->posts}.post_password = \'\' ";
}
页面模板有几种方法。一种是使用过滤器:
add_filter( \'the_excerpt\', \'wpse135443PasswordProtectedExcerpt\' );
function wpse135443PasswordProtectedExcerpt( $excerpt )
{
return post_password_required()
? get_the_password_form()
: $excerpt;
}
更简单的方法是只列出标题,然后将摘录或内容直接添加到专用模板中。
post_password_required()
返回布尔结果,以便轻松检查。该函数接受一个参数,即post ID。它已经从循环内的全局中检索到,但您也可以在循环外使用它,并向其中抛出密码。
post_password_required() AND print get_the_password_form();
检索密码并将其添加到查询中:以下函数将密码添加到(公共)查询字符串中。正如您所见,此解决方案非常不安全:它在HTTP请求中公开密码。
add_action( \'login_form_postpass\', \'wpse135443PasswordToQueryString\' );
function wpse135443PasswordToQueryString()
{
require_once ABSPATH . \'wp-includes/class-phpass.php\';
$hasher = new PasswordHash( 8, true );
$expire = apply_filters( \'post_password_expires\', time() + 10 * DAY_IN_SECONDS );
setcookie( \'wp-postpass_\' . COOKIEHASH, $hasher->HashPassword( wp_unslash( $_POST[\'post_password\'] ) ), $expire, COOKIEPATH );
exit( wp_safe_redirect( add_query_arg(
\'post_password\',
esc_attr( $_POST[\'post_password\'] ),
wp_get_referer()
) ) );
} );
虽然这可能有用,但实际上
not recommended 这样做。如果你真的想从那里走下去,希望你知道如何继续。为了理智起见,我不会透露比这更多的内容——片段搜寻者可能会在公开发布的主题中抓住并使用它,我不想说出问题的根源。
什么都没用?其他选择
第一个大问题是,WordPress默认没有“受保护的帖子存档”
see in the template hierarchy. 当我们想要改变页面模板中的主查询时,我们必须检查SQL字符串并与之进行比较。插件可能会拦截(很差),并且没有可靠的方法来处理WP中被拦截和修改的SQL子句。筛选器回调/操作的值的外观示例,链接到
posts_clauses
.
$wpdb->posts
是posts表的名称。
array (size=7)
\'where\' => string \' AND $wpdb->posts.ID = 1519 AND $wpdb->posts.post_type = \'page\'\' (length=63)
\'groupby\' => string \'\' (length=0)
\'join\' => string \'\' (length=0)
\'orderby\' => string \'$wpdb->posts.post_date DESC\' (length=27)
\'distinct\' => string \'\' (length=0)
\'fields\' => string \'$wpdb->posts.*\' (length=14)
\'limits\' => string \'\' (length=0)
这里没什么可看的。
因此,下一步我们将尝试添加另一个查询。为了便于识别,我们将添加一个额外的自定义查询变量。
$ppQuery = new WP_Query( array(
\'password_protected\' => TRUE,
\'post_type\' => \'post\',
\'posts_per_page\' => get_option( \'posts_per_page\' ),
\'post_status\' => \'publish\',
) );
生成的SQL语句将类似于以下内容:
SELECT SQL_CALC_FOUND_ROWS $wpdb->posts.*
FROM $wpdb->posts
WHERE 1=1
AND $wpdb->posts.post_type = \'post\'
AND ($wpdb->posts.post_status = \'publish\')
AND NOT ($wpdb->posts.post_password = \'\')
ORDER BY $wpdb->posts.post_date DESC LIMIT 0, 10
然后我们可以循环:
<?php
if ( $ppQuery->have_posts() )
{
?>
<div class="entry-content">
<?php echo get_the_password_form( $GLOBALS[\'post\'] ); ?>
</div>
<?php
while ( $ppQuery->have_posts() )
{
$ppQuery->the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<?php the_title( \'<header class="entry-header"><h2>\', \'</h2></header>\' ); ?>
<div class="entry-content">
<?php
post_password_required()
OR the_excerpt();
?>
</div>
</article>
<?php
}
} ?>
密码表单显示在模板顶部。它从全球
$post
对象,它是第一个
WP_Post
实例由全局
$wp_query
对象使用哪个帖子并不重要,因为它只用于构建
<label>
的窗体和ID
input
字段而不是其他内容。表单仍将为
name="post_password"
.
只有在以下情况下才会显示摘录post_password_required()
是TRUE
. 启动窗体时,wp-login.php?action=postpass
检索$_POST
数据,设置cookie并执行安全重定向。因此every 共享相同密码的帖子将被“解锁”——WP只是根据所有帖子密码的哈希值检查Cookie哈希值。如果匹配,则显示。
下面是对齐此模板的插件,这是使其工作所需的插件。
<?php
/**
* Plugin Name: (#135443) Password Protected Posts Page Template Query
*/
add_action( \'pre_get_posts\', \'wpse135443ExcludeProtectedAction\' );
function wpse135443ExcludeProtectedAction( $query )
{
if (
$query->get( \'password_protected\' )
AND ! is_admin()
)
add_filter( \'posts_where\', \'wpse135443ExcludeProtected\' );
}
// Callback for the posts WHERE clause that only queries for password protected posts
function wpse135443ExcludeProtected( $pieces )
{
remove_filter( current_filter(), __FUNCTION__ );
return $where .= " AND NOT ({$GLOBALS[\'wpdb\']->posts}.post_password = \'\') ";
}
过滤帖子在查询过程中,几乎不可能(或几乎不可能)查询散列值并对帖子密码进行散列。因此,最简单的方法是使用
FilterIterator
:
class PasswordProtectedPostsLoop extends \\FilterIterator implements \\Countable
{
protected $wp_query;
protected $allowed = FALSE;
protected $total = 0;
protected $counter = 0;
/**
* @param \\Iterator $iterator
* @param \\WP_Query $wp_query
*/
public function __construct( \\Iterator $iterator, \\WP_Query $wp_query )
{
# Setup properties
// Global main query object
NULL === $this->wp_query AND $this->wp_query = $wp_query;
// Posts for this request
$this->total = $this->wp_query->query_vars[\'posts_per_page\'];
// Internal counter
0 !== $this->counter AND $this->counter = 0;
$this->allowed = $this->wp_query->have_posts();
parent::__construct( $iterator );
}
/**
* @return bool
*/
public function accept()
{
if (
! $this->allowed
OR ! $this->current() instanceof \\WP_Post
)
return FALSE;
$this->wp_query->the_post();
// Rewind posts for next loop
$this->wp_query->current_post === $this->total -1
AND $this->wp_query->rewind_posts();
if ( ! post_password_required() )
return FALSE;
$this->counter++;
return TRUE;
}
/**
* Helper function to retrieve the ID of the currently looped post.
* @return int
*/
public function getID()
{
return $this->current()->ID;
}
/**
* @return int
*/
public function count()
{
return $this->counter;
}
}
循环将非常简单:
$ppArrayObj = new \\ArrayObject( $ppQuery->->get_posts() );
$ppLoop = new PasswordProtectedPostsLoop( $ppArrayObj->getIterator(), $ppQuery );
$ppLoop->rewind();
foreach ( $ppyLoop as $post )
{
?>
<article <?php post_class(); ?>>
<?php the_title( \'<header class="entry-header"><h2>\', \'</h2></header>\' ); ?>
<div class="entry-content">
<?php the_excerpt(); >
</div>
</article>
<?php
}