/**
* CREHQ Modern Theme Functions
*
* @package CREHQ_Modern
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
// Include Pro Features for company profiles
require_once get_template_directory() . "/inc/pro-features.php";
/**
* Theme Setup
*/
function crehq_modern_setup() {
// Add default posts and comments RSS feed links to head
add_theme_support('automatic-feed-links');
// Let WordPress manage the document title
add_theme_support('title-tag');
// Enable support for Post Thumbnails on posts and pages
add_theme_support('post-thumbnails');
// Set default thumbnail size
set_post_thumbnail_size(1200, 675, true);
// Register navigation menus
register_nav_menus(array(
'primary' => __('Primary Menu', 'crehq-modern'),
'footer' => __('Footer Menu', 'crehq-modern'),
));
// Switch default core markup to output valid HTML5
add_theme_support('html5', array(
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'script',
'style',
));
// Add theme support for Custom Logo
add_theme_support('custom-logo', array(
'height' => 50,
'width' => 200,
'flex-width' => true,
'flex-height' => true,
));
// Add support for responsive embedded content
add_theme_support('responsive-embeds');
// Add support for editor styles
add_theme_support('editor-styles');
// Add support for wide alignment
add_theme_support('align-wide');
}
add_action('after_setup_theme', 'crehq_modern_setup');
/**
* Enqueue Scripts and Styles
*/
function crehq_modern_scripts() {
// Enqueue Google Fonts - Inter for modern, professional typography
wp_enqueue_style(
'crehq-modern-fonts',
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap',
array(),
null
);
// Enqueue Tailwind CSS from CDN
wp_enqueue_style(
'tailwindcss',
get_template_directory_uri() . '/assets/css/tailwind.min.css',
array(),
'3.4.1'
);
// Enqueue main stylesheet (after Tailwind for overrides)
wp_enqueue_style('crehq-modern-style', get_stylesheet_uri(), array('tailwindcss'), '1.0.1');
// Enqueue main theme script (if file exists)
$main_js = get_template_directory() . '/assets/js/main.js';
if (file_exists($main_js)) {
wp_enqueue_script(
'crehq-modern-script',
get_template_directory_uri() . '/assets/js/main.js',
array(),
'1.0.0',
true
);
}
// Enqueue comment reply script
if (is_singular() && comments_open() && get_option('thread_comments')) {
wp_enqueue_script('comment-reply');
}
}
add_action('wp_enqueue_scripts', 'crehq_modern_scripts');
/**
* Register Widget Areas
*/
function crehq_modern_widgets_init() {
// Sidebar widget area
register_sidebar(array(
'name' => __('Sidebar', 'crehq-modern'),
'id' => 'sidebar-1',
'description' => __('Add widgets here to appear in your sidebar.', 'crehq-modern'),
'before_widget' => '',
'before_title' => '
',
'after_title' => '
',
));
// Footer widget area 1
register_sidebar(array(
'name' => __('Footer 1', 'crehq-modern'),
'id' => 'footer-1',
'description' => __('Add widgets here to appear in your footer.', 'crehq-modern'),
'before_widget' => '',
'before_title' => '',
'after_title' => '
',
));
// Footer widget area 2
register_sidebar(array(
'name' => __('Footer 2', 'crehq-modern'),
'id' => 'footer-2',
'description' => __('Add widgets here to appear in your footer.', 'crehq-modern'),
'before_widget' => '',
'before_title' => '',
'after_title' => '
',
));
// Footer widget area 3
register_sidebar(array(
'name' => __('Footer 3', 'crehq-modern'),
'id' => 'footer-3',
'description' => __('Add widgets here to appear in your footer.', 'crehq-modern'),
'before_widget' => '',
'before_title' => '',
'after_title' => '
',
));
}
add_action('widgets_init', 'crehq_modern_widgets_init');
/**
* Custom excerpt length
*/
function crehq_modern_excerpt_length($length) {
return 30;
}
add_filter('excerpt_length', 'crehq_modern_excerpt_length');
/**
* Custom excerpt more
*/
function crehq_modern_excerpt_more($more) {
return '...';
}
add_filter('excerpt_more', 'crehq_modern_excerpt_more');
/**
* Include helper files
*/
require_once get_template_directory() . '/inc/post-types.php';
require_once get_template_directory() . '/inc/membership-functions.php';
require_once get_template_directory() . '/inc/company-functions.php';
require_once get_template_directory() . '/inc/customizer.php';
/**
* Enqueue additional scripts for advanced functionality
*/
function crehq_modern_enqueue_advanced_scripts() {
// Dark mode script
wp_enqueue_script(
'crehq-dark-mode',
get_template_directory_uri() . '/assets/js/dark-mode.js',
array(),
'1.0.0',
true
);
// Company search and filters (only on company archive)
if (is_post_type_archive('company') || is_tax('company_category')) {
wp_enqueue_script(
'crehq-company-search',
get_template_directory_uri() . '/assets/js/company-search.js',
array('jquery'),
'1.0.0',
true
);
wp_enqueue_script(
'crehq-company-filters',
get_template_directory_uri() . '/assets/js/company-filters.js',
array('jquery'),
'1.0.0',
true
);
// Localize script for AJAX
wp_localize_script('crehq-company-search', 'crehqAjax', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('crehq_search_nonce'),
));
}
// Company tabs (only on single company)
if (is_singular('company')) {
wp_enqueue_script(
'crehq-company-tabs',
get_template_directory_uri() . '/assets/js/company-tabs.js',
array(),
'1.0.0',
true
);
}
}
add_action('wp_enqueue_scripts', 'crehq_modern_enqueue_advanced_scripts');
/**
* AJAX handler for company search
*/
function crehq_ajax_company_search() {
check_ajax_referer('crehq_search_nonce', 'nonce');
$search_term = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
$args = array(
'post_type' => 'company',
'post_status' => 'publish',
'posts_per_page' => 10,
's' => $search_term,
);
$query = new WP_Query($args);
$results = array();
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$results[] = array(
'id' => get_the_ID(),
'title' => get_the_title(),
'url' => get_permalink(),
'logo' => crehq_get_company_logo(get_the_ID(), 'thumbnail'),
'location' => get_post_meta(get_the_ID(), 'company_location', true),
'store_count' => get_post_meta(get_the_ID(), 'store_count', true),
);
}
}
wp_reset_postdata();
wp_send_json_success($results);
}
add_action('wp_ajax_crehq_company_search', 'crehq_ajax_company_search');
add_action('wp_ajax_nopriv_crehq_company_search', 'crehq_ajax_company_search');
/**
* AJAX handler for saving searches
*/
function crehq_ajax_save_search() {
check_ajax_referer('crehq_search_nonce', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error(array('message' => __('Please log in to save searches.', 'crehq-modern')));
}
$search_name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
$search_params = isset($_POST['params']) ? $_POST['params'] : array();
if (empty($search_name)) {
wp_send_json_error(array('message' => __('Please provide a name for your search.', 'crehq-modern')));
}
$result = crehq_save_user_search($search_name, $search_params);
if ($result) {
wp_send_json_success(array('message' => __('Search saved successfully!', 'crehq-modern')));
} else {
wp_send_json_error(array('message' => __('Failed to save search. Premium membership required.', 'crehq-modern')));
}
}
add_action('wp_ajax_crehq_save_search', 'crehq_ajax_save_search');
/**
* Add body classes for theme features
*/
function crehq_modern_body_classes($classes) {
// Add class if user is premium
if (crehq_is_premium_user()) {
$classes[] = 'premium-user';
}
// Add class for current view (if set in cookie/localStorage)
if (isset($_COOKIE['crehq_view'])) {
$classes[] = 'view-' . sanitize_html_class($_COOKIE['crehq_view']);
}
return $classes;
}
add_filter('body_class', 'crehq_modern_body_classes');
/**
* Modify the company archive query
*/
function crehq_modify_company_archive($query) {
if (!is_admin() && $query->is_main_query() && is_post_type_archive('company')) {
$query->set('posts_per_page', 24);
// Handle sorting
if (!empty($_GET['orderby'])) {
switch ($_GET['orderby']) {
case 'title':
$query->set('orderby', 'title');
$query->set('order', 'ASC');
break;
case 'date':
$query->set('orderby', 'date');
$query->set('order', 'DESC');
break;
case 'store_count':
$query->set('meta_key', 'store_count');
$query->set('orderby', 'meta_value_num');
$query->set('order', 'DESC');
break;
}
}
}
}
add_action('pre_get_posts', 'crehq_modify_company_archive');
// =============================================================================
// USER PROFILE FUNCTIONS
// =============================================================================
if (!function_exists('crehq_get_user_types')) {
/**
* Get available user types for profile selection
*/
function crehq_get_user_types() {
return array(
'cre_professional' => array(
'label' => 'CRE Professional',
'description' => 'Broker, developer, property manager, or other CRE professional',
'icon' => '🏢',
'public_profile' => true
),
'building_owner' => array(
'label' => 'Property Owner',
'description' => 'I own or manage commercial properties',
'icon' => '🏠',
'public_profile' => true
),
'investor' => array(
'label' => 'Investor',
'description' => 'I invest in commercial real estate',
'icon' => '💰',
'public_profile' => true
),
'business_owner' => array(
'label' => 'Business Owner/Tenant',
'description' => 'Looking for retail or commercial space',
'icon' => '🏪',
'public_profile' => false
),
'city_employee' => array(
'label' => 'Government/EDC',
'description' => 'Economic development or city planning',
'icon' => '🏛️',
'public_profile' => true
),
'other' => array(
'label' => 'Other',
'description' => 'Researcher, student, or other interest',
'icon' => '👤',
'public_profile' => false
)
);
}
}
if (!function_exists('crehq_get_professional_roles')) {
/**
* Get professional role categories for directory
*/
function crehq_get_professional_roles() {
return array(
'broker' => 'Broker/Agent',
'developer' => 'Developer',
'property_manager' => 'Property Manager',
'appraiser' => 'Appraiser',
'lender' => 'Lender/Capital Markets',
'attorney' => 'Attorney',
'consultant' => 'Consultant',
'asset_manager' => 'Asset Manager',
'architect' => 'Architect/Designer',
'contractor' => 'Contractor/Construction',
'investor' => 'Investor',
'other' => 'Other CRE Professional'
);
}
}
if (!function_exists('crehq_get_property_specializations')) {
/**
* Get property type specializations
*/
function crehq_get_property_specializations() {
return array(
'office' => 'Office',
'retail' => 'Retail',
'industrial' => 'Industrial',
'multifamily' => 'Multifamily',
'hospitality' => 'Hospitality',
'healthcare' => 'Healthcare',
'land' => 'Land',
'mixed_use' => 'Mixed-Use',
'net_lease' => 'Net Lease',
'self_storage' => 'Self Storage'
);
}
}
if (!function_exists('crehq_get_profile_fields')) {
/**
* Get profile fields based on user type
*/
function crehq_get_profile_fields($user_type = '') {
$common_fields = array(
'phone' => array(
'label' => 'Phone Number',
'type' => 'tel',
'placeholder' => '(555) 123-4567'
),
'city' => array(
'label' => 'City',
'type' => 'text',
'placeholder' => 'e.g., Chicago'
),
'state' => array(
'label' => 'State',
'type' => 'select',
'options' => array('' => 'Select State') + array('AL'=>'Alabama','AK'=>'Alaska','AZ'=>'Arizona','AR'=>'Arkansas','CA'=>'California','CO'=>'Colorado','CT'=>'Connecticut','DE'=>'Delaware','FL'=>'Florida','GA'=>'Georgia','HI'=>'Hawaii','ID'=>'Idaho','IL'=>'Illinois','IN'=>'Indiana','IA'=>'Iowa','KS'=>'Kansas','KY'=>'Kentucky','LA'=>'Louisiana','ME'=>'Maine','MD'=>'Maryland','MA'=>'Massachusetts','MI'=>'Michigan','MN'=>'Minnesota','MS'=>'Mississippi','MO'=>'Missouri','MT'=>'Montana','NE'=>'Nebraska','NV'=>'Nevada','NH'=>'New Hampshire','NJ'=>'New Jersey','NM'=>'New Mexico','NY'=>'New York','NC'=>'North Carolina','ND'=>'North Dakota','OH'=>'Ohio','OK'=>'Oklahoma','OR'=>'Oregon','PA'=>'Pennsylvania','RI'=>'Rhode Island','SC'=>'South Carolina','SD'=>'South Dakota','TN'=>'Tennessee','TX'=>'Texas','UT'=>'Utah','VT'=>'Vermont','VA'=>'Virginia','WA'=>'Washington','WV'=>'West Virginia','WI'=>'Wisconsin','WY'=>'Wyoming')
),
'linkedin' => array(
'label' => 'LinkedIn Profile',
'type' => 'url',
'placeholder' => 'https://linkedin.com/in/yourprofile'
),
'bio' => array(
'label' => 'Bio / About',
'type' => 'textarea',
'rows' => 4,
'placeholder' => 'Tell us about yourself and your experience...'
)
);
$professional_fields = array(
'professional_role' => array(
'label' => 'Professional Role',
'type' => 'select',
'options' => array('' => 'Select your role') + crehq_get_professional_roles(),
'required' => true
),
'company_name' => array(
'label' => 'Company Name',
'type' => 'text',
'placeholder' => 'Your company or brokerage'
),
'brokerage' => array(
'label' => 'Brokerage/Firm',
'type' => 'text',
'placeholder' => 'e.g., CBRE, JLL, Cushman & Wakefield'
),
'years_experience' => array(
'label' => 'Years of Experience',
'type' => 'select',
'options' => array(
'' => 'Select',
'1' => '1-2 years',
'3' => '3-5 years',
'6' => '6-10 years',
'11' => '11-20 years',
'20' => '20+ years'
)
),
'specializations' => array(
'label' => 'Property Type Specializations',
'type' => 'checkboxes',
'options' => crehq_get_property_specializations()
),
'public_profile' => array(
'label' => 'List in Professional Directory',
'type' => 'checkbox',
'description' => 'Make my profile visible in the public CRE Professional Directory (free for all members)'
),
'profile_photo' => array(
'label' => 'Profile Photo',
'type' => 'file',
'accept' => 'image/*'
)
);
$owner_fields = array(
'company_name' => array(
'label' => 'Company/Entity Name',
'type' => 'text',
'placeholder' => 'Your company or LLC'
),
'portfolio_size' => array(
'label' => 'Portfolio Size',
'type' => 'select',
'options' => array(
'' => 'Select',
'1-5' => '1-5 properties',
'6-20' => '6-20 properties',
'21-50' => '21-50 properties',
'50+' => '50+ properties'
)
),
'property_types' => array(
'label' => 'Property Types Owned',
'type' => 'checkboxes',
'options' => crehq_get_property_specializations()
),
'public_profile' => array(
'label' => 'List in Professional Directory',
'type' => 'checkbox',
'description' => 'Make my profile visible in the public CRE Professional Directory'
)
);
switch ($user_type) {
case 'cre_professional':
case 'cre_agent': // Legacy support
return array_merge($professional_fields, $common_fields);
case 'building_owner':
return array_merge($owner_fields, $common_fields);
case 'investor':
return array_merge(array(
'investment_focus' => array(
'label' => 'Investment Focus',
'type' => 'checkboxes',
'options' => crehq_get_property_specializations()
),
'investment_range' => array(
'label' => 'Investment Range',
'type' => 'select',
'options' => array(
'' => 'Select',
'under_1m' => 'Under $1M',
'1m_5m' => '$1M - $5M',
'5m_25m' => '$5M - $25M',
'25m_plus' => '$25M+'
)
),
'public_profile' => array(
'label' => 'List in Professional Directory',
'type' => 'checkbox',
'description' => 'Make my profile visible in the public CRE Professional Directory'
)
), $common_fields);
case 'city_employee':
return array_merge(array(
'organization' => array(
'label' => 'Organization/Agency',
'type' => 'text',
'placeholder' => 'e.g., City of Chicago EDC'
),
'title' => array(
'label' => 'Title/Position',
'type' => 'text',
'placeholder' => 'Your job title'
),
'public_profile' => array(
'label' => 'List in Professional Directory',
'type' => 'checkbox',
'description' => 'Make my profile visible in the public CRE Professional Directory'
)
), $common_fields);
default:
return $common_fields;
}
}
}
if (!function_exists('crehq_save_user_profile')) {
/**
* Save user profile data
*/
function crehq_save_user_profile($user_id, $data) {
// Fields to save
$meta_fields = array(
'user_type', 'professional_role', 'phone', 'city', 'state',
'company_name', 'brokerage', 'years_experience', 'bio',
'linkedin', 'portfolio_size', 'investment_range', 'organization', 'title'
);
foreach ($meta_fields as $field) {
if (isset($data[$field])) {
update_user_meta($user_id, '_crehq_' . $field, sanitize_text_field($data[$field]));
}
}
// Handle arrays (checkboxes)
$array_fields = array('specializations', 'property_types', 'investment_focus');
foreach ($array_fields as $field) {
if (isset($data[$field]) && is_array($data[$field])) {
update_user_meta($user_id, '_crehq_' . $field, array_map('sanitize_text_field', $data[$field]));
} elseif (isset($data[$field])) {
update_user_meta($user_id, '_crehq_' . $field, array());
}
}
// Handle public profile checkbox
$public_profile = isset($data['public_profile']) && $data['public_profile'] === '1' ? '1' : '0';
update_user_meta($user_id, '_crehq_public_profile', $public_profile);
// Handle profile photo upload
if (!empty($_FILES['profile_photo']['tmp_name'])) {
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
$attachment_id = media_handle_upload('profile_photo', 0);
if (!is_wp_error($attachment_id)) {
update_user_meta($user_id, '_crehq_profile_photo_id', $attachment_id);
update_user_meta($user_id, '_crehq_profile_photo', wp_get_attachment_url($attachment_id));
}
}
// Calculate and update profile completion
$completion = crehq_calculate_profile_completion($user_id);
update_user_meta($user_id, '_crehq_profile_completion', $completion);
return true;
}
}
if (!function_exists('crehq_calculate_profile_completion')) {
/**
* Calculate profile completion percentage
*/
function crehq_calculate_profile_completion($user_id) {
$user = get_user_by('ID', $user_id);
if (!$user) return 0;
$fields_to_check = array(
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'user_type' => get_user_meta($user_id, '_crehq_user_type', true),
'city' => get_user_meta($user_id, '_crehq_city', true),
'state' => get_user_meta($user_id, '_crehq_state', true),
'bio' => get_user_meta($user_id, '_crehq_bio', true),
'professional_role' => get_user_meta($user_id, '_crehq_professional_role', true),
'company_name' => get_user_meta($user_id, '_crehq_company_name', true)
);
$completed = 0;
$total = count($fields_to_check);
foreach ($fields_to_check as $field => $value) {
if (!empty($value)) {
$completed++;
}
}
return round(($completed / $total) * 100);
}
}
// Redirect /agents/ to /professionals/
add_action('template_redirect', function() {
if (is_page('agents') || strpos($_SERVER['REQUEST_URI'], '/agents/') !== false || strpos($_SERVER['REQUEST_URI'], '/agents') !== false) {
$new_url = str_replace('/agents', '/professionals', $_SERVER['REQUEST_URI']);
wp_redirect(home_url($new_url), 301);
exit;
}
});
// =============================================================================
// PROFESSIONAL PROFILE URL REWRITE
// =============================================================================
// Add rewrite rules for professional profiles
add_action('init', function() {
add_rewrite_rule(
'^professional/([^/]+)/?$',
'index.php?professional_name=$matches[1]',
'top'
);
// Legacy support - redirect /agent/ to /professional/
add_rewrite_rule(
'^agent/([^/]+)/?$',
'index.php?redirect_agent=$matches[1]',
'top'
);
});
// Register query vars
add_filter('query_vars', function($vars) {
$vars[] = 'professional_name';
$vars[] = 'redirect_agent';
$vars[] = 'agent_name'; // Legacy support
return $vars;
});
// Handle template loading and redirects
add_action('template_redirect', function() {
// Redirect /agent/{name}/ to /professional/{name}/
$redirect_agent = get_query_var('redirect_agent');
if ($redirect_agent) {
wp_redirect(home_url('/professional/' . $redirect_agent . '/'), 301);
exit;
}
// Load professional profile template
$professional_name = get_query_var('professional_name');
if ($professional_name) {
// Set for template compatibility
set_query_var('agent_name', $professional_name);
include(get_template_directory() . '/template-agent-profile.php');
exit;
}
}, 5);
// =============================================================================
// CRE INTELLIGENCE METRICS FUNCTIONS
// =============================================================================
/**
* Count development contacts for a company
* Counts CI_Name_* fields that have values
*/
function crehq_count_development_contacts($company_id) {
$count = 0;
// Check contacts meta field first (if stored as count)
$contacts_count = get_post_meta($company_id, 'contacts', true);
if ($contacts_count && is_numeric($contacts_count)) {
return intval($contacts_count);
}
// Otherwise count CI_Name_* fields
for ($i = 1; $i <= 20; $i++) {
$name = get_post_meta($company_id, 'CI_Name_' . $i, true);
if (!empty($name)) {
$count++;
}
}
// Also check CIName_* format
if ($count === 0) {
for ($i = 1; $i <= 20; $i++) {
$name = get_post_meta($company_id, 'CIName_' . $i, true);
if (!empty($name)) {
$count++;
}
}
}
return $count;
}
/**
* Count site requirement data points for a company
* Counts REC* fields that have meaningful values
*/
function crehq_count_site_data_points($company_id) {
$count = 0;
// Key REC fields to check
$rec_fields = array(
'RECSQFootageMin_1', 'RECSQFootageMax_1',
'RECsite_acreage_min_1', 'RECsite_acreage_max_1',
'RECTraffic_1', 'RECPopulation_1', 'RECPopulationDaytime_1',
'RECMinimumParking_1', 'RECFTFrontageMin_1', 'RECFTFrontageMax_1',
'RECMedianIncome_1', 'REChouseholds_1', 'RECgrowth_rate_1',
'RECsite_characteristics_1', 'RECtarget_markets_1',
'RECco_tenancy_1', 'RECcorner_preference_1', 'RECaccess_points_1',
'RECbuilding_dimensions_1', 'RECbuilding_height_1',
'RECdrive_thru_stack_1', 'RECexclusions_1', 'RECmarket_notes_1',
'RECconcept_one_1', 'RECProcessSteps_1', 'RECTimeline_1'
);
foreach ($rec_fields as $field) {
$value = get_post_meta($company_id, $field, true);
if (!empty($value) && $value !== '0') {
$count++;
}
}
return $count;
}
/**
* Get expansion status label and color
*/
function crehq_get_expansion_status_display($company_id) {
$status = get_post_meta($company_id, 'expansion_status', true);
$statuses = array(
'actively_expanding' => array(
'label' => 'Actively Expanding',
'color' => 'emerald',
'icon' => 'trending-up'
),
'selective_growth' => array(
'label' => 'Selective Growth',
'color' => 'blue',
'icon' => 'target'
),
'stable' => array(
'label' => 'Stable',
'color' => 'gray',
'icon' => 'minus'
),
'restructuring' => array(
'label' => 'Restructuring',
'color' => 'amber',
'icon' => 'refresh'
),
'contracting' => array(
'label' => 'Contracting',
'color' => 'red',
'icon' => 'trending-down'
)
);
if (isset($statuses[$status])) {
return $statuses[$status];
}
// Default if no status set
return array(
'label' => 'Status Unknown',
'color' => 'gray',
'icon' => 'question'
);
}
/**
* Format store count for display
*/
if (!function_exists('crehq_format_store_count')) {
function crehq_format_store_count($count) {
if ($count >= 1000) {
return number_format($count / 1000, 1) . 'K';
}
return number_format($count);
}
}
/**
* Redirect users to customer dashboard after login instead of wp-admin
* Admins can still access wp-admin via the Admin link in header
*/
add_filter('login_redirect', function($redirect_to, $requested_redirect_to, $user) {
// If user object isn't valid, return default
if (!is_a($user, 'WP_User')) {
return $redirect_to;
}
// If explicitly requesting wp-admin (e.g., came from admin page), allow it
if (strpos($requested_redirect_to, 'wp-admin') !== false) {
return $redirect_to;
}
// Redirect all users to customer dashboard by default
return home_url('/dashboard/');
}, 10, 3);
/**
* Security: Disable user enumeration via REST API
* Prevents unauthenticated access to /wp-json/wp/v2/users
*/
add_filter('rest_endpoints', function($endpoints) {
if (!is_user_logged_in()) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P[\d]+)']);
}
}
return $endpoints;
});
/**
* Security: Disable author archives to prevent user enumeration
*/
add_action('template_redirect', function() {
if (is_author() && !is_user_logged_in()) {
wp_redirect(home_url(), 301);
exit;
}
});
/**
* Get s2Member checkout shortcode for membership plans
*
* @param string $plan 'annual' or 'monthly'
* @return string The rendered shortcode output
*/
function crehq_get_checkout_shortcode($plan = 'annual') {
if ($plan === 'monthly') {
// Monthly Pro membership - $49.99/month
$shortcode = '[s2Member-Pro-Stripe-Form level="1" ccaps="" desc="CREHQ Pro Monthly ($49.99/month)" cc="USD" custom="crehq.com" ta="0" tp="0" tt="D" ra="49.99" rp="1" rt="M" rr="1" coupon="" accept_coupons="1" default_country_code="US" captcha="0" /]';
} else {
// Annual Pro membership - $539.88/year ($44.99/month)
$shortcode = '[s2Member-Pro-Stripe-Form level="1" ccaps="" desc="CREHQ Pro Annual ($539.88/year)" cc="USD" custom="crehq.com" ta="0" tp="0" tt="D" ra="539.88" rp="1" rt="Y" rr="1" coupon="" accept_coupons="1" default_country_code="US" captcha="0" /]';
}
return do_shortcode($shortcode);
}
/**
* SEO: Custom meta descriptions for Data Store pages
*/
add_filter('wpseo_metadesc', function($description) {
// Dataset single page
if (get_query_var('crehq_dataset_slug')) {
$slug = get_query_var('crehq_dataset_slug');
if (class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) {
$dataset = \CREHQ\CompanyLocations\DataStore\Dataset::find_by_slug($slug);
if ($dataset) {
return sprintf(
'Download %s store location data with %s locations across %s. Includes addresses, coordinates, phone numbers, and hours. CSV, JSON, GeoJSON formats.',
$dataset->name,
number_format($dataset->location_count),
implode(', ', array_slice($dataset->countries, 0, 3))
);
}
}
}
// Data store listing page
if (is_page('data-store')) {
return 'Download retail store location datasets for market analysis. 200+ brands with addresses, coordinates, hours, and phone numbers. CSV, JSON, GeoJSON formats available.';
}
return $description;
});
/**
* SEO: Custom titles for Data Store pages
*/
add_filter('wpseo_title', function($title) {
// Dataset single page
if (get_query_var('crehq_dataset_slug')) {
$slug = get_query_var('crehq_dataset_slug');
if (class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) {
$dataset = \CREHQ\CompanyLocations\DataStore\Dataset::find_by_slug($slug);
if ($dataset) {
return sprintf('%s Store Locations Data | %s Locations | CREHQ', $dataset->name, number_format($dataset->location_count));
}
}
}
return $title;
});
/**
* SEO: Open Graph tags for Data Store pages
*/
add_filter('wpseo_opengraph_desc', function($description) {
if (get_query_var('crehq_dataset_slug')) {
$slug = get_query_var('crehq_dataset_slug');
if (class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) {
$dataset = \CREHQ\CompanyLocations\DataStore\Dataset::find_by_slug($slug);
if ($dataset) {
return sprintf(
'Download %s location data - %s store locations with addresses, geo coordinates, hours, and contact info.',
$dataset->name,
number_format($dataset->location_count)
);
}
}
}
return $description;
});
/**
* SEO: Add structured data for datasets (Schema.org Dataset)
*/
add_action('wp_head', function() {
if (!get_query_var('crehq_dataset_slug')) return;
$slug = get_query_var('crehq_dataset_slug');
if (!class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) return;
$dataset = \CREHQ\CompanyLocations\DataStore\Dataset::find_by_slug($slug);
if (!$dataset) return;
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Dataset',
'name' => $dataset->name . ' Store Locations',
'description' => sprintf('Location data for %s stores including addresses, coordinates, hours, and contact information.', $dataset->name),
'url' => home_url('/data-store/' . $dataset->slug . '/'),
'keywords' => [$dataset->name, 'store locations', 'retail data', 'location data', $dataset->category],
'creator' => [
'@type' => 'Organization',
'name' => 'CREHQ',
'url' => home_url('/')
],
'distribution' => [
[
'@type' => 'DataDownload',
'encodingFormat' => 'text/csv',
'contentUrl' => home_url('/data-store/' . $dataset->slug . '/')
],
[
'@type' => 'DataDownload',
'encodingFormat' => 'application/json',
'contentUrl' => home_url('/data-store/' . $dataset->slug . '/')
]
],
'variableMeasured' => 'Store count: ' . number_format($dataset->location_count),
'spatialCoverage' => implode(', ', $dataset->countries),
'dateModified' => $dataset->last_updated ?: date('Y-m-d')
];
echo '' . "
";
}, 5);
/**
* SEO: Add rewrite rules for Data Store category pages
* Creates URLs like /data-store/category/fast-food/
*/
add_action('init', function() {
add_rewrite_rule(
'^data-store/category/([^/]+)/?$',
'index.php?crehq_datastore=1&ds_category=$matches[1]',
'top'
);
});
add_filter('query_vars', function($vars) {
$vars[] = 'ds_category';
return $vars;
});
/**
* SEO: Meta descriptions for category pages
*/
add_filter('wpseo_metadesc', function($description) {
// Parse URL directly since query vars may not be set when Yoast runs
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (preg_match('#/data-store/category/([^/]+)/?#', $uri, $matches)) {
$category_name = ucwords(str_replace('-', ' ', $matches[1]));
return sprintf(
'Download %s store location datasets. Browse verified retail data with addresses, coordinates, and hours. CSV, JSON, GeoJSON formats.',
$category_name
);
}
return $description;
}, 999);
add_filter('wpseo_title', function($title) {
// Parse URL directly since query vars may not be set when Yoast runs
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (preg_match('#/data-store/category/([^/]+)/?#', $uri, $matches)) {
$category_name = ucwords(str_replace('-', ' ', $matches[1]));
return sprintf('%s Location Datasets | Data Store | CREHQ', $category_name);
}
return $title;
}, 999);
/**
* SEO: Override document title for category pages (higher priority than Yoast)
*/
add_filter('pre_get_document_title', function($title) {
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (preg_match('#/data-store/category/([^/]+)/?#', $uri, $matches)) {
$category_name = ucwords(str_replace('-', ' ', $matches[1]));
return sprintf('%s Location Datasets | Data Store | CREHQ', $category_name);
}
return $title;
}, 999);
/**
* SEO: Fix canonical URL for category pages
*/
add_filter('wpseo_canonical', function($canonical) {
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (preg_match('#/data-store/category/([^/]+)/?#', $uri, $matches)) {
return home_url('/data-store/category/' . $matches[1] . '/');
}
return $canonical;
}, 999);
/**
* Get related datasets by category
*/
function crehq_get_related_datasets($current_dataset, $limit = 4) {
if (!class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) {
return [];
}
$datasets = \CREHQ\CompanyLocations\DataStore\Dataset::query([
'status' => 'active',
'category' => $current_dataset->category,
'limit' => $limit + 1, // +1 to account for current dataset
'orderby' => 'location_count',
'order' => 'DESC'
]);
$related = [];
foreach ($datasets as $ds) {
if ($ds->slug === $current_dataset->slug) continue;
if ($ds->location_count > 0) {
$related[] = $ds;
if (count($related) >= $limit) break;
}
}
return $related;
}
/**
* Add Data Store datasets to Yoast sitemap
*/
add_filter('wpseo_sitemap_index', function($sitemap_custom_items) {
global $wpdb;
$table = $wpdb->prefix . 'crehq_datasets';
$last_modified = $wpdb->get_var("SELECT MAX(last_updated) FROM $table WHERE status = 'active'");
if (!$last_modified) $last_modified = current_time('c');
$sitemap_custom_items .= '' . PHP_EOL;
$sitemap_custom_items .= '' . home_url('/datastore-sitemap.xml') . '' . PHP_EOL;
$sitemap_custom_items .= '' . date('c', strtotime($last_modified)) . '' . PHP_EOL;
$sitemap_custom_items .= '' . PHP_EOL;
return $sitemap_custom_items;
});
/**
* Register custom sitemap route for data store
*/
add_action('init', function() {
add_rewrite_rule('^datastore-sitemap\.xml$', 'index.php?crehq_datastore_sitemap=1', 'top');
});
add_filter('query_vars', function($vars) {
$vars[] = 'crehq_datastore_sitemap';
return $vars;
});
add_action('template_redirect', function() {
if (!get_query_var('crehq_datastore_sitemap')) return;
global $wpdb;
$table = $wpdb->prefix . 'crehq_datasets';
$datasets = $wpdb->get_results("SELECT slug, last_updated FROM $table WHERE status = 'active' ORDER BY name");
header('Content-Type: application/xml; charset=UTF-8');
echo '' . PHP_EOL;
echo '' . PHP_EOL;
// Main data store page
echo '' . PHP_EOL;
echo '' . home_url('/data-store/') . '' . PHP_EOL;
echo 'weekly' . PHP_EOL;
echo '0.8' . PHP_EOL;
echo '' . PHP_EOL;
// Category pages
$categories = $wpdb->get_col("SELECT DISTINCT category FROM $table WHERE status = 'active' AND category IS NOT NULL");
foreach ($categories as $cat) {
echo '' . PHP_EOL;
echo '' . home_url('/data-store/category/' . sanitize_title($cat) . '/') . '' . PHP_EOL;
echo 'weekly' . PHP_EOL;
echo '0.7' . PHP_EOL;
echo '' . PHP_EOL;
}
// Individual dataset pages
foreach ($datasets as $ds) {
echo '' . PHP_EOL;
echo '' . home_url('/data-store/' . $ds->slug . '/') . '' . PHP_EOL;
echo '' . date('c', strtotime($ds->last_updated)) . '' . PHP_EOL;
echo 'monthly' . PHP_EOL;
echo '0.6' . PHP_EOL;
echo '' . PHP_EOL;
}
echo '';
exit;
});
/**
* Get dataset for company by company_id
*/
function crehq_get_company_dataset($company_id) {
if (!class_exists('CREHQ\CompanyLocations\DataStore\Dataset')) {
return null;
}
global $wpdb;
$table = $wpdb->prefix . 'crehq_datasets';
$dataset = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table WHERE company_id = %d AND status = 'active' LIMIT 1",
$company_id
));
return $dataset;
}
/**
* Get homepage statistics for company counts
*/
function crehq_get_homepage_stats() {
$stats = get_transient("crehq_homepage_stats");
if (false === $stats) {
$company_count = wp_count_posts("companies");
$stats = array(
"companies" => isset($company_count->publish) ? $company_count->publish : 2400,
"locations" => 500000,
"contacts" => 15000
);
set_transient("crehq_homepage_stats", $stats, HOUR_IN_SECONDS);
}
return $stats;
}
/**
* Company Notes & Reminders System
*/
// =========================================================================
// COMPANY NOTES & REMINDERS SYSTEM
// =========================================================================
/**
* Create notes table on activation
*/
function crehq_create_notes_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
company_id bigint(20) NOT NULL,
note_type varchar(50) DEFAULT 'general',
note_content longtext NOT NULL,
tagged_contacts text,
reminder_date datetime DEFAULT NULL,
reminder_sent tinyint(1) DEFAULT 0,
is_pinned tinyint(1) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY company_id (company_id),
KEY reminder_date (reminder_date)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
add_action('after_switch_theme', 'crehq_create_notes_table');
// Run once to create table
if (!get_option('crehq_notes_table_created')) {
crehq_create_notes_table();
update_option('crehq_notes_table_created', true);
}
/**
* Get notes for a company
*/
function crehq_get_company_notes($company_id, $user_id = null) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
if (!$user_id) {
$user_id = get_current_user_id();
}
$notes = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_name
WHERE company_id = %d AND user_id = %d
ORDER BY is_pinned DESC, created_at DESC",
$company_id, $user_id
));
return $notes;
}
/**
* Add a note
*/
function crehq_add_company_note($data) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
$result = $wpdb->insert($table_name, array(
'user_id' => get_current_user_id(),
'company_id' => intval($data['company_id']),
'note_type' => sanitize_text_field($data['note_type']),
'note_content' => wp_kses_post($data['note_content']),
'tagged_contacts' => isset($data['tagged_contacts']) ? sanitize_text_field($data['tagged_contacts']) : '',
'reminder_date' => !empty($data['reminder_date']) ? sanitize_text_field($data['reminder_date']) : null,
'is_pinned' => isset($data['is_pinned']) ? 1 : 0
));
if ($result) {
return $wpdb->insert_id;
}
return false;
}
/**
* Update a note
*/
function crehq_update_company_note($note_id, $data) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
$update_data = array();
if (isset($data['note_content'])) {
$update_data['note_content'] = wp_kses_post($data['note_content']);
}
if (isset($data['note_type'])) {
$update_data['note_type'] = sanitize_text_field($data['note_type']);
}
if (isset($data['tagged_contacts'])) {
$update_data['tagged_contacts'] = sanitize_text_field($data['tagged_contacts']);
}
if (isset($data['reminder_date'])) {
$update_data['reminder_date'] = !empty($data['reminder_date']) ? sanitize_text_field($data['reminder_date']) : null;
}
if (isset($data['is_pinned'])) {
$update_data['is_pinned'] = $data['is_pinned'] ? 1 : 0;
}
return $wpdb->update(
$table_name,
$update_data,
array('id' => $note_id, 'user_id' => get_current_user_id())
);
}
/**
* Delete a note
*/
function crehq_delete_company_note($note_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
return $wpdb->delete($table_name, array(
'id' => $note_id,
'user_id' => get_current_user_id()
));
}
/**
* Toggle pin status
*/
function crehq_toggle_note_pin($note_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
$current = $wpdb->get_var($wpdb->prepare(
"SELECT is_pinned FROM $table_name WHERE id = %d AND user_id = %d",
$note_id, get_current_user_id()
));
return $wpdb->update(
$table_name,
array('is_pinned' => $current ? 0 : 1),
array('id' => $note_id, 'user_id' => get_current_user_id())
);
}
/**
* Get upcoming reminders for user
*/
function crehq_get_user_reminders($user_id = null, $days_ahead = 7) {
global $wpdb;
$table_name = $wpdb->prefix . 'crehq_company_notes';
if (!$user_id) {
$user_id = get_current_user_id();
}
$reminders = $wpdb->get_results($wpdb->prepare(
"SELECT n.*, p.post_title as company_name
FROM $table_name n
LEFT JOIN {$wpdb->posts} p ON n.company_id = p.ID
WHERE n.user_id = %d
AND n.reminder_date IS NOT NULL
AND n.reminder_date <= DATE_ADD(NOW(), INTERVAL %d DAY)
AND n.reminder_date >= NOW()
ORDER BY n.reminder_date ASC",
$user_id, $days_ahead
));
return $reminders;
}
// =========================================================================
// AJAX HANDLERS
// =========================================================================
/**
* Get notes AJAX
*/
function crehq_ajax_get_notes() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$company_id = intval($_POST['company_id']);
$notes = crehq_get_company_notes($company_id);
wp_send_json_success(array('notes' => $notes));
}
add_action('wp_ajax_crehq_get_notes', 'crehq_ajax_get_notes');
/**
* Add note AJAX
*/
function crehq_ajax_add_note() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$note_id = crehq_add_company_note($_POST);
if ($note_id) {
$notes = crehq_get_company_notes(intval($_POST['company_id']));
wp_send_json_success(array('note_id' => $note_id, 'notes' => $notes));
} else {
wp_send_json_error('Failed to add note');
}
}
add_action('wp_ajax_crehq_add_note', 'crehq_ajax_add_note');
/**
* Update note AJAX
*/
function crehq_ajax_update_note() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$note_id = intval($_POST['note_id']);
$result = crehq_update_company_note($note_id, $_POST);
if ($result !== false) {
$notes = crehq_get_company_notes(intval($_POST['company_id']));
wp_send_json_success(array('notes' => $notes));
} else {
wp_send_json_error('Failed to update note');
}
}
add_action('wp_ajax_crehq_update_note', 'crehq_ajax_update_note');
/**
* Delete note AJAX
*/
function crehq_ajax_delete_note() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$note_id = intval($_POST['note_id']);
$company_id = intval($_POST['company_id']);
$result = crehq_delete_company_note($note_id);
if ($result) {
$notes = crehq_get_company_notes($company_id);
wp_send_json_success(array('notes' => $notes));
} else {
wp_send_json_error('Failed to delete note');
}
}
add_action('wp_ajax_crehq_delete_note', 'crehq_ajax_delete_note');
/**
* Toggle pin AJAX
*/
function crehq_ajax_toggle_pin() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$note_id = intval($_POST['note_id']);
$company_id = intval($_POST['company_id']);
crehq_toggle_note_pin($note_id);
$notes = crehq_get_company_notes($company_id);
wp_send_json_success(array('notes' => $notes));
}
add_action('wp_ajax_crehq_toggle_pin', 'crehq_ajax_toggle_pin');
/**
* Get reminders AJAX (for dashboard widget)
*/
function crehq_ajax_get_reminders() {
check_ajax_referer('crehq_pro_features', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error('Not logged in');
}
$days = isset($_POST['days']) ? intval($_POST['days']) : 7;
$reminders = crehq_get_user_reminders(null, $days);
wp_send_json_success(array('reminders' => $reminders));
}
add_action('wp_ajax_crehq_get_reminders', 'crehq_ajax_get_reminders');