Files
Digital-Dabei-Hamburg-Job-M…/includes/class-notifications.php
Viktor Miller 4145a92ca7 fix(quick-002): prevent duplicate mentor notifications on job republish
- Add post meta guard `_ddhh_mentors_notified` to track notification status
- Check meta before scheduling notifications, skip if already notified
- Set meta flag after successful batch scheduling
- Prevents re-notification when job edited and republished (pending -> publish)
- Maintains existing publish -> publish guard logic
2026-01-29 14:28:00 +09:00

486 lines
17 KiB
PHP

<?php
/**
* Email notification system for job submissions
*
* @package DDHH_Job_Manager
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
/**
* Handles email notifications for job submissions
*/
class DDHH_JM_Notifications {
/**
* Setup notification hooks
*/
public static function setup_hooks() {
// Hook into job submission to send admin notification (after metadata is saved)
add_action( 'ddhh_job_submitted', array( __CLASS__, 'send_admin_new_job_notification_after_submit' ), 10, 2 );
// Hook into job edit to send admin notification (after metadata is saved)
add_action( 'ddhh_job_edited', array( __CLASS__, 'send_admin_job_edit_notification_after_submit' ), 10, 2 );
// Hook into job deactivation to send admin notification (after metadata is saved)
add_action( 'ddhh_job_deactivated', array( __CLASS__, 'send_admin_job_deactivation_notification' ), 10, 2 );
// Hook into post status transitions to notify mentors on job publish
add_action( 'transition_post_status', array( __CLASS__, 'notify_mentors_on_job_publish' ), 10, 3 );
// Hook into Formidable form submissions to send application notifications
add_action( 'frm_after_create_entry', array( __CLASS__, 'send_provider_application_notification' ), 30, 2 );
}
/**
* Send admin notification when a new job is submitted (called after metadata is saved)
*
* @param int $post_id Post ID.
* @param int $entry_id Entry ID.
*/
public static function send_admin_new_job_notification_after_submit( $post_id, $entry_id ) {
$post = get_post( $post_id );
if ( ! $post || 'job_offer' !== $post->post_type ) {
return;
}
// Get admin email
$admin_email = get_option( 'admin_email' );
if ( ! $admin_email ) {
error_log( 'DDHH Job Manager: Cannot send admin notification - admin_email option not set' );
return;
}
// Prepare email data
$job_title = $post->post_title;
$author = get_userdata( $post->post_author );
$author_name = $author ? $author->display_name : 'Unbekannt';
$author_org = get_user_meta( $post->post_author, 'ddhh_org_name', true );
if ( empty( $author_org ) ) {
$author_org = 'Nicht angegeben';
}
// Get post meta fields
$job_location = get_post_meta( $post->ID, 'job_location', true );
$job_type = get_post_meta( $post->ID, 'job_type', true );
$job_deadline = get_post_meta( $post->ID, 'job_deadline', true );
$job_contact_email = get_post_meta( $post->ID, 'job_contact_email', true );
// Get job description (truncate if too long)
$job_description = $post->post_content;
$description_text = wp_strip_all_tags( $job_description );
if ( strlen( $description_text ) > 500 ) {
$description_text = substr( $description_text, 0, 500 ) . '...';
}
// Format deadline if present
$deadline_formatted = 'Nicht angegeben';
if ( ! empty( $job_deadline ) ) {
$deadline_formatted = date( 'd.m.Y', strtotime( $job_deadline ) );
}
// Get submission date
$submission_date = get_the_date( 'd.m.Y H:i', $post->ID );
// Get edit link
$edit_link = get_edit_post_link( $post->ID, '' );
// Build email subject
$subject = sprintf( 'Neues Stellenangebot zur Prüfung: %s', $job_title );
// Build email body as HTML
$html_body = 'Ein neues Stellenangebot wurde eingereicht und wartet auf Ihre Prüfung.<br><br>';
$html_body .= '<strong>Titel:</strong> ' . esc_html( $job_title ) . '<br>';
$html_body .= '<strong>Anbieter:</strong> ' . esc_html( $author_name ) . ' (' . esc_html( $author_org ) . ')<br>';
$html_body .= '<strong>Standort:</strong> ' . esc_html( $job_location ? $job_location : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Art:</strong> ' . esc_html( $job_type ? $job_type : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Bewerbungsfrist:</strong> ' . esc_html( $deadline_formatted ) . '<br>';
$html_body .= '<strong>Kontakt-E-Mail:</strong> ' . esc_html( $job_contact_email ? $job_contact_email : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Beschreibung:</strong><br>' . esc_html( $description_text ) . '<br><br>';
$html_body .= '<strong>Eingereicht am:</strong> ' . esc_html( $submission_date ) . '<br><br>';
$html_body .= '<a href="' . esc_url( $edit_link ) . '">Stellenangebot in WordPress prüfen</a><br><br>';
$html_body .= '---<br>';
$html_body .= 'Diese E-Mail wurde automatisch gesendet.';
// Set email headers
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
// Send email
$sent = wp_mail( $admin_email, $subject, $html_body, $headers );
// Log if email fails
if ( ! $sent ) {
error_log(
sprintf(
'DDHH Job Manager: Failed to send admin notification for job #%d "%s". Email may be disabled in Local WP environment.',
$post->ID,
$job_title
)
);
}
}
/**
* Send admin notification when a job is edited (called after metadata is saved)
*
* @param int $post_id Post ID.
* @param int $entry_id Entry ID.
*/
public static function send_admin_job_edit_notification_after_submit( $post_id, $entry_id ) {
$post = get_post( $post_id );
if ( ! $post || 'job_offer' !== $post->post_type ) {
return;
}
// Get admin email
$admin_email = get_option( 'admin_email' );
if ( ! $admin_email ) {
error_log( 'DDHH Job Manager: Cannot send admin notification - admin_email option not set' );
return;
}
// Prepare email data
$job_title = $post->post_title;
$author = get_userdata( $post->post_author );
$author_name = $author ? $author->display_name : 'Unbekannt';
$author_org = get_user_meta( $post->post_author, 'ddhh_org_name', true );
if ( empty( $author_org ) ) {
$author_org = 'Nicht angegeben';
}
// Get post meta fields
$job_location = get_post_meta( $post->ID, 'job_location', true );
$job_type = get_post_meta( $post->ID, 'job_type', true );
$job_deadline = get_post_meta( $post->ID, 'job_deadline', true );
$job_contact_email = get_post_meta( $post->ID, 'job_contact_email', true );
// Get job description (truncate if too long)
$job_description = $post->post_content;
$description_text = wp_strip_all_tags( $job_description );
if ( strlen( $description_text ) > 500 ) {
$description_text = substr( $description_text, 0, 500 ) . '...';
}
// Format deadline if present
$deadline_formatted = 'Nicht angegeben';
if ( ! empty( $job_deadline ) ) {
$deadline_formatted = date( 'd.m.Y', strtotime( $job_deadline ) );
}
// Get submission date
$submission_date = current_time( 'd.m.Y H:i' );
// Get edit link
$edit_link = get_edit_post_link( $post->ID, '' );
// Build email subject
$subject = sprintf( 'Stellenangebot bearbeitet und wartet auf Prüfung: %s', $job_title );
// Build email body as HTML
$html_body = 'Ein Stellenangebot wurde bearbeitet und wartet auf erneute Prüfung.<br><br>';
$html_body .= '<strong>Titel:</strong> ' . esc_html( $job_title ) . '<br>';
$html_body .= '<strong>Anbieter:</strong> ' . esc_html( $author_name ) . ' (' . esc_html( $author_org ) . ')<br>';
$html_body .= '<strong>Standort:</strong> ' . esc_html( $job_location ? $job_location : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Art:</strong> ' . esc_html( $job_type ? $job_type : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Bewerbungsfrist:</strong> ' . esc_html( $deadline_formatted ) . '<br>';
$html_body .= '<strong>Kontakt-E-Mail:</strong> ' . esc_html( $job_contact_email ? $job_contact_email : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Beschreibung:</strong><br>' . esc_html( $description_text ) . '<br><br>';
$html_body .= '<strong>Bearbeitet am:</strong> ' . esc_html( $submission_date ) . '<br><br>';
$html_body .= '<a href="' . esc_url( $edit_link ) . '">Stellenangebot in WordPress prüfen</a><br><br>';
$html_body .= '---<br>';
$html_body .= 'Diese E-Mail wurde automatisch gesendet.';
// Set email headers
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
// Send email
$sent = wp_mail( $admin_email, $subject, $html_body, $headers );
// Log if email fails
if ( ! $sent ) {
error_log(
sprintf(
'DDHH Job Manager: Failed to send admin edit notification for job #%d "%s". Email may be disabled in Local WP environment.',
$post->ID,
$job_title
)
);
}
}
/**
* Send admin notification when a job is deactivated by provider
*
* @param int $post_id Post ID.
* @param int $entry_id Entry ID.
*/
public static function send_admin_job_deactivation_notification( $post_id, $entry_id ) {
$post = get_post( $post_id );
if ( ! $post || 'job_offer' !== $post->post_type ) {
return;
}
// Get admin email
$admin_email = get_option( 'admin_email' );
if ( ! $admin_email ) {
error_log( 'DDHH Job Manager: Cannot send deactivation notification - admin_email option not set' );
return;
}
// Prepare email data
$job_title = $post->post_title;
$author = get_userdata( $post->post_author );
$author_name = $author ? $author->display_name : 'Unbekannt';
$author_org = get_user_meta( $post->post_author, 'ddhh_org_name', true );
if ( empty( $author_org ) ) {
$author_org = 'Nicht angegeben';
}
// Get post meta fields
$job_location = get_post_meta( $post->ID, 'job_location', true );
$job_type = get_post_meta( $post->ID, 'job_type', true );
$job_deadline = get_post_meta( $post->ID, 'job_deadline', true );
$job_contact_email = get_post_meta( $post->ID, 'job_contact_email', true );
// Format deadline if present
$deadline_formatted = 'Nicht angegeben';
if ( ! empty( $job_deadline ) ) {
$deadline_formatted = date( 'd.m.Y', strtotime( $job_deadline ) );
}
// Get deactivation reason from post meta (now saved before this hook fires)
$deactivation_reason = get_post_meta( $post->ID, 'job_deactivation_reason', true );
if ( empty( $deactivation_reason ) ) {
$deactivation_reason = 'Kein Grund angegeben';
}
// Get deactivation date
$deactivation_date = current_time( 'd.m.Y H:i' );
// Get edit link
$edit_link = get_edit_post_link( $post->ID, '' );
// Build email subject
$subject = sprintf( 'Stellenangebot deaktiviert: %s', $job_title );
// Build email body as HTML
$html_body = 'Ein Stellenangebot wurde vom Anbieter deaktiviert.<br><br>';
$html_body .= '<strong>Titel:</strong> ' . esc_html( $job_title ) . '<br>';
$html_body .= '<strong>Anbieter:</strong> ' . esc_html( $author_name ) . ' (' . esc_html( $author_org ) . ')<br>';
$html_body .= '<strong>Standort:</strong> ' . esc_html( $job_location ? $job_location : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Art:</strong> ' . esc_html( $job_type ? $job_type : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Bewerbungsfrist:</strong> ' . esc_html( $deadline_formatted ) . '<br>';
$html_body .= '<strong>Kontakt-E-Mail:</strong> ' . esc_html( $job_contact_email ? $job_contact_email : 'Nicht angegeben' ) . '<br>';
$html_body .= '<strong>Deaktiviert am:</strong> ' . esc_html( $deactivation_date ) . '<br><br>';
$html_body .= '<strong>Grund für Deaktivierung:</strong><br>' . esc_html( $deactivation_reason ) . '<br><br>';
$html_body .= '<a href="' . esc_url( $edit_link ) . '">Stellenangebot in WordPress ansehen</a><br><br>';
$html_body .= '---<br>';
$html_body .= 'Diese E-Mail wurde automatisch gesendet.';
// Set email headers
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
// Send email
$sent = wp_mail( $admin_email, $subject, $html_body, $headers );
// Log if email fails
if ( ! $sent ) {
error_log(
sprintf(
'DDHH Job Manager: Failed to send admin deactivation notification for job #%d "%s". Email may be disabled in Local WP environment.',
$post->ID,
$job_title
)
);
}
}
/**
* Send provider notification when a mentor applies to their job
*
* @param int $entry_id Entry ID.
* @param int $form_id Form ID.
*/
public static function send_provider_application_notification( $entry_id, $form_id ) {
// Only process job application form submissions
$application_form_id = DDHH_JM_Formidable::get_job_application_form_id();
if ( $form_id != $application_form_id ) {
return;
}
// Get entry data
$entry = FrmEntry::getOne( $entry_id, true );
if ( ! $entry ) {
return;
}
// Extract field values
$applicant_name = '';
$applicant_email = '';
$applicant_message = '';
$job_id = 0;
foreach ( $entry->metas as $field_id => $value ) {
$field = FrmField::getOne( $field_id );
if ( ! $field ) {
continue;
}
switch ( $field->field_key ) {
case 'applicant_name':
$applicant_name = sanitize_text_field( $value );
break;
case 'applicant_email':
$applicant_email = sanitize_email( $value );
break;
case 'applicant_message':
$applicant_message = sanitize_textarea_field( $value );
break;
case 'job_id':
case 'job_id2':
$job_id = absint( $value );
break;
}
}
// Validate required fields
if ( empty( $applicant_name ) || empty( $applicant_email ) || empty( $applicant_message ) || empty( $job_id ) ) {
error_log( 'DDHH Job Manager: Missing required fields in job application submission' );
return;
}
// Get job post
$post = get_post( $job_id );
if ( ! $post || 'job_offer' !== $post->post_type ) {
error_log( sprintf( 'DDHH Job Manager: Invalid job_id %d in application submission', $job_id ) );
return;
}
// Get job details
$job_title = $post->post_title;
$job_location = get_post_meta( $job_id, 'job_location', true );
$job_type = get_post_meta( $job_id, 'job_type', true );
// Get provider contact email from post meta
$provider_email = get_post_meta( $job_id, 'job_contact_email', true );
if ( empty( $provider_email ) ) {
error_log(
sprintf(
'DDHH Job Manager: Cannot send application notification for job #%d - job_contact_email not set',
$job_id
)
);
return;
}
// Build email subject
$subject = sprintf( 'Neue Bewerbung für: %s', $job_title );
// Build email body
$body = sprintf(
"Sie haben eine neue Bewerbung für Ihr Stellenangebot erhalten.\n\n" .
"Stelle: %s\n" .
"Standort: %s\n" .
"Art: %s\n\n" .
"--- Bewerber ---\n" .
"Name: %s\n" .
"E-Mail: %s\n\n" .
"Nachricht:\n%s\n\n" .
"---\n" .
"Bitte antworten Sie direkt an %s.\n\n" .
"Diese E-Mail wurde automatisch gesendet.",
$job_title,
$job_location ? $job_location : 'Nicht angegeben',
$job_type ? $job_type : 'Nicht angegeben',
$applicant_name,
$applicant_email,
$applicant_message,
$applicant_email
);
// Set email headers
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
// Convert plain text to HTML with line breaks
$html_body = nl2br( esc_html( $body ) );
// Send email
$sent = wp_mail( $provider_email, $subject, $html_body, $headers );
// Log if email fails
if ( ! $sent ) {
error_log(
sprintf(
'DDHH Job Manager: Failed to send application notification for job #%d "%s". Email may be disabled in Local WP environment.',
$job_id,
$job_title
)
);
}
}
/**
* Notify opted-in mentors when a job is published
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param WP_Post $post Post object.
*/
public static function notify_mentors_on_job_publish( $new_status, $old_status, $post ) {
// Only trigger on job_offer posts
if ( 'job_offer' !== $post->post_type ) {
return;
}
// Only send notification on initial publish (not updates to already published posts)
if ( 'publish' !== $new_status || 'publish' === $old_status ) {
return;
}
// Check if mentors have already been notified for this job
$already_notified = get_post_meta( $post->ID, '_ddhh_mentors_notified', true );
if ( '1' === $already_notified ) {
error_log(
sprintf(
'DDHH Job Manager: Skipping mentor notification for job #%d - mentors already notified on initial publish',
$post->ID
)
);
return;
}
// Get opted-in mentors
$mentor_ids = DDHH_JM_User_Preferences::get_opted_in_mentors();
// Check if any mentors opted in
if ( empty( $mentor_ids ) ) {
error_log(
sprintf(
'DDHH Job Manager: No opted-in mentors to notify for job #%d "%s"',
$post->ID,
$post->post_title
)
);
return;
}
// Schedule async batch notifications
$batch_count = DDHH_JM_Scheduler::schedule_mentor_notification_batch( $mentor_ids, $post->ID );
// Mark job as having notified mentors to prevent duplicate notifications
update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' );
// Log success
error_log(
sprintf(
'DDHH Job Manager: Scheduled %d notification batches for job #%d "%s" to %d mentors',
$batch_count,
$post->ID,
$post->post_title,
count( $mentor_ids )
)
);
}
}