...,'value'=>...) // but values saved before this fix may be plain strings, causing a fatal // "Cannot access offset of type string on string" error on admin edit screens. add_filter( 'acf/load_value/type=icon_picker', array( $this, 'fix_icon_picker_value' ), 10, 3 ); } /** * Enqueue admin scripts and styles * * @since 1.0.0 * @param string $hook Current admin page hook. * @return void */ public function admin_enqueue_scripts( $hook ) { // Only load on our plugin pages. if ( strpos( $hook, 'affcf' ) === false ) { return; } // CSS. wp_enqueue_style( 'affcf-admin', AFFCF_PLUGIN_URL . 'assets/css/admin/admin-global.css', array(), AFFCF_VERSION ); // JavaScript. wp_enqueue_script( 'affcf-admin', AFFCF_PLUGIN_URL . 'assets/js/admin/admin-global.js', array( 'jquery' ), AFFCF_VERSION, true ); // Localize script. wp_localize_script( 'affcf-admin', 'affcfAdmin', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'affcf_admin_nonce' ), ) ); } /** * Ensure dateTimePickerL10n is available for time_picker fields * * Fixes an SCF/ACF bug where the time_picker field JS crashes because it * depends on dateTimePickerL10n localization set by the date_time_picker * field class. When this localization is missing (e.g., the date_time_picker * scripts weren't loaded), the time_picker initialization fails with a * TypeError on admin post edit screens. * * Injects inline JS after the acf-input script to set the fallback data * directly on acf.data before DOMContentLoaded field initialization runs. * * @since 1.0.0 * @return void */ public function ensure_datetime_picker_l10n() { // Only act if acf-input script is registered (ACF/SCF is active). if ( ! wp_script_is( 'acf-input', 'registered' ) && ! wp_script_is( 'acf-input', 'enqueued' ) ) { return; } $inline_js = '(function(){' . 'if(typeof acf!=="undefined"&&acf.data&&!acf.data.dateTimePickerL10n){' . 'acf.data.dateTimePickerL10n={' . 'timeOnlyTitle:"Choose Time",' . 'timeText:"Time",' . 'hourText:"Hour",' . 'minuteText:"Minute",' . 'secondText:"Second",' . 'millisecText:"Millisecond",' . 'microsecText:"Microsecond",' . 'timezoneText:"Time Zone",' . 'currentText:"Now",' . 'closeText:"Done",' . 'selectText:"Select",' . 'amNames:["AM","A"],' . 'pmNames:["PM","P"]' . '};' . '}' . '})();'; wp_add_inline_script( 'acf-input', $inline_js, 'after' ); } /** * Fix icon_picker field values that were saved as strings * * SCF's icon_picker render_field() expects the value to be an array with * 'type' and 'value' keys. If a value was saved as a plain string (e.g., * from a frontend form submission before the format fix), this filter * converts it to the expected array format to prevent a PHP fatal error. * * @since 1.0.0 * @param mixed $value The field value. * @param int $post_id The post ID. * @param array $field The field array. * @return array The value in the expected array format. */ public function fix_icon_picker_value( $value, $post_id, $field ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Parameters required by ACF filter signature. // Already correct format. if ( is_array( $value ) ) { return $value; } // Null or empty — return empty array structure. if ( empty( $value ) ) { return array( 'type' => '', 'value' => '', ); } // String value — convert to expected array format. if ( is_string( $value ) ) { $icon_type = 'dashicons'; if ( filter_var( $value, FILTER_VALIDATE_URL ) ) { $icon_type = 'url'; } return array( 'type' => $icon_type, 'value' => $value, ); } // Unexpected type — return safe default. return array( 'type' => '', 'value' => '', ); } /** * Handle post status changes for email notifications * * @since 1.0.0 * @param string $new_status New post status. * @param string $old_status Old post status. * @param WP_Post $post Post object. * @return void */ public function handle_post_status_change( $new_status, $old_status, $post ) { // Skip if not our form submission. $form_id = get_post_meta( $post->ID, '_affcf_form_id', true ); if ( ! $form_id ) { return; } // Skip if status hasn't actually changed. if ( $new_status === $old_status ) { return; } // Skip auto-drafts and revisions. if ( in_array( $new_status, array( 'auto-draft', 'inherit' ), true ) || wp_is_post_revision( $post->ID ) ) { return; } try { // Get form object. $form_repo = new AFFCF_Form_Repository(); $form = $form_repo->get_by_id( $form_id ); if ( ! $form ) { return; } // Get post author. $user_id = $post->post_author; // Initialize email manager. $email_manager = new AFFCF_Email_Manager(); // Handle post published. if ( 'publish' === $new_status && 'publish' !== $old_status ) { $email_manager->send_post_published( $user_id, $post->ID, $form ); } // Handle post moved to pending (from published or other status). if ( 'pending' === $new_status && in_array( $old_status, array( 'publish', 'draft' ), true ) ) { // Send submission received email for resubmissions. $email_manager->send_submission_received( $user_id, $post->ID, $form ); } } catch ( Exception $e ) { // Silently fail - email notification is not critical. // Error is already handled by email manager. unset( $e ); // Suppress unused variable warning. } } }