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