PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.4.3
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.4.3
4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / EmbedPress / Core.php
embedpress / EmbedPress Last commit date
AMP 2 years ago Analytics 9 months ago Elementor 8 months ago Ends 8 months ago Gutenberg 9 months ago Includes 8 months ago Plugins 1 year ago Providers 8 months ago ThirdParty 9 months ago AutoLoader.php 2 years ago Compatibility.php 2 years ago Core.php 8 months ago CoreLegacy.php 9 months ago DisablerLegacy.php 2 years ago Loader.php 2 years ago RestAPI.php 1 year ago Shortcode.php 8 months ago index.html 7 years ago simple_html_dom.php 4 years ago
Core.php
893 lines
1 <?php
2
3 namespace EmbedPress;
4
5 use EmbedPress\Ends\Back\Handler as EndHandlerAdmin;
6 use EmbedPress\Ends\Back\Settings\EmbedpressSettings;
7 use EmbedPress\Ends\Front\Handler as EndHandlerPublic;
8 use EmbedPress\Includes\Traits\Shared;
9
10
11 (defined('ABSPATH') && defined('EMBEDPRESS_IS_LOADED')) or die("No direct script access allowed.");
12
13 /**
14 * Entity that glues together all pieces that the plugin is made of, for WordPress 5+.
15 *
16 * @package EmbedPress
17 * @author EmbedPress <help@embedpress.com>
18 * @copyright Copyright (C) 2021 WPDeveloper. All rights reserved.
19 * @license GPLv3 or later
20 * @since 1.0.0
21 */
22 class Core
23 {
24 use Shared;
25
26 /**
27 * The name of the plugin.
28 *
29 * @since 1.0.0
30 * @access protected
31 *
32 * @var string $pluginName The name of the plugin.
33 */
34 protected $pluginName;
35
36 /**
37 * The version of the plugin.
38 *
39 * @since 1.0.0
40 * @access protected
41 *
42 * @var string $pluginVersion The version of the plugin.
43 */
44 protected $pluginVersion;
45
46 /**
47 * An instance of the plugin loader.
48 *
49 * @since 1.0.0
50 * @access protected
51 *
52 * @var Loader $pluginVersion The version of the plugin.
53 */
54 protected $loaderInstance;
55
56 /**
57 * An associative array storing all registered/active EmbedPress plugins and their namespaces.
58 *
59 * @since 1.4.0
60 * @access private
61 * @static
62 *
63 * @var array
64 */
65 private static $plugins = [];
66
67 /**
68 * Initialize the plugin and set its properties.
69 *
70 * @return void
71 * @since 1.0.0
72 *
73 */
74 public function __construct()
75 {
76 $this->pluginName = EMBEDPRESS_PLG_NAME;
77 $this->pluginVersion = EMBEDPRESS_VERSION;
78
79 $this->loaderInstance = new Loader();
80
81 add_action('in_admin_header', [$this, 'remove_admin_notice'], 99);
82 add_action('ep_admin_notices', [$this, 'embedpress_admin_notice']);
83 add_action('ep_admin_notices', [$this, 'admin_notice']);
84
85 add_filter('upload_mimes', [$this, 'extended_mime_types']);
86
87 add_action('wp_mail_failed', [$this, 'capture_mail_error'], 10, 1);
88 }
89
90 /**
91 * Method that retrieves the plugin name.
92 *
93 * @return string
94 * @since 1.0.0
95 *
96 */
97 public function getPluginName()
98 {
99 return $this->pluginName;
100 }
101
102 /**
103 * Method that retrieves the plugin version.
104 *
105 * @return string
106 * @since 1.0.0
107 *
108 */
109 public function getPluginVersion()
110 {
111 return $this->pluginVersion;
112 }
113
114 /**
115 * Method that retrieves the loader instance.
116 *
117 * @return Loader
118 * @since 1.0.0
119 *
120 */
121 public function getLoader()
122 {
123 return $this->loaderInstance;
124 }
125
126 /**
127 * Method responsible to connect all required hooks in order to make the plugin work.
128 *
129 * @return void
130 * @since 1.0.0
131 *
132 */
133 public function initialize()
134 {
135 global $wp_actions;
136 add_filter('oembed_providers', [$this, 'addOEmbedProviders']);
137 add_action('rest_api_init', [$this, 'registerOEmbedRestRoutes']);
138
139 // just disabled rating and feedback
140 // add_action('rest_api_init', [$this, 'register_feedback_email_endpoint']);
141
142
143 $this->start_plugin_tracking();
144
145 if (is_admin()) {
146 new EmbedpressSettings();
147
148 add_action('init', [$this, 'admin_notice']);
149
150 add_filter(
151 'plugin_action_links_embedpress/embedpress.php',
152 ['\\EmbedPress\\Core', 'handleActionLinks'],
153 10,
154 2
155 );
156
157 // Old enqueue handlers removed - now handled by AssetManager
158 add_action('wp_ajax_embedpress_notice_dismiss', ['\\EmbedPress\\Ends\\Back\\Handler', 'embedpress_notice_dismiss']);
159 new EndHandlerAdmin($this->getPluginName(), $this->getPluginVersion());
160 // Asset enqueuing now handled by AssetManager - keeping only non-asset functionality
161 } else {
162 // Asset enqueuing now handled by AssetManager - keeping only non-asset functionality
163 new EndHandlerPublic($this->getPluginName(), $this->getPluginVersion());
164 }
165
166 // Add support for embeds on AMP pages
167 add_filter('pp_embed_parsed_content', ['\\EmbedPress\\AMP\\EmbedHandler', 'processParsedContent'], 10, 3);
168
169 // Add support for our embeds on Beaver Builder. Without this it only run the native embeds.
170 add_filter(
171 'fl_builder_before_render_shortcodes',
172 ['\\EmbedPress\\ThirdParty\\BeaverBuilder', 'before_render_shortcodes']
173 );
174 $this->loaderInstance->run();
175 }
176
177 /**
178 * Initialize minimal plugin functionality without script handlers
179 * Used when the new block system is active to avoid conflicts
180 *
181 * @return void
182 * @since 4.2.7
183 */
184 public function initialize_minimal()
185 {
186
187 add_filter('oembed_providers', [$this, 'addOEmbedProviders']);
188 add_action('rest_api_init', [$this, 'registerOEmbedRestRoutes']);
189
190 // just disabled rating and feedback
191 // add_action('rest_api_init', [$this, 'register_feedback_email_endpoint']);
192
193 $this->start_plugin_tracking();
194
195 // Skip the admin and frontend handlers that enqueue scripts
196 // Only initialize core functionality
197
198
199 // Add support for embeds on AMP pages
200 add_filter('pp_embed_parsed_content', ['\\EmbedPress\\AMP\\EmbedHandler', 'processParsedContent'], 10, 3);
201
202 // Add support for our embeds on Beaver Builder
203 add_filter(
204 'fl_builder_before_render_shortcodes',
205 ['\\EmbedPress\\ThirdParty\\BeaverBuilder', 'before_render_shortcodes']
206 );
207
208 $this->loaderInstance->run();
209 }
210
211 /**
212 * @param $providers
213 *
214 * @return mixed
215 */
216 public function addOEmbedProviders($providers)
217 {
218 $newProviders = [
219 // Viddler
220 '#https?://(.+\.)?viddler\.com/v/.+#i' => 'viddler',
221
222 // Deviantart.com (http://www.deviantart.com)
223 // '#https?://(.+\.)?deviantart\.com/art/.+#i' => 'devianart',
224 // '#https?://(.+\.)?deviantart\.com/.+#i' => 'devianart',
225 // '#https?://(.+\.)?deviantart\.com/.*/d.+#i' => 'devianart',
226 // '#https?://(.+\.)?fav\.me/.+#i' => 'devianart',
227 // '#https?://(.+\.)?sta\.sh/.+#i' => 'devianart',
228
229 // chirbit.com (http://www.chirbit.com/)
230 //'#https?://(.+\.)?chirb\.it/.+#i' => 'chirbit',
231
232
233 // nfb.ca (http://www.nfb.ca/)
234 //'#https?://(.+\.)?nfb\.ca/film/.+#i' => 'nfb',
235
236 // Dotsub (http://dotsub.com/)
237 //'#https?://(.+\.)?dotsub\.com/view/.+#i' => 'dotsub',
238
239 // Rdio (http://rdio.com/)
240 '#https?://(.+\.)?rdio\.com/(artist|people)/.+#i' => 'rdio',
241
242 // Sapo Videos (http://videos.sapo.pt)
243 //'#https?://(.+\.)?videos\.sapo\.pt/.+#i' => 'sapo',
244
245 // Official FM (http://official.fm)
246 '#https?://(.+\.)?official\.fm/(tracks|playlists)/.+#i' => 'officialfm',
247
248 // HuffDuffer (http://huffduffer.com)
249 //'#https?://(.+\.)?huffduffer\.com/.+#i' => 'huffduffer',
250
251 // Shoudio (http://shoudio.com)
252 //'#https?://(.+\.)?shoudio\.(com|io)/.+#i' => 'shoudio',
253
254 // Moby Picture (http://www.mobypicture.com)
255 '#https?://(.+\.)?mobypicture\.com/user/.+/view/.+#i' => 'mobypicture',
256 '#https?://(.+\.)?moby\.to/.+#i' => 'mobypicture',
257
258 // 23HQ (http://www.23hq.com)
259 //'#https?://(.+\.)?23hq\.com/.+/photo/.+#i' => '23hq',
260
261 // Cacoo (https://cacoo.com)
262 '#https?://(.+\.)?cacoo\.com/diagrams/.+#i' => 'cacoo',
263
264 // Dipity (http://www.dipity.com)
265 '#https?://(.+\.)?dipity\.com/.+#i' => 'dipity',
266
267 // Roomshare (http://roomshare.jp)
268 //'#https?://(.+\.)?roomshare\.jp/(en/)?post/.+#i' => 'roomshare',
269
270 // Crowd Ranking (http://crowdranking.com)
271 '#https?://(.+\.)?c9ng\.com/.+#i' => 'crowd',
272
273 // CircuitLab (https://www.circuitlab.com/)
274 //'#https?://(.+\.)?circuitlab\.com/circuit/.+#i' => 'circuitlab',
275
276 // Coub (http://coub.com/)
277 //'#https?://(.+\.)?coub\.com/(view|embed)/.+#i' => 'coub',
278
279 // Ustream (http://www.ustream.tv)
280 //'#https?://(.+\.)?ustream\.(tv|com)/.+#i' => 'ustream',
281
282 // Daily Mile (http://www.dailymile.com)
283 '#https?://(.+\.)?dailymile\.com/people/.+/entries/.+#i' => 'daily',
284
285 // Sketchfab (http://sketchfab.com)
286 '#https?://(.+\.)?sketchfab\.com/models/.+#i' => 'sketchfab',
287 '#https?://(.+\.)?sketchfab\.com/.+/folders/.+#i' => 'sketchfab',
288
289 // AudioSnaps (http://audiosnaps.com)
290 '#https?://(.+\.)?audiosnaps\.com/k/.+#i' => 'audiosnaps',
291
292 // RapidEngage (https://rapidengage.com)
293 '#https?://(.+\.)?rapidengage\.com/s/.+#i' => 'rapidengage',
294
295 // Getty Images (http://www.gettyimages.com/)
296 //'#https?://(.+\.)?gty\.im/.+#i' => 'gettyimages',
297 //'#https?://(.+\.)?gettyimages\.com/detail/photo/.+#i' => 'gettyimages',
298
299 // amCharts Live Editor (http://live.amcharts.com/)
300 //'#https?://(.+\.)?live\.amcharts\.com/.+#i' => 'amcharts',
301
302 // Infogram (https://infogr.am/)
303 //'#https?://(.+\.)?infogr\.am/.+#i' => 'infogram',
304 //(https://infogram.com/)
305 //'#https?://(.+\.)?infogram\.com/.+#i' => 'infogram',
306
307 // ChartBlocks (http://www.chartblocks.com/)
308 //'#https?://(.+\.)?public\.chartblocks\.com/c/.+#i' => 'chartblocks',
309
310 // ReleaseWire (http://www.releasewire.com/)
311 //'#https?://(.+\.)?rwire\.com/.+#i' => 'releasewire',
312
313 // ShortNote (https://www.shortnote.jp/)
314 //'#https?://(.+\.)?shortnote\.jp/view/notes/.+#i' => 'shortnote',
315
316 // EgliseInfo (http://egliseinfo.catholique.fr/)
317 '#https?://(.+\.)?egliseinfo\.catholique\.fr/.+#i' => 'egliseinfo',
318
319 // Silk (http://www.silk.co/)
320 '#https?://(.+\.)?silk\.co/explore/.+#i' => 'silk',
321 '#https?://(.+\.)?silk\.co/s/embed/.+#i' => 'silk',
322
323 // http://bambuser.com
324 '#https?://(.+\.)?bambuser\.com/v/.+#i' => 'bambuser',
325
326 // https://clyp.it
327 //'#https?://(.+\.)?clyp\.it/.+#i' => 'clyp',
328
329 // https://gist.github.com
330 // '#https?://(.+\.)?gist\.github\.com/.+#i' => 'github',
331
332 // https://portfolium.com
333 //'#https?://(.+\.)?portfolium\.com/.+#i' => 'portfolium',
334
335 // http://rutube.ru
336 '#https?://(.+\.)?rutube\.ru/video/.+#i' => 'rutube',
337
338 // http://www.videojug.com
339 '#https?://(.+\.)?videojug\.com/.+#i' => 'videojug',
340
341 // https://vine.com
342 //'#https?://(.+\.)?vine\.co/v/.+#i' => 'vine',
343
344 // Google Shortened Url
345 '#https?://(.+\.)?goo\.gl/.+#i' => 'google',
346
347 // Google Maps
348 //'#https?://(.+\.)?google\.com/maps/.+#i' => 'googlemaps',
349 //'#https?://(.+\.)?maps\.google\.com/.+#i' => 'googlemaps',
350
351 // Google Docs
352 //'#https?://(.+\.)?docs\.google\.com/(.+/)?(document|presentation|spreadsheets|forms|drawings)/.+#i' => 'googledocs',
353
354 // Twitch.tv
355 //'#https?://(.+\.)?twitch\.tv/.+#i' => 'twitch',
356
357 // Giphy
358 //'#https?://(.+\.)?giphy\.com/gifs/.+#i' => 'giphy',
359 //'#https?://(.+\.)?i\.giphy\.com/.+#i' => 'giphy',
360 //'#https?://(.+\.)?gph\.is/.+#i' => 'giphy',
361
362 // Wistia
363 //'#https?://(.+\.)?wistia\.com/medias/.+#i' => 'wistia',
364 //'#https?://(.+\.)?fast\.wistia\.com/embed/medias/.+#i\.jsonp' => 'wistia',
365 ];
366
367 /**
368 * ========================================
369 * Make sure the $wp_write global is set.
370 * This fix compatibility with JetPack, Classical Editor and Disable Gutenberg. JetPack makes
371 * the oembed_providers filter be called and this activates our class too, but one dependency
372 * of the rest_url method is not loaded yet.
373 */
374 global $wp_rewrite;
375
376 if (!class_exists('\\WP_Rewrite')) {
377 $path = ABSPATH . WPINC . '/class-wp-rewrite.php';
378 if (file_exists($path)) {
379 require_once $path;
380 }
381 }
382
383 if (!is_object($wp_rewrite)) {
384 $wp_rewrite = new \WP_Rewrite();
385 $_GLOBALS['wp_write'] = $wp_rewrite;
386 }
387 /*========================================*/
388
389 foreach ($newProviders as $url => &$data) {
390 $data = [
391 rest_url('embedpress/v1/oembed/' . $data),
392 true,
393 ];
394 }
395
396 $providers = array_merge($providers, $newProviders);
397
398 return $providers;
399 }
400
401 /**
402 * Register OEmbed Rest Routes
403 */
404 public function registerOEmbedRestRoutes()
405 {
406 register_rest_route(
407 'embedpress/v1',
408 '/oembed/(?P<provider>[a-zA-Z0-9\-]+)',
409 [
410 'methods' => \WP_REST_Server::READABLE,
411 'callback' => ['\\EmbedPress\\RestAPI', 'oembed'],
412 'permission_callback' => '__return_true',
413 ]
414 );
415 register_rest_route(
416 'embedpress/v1',
417 '/oembed/(?P<provider>[a-zA-Z0-9\-]+)',
418 [
419 'methods' => \WP_REST_Server::CREATABLE,
420 'callback' => ['\\EmbedPress\\RestAPI', 'oembed'],
421 'permission_callback' => '__return_true',
422 ]
423 );
424 }
425
426 public function send_user_feedback_email($request)
427 {
428 // Ensure we have a valid REST request object
429 if (!($request instanceof \WP_REST_Request)) {
430 return new \WP_REST_Response(['message' => 'Invalid request'], 400);
431 }
432
433 // CRITICAL: Check if feedback was already sent FIRST to prevent spam
434 $is_feedback_already_sent = get_option('embedpress_feedback_submited');
435 if ($is_feedback_already_sent) {
436 return new \WP_REST_Response(['message' => 'Feedback already submitted'], 200);
437 }
438
439 // Rate limiting: Only allow one request per IP per minute
440 $user_ip = $this->get_user_ip();
441 $rate_limit_key = 'embedpress_feedback_rate_limit_' . md5($user_ip);
442 if (get_transient($rate_limit_key)) {
443 return new \WP_REST_Response(['message' => 'Too many requests. Please try again later.'], 429);
444 }
445 set_transient($rate_limit_key, 2, MINUTE_IN_SECONDS);
446
447 // Verify user is logged in and has admin capabilities
448 if (!is_user_logged_in()) {
449 return new \WP_REST_Response(['message' => 'Unauthorized. You must be logged in.'], 401);
450 }
451
452 if (!current_user_can('manage_options')) {
453 return new \WP_REST_Response(['message' => 'Forbidden. Insufficient permissions.'], 403);
454 }
455
456 // Verify nonce for CSRF protection
457 $nonce = $request->get_header('X-WP-Nonce');
458
459 if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
460 return new \WP_REST_Response(['message' => 'Invalid security token. Please refresh the page and try again.'], 403);
461 }
462
463
464 $params = $request->get_params();
465
466 // Safely extract and sanitize incoming params to avoid undefined index notices
467 $params = is_array($params) ? $params : [];
468 $user_email = isset($params['email']) ? sanitize_email($params['email']) : '';
469 $user_name = isset($params['name']) ? sanitize_text_field($params['name']) : '';
470 $user_rating = isset($params['rating']) ? intval($params['rating']) : 0;
471 $user_msg = isset($params['message']) ? sanitize_textarea_field($params['message']) : '';
472
473 // Validate rating is within acceptable range
474 if ($user_rating < 1 || $user_rating > 5) {
475 return new \WP_REST_Response(['message' => 'Invalid rating value. Must be between 1 and 5.'], 400);
476 }
477
478 // Prevent submissions with empty/invalid user data (prevents N/A spam)
479 if (empty($user_email) || !is_email($user_email)) {
480 return new \WP_REST_Response(['message' => 'Valid email address is required.'], 400);
481 }
482
483 if (empty($user_name) || strlen(trim($user_name)) < 2) {
484 return new \WP_REST_Response(['message' => 'Valid name is required.'], 400);
485 }
486
487 // If the payload is completely empty, ignore to prevent blank/spam emails
488 $has_meaningful_input = false;
489
490 if ($user_rating > 0) {
491 if ($user_rating < 5) {
492 // description required
493 if (trim($user_msg) !== '') {
494 $has_meaningful_input = true;
495 }
496 } else {
497 // rating is 5, description not required
498 $has_meaningful_input = true;
499 }
500 }
501
502 if (!$has_meaningful_input) {
503 return new \WP_REST_Response(['message' => 'No feedback content provided; ignored.'], 200);
504 }
505
506 // Prevent accidental duplicate submissions (double-clicks, quick retries)
507 $payload_hash = md5(json_encode([$user_email, $user_name, $user_rating, $user_msg]));
508 if (get_transient('embedpress_feedback_dupe_' . $payload_hash)) {
509 return new \WP_REST_Response(['message' => 'Duplicate feedback detected; already processed.'], 200);
510 }
511 set_transient('embedpress_feedback_dupe_' . $payload_hash, 1, 5 * MINUTE_IN_SECONDS);
512
513 $email_html = $user_email ? '<a href="mailto:' . esc_attr($user_email) . '">' . esc_html($user_email) . '</a>' : 'N/A';
514 $rating_html = $user_rating ? esc_html($user_rating) . ' ⭐️' : 'N/A';
515 $message_html = $user_msg !== '' ? nl2br(esc_html($user_msg)) : 'N/A';
516
517
518 $site_name = get_bloginfo('name');
519 $site_url = get_site_url();
520 $admin_email = get_option('admin_email');
521 $wp_version = get_bloginfo('version');
522
523 $admin_user = get_user_by('ID', 1);
524 if ($admin_user) {
525 $first_name = get_user_meta($admin_user->ID, 'first_name', true);
526 $last_name = get_user_meta($admin_user->ID, 'last_name', true);
527
528 $admin_full_name = trim("$first_name $last_name");
529
530 // Fallback to display name if full name is not set
531 if (empty($admin_full_name)) {
532 $admin_full_name = $admin_user->display_name;
533 }
534 } else {
535 $admin_full_name = 'Unknown';
536 }
537
538 $to = 'akash@wpdeveloper.com, rasel@wpdeveloper.com, nahid@wpdeveloper.com, md-nahid-hasan@wpdeveloper.com'; // Replace with the recipient's email
539 $subject = '[IMPORTANT] New feedback received from an EmbedPress user.';
540
541 // HTML Email Template
542 $message = '<html><body style="font-family: Arial, sans-serif; padding: 20px;">';
543 $message .= '<div style="max-width: 600px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: auto;">';
544 $message .= '<div style="text-align: center; padding-bottom: 20px; border-bottom: 1px solid #ddd">';
545 $message .= '<img src="https://embedpress.com/wp-content/uploads/2025/03/logo.png" alt="EmbedPress" style="max-width: 150px;">';
546 $message .= '</div>';
547 $message .= '<h2 style="font-family: system-ui; color: #333; text-align: center;">Feedback Overview</h2>';
548 $message .= '<table style="font-family: system-ui; width: 100%; border-collapse: collapse; border: 1px solid #ddd">';
549
550 // Email
551 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Email :</td>';
552 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd;">' . $email_html . '</td></tr>';
553
554 // Rating
555 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Rating :</td>';
556 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd;">' . $rating_html . '</td></tr>';
557
558 // User
559 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Name :</td>';
560 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd; font-weight: 500;">' . esc_html($admin_full_name) . '</td></tr>';
561
562 // Pack
563 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Site Url :</td>';
564 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd;"><a target="_blank" href="' . esc_url($site_url) . '" style="color: blue;">' . esc_html($site_url) . '</a></td></tr>';
565
566 // Feedback
567 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; display: flex;">Feedback :</td>';
568 $message .= '<td style="padding: 10px;">' . $message_html . '</td></tr>';
569
570 $message .= '</table>';
571 $message .= '</div></body></html>';
572
573 $headers = [
574 'Content-Type: text/html; charset=UTF-8',
575 'From: ' . esc_html($site_name) . ' <' . sanitize_email($admin_email) . '>'
576 ];
577 if ($user_email) {
578 $reply_to = $user_name ? esc_html($user_name) . ' <' . sanitize_email($user_email) . '>' : sanitize_email($user_email);
579 $headers[] = 'Reply-To: ' . $reply_to;
580 }
581
582 // Send the email
583 $sent = wp_mail($to, $subject, $message, $headers);
584
585 if ($sent) {
586 update_option('embedpress_feedback_submited', true);
587
588 return new \WP_REST_Response(['message' => 'Email sent successfully!'], 200);
589 } else {
590 // Retrieve last error
591 $last_error = get_transient('embedpress_last_mail_error');
592 $error_message = 'Failed to send email.';
593 if ($last_error instanceof \WP_Error) {
594 $error_message = $last_error->get_error_message();
595 }
596
597 return new \WP_REST_Response([
598 'message' => $error_message
599 ], 422); // using 422 instead of 500
600 }
601 }
602
603 public function capture_mail_error($wp_error)
604 {
605 if ($wp_error instanceof \WP_Error) {
606 set_transient('embedpress_last_mail_error', $wp_error, 60);
607 }
608 }
609
610 /**
611 * Get user IP address safely
612 *
613 * @return string
614 */
615 private function get_user_ip()
616 {
617 $ip_keys = [
618 'HTTP_CF_CONNECTING_IP', // CloudFlare
619 'HTTP_X_FORWARDED_FOR',
620 'HTTP_X_REAL_IP',
621 'REMOTE_ADDR'
622 ];
623
624 foreach ($ip_keys as $key) {
625 if (!empty($_SERVER[$key])) {
626 $ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
627 // Handle comma-separated IPs (from proxies)
628 if (strpos($ip, ',') !== false) {
629 $ip = trim(explode(',', $ip)[0]);
630 }
631 if (filter_var($ip, FILTER_VALIDATE_IP)) {
632 return $ip;
633 }
634 }
635 }
636
637 return '0.0.0.0';
638 }
639
640 /**
641 * Permission callback for feedback endpoint
642 *
643 * We return true here to allow the request to reach our handler,
644 * where we perform detailed authentication and authorization checks
645 * with custom error messages.
646 *
647 * @return bool
648 */
649 public function feedback_permission_callback()
650 {
651 // Return true to allow request to reach handler
652 // Actual auth checks are done in send_user_feedback_email()
653 return true;
654 }
655
656
657 public function register_feedback_email_endpoint()
658 {
659 register_rest_route('embedpress/v1', '/send-feedback', [
660 'methods' => 'POST',
661 'callback' => [$this, 'send_user_feedback_email'],
662 'permission_callback' => [$this, 'feedback_permission_callback'],
663 'args' => [
664 'email' => [
665 'required' => false,
666 'type' => 'string',
667 'sanitize_callback' => 'sanitize_email',
668 ],
669 'name' => [
670 'required' => false,
671 'type' => 'string',
672 'sanitize_callback' => 'sanitize_text_field',
673 ],
674 'rating' => [
675 'required' => true,
676 'type' => 'integer',
677 'validate_callback' => function($param) {
678 return is_numeric($param) && $param >= 1 && $param <= 5;
679 }
680 ],
681 'message' => [
682 'required' => false,
683 'type' => 'string',
684 'sanitize_callback' => 'sanitize_textarea_field',
685 ],
686 ]
687 ]);
688 }
689
690
691
692 /**
693 * Callback called right after the plugin has been activated.
694 *
695 * @return void
696 * @since 1.0.0
697 * @static
698 *
699 */
700 public static function onPluginActivationCallback()
701 {
702 $dirname = wp_get_upload_dir()['basedir'] . '/embedpress';
703 if (!file_exists($dirname)) {
704 mkdir($dirname, 0777);
705 }
706 flush_rewrite_rules();
707 embedpress_schedule_cache_cleanup();
708
709 // Set flag for activation redirect
710 $settings = get_option(EMBEDPRESS_PLG_NAME, []);
711 $settings['need_first_time_redirect'] = true;
712 update_option(EMBEDPRESS_PLG_NAME, $settings);
713
714 // Clear any previous redirect done flag
715 delete_option('embedpress_activation_redirect_done');
716 }
717
718 /**
719 * Callback called right after the plugin has been deactivated.
720 *
721 * @return void
722 * @since 1.0.0
723 * @static
724 *
725 */
726 public static function onPluginDeactivationCallback()
727 {
728 flush_rewrite_rules();
729 embedpress_cache_cleanup();
730 $timestamp = wp_next_scheduled('embedpress_backup_cleanup_action');
731 if ($timestamp) {
732 wp_unschedule_event($timestamp, 'embedpress_backup_cleanup_action');
733 }
734 }
735
736 /**
737 * Method that retrieves all additional service providers defined in the ~<plugin_root_path>/providers.php file.
738 *
739 * @return array
740 * @since 1.0.0
741 * @static
742 *
743 */
744 public static function getAdditionalServiceProviders()
745 {
746 $additionalProvidersFilePath = EMBEDPRESS_PATH_BASE . 'providers.php';
747 if (file_exists($additionalProvidersFilePath)) {
748 include $additionalProvidersFilePath;
749
750 if (isset($additionalServiceProviders)) {
751 return apply_filters('embedpress_additional_service_providers', $additionalServiceProviders);
752 }
753 }
754
755 return apply_filters('embedpress_additional_service_providers', []);
756 }
757
758 /**
759 * Method that checks if an embed of a given service provider can be responsive.
760 *
761 * @param string $serviceProviderAlias The service's slug.
762 *
763 * @return boolean
764 * @since 1.0.0
765 * @static
766 *
767 */
768 public static function canServiceProviderBeResponsive($serviceProviderAlias)
769 {
770 return in_array($serviceProviderAlias, [
771 "dailymotion",
772 "kickstarter",
773 "rutube",
774 "ted",
775 "vimeo",
776 "youtube",
777 "ustream",
778 "google-docs",
779 "animatron",
780 "amcharts",
781 "on-aol-com",
782 "animoto",
783 "videojug",
784 'issuu',
785 ]);
786 }
787
788 /**
789 * Method that retrieves the plugin settings defined by the user.
790 *
791 * @return object
792 * @since 1.0.0
793 * @static
794 *
795 */
796 public static function getSettings()
797 {
798 // Fetch settings from the database
799 $settings = get_option(EMBEDPRESS_PLG_NAME);
800
801 // If the settings are not an array (it might return false), initialize as an empty array
802 if (!is_array($settings)) {
803 $settings = [];
804 }
805
806 // Default values if settings are missing
807 if (!isset($settings['enablePluginInAdmin'])) {
808 $settings['enablePluginInAdmin'] = true;
809 }
810
811 if (!isset($settings['enablePluginInFront'])) {
812 $settings['enablePluginInFront'] = true;
813 }
814
815 if (!isset($settings['enableGlobalEmbedResize'])) {
816 $settings['enableGlobalEmbedResize'] = false;
817 }
818
819 if (!isset($settings['enableEmbedResizeHeight'])) {
820 $settings['enableEmbedResizeHeight'] = 550; // old 552
821 }
822
823 if (!isset($settings['enableEmbedResizeWidth'])) {
824 $settings['enableEmbedResizeWidth'] = 600; // old 652
825 }
826
827 return (object) $settings;
828 }
829
830 /**
831 * Retrieve all registered plugins.
832 *
833 * @return array
834 * @since 1.4.0
835 * @static
836 *
837 */
838 public static function getPlugins()
839 {
840 return self::$plugins;
841 }
842
843 /**
844 * Handle links displayed below the plugin name in the WordPress Installed Plugins page.
845 *
846 * @return array
847 * @since 1.4.0
848 * @static
849 *
850 */
851 public static function handleActionLinks($links, $file)
852 {
853 $settingsLink = '<a href="' . admin_url('admin.php?page=embedpress') . '" aria-label="' . __(
854 'Open settings page',
855 'embedpress'
856 ) . '">' . __('Settings', 'embedpress') . '</a>';
857
858 array_unshift($links, $settingsLink);
859 if (!apply_filters('embedpress/is_allow_rander', false)) {
860 $links[] = '<a href="https://wpdeveloper.com/in/upgrade-embedpress" target="_blank" class="embedpress-go-pro-action" style="color: green">' . __('Go Pro', 'embedpress') . '</a>';
861 }
862 return $links;
863 }
864
865
866 /**
867 * Method that ensures the API's url are whitelisted to WordPress external requests.
868 *
869 * @param boolean $isAllowed
870 * @param string $host
871 * @param string $url
872 *
873 * @return boolean
874 * @since 1.4.0
875 * @static
876 *
877 */
878 public static function allowApiHost($isAllowed, $host, $url)
879 {
880 if ($host === EMBEDPRESS_LICENSES_API_HOST) {
881 $isAllowed = true;
882 }
883
884 return $isAllowed;
885 }
886
887 public function extended_mime_types($mimes)
888 {
889 $mimes['ppsx'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
890 return $mimes;
891 }
892 }
893