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 = '';
$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 ) ) );
}
}