PluginProbe
AI / 0.2.0
AI v0.2.0
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / helpers.php

helpers.php in AI 0.2.0, at includes/helpers.php

273 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Helper functions for the AI plugin.
4 *
5 * @package WordPress\AI
6 */
7
8 declare( strict_types=1 );
9
10 namespace WordPress\AI;
11
12 use Throwable;
13 use WordPress\AI_Client\AI_Client;
14
15 /**
16 * Purposely using return instead of exit here.
17 *
18 * This file is loaded via the composer files directive.
19 * When tools like PHPCS and PHPStan run, they include
20 * our composer autoloader and that will then load this file,
21 * causing the script to exit and not function properly.
22 */
23 if ( ! defined( 'ABSPATH' ) ) {
24 return;
25 }
26
27 /**
28 * Normalizes the content by cleaning it and removing unwanted HTML tags.
29 *
30 * @since 0.1.0
31 *
32 * @param string $content The content to normalize.
33 * @return string The normalized content.
34 */
35 function normalize_content( string $content ): string {
36 /**
37 * Hook to filter content before cleaning it.
38 *
39 * @since 0.1.0
40 *
41 * @param string $post_content The post content.
42 *
43 * @return string The filtered Post content.
44 */
45 $content = (string) apply_filters( 'ai_experiments_pre_normalize_content', $content );
46
47 // Strip HTML entities.
48 $content = preg_replace( '/&#?[a-z0-9]{2,8};/i', '', $content );
49
50 // Replace HTML linebreaks with newlines.
51 $content = preg_replace( '#<br\s?/?>#', "\n\n", (string) $content );
52
53 // Strip all HTML tags.
54 $content = wp_strip_all_tags( (string) $content );
55
56 // Remove unrendered shortcode tags.
57 $content = preg_replace( '#\[.+\](.+)\[/.+\]#', '$1', $content );
58
59 /**
60 * Filters the normalized content to allow for additional cleanup.
61 *
62 * @since 0.1.0
63 *
64 * @param string $content The normalized content.
65 *
66 * @return string The filtered normalized content.
67 */
68 $content = (string) apply_filters( 'ai_experiments_normalize_content', (string) $content );
69
70 return trim( $content );
71 }
72
73 /**
74 * Returns the context for the given post ID.
75 *
76 * @since 0.1.0
77 *
78 * @param int $post_id The ID of the post to get the context for.
79 * @return array<string, string> The context for the given post ID.
80 */
81 function get_post_context( int $post_id ): array {
82 $context = array();
83
84 // Get the post details using the get-post-details ability.
85 $details_ability = wp_get_ability( 'ai/get-post-details' );
86 if ( $details_ability ) {
87 $details = $details_ability->execute( array( 'post_id' => $post_id ) );
88
89 if ( is_array( $details ) ) {
90 $context = array_merge( $context, $details );
91
92 if ( isset( $context['content'] ) ) {
93 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
94 $context['content'] = normalize_content( (string) apply_filters( 'the_content', $context['content'] ) );
95 }
96
97 if ( isset( $context['type'] ) ) {
98 $context['content_type'] = $context['type'];
99 unset( $context['type'] );
100 }
101
102 // Remove any empty context values.
103 $context = array_filter( $context );
104 }
105 }
106
107 // Get the post terms using the get-terms ability.
108 $terms_ability = wp_get_ability( 'ai/get-post-terms' );
109 if ( $terms_ability ) {
110 $terms = $terms_ability->execute( array( 'post_id' => $post_id ) );
111
112 if ( $terms && ! is_wp_error( $terms ) ) {
113 $grouped_terms = array();
114
115 foreach ( $terms as $term ) {
116 $grouped_terms[ $term->taxonomy ][] = $term->name;
117 }
118
119 $context = array_merge(
120 $context,
121 array_map(
122 static fn( array $term_names ): string => implode( ', ', $term_names ),
123 $grouped_terms
124 )
125 );
126 }
127 }
128
129 return $context;
130 }
131
132 /**
133 * Returns the preferred models.
134 *
135 * @since 0.1.0
136 *
137 * @return array<int, array{string, string}> The preferred models.
138 */
139 function get_preferred_models(): array {
140 $preferred_models = array(
141 array(
142 'anthropic',
143 'claude-haiku-4-5',
144 ),
145 array(
146 'google',
147 'gemini-2.5-flash',
148 ),
149 array(
150 'openai',
151 'gpt-4o-mini',
152 ),
153 array(
154 'openai',
155 'gpt-4.1',
156 ),
157 );
158
159 /**
160 * Filters the preferred models.
161 *
162 * @since 0.1.0
163 *
164 * @param array<int, array{string, string}> $preferred_models The preferred models.
165 * @return array<int, array{string, string}> The filtered preferred models.
166 */
167 return (array) apply_filters( 'ai_experiments_preferred_models', $preferred_models );
168 }
169
170 /**
171 * Returns the preferred image models.
172 *
173 * @since 0.2.0
174 *
175 * @return array<int, array{string, string}> The preferred image models.
176 */
177 function get_preferred_image_models(): array {
178 $preferred_models = array(
179 array(
180 'google',
181 'gemini-3-pro-image-preview',
182 ),
183 array(
184 'google',
185 'gemini-2.5-flash-image',
186 ),
187 array(
188 'google',
189 'imagen-4.0-generate-001',
190 ),
191 array(
192 'openai',
193 'gpt-image-1',
194 ),
195 array(
196 'openai',
197 'dall-e-3',
198 ),
199 );
200
201 /**
202 * Filters the preferred image models.
203 *
204 * @since 0.2.0
205 *
206 * @param array<int, array{string, string}> $preferred_models The preferred image models.
207 * @return array<int, array{string, string}> The filtered preferred image models.
208 */
209 return (array) apply_filters( 'ai_experiments_preferred_image_models', $preferred_models );
210 }
211
212 /**
213 * Checks if we have AI credentials set.
214 *
215 * @since 0.1.0
216 *
217 * @return bool True if we have AI credentials, false otherwise.
218 */
219 function has_ai_credentials(): bool {
220 $credentials = get_option( 'wp_ai_client_provider_credentials', array() );
221
222 // If there are no credentials, return false.
223 if ( ! is_array( $credentials ) || empty( $credentials ) ) {
224 return false;
225 }
226
227 // If all of the AI keys are empty, return false; otherwise, return true.
228 return ! empty(
229 array_filter(
230 $credentials,
231 static function ( $api_key ): bool {
232 return is_string( $api_key ) && '' !== $api_key;
233 }
234 )
235 );
236 }
237
238 /**
239 * Checks if we have valid AI credentials.
240 *
241 * @since 0.1.0
242 *
243 * @return bool True if we have valid AI credentials, false otherwise.
244 */
245 function has_valid_ai_credentials(): bool {
246 // If we have no AI credentials, return false.
247 if ( ! has_ai_credentials() ) {
248 return false;
249 }
250
251 /**
252 * Filters whether valid AI credentials are available.
253 *
254 * Allows overriding the credentials check, useful for testing.
255 *
256 * @since 0.1.0
257 *
258 * @param bool|null $has_valid_credentials Whether valid credentials are available. Return null to use default check.
259 * @return bool|null True if valid credentials are available, false otherwise, or null to use default check.
260 */
261 $valid = apply_filters( 'ai_experiments_pre_has_valid_credentials_check', null );
262 if ( null !== $valid ) {
263 return (bool) $valid;
264 }
265
266 // See if we have credentials that give us access to generate text.
267 try {
268 return AI_Client::prompt( 'Test' )->is_supported_for_text_generation();
269 } catch ( Throwable $t ) {
270 return false;
271 }
272 }
273