FILE 1: /home/custom-business-cards.digitalprank.com/public_html/tool_config.json
code
JSON
{
"tool": {
"identity": {
"slug": "custom-business-cards",
"name": "Custom Business Card Generator",
"category": "prank",
"tagline": "Design hilarious custom business cards in seconds!",
"description": "Create novelty business cards with custom names, titles, and companies. Great for jokes, roleplay, or party fun.",
"keywords": ["custom business card", "prank card", "funny title", "novelty contact", "digital prank"]
},
"features": {
"bulk_enabled": false,
"history_enabled": true,
"export_enabled": true,
"api_enabled": true
},
"fields": [
{
"id": "full_name",
"type": "text",
"label": "Name",
"placeholder": "e.g., Dr. McLovin",
"required": true,
"validation": {
"pattern": "^.{2,100}$",
"min_length": 2,
"max_length": 100
},
"pro_only": false,
"help_text": "Enter the full name to appear on the card."
},
{
"id": "job_title",
"type": "text",
"label": "Job Title",
"placeholder": "e.g., Undercover Time Traveler",
"required": true,
"validation": {
"pattern": "^.{2,100}$",
"min_length": 2,
"max_length": 100
},
"pro_only": false,
"help_text": "Use something serious... or hilarious!"
},
{
"id": "company_name",
"type": "text",
"label": "Company Name",
"placeholder": "e.g., BananaCorp Inc.",
"required": true,
"validation": {
"pattern": "^.{2,100}$",
"min_length": 2,
"max_length": 100
},
"pro_only": false,
"help_text": "This will be the custom business or organization."
},
{
"id": "email",
"type": "text",
"label": "Email Address",
"placeholder": "e.g., ceo@bananacorp.lol",
"required": false,
"validation": {
"pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
},
"pro_only": false,
"help_text": "Totally real... or totally fake."
},
{
"id": "phone",
"type": "text",
"label": "Phone Number",
"placeholder": "e.g., 555-867-5309",
"required": false,
"validation": {
"pattern": "^[0-9+\\-() ]{7,20}$"
},
"pro_only": false,
"help_text": "Any number works. Be creative!"
},
{
"id": "website",
"type": "text",
"label": "Website URL",
"placeholder": "e.g., www.bananacorp.lol",
"required": false,
"validation": {
"pattern": "^(https?:\\/\\/)?([\\w.-]+)\\.([a-z]{2,})(\\/[\\w.-]*)*$"
},
"pro_only": false,
"help_text": "Optional — adds a web link to the card."
},
{
"id": "design_template",
"type": "select",
"label": "Card Design Template",
"default": "classic_clean",
"options": [
{ "value": "classic_clean", "label": "Classic Clean" },
{ "value": "modern_bold", "label": "Modern Bold" },
{ "value": "funny_cartoon", "label": "Funny Cartoon" },
{ "value": "vintage", "label": "Retro Vintage" },
{ "value": "custom_pro", "label": "Custom Upload (Pro)" }
],
"pro_only": false,
"help_text": "Pick your card style. More available in Pro."
},
{
"id": "custom_logo",
"type": "file",
"label": "Upload Custom Logo (Pro)",
"required": false,
"pro_only": true,
"help_text": "PNG, JPG, or WEBP only. Max 2MB."
},
{
"id": "qr_code",
"type": "checkbox",
"label": "Include QR Code (to website/email)",
"default": false,
"pro_only": true,
"help_text": "Add a scannable QR code linking to your site or contact."
}
],
"limits": {
"tier_daily": {
"free": 3,
"basic": 20,
"gold": 200,
"ultimate": -1
},
"rate_limit_per_minute": 10,
"max_concurrent_requests": 5
},
"billing": {
"credit_cost": 1,
"one_off_enabled": true,
"one_off_price_cents": 50,
"bill_on": "success"
},
"ui": {
"theme": {
"primary_color": "#228B22",
"secondary_color": "#f4f4f4"
},
"layout": {
"show_sidebar_ads": true,
"form_style": "tabs",
"result_display": "preview_card"
}
},
"dependencies": {
"php_extensions": ["gd", "json"],
"system_packages": ["imagemagick"],
"python_packages": ["pillow", "qrcode"],
"external_apis": [],
"requires_internet": false
},
"database": {
"tool_specific_table": "custom_business_cards",
"store_results": true,
"enable_history": true,
"retention_days": 60
},
"seo": {
"meta_title": "Custom Business Card Generator | Hilarious Printable Cards",
"meta_description": "Generate funny and custom business cards for fun. Customize names, titles, companies, and designs. Print or download instantly!",
"canonical_url": "https://digitalprank.com/tools/custom-business-cards",
"structured_data": {
"type": "WebApplication",
"category": "Entertainment"
}
},
"help": {
"quick_start": [
"Step 1: Enter custom name, title, and company.",
"Step 2: Add optional contact details.",
"Step 3: Pick a card design.",
"Step 4: Click 'Generate Card'.",
"Step 5: Download, share, or print!"
],
"faq": [
{
"question": "Can I print these cards?",
"answer": "Yes, all generated cards can be downloaded in high resolution, perfect for printing."
},
{
"question": "Can I add my own logo?",
"answer": "Yes, Pro users can upload a custom logo in PNG, JPG, or WEBP format."
},
{
"question": "Is there a watermark?",
"answer": "Free cards include a small watermark. Pro users can remove it."
}
],
"examples": [
{
"title": "Secret Spy Identity",
"description": "Funny alias for a party prank.",
"input": {
"full_name": "Agent NoName",
"job_title": "Covert Sandwich Operative",
"company_name": "Secret Subs",
"design_template": "funny_cartoon"
}
},
{
"title": "CEO of Bananas",
"description": "Corporate satire example.",
"input": {
"full_name": "Bobby Peel",
"job_title": "Chief Banana Officer",
"company_name": "BananaCorp Inc.",
"design_template": "modern_bold"
}
}
]
},
"monitoring": {
"track_usage": true,
"track_performance": true,
"error_reporting": true,
"analytics_events": [
"card_generated",
"custom_logo_uploaded",
"pro_upgrade"
]
},
"security": {
"input_sanitization": true,
"csrf_protection": true,
"rate_limiting": true,
"blocked_domains": [],
"max_input_length": 500,
"allowed_file_types": ["png", "jpg", "jpeg", "webp"]
},
"version": {
"schema": "3.0.0",
"tool": "1.0.0",
"api_version": "v1"
}
}
}```
### FILE 2: /home/custom-business-cards.digitalprank.com/public_html/processor.php
```php
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database connection failed.']);
exit;
}
// --- Initial Setup ---
$startTime = microtime(true);
$config = json_decode(file_get_contents('tool_config.json'), true)['tool'];
$tool_slug = $config['identity']['slug'];
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0;
$user_ip = $_SERVER['REMOTE_ADDR'];
$response = [
'success' => false,
'message' => 'An unknown error occurred.',
'data' => null,
'usage' => [],
'access' => [],
'features' => $config['features']
];
// --- CSRF Protection ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
$response['message'] = 'Invalid CSRF token.';
echo json_encode($response);
exit;
}
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Regenerate token
$response['csrf_token'] = $_SESSION['csrf_token'];
// --- Get User Access & Overrides ---
$access = getUserAccessLevel($pdo, $user_id, $tool_slug);
$response['access'] = $access;
$daily_limit = $config['limits']['tier_daily'][$access['tier']];
$field_overrides = getToolFieldOverrides($pdo, $tool_slug);
// --- Check Usage Limits ---
if (!checkDailyUsage($pdo, $tool_slug, $user_ip, $user_id, $daily_limit)) {
http_response_code(429);
$response['message'] = 'You have exceeded your daily usage limit for this tool.';
echo json_encode($response);
exit;
}
$response['usage'] = ['daily_limit' => $daily_limit === -1 ? 'unlimited' : $daily_limit];
// --- Main Processing Logic ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = $_POST;
$files = $_FILES;
$errors = [];
// {{TOOL_PROCESSING_START}}
// --- Validation and Sanitization ---
foreach ($config['fields'] as $field) {
$id = $field['id'];
// Check if field is disabled by override
if (isset($field_overrides[$id]) && $field_overrides[$id]['override_type'] === 'disabled') {
continue;
}
// Check tier access for the field
if (isset($field_overrides[$id]) && $field_overrides[$id]['override_type'] === 'tier') {
if (!$access['tiers'][$field_overrides[$id]['tier_required']]) {
if (isset($input[$id]) && !empty($input[$id])) {
$errors[] = "Field '{$field['label']}' requires a higher subscription tier.";
}
continue;
}
} else if ($field['pro_only'] && !$access['has_pro_access']) {
if ((isset($input[$id]) && !empty($input[$id])) || (isset($files[$id]) && $files[$id]['error'] === UPLOAD_ERR_OK)) {
$errors[] = "Field '{$field['label']}' is a Pro feature. Please upgrade your plan.";
}
continue;
}
// Handle file uploads
if ($field['type'] === 'file') {
if (isset($files[$id]) && $files[$id]['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $files[$id]['tmp_name'];
$file_size = $files[$id]['size'];
$file_type = mime_content_type($file_tmp_path);
$allowed_types = ['image/png', 'image/jpeg', 'image/webp'];
if (!in_array($file_type, $allowed_types) || $file_size > 2 * 1024 * 1024) {
$errors[] = "Invalid file for '{$field['label']}'. PNG, JPG, WEBP only, max 2MB.";
} else {
$input[$id] = $files[$id];
}
}
continue;
}
// Handle checkbox
if ($field['type'] === 'checkbox') {
$input[$id] = isset($input[$id]);
continue;
}
$value = isset($input[$id]) ? trim($input[$id]) : '';
$input[$id] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
if ($field['required'] && $value === '') {
$errors[] = "Field '{$field['label']}' is required.";
}
if ($value !== '' && isset($field['validation']['pattern'])) {
if (!preg_match('/' . $field['validation']['pattern'] . '/', $value)) {
$errors[] = "Invalid format for '{$field['label']}'.";
}
}
}
if (!empty($errors)) {
http_response_code(400);
$response['message'] = implode(' ', $errors);
$log_status = 'failed';
$log_output = ['errors' => $errors];
} else {
try {
// --- Image Generation Logic ---
$template = $input['design_template'];
$template_path = "assets/templates/{$template}.png";
if (!file_exists($template_path)) {
throw new Exception("Design template '{$template}' not found.");
}
$image = imagecreatefrompng($template_path);
if ($image === false) {
throw new Exception("Failed to load the template image.");
}
// Define colors and fonts
$font_path = 'assets/fonts/Roboto-Regular.ttf';
$font_bold_path = 'assets/fonts/Roboto-Bold.ttf';
$text_color = imagecolorallocate($image, 30, 30, 30);
$company_color = imagecolorallocate($image, 20, 100, 20);
// Draw text onto the image
imagettftext($image, 30, 0, 50, 100, $text_color, $font_bold_path, $input['full_name']);
imagettftext($image, 22, 0, 50, 150, $text_color, $font_path, $input['job_title']);
imagettftext($image, 24, 0, 50, 250, $company_color, $font_bold_path, $input['company_name']);
$contact_y_pos = 350;
if (!empty($input['phone'])) {
imagettftext($image, 18, 0, 50, $contact_y_pos, $text_color, $font_path, 'P: ' . $input['phone']);
$contact_y_pos += 30;
}
if (!empty($input['email'])) {
imagettftext($image, 18, 0, 50, $contact_y_pos, $text_color, $font_path, 'E: ' . $input['email']);
$contact_y_pos += 30;
}
if (!empty($input['website'])) {
imagettftext($image, 18, 0, 50, $contact_y_pos, $text_color, $font_path, 'W: ' . $input['website']);
}
// Handle custom logo (Pro feature)
$has_custom_logo = false;
if ($access['has_pro_access'] && isset($input['custom_logo']) && is_array($input['custom_logo'])) {
$logo_path = $input['custom_logo']['tmp_name'];
$logo_info = getimagesize($logo_path);
$logo_img = null;
switch($logo_info[2]) {
case IMAGETYPE_JPEG: $logo_img = imagecreatefromjpeg($logo_path); break;
case IMAGETYPE_PNG: $logo_img = imagecreatefrompng($logo_path); break;
case IMAGETYPE_WEBP: $logo_img = imagecreatefromwebp($logo_path); break;
}
if ($logo_img) {
imagecopyresampled($image, $logo_img, 700, 50, 0, 0, 150, 150, imagesx($logo_img), imagesy($logo_img));
imagedestroy($logo_img);
$has_custom_logo = true;
}
}
// Handle QR Code (Pro feature)
$has_qr_code = false;
if ($access['has_pro_access'] && $input['qr_code'] && (!empty($input['website']) || !empty($input['email']))) {
$qr_data = !empty($input['website']) ? $input['website'] : 'mailto:' . $input['email'];
$qr_code_file = sys_get_temp_dir() . '/' . uniqid('qr_', true) . '.png';
// Use python script specified in dependencies
$command = "python3 ../venv/bin/generate_qr.py " . escapeshellarg($qr_data) . " " . escapeshellarg($qr_code_file);
exec($command, $output, $return_var);
if ($return_var === 0 && file_exists($qr_code_file)) {
$qr_img = imagecreatefrompng($qr_code_file);
imagecopyresampled($image, $qr_img, 700, 300, 0, 0, 150, 150, imagesx($qr_img), imagesy($qr_img));
imagedestroy($qr_img);
unlink($qr_code_file);
$has_qr_code = true;
}
}
// Add watermark for free users
if (!$access['has_pro_access']) {
$watermark_text = 'digitalprank.com';
$watermark_color = imagecolorallocatealpha($image, 0, 0, 0, 90);
imagettftext($image, 10, 0, 820, 480, $watermark_color, $font_path, $watermark_text);
}
// Save the final image
$output_dir = 'results';
if (!is_dir($output_dir)) {
mkdir($output_dir, 0755, true);
}
$filename = uniqid($tool_slug . '_', true) . '.png';
$filepath = $output_dir . '/' . $filename;
imagepng($image, $filepath);
imagedestroy($image);
$response['success'] = true;
$response['message'] = 'Business card generated successfully!';
$response['data'] = ['imageUrl' => '/' . $filepath];
$log_status = 'success';
$log_output = $response['data'];
// Store result in database if configured
if ($config['database']['store_results']) {
$stmt = $pdo->prepare(
"INSERT INTO custom_business_cards (user_id, session_id, full_name, job_title, company_name, email, phone, website, design_template, has_custom_logo, has_qr_code, image_path)
VALUES (:user_id, :session_id, :full_name, :job_title, :company_name, :email, :phone, :website, :design_template, :has_custom_logo, :has_qr_code, :image_path)"
);
$stmt->execute([
':user_id' => $user_id,
':session_id' => session_id(),
':full_name' => $input['full_name'],
':job_title' => $input['job_title'],
':company_name' => $input['company_name'],
':email' => $input['email'],
':phone' => $input['phone'],
':website' => $input['website'],
':design_template' => $input['design_template'],
':has_custom_logo' => (int)$has_custom_logo,
':has_qr_code' => (int)$has_qr_code,
':image_path' => $filepath
]);
}
} catch (Exception $e) {
http_response_code(500);
$response['message'] = 'An internal error occurred during image generation: ' . $e->getMessage();
$log_status = 'failed';
$log_output = ['error' => $e->getMessage()];
}
}
// --- Logging ---
$processing_time = microtime(true) - $startTime;
logUsage($pdo, $tool_slug, $user_ip, $user_id, 'generate', $input, $log_output, $processing_time, $log_status);
// {{TOOL_PROCESSING_END}}
}
// --- Final Output ---
echo json_encode($response);
// --- Universal Helper Functions ---
function getToolFieldOverrides($pdo, $tool_slug) {
$stmt = $pdo->prepare("
SELECT o.field_id, o.tier_required, o.override_type
FROM wp_digitalprank_tool_overrides o
JOIN wp_digitalprank_tools t ON o.tool_id = t.tool_id
WHERE t.slug = :tool_slug AND o.is_active = 1
");
$stmt->execute([':tool_slug' => $tool_slug]);
$overrides = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$overrides[$row['field_id']] = $row;
}
return $overrides;
}
?>
FILE 3: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php
code
PHP
FILE 4: /home/custom-business-cards.digitalprank.com/public_html/diagnostic.php
code
PHP
SUCCESS: $success_msg";
} else {
return "FAILURE: $failure_msg ";
}
}
?>
Diagnostic Report:
Diagnostic Report:
1. Configuration File
2. Database Connectivity
3. Required Tables
query("SELECT 1 FROM `{$table}` LIMIT 1");
echo check_status($result !== false, "Table '{$table}' exists.", "Table '{$table}' does not exist or is not accessible.");
} catch (Exception $e) {
echo check_status(false, "", "Error checking table '{$table}': " . $e->getMessage());
}
}
} else {
echo "SKIPPED: Cannot check tables without a database connection. ";
}
?>
4. Dependencies
5. Tool-Specific Functionality Test
SKIPPED: GD not loaded or template missing.";
}
$qr_script_path = '../venv/bin/generate_qr.py';
echo check_status(file_exists($qr_script_path) && is_executable($qr_script_path), "QR generator script '{$qr_script_path}' is executable.", "QR generator script '{$qr_script_path}' is missing or not executable.");
?>
6. File Permissions
Diagnostic complete.
FILE 5: /home/custom-business-cards.digitalprank.com/public_html/help.md
code
Markdown
# Help: Custom Business Card Generator
## Quick Start Guide
Here's how to create your hilarious custom business card in under a minute:
- **Step 1:** Enter custom name, title, and company.
- **Step 2:** Add optional contact details.
- **Step 3:** Pick a card design.
- **Step 4:** Click 'Generate Card'.
- **Step 5:** Download, share, or print!
---
## Features
This tool allows you to create novelty business cards with custom names, titles, and companies. It's great for jokes, roleplaying, or party fun.
### Free Features
* **Core Fields:** Access to Name, Job Title, Company, and optional contact details (Email, Phone, Website).
* **Standard Designs:** Choose from several built-in design templates like "Classic Clean", "Modern Bold", and "Funny Cartoon".
* **Instant Downloads:** Download your generated card as a high-quality PNG image.
### Pro Features
* **Custom Logo Upload:** Upgrade to Pro to upload your own logo (PNG, JPG, or WEBP) and have it placed on the card for a personalized touch.
* **QR Code Generation:** Pro users can automatically generate and add a scannable QR code that links to the provided website or email address.
* **No Watermark:** Cards generated by Pro users do not have the "digitalprank.com" watermark.
* **Higher Usage Limits:** Pro tiers come with significantly more daily generations.
---
## Frequently Asked Questions (FAQ)
**Q: Can I print these cards?**
> A: Yes, all generated cards can be downloaded in high resolution, perfect for printing.
**Q: Can I add my own logo?**
> A: Yes, Pro users can upload a custom logo in PNG, JPG, or WEBP format.
**Q: Is there a watermark?**
> A: Free cards include a small watermark. Pro users can remove it.
---
## Usage Examples
### 1. Secret Spy Identity
A funny alias for a party prank.
* **Name:** Agent NoName
* **Job Title:** Covert Sandwich Operative
* **Company Name:** Secret Subs
* **Card Design:** Funny Cartoon
### 2. CEO of Bananas
A corporate satire example for a gag gift.
* **Name:** Bobby Peel
* **Job Title:** Chief Banana Officer
* **Company Name:** BananaCorp Inc.
* **Card Design:** Modern Bold
FILE 6: /home/digitalprank.com/public_html/blog/data/tools/custom-business-cards.json
code
JSON
{
"slug": "custom-business-cards",
"name": "Custom Business Card Generator",
"meta_title": "Custom Business Card Generator | Hilarious Printable Cards",
"meta_description": "Generate funny and custom business cards for fun. Customize names, titles, companies, and designs. Print or download instantly!",
"canonical_url": "https://digitalprank.com/tools/custom-business-cards",
"schema": {
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Custom Business Card Generator",
"description": "Create novelty business cards with custom names, titles, and companies. Great for jokes, roleplay, or party fun.",
"applicationCategory": "Entertainment",
"operatingSystem": "Any (Web-based)",
"url": "https://custom-business-cards.digitalprank.com",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
},
"features_summary": [
{
"feature": "Customizable Text",
"description": "Enter any name, job title, and company you can imagine.",
"is_pro": false
},
{
"feature": "Multiple Templates",
"description": "Choose from a variety of design templates, from professional to comical.",
"is_pro": false
},
{
"feature": "Instant Download",
"description": "Download your creation as a high-resolution PNG file, ready for printing.",
"is_pro": false
},
{
"feature": "Custom Logo Upload",
"description": "Personalize your card by uploading your own logo image.",
"is_pro": true
},
{
"feature": "QR Code Generation",
"description": "Add a scannable QR code linking to your website or email for an authentic touch.",
"is_pro": true
},
{
"feature": "No Watermarks",
"description": "Pro members can generate cards completely free of any watermarks.",
"is_pro": true
}
],
"user_guide": {
"title": "How to Create the Perfect Custom Business Card",
"steps": [
"Fill in the essential details: Name, Job Title, and Company Name. Be creative!",
"Add optional contact information like a custom email or phone number to make it more believable.",
"Select a design template that matches the persona you're creating.",
"For Pro users: Upload a custom logo or check the box to add a QR code.",
"Click the 'Generate Card' button to see your masterpiece.",
"Right-click the generated image to save it to your device for printing or sharing."
]
},
"technical_details": {
"backend": "PHP 8.1 with GD Graphics Library",
"dependencies": "ImageMagick, Python (for QR codes)",
"output_format": "PNG (Portable Network Graphics)"
}
}
FILE 7: /home/custom-business-cards.digitalprank.com/public_html/deploy.sh```bash
#!/bin/bash
Deployment script for 'custom-business-cards' tool on digitalprank.com platform
set -e
TOOL_SLUG="custom-business-cards"
TOOL_DOMAIN="
T
O
O
L
S
L
U
G
.
d
i
g
i
t
a
l
p
r
a
n
k
.
c
o
m
"
T
O
O
L
D
I
R
=
"
/
h
o
m
e
/
TOOL
S
LUG.digitalprank.com"TOOL
D
IR="/home/
{TOOL_DOMAIN}/public_html"
VENV_DIR="/home/
Dealer2355"
echo "--- Starting deployment for ${TOOL_SLUG} ---"
1. Install System and PHP Dependencies
echo "[1/7] Installing system and PHP packages..."
apt-get update
apt-get install -y imagemagick python3-pip python3-venv
apt-get install -y php8.1-gd php8.1-mysql # Assuming PHP 8.1 FPM is used
2. Setup Python Virtual Environment
echo "[2/7] Setting up Python virtual environment..."
if [ ! -d "
V
E
N
V
D
I
R
"
]
;
t
h
e
n
p
y
t
h
o
n
3
−
m
v
e
n
v
"
VENV
D
IR"];thenpython3−mvenv"
{VENV_DIR}"
echo "Virtual environment created."
fi
source "${VENV_DIR}/bin/activate"
pip install --upgrade pip
pip install pillow qrcode
deactivate
echo "Python dependencies installed."
Add a helper script for the processor to call
cat << 'EOF' > ${VENV_DIR}/bin/generate_qr.py
#!/usr/bin/env python3
import qrcode
import sys
if len(sys.argv) != 3:
print("Usage: python generate_qr.py ")
sys.exit(1)
data_to_encode = sys.argv[1]
output_filename = sys.argv[2]
img = qrcode.make(data_to_encode)
img.save(output_filename)
sys.exit(0)
EOF
chmod +x ${VENV_DIR}/bin/generate_qr.py
3. Create Tool-Specific Database Table
echo "[3/7] Creating database table 'custom_business_cards'..."
SQL_COMMAND="CREATE TABLE IF NOT EXISTS `custom_business_cards` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`session_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`full_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`job_title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`company_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`website` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`design_template` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`has_custom_logo` tinyint(1) DEFAULT 0,
`has_qr_code` tinyint(1) DEFAULT 0,
`image_path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
mysql -u"
D
B
U
S
E
R
"
−
p
"
DB
U
SER"−p"
{DB_PASS}" -D"
D
B
N
A
M
E
"
−
e
"
DB
N
AME"−e"
{SQL_COMMAND}"
echo "Database table is ready."
4. Create Directories and Set Permissions
echo "[4/7] Setting up directories and permissions..."
mkdir -p "
T
O
O
L
D
I
R
/
r
e
s
u
l
t
s
"
m
k
d
i
r
−
p
"
TOOL
D
IR/results"mkdir−p"
{TOOL_DIR}/assets/templates"
mkdir -p "
T
O
O
L
D
I
R
/
a
s
s
e
t
s
/
f
o
n
t
s
"
c
h
o
w
n
−
R
w
w
w
−
d
a
t
a
:
w
w
w
−
d
a
t
a
"
/
h
o
m
e
/
TOOL
D
IR/assets/fonts"chown−Rwww−data:www−data"/home/
{TOOL_DOMAIN}"
find "/home/
T
O
O
L
D
O
M
A
I
N
"
−
t
y
p
e
d
−
e
x
e
c
c
h
m
o
d
755
f
i
n
d
"
/
h
o
m
e
/
TOOL
D
OMAIN"−typed−execchmod755find"/home/
{TOOL_DOMAIN}" -type f -exec chmod 644 {} ;
chmod 775 "${TOOL_DIR}/results" # Give write access to the results directory
echo "Directory structure and permissions configured."
5. Configure OpenLiteSpeed Virtual Host
echo "[5/7] Generating OpenLiteSpeed vHost configuration..."
OLS_VHOST_CONFIG="
vhconf.conf:
docRoot $VH_ROOT/public_html/
vhDomain ${TOOL_DOMAIN}
adminEmails admin@digitalprank.com
Consider adding rewrite rules if using a framework
rewrite.enable 1
rewrite.rules <<
L
O
G
R
O
T
A
T
E
C
O
N
F
I
G
<
<
E
O
L
/
v
a
r
/
l
o
g
/
o
l
s
/
LOGROTATE
C
ONFIG< /dev/null; then echo "PHP is not in PATH"; exit 1; fi
if ! php -m | grep -q 'gd'; then echo "GD extension missing"; exit 1; fi
if [ ! -f "
T
O
O
L
D
I
R
/
t
o
o
l
c
o
n
f
i
g
.
j
s
o
n
"
]
;
t
h
e
n
e
c
h
o
"
t
o
o
l
c
o
n
f
i
g
.
j
s
o
n
n
o
t
f
o
u
n
d
!
"
;
e
x
i
t
1
;
f
i
i
f
[
!
−
f
"
TOOL
D
IR/tool
c
onfig.json"];thenecho"tool
c
onfig.jsonnotfound!";exit1;fiif[!−f"
{TOOL_DIR}/processor.php" ]; then echo "processor.php not found!"; exit 1; fi
echo "--- Deployment for ${TOOL_SLUG} completed successfully! ---"
echo "ACTION REQUIRED: Update OpenLiteSpeed configuration and restart the server."
code
Code
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 8: /home/custom-business-cards.digitalprank.com/public_html/assets/fonts/Roboto-Regular.ttf
code
Code
[NOTE: This is a placeholder for the binary TTF font file for Roboto Regular. A real font file must be placed here for the tool to render text correctly.]
FILE 9: /home/custom-business-cards.digitalprank.com/public_html/assets/fonts/Roboto-Bold.ttf
code
Code
[NOTE: This is a placeholder for the binary TTF font file for Roboto Bold. A real font file must be placed here for the tool to render text correctly.]
FILE 10: /home/custom-business-cards.digitalprank.com/public_html/assets/templates/classic_clean.png
code
Code
[NOTE: This is a placeholder for the 'Classic Clean' PNG template image. It should be a blank or minimally styled business card background, approximately 900x500 pixels. A real PNG file must be placed here.]
FILE 11: /home/custom-business-cards.digitalprank.com/public_html/assets/templates/modern_bold.png
code
Code
[NOTE: This is a placeholder for the 'Modern Bold' PNG template image. It should be a business card background with a modern, bold design, approximately 900x500 pixels. A real PNG file must be placed here.]
FILE 12: /home/custom-business-cards.digitalprank.com/public_html/assets/templates/funny_cartoon.png
code
Code
[NOTE: This is a placeholder for the 'Funny Cartoon' PNG template image. It should be a business card background with a comical or cartoonish design, approximately 900x500 pixels. A real PNG file must be placed here.]
FILE 13: /home/custom-business-cards.digitalprank.com/public_html/assets/templates/vintage.png
code
Code
[NOTE: This is a placeholder for the 'Retro Vintage' PNG template image. It should be a business card background with a retro or vintage design, approximately 900x500 pixels. A real PNG file must be placed here.]
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 3: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED)
code
PHP
FILE 14: /home/custom-business-cards.digitalprank.com/public_html/assets/css/style.css
code
CSS
/* General Body Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: var(--secondary-color, #f4f4f4);
color: #333;
line-height: 1.6;
padding: 20px;
margin: 0;
}
/* Main Container */
.container {
max-width: 800px;
margin: auto;
background: #fff;
padding: 20px 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
/* Typography */
h1 {
color: var(--primary-color, #228B22);
text-align: center;
margin-bottom: 10px;
}
p {
text-align: center;
margin-top: 0;
color: #555;
}
/* Form Styling */
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"], select, input[type="file"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
transition: border-color 0.2s;
}
input[type="text"]:focus, select:focus {
border-color: var(--primary-color, #228B22);
outline: none;
}
.help-text {
font-size: 0.9em;
color: #666;
margin-top: 5px;
text-align: left;
}
/* Pro Features */
.pro-badge {
background-color: gold;
color: #333;
font-size: 0.7em;
padding: 2px 5px;
border-radius: 3px;
margin-left: 8px;
font-weight: bold;
vertical-align: middle;
}
/* Buttons */
button {
background-color: var(--primary-color, #228B22);
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1.1em;
width: 100%;
transition: background-color 0.2s;
}
button:hover {
opacity: 0.9;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
/* Result Area */
#result-area {
margin-top: 30px;
text-align: center;
}
#loading-spinner {
display: none;
border: 4px solid #f3f3f3;
border-top: 4px solid var(--primary-color, #228B22);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
color: #d9534f;
background: #f2dede;
border: 1px solid #ebccd1;
padding: 15px;
border-radius: 4px;
margin-top: 15px;
}
#result-card {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
margin-top: 10px;
}
#result-content h3 {
margin-bottom: 5px;
}
#result-content p {
margin-top: 0;
color: #666;
}
/* Custom Checkbox */
.checkbox-container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 1rem;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
border: 1px solid #ccc;
border-radius: 4px;
}
.checkbox-container:hover input ~ .checkmark {
background-color: #ccc;
}
.checkbox-container input:checked ~ .checkmark {
background-color: var(--primary-color, #228B22);
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.checkbox-container input:checked ~ .checkmark:after {
display: block;
}
.checkbox-container .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
FILE 15: /home/custom-business-cards.digitalprank.com/public_html/.gitignore
code
Code
# Generated files and user uploads
/results/
# Python virtual environment
/../venv/
# Local development files
.DS_Store
Thumbs.db
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 16: /home/custom-business-cards.digitalprank.com/public_html/includes/functions.php
code
PHP
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'API service unavailable.']);
exit;
}
// --- API Key Authentication ---
$api_key = $_SERVER['HTTP_X_API_KEY'] ?? '';
if (empty($api_key)) {
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'API key is missing.']);
exit;
}
$stmt = $pdo->prepare("
SELECT u.id, u.user_id, u.rate_limit, u.active
FROM wp_digitalprank_api_keys u
WHERE u.api_key = :api_key AND u.active = 1
");
$stmt->execute([':api_key' => $api_key]);
$key_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$key_data) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Invalid or inactive API key.']);
exit;
}
$user_id = $key_data['user_id'];
// --- Get Input and Config ---
$startTime = microtime(true);
$input = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Invalid JSON body.']);
exit;
}
$config = json_decode(file_get_contents('tool_config.json'), true)['tool'];
$tool_slug = $config['identity']['slug'];
$response = ['success' => false, 'message' => 'An unknown error occurred.'];
// --- Access and Usage Check (API context) ---
$access = getUserAccessLevel($pdo, $user_id, $tool_slug);
if (!$access['tiers']['ultimate']) { // Assuming API access is for Ultimate tier
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'API access requires an Ultimate tier subscription.']);
exit;
}
if (!checkDailyUsage($pdo, $tool_slug, 'api', $user_id, -1)) { // Unlimited for Ultimate
http_response_code(429);
echo json_encode(['success' => false, 'message' => 'Usage limit exceeded.']);
exit;
}
// --- Input Validation ---
$errors = [];
$sanitized_input = [];
foreach ($config['fields'] as $field) {
$id = $field['id'];
if ($field['type'] === 'file') continue; // Files not supported via this API version
$value = $input[$id] ?? ($field['default'] ?? null);
if ($field['type'] === 'checkbox') {
$sanitized_input[$id] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
} else {
$sanitized_input[$id] = is_scalar($value) ? trim((string)$value) : '';
}
if ($field['required'] && empty($sanitized_input[$id])) {
$errors[] = "Field '{$id}' is required.";
}
if (!empty($sanitized_input[$id]) && isset($field['validation']['pattern'])) {
if (!preg_match('/' . $field['validation']['pattern'] . '/', $sanitized_input[$id])) {
$errors[] = "Invalid format for '{$id}'.";
}
}
}
if (!empty($errors)) {
http_response_code(400);
$response['message'] = 'Input validation failed.';
$response['errors'] = $errors;
$log_status = 'failed';
$log_output = ['errors' => $errors];
} else {
// --- Call Core Logic ---
try {
$image_path = generateBusinessCard($sanitized_input, $access);
$full_url = "https://{$config['identity']['slug']}.digitalprank.com/{$image_path}";
$response['success'] = true;
$response['message'] = 'Business card generated successfully.';
$response['data'] = ['imageUrl' => $full_url];
$log_status = 'success';
$log_output = $response['data'];
} catch (Exception $e) {
http_response_code(500);
$response['message'] = 'Generation failed: ' . $e->getMessage();
$log_status = 'failed';
$log_output = ['error' => $e->getMessage()];
}
}
// --- Logging ---
$processing_time = microtime(true) - $startTime;
logUsage($pdo, $tool_slug, 'api:' . $key_data['id'], $user_id, 'api_generate', $input, $log_output, $processing_time, $log_status);
echo json_encode($response);
FILE 2: /home/custom-business-cards.digitalprank.com/public_html/processor.php (REVISED)
code
PHP
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database connection failed.']);
exit;
}
// --- Initial Setup ---
$startTime = microtime(true);
$config = json_decode(file_get_contents('tool_config.json'), true)['tool'];
$tool_slug = $config['identity']['slug'];
$user_id = $_SESSION['user_id'] ?? 0;
$user_ip = $_SERVER['REMOTE_ADDR'];
$response = ['success' => false, 'message' => 'An unknown error occurred.'];
// --- CSRF Protection ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
$response['message'] = 'Invalid CSRF token.';
echo json_encode($response);
exit;
}
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$response['csrf_token'] = $_SESSION['csrf_token'];
// --- Get User Access & Limits ---
$access = getUserAccessLevel($pdo, $user_id, $tool_slug);
$daily_limit = $config['limits']['tier_daily'][$access['tier']];
if (!checkDailyUsage($pdo, $tool_slug, $user_ip, $user_id, $daily_limit)) {
http_response_code(429);
$response['message'] = 'You have exceeded your daily usage limit for this tool.';
echo json_encode($response);
exit;
}
// --- Main Processing Logic ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = $_POST;
$files = $_FILES;
$errors = [];
$sanitized_input = [];
// --- Validation and Sanitization ---
foreach ($config['fields'] as $field) {
$id = $field['id'];
if ($field['pro_only'] && !$access['has_pro_access']) {
if ((isset($input[$id]) && !empty($input[$id])) || (isset($files[$id]) && $files[$id]['error'] === UPLOAD_ERR_OK)) {
$errors[] = "Field '{$field['label']}' is a Pro feature.";
}
continue;
}
if ($field['type'] === 'file') {
if (isset($files[$id]) && $files[$id]['error'] === UPLOAD_ERR_OK) {
// Basic file validation
$allowed_types = $config['security']['allowed_file_types'];
$ext = strtolower(pathinfo($files[$id]['name'], PATHINFO_EXTENSION));
if (in_array($ext, $allowed_types) && $files[$id]['size'] < 2 * 1024 * 1024) {
$sanitized_input['custom_logo_path'] = $files[$id]['tmp_name'];
} else {
$errors[] = "Invalid file for '{$field['label']}'.";
}
}
} elseif ($field['type'] === 'checkbox') {
$sanitized_input[$id] = isset($input[$id]);
} else {
$value = trim($input[$id] ?? ($field['default'] ?? ''));
$sanitized_input[$id] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
if ($field['required'] && $value === '') {
$errors[] = "Field '{$field['label']}' is required.";
}
if ($value !== '' && isset($field['validation']['pattern']) && !preg_match('/' . $field['validation']['pattern'] . '/', $value)) {
$errors[] = "Invalid format for '{$field['label']}'.";
}
}
}
if (!empty($errors)) {
http_response_code(400);
$response['message'] = implode(' ', $errors);
$log_status = 'failed';
$log_output = ['errors' => $errors];
} else {
try {
// --- Call Core Logic ---
$image_path = generateBusinessCard($sanitized_input, $access);
$response['success'] = true;
$response['message'] = 'Business card generated successfully!';
$response['data'] = ['imageUrl' => '/' . $image_path];
$log_status = 'success';
$log_output = $response['data'];
// --- Database Logging ---
if ($config['database']['store_results']) {
$stmt = $pdo->prepare(
"INSERT INTO custom_business_cards (user_id, session_id, full_name, job_title, company_name, email, phone, website, design_template, has_custom_logo, has_qr_code, image_path)
VALUES (:user_id, :session_id, :full_name, :job_title, :company_name, :email, :phone, :website, :design_template, :has_custom_logo, :has_qr_code, :image_path)"
);
$stmt->execute([
':user_id' => $user_id, ':session_id' => session_id(),
':full_name' => $sanitized_input['full_name'], ':job_title' => $sanitized_input['job_title'],
':company_name' => $sanitized_input['company_name'], ':email' => $sanitized_input['email'],
':phone' => $sanitized_input['phone'], ':website' => $sanitized_input['website'],
':design_template' => $sanitized_input['design_template'],
':has_custom_logo' => isset($sanitized_input['custom_logo_path']),
':has_qr_code' => $sanitized_input['qr_code'], ':image_path' => $image_path
]);
}
} catch (Exception $e) {
http_response_code(500);
$response['message'] = 'Generation failed: ' . $e->getMessage();
$log_status = 'failed';
$log_output = ['error' => $e->getMessage()];
}
}
$processing_time = microtime(true) - $startTime;
logUsage($pdo, $tool_slug, $user_ip, $user_id, 'generate', $input, $log_output, $processing_time, $log_status);
}
// --- Final Output ---
echo json_encode($response);
?>
FILE 7: /home/custom-business-cards.digitalprank.com/public_html/deploy.sh (REVISED)
code
Bash
#!/bin/bash
# Deployment script for 'custom-business-cards' tool on digitalprank.com platform
set -e
TOOL_SLUG="custom-business-cards"
TOOL_DOMAIN="${TOOL_SLUG}.digitalprank.com"
TOOL_DIR="/home/${TOOL_DOMAIN}/public_html"
VENV_DIR="/home/${TOOL_DOMAIN}/venv"
DB_NAME="digitalprank_db"
DB_USER="dp_user"
DB_PASS="#$Dealer2355"
echo "--- Starting deployment for ${TOOL_SLUG} ---"
# 1. Install System and PHP Dependencies
echo "[1/8] Installing system and PHP packages..."
apt-get update
apt-get install -y imagemagick python3-pip python3-venv
apt-get install -y php8.1-gd php8.1-mysql
# 2. Setup Python Virtual Environment
echo "[2/8] Setting up Python virtual environment..."
if [ ! -d "${VENV_DIR}" ]; then
python3 -m venv "${VENV_DIR}"
echo "Virtual environment created."
fi
source "${VENV_DIR}/bin/activate"
pip install --upgrade pip
pip install pillow qrcode
deactivate
cat << 'EOF' > ${VENV_DIR}/bin/generate_qr.py
#!/usr/bin/env python3
import qrcode, sys
if len(sys.argv) != 3: sys.exit(1)
img = qrcode.make(sys.argv[1])
img.save(sys.argv[2])
sys.exit(0)
EOF
chmod +x ${VENV_DIR}/bin/generate_qr.py
echo "Python dependencies installed."
# 3. Create/Update Database Tables
echo "[3/8] Creating/updating database tables..."
# Tool-specific table
SQL_TOOL_TABLE="CREATE TABLE IF NOT EXISTS \`custom_business_cards\` (
\`id\` bigint(20) NOT NULL AUTO_INCREMENT, \`user_id\` bigint(20) DEFAULT NULL, \`session_id\` varchar(255) DEFAULT NULL,
\`full_name\` varchar(100) NOT NULL, \`job_title\` varchar(100) NOT NULL, \`company_name\` varchar(100) NOT NULL,
\`email\` varchar(255) DEFAULT NULL, \`phone\` varchar(20) DEFAULT NULL, \`website\` varchar(255) DEFAULT NULL,
\`design_template\` varchar(50) NOT NULL, \`has_custom_logo\` tinyint(1) DEFAULT 0, \`has_qr_code\` tinyint(1) DEFAULT 0,
\`image_path\` varchar(255) NOT NULL, \`created_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (\`id\`), KEY \`user_id\` (\`user_id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
mysql -u"${DB_USER}" -p"${DB_PASS}" -D"${DB_NAME}" -e"${SQL_TOOL_TABLE}"
# API Keys table (if not exists)
SQL_API_TABLE="CREATE TABLE IF NOT EXISTS \`wp_digitalprank_api_keys\` (
\`id\` int(11) NOT NULL AUTO_INCREMENT, \`user_id\` bigint(20) NOT NULL,
\`api_key\` varchar(64) NOT NULL, \`rate_limit\` int(11) DEFAULT 100,
\`active\` tinyint(1) NOT NULL DEFAULT 1, \`created_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (\`id\`), UNIQUE KEY \`api_key\` (\`api_key\`), KEY \`user_id\` (\`user_id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
mysql -u"${DB_USER}" -p"${DB_PASS}" -D"${DB_NAME}" -e"${SQL_API_TABLE}"
echo "Database tables are ready."
# 4. Create Directories and Set Permissions
echo "[4/8] Setting up directories and permissions..."
mkdir -p "${TOOL_DIR}/results" "${TOOL_DIR}/assets/templates" "${TOOL_DIR}/assets/fonts" "${TOOL_DIR}/assets/css" "${TOOL_DIR}/includes"
chown -R www-data:www-data "/home/${TOOL_DOMAIN}"
find "/home/${TOOL_DOMAIN}" -type d -exec chmod 755 {} \;
find "/home/${TOOL_DOMAIN}" -type f -exec chmod 644 {} \;
chmod 775 "${TOOL_DIR}/results"
echo "Directory structure and permissions configured."
# 5. Configure OpenLiteSpeed Virtual Host
echo "[5/8] Generating OpenLiteSpeed vHost configuration..."
echo "ACTION REQUIRED: Please ensure the OpenLiteSpeed vHost for ${TOOL_DOMAIN} is configured with docRoot: \$VH_ROOT/public_html/"
# 6. Configure Log Rotation
echo "[6/8] Setting up log rotation..."
LOGROTATE_CONFIG="/etc/logrotate.d/${TOOL_SLUG}"
cat > ${LOGROTATE_CONFIG} << EOL
/var/log/ols/${TOOL_DOMAIN}.access.log {
daily missingok rotate 14 compress delaycompress notifempty
create 640 www-data adm
sharedscripts
postrotate
/usr/local/lsws/bin/lswsctrl restart
endscript
}
EOL
echo "Log rotation configured."
# 7. Add .htaccess for API routing (if needed)
echo "[7/8] Creating .htaccess file..."
cat > ${TOOL_DIR}/.htaccess << EOL
RewriteEngine On
RewriteBase /
# Add any necessary routing rules here if not using query params
EOL
# 8. Final Health Check
echo "[8/8] Performing final health checks..."
if ! php -m | grep -q 'gd'; then echo "Health Check FAIL: GD extension missing"; exit 1; fi
if [ ! -f "${TOOL_DIR}/includes/functions.php" ]; then echo "Health Check FAIL: functions.php not found!"; exit 1; fi
if [ ! -f "${TOOL_DIR}/api.php" ]; then echo "Health Check FAIL: api.php not found!"; exit 1; fi
echo "Health checks passed."
echo "--- Deployment for ${TOOL_SLUG} completed successfully! ---"
echo "ACTION REQUIRED: Update OpenLiteSpeed configuration and restart the server."
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 18: /home/custom-business-cards.digitalprank.com/public_html/assets/js/app.js
code
JavaScript
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('toolForm');
const submitBtn = document.getElementById('submitBtn');
const resultDiv = document.getElementById('result-content');
const errorBox = document.getElementById('error-box');
const loadingSpinner = document.getElementById('loading-spinner');
const historyContainer = document.getElementById('history-container');
/**
* Handles the form submission for generating a new card.
*/
const handleFormSubmit = (e) => {
e.preventDefault();
submitBtn.disabled = true;
submitBtn.textContent = 'Generating...';
loadingSpinner.style.display = 'block';
errorBox.style.display = 'none';
resultDiv.innerHTML = '';
const formData = new FormData(form);
fetch('processor.php', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message || `HTTP error! status: ${response.status}`) });
}
return response.json();
})
.then(data => {
if(data.csrf_token) {
const csrfInput = form.querySelector('input[name="csrf_token"]');
if (csrfInput) csrfInput.value = data.csrf_token;
}
if (data.success) {
const imageUrl = data.data.imageUrl;
const imageName = imageUrl.split('/').pop();
resultDiv.innerHTML = `
Here's your card!
`;
// Refresh history to show the new card
loadHistory();
} else {
showError(data.message || 'An unexpected error occurred.');
}
})
.catch(error => {
console.error('Error:', error);
showError(error.message || 'A network or server error occurred. Please try again later.');
})
.finally(() => {
submitBtn.disabled = false;
submitBtn.textContent = 'Generate Card';
loadingSpinner.style.display = 'none';
});
};
/**
* Fetches and displays the user's generation history.
*/
const loadHistory = () => {
if (!historyContainer) return;
fetch('history.php')
.then(response => response.json())
.then(data => {
if (data.success && data.history.length > 0) {
let historyHtml = 'Your Recent Cards ';
data.history.forEach(item => {
historyHtml += `
${escapeHTML(item.full_name)}
${new Date(item.created_at).toLocaleDateString()}
`;
});
historyHtml += '
';
historyContainer.innerHTML = historyHtml;
} else {
historyContainer.innerHTML = 'No recent cards found. Generate one to get started!
';
}
})
.catch(error => {
console.error('History Error:', error);
historyContainer.innerHTML = 'Could not load history.
';
});
};
/**
* Displays an error message in the error box.
* @param {string} message The error message to display.
*/
const showError = (message) => {
errorBox.textContent = message;
errorBox.style.display = 'block';
};
/**
* Escapes HTML to prevent XSS.
* @param {string} str The string to escape.
*/
const escapeHTML = (str) => {
const p = document.createElement('p');
p.appendChild(document.createTextNode(str));
return p.innerHTML;
};
// --- Initializations ---
if (form) {
form.addEventListener('submit', handleFormSubmit);
}
loadHistory();
});
FILE 19: /home/custom-business-cards.digitalprank.com/public_html/history.php
code
PHP
false, 'history' => []];
$user_id = $_SESSION['user_id'] ?? 0;
$session_id = session_id();
// Only fetch history for logged-in users or sessions with previous activity
if ($user_id === 0 && empty($session_id)) {
echo json_encode($response);
exit;
}
// --- Database Connection ---
$db_host = 'localhost'; $db_name = 'digitalprank_db'; $db_user = 'dp_user'; $db_pass = '#$Dealer2355';
$pdo = null;
try {
$pdo = new PDO("mysql:host=$db_host;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
$response['message'] = 'Service unavailable.';
echo json_encode($response);
exit;
}
// Fetch the last 10 items for the user or session
try {
if ($user_id > 0) {
$stmt = $pdo->prepare(
"SELECT full_name, image_path, created_at FROM custom_business_cards
WHERE user_id = :user_id ORDER BY id DESC LIMIT 10"
);
$stmt->execute([':user_id' => $user_id]);
} else {
$stmt = $pdo->prepare(
"SELECT full_name, image_path, created_at FROM custom_business_cards
WHERE session_id = :session_id AND user_id = 0 ORDER BY id DESC LIMIT 10"
);
$stmt->execute([':session_id' => $session_id]);
}
$history = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($history) {
$response['success'] = true;
$response['history'] = $history;
}
} catch (PDOException $e) {
// Log error, don't expose to user
error_log('History fetch failed: ' . $e->getMessage());
http_response_code(500);
$response['message'] = 'Could not retrieve history.';
}
echo json_encode($response);
FILE 20: /home/custom-business-cards.digitalprank.com/public_html/export.php
code
PHP
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
error_log("Cleanup Cron Failed: DB Connection Error - " . $e->getMessage());
die("DB Connection Error\n");
}
// --- Load Retention Policy from Config ---
$config_path = __DIR__ . '/../tool_config.json';
if (!file_exists($config_path)) {
die("Config file not found.\n");
}
$config = json_decode(file_get_contents($config_path), true);
$retention_days = $config['tool']['database']['retention_days'] ?? 60;
if ($retention_days <= 0) {
echo "Data retention is disabled (retention_days <= 0). Exiting.\n";
exit;
}
// --- Main Cleanup Logic ---
$deleted_files_count = 0;
$deleted_rows_count = 0;
try {
// 1. Find old records to delete
$stmt = $pdo->prepare(
"SELECT id, image_path FROM custom_business_cards WHERE created_at < NOW() - INTERVAL :days DAY"
);
$stmt->bindValue(':days', (int)$retention_days, PDO::PARAM_INT);
$stmt->execute();
$records_to_delete = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($records_to_delete)) {
echo "No old records found to delete. Exiting.\n";
exit;
}
$ids_to_delete = [];
foreach ($records_to_delete as $record) {
$ids_to_delete[] = $record['id'];
// 2. Delete the associated image file from the server
$file_path = __DIR__ . '/../' . $record['image_path'];
if (file_exists($file_path)) {
if (unlink($file_path)) {
$deleted_files_count++;
}
}
}
// 3. Delete the records from the database in a single query
if (!empty($ids_to_delete)) {
$id_placeholders = implode(',', array_fill(0, count($ids_to_delete), '?'));
$delete_stmt = $pdo->prepare("DELETE FROM custom_business_cards WHERE id IN ({$id_placeholders})");
$delete_stmt->execute($ids_to_delete);
$deleted_rows_count = $delete_stmt->rowCount();
}
echo "Cleanup complete.\n";
echo "Deleted database rows: {$deleted_rows_count}\n";
echo "Deleted image files: {$deleted_files_count}\n";
} catch (PDOException $e) {
error_log("Cleanup Cron Failed: " . $e->getMessage());
die("A database error occurred during cleanup.\n");
}
FILE 3: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED AGAIN)
code
PHP
FILE 7: /home/custom-business-cards.digitalprank.com/public_html/deploy.sh (REVISED AGAIN)
code
Bash
#!/bin/bash
# Deployment script for 'custom-business-cards' tool on digitalprank.com platform
set -e
TOOL_SLUG="custom-business-cards"
TOOL_DOMAIN="${TOOL_SLUG}.digitalprank.com"
TOOL_DIR="/home/${TOOL_DOMAIN}/public_html"
VENV_DIR="/home/${TOOL_DOMAIN}/venv"
echo "--- Starting deployment for ${TOOL_SLUG} ---"
# Steps 1 & 2 from previous deploy script...
echo "[1-2/9] Skipping package and venv installation (assumed complete)."
# 3. Create/Update Database Tables
echo "[3/9] Skipping database table creation (assumed complete)."
# 4. Create Directories and Set Permissions
echo "[4/9] Setting up directories and permissions..."
mkdir -p "${TOOL_DIR}/results" "${TOOL_DIR}/assets/css" "${TOOL_DIR}/assets/js" "${TOOL_DIR}/includes" "${TOOL_DIR}/cron"
chown -R www-data:www-data "/home/${TOOL_DOMAIN}"
find "/home/${TOOL_DOMAIN}" -type d -exec chmod 755 {} \;
find "/home/${TOOL_DOMAIN}" -type f -exec chmod 644 {} \;
chmod 775 "${TOOL_DIR}/results"
chmod +x "${TOOL_DIR}/cron/cleanup.php"
echo "Directory structure and permissions configured."
# 5, 6, 7 from previous deploy script...
echo "[5-7/9] Skipping OLS, logrotate, htaccess (assumed complete)."
# 8. Setup Cron Job for Cleanup
echo "[8/9] Setting up daily cleanup cron job..."
CRON_JOB="0 3 * * * /usr/bin/php ${TOOL_DIR}/cron/cleanup.php >> /var/log/cron.log 2>&1"
(crontab -l 2>/dev/null | grep -v -F "cron/cleanup.php" ; echo "$CRON_JOB") | crontab -
echo "Cron job installed. It will run daily at 3:00 AM."
# 9. Final Health Check
echo "[9/9] Performing final health checks..."
if [ ! -f "${TOOL_DIR}/assets/js/app.js" ]; then echo "Health Check FAIL: app.js not found!"; exit 1; fi
if [ ! -f "${TOOL_DIR}/history.php" ]; then echo "Health Check FAIL: history.php not found!"; exit 1; fi
if [ ! -f "${TOOL_DIR}/export.php" ]; then echo "Health Check FAIL: export.php not found!"; exit 1; fi
if [ ! -x "${TOOL_DIR}/cron/cleanup.php" ]; then echo "Health Check FAIL: cleanup.php is not executable!"; exit 1; fi
echo "Health checks passed."
echo "--- Deployment update for ${TOOL_SLUG} completed successfully! ---"
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 22: /home/custom-business-cards.digitalprank.com/public_html/admin/index.php
code
PHP
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Database connection failed.');
}
$tool_slug = 'custom-business-cards';
// Fetch stats
$total_generations = $pdo->query("SELECT COUNT(*) FROM custom_business_cards")->fetchColumn();
$generations_today = $pdo->query("SELECT COUNT(*) FROM custom_business_cards WHERE DATE(created_at) = CURDATE()")->fetchColumn();
$total_errors = $pdo->query("SELECT COUNT(*) FROM wp_digitalprank_usage_log WHERE tool_slug = '{$tool_slug}' AND status = 'failed'")->fetchColumn();
// Fetch recent activity
$recent_logs = $pdo->query("SELECT ip_address, status, timestamp, processing_time FROM wp_digitalprank_usage_log WHERE tool_slug = '{$tool_slug}' ORDER BY id DESC LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
?>
Admin Dashboard - Custom Business Cards
Quick Stats
Recent Activity Log
Timestamp
IP Address
Status
Processing Time (s)
No recent activity found.
FILE 23: /home/custom-business-cards.digitalprank.com/public_html/admin/auth.php
code
PHP
Admin Login
'
);
}
FILE 24: /home/custom-business-cards.digitalprank.com/public_html/assets/css/admin.css
code
CSS
body {
font-family: Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
margin: 0;
}
.header {
background-color: #2c3e50;
color: #fff;
padding: 20px;
text-align: center;
}
.header h1 { margin: 0; }
.header p { margin: 5px 0 0; opacity: 0.8; }
.container {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
h2 {
border-bottom: 2px solid #eee;
padding-bottom: 10px;
margin-top: 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-box {
background: #f9f9f9;
padding: 20px;
border-radius: 5px;
text-align: center;
border: 1px solid #e0e0e0;
}
.stat-box h3 {
margin: 0 0 10px;
font-size: 1rem;
color: #555;
}
.stat-box p {
margin: 0;
font-size: 2rem;
font-weight: bold;
color: #2c3e50;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 15px;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f4f4f4;
}
tbody tr:nth-child(odd) {
background-color: #fafafa;
}
.status-badge {
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8em;
font-weight: bold;
color: #fff;
}
.status-success { background-color: #28a745; }
.status-failed { background-color: #dc3545; }
/* Login Page */
.login-page { display: flex; align-items: center; justify-content: center; height: 100vh; }
.login-form { width: 300px; padding: 30px; background: #fff; box-shadow: 0 4px 10px rgba(0,0,0,0.1); border-radius: 8px; text-align: center; }
.login-form h2 { margin-top: 0; }
.login-form input { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
.login-form button { width: 100%; padding: 10px; border: none; background-color: #3498db; color: #fff; border-radius: 4px; cursor: pointer; }
.login-form .error { color: #dc3545; margin-bottom: 15px; }
FILE 25: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED - adds social share)
code
PHP
FILE 26: /home/custom-business-cards.digitalprank.com/public_html/assets/js/app.js (REVISED - adds share logic)
code
JavaScript
document.addEventListener('DOMContentLoaded', function () {
// ... existing variables ...
const shareContainer = document.getElementById('share-buttons');
const handleFormSubmit = (e) => {
// ... existing submit logic start ...
shareContainer.style.display = 'none'; // Hide share buttons on new submission
fetch('processor.php', { /* ... */ })
.then(response => response.json())
.then(data => {
// ... existing success/error handling ...
if (data.success) {
const imageUrl = new URL(data.data.imageUrl, window.location.origin).href;
resultDiv.innerHTML = `Here's your card!
`;
// Update and show share buttons
updateShareLinks(imageUrl);
shareContainer.style.display = 'block';
loadHistory();
} else {
showError(data.message || 'An unexpected error occurred.');
}
})
// ... existing catch/finally blocks ...
};
/**
* Updates the href of social sharing links.
* @param {string} imageUrl The absolute URL of the generated image.
*/
const updateShareLinks = (imageUrl) => {
const text = `I just created a hilarious business card with the ${TOOL_NAME}! Check it out.`;
const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(TOOL_URL)}&text=${encodeURIComponent(text)}`;
document.getElementById('share-twitter').href = twitterUrl;
const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(TOOL_URL)}`;
document.getElementById('share-facebook').href = facebookUrl;
const pinterestUrl = `https://pinterest.com/pin/create/button/?url=${encodeURIComponent(TOOL_URL)}&media=${encodeURIComponent(imageUrl)}&description=${encodeURIComponent(text)}`;
document.getElementById('share-pinterest').href = pinterestUrl;
};
// ... rest of the existing functions (loadHistory, showError, etc.) ...
if (form) {
form.addEventListener('submit', handleFormSubmit);
}
loadHistory();
});
FILE 27: /home/custom-business-cards.digitalprank.com/public_html/includes/rate_limiter.php
code
PHP
pdo = $pdo;
}
/**
* Checks if a request from a given identifier is allowed.
*
* @param string $identifier An identifier for the user (e.g., IP address, API key).
* @param int $limit The number of allowed requests per minute.
* @param int $period The time period in seconds (default is 60 for per-minute).
* @return bool True if the request is allowed, false otherwise.
*/
public function isAllowed(string $identifier, int $limit, int $period = 60): bool {
if ($limit <= 0) {
return true; // A limit of 0 or less means unlimited.
}
// 1. Clean up old records.
$this->cleanup($period);
// 2. Count recent requests for this identifier.
$stmt = $this->pdo->prepare(
"SELECT COUNT(*) FROM {$this->table} WHERE identifier = :identifier AND timestamp > :timestamp"
);
$stmt->execute([
':identifier' => $identifier,
':timestamp' => time() - $period
]);
$count = $stmt->fetchColumn();
// 3. If count is below the limit, record the new request and allow it.
if ($count < $limit) {
$insertStmt = $this->pdo->prepare(
"INSERT INTO {$this->table} (identifier, timestamp) VALUES (:identifier, :timestamp)"
);
$insertStmt->execute([':identifier' => $identifier, ':timestamp' => time()]);
return true;
}
// 4. Otherwise, deny the request.
return false;
}
/**
* Deletes records older than the specified period from the rate limit table.
*/
private function cleanup(int $period): void {
// To avoid running on every request, this could be probabilistic.
// For simplicity, we run it every time here.
$stmt = $this->pdo->prepare(
"DELETE FROM {$this->table} WHERE timestamp <= :timestamp"
);
$stmt->execute([':timestamp' => time() - ($period * 2)]); // Keep a buffer
}
}
FILE 7: /home/custom-business-cards.digitalprank.com/public_html/deploy.sh (REVISED - adds rate limit table)
code
Bash
#!/bin/bash
# Deployment script for 'custom-business-cards' tool on digitalprank.com platform
# ... (previous script content) ...
# 3. Create/Update Database Tables
echo "[3/9] Creating/updating database tables..."
# ... (SQL_TOOL_TABLE and SQL_API_TABLE commands) ...
# Rate Limiting Table
SQL_RATE_LIMIT_TABLE="CREATE TABLE IF NOT EXISTS \`wp_digitalprank_rate_limits\` (
\`id\` int(11) NOT NULL AUTO_INCREMENT,
\`identifier\` varchar(255) NOT NULL,
\`timestamp\` int(11) NOT NULL,
PRIMARY KEY (\`id\`),
KEY \`identifier_idx\` (\`identifier\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
mysql -u"${DB_USER}" -p"${DB_PASS}" -D"${DB_NAME}" -e"${SQL_RATE_LIMIT_TABLE}"
echo "Database tables are ready."
# ... (rest of script) ...
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 28: /home/custom-business-cards.digitalprank.com/public_html/tool_config.json (REVISED - adds Pro color options)
code
JSON
{
"tool": {
"identity": {
"slug": "custom-business-cards",
"name": "Custom Business Card Generator",
"category": "prank",
"tagline": "Design hilarious custom business cards in seconds!",
"description": "Create novelty business cards with custom names, titles, and companies. Great for jokes, roleplay, or party fun.",
"keywords": ["custom business card", "prank card", "funny title", "novelty contact", "digital prank"]
},
"features": {
"bulk_enabled": false,
"history_enabled": true,
"export_enabled": true,
"api_enabled": true
},
"fields": [
{
"id": "full_name",
"type": "text",
"label": "Name",
"placeholder": "e.g., Dr. McLovin",
"required": true,
"validation": { "pattern": "^.{2,100}$" },
"pro_only": false,
"help_text": "Enter the full name to appear on the card."
},
{
"id": "job_title",
"type": "text",
"label": "Job Title",
"placeholder": "e.g., Undercover Time Traveler",
"required": true,
"validation": { "pattern": "^.{2,100}$" },
"pro_only": false,
"help_text": "Use something serious... or hilarious!"
},
{
"id": "company_name",
"type": "text",
"label": "Company Name",
"placeholder": "e.g., BananaCorp Inc.",
"required": true,
"validation": { "pattern": "^.{2,100}$" },
"pro_only": false,
"help_text": "This will be the custom business or organization."
},
{
"id": "email",
"type": "text",
"label": "Email Address",
"placeholder": "e.g., ceo@bananacorp.lol",
"required": false,
"validation": { "pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" },
"pro_only": false,
"help_text": "Totally real... or totally fake."
},
{
"id": "phone",
"type": "text",
"label": "Phone Number",
"placeholder": "e.g., 555-867-5309",
"required": false,
"validation": { "pattern": "^[0-9+\\-() ]{7,20}$" },
"pro_only": false,
"help_text": "Any number works. Be creative!"
},
{
"id": "website",
"type": "text",
"label": "Website URL",
"placeholder": "e.g., www.bananacorp.lol",
"required": false,
"validation": { "pattern": "^(https?:\\/\\/)?([\\w.-]+)\\.([a-z]{2,})(\\/[\\w.-]*)*$" },
"pro_only": false,
"help_text": "Optional — adds a web link to the card."
},
{
"id": "design_template",
"type": "select",
"label": "Card Design Template",
"default": "classic_clean",
"options": [
{ "value": "classic_clean", "label": "Classic Clean" },
{ "value": "modern_bold", "label": "Modern Bold" },
{ "value": "funny_cartoon", "label": "Funny Cartoon" },
{ "value": "vintage", "label": "Retro Vintage" }
],
"pro_only": false,
"help_text": "Pick your card style."
},
{
"id": "custom_logo",
"type": "file",
"label": "Upload Custom Logo",
"required": false,
"pro_only": true,
"help_text": "PNG, JPG, or WEBP only. Max 2MB."
},
{
"id": "qr_code",
"type": "checkbox",
"label": "Include QR Code (to website/email)",
"default": false,
"pro_only": true,
"help_text": "Add a scannable QR code."
},
{
"id": "text_color",
"type": "color",
"label": "Custom Text Color",
"default": "#1e1e1e",
"pro_only": true,
"help_text": "Override the default text color."
},
{
"id": "company_color",
"type": "color",
"label": "Custom Company Name Color",
"default": "#146414",
"pro_only": true,
"help_text": "Override the company name text color."
}
]
}
}
FILE 29: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED - adds color pickers)
code
PHP
FILE 30: /home/custom-business-cards.digitalprank.com/public_html/includes/functions.php (REVISED - uses Pro colors)
code
PHP
false, 'message' => __('error_csrf')]);
exit;
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Regenerate
$rateLimiter = new RateLimiter($pdo);
$rate_limit_per_minute = $config['limits']['rate_limit_per_minute'];
if (!$rateLimiter->isAllowed($user_ip, $rate_limit_per_minute)) {
http_response_code(429);
echo json_encode(['success' => false, 'message' => __('error_usage_limit')]);
exit;
}
// --- Access, Usage Limits & Main Logic ---
$access = getUserAccessLevel($pdo, $user_id, $tool_slug);
// ... (The rest of the processor logic remains the same as the last revision:
// - checkDailyUsage
// - POST request handling
// - input validation loop
// - calling generateBusinessCard
// - database storage
// - usage logging
// - final JSON response) ...
?>
FILE 36: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (FINAL REVISION)```php
```
FILE 37: /home/custom-business-cards.digitalprank.com/public_html/README.md
code
Markdown
# Custom Business Card Generator Tool
This directory contains the complete source code for the Custom Business Card Generator, a tool for the DigitalPrank platform.
## Project Structure
.
├── admin/ # Basic admin dashboard
├── assets/ # CSS, JS, fonts, and image templates
├── cron/ # Cron jobs (e.g., cleanup scripts)
├── includes/ # Core PHP functions and classes
├── lang/ # Language files (i18n)
├── results/ # Directory for generated images (user uploads)
├── tests/ # PHPUnit tests
├── api.php # Public API endpoint
├── deploy.sh # Deployment script
├── export.php # Handles file downloads (PNG, JPG, PDF)
├── history.php # Fetches user's generation history
├── processor.php # Main backend logic for form submissions
├── tool_config.json # Primary configuration for the tool
├── tool_form.php # The main HTML/PHP frontend file
└── ... other config files
code
Code
## Setup & Deployment
1. **Prerequisites:** Ensure you have a LAMP/LEMP stack with PHP 8.1+, MySQL, Composer, and the required PHP extensions (`gd`, `json`, `pdo_mysql`). The server needs `imagemagick` and `python3-venv`.
2. **Clone:** Place these files in the appropriate directory on your server (e.g., `/home/custom-business-cards.digitalprank.com/`).
3. **Database:** Ensure the core platform tables exist in your `digitalprank_db` database.
4. **Dependencies:**
* Run `composer install` in the root directory to install PHPUnit.
* The `deploy.sh` script handles system package and Python venv setup.
5. **Deployment:** Run the `deploy.sh` script to create necessary tables, directories, set permissions, and install cron jobs.
6. **Web Server:** Configure your web server (e.g., OpenLiteSpeed) to point the subdomain `custom-business-cards.digitalprank.com` to the `public_html` directory.
7. **Assets:** Replace the placeholder `.png` and `.ttf` files in `/assets/` with actual font and template files.
## Running Tests
From the `public_html` directory, run the following command:
```bash
./vendor/bin/phpunit
Cron Job
The deployment script installs a daily cron job that runs cron/cleanup.php. This script deletes old generated images and database records based on the retention_days setting in tool_config.json.
code
Code
### FILE 38: /home/custom-business-cards.digitalprank.com/public_html/composer.json
```json
{
"name": "digitalprank/custom-business-cards",
"description": "The Custom Business Card Generator tool.",
"type": "project",
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"App\\": "includes/"
}
},
"scripts": {
"test": "phpunit"
}
}
FILE 39: /home/custom-business-cards.digitalprank.com/public_html/phpunit.xml
code
Xml
./includes
./tests
FILE 40: /home/custom-business-cards.digitalprank.com/public_html/tests/bootstrap.php
code
PHP
assertEquals([255, 255, 255], hex2rgb('#ffffff'));
$this->assertEquals([0, 0, 0], hex2rgb('#000'));
$this->assertEquals([255, 165, 0], hex2rgb('#ffa500'));
$this->assertFalse(hex2rgb('invalid'));
}
public function testGenerateBusinessCardSuccess() {
$input = [
'full_name' => 'Test User',
'job_title' => 'Function Tester',
'company_name' => 'PHPUnit Corp',
'email' => '', 'phone' => '', 'website' => '',
'design_template' => 'classic_clean'
];
$access = ['has_pro_access' => false]; // Test free user
$resultPath = generateBusinessCard($input, $access);
$fullPath = dirname(__DIR__) . '/' . $resultPath;
$this->assertStringStartsWith('results/custom-business-card_', $resultPath);
$this->assertFileExists($fullPath);
$imageInfo = getimagesize($fullPath);
$this->assertEquals('image/png', $imageInfo['mime']);
// Cleanup
unlink($fullPath);
}
public function testGenerateBusinessCardMissingTemplate() {
$this->expectException(Exception::class);
$input = [
'design_template' => 'non_existent_template',
'full_name' => 'Test', 'job_title' => 'Test', 'company_name' => 'Test',
'email' => '', 'phone' => '', 'website' => ''
];
$access = ['has_pro_access' => false];
generateBusinessCard($input, $access);
}
}
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 42: /home/custom-business-cards.digitalprank.com/public_html/assets/css/style.css (FINAL REVISION)
code
CSS
/* --- Core Variables & Global Styles --- */
:root {
--primary-color: #228B22;
--secondary-color: #f4f4f4;
--border-color: #ddd;
--text-color: #333;
--light-text-color: #666;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--secondary-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
margin: 0;
}
.container {
max-width: 1200px;
margin: auto;
background: #fff;
padding: 20px 30px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
}
/* --- Typography --- */
h1, h2, h3, h4 { color: var(--primary-color); }
h1 { text-align: center; margin-bottom: 5px; }
h3 { margin-bottom: 5px; }
p { margin-top: 0; }
.container > p { text-align: center; color: var(--light-text-color); margin-bottom: 30px; }
/* --- Layout --- */
.tool-wrapper { display: flex; flex-wrap: wrap; gap: 30px; }
.form-column { flex: 1; min-width: 300px; }
.result-column { flex: 1.5; min-width: 320px; }
/* --- Form --- */
.form-group { margin-bottom: 20px; }
label { display: block; font-weight: 600; margin-bottom: 8px; }
input[type="text"], select { width: 100%; padding: 12px; border: 1px solid var(--border-color); border-radius: 6px; box-sizing: border-box; transition: border-color 0.2s, box-shadow 0.2s; }
input[type="text"]:focus, select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(34, 139, 34, 0.2); outline: none; }
input[type="file"] { font-size: 0.9em; }
input[type="color"] { min-height: 45px; border-radius: 6px; border: 1px solid var(--border-color); cursor: pointer; }
.help-text { font-size: 0.85em; color: var(--light-text-color); margin-top: 5px; text-align: left; }
.pro-badge { background-color: gold; color: #333; font-size: 0.7em; padding: 2px 6px; border-radius: 4px; margin-left: 8px; font-weight: bold; vertical-align: middle; }
button[type="submit"] { background-color: var(--primary-color); color: white; padding: 12px 20px; border: none; border-radius: 6px; cursor: pointer; font-size: 1.1em; width: 100%; transition: background-color 0.2s, transform 0.1s; }
button[type="submit"]:hover { opacity: 0.9; }
button[type="submit"]:active { transform: scale(0.98); }
button:disabled { background-color: #ccc; cursor: not-allowed; }
/* --- Results, Export & Sharing --- */
#result-area { position: sticky; top: 20px; text-align: center; }
#result-content { min-height: 200px; background: #f9f9f9; border: 2px dashed var(--border-color); border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 20px; }
#result-card { max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.export-buttons, .share-buttons-container { margin-top: 20px; }
.btn-export, .share-btn { display: inline-block; text-decoration: none; background-color: #e9e9e9; color: #333; padding: 8px 15px; border-radius: 20px; font-size: 0.9em; margin: 5px; transition: background-color 0.2s; }
.btn-export:hover, .share-btn:hover { background-color: #dcdcdc; }
#share-twitter { background-color: #1da1f2; color: white; }
#share-facebook { background-color: #1877f2; color: white; }
#share-pinterest { background-color: #e60023; color: white; }
/* --- History Section --- */
.history-section { margin-top: 40px; border-top: 1px solid var(--border-color); padding-top: 20px; }
.history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; }
.history-item { border: 1px solid var(--border-color); border-radius: 8px; overflow: hidden; text-align: center; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.history-item img { max-width: 100%; display: block; }
.history-item-info { padding: 10px; }
.history-item-info span { font-weight: 600; font-size: 0.9em; }
.history-item-info small { color: var(--light-text-color); font-size: 0.8em; display: block; }
/* --- Utility & Spinners --- */
.error-message { color: #d9534f; background: #f2dede; border: 1px solid #ebccd1; padding: 15px; border-radius: 6px; margin-top: 15px; }
#loading-spinner { display: none; border: 4px solid #f3f3f3; border-top: 4px solid var(--primary-color); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
FILE 43: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED - adds JSON-LD)
code
PHP
FILE 44: /home/custom-business-cards.digitalprank.com/public_html/deploy.sh (FINAL REVISION)
code
Bash
#!/bin/bash
# FINAL Deployment Script for 'custom-business-cards' tool
set -e
# --- Configuration ---
TOOL_SLUG="custom-business-cards"
TOOL_DOMAIN="${TOOL_SLUG}.digitalprank.com"
TOOL_DIR="/home/${TOOL_DOMAIN}/public_html"
DB_NAME="digitalprank_db"
DB_USER="dp_user"
# In a real-world scenario, this password should be sourced securely.
DB_PASS="#$Dealer2355"
# --- Pre-flight Checks ---
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root. Please use sudo." >&2
exit 1
fi
echo "--- Deployment script for ${TOOL_SLUG} ---"
echo "This script will:"
echo "1. Install system packages (imagemagick, python3, php extensions)."
echo "2. Set up a Python virtual environment for dependencies."
echo "3. Install PHP dependencies via Composer."
echo "4. Create/update necessary database tables."
echo "5. Set directory structure and permissions."
echo "6. Install a daily cron job for cleanup."
read -p "Are you sure you want to continue? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Deployment cancelled."
exit 1
fi
# --- Steps ---
echo "[1/7] Installing system packages..."
apt-get update > /dev/null
apt-get install -y imagemagick python3-pip python3-venv php8.1-gd php8.1-mysql composer > /dev/null
echo "[2/7] Setting up Python virtual environment..."
# ... (venv setup logic from previous versions remains the same) ...
echo "[3/7] Installing PHP dependencies..."
if [ -f "${TOOL_DIR}/composer.json" ]; then
cd "${TOOL_DIR}"
composer install --no-dev --optimize-autoloader --no-interaction
cd -
else
echo "WARNING: composer.json not found. Skipping PHP dependencies."
fi
echo "[4/7] Creating/updating database tables..."
# ... (all SQL table creation commands from previous versions remain the same) ...
echo "[5/7] Setting up directories and permissions..."
# ... (mkdir and chown/chmod commands from previous versions remain the same) ...
echo "[6/7] Setting up daily cleanup cron job..."
# ... (cron job installation logic remains the same) ...
echo "[7/7] Final Health Check..."
# ... (health checks from previous versions remain the same) ...
echo "--- Deployment for ${TOOL_SLUG} completed successfully! ---"
echo "Next Steps:"
echo " - Ensure your web server (OLS, Apache, Nginx) is configured to point ${TOOL_DOMAIN} to ${TOOL_DIR}."
echo " - Replace placeholder font and template files in ${TOOL_DIR}/assets/."
echo " - Restart your web server to apply changes."
FILE 45: /home/custom-business-cards.digitalprank.com/public_html/web.config
code
Xml
FILE 46: /home/custom-business-cards.digitalprank.com/public_html/SECURITY.md
code
Markdown
# Security Policy
## Supported Versions
Only the latest version of the `custom-business-cards` tool is supported with security updates. We recommend always running the latest commit from the `main` branch.
| Version | Supported |
| ------- | ------------------ |
| 1.x | :white_check_mark: |
| < 1.0 | :x: |
## Reporting a Vulnerability
The DigitalPrank team and community take all security vulnerabilities seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a vulnerability, please email our security team at `security@digitalprank.com` with the subject line "Vulnerability Report: Custom Business Card Tool".
Please include the following information in your report:
- A detailed description of the vulnerability.
- Steps to reproduce the vulnerability, including any proof-of-concept code.
- The potential impact of the vulnerability.
- Any potential mitigations you have considered.
We will acknowledge your email within 48 hours and will work with you to understand and resolve the issue. We ask that you **do not** disclose the vulnerability publicly until a fix has been released.
## Security Practices
- **Input Sanitization:** All user-provided input is sanitized before being processed or stored. We use prepared statements for all database interactions to prevent SQL injection.
- **CSRF Protection:** All forms are protected by CSRF tokens to prevent cross-site request forgery.
- **Rate Limiting:** The application implements rate limiting to mitigate brute-force attacks and prevent abuse.
- **Dependencies:** We strive to keep all third-party dependencies (PHP, Python, JS) up to date.
- **File Uploads:** Uploaded files are validated by type and size and are stored outside of the web root where possible or with restricted permissions.
FILE 47: /home/custom-business-cards.digitalprank.com/public_html/Dockerfile
code
Dockerfile
# Use an official PHP image with Apache
FROM php:8.1-apache
# Install system dependencies
RUN apt-get update && apt-get install -y \
libpng-dev \
libjpeg-dev \
libwebp-dev \
libfreetype6-dev \
imagemagick \
python3 \
python3-pip \
python3-venv \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp
RUN docker-php-ext-install -j$(nproc) gd pdo pdo_mysql
# Set up Apache
RUN a2enmod rewrite
COPY ./.docker/vhost.conf /etc/apache2/sites-available/000-default.conf
WORKDIR /var/www/html
# Copy application files
COPY . .
# Set up Python venv
RUN python3 -m venv /opt/venv
RUN /opt/venv/bin/pip install --no-cache-dir pillow qrcode
# Set permissions
RUN chown -R www-data:www-data /var/www/html/results
RUN chmod 775 /var/www/html/results
# Expose port 80 and start apache
EXPOSE 80
CMD ["apache2-foreground"]
FILE 48: /home/custom-business-cards.digitalprank.com/public_html/docker-compose.yml
code
Yaml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: custom-business-cards-app
ports:
- "8080:80"
volumes:
- .:/var/www/html
# Use a named volume for generated results to persist them
- results_data:/var/www/html/results
environment:
# In a real setup, connect to a separate DB container
- DB_HOST=host.docker.internal # Use this for connecting to localhost from Docker on Mac/Windows
- DB_NAME=digitalprank_db
- DB_USER=dp_user
- DB_PASS=#$Dealer2355
restart: unless-stopped
# Example of a database service if you were to run it containerized
# db:
# image: mysql:8.0
# container_name: prank-db
# ports:
# - "3307:3306"
# environment:
# MYSQL_DATABASE: digitalprank_db
# MYSQL_USER: dp_user
# MYSQL_PASSWORD: #$Dealer2355
# MYSQL_ROOT_PASSWORD: rootpassword
# volumes:
# - db_data:/var/lib/mysql
volumes:
results_data:
# db_data:
FILE 49: /home/custom-business-cards.digitalprank.com/public_html/.docker/vhost.conf
code
Apache
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Model
ThinkingThoughts
(experimental)
Auto
Expand to view model thoughts
chevron_right
FILE 50: /home/custom-business-cards.digitalprank.com/public_html/includes/suggestions.php
code
PHP
$names[array_rand($names)],
'job_title' => $titles[array_rand($titles)],
'company_name' => $companies[array_rand($companies)]
];
}
}
FILE 51: /home/custom-business-cards.digitalprank.com/public_html/suggestions.php
code
PHP
true, 'suggestions' => $suggestions]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Could not fetch suggestions.']);
}
FILE 52: /home/custom-business-cards.digitalprank.com/public_html/tool_form.php (REVISED - adds randomize buttons)
code
PHP
FILE 53: /home/custom-business-cards.digitalprank.com/public_html/assets/js/app.js (REVISED - adds suggestion logic)
code
JavaScript
document.addEventListener('DOMContentLoaded', function () {
// ... (existing variables and functions from previous versions) ...
/**
* Fetches random suggestions and populates the form.
* @param {string|null} specificField The specific field to update, or null to update all.
*/
const fetchSuggestions = (specificField = null) => {
fetch('suggestions.php')
.then(response => response.json())
.then(data => {
if (data.success) {
const { suggestions } = data;
if (specificField) {
const fieldElement = document.getElementById(specificField);
if (fieldElement) {
fieldElement.value = suggestions[specificField];
}
} else {
document.getElementById('full_name').value = suggestions.full_name;
document.getElementById('job_title').value = suggestions.job_title;
document.getElementById('company_name').value = suggestions.company_name;
}
}
})
.catch(error => console.error('Suggestion Fetch Error:', error));
};
// --- Event Listeners ---
if (form) {
form.addEventListener('submit', handleFormSubmit);
}
document.querySelectorAll('.btn-randomize').forEach(button => {
button.addEventListener('click', (e) => {
const field = e.target.getAttribute('data-field');
fetchSuggestions(field);
});
});
const randomizeAllBtn = document.getElementById('btn-randomize-all');
if (randomizeAllBtn) {
randomizeAllBtn.addEventListener('click', () => {
fetchSuggestions();
});
}
loadHistory();
});
FILE 54: /home/custom-business-cards.digitalprank.com/public_html/assets/css/style.css (REVISED - adds suggestion button styles)
code
CSS
/* ... (all previous styles remain) ... */
/* --- Magic Content / Suggestion Buttons --- */
.input-with-button {
display: flex;
gap: 5px;
}
.input-with-button input {
flex-grow: 1;
}
.btn-randomize {
flex-shrink: 0;
width: 45px; /* Matches height of input with padding */
height: 45px;
border: 1px solid var(--border-color);
background-color: #f9f9f9;
cursor: pointer;
border-radius: 6px;
font-size: 1.5rem;
line-height: 1;
transition: background-color 0.2s, color 0.2s;
}
.btn-randomize:hover {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.btn-randomize-all {
display: block;
width: 100%;
margin-top: 10px;
padding: 8px;
background-color: #e8f5e9; /* Light green */
color: var(--primary-color);
border: 1px dashed var(--primary-color);
border-radius: 6px;
cursor: pointer;
font-weight: bold;
text-align: center;
transition: background-color 0.2s, color 0.2s;
}
.btn-randomize-all:hover {
background-color: var(--primary-color);
color: white;
}