PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 4.5.3
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v4.5.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 1 month ago Ends 1 month ago Gutenberg 1 month ago Includes 1 month ago Plugins 1 year ago Providers 1 month ago ThirdParty 1 month ago AutoLoader.php 2 years ago Compatibility.php 2 years ago Core.php 2 months ago CoreLegacy.php 2 months ago DisablerLegacy.php 2 years ago Loader.php 2 years ago MilestoneNotification.php 6 months ago RestAPI.php 3 months ago Shortcode.php 1 month ago index.html 7 years ago simple_html_dom.php 4 years ago
Core.php
905 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 // Trigger the setup wizard redirect only on a true fresh install. The
714 // 'embedpress_install_type' marker is set in EmbedpressSettings::__construct
715 // before any default settings are written, so it reliably distinguishes
716 // first-ever installs from re-activations after a plugin update.
717 $pro_active = apply_filters( 'embedpress/is_allow_rander', false );
718 $install_type = get_option( 'embedpress_install_type', false );
719
720 if ( ! $pro_active && $install_type !== 'existing' ) {
721 $settings = get_option( EMBEDPRESS_PLG_NAME, [] );
722 $settings['need_first_time_redirect'] = true;
723 update_option( EMBEDPRESS_PLG_NAME, $settings );
724
725 // Clear any previous redirect done flag
726 delete_option( 'embedpress_activation_redirect_done' );
727 }
728 }
729
730 /**
731 * Callback called right after the plugin has been deactivated.
732 *
733 * @return void
734 * @since 1.0.0
735 * @static
736 *
737 */
738 public static function onPluginDeactivationCallback()
739 {
740 flush_rewrite_rules();
741 embedpress_cache_cleanup();
742 $timestamp = wp_next_scheduled('embedpress_backup_cleanup_action');
743 if ($timestamp) {
744 wp_unschedule_event($timestamp, 'embedpress_backup_cleanup_action');
745 }
746 }
747
748 /**
749 * Method that retrieves all additional service providers defined in the ~<plugin_root_path>/providers.php file.
750 *
751 * @return array
752 * @since 1.0.0
753 * @static
754 *
755 */
756 public static function getAdditionalServiceProviders()
757 {
758 $additionalProvidersFilePath = EMBEDPRESS_PATH_BASE . 'providers.php';
759 if (file_exists($additionalProvidersFilePath)) {
760 include $additionalProvidersFilePath;
761
762 if (isset($additionalServiceProviders)) {
763 return apply_filters('embedpress_additional_service_providers', $additionalServiceProviders);
764 }
765 }
766
767 return apply_filters('embedpress_additional_service_providers', []);
768 }
769
770 /**
771 * Method that checks if an embed of a given service provider can be responsive.
772 *
773 * @param string $serviceProviderAlias The service's slug.
774 *
775 * @return boolean
776 * @since 1.0.0
777 * @static
778 *
779 */
780 public static function canServiceProviderBeResponsive($serviceProviderAlias)
781 {
782 return in_array($serviceProviderAlias, [
783 "dailymotion",
784 "kickstarter",
785 "rutube",
786 "ted",
787 "vimeo",
788 "youtube",
789 "ustream",
790 "google-docs",
791 "animatron",
792 "amcharts",
793 "on-aol-com",
794 "animoto",
795 "videojug",
796 'issuu',
797 ]);
798 }
799
800 /**
801 * Method that retrieves the plugin settings defined by the user.
802 *
803 * @return object
804 * @since 1.0.0
805 * @static
806 *
807 */
808 public static function getSettings()
809 {
810 // Fetch settings from the database
811 $settings = get_option(EMBEDPRESS_PLG_NAME);
812
813 // If the settings are not an array (it might return false), initialize as an empty array
814 if (!is_array($settings)) {
815 $settings = [];
816 }
817
818 // Default values if settings are missing
819 if (!isset($settings['enablePluginInAdmin'])) {
820 $settings['enablePluginInAdmin'] = true;
821 }
822
823 if (!isset($settings['enablePluginInFront'])) {
824 $settings['enablePluginInFront'] = true;
825 }
826
827 if (!isset($settings['enableGlobalEmbedResize'])) {
828 $settings['enableGlobalEmbedResize'] = false;
829 }
830
831 if (!isset($settings['enableEmbedResizeHeight'])) {
832 $settings['enableEmbedResizeHeight'] = 550; // old 552
833 }
834
835 if (!isset($settings['enableEmbedResizeWidth'])) {
836 $settings['enableEmbedResizeWidth'] = 600; // old 652
837 }
838
839 return (object) $settings;
840 }
841
842 /**
843 * Retrieve all registered plugins.
844 *
845 * @return array
846 * @since 1.4.0
847 * @static
848 *
849 */
850 public static function getPlugins()
851 {
852 return self::$plugins;
853 }
854
855 /**
856 * Handle links displayed below the plugin name in the WordPress Installed Plugins page.
857 *
858 * @return array
859 * @since 1.4.0
860 * @static
861 *
862 */
863 public static function handleActionLinks($links, $file)
864 {
865 $settingsLink = '<a href="' . admin_url('admin.php?page=embedpress') . '" aria-label="' . __(
866 'Open settings page',
867 'embedpress'
868 ) . '">' . __('Settings', 'embedpress') . '</a>';
869
870 array_unshift($links, $settingsLink);
871 if (!apply_filters('embedpress/is_allow_rander', false)) {
872 $links[] = '<a href="https://wpdeveloper.com/in/upgrade-embedpress" target="_blank" class="embedpress-go-pro-action" style="color: green">' . __('Go Pro', 'embedpress') . '</a>';
873 }
874 return $links;
875 }
876
877
878 /**
879 * Method that ensures the API's url are whitelisted to WordPress external requests.
880 *
881 * @param boolean $isAllowed
882 * @param string $host
883 * @param string $url
884 *
885 * @return boolean
886 * @since 1.4.0
887 * @static
888 *
889 */
890 public static function allowApiHost($isAllowed, $host, $url)
891 {
892 if ($host === EMBEDPRESS_LICENSES_API_HOST) {
893 $isAllowed = true;
894 }
895
896 return $isAllowed;
897 }
898
899 public function extended_mime_types($mimes)
900 {
901 $mimes['ppsx'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
902 return $mimes;
903 }
904 }
905