/** * 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' => '
', 'after_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' => '
', 'after_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' => '
', 'after_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' => '
', 'after_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');