PluginProbe
Code Embed / trunk
Code Embed vtrunk
2.6.4 2.6.3 2.6.2 2.6.1 trunk 1.0 1.1 1.2 1.3 1.4.1 1.5.1 1.6.1 2.0.2 2.1.2 2.2.2 2.3.9 2.4 2.5.1 2.5.2 2.6
simple-embed-code / includes / add-embeds.php

add-embeds.php in Code Embed trunk, at includes/add-embeds.php

355 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Add Embed to Posts
4 *
5 * Functions to add embed code to posts.
6 *
7 * @package simple-embed-code
8 */
9
10 // Exit if accessed directly.
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Add filter to add embed code
18 *
19 * Filter to add embed to any posts.
20 *
21 * @uses ce_get_embed_code Get embed code from other posts.
22 * @uses ce_quick_replace Perform a quick/replace of content.
23 * @uses. ce_generate_code Create the appropriate embed code.
24 *
25 * @param string $content Post content without embedded code.
26 * @return string Post content with embedded code.
27 */
28 function ce_filter( $content ) {
29
30 global $post;
31
32 if ( ! isset( $post->ID ) ) {
33 return $content;
34 }
35
36 // Set initial values.
37 static $options = null;
38 if ( null === $options ) {
39 $options = get_option( 'artiss_code_embed' );
40 }
41
42 if ( ! is_array( $options ) ) {
43 return $content;
44 }
45
46 $found_pos = strpos( $content, $options['opening_ident'] . $options['keyword_ident'], 0 );
47 $prefix_len = strlen( $options['opening_ident'] . $options['keyword_ident'] );
48
49 // Whether the author of the post being rendered may output raw, unfiltered HTML.
50
51 // This one decision gates raw output for both local and global embeds, so an untrusted author's page
52 // can never emit unsanitized markup - regardless of who authored the post a global embed is stored on.
53
54 $post_author = get_post_field( 'post_author', $post->ID );
55 $author_allows_raw = user_can( $post_author, 'unfiltered_html' );
56
57 // Loop around the post content looking for all requests for code embeds.
58
59 while ( false !== $found_pos ) {
60 // Get the position of the closing identifier - ignore if one is not found!
61
62 $end_pos = strpos( $content, $options['closing_ident'], $found_pos + $prefix_len );
63 if ( false !== $end_pos ) {
64
65 // Extract the suffix.
66
67 $suffix_len = $end_pos - ( $found_pos + $prefix_len );
68 if ( 0 === $suffix_len ) {
69 $suffix = '';
70 } else {
71 $suffix = substr( $content, $found_pos + $prefix_len, $suffix_len );
72 }
73 $full_suffix = $suffix;
74
75 // Check for responsive part of suffix.
76
77 $responsive = false;
78 $max_width = false;
79 $res_pos = false;
80 if ( 1 < strlen( $suffix ) ) {
81 $res_pos = strpos( $suffix, '_RES', 1 );
82 }
83
84 if ( false !== $res_pos ) {
85
86 // If responsive section found, check it's at the end or has an underscore afterwards.
87 // Otherwise it may be part of something else.
88
89 if ( strlen( $suffix ) - 4 === $res_pos ) {
90 $responsive = true;
91 } elseif ( '_' === substr( $suffix, $res_pos + 4, 1 ) ) {
92 $responsive = true;
93 $max_width = substr( $suffix, $res_pos + 5 );
94 if ( ! is_numeric( $max_width ) ) {
95 $max_width = '';
96 }
97 }
98
99 if ( $responsive ) {
100 $suffix = substr( $suffix, 0, $res_pos );
101 }
102 }
103
104 // Get the meta for the current post.
105
106 $post_meta = get_post_meta( $post->ID, $options['keyword_ident'] . $suffix, false );
107 if ( isset( $post_meta[0] ) ) {
108 // Only a string can be embed markup; anything else (e.g. an array meta value) is neutralized.
109 $value = is_string( $post_meta[0] ) ? $post_meta[0] : '';
110 if ( '' !== $value && $author_allows_raw ) {
111 $html = $value;
112 } else {
113 $html = wp_kses_post( $value );
114 }
115 } else {
116 // No meta found, so look for it elsewhere.
117 $html = ce_get_embed_code( $options['keyword_ident'], $suffix, $author_allows_raw );
118 }
119
120 // Build the string to search for.
121
122 $search = $options['opening_ident'] . $options['keyword_ident'] . $full_suffix . $options['closing_ident'];
123
124 // Build the string of code to replace with.
125
126 $replace = ce_generate_code( $html, $responsive, $max_width );
127
128 // Now modify all references.
129
130 $content = str_replace( $search, $replace, $content );
131
132 }
133 $found_pos = strpos( $content, $options['opening_ident'] . $options['keyword_ident'], $found_pos + 1 );
134 }
135
136 // Loop around the post content looking for HTTP addresses.
137
138 if ( '1' === get_post_meta( $post->ID, '_ce_allow_url_embeds', true ) ) {
139 $content = ce_quick_replace( $content, $options, 'http://' );
140
141 // Loop around the post content looking for HTTPS addresses.
142
143 $content = ce_quick_replace( $content, $options, 'https://' );
144 }
145
146 return $content;
147 }
148
149 add_filter( 'the_content', 'ce_filter' );
150 add_filter( 'widget_text_content', 'ce_filter' );
151 add_action( 'save_post', 'ce_save_url_embed_permission' );
152
153 /**
154 * Save URL Embed Permission
155 *
156 * On post save, records whether the post author has the unfiltered_html
157 * capability, so that URL embed tokens are only expanded at render time
158 * for content authored by trusted users.
159 *
160 * @param string $post_id The post ID.
161 */
162 function ce_save_url_embed_permission( $post_id ) {
163
164 if ( wp_is_post_revision( $post_id ) ) {
165 return;
166 }
167
168 $post = get_post( $post_id );
169 $allowed = user_can( $post->post_author, 'unfiltered_html' ) ? '1' : '0';
170 update_post_meta( $post_id, '_ce_allow_url_embeds', $allowed );
171 }
172
173 /**
174 * Quick Replace
175 *
176 * Function to do a quick search/replace of the content.
177 *
178 * @param string $content The content.
179 * @param array $options The options array.
180 * @param string $search The string to search for.
181 * @return string The updated content.
182 */
183 function ce_quick_replace( $content = '', $options = '', $search = '' ) {
184
185 $start_pos = strpos( $content, $options['opening_ident'] . $search, 0 );
186
187 while ( false !== $start_pos ) {
188
189 $end_pos = strpos( $content, $options['closing_ident'], $start_pos + 1 );
190
191 if ( false !== $end_pos ) {
192 $url = substr( $content, $start_pos + strlen( $options['opening_ident'] ), $end_pos - ( $start_pos + strlen( $options['opening_ident'] ) ) );
193 $file = ce_get_file( $url );
194 if ( false === $file ) {
195 $file = ce_report_error( __( 'File could not be fetched', 'simple-embed-code' ), 'Code Embed', false );
196 }
197 $content = str_replace( $options['opening_ident'] . $url . $options['closing_ident'], wp_kses_post( $file ), $content );
198 $start_pos = strpos( $content, $options['opening_ident'] . $search, 0 );
199 } else {
200 $start_pos = strpos( $content, $options['opening_ident'] . $search, $start_pos + 1 );
201 }
202 }
203
204 return $content;
205 }
206
207 /**
208 * Generate Embed Code
209 *
210 * Function to generate the embed code that will be output.
211 *
212 * @param string $html The embed code (required).
213 * @param bool $responsive Responsive output required? (optional).
214 * @param string|bool $max_width Maximum width of responsive output (optional).
215 * @return string The embed code.
216 */
217 function ce_generate_code( $html, $responsive = false, $max_width = false ) {
218
219 $code = '';
220
221 if ( false !== $max_width && '' !== $max_width ) {
222 $code .= '<div style="width: ' . $max_width . 'px; max-width: 100%">';
223 }
224
225 if ( $responsive ) {
226 $code .= '<div class="ce-video-container">';
227 }
228
229 $code .= $html;
230
231 if ( $responsive ) {
232 $code .= '</div>';
233 }
234
235 if ( false !== $max_width && '' !== $max_width ) {
236 $code .= '</div>';
237 }
238
239 return $code;
240 }
241
242 /**
243 * Get the Global Embed Code
244 *
245 * Function to look for and, if available, return the global embed code.
246 *
247 * @uses ce_report_error Generate an error message
248 *
249 * @param string $ident The embed code opening identifier.
250 * @param string $suffix The embed code suffix.
251 * @param bool $allow_raw Whether the current post's author may output raw HTML.
252 * @return string The embed code (or error).
253 */
254 function ce_get_embed_code( $ident, $suffix, $allow_raw = false ) {
255
256 // Meta was not found in the current post, so look across the meta table.
257
258 $meta_name = $ident . $suffix;
259 global $wpdb;
260 $meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_value, post_title, ID FROM $wpdb->postmeta INNER JOIN $wpdb->posts ON post_id = ID WHERE BINARY meta_key = %s AND post_status = 'publish' ORDER BY ID", $meta_name ) ); // @codingStandardsIgnoreLine -- requires the latest data when called, so caching is inappropriate
261
262 $total_records = count( $meta );
263
264 if ( 0 < $total_records ) {
265
266 // Results were found - count how many distinct embed code values exist.
267
268 $records = count( array_unique( wp_list_pluck( $meta, 'meta_value' ) ) );
269
270 if ( 1 === $records ) {
271
272 // Only one unique code result returned, so assume this is the global embed.
273 $value = is_string( $meta[0]->meta_value ) ? $meta[0]->meta_value : '';
274 if ( '' !== $value && $allow_raw ) {
275 $html = $value;
276 } else {
277 $html = wp_kses_post( $value );
278 }
279 } else {
280
281 // More than one unique code result returned, so output the list of posts.
282
283 /* translators: %1$s: the embed code name, %2$d: the number of pieces of embed code being stored with that name, %3$d: the number of posts using that embed name */
284 $error = sprintf( __( 'Cannot use %1$s as a global code as it is being used to store %2$d unique pieces of code in %3$d posts', 'simple-embed-code' ), $meta_name, $records, $total_records );
285 $html = ce_report_error( $error, 'Code Embed', false );
286 }
287 } else {
288
289 // No meta code was found, so write out an error.
290
291 /* translators: %s: the name of the embed */
292 $html = ce_report_error( sprintf( __( 'No embed code was found for %s', 'simple-embed-code' ), $meta_name ), 'Code Embed', false );
293
294 }
295 return $html;
296 }
297
298 /**
299 * Fetch a file
300 *
301 * Use WordPress API to fetch a file and check the results.
302 *
303 * @param string $filein File name to fetch.
304 * @return string|false File contents as a string, or false on failure.
305 */
306 function ce_get_file( $filein ) {
307
308 if ( function_exists( 'vip_safe_wp_remote_get' ) ) {
309
310 $response = vip_safe_wp_remote_get( $filein, '', 3, 3 );
311
312 } else {
313
314 $response = wp_safe_remote_get( $filein, array( 'timeout' => 3 ) ); // @codingStandardsIgnoreLine -- for non-VIP environments
315
316 }
317
318 if ( is_array( $response ) ) {
319
320 return $response['body'];
321
322 } else {
323
324 return false;
325
326 }
327 }
328
329 /**
330 * Report an error
331 *
332 * Function to report an error.
333 *
334 * @param string $error Error message.
335 * @param string $plugin_name The name of the plugin.
336 * @param bool $echo_out True to echo the error, false to return it.
337 * @return string The error message (empty string when echoing, or when the viewer cannot edit posts).
338 */
339 function ce_report_error( $error, $plugin_name, $echo_out = true ) {
340
341 $output = '<p role="alert" style="color: #b91c1c; font-weight: bold;">' . esc_html( $plugin_name ) . ': ' . esc_html( $error ) . "</p>\n";
342
343 // Plugin errors are only ever surfaced to users who can edit posts; visitors see nothing.
344 if ( ! current_user_can( 'edit_posts' ) ) {
345 return '';
346 }
347
348 if ( $echo_out ) {
349 echo $output; // @codingStandardsIgnoreLine -- being escaped above so this is a false positive
350 return '';
351 }
352
353 return $output;
354 }
355