/**
* @package Crockidialer
* @version 1.0.1
*/
/*
Plugin Name: Crockidialer
Plugin URI:
Description: CRM and Dialing System
Version: 1.0.1
Author: Crockidialer
Author URI:
Text Domain: crockidialer
Domain Path: /languages
Requires at least: 5.0
Requires PHP: 7.4
*/
// Absolute first - Prevent direct access
if (!defined('ABSPATH')) {
if (!headers_sent()) {
header('HTTP/1.1 403 Forbidden');
}
die('Access Denied. No direct script access allowed.');
}
// Critical constants that must exist
if (!defined('WPINC')) {
die('WordPress environment not loaded properly');
}
// Prevent double loading
if (defined('CROCKIDIALER_VERSION')) {
return;
}
// Define plugin constants with unique prefixes
define('CROCKIDIALER_VERSION', '1.0.1');
define('CROCKIDIALER_FILE', __FILE__);
define('CROCKIDIALER_PATH', plugin_dir_path(__FILE__));
define('CROCKIDIALER_URL', plugin_dir_url(__FILE__));
define('CROCKIDIALER_BASENAME', plugin_basename(__FILE__));
define('CROCKIDIALER_DEBUG', false);
// Minimum PHP version check
if (version_compare(PHP_VERSION, '7.4', '<')) {
add_action('admin_notices', function() {
$message = sprintf(
'Crockidialer requires PHP version 7.4 or higher. Your current version is %s. Please upgrade PHP.',
PHP_VERSION
);
printf('
', esc_html($message));
});
return;
}
// WordPress version compatibility check
global $wp_version;
if (version_compare($wp_version, '5.0', '<')) {
add_action('admin_notices', function() use ($wp_version) {
$message = sprintf(
'Crockidialer requires WordPress 5.0 or higher. Your current version is %s.',
$wp_version
);
printf('', esc_html($message));
});
return;
}
// Required WordPress files
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
// Custom exception handler that won't break WordPress
class CrockidialerException extends Exception {
public function __construct($message = "", $code = 0, Throwable $previous = null) {
error_log("Crockidialer Exception: {$message}");
parent::__construct($message, $code, $previous);
}
}
// Enhanced error handler that logs but never crashes
function crockidialer_error_handler($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return false;
}
$error_message = sprintf(
"Crockidialer Error [%s]: %s in %s on line %d",
$errno,
$errstr,
str_replace(CROCKIDIALER_PATH, '', $errfile),
$errline
);
error_log($error_message);
if (CROCKIDIALER_DEBUG && current_user_can('manage_options')) {
add_action('admin_notices', function() use ($error_message) {
printf('', esc_html($error_message));
});
}
return true;
}
set_error_handler('crockidialer_error_handler');
// Bulletproof autoloader with multiple safety checks
spl_autoload_register(function ($class) {
try {
// Early return if not our namespace
if (strpos($class, 'Crockidialer\\') !== 0) {
return;
}
// Sanitize class name to prevent directory traversal
$class = str_replace(['../', '..\\'], '', $class);
$prefix = 'Crockidialer\\';
$base_dir = CROCKIDIALER_PATH . 'src/';
$relative_class = substr($class, strlen($prefix));
$file = $base_dir . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class) . '.php';
// Multiple validation checks
if (
!is_readable($file) ||
!is_file($file) ||
pathinfo($file, PATHINFO_EXTENSION) !== 'php'
) {
throw new CrockidialerException("Invalid class file: {$relative_class}");
}
require_once $file;
if (!class_exists($class, false) && !interface_exists($class, false) && !trait_exists($class, false)) {
throw new CrockidialerException("Class {$class} not found in {$file}");
}
} catch (Throwable $e) {
error_log("Crockidialer Autoloader Error: " . $e->getMessage());
if (CROCKIDIALER_DEBUG && current_user_can('manage_options')) {
add_action('admin_notices', function() use ($e) {
printf(
'',
esc_html($e->getMessage())
);
});
}
}
});
// Core initialization with comprehensive error handling
function crockidialer_init() {
// Check if plugin is properly initialized
if (get_option('crockidialer_initialized') !== '1') {
add_action('admin_notices', function() {
printf(
'',
esc_html__('Crockidialer plugin is not properly initialized. Please deactivate and reactivate the plugin.', 'crockidialer')
);
});
return;
}
if (!current_user_can('activate_plugins')) {
return;
}
try {
// Verify autoloader is available
if (!file_exists(CROCKIDIALER_PATH . 'vendor/autoload.php')) {
throw new CrockidialerException('Composer dependencies not installed');
}
// Required files with detailed error reporting
$required_files = [
'src/Controllers/LeadController.php',
'src/Controllers/ClientController.php',
'src/Controllers/EmailController.php',
'src/Controllers/TwilioController.php',
'src/Controllers/AnalyticsController.php',
'src/Controllers/MassComController.php',
'src/Controllers/AjaxController.php',
'src/Controllers/LeadIntegrationController.php',
'src/Controllers/TemplateController.php',
'src/Controllers/CommunicationController.php',
'src/Controllers/EmailTemplateController.php'
];
// Verify directory exists first
if (!is_dir(CROCKIDIALER_PATH . 'src/Controllers')) {
throw new CrockidialerException('Controllers directory not found');
}
// Check file permissions and readability
$missing_files = array_filter($required_files, function($file) {
$full_path = CROCKIDIALER_PATH . $file;
return !is_readable($full_path) || !is_file($full_path);
});
if (!empty($missing_files)) {
throw new CrockidialerException(
'Missing or unreadable files: ' . implode(', ', $missing_files)
);
}
// Admin-specific initialization with safety checks
if (is_admin()) {
if (!function_exists('add_menu_page')) {
throw new CrockidialerException('WordPress admin functions not available');
}
add_action('admin_menu', 'crockidialer_admin_menu');
// Controller initialization with individual error handling
$controllers = [
'LeadController',
'ClientController',
'EmailController',
'TwilioController',
'AnalyticsController',
'MassComController',
'AjaxController',
'LeadIntegrationController',
'TemplateController',
'CommunicationController',
'EmailTemplateController'
];
// Verify namespace exists
if (!interface_exists('\\Crockidialer\\Controllers\\ControllerInterface')) {
throw new CrockidialerException('Controller interface not found');
}
foreach ($controllers as $controller) {
try {
$class = "\\Crockidialer\\Controllers\\{$controller}";
// Multiple validation checks
if (!class_exists($class)) {
throw new CrockidialerException("Controller class not found: {$controller}");
}
$reflection = new ReflectionClass($class);
if (!$reflection->implementsInterface('\\Crockidialer\\Controllers\\ControllerInterface')) {
throw new CrockidialerException("Controller {$controller} must implement ControllerInterface");
}
if (!$reflection->hasMethod('init')) {
throw new CrockidialerException("Controller missing init method: {$controller}");
}
$instance = new $class();
// Verify instance
if (!($instance instanceof \Crockidialer\Controllers\ControllerInterface)) {
throw new CrockidialerException("Invalid controller instance: {$controller}");
}
$instance->init();
} catch (Throwable $e) {
error_log("Crockidialer Controller Error ({$controller}): " . $e->getMessage());
// Only show to admins in debug mode
if (CROCKIDIALER_DEBUG && current_user_can('manage_options')) {
add_action('admin_notices', function() use ($e, $controller) {
printf(
'Controller Error (%s): %s
',
esc_html($controller),
esc_html($e->getMessage())
);
});
}
continue; // Skip failed controller but continue with others
}
}
}
} catch (Throwable $e) {
error_log("Crockidialer Critical Error: " . $e->getMessage());
if (current_user_can('manage_options')) {
add_action('admin_notices', function() use ($e) {
printf(
'',
esc_html($e->getMessage())
);
});
}
// Deactivate plugin on critical error
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
deactivate_plugins(plugin_basename(__FILE__));
if (isset($_GET['activate'])) {
unset($_GET['activate']);
}
}
}
// Admin menu setup with validation
function crockidialer_admin_menu() {
if (!current_user_can('manage_options')) {
return;
}
add_menu_page(
esc_html__('Crockidialer', 'crockidialer'),
esc_html__('Crockidialer', 'crockidialer'),
'manage_options',
'crockidialer',
'crockidialer_admin_page',
'dashicons-phone',
30
);
}
// Admin page with comprehensive security
function crockidialer_admin_page() {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'crockidialer'));
}
$dashboard_file = CROCKIDIALER_PATH . 'src/Views/admin/dashboard.php';
if (is_readable($dashboard_file) && is_file($dashboard_file)) {
include $dashboard_file;
} else {
printf(
'',
esc_html__('Crockidialer', 'crockidialer'),
esc_html__('Dashboard template not found.', 'crockidialer')
);
}
}
function crockidialer_pre_activation_check(): array {
$requirements = [];
$errors = [];
// Check PHP version
$requirements['php'] = [
'required' => '7.4',
'current' => PHP_VERSION,
'pass' => version_compare(PHP_VERSION, '7.4', '>=')
];
if (!$requirements['php']['pass']) {
$errors[] = sprintf('PHP version %s or higher is required. Your version: %s', '7.4', PHP_VERSION);
}
// Check WordPress version
global $wp_version;
$requirements['wordpress'] = [
'required' => '5.0',
'current' => $wp_version,
'pass' => version_compare($wp_version, '5.0', '>=')
];
if (!$requirements['wordpress']['pass']) {
$errors[] = sprintf('WordPress version %s or higher is required. Your version: %s', '5.0', $wp_version);
}
// Check PHP extensions
$required_extensions = ['curl', 'json', 'mbstring', 'openssl', 'pdo'];
foreach ($required_extensions as $ext) {
$requirements['extensions'][$ext] = [
'required' => true,
'current' => extension_loaded($ext),
'pass' => extension_loaded($ext)
];
if (!$requirements['extensions'][$ext]['pass']) {
$errors[] = sprintf('PHP extension %s is required but not installed.', $ext);
}
}
// Check write permissions
$required_writable_paths = [
CROCKIDIALER_PATH . 'logs',
WP_CONTENT_DIR . '/uploads'
];
foreach ($required_writable_paths as $path) {
$is_writable = wp_mkdir_p($path) && is_writable($path);
$requirements['writable'][$path] = [
'required' => true,
'current' => $is_writable,
'pass' => $is_writable
];
if (!$requirements['writable'][$path]['pass']) {
$errors[] = sprintf('Directory %s must be writable.', $path);
}
}
// Check MySQL version
global $wpdb;
$mysql_version = $wpdb->db_version();
$requirements['mysql'] = [
'required' => '5.6',
'current' => $mysql_version,
'pass' => version_compare($mysql_version, '5.6', '>=')
];
if (!$requirements['mysql']['pass']) {
$errors[] = sprintf('MySQL version %s or higher is required. Your version: %s', '5.6', $mysql_version);
}
return [
'requirements' => $requirements,
'errors' => $errors,
'pass' => empty($errors)
];
}
function crockidialer_activate() {
if (!current_user_can('activate_plugins')) {
return;
}
try {
// Run pre-activation checks
$check_results = crockidialer_pre_activation_check();
if (!$check_results['pass']) {
throw new CrockidialerException(
"Pre-activation checks failed:\n" . implode("\n", $check_results['errors'])
);
}
// Check for Composer autoloader
if (!file_exists(CROCKIDIALER_PATH . 'vendor/autoload.php')) {
throw new CrockidialerException('Composer dependencies not installed. Please run composer install.');
}
// Load Composer autoloader
require_once CROCKIDIALER_PATH . 'vendor/autoload.php';
// Verify required directories exist and are writable
$required_dirs = [
CROCKIDIALER_PATH . 'src/Controllers',
CROCKIDIALER_PATH . 'src/Services',
CROCKIDIALER_PATH . 'src/Templates',
CROCKIDIALER_PATH . 'logs'
];
foreach ($required_dirs as $dir) {
if (!is_dir($dir) && !wp_mkdir_p($dir)) {
throw new CrockidialerException("Failed to create directory: $dir");
}
if (!is_writable($dir)) {
throw new CrockidialerException("Directory not writable: $dir");
}
}
// Verify required files exist
$required_files = [
CROCKIDIALER_PATH . 'src/Controllers/ControllerInterface.php',
CROCKIDIALER_PATH . 'src/Services/BaseService.php'
];
foreach ($required_files as $file) {
if (!is_readable($file)) {
throw new CrockidialerException("Required file not readable: $file");
}
}
// Set up database tables with transaction support
global $wpdb;
$wpdb->show_errors();
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
// Start transaction if supported
$has_transaction = method_exists($wpdb, 'query') && $wpdb->query('START TRANSACTION');
try {
// Set up database tables
$charset_collate = $wpdb->get_charset_collate();
// Create leads table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_leads (
id bigint(20) NOT NULL AUTO_INCREMENT,
client_name varchar(255) NOT NULL,
phone_number varchar(20) NOT NULL,
email varchar(255),
source varchar(50),
status varchar(50) DEFAULT 'new',
disposition varchar(50),
family_members int(11),
date_of_birth date,
address text,
meta_data longtext,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_status (status),
KEY idx_disposition (disposition),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Create templates table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_templates (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
type varchar(50) NOT NULL,
subject varchar(255),
content longtext NOT NULL,
variables text,
category varchar(50),
created_by bigint(20),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_type (type),
KEY idx_category (category)
) $charset_collate;";
dbDelta($sql);
// Create campaigns table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_campaigns (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
type varchar(50) NOT NULL,
message text,
email_subject varchar(255),
email_template varchar(50),
schedule_type varchar(50),
schedule_time datetime,
target_segment varchar(50),
status varchar(50) DEFAULT 'draft',
last_run datetime,
created_by bigint(20),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_status (status),
KEY idx_schedule_time (schedule_time)
) $charset_collate;";
dbDelta($sql);
// Create communications log table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_communications (
id bigint(20) NOT NULL AUTO_INCREMENT,
lead_id bigint(20) NOT NULL,
type varchar(50) NOT NULL,
message text NOT NULL,
metadata text,
created_by bigint(20),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_lead_id (lead_id),
KEY idx_type (type)
) $charset_collate;";
dbDelta($sql);
// Create activity log table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_activity_log (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
action varchar(50) NOT NULL,
data text,
ip varchar(45),
timestamp datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_user_id (user_id),
KEY idx_action (action)
) $charset_collate;";
dbDelta($sql);
// Create email log table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_email_log (
id bigint(20) NOT NULL AUTO_INCREMENT,
lead_id bigint(20) NOT NULL,
template_type varchar(50),
sent_at datetime DEFAULT CURRENT_TIMESTAMP,
sent_by bigint(20),
PRIMARY KEY (id),
KEY idx_lead_id (lead_id),
KEY idx_template_type (template_type)
) $charset_collate;";
dbDelta($sql);
// Create calls table
$sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}crockidialer_calls (
id bigint(20) NOT NULL AUTO_INCREMENT,
lead_id bigint(20) NOT NULL,
twilio_sid varchar(255),
status varchar(50),
duration int(11) DEFAULT 0,
recording_url text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_lead_id (lead_id),
KEY idx_twilio_sid (twilio_sid)
) $charset_collate;";
dbDelta($sql);
if ($has_transaction) {
$wpdb->query('COMMIT');
}
} catch (Throwable $e) {
if ($has_transaction) {
$wpdb->query('ROLLBACK');
}
throw $e;
}
// Set default options with validation
$default_options = [
'crockidialer_twilio_account_sid' => '',
'crockidialer_twilio_auth_token' => '',
'crockidialer_twilio_phone_number' => '',
'crockidialer_sendgrid_api_key' => '',
'crockidialer_email_from_address' => get_option('admin_email'),
'crockidialer_email_from_name' => get_bloginfo('name'),
'crockidialer_debug_mode' => '0',
'crockidialer_log_level' => 'error',
'crockidialer_initialized' => '0'
];
foreach ($default_options as $option => $value) {
if (get_option($option) === false) {
add_option($option, $value);
}
}
// Create .htaccess for logs directory
$htaccess_content = "Order deny,allow\nDeny from all";
$htaccess_file = CROCKIDIALER_PATH . 'logs/.htaccess';
if (!file_exists($htaccess_file)) {
if (file_put_contents($htaccess_file, $htaccess_content) === false) {
throw new CrockidialerException('Failed to create .htaccess file for logs directory');
}
}
// Store activation time and mark as initialized
update_option('crockidialer_activated_time', time());
update_option('crockidialer_initialized', '1');
// Flush rewrite rules
flush_rewrite_rules();
// Clear any existing error logs
$log_file = CROCKIDIALER_PATH . 'logs/crockidialer.log';
if (file_exists($log_file)) {
@unlink($log_file);
}
} catch (Throwable $e) {
error_log("Crockidialer Activation Error: " . $e->getMessage());
// Cleanup on activation failure
foreach ($default_options as $option => $value) {
delete_option($option);
}
wp_die(
esc_html($e->getMessage()),
'Activation Error',
['back_link' => true]
);
}
}
// Careful deactivation that preserves data
function crockidialer_deactivate() {
if (!current_user_can('activate_plugins')) {
return;
}
try {
// Cleanup transients
delete_transient('crockidialer_doing_update');
// Log deactivation
error_log("Crockidialer: Plugin deactivated successfully");
// Store deactivation time
update_option('crockidialer_deactivated_time', time());
} catch (Throwable $e) {
error_log("Crockidialer Deactivation Error: " . $e->getMessage());
}
}
// Uninstall hook for complete cleanup
function crockidialer_uninstall() {
if (!current_user_can('activate_plugins')) {
return;
}
try {
// Clean up all plugin-specific options
delete_option('crockidialer_activated_time');
delete_option('crockidialer_deactivated_time');
// Log uninstall
error_log("Crockidialer: Plugin uninstalled successfully");
} catch (Throwable $e) {
error_log("Crockidialer Uninstall Error: " . $e->getMessage());
}
}
// Register core hooks with existence checks
if (function_exists('register_activation_hook')) {
register_activation_hook(CROCKIDIALER_FILE, 'crockidialer_activate');
}
if (function_exists('register_deactivation_hook')) {
register_deactivation_hook(CROCKIDIALER_FILE, 'crockidialer_deactivate');
}
if (function_exists('register_uninstall_hook')) {
register_uninstall_hook(CROCKIDIALER_FILE, 'crockidialer_uninstall');
}
// Initialize only if all systems are go
if (
defined('CROCKIDIALER_VERSION') &&
function_exists('add_action') &&
did_action('plugins_loaded') === 0
) {
add_action('plugins_loaded', 'crockidialer_init');
}
https://crockidial.com/wp-sitemap-posts-post-1.xmlhttps://crockidial.com/wp-sitemap-posts-page-1.xmlhttps://crockidial.com/wp-sitemap-taxonomies-category-1.xmlhttps://crockidial.com/wp-sitemap-users-1.xml