• Add user role field to registration, profile display, and profile edit - 10 role options: technician, installer, supervisor, manager, trainer, consultant, sales rep, engineer, business owner, other - Required field with server-side validation - Radio buttons in registration, dropdown in profile edit - Displays in profile with proper capitalization • Implement advanced certification tracking system - Date Certified: HTML5 date picker with validation (no future dates) - Certification Type: dropdown with "Certified measureQuick Trainer" and "Certified measureQuick Champion" - Certification Status: color-coded status badges (Active/Expired/Pending/Disabled) • Add sophisticated role-based access control - Regular trainers: read-only access to certification fields - Administrators & master trainers: full edit access to certification fields - Visual indicators for read-only fields - Server-side permission validation • Enhance plugin activation system - Initialize all 36 user meta fields for existing users - Smart default assignment based on user capabilities - Backward compatibility maintained • Add professional UI styling - Blue-bordered certification section with trophy icon - Color-coded status badges with proper contrast - Read-only field styling with visual indicators - Enhanced form controls with focus states • Comprehensive testing and documentation - E2E test coverage with visual verification - Updated API reference with new meta fields - Access control patterns documented - 100% test pass rate on staging environment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
414 lines
No EOL
12 KiB
PHP
414 lines
No EOL
12 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Breadcrumbs System
|
|
*
|
|
* Generates breadcrumbs based on URL structure with Schema.org markup for SEO
|
|
* Supports hierarchical trainer navigation patterns
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class HVAC_Breadcrumbs {
|
|
|
|
/**
|
|
* Plugin instance
|
|
*
|
|
* @var HVAC_Breadcrumbs
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get plugin instance
|
|
*
|
|
* @return HVAC_Breadcrumbs
|
|
*/
|
|
public static function instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
|
|
}
|
|
|
|
/**
|
|
* Enqueue breadcrumb styles
|
|
*/
|
|
public function enqueue_styles() {
|
|
if ($this->is_trainer_page()) {
|
|
wp_enqueue_style(
|
|
'hvac-breadcrumbs',
|
|
HVAC_PLUGIN_URL . 'assets/css/hvac-breadcrumbs.css',
|
|
array(),
|
|
HVAC_PLUGIN_VERSION
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if current page is a trainer page
|
|
*/
|
|
private function is_trainer_page() {
|
|
global $wp;
|
|
$current_url = home_url(add_query_arg(array(), $wp->request));
|
|
return (strpos($current_url, '/trainer/') !== false || strpos($current_url, '/master-trainer/') !== false);
|
|
}
|
|
|
|
/**
|
|
* Render breadcrumbs for trainer pages
|
|
*
|
|
* @param array $options Configuration options
|
|
* @return string HTML breadcrumb output
|
|
*/
|
|
public function render_breadcrumbs($options = array()) {
|
|
if (!$this->is_trainer_page()) {
|
|
return '';
|
|
}
|
|
|
|
$defaults = array(
|
|
'home_text' => 'Home',
|
|
'separator' => '>',
|
|
'show_home' => true,
|
|
'schema_markup' => true,
|
|
'css_class' => 'hvac-breadcrumbs'
|
|
);
|
|
|
|
$options = wp_parse_args($options, $defaults);
|
|
|
|
$breadcrumbs = $this->generate_breadcrumb_trail();
|
|
|
|
if (empty($breadcrumbs)) {
|
|
return '';
|
|
}
|
|
|
|
return $this->render_breadcrumb_html($breadcrumbs, $options);
|
|
}
|
|
|
|
/**
|
|
* Generate breadcrumb trail based on current URL
|
|
*
|
|
* @return array Array of breadcrumb items
|
|
*/
|
|
private function generate_breadcrumb_trail() {
|
|
global $wp;
|
|
$request = $wp->request;
|
|
|
|
// Remove trailing slash and split by forward slash
|
|
$path_parts = array_filter(explode('/', trim($request, '/')));
|
|
|
|
if (empty($path_parts)) {
|
|
return array();
|
|
}
|
|
|
|
$breadcrumbs = array();
|
|
$current_path = '';
|
|
|
|
// Define breadcrumb mapping for trainer paths
|
|
$breadcrumb_map = array(
|
|
'trainer' => array(
|
|
'title' => 'Trainer',
|
|
'url' => '/trainer/dashboard/'
|
|
),
|
|
'master-trainer' => array(
|
|
'title' => 'Master Trainer',
|
|
'url' => '/master-trainer/master-dashboard/'
|
|
),
|
|
'dashboard' => array(
|
|
'title' => 'Dashboard',
|
|
'parent' => 'Events'
|
|
),
|
|
'master-dashboard' => array(
|
|
'title' => 'Dashboard',
|
|
'parent' => 'Master Trainer'
|
|
),
|
|
'event' => array(
|
|
'title' => 'Events',
|
|
'url' => '/trainer/dashboard/'
|
|
),
|
|
'manage' => array(
|
|
'title' => 'New Event',
|
|
'parent' => 'Events'
|
|
),
|
|
'certificate-reports' => array(
|
|
'title' => 'Reports',
|
|
'parent' => 'Certificates'
|
|
),
|
|
'generate-certificates' => array(
|
|
'title' => 'New Certificate',
|
|
'parent' => 'Certificates'
|
|
),
|
|
'certificates' => array(
|
|
'title' => 'Certificates',
|
|
'url' => '/trainer/certificate-reports/'
|
|
),
|
|
'profile' => array(
|
|
'title' => 'Personal Profile',
|
|
'parent' => 'Customize'
|
|
),
|
|
'organizer' => array(
|
|
'title' => 'Training Organizers',
|
|
'parent' => 'Customize'
|
|
),
|
|
'venue' => array(
|
|
'title' => 'Training Venues',
|
|
'parent' => 'Customize'
|
|
),
|
|
'list' => array(
|
|
'title' => 'List',
|
|
'context_dependent' => true
|
|
),
|
|
'edit' => array(
|
|
'title' => 'Edit',
|
|
'context_dependent' => true
|
|
),
|
|
'customize' => array(
|
|
'title' => 'Customize',
|
|
'url' => '/trainer/profile/'
|
|
)
|
|
);
|
|
|
|
// Build breadcrumb trail
|
|
for ($i = 0; $i < count($path_parts); $i++) {
|
|
$part = $path_parts[$i];
|
|
$current_path .= '/' . $part;
|
|
|
|
if (isset($breadcrumb_map[$part])) {
|
|
$crumb = $breadcrumb_map[$part];
|
|
|
|
// Handle context-dependent titles
|
|
if (isset($crumb['context_dependent']) && $crumb['context_dependent']) {
|
|
$crumb['title'] = $this->get_contextual_title($part, $path_parts, $i);
|
|
}
|
|
|
|
// Add parent breadcrumb if specified
|
|
if (isset($crumb['parent']) && !$this->breadcrumb_exists($breadcrumbs, $crumb['parent'])) {
|
|
$parent_crumb = $this->get_parent_breadcrumb($crumb['parent']);
|
|
if ($parent_crumb) {
|
|
$breadcrumbs[] = $parent_crumb;
|
|
}
|
|
}
|
|
|
|
// Determine URL
|
|
$url = isset($crumb['url']) ? $crumb['url'] : home_url($current_path . '/');
|
|
|
|
// Don't add URL for the current page (last item)
|
|
if ($i === count($path_parts) - 1) {
|
|
$url = null;
|
|
}
|
|
|
|
$breadcrumbs[] = array(
|
|
'title' => $crumb['title'],
|
|
'url' => $url,
|
|
'position' => count($breadcrumbs) + 1
|
|
);
|
|
}
|
|
}
|
|
|
|
return $breadcrumbs;
|
|
}
|
|
|
|
/**
|
|
* Get contextual title for context-dependent breadcrumbs
|
|
*
|
|
* @param string $part Current path part
|
|
* @param array $path_parts All path parts
|
|
* @param int $index Current index
|
|
* @return string Contextual title
|
|
*/
|
|
private function get_contextual_title($part, $path_parts, $index) {
|
|
if ($part === 'list') {
|
|
// Look at previous part for context
|
|
if ($index > 0) {
|
|
$previous = $path_parts[$index - 1];
|
|
if ($previous === 'organizer') {
|
|
return 'Organizer List';
|
|
} elseif ($previous === 'venue') {
|
|
return 'Venue List';
|
|
}
|
|
}
|
|
return 'List';
|
|
}
|
|
|
|
if ($part === 'edit') {
|
|
return 'Edit';
|
|
}
|
|
|
|
if ($part === 'manage') {
|
|
// Look at previous part for context
|
|
if ($index > 0) {
|
|
$previous = $path_parts[$index - 1];
|
|
if ($previous === 'organizer') {
|
|
return 'Manage Organizer';
|
|
} elseif ($previous === 'venue') {
|
|
return 'Manage Venue';
|
|
} elseif ($previous === 'event') {
|
|
return 'New Event';
|
|
}
|
|
}
|
|
return 'Manage';
|
|
}
|
|
|
|
return ucfirst($part);
|
|
}
|
|
|
|
/**
|
|
* Check if breadcrumb with given title already exists
|
|
*
|
|
* @param array $breadcrumbs Existing breadcrumbs
|
|
* @param string $title Title to check
|
|
* @return bool
|
|
*/
|
|
private function breadcrumb_exists($breadcrumbs, $title) {
|
|
foreach ($breadcrumbs as $crumb) {
|
|
if ($crumb['title'] === $title) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get parent breadcrumb definition
|
|
*
|
|
* @param string $parent_title Parent title
|
|
* @return array|null Parent breadcrumb data
|
|
*/
|
|
private function get_parent_breadcrumb($parent_title) {
|
|
$parent_map = array(
|
|
'Events' => array(
|
|
'title' => 'Events',
|
|
'url' => '/trainer/dashboard/'
|
|
),
|
|
'Certificates' => array(
|
|
'title' => 'Certificates',
|
|
'url' => '/trainer/certificate-reports/'
|
|
),
|
|
'Customize' => array(
|
|
'title' => 'Customize',
|
|
'url' => '/trainer/profile/'
|
|
)
|
|
);
|
|
|
|
if (isset($parent_map[$parent_title])) {
|
|
return array(
|
|
'title' => $parent_map[$parent_title]['title'],
|
|
'url' => home_url($parent_map[$parent_title]['url']),
|
|
'position' => 1
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Render breadcrumb HTML with Schema.org markup
|
|
*
|
|
* @param array $breadcrumbs Breadcrumb items
|
|
* @param array $options Rendering options
|
|
* @return string HTML output
|
|
*/
|
|
private function render_breadcrumb_html($breadcrumbs, $options) {
|
|
if (empty($breadcrumbs)) {
|
|
return '';
|
|
}
|
|
|
|
$html = '<nav class="' . esc_attr($options['css_class']) . '" aria-label="Breadcrumb">';
|
|
|
|
// Add Schema.org JSON-LD if enabled
|
|
if ($options['schema_markup']) {
|
|
$html .= $this->generate_schema_markup($breadcrumbs);
|
|
}
|
|
|
|
$html .= '<ol class="hvac-breadcrumb-list">';
|
|
|
|
// Add home breadcrumb if enabled
|
|
if ($options['show_home']) {
|
|
$html .= '<li class="hvac-breadcrumb-item hvac-breadcrumb-home">';
|
|
$html .= '<a href="' . esc_url(home_url('/')) . '">' . esc_html($options['home_text']) . '</a>';
|
|
$html .= '<span class="hvac-breadcrumb-separator">' . esc_html($options['separator']) . '</span>';
|
|
$html .= '</li>';
|
|
}
|
|
|
|
$total_crumbs = count($breadcrumbs);
|
|
|
|
foreach ($breadcrumbs as $index => $crumb) {
|
|
$is_last = ($index === $total_crumbs - 1);
|
|
$item_class = 'hvac-breadcrumb-item';
|
|
|
|
if ($is_last) {
|
|
$item_class .= ' hvac-breadcrumb-current';
|
|
}
|
|
|
|
$html .= '<li class="' . esc_attr($item_class) . '">';
|
|
|
|
if (!$is_last && !empty($crumb['url'])) {
|
|
$html .= '<a href="' . esc_url($crumb['url']) . '">' . esc_html($crumb['title']) . '</a>';
|
|
} else {
|
|
$html .= '<span class="hvac-breadcrumb-current-text">' . esc_html($crumb['title']) . '</span>';
|
|
}
|
|
|
|
if (!$is_last) {
|
|
$html .= '<span class="hvac-breadcrumb-separator">' . esc_html($options['separator']) . '</span>';
|
|
}
|
|
|
|
$html .= '</li>';
|
|
}
|
|
|
|
$html .= '</ol>';
|
|
$html .= '</nav>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Generate Schema.org JSON-LD markup for breadcrumbs
|
|
*
|
|
* @param array $breadcrumbs Breadcrumb items
|
|
* @return string JSON-LD script tag
|
|
*/
|
|
private function generate_schema_markup($breadcrumbs) {
|
|
$schema_items = array();
|
|
$position = 1;
|
|
|
|
// Add home item
|
|
$schema_items[] = array(
|
|
'@type' => 'ListItem',
|
|
'position' => $position++,
|
|
'name' => 'Home',
|
|
'item' => home_url('/')
|
|
);
|
|
|
|
// Add breadcrumb items
|
|
foreach ($breadcrumbs as $crumb) {
|
|
$item = array(
|
|
'@type' => 'ListItem',
|
|
'position' => $position++,
|
|
'name' => $crumb['title']
|
|
);
|
|
|
|
if (!empty($crumb['url'])) {
|
|
$item['item'] = $crumb['url'];
|
|
}
|
|
|
|
$schema_items[] = $item;
|
|
}
|
|
|
|
$schema = array(
|
|
'@context' => 'https://schema.org',
|
|
'@type' => 'BreadcrumbList',
|
|
'itemListElement' => $schema_items
|
|
);
|
|
|
|
return '<script type="application/ld+json">' . wp_json_encode($schema, JSON_UNESCAPED_SLASHES) . '</script>';
|
|
}
|
|
} |