PluginProbe
Embed Privacy / 1.1.3
Embed Privacy v1.1.3
1.14.0 1.13.0 trunk 0.1 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.10.1 1.10.10 1.10.2 1.10.3 1.10.4 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9 1.11.0 1.11.1 1.11.2 All 68 releases
embed-privacy / inc / class-embed-privacy.php

class-embed-privacy.php in Embed Privacy 1.1.3, at inc/class-embed-privacy.php

432 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace epiphyt\Embed_Privacy;
3 use function add_action;
4 use function add_filter;
5 use function addslashes;
6 use function apply_filters;
7 use function defined;
8 use function dirname;
9 use function esc_attr;
10 use function esc_html;
11 use function esc_html__;
12 use function file_exists;
13 use function filemtime;
14 use function function_exists;
15 use function get_sites;
16 use function htmlentities;
17 use function is_admin;
18 use function is_plugin_active_for_network;
19 use function json_decode;
20 use function load_plugin_textdomain;
21 use function md5;
22 use function plugin_basename;
23 use function plugin_dir_path;
24 use function plugin_dir_url;
25 use function preg_match_all;
26 use function sanitize_text_field;
27 use function sanitize_title;
28 use function sprintf;
29 use function str_replace;
30 use function strpos;
31 use function strtolower;
32 use function switch_to_blog;
33 use function wp_enqueue_script;
34 use function wp_enqueue_style;
35 use function wp_generate_uuid4;
36 use function wp_json_encode;
37 use function wp_unslash;
38 use const DEBUG_MODE;
39 use const EPI_EMBED_PRIVACY_BASE;
40 use const EPI_EMBED_PRIVACY_URL;
41
42 /**
43 * Two click embed main class.
44 *
45 * @author Epiphyt
46 * @license GPL2
47 * @package epiphyt\Embed_Privacy
48 */
49 class Embed_Privacy {
50 /**
51 * @since 1.1.0
52 */
53 const IFRAME_REGEX = '/<iframe(.*?)src="([^"]+)"([^>]*)>((?!<\/iframe).)*<\/iframe>/ms';
54
55 /**
56 * @var \epiphyt\Embed_Privacy\Embed_Privacy
57 */
58 public static $instance;
59
60 /**
61 * @var string The full path to the main plugin file
62 */
63 public $plugin_file = '';
64
65 /**
66 * @var bool Determine if we use the cache
67 */
68 private $usecache = false;
69
70 /**
71 * @var array The supported media providers
72 */
73 public $embed_providers = [
74 '.amazon.' => 'Amazon Kindle',
75 '.amzn.' => 'Amazon Kindle',
76 'a.co' => 'Amazon Kindle',
77 'z.cn' => 'Amazon Kindle',
78 'animoto.com' => 'Animoto',
79 'cloudup.com' => 'Cloudup',
80 'crowdsignal.com' => 'Crowdsignal',
81 'dailymotion.com' => 'DailyMotion',
82 'facebook.com' => 'Facebook',
83 'flickr.com' => 'Flickr',
84 'funnyordie.com' => 'Funny Or Die',
85 'imgur.com' => 'Imgur',
86 'instagram.com' => 'Instagram',
87 'issuu.com' => 'Issuu',
88 'kickstarter.com' => 'Kickstarter',
89 'meetup.com' => 'Meetup',
90 'mixcloud.com' => 'Mixcloud',
91 'photobucket.com' => 'Photobucket',
92 'poll.fm' => 'Crowdsignal',
93 'polldaddy.com' => 'Crowdsignal',
94 'reddit.com' => 'Reddit',
95 'reverbnation.com' => 'ReverbNation',
96 'scribd.com' => 'Scribd',
97 'sketchfab.com' => 'Sketchfab',
98 'slideshare.net' => 'SlideShare',
99 'smugmug.com' => 'SmugMug',
100 'soundcloud.com' => 'SoundCloud',
101 'speakerdeck.com' => 'Speaker Deck',
102 'spotify.com' => 'Spotify',
103 'survey.fm' => 'Crowdsignal',
104 'tiktok.com' => 'TikTok',
105 'ted.com' => 'TED',
106 'tumblr.com' => 'Tumblr',
107 'twitter.com' => 'Twitter',
108 'videopress.com' => 'VideoPress',
109 'vimeo.com' => 'Vimeo',
110 'wordpress.org/plugins' => 'WordPress.org',
111 'wordpress.tv' => 'WordPress.tv',
112 'youtu.be' => 'YouTube',
113 'youtube.com' => 'YouTube',
114 ];
115
116 /**
117 * Embed Privacy constructor.
118 */
119 public function __construct() {
120 // actions
121 add_action( 'init', [ $this, 'load_textdomain' ] );
122 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
123
124 // assign variables
125 $this->usecache = ! is_admin();
126
127 // filters
128 if ( ! $this->usecache ) {
129 // set ttl to 0 in admin
130 add_filter( 'oembed_ttl', function( $time ) {
131 return 0;
132 }, 10, 1 );
133 }
134
135 add_filter( 'do_shortcode_tag', [ $this, 'replace_google_maps' ] );
136 add_filter( 'embed_oembed_html', [ $this, 'replace_embeds' ], 10, 3 );
137 add_filter( 'embed_privacy_widget_output', [ $this, 'replace_google_maps' ] );
138 add_filter( 'the_content', [ $this, 'replace_google_maps' ] );
139 }
140
141 /**
142 * Embeds are cached in the postmeta database table and need to be removed
143 * whenever the plugin will be enabled or disabled.
144 */
145 public function clear_embed_cache() {
146 global $wpdb;
147
148 // the query to delete cache
149 $query = "DELETE FROM " . $wpdb->get_blog_prefix() . "postmeta
150 WHERE meta_key LIKE '%_oembed_%'";
151
152 if ( is_plugin_active_for_network( 'embed-privacy/embed-privacy.php' ) ) {
153 // on networks we need to iterate through every site
154 $sites = get_sites( 99999 );
155
156 foreach ( $sites as $site ) {
157 switch_to_blog( $site );
158
159 $wpdb->query( $query );
160 }
161 }
162 else {
163 $wpdb->query( $query );
164 }
165 }
166
167 /**
168 * Enqueue our assets for the frontend.
169 */
170 public function enqueue_assets() {
171 $suffix = ( defined( 'DEBUG_MODE' ) && DEBUG_MODE ? '' : '.min' );
172 $css_file = EPI_EMBED_PRIVACY_BASE . 'assets/style/embed-privacy' . $suffix . '.css';
173 $css_file_url = EPI_EMBED_PRIVACY_URL . 'assets/style/embed-privacy' . $suffix . '.css';
174
175 wp_enqueue_style( 'embed-privacy', $css_file_url, [], filemtime( $css_file ) );
176
177 if ( ! $this->is_amp() ) {
178 $js_file = EPI_EMBED_PRIVACY_BASE . 'assets/js/embed-privacy' . $suffix . '.js';
179 $js_file_url = EPI_EMBED_PRIVACY_URL . 'assets/js/embed-privacy' . $suffix . '.js';
180
181 wp_enqueue_script( 'embed-privacy', $js_file_url, [], filemtime( $js_file ) );
182 }
183 }
184
185 /**
186 * Get a unique instance of the class.
187 *
188 * @since 1.1.0
189 *
190 * @return \epiphyt\Embed_Privacy\Embed_Privacy The single instance of this class
191 */
192 public static function get_instance() {
193 if ( self::$instance === null ) {
194 self::$instance = new self();
195 }
196
197 return self::$instance;
198 }
199
200 /**
201 * Determine whether this is an AMP response.
202 * Note that this must only be called after the parse_query action.
203 *
204 * @return bool True if the current page is an AMP page, false otherwise
205 */
206 private function is_amp() {
207 return function_exists( 'is_amp_endpoint' ) && is_amp_endpoint();
208 }
209
210 /**
211 * Load the translation files.
212 */
213 public function load_textdomain() {
214 load_plugin_textdomain( 'embed-privacy', false, dirname( plugin_basename( $this->plugin_file ) ) . '/languages' );
215 }
216
217 /**
218 * Replace embeds with a container and hide the embed with an HTML comment.
219 *
220 * @version 1.0.1
221 *
222 * @param string $output The original output
223 * @param string $url The URL to the embed
224 * @param array $args Additional arguments of the embed
225 * @return string The updated embed code
226 */
227 public function replace_embeds( $output, $url, $args ) {
228 // don't do anything in admin
229 if ( ! $this->usecache ) return $output;
230
231 $embed_provider = '';
232 $embed_provider_lowercase = '';
233
234 // get embed provider name
235 foreach ( $this->embed_providers as $url_part => $name ) {
236 // save name of provider and stop loop
237 if ( strpos( $url, $url_part ) !== false ) {
238 $embed_provider = $name;
239 $embed_provider_lowercase = str_replace( [ ' ', '.' ], '-', strtolower( $name ) );
240 break;
241 }
242 }
243
244 // replace youtube.com to youtube-nocookie.com
245 $output = str_replace( 'youtube.com', 'youtube-nocookie.com', $output );
246
247 // check if cookie is set
248 if ( $embed_provider_lowercase !== 'default' && $this->is_always_active_provider( $embed_provider_lowercase ) ) {
249 return $output;
250 }
251
252 // add two click to markup
253 return $this->get_output_template( $embed_provider, $embed_provider_lowercase, $output, $args );
254 }
255
256 /**
257 * Replace Google Maps iframes.
258 *
259 * @since 1.1.0
260 *
261 * @param string $content The post content
262 * @return string The post content
263 */
264 public function replace_google_maps( $content ) {
265 preg_match_all( self::IFRAME_REGEX, $content, $matches );
266
267 if ( empty( $matches ) || empty( $matches[0] ) ) {
268 return $content;
269 }
270
271 $embed_provider = 'Google Maps';
272 $embed_provider_lowercase = 'google-maps';
273
274 // check if cookie is set
275 if ( $this->is_always_active_provider( $embed_provider_lowercase ) ) {
276 return $content;
277 }
278
279 foreach ( $matches[0] as $match ) {
280 if ( strpos( $match, 'google.com/maps' ) === false ) {
281 continue;
282 }
283
284 $overlay_output = $this->get_output_template( $embed_provider, $embed_provider_lowercase, $match );
285 $content = str_replace( $match, $overlay_output, $content );
286 }
287
288 return $content;
289 }
290
291 /**
292 * Get the Embed Privacy cookie.
293 *
294 * @return array|mixed|object|string The content of the cookie
295 */
296 private function get_cookie() {
297 if ( empty( $_COOKIE['embed-privacy'] ) ) {
298 return '';
299 }
300
301 $object = json_decode( sanitize_text_field( wp_unslash( $_COOKIE['embed-privacy'] ) ) );
302
303 return $object;
304 }
305
306 /**
307 * Output a complete template of the overlay.
308 *
309 * @since 1.1.0
310 *
311 * @param string $embed_provider The embed provider
312 * @param string $embed_provider_lowercase The embed provider without spaces and in lowercase
313 * @param string $output The output before replacing it
314 * @param array $args Additional arguments
315 * @return string The overlay template
316 */
317 public function get_output_template( $embed_provider, $embed_provider_lowercase, $output, $args = [] ) {
318 // add two click to markup
319 $embed_provider_lowercase = sanitize_title( $embed_provider_lowercase );
320 $embed_class = 'embed-' . ( ! empty( $embed_provider_lowercase ) ? $embed_provider_lowercase : 'default' );
321 $embed_classes = $embed_class;
322
323 if ( ! empty( $args['align'] ) ) {
324 $embed_classes .= ' align' . $args['align'];
325 }
326
327 // display embed provider logo
328 $background_path = plugin_dir_path( $this->plugin_file ) . 'assets/images/embed-' . $embed_provider_lowercase . '.png';
329 $background_url = plugin_dir_url( $this->plugin_file ) . 'assets/images/embed-' . $embed_provider_lowercase . '.png';
330
331 /**
332 * Filter the path to the background image.
333 *
334 * @param string $background_path The default background path
335 * @param string $embed_provider_lowercase The current embed provider in lowercase
336 */
337 $background_path = apply_filters( "embed_privacy_logo_path_{$embed_provider_lowercase}", $background_path, $embed_provider_lowercase );
338
339 /**
340 * Filter the URL to the background image.
341 *
342 * @param string $background_url The default background URL
343 * @param string $embed_provider_lowercase The current embed provider in lowercase
344 */
345 $background_url = apply_filters( "embed_privacy_logo_url_{$embed_provider_lowercase}", $background_url, $embed_provider_lowercase );
346
347 $embed_md5 = md5( $output . wp_generate_uuid4() );
348 $width = ( ! empty( $args['width'] ) ? 'width: ' . $args['width'] . 'px;' : '' );
349 $markup = '<div class="embed-privacy-container ' . esc_attr( $embed_classes ) . '" id="oembed_' . esc_attr( $embed_md5 ) . '">';
350 $markup .= '<div class="embed-privacy-overlay" style="' . esc_attr( $width ) . '">';
351 $markup .= '<div class="embed-privacy-inner">';
352 $markup .= ( file_exists( $background_path ) ? '<div class="embed-privacy-logo"></div>' : '' );
353 $content = '<p>';
354
355 if ( ! empty( $embed_provider ) ) {
356 /* translators: the embed provider */
357 $content .= sprintf( esc_html__( 'Click here to display content from %s', 'embed-privacy' ), esc_html( $embed_provider ) );
358 }
359 else {
360 $content .= esc_html__( 'Click here to display content from external service', 'embed-privacy' );
361 }
362
363 $content .= '</p>';
364
365 $checkbox_id = 'embed-privacy-store-' . $embed_provider_lowercase . '-' . $embed_md5;
366
367 if ( $embed_provider_lowercase !== 'default' ) {
368 /* translators: the embed provider */
369 $content .= '<p><label for="' . esc_attr( $checkbox_id ) . '" class="embed-privacy-label" data-embed-provider="' . esc_attr( $embed_provider_lowercase ) . '"><input id="' . esc_attr( $checkbox_id ) . '" type="checkbox" value="1"> ' . sprintf( esc_html__( 'Always display content from %s', 'embed-privacy' ), esc_html( $embed_provider ) ) . '</label></p>';
370 }
371
372 /**
373 * Filter the content of the embed overlay.
374 *
375 * @param string $content The content
376 * @param string $embed_provider The embed provider of this embed
377 */
378 $content = apply_filters( 'embed_privacy_content', $content, $embed_provider );
379
380 $markup .= $content;
381 $markup .= '</div>';
382 $markup .= '</div>';
383 $markup .= '<div class="embed-privacy-content"><script>var _oembed_' . $embed_md5 . ' = \'' . addslashes( wp_json_encode( [ 'embed' => htmlentities( $output ) ] ) ) . '\';</script></div>';
384 $markup .= '</div>';
385
386 // display only if file exists
387 if ( file_exists( $background_path ) ) {
388 $version = filemtime( $background_path );
389 $markup .= '
390 <style>
391 .' . $embed_class . ' .embed-privacy-logo {
392 background-image: url(' . $background_url . '?v=' . $version . ');
393 }
394 </style>
395 ';
396 }
397
398 return $markup;
399 }
400
401 /**
402 * Check if a provider is always active.
403 *
404 * @since 1.1.0
405 *
406 * @param string $provider The embed provider in lowercase
407 * @return bool True if provider is always active, false otherwise
408 */
409 public function is_always_active_provider( $provider ) {
410 $cookie = $this->get_cookie();
411
412 if ( isset( $cookie->{$provider} ) && $cookie->{$provider} === true ) {
413 return true;
414 }
415
416 return false;
417 }
418
419 /**
420 * Set the plugin file.
421 *
422 * @since 1.1.0
423 *
424 * @param string $file The path to the file
425 */
426 public function set_plugin_file( $file ) {
427 if ( file_exists( $file ) ) {
428 $this->plugin_file = $file;
429 }
430 }
431 }
432