documents->get( $post_id ); if ( ! $document ) { return false; } $elements_data = $document->get_elements_data(); if ( empty( $elements_data ) ) { return false; } $modified = self::traverse_and_modify( $elements_data, $callback ); if ( $modified ) { // Save back through Elementor API. This updates both _elementor_data and post_content $document->save( [ 'elements' => $elements_data, ] ); return true; } return false; } /** * Recursively traverse Elementor elements and apply the callback to text widgets. * * @param array $elements * @param callable $callback * @return bool */ private static function traverse_and_modify( &$elements, $callback ) { $modified = false; foreach ( $elements as &$element ) { // Check if this is a widget that contains text we want to modify if ( isset( $element['elType'] ) && $element['elType'] === 'widget' ) { $widget_type = $element['widgetType']; if ( $widget_type === 'text-editor' && isset( $element['settings']['editor'] ) ) { $old_html = $element['settings']['editor']; $new_html = call_user_func( $callback, $old_html ); if ( $new_html !== $old_html ) { $element['settings']['editor'] = $new_html; $modified = true; } } elseif ( $widget_type === 'heading' && isset( $element['settings']['title'] ) ) { $old_html = $element['settings']['title']; $new_html = call_user_func( $callback, $old_html ); if ( $new_html !== $old_html ) { $element['settings']['title'] = $new_html; $modified = true; } } } // Recurse into children if ( ! empty( $element['elements'] ) ) { if ( self::traverse_and_modify( $element['elements'], $callback ) ) { $modified = true; } } } return $modified; } }