File: //home/globfdxw/public_html/wp-content/plugins/wpforms-pdf/src/Access/PDF.php
<?php
namespace WPFormsPDF\Access;
use WPFormsPDF\Notifications\Process;
use WPFormsPDF\Storage;
use WPForms\Helpers\File;
use WP_User;
/**
* PDF Access control and download handler.
*
* @since 1.0.0
*/
class PDF {
/**
* Class constructor.
*
* @since 1.0.0
*/
public function __construct() {
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.0.0
*/
private function hooks(): void {
add_action( 'template_redirect', [ $this, 'handle_pdf_download_request' ] );
}
/**
* Handle PDF download request via query param.
*
* @since 1.0.0
*/
public function handle_pdf_download_request(): void {
$hash = $this->get_pdf_hash_from_request();
if ( ! isset( $hash ) ) {
return;
}
$pdf_info = $this->get_pdf_info_from_hash( $hash );
[ $form_data, $entry, $entry_fields ] = $this->get_form_and_entry_data( $pdf_info );
$pdf_settings = $this->get_pdf_settings( $form_data, $pdf_info['pdf_id'] );
if ( ! $this->is_access_granted( $pdf_settings ) ) {
$this->render_access_denied();
}
if ( $this->needs_password( $pdf_settings ) && ! $this->check_password( $pdf_settings ) ) {
$error_message = $this->get_password_error_message();
$this->render_password_form( $hash, $error_message );
}
$pdf_file = $this->get_pdf_file_path( $form_data, (array) $entry, $entry_fields, $pdf_settings, $pdf_info );
$this->serve_pdf_file( $pdf_file );
}
/**
* Extract PDF hash from GET.
*
* @since 1.0.0
*
* @return string|null
*/
private function get_pdf_hash_from_request(): ?string {
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
if ( ! empty( $_GET['wpforms_pdf_download'] ) ) {
return sanitize_text_field( wp_unslash( $_GET['wpforms_pdf_download'] ) );
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
return null;
}
/**
* Get an error message for incorrect password.
*
* @since 1.0.0
*
* @return string
*/
private function get_password_error_message(): string {
return isset( $_POST['wpforms_pdf_password'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
? __( 'Sorry, the password you entered is incorrect.', 'wpforms-pdf' )
: '';
}
/**
* Validate and decode hash from URL.
*
* @since 1.0.0
*
* @param string $hash Hash from URL.
*
* @return array
*/
private function get_pdf_info_from_hash( string $hash ): array {
$hash = sanitize_text_field( $hash );
$pdf_info = $this->decode_hash( $hash );
if ( ! $pdf_info ) {
$this->render_access_denied();
}
$form_id = (int) ( $pdf_info['form_id'] ?? 0 );
$entry_id = (int) ( $pdf_info['entry_id'] ?? 0 );
$pdf_id = (int) ( $pdf_info['pdf_id'] ?? 0 );
if ( ! $form_id || ! $entry_id || ! $pdf_id ) {
$this->render_access_denied();
}
return $pdf_info;
}
/**
* Fetch and validate form and entry data.
*
* @since 1.0.0
*
* @param array $pdf_info PDF info.
*
* @return array (form_data, entry, entry_fields)
*/
private function get_form_and_entry_data( array $pdf_info ): array {
$form_data = $this->get_form_data( $pdf_info );
$entry = $this->get_entry( $pdf_info );
$entry_fields = $entry->fields ?? [];
$entry_fields = is_array( $entry_fields ) ? $entry_fields : json_decode( $entry_fields, true );
return [ $form_data, $entry, $entry_fields ];
}
/**
* Get form data.
*
* @since 1.0.0
*
* @param array $pdf_info PDF info.
*
* @return array
*/
private function get_form_data( array $pdf_info ): array {
$form_id = (int) $pdf_info['form_id'];
$form_obj = wpforms()->obj( 'form' );
$form_data = $form_obj ? $form_obj->get( $form_id, [ 'content_only' => true ] ) : null;
if ( empty( $form_data ) ) {
$this->render_access_denied();
}
return is_array( $form_data ) ? $form_data : (array) json_decode( $form_data, true );
}
/**
* Get entry data.
*
* @since 1.0.0
*
* @param array $pdf_info PDF info.
*
* @return object
*/
private function get_entry( array $pdf_info ): object {
$entry_id = (int) $pdf_info['entry_id'];
$entry_obj = wpforms()->obj( 'entry' );
$entry = $entry_obj ? $entry_obj->get( $entry_id ) : null;
if ( empty( $entry ) ) {
$this->render_access_denied();
}
return $entry;
}
/**
* Fetch and validate PDF settings.
*
* @since 1.0.0
*
* @param array $form_data Form data.
* @param int $pdf_id PDF ID.
*
* @return array
*/
private function get_pdf_settings( array $form_data, int $pdf_id ): array {
$pdf_settings = $form_data['settings']['pdfs'][ $pdf_id ] ?? [];
if ( empty( $pdf_settings ) ) {
$this->render_access_denied();
}
return $pdf_settings;
}
/**
* Check if PDF needs password protection.
*
* @since 1.0.0
*
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
private function needs_password( array $pdf_settings ): bool {
return ! empty( $pdf_settings['is_protected'] ) && ! empty( $pdf_settings['protection_password'] );
}
/**
* Get the full path to the PDF file.
*
* @since 1.0.0
*
* @param array $form_data Form data.
* @param array $entry Entry.
* @param array $entry_fields Entry fields.
* @param array $pdf_settings PDF settings.
* @param array $pdf_info PDF info.
*
* @return string
* @noinspection PhpUnusedParameterInspection
*/
private function get_pdf_file_path( array $form_data, array $entry, array $entry_fields, array $pdf_settings, array $pdf_info ): string {
$form_id = (int) $pdf_info['form_id'];
$entry_id = (int) $pdf_info['entry_id'];
$pdf_dir = Storage::get_dir( $form_id, $entry_id );
$pdf_file = $pdf_dir . Process::get_pdf_file_name( $pdf_settings, $form_data, $entry_fields, $entry_id );
if ( ! File::exists( $pdf_file ) ) {
$this->render_file_not_found();
}
return $pdf_file;
}
/**
* Check if a user has access to PDF based on restrictions.
*
* @since 1.0.0
*
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
private function is_access_granted( array $pdf_settings ): bool {
$user_restrictions = $pdf_settings['user_restrictions'] ?? 'none';
if ( $user_restrictions === 'none' ) {
return true;
}
if ( $user_restrictions === 'loggedin' && ! is_user_logged_in() ) {
return false;
}
$current_user = wp_get_current_user();
// Use inclusive OR logic: access is granted if the user satisfies EITHER
// the role restriction OR the user ID restriction.
return $this->has_role_access( $current_user, $pdf_settings )
|| $this->has_user_id_access( $current_user, $pdf_settings );
}
/**
* Check if a user has access by role.
*
* @since 1.0.0
*
* @param WP_User|null $current_user Current user object.
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
private function has_role_access( ?WP_User $current_user, array $pdf_settings ): bool {
// No role restriction configured or no user — deny role-based access.
if ( empty( $pdf_settings['user_roles_restrictions'] ) || ! $current_user ) {
return false;
}
$roles = is_string( $pdf_settings['user_roles_restrictions'] )
? json_decode( $pdf_settings['user_roles_restrictions'], true )
: $pdf_settings['user_roles_restrictions'];
return empty( $roles ) || array_intersect( $current_user->roles, $roles );
}
/**
* Check if a user has access via the user ID.
*
* @since 1.0.0
*
* @param WP_User|null $current_user Current user object.
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
private function has_user_id_access( ?WP_User $current_user, array $pdf_settings ): bool {
// No user ID restriction configured or no user — deny user-based access.
if ( empty( $pdf_settings['user_names_restrictions'] ) || ! $current_user ) {
return false;
}
$user_ids = is_string( $pdf_settings['user_names_restrictions'] )
? json_decode( $pdf_settings['user_names_restrictions'], true )
: $pdf_settings['user_names_restrictions'];
// Convert user IDs to integers.
$user_ids = array_map( 'intval', $user_ids );
return empty( $user_ids ) || in_array( $current_user->ID, $user_ids, true );
}
/**
* Check the password for PDF.
*
* @since 1.0.0
*
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
private function check_password( array $pdf_settings ): bool {
if ( ! isset( $_POST['wpforms_pdf_password'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
return false;
}
$pw_submitted = sanitize_text_field( wp_unslash( $_POST['wpforms_pdf_password'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
$hash_pw = $pdf_settings['protection_password'];
if ( hash_equals( $hash_pw, $pw_submitted ) ) {
return true;
}
return false;
}
/**
* Render access denied page.
*
* @since 1.0.0
*/
private function render_access_denied(): void {
status_header( 403 );
include wp_normalize_path( WPFORMS_PDF_PATH . 'templates/access/file-protected.php' );
exit;
}
/**
* Render file not found page.
*
* @since 1.0.0
*/
private function render_file_not_found(): void {
status_header( 404 );
include wp_normalize_path( WPFORMS_PDF_PATH . 'templates/access/file-not-found.php' );
exit;
}
/**
* Render password form.
*
* @since 1.0.0
*
* @param string $hash Hash from URL.
* @param string $error_message Error message (optional).
*
* @noinspection PhpUnusedParameterInspection
*/
private function render_password_form( string $hash, string $error_message ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
status_header( 200 );
include wp_normalize_path( WPFORMS_PDF_PATH . 'templates/access/file-password-required.php' );
exit;
}
/**
* Serve the PDF file for download.
*
* @since 1.0.0
*
* @param string $pdf_file PDF file path.
*/
private function serve_pdf_file( string $pdf_file ): void {
if ( ! file_exists( $pdf_file ) ) {
$this->render_file_not_found();
}
header( 'Content-Description: File Transfer' );
header( 'Content-Type: application/pdf' );
header( 'Content-Disposition: attachment; filename="' . basename( $pdf_file ) . '"' );
header( 'Content-Transfer-Encoding: binary' );
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate' );
header( 'Pragma: public' );
header( 'Content-Length: ' . filesize( $pdf_file ) );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo File::get_contents( $pdf_file );
exit;
}
/**
* Generate hash for a download link.
*
* @since 1.0.0
*
* @param int $form_id Form ID.
* @param int $entry_id Entry ID.
* @param int $pdf_id PDF ID.
*
* @return string
*/
public static function generate_hash( int $form_id, int $entry_id, int $pdf_id ): string {
$data = $form_id . '|' . $entry_id . '|' . $pdf_id;
$token = wp_hash( $data . self::nonce_salt() );
return base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
wp_json_encode(
[
'form_id' => $form_id,
'entry_id' => $entry_id,
'pdf_id' => $pdf_id,
'token' => $token,
]
)
);
}
/**
* Get nonce salt.
*
* @since 1.0.0
*
* @return string
*/
private static function nonce_salt(): string {
return defined( 'NONCE_SALT' ) ? NONCE_SALT : 'wpforms-pdf-nonce-salt';
}
/**
* Decode hash from URL.
*
* @since 1.0.0
*
* @param string $hash Hash from URL.
*
* @return array|false
*/
private function decode_hash( string $hash ) {
$data = json_decode( base64_decode( $hash ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
if ( ! is_array( $data ) ) {
return false;
}
$expected = wp_hash( $data['form_id'] . '|' . $data['entry_id'] . '|' . $data['pdf_id'] . self::nonce_salt() );
if ( empty( $data['token'] ) || $data['token'] !== $expected ) {
return false;
}
return $data;
}
/**
* Generate the full download URL for a protected PDF.
*
* @since 1.0.0
*
* @param int $form_id Form ID.
* @param int $entry_id Entry ID.
* @param int $pdf_id PDF ID.
*
* @return string Full download URL.
*/
public static function generate_pdf_download_url( int $form_id, int $entry_id, int $pdf_id ): string {
$hash = self::generate_hash( $form_id, $entry_id, $pdf_id );
return add_query_arg( 'wpforms_pdf_download', rawurlencode( $hash ), home_url( '/' ) );
}
/**
* Check if a PDF file is protected.
*
* @since 1.0.0
*
* @param array $pdf_settings PDF settings.
*
* @return bool
*/
public static function is_file_protected( array $pdf_settings ): bool {
// Access protection is not toggled on.
if ( empty( $pdf_settings['access'] ) ) {
return false;
}
// It is not either user restriction enabled or password protection enabled.
if ( $pdf_settings['user_restrictions'] === 'none' && empty( $pdf_settings['is_protected'] ) ) {
return false;
}
return true;
}
}