avatar-privacy/includes/avatar-privacy/core/class-comment-author-fields...

414 lines
12 KiB
PHP

<?php
/**
* This file is part of Avatar Privacy.
*
* Copyright 2018-2023 Peter Putzer.
* Copyright 2012-2013 Johannes Freudendahl.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ***
*
* @package mundschenk-at/avatar-privacy
* @license http://www.gnu.org/licenses/gpl-2.0.html
*/
namespace Avatar_Privacy\Core;
use Avatar_Privacy\Core\API;
use Avatar_Privacy\Data_Storage\Cache;
use Avatar_Privacy\Data_Storage\Database\Comment_Author_Table;
use Avatar_Privacy\Data_Storage\Database\Hashes_Table;
use Avatar_Privacy\Tools\Hasher;
use const MINUTE_IN_SECONDS;
/**
* The API for handling (anonymous) comment author data as part of the
* Avatar Privacy Core API.
*
* @since 2.4.0
*
* @author Peter Putzer <github@mundschenk.at>
* @author Johannes Freudendahl <wordpress@freudendahl.net>
*
* @phpstan-import-type ColumnValueTuples from \Avatar_Privacy\Data_Storage\Database\Table
*/
class Comment_Author_Fields implements API {
/**
* Prefix for caching avatar privacy for non-logged-in users.
*/
const EMAIL_CACHE_PREFIX = 'email_';
/**
* The cache handler.
*
* @var Cache
*/
private Cache $cache;
/**
* The hashing helper.
*
* @var Hasher
*/
private Hasher $hasher;
/**
* The comment author table handler.
*
* @var Comment_Author_Table
*/
private Comment_Author_Table $comment_author_table;
/**
* The comment author table handler.
*
* @var Hashes_Table
*/
private Hashes_Table $hashes_table;
/**
* A cache to prevent multiple hash updates.
*
* @since 2.6.0
*
* @var array<string,string>
*/
private array $updated_hashes = [];
/**
* Creates a new instance.
*
* @param Cache $cache Required.
* @param Hasher $hasher Required.
* @param Comment_Author_Table $comment_author_table The comment author database table.
* @param Hashes_Table $hashes_table The hashes database table.
*/
public function __construct( Cache $cache, Hasher $hasher, Comment_Author_Table $comment_author_table, Hashes_Table $hashes_table ) {
$this->cache = $cache;
$this->hasher = $hasher;
$this->comment_author_table = $comment_author_table;
$this->hashes_table = $hashes_table;
}
/**
* Retrieves the hash for the given comment author e-mail address.
*
* @param string $email The comment author's e-mail address.
*
* @return string
*/
public function get_hash( $email ) {
return $this->hasher->get_hash( $email );
}
/**
* Checks whether an anonymous comment author has opted-in to Gravatar usage.
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
*
* @return bool
*/
public function allows_gravatar_use( $email_or_hash ) {
$data = $this->load( $email_or_hash );
return ! empty( $data ) && ! empty( $data->use_gravatar );
}
/**
* Checks whether an anonymous comment author is in our Gravatar policy database.
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
*
* @return bool
*/
public function has_gravatar_policy( $email_or_hash ) {
$data = $this->load( $email_or_hash );
return ! empty( $data ) && isset( $data->use_gravatar );
}
/**
* Retrieves the database primary key for the given email address.
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
*
* @return int The database key for the given email address (or 0).
*/
public function get_key( $email_or_hash ) {
$data = $this->load( $email_or_hash );
if ( isset( $data->id ) ) {
return $data->id;
}
return 0;
}
/**
* Returns the dataset from the 'use gravatar' table for the given e-mail
* address.
*
* @internal
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
*
* @return \stdClass|null The dataset as an object or null.
*/
public function load( $email_or_hash ) {
global $wpdb;
// Won't change valid hashes.
$email_or_hash = \strtolower( \trim( $email_or_hash ) );
if ( empty( $email_or_hash ) ) {
return null;
}
// Check cache.
$type = ( false === \strpos( $email_or_hash, '@' ) ) ? 'hash' : 'email';
$key = $this->get_cache_key( $email_or_hash, $type );
$data = $this->cache->get( $key );
if ( false === $data ) {
// We need to query the database.
$data = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder -- DB and column name.
'SELECT c.*, h.hash FROM `%1$s` c LEFT OUTER JOIN `%2$s` h ON c.email = h.identifier AND h.type = "comment" WHERE `%3$s` = "%4$s"',
$wpdb->avatar_privacy,
$wpdb->avatar_privacy_hashes,
$type,
$email_or_hash
),
\OBJECT
);
$this->cache->set( $key, $data, 5 * MINUTE_IN_SECONDS );
}
return $data;
}
/**
* Retrieves the email for the given comment author database key.
*
* @param string $hash The hashed mail address.
*
* @return string
*/
public function get_email( $hash ) {
$data = $this->load( $hash );
return ! empty( $data->email ) ? $data->email : '';
}
/**
* Updates the comment author information. Also clears the cache.
*
* @param int $id The row ID.
* @param string $email The mail address.
* @param array $columns An array of values index by column name.
*
* @return int|false The number of rows updated, or false on error.
*
* @throws \RuntimeException A \RuntimeException is raised when invalid
* column names are used.
*
* @phpstan-param ColumnValueTuples $columns
*/
protected function update( $id, $email, array $columns ) {
$result = $this->comment_author_table->update( $columns, [ 'id' => $id ] );
if ( false !== $result && $result > 0 ) {
// Clear any previously cached value.
$this->clear_cache( $email );
}
return $result;
}
/**
* Inserts data into the comment author table (and the supplementary hashes
* table). Also clears the cache if successful.
*
* @param string $email The mail address.
* @param int|null $use_gravatar A flag indicating if gravatar use is allowed. `null` indicates the default policy (i.e. not set).
* @param string $log_message The log message.
*
* @return int|false The number of rows updated, or false on error.
*/
protected function insert( $email, $use_gravatar, $log_message ) {
$columns = [
'email' => $email,
'use_gravatar' => $use_gravatar,
'log_message' => $log_message,
];
// Update database.
$result = $this->comment_author_table->insert( $columns );
if ( ! empty( $result ) ) {
// Clear any previously cached value, just in case.
$this->update_hash( $email, true );
}
return $result;
}
/**
* Ensures that the comment author gravatar policy is updated.
*
* @param string $email The comment author's mail address.
* @param int $comment_id The comment ID.
* @param int $use_gravatar 1 if Gravatar.com is enabled, 0 otherwise.
*
* @return void
*/
public function update_gravatar_use( $email, $comment_id, $use_gravatar ) {
$data = $this->load( $email );
if ( empty( $data ) ) {
// Nothing found in the database, insert the dataset.
$this->insert( $email, $use_gravatar, $this->get_log_message( $comment_id ) );
} else {
if ( $data->use_gravatar !== $use_gravatar ) {
// Dataset found but with different value, update it.
$new_values = [
'use_gravatar' => $use_gravatar,
'log_message' => $this->get_log_message( $comment_id ),
];
$this->update( $data->id, $data->email, $new_values );
}
// We might also need to update the hash.
if ( empty( $data->hash ) ) {
$this->update_hash( $data->email );
}
}
}
/**
* Updates the hash for the comment author email. Also clears the cache if
* necessary.
*
* @param string $email The email.
* @param bool $clear_cache Optional. Force the cache to be cleared. Default false.
*
* @return int|false The number of rows updated, or false on error.
*/
public function update_hash( $email, $clear_cache = false ) {
$result = 0;
$hash = $this->get_hash( $email );
// Let's check the update cache first.
if ( ! isset( $this->updated_hashes[ $email ] ) || $hash !== $this->updated_hashes[ $email ] ) {
$data = [
'identifier' => $email,
'hash' => $hash,
'type' => 'comment',
];
// Actually update database.
$result = $this->hashes_table->insert_or_update_row( $data );
// Mark hash as updated.
$this->updated_hashes[ $email ] = $hash;
}
// Check whether we need to clear the cache.
if ( $clear_cache || ! empty( $result ) ) {
$this->clear_cache( $hash, 'hash' );
}
return $result;
}
/**
* Returns a formatted log message for comment author data.
*
* @param int $comment_id A valid comment ID.
*
* @return string
*/
protected function get_log_message( $comment_id ) {
$log_message = 'set with comment %d';
$parameters = [ $comment_id ];
if ( \is_multisite() ) {
global $wpdb;
$log_message .= ' (site: %d, blog: %d)';
$parameters[] = $wpdb->siteid;
$parameters[] = $wpdb->blogid;
}
return \vsprintf( $log_message, $parameters );
}
/**
* Clears the cache for the given comment author e-mail address or hash.
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
* @param string $type Optional. The identifier type ('email' or 'hash'). Default 'email'.
*
* @return void
*/
public function clear_cache( $email_or_hash, $type = 'email' ) {
$this->cache->delete( $this->get_cache_key( $email_or_hash, $type ) );
}
/**
* Calculates the cache key for the given identifier.
*
* @param string $email_or_hash The comment author's e-mail address or the unique hash.
* @param string $type Optional. The identifier type ('email' or 'hash'). Default 'email'.
*
* @return string
*/
protected function get_cache_key( $email_or_hash, $type = 'email' ) {
if ( 'email' === $type ) {
// We only need the hash here.
$email_or_hash = $this->get_hash( $email_or_hash );
}
return self::EMAIL_CACHE_PREFIX . $email_or_hash;
}
/**
* Deletes the data for the comment author identified by an email address.
*
* @param string $email The comment author's e-mail address or the unique hash.
*
* @return int|false The number of rows deleted, or false on error.
*/
public function delete( $email ) {
$comment_author_rows = $this->comment_author_table->delete( [ 'email' => $email ] );
$hashes_rows = $this->hashes_table->delete(
[
'identifier' => $email,
'type' => 'comment',
]
);
if ( ! empty( $comment_author_rows ) || ! empty( $hashes_rows ) ) {
$this->clear_cache( $email );
return \max( (int) $comment_author_rows, (int) $hashes_rows );
}
return false;
}
}