put_contents( $file_path, $ics_content, FS_CHMOD_FILE ); } if ( ! file_exists( $file_path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents file_put_contents( $file_path, $ics_content ); } return $file_path; } /** * Generate an ICS calendar attachment. */ function schoolslots_generate_ics_attachment( $event_title, $date_str, $time_str, $duration_minutes = 15, $room = '', $student_info = '' ) { /* translators: %s: student name and class */ $summary = sprintf( __( 'School Appointment - Student: %s', 'schoolslots' ), $student_info ); $location = schoolslots_get_formatted_location( 'kozponti_fogado', '', $room ); $clean_loc = wp_strip_all_tags( str_replace( '
', ' ', $location ) ); $full_time = ( 5 === strlen( $time_str ) ) ? $time_str . ':00' : $time_str; return schoolslots_generate_ics_file( $event_title, $summary, $clean_loc, $date_str, $full_time, $duration_minutes ); } /** * Generate a combined multi-event ICS calendar file for multiple presentations. */ function schoolslots_generate_multi_ics_file( $events_list, $date_str, $duration_minutes = 45 ) { if ( empty( $events_list ) ) { return ''; } $dtstamp = gmdate( 'Ymd\THis\Z' ); $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : 'schoolslots'; $ics_content = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//SchoolSlots//EN\r\nMETHOD:REQUEST\r\n"; foreach ( $events_list as $idx => $ev_item ) { $time_str = ( 5 === strlen( $ev_item['time'] ) ) ? $ev_item['time'] . ':00' : $ev_item['time']; $start_ts = strtotime( $date_str . ' ' . $time_str ); $end_ts = $start_ts + ( $duration_minutes * 60 ); $dtstart = gmdate( 'Ymd\THis\Z', $start_ts ); $dtend = gmdate( 'Ymd\THis\Z', $end_ts ); $uid = uniqid( 'pres_' . $idx . '_' ) . '@' . $host; $clean_location = str_replace( array( "\r\n", "\r", "\n" ), ' ', (string) $ev_item['location'] ); $ics_content .= "BEGIN:VEVENT\r\n"; $ics_content .= "UID:{$uid}\r\nDTSTAMP:{$dtstamp}\r\nDTSTART:{$dtstart}\r\nDTEND:{$dtend}\r\n"; $ics_content .= 'SUMMARY:' . addcslashes( (string) $ev_item['summary'], ',\\;' ) . "\r\n"; $ics_content .= 'DESCRIPTION:' . addcslashes( (string) $ev_item['description'], ',\\;' ) . "\r\n"; $ics_content .= 'LOCATION:' . addcslashes( $clean_location, ',\\;' ) . "\r\n"; $ics_content .= "STATUS:CONFIRMED\r\nEND:VEVENT\r\n"; } $ics_content .= "END:VCALENDAR\r\n"; $upload_dir = wp_upload_dir(); $file_path = $upload_dir['basedir'] . '/presentations_' . uniqid() . '.ics'; global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem ) { $wp_filesystem->put_contents( $file_path, $ics_content, FS_CHMOD_FILE ); } if ( ! file_exists( $file_path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents file_put_contents( $file_path, $ics_content ); } return $file_path; } /** * Get formatted location string for events. */ function schoolslots_get_formatted_location( $event_type, $event_location = '', $teacher_room = '' ) { $default_fallback = __( "Central High School / Main Building\n1000 Main Street 1.", 'schoolslots' ); $default_address = get_option( 'schoolslots_default_school_address', $default_fallback ); $formatted_default_address = nl2br( esc_html( $default_address ) ); if ( 'kozponti_fogado' === $event_type || 'egyeni_fogado' === $event_type ) { $room = ! empty( $event_location ) ? $event_location : $teacher_room; if ( ! empty( $room ) ) { /* translators: %s: room name or number */ return sprintf( esc_html__( 'Room: %s', 'schoolslots' ), esc_html( $room ) ) . '
' . $formatted_default_address; } } return $formatted_default_address; } /** * Generate an inline SVG QR code data URL. */ function schoolslots_get_qr_image_url( $data ) { $raw_str = (string) $data; if ( false !== strpos( $raw_str, 'token=' ) ) { parse_str( (string) wp_parse_url( $raw_str, PHP_URL_QUERY ), $q_params ); if ( ! empty( $q_params['token'] ) ) { $raw_str = (string) $q_params['token']; } } // Rövidített egyedi jegy-azonosító (max 16 bájt), amely illeszkedik a REST API LIKE keresésére és az ISO QR Version 1-L-be $payload = substr( trim( $raw_str ), 0, 16 ); $hash = md5( 'v2_iso_' . $payload ); $size = 21; // ISO/IEC 18004 Version 1 (21x21) $matrix = array_fill( 0, $size, array_fill( 0, $size, 0 ) ); $reserved = array_fill( 0, $size, array_fill( 0, $size, false ) ); // 1. Finder patterns (7x7) + Separators (8x8) $place_finder = function ( $row, $col ) use ( &$matrix, &$reserved, $size ) { for ( $r = -1; $r <= 7; $r++ ) { for ( $c = -1; $c <= 7; $c++ ) { $rr = $row + $r; $cc = $col + $c; if ( $rr >= 0 && $rr < $size && $cc >= 0 && $cc < $size ) { $reserved[ $rr ][ $cc ] = true; if ( $r >= 0 && $r <= 6 && $c >= 0 && $c <= 6 && ( 0 === $r || 6 === $r || 0 === $c || 6 === $c || ( $r >= 2 && $r <= 4 && $c >= 2 && $c <= 4 ) ) ) { $matrix[ $rr ][ $cc ] = 1; } else { $matrix[ $rr ][ $cc ] = 0; } } } } }; $place_finder( 0, 0 ); $place_finder( 0, $size - 7 ); $place_finder( $size - 7, 0 ); // 2. Timing patterns (row 6 & col 6) for ( $i = 8; $i < $size - 8; $i++ ) { $reserved[6][ $i ] = true; $reserved[ $i ][6] = true; $bit = ( 0 === $i % 2 ) ? 1 : 0; $matrix[6][ $i ] = $bit; $matrix[ $i ][6] = $bit; } // 3. Dark module & Format info areas reservation $reserved[ $size - 8 ][8] = true; $matrix[ $size - 8 ][8] = 1; for ( $i = 0; $i < 9; $i++ ) { $reserved[8][ $i ] = true; $reserved[ $i ][8] = true; } for ( $i = $size - 8; $i < $size; $i++ ) { $reserved[8][ $i ] = true; $reserved[ $i ][8] = true; } // 4. Encode Byte Mode Data (19 data bytes for Version 1-L) $bits = '0100' . str_pad( decbin( strlen( $payload ) ), 8, '0', STR_PAD_LEFT ); for ( $i = 0, $len = strlen( $payload ); $i < $len; $i++ ) { $bits .= str_pad( decbin( ord( $payload[ $i ] ) ), 8, '0', STR_PAD_LEFT ); } $bits .= str_repeat( '0', min( 4, 152 - strlen( $bits ) ) ); $bits_len = strlen( $bits ); while ( 0 !== $bits_len % 8 ) { $bits .= '0'; $bits_len = strlen( $bits ); } $pad_bytes = array( 236, 17 ); $pad_idx = 0; while ( $bits_len < 152 ) { $bits .= str_pad( decbin( $pad_bytes[ $pad_idx % 2 ] ), 8, '0', STR_PAD_LEFT ); $bits_len = strlen( $bits ); ++$pad_idx; } $data_cw = array(); for ( $i = 0; $i < 152; $i += 8 ) { $data_cw[] = bindec( substr( $bits, $i, 8 ) ); } // 5. Reed-Solomon ECC (7 ECC bytes over GF(256), primitive poly 0x11d) $gf_exp = array_fill( 0, 512, 0 ); $gf_log = array_fill( 0, 256, 0 ); $val = 1; for ( $i = 0; $i < 255; $i++ ) { $gf_exp[ $i ] = $val; $gf_exp[ $i + 255 ] = $val; $gf_log[ $val ] = $i; $val <<= 1; if ( $val & 256 ) { $val ^= 285; } } $gen = array( 1 ); for ( $i = 0; $i < 7; $i++ ) { $next = array_fill( 0, count( $gen ) + 1, 0 ); for ( $j = 0, $g_len = count( $gen ); $j < $g_len; $j++ ) { $next[ $j ] ^= $gen[ $j ]; $next[ $j + 1 ] ^= $gf_exp[ $gf_log[ $gen[ $j ] ] + $i ]; } $gen = $next; } $ecc = array_fill( 0, 7, 0 ); foreach ( $data_cw as $b ) { $factor = $b ^ array_shift( $ecc ); $ecc[] = 0; if ( 0 !== $factor ) { for ( $j = 0; $j < 7; $j++ ) { $ecc[ $j ] ^= $gf_exp[ $gf_log[ $gen[ $j + 1 ] ] + $gf_log[ $factor ] ]; } } } $all_cw = array_merge( $data_cw, $ecc ); $all_bits = ''; foreach ( $all_cw as $cw ) { $all_bits .= str_pad( decbin( $cw ), 8, '0', STR_PAD_LEFT ); } // 6. Module placement with Mask 0 ((r + c) % 2 === 0) $bit_idx = 0; $upward = true; for ( $col = $size - 1; $col > 0; $col -= 2 ) { if ( 6 === $col ) { --$col; } for ( $step = 0; $step < $size; $step++ ) { $r = $upward ? ( $size - 1 - $step ) : $step; for ( $c_off = 0; $c_off < 2; $c_off++ ) { $c = $col - $c_off; if ( ! $reserved[ $r ][ $c ] ) { $bit = ( $bit_idx < strlen( $all_bits ) ) ? intval( $all_bits[ $bit_idx ] ) : 0; ++$bit_idx; if ( 0 === ( $r + $c ) % 2 ) { $bit ^= 1; } $matrix[ $r ][ $c ] = $bit; } } } $upward = ! $upward; } // 7. Format Information bits for Level L (01) + Mask 0 (000) -> 0x77c4 $fmt_bits = '111011111000100'; $tl_cols = array( 0, 1, 2, 3, 4, 5, 7, 8 ); for ( $i = 0; $i < 8; $i++ ) { $matrix[8][ $tl_cols[ $i ] ] = intval( $fmt_bits[ $i ] ); } $tl_rows = array( 7, 5, 4, 3, 2, 1, 0 ); for ( $i = 0; $i < 7; $i++ ) { $matrix[ $tl_rows[ $i ] ][8] = intval( $fmt_bits[ $i + 8 ] ); } for ( $i = 0; $i < 7; $i++ ) { $matrix[ $size - 1 - $i ][8] = intval( $fmt_bits[ $i ] ); } for ( $i = 0; $i < 8; $i++ ) { $matrix[8][ $size - 8 + $i ] = intval( $fmt_bits[ $i + 7 ] ); } $module_size = 8; $margin = 16; $total_px = ( $size * $module_size ) + ( 2 * $margin ); $svg = ''; $svg .= ''; for ( $i = 0; $i < $size; $i++ ) { for ( $j = 0; $j < $size; $j++ ) { if ( 1 === $matrix[ $i ][ $j ] ) { $x = $margin + ( $j * $module_size ); $y = $margin + ( $i * $module_size ); $svg .= ''; } } } $svg .= ''; $upload_dir = wp_upload_dir(); $filename = 'qr_' . $hash . '.svg'; $file_path = $upload_dir['basedir'] . '/' . $filename; $file_url = $upload_dir['baseurl'] . '/' . $filename; if ( ! file_exists( $file_path ) ) { global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem ) { $wp_filesystem->put_contents( $file_path, $svg, FS_CHMOD_FILE ); } if ( ! file_exists( $file_path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents file_put_contents( $file_path, $svg ); } } return $file_url; } /** * Generate sortable table column link. */ function schoolslots_sort_link( $column_key, $label, $current_orderby, $current_order ) { $new_order = ( $current_orderby === $column_key && 'ASC' === $current_order ) ? 'DESC' : 'ASC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $params = map_deep( wp_unslash( $_GET ), 'sanitize_text_field' ); $params['orderby'] = $column_key; $params['order'] = $new_order; $url = add_query_arg( $params, admin_url( 'admin.php' ) ); $arrow = ( $current_orderby === $column_key ) ? ( 'ASC' === $current_order ? ' ▲' : ' ▼' ) : ''; return '' . esc_html( $label ) . $arrow . ''; } /** * Generate teacher sortable column link. */ function schoolslots_teacher_sort_link( $col, $label, $cur_orderby, $cur_order ) { $new_order = ( $cur_orderby === $col && 'ASC' === $cur_order ) ? 'DESC' : 'ASC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $params = map_deep( wp_unslash( $_GET ), 'sanitize_text_field' ); $params['t_orderby'] = $col; $params['t_order'] = $new_order; $url = add_query_arg( $params, admin_url( 'admin.php' ) ); $arrow = ( $cur_orderby === $col ) ? ( 'ASC' === $cur_order ? ' ▲' : ' ▼' ) : ''; return '' . esc_html( $label ) . $arrow . ''; } /** * Generate presentation sortable column link. */ function schoolslots_presentation_sort_link( $col, $label, $cur_orderby, $cur_order, $pres_ev_id ) { $new_order = ( $cur_orderby === $col && 'ASC' === $cur_order ) ? 'DESC' : 'ASC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $params = map_deep( wp_unslash( $_GET ), 'sanitize_text_field' ); $params['page'] = 'schoolslots-presentations'; $params['pres_event_id'] = intval( $pres_ev_id ); $params['p_orderby'] = $col; $params['p_order'] = $new_order; $url = add_query_arg( $params, admin_url( 'admin.php' ) ); $arrow = ( $cur_orderby === $col ) ? ( 'ASC' === $cur_order ? ' ▲' : ' ▼' ) : ''; return '' . esc_html( $label ) . $arrow . ''; } // Globális feladó e-mail cím átállítása a titkársági e-mailre add_filter( 'wp_mail_from', 'schoolslots_filter_mail_from' ); /** * Filter WordPress default email sender address. */ function schoolslots_filter_mail_from( $original_email ) { $secretary_email = get_option( 'schoolslots_secretary_email', get_option( 'admin_email' ) ); return is_email( $secretary_email ) ? $secretary_email : $original_email; } // Globális feladó név átállítása az iskola nevére add_filter( 'wp_mail_from_name', 'schoolslots_filter_mail_from_name' ); /** * Filter WordPress default email sender name. */ function schoolslots_filter_mail_from_name( $original_name ) { $school_address = get_option( 'schoolslots_default_school_address', '' ); $first_line = strtok( $school_address, "\n" ); return ! empty( $first_line ) ? trim( $first_line ) : $original_name; } /** * Retrieve normalized, sorted array of school classes. */ function schoolslots_get_school_classes_list() { $raw_opt = (string) get_option( 'schoolslots_school_classes', '' ); $parts = explode( ',', $raw_opt ); $map = array(); foreach ( $parts as $p ) { $clean = trim( $p ); if ( '' !== $clean ) { $norm_key = mb_strtolower( preg_replace( '/\s+/', '', $clean ) ); if ( ! isset( $map[ $norm_key ] ) ) { $map[ $norm_key ] = $clean; } } } if ( ! empty( $map ) ) { natcasesort( $map ); return array_values( $map ); } return array(); } /** * Merge newly imported or saved classes into the central school classes option. */ function schoolslots_merge_school_classes( $new_classes_str ) { if ( empty( $new_classes_str ) ) { return; } $current_list = schoolslots_get_school_classes_list(); $combined_str = implode( ', ', $current_list ) . ', ' . (string) $new_classes_str; $parts = explode( ',', $combined_str ); $map = array(); foreach ( $parts as $p ) { $clean = trim( $p ); if ( '' !== $clean && '-' !== $clean ) { $norm_key = mb_strtolower( preg_replace( '/\s+/', '', $clean ) ); if ( ! isset( $map[ $norm_key ] ) ) { $map[ $norm_key ] = $clean; } } } if ( ! empty( $map ) ) { natcasesort( $map ); update_option( 'schoolslots_school_classes', implode( ', ', array_values( $map ) ) ); } }