<?php

/**
 * Plugin Name: EPE Formetto - PDF Attachments for Contact Forms
 * Plugin URI: https://epetechsolutions.com
 * Description: Automatically generate a PDF from Contact Form 7 submissions and attach it to the email. Great for leads, records, and client documentation.
 * Version: 1.0.1
 * Author: EPE Tech Solutions
 * Author URI: https://epetechsolutions.com/about/
 * License: GPLv2 or later
 * Text Domain: epe-formetto-pdf-attachments-for-contact-forms
 * Requires Plugins: contact-form-7
 */

if (!defined('ABSPATH')) exit;

define('CF7_PDF_GEN_VERSION', '1.0.1');
define('CF7_PDF_GEN_PATH', plugin_dir_path(__FILE__));
define('CF7_PDF_GEN_URL', plugin_dir_url(__FILE__));

/**
 * Keep internal slug stable for settings page + uploads folder.
 * (This is internal naming; it does NOT need to match the WordPress.org permalink.)
 */
define('CF7_PDF_GEN_SLUG', 'epe-formetto-pdf-attachments');

/**
 * Canonical plugin name and URL used in templates and admin UI links.
 * IMPORTANT: This is user-facing and must not imply affiliation with Contact Form 7.
 */
define('CF7_PDF_GEN_BRAND_NAME', 'EPE Formetto');
define('CF7_PDF_GEN_BRAND_URL',  'https://epetechsolutions.com');

/**
 * Load plugin modules.
 */
require_once CF7_PDF_GEN_PATH . 'includes/admin-settings.php';
require_once CF7_PDF_GEN_PATH . 'includes/watermark.php';
require_once CF7_PDF_GEN_PATH . 'includes/pdf-generator.php';

/**
 * Check whether Contact Form 7 is available.
 */
function cf7_pdf_gen_is_cf7_active(): bool
{
  return defined('WPCF7_VERSION');
}

/**
 * A lightweight “enabled” check for use across plugin modules.
 * The plugin can only operate when CF7 is active and a form is selected.
 */
function cf7_pdf_gen_is_enabled(): bool
{
  if (!cf7_pdf_gen_is_cf7_active()) return false;

  if (defined('CF7_PDF_GEN_OPT_ALLOWED_FORM_ID')) {
    $allowed = (string) get_option(CF7_PDF_GEN_OPT_ALLOWED_FORM_ID, '');
    return ($allowed !== '');
  }

  return true;
}

/**
 * Store generated PDF path per submission "unit_tag" so it can be attached to email.
 */
$GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'] = array();

/**
 * Cleanup at end of request (covers scenarios where CF7 sends multiple mails).
 */
add_action('shutdown', function () {
  if (isset($GLOBALS['cf7_pdf_gen_pdf_by_unit_tag']) && is_array($GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'])) {
    $GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'] = array();
  }
});

/**
 * Generate the PDF before CF7 sends mail, so it can be attached.
 */
add_action('wpcf7_before_send_mail', 'cf7_pdf_gen_handle_submission', 10, 1);

function cf7_pdf_gen_handle_submission($contact_form): void
{
  if (!cf7_pdf_gen_is_cf7_active()) return;
  if (!is_object($contact_form) || !method_exists($contact_form, 'id')) return;

  $form_id = (int) $contact_form->id();

  // Respect the selected form configured in plugin settings.
  if (!function_exists('cf7_pdf_gen_is_form_allowed') || !cf7_pdf_gen_is_form_allowed((string)$form_id)) {
    return;
  }

  if (!class_exists('WPCF7_Submission')) return;

  $submission = WPCF7_Submission::get_instance();
  if (!$submission) return;

  $unit_tag = (string) $submission->get_meta('unit_tag');
  if ($unit_tag === '') return;

  // Avoid regenerating a PDF if the same submission triggers more than once.
  if (!empty($GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'][$unit_tag])) {
    $existing = $GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'][$unit_tag];
    if (is_string($existing) && $existing !== '' && file_exists($existing)) {
      return;
    }
  }

  if (!function_exists('cf7_pdf_gen_create_pdf')) return;

  $filepath = cf7_pdf_gen_create_pdf($contact_form);

  if (is_string($filepath) && $filepath !== '' && file_exists($filepath)) {
    $GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'][$unit_tag] = $filepath;
  }
}

/**
 * Attach the generated PDF to outgoing CF7 email.
 */
add_filter('wpcf7_mail_components', 'cf7_pdf_gen_attach_pdf_to_mail', 10, 3);

function cf7_pdf_gen_attach_pdf_to_mail($components, $contact_form, $mail_tag)
{
  if (!cf7_pdf_gen_is_cf7_active()) return $components;
  if (!class_exists('WPCF7_Submission')) return $components;

  $submission = WPCF7_Submission::get_instance();
  if (!$submission) return $components;

  $unit_tag = (string) $submission->get_meta('unit_tag');
  if ($unit_tag === '') return $components;

  if (empty($GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'][$unit_tag])) return $components;

  $filepath = $GLOBALS['cf7_pdf_gen_pdf_by_unit_tag'][$unit_tag];
  if (!is_string($filepath) || $filepath === '' || !file_exists($filepath)) return $components;

  // Normalize attachments into an array.
  if (!isset($components['attachments'])) {
    $components['attachments'] = array();
  }

  if (is_string($components['attachments'])) {
    $components['attachments'] = preg_split('/\s+/', trim($components['attachments']));
    if (!is_array($components['attachments'])) {
      $components['attachments'] = array();
    }
  }

  if (is_array($components['attachments'])) {
    if (!in_array($filepath, $components['attachments'], true)) {
      $components['attachments'][] = $filepath;
    }
    $components['attachments'] = array_values(array_unique(array_filter($components['attachments'])));
  }

  return $components;
}

/**
 * Mail template normalization.
 *
 * This filter only adjusts literal placeholders in CF7 templates when they are present:
 * - [_site_title]
 * - [_site_url]
 * It also updates the default CF7 footer sentence if it exists in the message body.
 */
add_filter('wpcf7_mail_components', 'cf7_pdf_gen_mail_template_normalization', 20, 3);

function cf7_pdf_gen_mail_template_normalization($components, $contact_form, $mail_tag)
{
  if (!is_array($components)) return $components;

  $brand_name = defined('CF7_PDF_GEN_BRAND_NAME') ? (string) CF7_PDF_GEN_BRAND_NAME : 'EPE Formetto';
  $brand_url  = defined('CF7_PDF_GEN_BRAND_URL') ? (string) CF7_PDF_GEN_BRAND_URL : '';

  // Subject: replace placeholders AND also replace the resolved real site title when present
  // so admins don't need to change Settings → General.
  if (!empty($components['subject']) && is_string($components['subject'])) {
    $subject = $components['subject'];

    if (strpos($subject, '[_site_title]') !== false) {
      $subject = str_replace('[_site_title]', $brand_name, $subject);
    }
    if ($brand_url !== '' && strpos($subject, '[_site_url]') !== false) {
      $subject = str_replace('[_site_url]', $brand_url, $subject);
    }

    // If CF7 already expanded the subject to "New submission: WP Dev Lab", replace the real site title.
    $site_title = (string) get_bloginfo('name');
    if ($site_title !== '' && $site_title !== $brand_name && strpos($subject, $site_title) !== false) {
      $subject = str_replace($site_title, $brand_name, $subject);
    }

    // Normalize "New submission:" subjects to always show our brand (still only affects the literal subject string).
    if (preg_match('/^\s*New submission:\s*/i', $subject)) {
      $subject = 'New submission: ' . $brand_name;
    }

    $components['subject'] = $subject;
  }

  // Body: replace placeholders and update the default CF7 footer sentence when present.
  if (!empty($components['body']) && is_string($components['body'])) {
    $body = $components['body'];

    if (strpos($body, '[_site_title]') !== false) {
      $body = str_replace('[_site_title]', $brand_name, $body);
    }
    if ($brand_url !== '' && strpos($body, '[_site_url]') !== false) {
      $body = str_replace('[_site_url]', $brand_url, $body);
    }

    // Replace the default CF7 footer sentence if it appears in the body.
    if ($brand_url !== '') {
      $replacement_line = 'This is a notification that a contact form was submitted via ' . $brand_name . ' (' . $brand_url . ').';
      $body = preg_replace(
        '/This is a notification that a contact form was submitted on your website\s*\(.*?\)\./i',
        $replacement_line,
        $body
      );
    } else {
      $body = preg_replace(
        '/This is a notification that a contact form was submitted on your website\s*\(.*?\)\./i',
        'This is a notification that a contact form was submitted via ' . $brand_name . '.',
        $body
      );
    }

    $components['body'] = $body;
  }

  return $components;
}

/**
 * Secure admin-only PDF download.
 */
add_action('admin_post_cf7_pdf_gen_download_pdf', 'cf7_pdf_gen_download_pdf');

function cf7_pdf_gen_download_pdf(): void
{
  if (!current_user_can('manage_options')) {
    wp_die('Forbidden', 403);
  }

  $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
  if (!wp_verify_nonce($nonce, 'cf7_pdf_gen_download_pdf')) {
    wp_die('Invalid nonce', 403);
  }

  $rel = isset($_GET['file']) ? sanitize_text_field(wp_unslash($_GET['file'])) : '';
  $rel = ltrim($rel, "/\\");
  $rel = str_replace(array("\\0", "\\r", "\\n"), '', $rel);

  if ($rel === '' || strpos($rel, '..') !== false) {
    wp_die('Invalid file', 400);
  }

  $upload = wp_upload_dir();
  if (empty($upload['basedir'])) {
    wp_die('Uploads not available', 500);
  }

  $base_dir  = rtrim($upload['basedir'], "/\\") . '/' . CF7_PDF_GEN_SLUG;
  $base_real = realpath($base_dir);
  if (!$base_real) {
    wp_die('Base folder missing', 404);
  }

  $target = $base_dir . '/' . $rel;
  $target_real = realpath($target);

  if (!$target_real || !file_exists($target_real)) {
    wp_die('File not found', 404);
  }

  $base_real = rtrim($base_real, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
  if (strpos($target_real, $base_real) !== 0) {
    wp_die('Invalid path', 403);
  }

  if (strtolower(pathinfo($target_real, PATHINFO_EXTENSION)) !== 'pdf') {
    wp_die('Invalid file type', 400);
  }

  // Use WP_Filesystem to satisfy Plugin Check (avoid readfile()).
  global $wp_filesystem;

  if (!function_exists('WP_Filesystem')) {
    require_once ABSPATH . 'wp-admin/includes/file.php';
  }

  WP_Filesystem();

  if (!$wp_filesystem) {
    wp_die('Filesystem unavailable', 500);
  }

  $contents = $wp_filesystem->get_contents($target_real);
  if ($contents === false || $contents === '') {
    wp_die('Could not read file', 500);
  }

  nocache_headers();
  header('Content-Type: application/pdf');
  header('Content-Disposition: inline; filename="' . basename($target_real) . '"');
  header('Content-Length: ' . strlen($contents));

  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary PDF output must not be escaped.
  echo $contents;
  exit;
}

/**
 * Secure admin-only PDF delete (used by admin settings "Recent PDFs" section)..
 */
add_action('admin_post_cf7_pdf_gen_delete_pdf', 'cf7_pdf_gen_delete_pdf');

function cf7_pdf_gen_delete_pdf(): void
{
  if (!current_user_can('manage_options')) {
    wp_die('Forbidden', 403);
  }

  $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
  if (!wp_verify_nonce($nonce, 'cf7_pdf_gen_delete_pdf')) {
    wp_die('Invalid nonce', 403);
  }

  $rel = isset($_GET['file']) ? sanitize_text_field(wp_unslash($_GET['file'])) : '';
  $rel = ltrim($rel, "/\\");
  $rel = str_replace(array("\\0", "\\r", "\\n"), '', $rel);

  if ($rel === '' || strpos($rel, '..') !== false) {
    wp_die('Invalid file', 400);
  }

  $upload = wp_upload_dir();
  if (empty($upload['basedir'])) {
    wp_die('Uploads not available', 500);
  }

  $base_dir  = rtrim($upload['basedir'], "/\\") . '/' . CF7_PDF_GEN_SLUG;
  $base_real = realpath($base_dir);
  if (!$base_real) {
    wp_die('Base folder missing', 404);
  }

  $target = $base_dir . '/' . $rel;
  $target_real = realpath($target);

  if (!$target_real || !file_exists($target_real)) {
    wp_die('File not found', 404);
  }

  $base_real = rtrim($base_real, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
  if (strpos($target_real, $base_real) !== 0) {
    wp_die('Invalid path', 403);
  }

  if (strtolower(pathinfo($target_real, PATHINFO_EXTENSION)) !== 'pdf') {
    wp_die('Invalid file type', 400);
  }

  wp_delete_file($target_real);

  if (file_exists($target_real)) {
    wp_die('Could not delete file', 500);
  }

  $back = admin_url('options-general.php?page=' . rawurlencode(CF7_PDF_GEN_SLUG));
  $back = add_query_arg(array('cf7_pdf_gen_msg' => 'deleted'), $back);

  wp_safe_redirect($back);
  exit;
}

/**
 * Build a secure admin-only URL to open a generated PDF.
 * $relative_path example: "form-9/cf7-form-9-....pdf"
 */
function cf7_pdf_gen_admin_pdf_url(string $relative_path): string
{
  $relative_path = ltrim($relative_path, "/\\");
  $relative_path = str_replace(array("\\0", "\\r", "\\n"), '', $relative_path);

  $url = admin_url('admin-post.php?action=cf7_pdf_gen_download_pdf&file=' . rawurlencode($relative_path));
  $url = wp_nonce_url($url, 'cf7_pdf_gen_download_pdf');

  return $url;
}

/**
 * Build a secure admin-only URL to delete a generated PDF.
 */
function cf7_pdf_gen_admin_pdf_delete_url(string $relative_path): string
{
  $relative_path = ltrim($relative_path, "/\\");
  $relative_path = str_replace(array("\\0", "\\r", "\\n"), '', $relative_path);

  $url = admin_url('admin-post.php?action=cf7_pdf_gen_delete_pdf&file=' . rawurlencode($relative_path));
  $url = wp_nonce_url($url, 'cf7_pdf_gen_delete_pdf');

  return $url;
}
