/** * This function retrieves the count of all available ads from the database. * If the count doesn't exist, it triggers an update operation to count and update the ad count in the database. * * @return int The count of all available ads. */ function wj_get_ad_count(): int { // Retrieve the ad count from the database. 'wj-adcount' is assumed to be an array with a 'count' key. $ads = get_option( 'wj-adcount', true ); // If $ads is empty or not an array, update the ad count in the database. if ( empty( $ads ) || ! is_array( $ads ) ) { // Create a new instance of the Search class $search = new Search(); // Call the method to update the ad count in the database. // It's assumed that this method returns the updated count. return $search->update_exposee_count_option(); } // If $ads is not empty and is an array, return the count. return $ads['count']; } function add_logger( $loggerName = 'WJ-Functions' ): Logger { static $loggers = array(); if ( ! isset( $loggers[ $loggerName ] ) ) { $dateFormat = "Y-m-d H:i:s"; // Include channel (logger name) so one combined file remains greppable by prefix $output = "[%datetime%] %channel% | %level_name%: %message% %context% %extra%\n"; $formatter = new LineFormatter( $output, $dateFormat ); // Zentrales Log-Verzeichnis (konfigurierbar) $default_dir = '/var/log/webclients/wj'; $log_dir_opt = function_exists('get_option') ? (string) get_option( 'wj_log_dir', '' ) : ''; $log_dir = $log_dir_opt !== '' ? rtrim( $log_dir_opt, "/" ) : $default_dir; if ( defined( 'WJ_LOG_DIR' ) ) { $log_dir = rtrim( (string) constant('WJ_LOG_DIR'), '/' ); } // Dateinamen-Schema: plugin+logname, wenn mehrere Dateien existieren // Für wj-functions nutzen wir pro Channel (loggerName) eine eigene Datei: wj-functions-.log $base_filename = 'wj-functions-' . generate_filename( $loggerName ); // Optionaler Override über Option/Konstante bleibt möglich $opt_file = function_exists('get_option') ? (string) get_option( 'wj_log_file', '' ) : ''; if ( $opt_file !== '' ) { $base_filename = $opt_file; } if ( defined( 'WJ_LOG_FILE' ) ) { $base_filename = (string) constant('WJ_LOG_FILE'); } $wanted_path = $log_dir . '/' . $base_filename; // Ensure directory exists and is writable; otherwise fall back to plugin logs dir $target_path = $wanted_path; $use_fallback = false; if ( ! @is_dir( $log_dir ) ) { @wp_mkdir_p( $log_dir ); } if ( ! @is_dir( $log_dir ) || ! @is_writable( $log_dir ) ) { $use_fallback = true; $plugin_logs_dir = __DIR__ . '/../logs'; if ( ! @is_dir( $plugin_logs_dir ) ) { @wp_mkdir_p( $plugin_logs_dir ); } $target_path = $plugin_logs_dir . '/' . $base_filename; // Deutlicher Hinweis in PHP-Errorlog, falls das Zielverzeichnis nicht nutzbar ist @error_log('[WJ-Functions] Hinweis: Konnte nicht nach ' . $log_dir . ' schreiben. Fallback auf ' . $target_path . '. Bitte Rechte auf ' . $log_dir . ' prüfen.'); } if ( ! file_exists( $target_path ) ) { @touch( $target_path ); } // Determine minimum level (debug gated by option) $debug_enabled = false; if ( function_exists('get_option') ) { $opt = get_option( 'wj_log_debug', null ); $debug_enabled = ( $opt === 1 || $opt === '1' || $opt === true || $opt === 'on' ); } if ( defined( 'WJ_LOG_DEBUG' ) ) { $debug_enabled = (bool) constant('WJ_LOG_DEBUG'); } $min_level = $debug_enabled ? Logger::DEBUG : Logger::INFO; $streamHandler = new StreamHandler( $target_path, $min_level ); $streamHandler->setFormatter( $formatter ); $logger = new Logger( $loggerName ); $logger->pushHandler( $streamHandler ); $admin_email = "server@knandi.de"; $servername = get_server_name(); // Avoid sending mails from CLI runs (wp-cli/cron) and allow global disable via constant $mail_disabled = ( defined( 'WJ_DISABLE_MAIL_LOG' ) && WJ_DISABLE_MAIL_LOG ); $is_cli = ( PHP_SAPI === 'cli' ) || ( defined( 'WP_CLI' ) && WP_CLI ); if ( ! $mail_disabled && ! $is_cli ) { $errorMailer = new NativeMailerHandler( $admin_email, $servername . ': WJ-Functions - Error', $servername, Logger::ERROR, true, 70 ); $errorMailer->setFormatter( $formatter ); $logger->pushHandler( $errorMailer ); // Info mails are noisy; enable only when explicitly requested via constant WJ_MAIL_INFO=true if ( defined( 'WJ_MAIL_INFO' ) && WJ_MAIL_INFO ) { $infoMailer = new NativeMailerHandler( $admin_email, $servername . ': WJ-Functions - Info', $servername, Logger::INFO, true, 70 ); $infoMailer->setFormatter( $formatter ); $logger->pushHandler( $infoMailer ); } } // If we had to fall back, log a one-time warning into the logger itself if ( $use_fallback ) { try { $logger->warning( 'Log dir not writable. Using plugin fallback path: ' . $target_path, ['wanted' => $wanted_path] ); } catch ( \Throwable $e ) {} } $loggers[ $loggerName ] = $logger; } return $loggers[ $loggerName ]; } /** * Simple wrapper around Monolog. Example: wj_log('info', 'Hello', ['id' => 123]) */ function wj_log( string $level, string $message, array $context = [] ): void { $logger = add_logger( 'app' ); // Normalize level $level = strtolower( $level ); switch ( $level ) { case 'debug': $logger->debug( $message, $context ); break; case 'info': $logger->info( $message, $context ); break; case 'notice': $logger->notice( $message, $context ); break; case 'warning': $logger->warning( $message, $context ); break; case 'error': $logger->error( $message, $context ); break; case 'critical': $logger->critical( $message, $context ); break; case 'alert': $logger->alert( $message, $context ); break; case 'emergency': $logger->emergency( $message, $context ); break; default: $logger->info( $message, $context ); } } /** * Generate filename with extension .log if it does not exist. */ function generate_filename( $loggerName ) { $filename = $loggerName; if ( ! str_contains( $loggerName, '.' ) || 'log' !== substr( strrchr( $loggerName, "." ), 1 ) ) { $filename .= '.log'; } return $filename; } // Caching TTL getters and dynamic threshold computation function wj_get_cache_ttl(): int { // precedence: option -> constant -> default ("aktuelle Werte als Fallback") $opt = function_exists('get_option') ? (int) get_option('wj_cache_ttl', 0) : 0; if ( $opt <= 0 ) { $opt = defined('REDISCACHE') ? (int) constant('REDISCACHE') : 42000; } // clamp to sane bounds: 10 min .. 7 days $opt = max(600, min($opt, 7*24*3600)); return $opt; } function wj_get_cache_ttl_user(): int { $opt = function_exists('get_option') ? (int) get_option('wj_cache_ttl_user', 0) : 0; if ( $opt <= 0 ) { $opt = defined('REDISCACHEUSER') ? (int) constant('REDISCACHEUSER') : 25000; } $opt = max(300, min($opt, 7*24*3600)); return $opt; } function wj_get_update_ttl_threshold(): int { $opt = function_exists('get_option') ? (int) get_option('wj_update_ttl_threshold', 0) : 0; $base = $opt > 0 ? $opt : ( defined('REDISUPDATETTL') ? (int) constant('REDISUPDATETTL') : 26200 ); $cacheTTL = wj_get_cache_ttl(); // ensure base below cacheTTL $base = min($base, max($cacheTTL - 60, 60)); $dynamic = function_exists('get_option') ? (bool) get_option('wj_dynamic_update_ttl_enabled', false) : false; if ( ! $dynamic ) { return $base; } // Lightweight heuristic using system load and last run stats $load = function_exists('sys_getloadavg') ? (float) (sys_getloadavg()[0] ?? 0.0) : 0.0; $stats = function_exists('get_option') ? (array) get_option('wj_cache_run_stats', []) : []; $duration = isset($stats['duration_s']) ? (int) $stats['duration_s'] : 0; $below = isset($stats['below_threshold']) ? (int) $stats['below_threshold'] : 0; $processed= isset($stats['processed_locations']) ? (int) $stats['processed_locations'] : 0; $eff = $base; // start from 90% of cacheTTL as target if system is idle and runs are fast $upperTarget = (int) round($cacheTTL * 0.95); $lowerTarget = (int) round(max($cacheTTL * 0.60, $base * 0.60)); if ( ($load < 1.0) && ($duration > 0 && $duration < 30) && ($below < max(10, (int) round($processed * 0.05))) ) { $eff = min($upperTarget, $cacheTTL - 30); } elseif ( ($load > 4.0) || ($duration > 180) ) { $eff = max((int) round($base * 0.80), $lowerTarget); } // final clamp to 60%..98% of cache TTL and strictly less than cache TTL $eff = max($lowerTarget, min($eff, (int) round($cacheTTL * 0.98))); $eff = min($eff, $cacheTTL - 5); return $eff; } /** * Get the server name from environment. * This function encapsulates the global variable $_SERVER for testing purposes. */ function get_server_name() { if ( function_exists( 'get_site_url' ) ) { // If within a WordPress environment return parse_url( get_site_url(), PHP_URL_HOST ); } else { // If not within a WordPress environment if ( isset( $_SERVER['SERVER_NAME'] ) ) { $server_name = $_SERVER['SERVER_NAME']; if ( filter_var( 'http://' . $server_name, FILTER_VALIDATE_URL ) ) { return $server_name; } else { // Handle invalid server name here return 'undefined'; } } else { // Handle unset server name here return 'undefined'; } } } if ( ! function_exists( 'str_contains' ) ) { function str_contains( string $haystack, string $needle ): bool { return '' === $needle || false !== strpos( $haystack, $needle ); } } if ( ! function_exists( 'getFirstNotEmptyValue' ) ) { function getFirstNotEmptyValue( $array ) { foreach ( $array as $value ) { if ( ! empty( $value ) && $value != 0 ) { return $value; } } return null; // return null if no such value is found } } if ( ! function_exists( 'getLastNotEmptyValue' ) ) { function getLastNotEmptyValue($array) { for ($i = count($array) - 1; $i >= 0; $i--) { if (!empty($array[$i]) && $array[$i] != 0) { return $array[$i]; } } return null; // return null if no such value is found } } if ( ! function_exists( 'print_array' ) ) { function print_array( $array ) { echo count( $array ) . ' Datensätze enthalten'; echo '
';
		print_r( $array ); // Print array content properly formatted
		echo '
'; } } /** * Collect a missing search term into a WordPress option as a unique array. * - Stores under option key 'wj_missing_terms' by default * - Adds only if not already present (strict comparison) * - Sanitizes and trims the value; ignores empty values */ function wj_add_missing_term( string $term, string $option_key = 'wj_missing_terms' ): void { $term = trim( wp_strip_all_tags( $term ) ); if ( $term === '' ) { return; } $existing = get_option( $option_key, [] ); if ( ! is_array( $existing ) ) { $existing = []; } if ( ! in_array( $term, $existing, true ) ) { $existing[] = $term; // Do not autoload to avoid polluting WP's autoloaded options update_option( $option_key, $existing, false ); } } function days_since( $date ) { $now = time(); $your_date = strtotime( $date ); $datediff = $now - $your_date; return round( $datediff / ( 60 * 60 * 24 ) ); } //set import date // WordPress-Header einbinden // This function updates the 'update_date' of the records in the 'nyl7rb6f_imports' table function update_update_date( $id, $random_days ) { global $wpdb; // Calculate new update date $new_update_date = date( 'Y-m-d H:i:s', strtotime( "-$random_days days" ) ); // Execute the UPDATE statement $wpdb->update( 'nyl7rb6f_imports', array( 'update_date' => $new_update_date ), array( 'id' => $id ), array( '%s' ), array( '%d' ) ); \WJ\wj_log('debug', "Updated the record with id: {$id} and set 'update_date' to {$new_update_date}"); } // This function will find the records to be updated and call the 'update_update_date' function function getDatesToUpdate() { global $wpdb; // Run the SELECT query $results = $wpdb->get_results( $wpdb->prepare("SELECT id FROM nyl7rb6f_imports WHERE client_id = %d AND update_date = %s", 9702825, '2024-04-06 07:10:13') ); $num_records = count($results); \WJ\wj_log('debug', "Found {$num_records} record(s) to be updated"); // Send each row for update foreach( $results as $record ) { // Generate a random number of days between 10 and 30 $random_days = rand( 10, 30 ); // Update the respective row update_update_date( $record->id, $random_days); } echo 'Successfully updated the \'update_date\' column.'; } // This function will initiate the update process if the current user is an administrator add_action('init', function() { // Check if the current user is an administrator if (current_user_can('administrator')) { //error_log("Running getDatesToUpdate function as the user is identified as an administrator."); // Call the getDatesToUpdate function //getDatesToUpdate(); // To only trigger the 'init' hook once, you can use the following code. // This code will remove the current action right after its execution remove_action( current_action(), __FUNCTION__ ); //error_log("Removed the current action after executing it"); } }); /** * Liefert die primäre Bild‑URL einer Anzeige. * Reihenfolge: * 1) Meta 'image_primary_url' * 2) Erstes Element aus 'images_urls' (Array) * 3) Fallback null * * Gibt immer eine absolute URL zurück, wenn möglich. */ function wj_get_primary_image_url( int $post_id ): ?string { // 1) Explizit gesetzte primäre URL $primary = (string) get_post_meta( $post_id, 'image_primary_url', true ); if ( ! empty( $primary ) ) { return wj_normalize_to_absolute_url( $primary ); } // 2) Erstes Element aus images_urls $urls = get_post_meta( $post_id, 'images_urls', true ); if ( is_array( $urls ) && ! empty( $urls ) ) { $first = (string) reset( $urls ); if ( ! empty( $first ) ) { return wj_normalize_to_absolute_url( $first ); } } return null; } /** * Wandelt relative Pfade (beginnend mit "/") in absolute URLs anhand von site_url() um. * Absolute URLs werden unverändert zurückgegeben. */ function wj_normalize_to_absolute_url( string $url ): string { $url = trim( $url ); if ( $url === '' ) { return $url; } if ( str_starts_with( $url, 'http://' ) || str_starts_with( $url, 'https://' ) ) { return $url; } // relative → absolute $base = rtrim( get_site_url(), '/' ); if ( str_starts_with( $url, '/' ) ) { return $base . $url; } return $base . '/' . $url; }