$request->get_route(), 'method' => $request->get_method(), 'params' => $request->get_params(), 'time' => microtime(true), ]; // Set up error handler for this request set_error_handler(function($errno, $errstr, $errfile, $errline) { if (!(error_reporting() & $errno)) { return false; } $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [ 'error_type' => $errno, 'error_message' => $errstr, 'file' => $errfile, 'line' => $errline, 'user_id' => get_current_user_id(), ]); error_log('JVB REST Error: ' . json_encode($context)); return false; // Let PHP handle it normally too }); return $result; }, 10, 3); add_filter('rest_post_dispatch', function($result, $server, $request) { // Restore default error handler restore_error_handler(); // Check for fatal errors that occurred during request $error = error_get_last(); if ($error && in_array($error['type'], [E_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR])) { $context = array_merge($GLOBALS['jvb_rest_request'] ?? [], [ 'fatal_error' => $error, 'user_id' => get_current_user_id(), ]); error_log('JVB REST Fatal: ' . json_encode($context)); } // Clean up unset($GLOBALS['jvb_rest_request']); return $result; }, 10, 3); function jvbIgnoredPostTypes():array { return [BASE.'directory', BASE.'dash', 'attachment', 'revision', 'nav_menu_item']; } define('JVB_TESTING', str_contains(get_home_url(),'.test')); //if (!JVB_TESTING) { add_filter('show_admin_bar', '__return_false'); //} //if (JVB_TESTING) { // error_log('In testing mode...'); //} else { // error_log('Not in testing mode...'); //} const JVB_DIR = WP_PLUGIN_DIR . '/jvb'; define('JVB_URL', plugin_dir_url(__FILE__)); // Session Security define('JVB_SESSION_FINGERPRINT', true); // Login Security define('JVB_MAX_LOGIN_ATTEMPTS', 5); define('JVB_LOCKOUT_DURATION', 15 * MINUTE_IN_SECONDS); require(JVB_DIR.'/base/_setup.php'); require(JVB_DIR.'/inc/utility/setup.php'); require(JVB_DIR.'/checks.php'); require(JVB_DIR . '/inc/registrar/_setup.php'); require(JVB_DIR . '/activate.php'); require(JVB_DIR . '/inc/helpers/all.php'); require(JVB_DIR . '/inc/ui/_setup.php'); require(JVB_DIR . '/inc/meta/_setup.php'); require(JVB_DIR . '/inc/importers/_setup.php'); require(JVB_DIR . '/inc/managers/_setup.php'); /** * Get an icon element * * @param string $name Icon name * @param array $options Options array: * - 'source' => 'icons'|'dash'|'forms'|etc. (default: 'icons') * - 'style' => 'regular'|'bold'|'fill'|etc. * - 'label' => 'Accessible label' * - 'decorative' => true * - 'class' => 'additional classes' * - 'size' => 24 * @return string HTML icon element */ function jvbIcon(string $name, array $options = []): string { $source = $options['source'] ?? 'icons'; // Remove source from options before passing to IconsManager unset($options['source']); return IconsManager::for($source)->get($name, $options); } function jvbFullIcon(string $name, array $options = []):string { $source = $options['source'] ?? 'icons'; unset($options['source']); return IconsManager::for($source)->getRawSvg($name, $options['style']??null); } /** * Get a CSS data URI for an icon * * @param string $name Icon name * @param array $options Options array: * - 'style' => 'regular'|'bold'|'fill'|etc. * - 'source' => 'icons'|'dash'|'forms'|etc. (for tracking purposes) * @return string data:image/svg+xml;base64,... URL */ function jvbCSSIcon(string $name, array $options = []): string { $style = $options['style'] ?? null; $source = $options['source'] ?? 'icons'; return IconsManager::for($source)->getCSSIcon($name, $style); } /** * Get a dashboard icon */ function jvbDashIcon(string $name, array $options = []): string { $options['source'] = 'dash'; return jvbIcon($name, $options); } /** * Get a form editor icon */ function jvbFormIcon(string $name, array $options = []): string { $options['source'] = 'forms'; return jvbIcon($name, $options); } require(JVB_DIR . '/inc/integrations/_setup.php'); require(JVB_DIR . '/inc/rest/_setup.php'); add_filter( 'cron_schedules', 'jvbCronSchedules'); function jvbCronSchedules($schedules) { $schedules[ 'every-5-minutes' ] = [ 'interval' => 5 * 60, 'display' => __('Every 5 minutes', 'jvb') ]; $schedules[ 'every-minute' ] = [ 'interval' => 60, 'display' => __('Every minute', 'jvb') ]; $schedules[ 'monthly' ] = [ 'interval' => 604800 * 4, 'display' => __('Once Monthly', 'jvb') ]; return $schedules; } register_activation_hook(JVB_DIR.'/jvb.php', 'jvbActivatePlugin'); register_deactivation_hook(JVB_DIR.'/jvb.php', 'jvbDeactivatePlugin'); add_action('init', 'jvbUserCheck'); function jvbUserCheck():void { if (is_admin() && isOurPeople()) { wp_redirect(get_home_url(null, '/dash')); exit; } } //require(JVB_DIR . '/inc/users/UserSettings.php'); require(JVB_DIR . '/inc/templates.php'); /** * Utilities */ require(JVB_DIR . '/cleanup.php'); require(JVB_DIR . '/inc/admin/_setup.php'); require(JVB_DIR . '/JVBase.php'); add_action('init', 'jvbLoadBase', 1); function jvbLoadBase():void { JVB::getInstance(); } function JVB(): JVB { return JVB::getInstance(); } require(JVB_DIR . '/inc/blocks/_setup.php'); add_filter('upload_mimes', function ($mimes) { $mimes['webp'] = 'image/webp'; $mimes['avif'] = 'image/avif'; return $mimes; }); // And this to ensure WebP is treated as an image add_filter('file_is_displayable_image', function ($result, $path) { if (preg_match('/\.webp$/i', $path)) { return true; } return $result; }, 10, 2); function jvbDump(mixed $array, string $name = 'var'):void { highlight_string(""); } /** * Scripts */ add_action('wp_enqueue_scripts', 'jvbScripts', 10); function jvbScripts():void { add_action('wp_head', 'jvbInlineNavStyles'); if (Site::has('dashboard')) { wp_enqueue_script('jvb-queue'); } wp_enqueue_script('jvb-auth'); wp_enqueue_script('jvb-settings'); wp_enqueue_script('jvb-navigation'); // wp_enqueue_script('jvb-ui'); // wp_enqueue_script('jvb-media'); wp_enqueue_script('jvb-gallery'); wp_enqueue_script('jvb-cache'); $interactions = []; if (Site::has('favourites')) { $interactions[] = 'favourites'; } if (!empty(Registrar::getFeatured('karma'))) { $interactions[] = 'karma'; } if (Site::has('notifications')) { $interactions[] = 'notifications'; } if (!empty($interactions)) { wp_enqueue_script('jvb-interactions'); foreach($interactions as $interaction) { wp_enqueue_script('jvb-'.$interaction); } } $queue = [ 'api' => rest_url('jvb/v1/'), 'redirect' => get_home_url(null, '/login/'), 'labels' => Registrar::getLabels(), ]; wp_localize_script('jvb-auth', 'jvbSettings', $queue); $initUserSettings = 'async function initUserItems() { if (!jvbSettings.currentUser) return; window.userFavourites = {}; window.userVotes = {}; '; if (Site::has('favourites')) { wp_enqueue_script('jvb-favourites'); $initUserSettings .= ' //Fetch user favourites try { const result = await window.jvbCache.fetchWithCache( `${jvbSettings.api}favourites?all=true`, { method: \'GET\', headers: {\'X-WP-Nonce\': jvbSettings.nonce} } ); if (result && result.favourites) { for (const key in result.favourites) { window.userFavourites[key] = new Set(); result.favourites[key].forEach(id => { window.userFavourites[key].add(parseInt(id)); }); } } } catch (error) { console.error(\'Failed to fetch user favourites:\', error); }'; } if (!empty(Registrar::getFeatured('karma'))) { wp_enqueue_script('jvb-votes'); $initUserSettings .= '// Fetch user votes try { const result = await window.jvbCache.fetchWithCache( `${jvbSettings.api}vote?user=${jvbSettings.currentUser}`, { method: \'GET\', headers: {\'X-WP-Nonce\': jvbSettings.nonce} }, {forceRefresh: true} ); window.userVotes = {}; if (result) { for (const key in result) { window.userVotes[key] = new Map(); for (const [id, vote] of Object.entries(result[key])) { window.userVotes[key].set(parseInt(id), vote); } } } } catch (error) { console.error(\'Failed to fetch user votes:\', error); }'; } $initUserSettings .= '} initUserItems();'; // wp_add_inline_script('jvb-queue', $initUserSettings, 'after'); if (is_search() || is_tax(BASE.'shop') || is_singular(BASE.'artist') || jvbIsDirectory() ) { wp_enqueue_script('jvb-page-nav'); } if (has_block('jvb/summaryBlock')) { wp_enqueue_script('jvb-page-nav'); } // Only load on single shop pages or other relevant pages if (is_tax(BASE.'shop') || is_singular(BASE.'partner')) { $ID = get_queried_object_id(); if (is_tax()) { $check = get_term_meta($ID, BASE.'has_map', true); } else { $check = get_post_meta($ID, BASE.'has_map', true); } // if ($check && $check !== '') { //// wp_enqueue_script( //// 'google-maps-frontend', //// 'https://maps.googleapis.com/maps/api/js?key=' . GOOGLE_API . '&callback=googleMapsLoaded', //// [], //// null, //// ['strategy' => 'async', //// 'in_footer' => true] //// ); // // // Add the callback function // wp_add_inline_script('google-maps-frontend', ' // function googleMapsLoaded() { // if (window.googleMapsCallbacks && window.googleMapsCallbacks.length) { // window.googleMapsCallbacks.forEach(callback => callback()); // } // } // '); // } } if (is_user_logged_in() && Site::has('notifications')) { wp_enqueue_script('jvb-notifications'); wp_localize_script('jvb-notifications', 'notificationSettings', array( 'homeUrl' => home_url(), 'strings' => array( 'loading' => 'Loading...', 'error' => 'Failed to load notifications', 'noNotifications' => 'No new notifications', 'viewAll' => 'View All Notifications', 'markRead' => 'Mark as read', 'review' => 'Review' ) )); } jvbAddScriptDependency('jvb-feed-view-script', 'jvb-queue'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-selector'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-data-store'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-cache'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-a11y'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-utility'); jvbAddScriptDependency('jvb-feed-view-script', 'jvb-gallery'); // jvbAddScriptDependency('jvb-feed-view-script', 'jvb-loading'); jvbAddScriptDependency('jvb-forms-view-script', 'jvb-queue'); jvbAddScriptDependency('jvb-forms-view-script', 'jvb-quill'); jvbAddScriptDependency('jvb-forms-view-script', 'jvb-form'); jvbAddScriptDependency('jvb-forms-view-script', 'jvb-tabs'); } function jvbAdminMap():void { // wp_enqueue_script( // 'google-maps', // 'https://maps.googleapis.com/maps/api/js?key=' . GOOGLE_API . '&libraries=places&callback=googleMapsLoaded', // [], // null, // [ // 'strategy' => 'async', // 'in_footer' => true // ] // ); // // // Add the callback function // wp_add_inline_script('google-maps', ' // function googleMapsLoaded() { // if (window.googleMapsCallbacks && window.googleMapsCallbacks.length) { // window.googleMapsCallbacks.forEach(callback => callback()); // } // } // '); } function jvbAddScriptDependency(string $handle, string $dep):bool { global $wp_scripts; $script = $wp_scripts->query($handle); if (!$script) { return false; } if (!in_array($dep, $script->deps)) { $script->deps[] = $dep; } return true; } add_action('wp_body_open', 'jvbAccessibility'); function jvbAccessibility():void { echo '
'; } add_action('wp_head', 'jvbFrontendBase',1); function jvbFrontendBase():void { ?> roles, true)); //}); // //add_filter('rest_pre_dispatch', function($result, $server, $request) { // if (strpos($request->get_route(), '/wp/v2/pages') !== false) { // error_log('Pages request - User: ' . get_current_user_id()); // error_log('Can edit pages: ' . (current_user_can('edit_pages') ? 'yes' : 'no')); // error_log('Nonce: ' . $request->get_header('X-WP-Nonce')); // } // return $result; //}, 10, 3); add_filter('rest_authentication_errors', function($result) { // Don't override existing authentication if (is_wp_error($result) || $result === true) { return $result; } // Try to authenticate from cookie $cookie_user = wp_validate_auth_cookie('', 'logged_in'); if ($cookie_user) { wp_set_current_user($cookie_user); return true; } return $result; }, 99); add_action('wp_footer', 'jvb_back_to_top'); function jvb_back_to_top():void { echo sprintf( '%sBack to Top', jvbIcon('caret-double-up') ); }