PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / trunk
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more vtrunk
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 1 month ago Elementor 1 week ago Ends 1 month ago Gutenberg 1 week ago Includes 1 week ago Plugins 1 year ago Providers 3 weeks ago ThirdParty 1 month ago AutoLoader.php 2 years ago Compatibility.php 2 years ago Core.php 3 weeks ago CoreLegacy.php 2 months ago DisablerLegacy.php 2 years ago Loader.php 2 years ago MilestoneNotification.php 6 months ago RestAPI.php 3 weeks ago Shortcode.php 1 week ago index.html 7 years ago simple_html_dom.php 4 years ago
Core.php
964 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 // Queue infinite-scroll: returns a batch of rendered <li.ep-yt-queue__item>
430 // for the next page of a YouTube playlist.
431 register_rest_route(
432 'embedpress/v1',
433 '/youtube-playlist-items',
434 [
435 'methods' => \WP_REST_Server::READABLE,
436 'callback' => ['\\EmbedPress\\RestAPI', 'youtube_playlist_items'],
437 'permission_callback' => '__return_true',
438 'args' => [
439 'playlist_id' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
440 'page_token' => ['required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
441 'page_size' => ['required' => false, 'type' => 'integer', 'sanitize_callback' => 'absint'],
442 'offset' => ['required' => false, 'type' => 'integer', 'sanitize_callback' => 'absint'],
443 'layout' => ['required' => false, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field'],
444 ],
445 ]
446 );
447
448 // Dynamic-source field enumerator (fbs-81736). Inspector drop-down
449 // calls this to populate field choices for the selected provider
450 // instead of asking the user to type the field key by hand.
451 register_rest_route(
452 'embedpress/v1',
453 '/dynamic-fields',
454 [
455 'methods' => \WP_REST_Server::READABLE,
456 'callback' => ['\\EmbedPress\\Includes\\Classes\\DynamicFieldResolver', 'rest_list_fields'],
457 'permission_callback' => function () {
458 return current_user_can('edit_posts')
459 && \EmbedPress\Includes\Classes\Helper::is_pro_features_enabled();
460 },
461 'args' => [
462 'source' => ['type' => 'string', 'required' => true],
463 ],
464 ]
465 );
466
467 // Dynamic-source value resolver (fbs-81736). The block editor calls this
468 // to live-preview the resolved custom-field URL instead of the saved
469 // placeholder, so the canvas matches the front-end render.
470 register_rest_route(
471 'embedpress/v1',
472 '/dynamic-resolve',
473 [
474 'methods' => \WP_REST_Server::READABLE,
475 'callback' => ['\\EmbedPress\\Includes\\Classes\\DynamicFieldResolver', 'rest_resolve_field'],
476 'permission_callback' => function () {
477 return current_user_can('edit_posts')
478 && \EmbedPress\Includes\Classes\Helper::is_pro_features_enabled();
479 },
480 'args' => [
481 'source' => ['type' => 'string', 'required' => true],
482 'field' => ['type' => 'string', 'required' => true],
483 'post_id' => ['type' => 'integer', 'required' => false],
484 ],
485 ]
486 );
487 }
488
489 public function send_user_feedback_email($request)
490 {
491 // Ensure we have a valid REST request object
492 if (!($request instanceof \WP_REST_Request)) {
493 return new \WP_REST_Response(['message' => 'Invalid request'], 400);
494 }
495
496 // CRITICAL: Check if feedback was already sent FIRST to prevent spam
497 $is_feedback_already_sent = get_option('embedpress_feedback_submited');
498 if ($is_feedback_already_sent) {
499 return new \WP_REST_Response(['message' => 'Feedback already submitted'], 200);
500 }
501
502 // Rate limiting: Only allow one request per IP per minute
503 $user_ip = $this->get_user_ip();
504 $rate_limit_key = 'embedpress_feedback_rate_limit_' . md5($user_ip);
505 if (get_transient($rate_limit_key)) {
506 return new \WP_REST_Response(['message' => 'Too many requests. Please try again later.'], 429);
507 }
508 set_transient($rate_limit_key, 2, MINUTE_IN_SECONDS);
509
510 // Verify user is logged in and has admin capabilities
511 if (!is_user_logged_in()) {
512 return new \WP_REST_Response(['message' => 'Unauthorized. You must be logged in.'], 401);
513 }
514
515 if (!current_user_can('manage_options')) {
516 return new \WP_REST_Response(['message' => 'Forbidden. Insufficient permissions.'], 403);
517 }
518
519 // Verify nonce for CSRF protection
520 $nonce = $request->get_header('X-WP-Nonce');
521
522 if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
523 return new \WP_REST_Response(['message' => 'Invalid security token. Please refresh the page and try again.'], 403);
524 }
525
526
527 $params = $request->get_params();
528
529 // Safely extract and sanitize incoming params to avoid undefined index notices
530 $params = is_array($params) ? $params : [];
531 $user_email = isset($params['email']) ? sanitize_email($params['email']) : '';
532 $user_name = isset($params['name']) ? sanitize_text_field($params['name']) : '';
533 $user_rating = isset($params['rating']) ? intval($params['rating']) : 0;
534 $user_msg = isset($params['message']) ? sanitize_textarea_field($params['message']) : '';
535
536 // Validate rating is within acceptable range
537 if ($user_rating < 1 || $user_rating > 5) {
538 return new \WP_REST_Response(['message' => 'Invalid rating value. Must be between 1 and 5.'], 400);
539 }
540
541 // Prevent submissions with empty/invalid user data (prevents N/A spam)
542 if (empty($user_email) || !is_email($user_email)) {
543 return new \WP_REST_Response(['message' => 'Valid email address is required.'], 400);
544 }
545
546 if (empty($user_name) || strlen(trim($user_name)) < 2) {
547 return new \WP_REST_Response(['message' => 'Valid name is required.'], 400);
548 }
549
550 // If the payload is completely empty, ignore to prevent blank/spam emails
551 $has_meaningful_input = false;
552
553 if ($user_rating > 0) {
554 if ($user_rating < 5) {
555 // description required
556 if (trim($user_msg) !== '') {
557 $has_meaningful_input = true;
558 }
559 } else {
560 // rating is 5, description not required
561 $has_meaningful_input = true;
562 }
563 }
564
565 if (!$has_meaningful_input) {
566 return new \WP_REST_Response(['message' => 'No feedback content provided; ignored.'], 200);
567 }
568
569 // Prevent accidental duplicate submissions (double-clicks, quick retries)
570 $payload_hash = md5(json_encode([$user_email, $user_name, $user_rating, $user_msg]));
571 if (get_transient('embedpress_feedback_dupe_' . $payload_hash)) {
572 return new \WP_REST_Response(['message' => 'Duplicate feedback detected; already processed.'], 200);
573 }
574 set_transient('embedpress_feedback_dupe_' . $payload_hash, 1, 5 * MINUTE_IN_SECONDS);
575
576 $email_html = $user_email ? '<a href="mailto:' . esc_attr($user_email) . '">' . esc_html($user_email) . '</a>' : 'N/A';
577 $rating_html = $user_rating ? esc_html($user_rating) . ' ⭐️' : 'N/A';
578 $message_html = $user_msg !== '' ? nl2br(esc_html($user_msg)) : 'N/A';
579
580
581 $site_name = get_bloginfo('name');
582 $site_url = get_site_url();
583 $admin_email = get_option('admin_email');
584 $wp_version = get_bloginfo('version');
585
586 $admin_user = get_user_by('ID', 1);
587 if ($admin_user) {
588 $first_name = get_user_meta($admin_user->ID, 'first_name', true);
589 $last_name = get_user_meta($admin_user->ID, 'last_name', true);
590
591 $admin_full_name = trim("$first_name $last_name");
592
593 // Fallback to display name if full name is not set
594 if (empty($admin_full_name)) {
595 $admin_full_name = $admin_user->display_name;
596 }
597 } else {
598 $admin_full_name = 'Unknown';
599 }
600
601 $to = 'akash@wpdeveloper.com, rasel@wpdeveloper.com, nahid@wpdeveloper.com, md-nahid-hasan@wpdeveloper.com'; // Replace with the recipient's email
602 $subject = '[IMPORTANT] New feedback received from an EmbedPress user.';
603
604 // HTML Email Template
605 $message = '<html><body style="font-family: Arial, sans-serif; padding: 20px;">';
606 $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;">';
607 $message .= '<div style="text-align: center; padding-bottom: 20px; border-bottom: 1px solid #ddd">';
608 $message .= '<img src="https://embedpress.com/wp-content/uploads/2025/03/logo.png" alt="EmbedPress" style="max-width: 150px;">';
609 $message .= '</div>';
610 $message .= '<h2 style="font-family: system-ui; color: #333; text-align: center;">Feedback Overview</h2>';
611 $message .= '<table style="font-family: system-ui; width: 100%; border-collapse: collapse; border: 1px solid #ddd">';
612
613 // Email
614 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Email :</td>';
615 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd;">' . $email_html . '</td></tr>';
616
617 // Rating
618 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Rating :</td>';
619 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd;">' . $rating_html . '</td></tr>';
620
621 // User
622 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Name :</td>';
623 $message .= '<td style="padding: 10px; border-bottom: 1px solid #ddd; font-weight: 500;">' . esc_html($admin_full_name) . '</td></tr>';
624
625 // Pack
626 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; border-bottom: 1px solid #ddd;">Site Url :</td>';
627 $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>';
628
629 // Feedback
630 $message .= '<tr><td style="padding: 10px; font-weight: bold; width: 100px; display: flex;">Feedback :</td>';
631 $message .= '<td style="padding: 10px;">' . $message_html . '</td></tr>';
632
633 $message .= '</table>';
634 $message .= '</div></body></html>';
635
636 $headers = [
637 'Content-Type: text/html; charset=UTF-8',
638 'From: ' . esc_html($site_name) . ' <' . sanitize_email($admin_email) . '>'
639 ];
640 if ($user_email) {
641 $reply_to = $user_name ? esc_html($user_name) . ' <' . sanitize_email($user_email) . '>' : sanitize_email($user_email);
642 $headers[] = 'Reply-To: ' . $reply_to;
643 }
644
645 // Send the email
646 $sent = wp_mail($to, $subject, $message, $headers);
647
648 if ($sent) {
649 update_option('embedpress_feedback_submited', true);
650
651 return new \WP_REST_Response(['message' => 'Email sent successfully!'], 200);
652 } else {
653 // Retrieve last error
654 $last_error = get_transient('embedpress_last_mail_error');
655 $error_message = 'Failed to send email.';
656 if ($last_error instanceof \WP_Error) {
657 $error_message = $last_error->get_error_message();
658 }
659
660 return new \WP_REST_Response([
661 'message' => $error_message
662 ], 422); // using 422 instead of 500
663 }
664 }
665
666 public function capture_mail_error($wp_error)
667 {
668 if ($wp_error instanceof \WP_Error) {
669 set_transient('embedpress_last_mail_error', $wp_error, 60);
670 }
671 }
672
673 /**
674 * Get user IP address safely
675 *
676 * @return string
677 */
678 private function get_user_ip()
679 {
680 $ip_keys = [
681 'HTTP_CF_CONNECTING_IP', // CloudFlare
682 'HTTP_X_FORWARDED_FOR',
683 'HTTP_X_REAL_IP',
684 'REMOTE_ADDR'
685 ];
686
687 foreach ($ip_keys as $key) {
688 if (!empty($_SERVER[$key])) {
689 $ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
690 // Handle comma-separated IPs (from proxies)
691 if (strpos($ip, ',') !== false) {
692 $ip = trim(explode(',', $ip)[0]);
693 }
694 if (filter_var($ip, FILTER_VALIDATE_IP)) {
695 return $ip;
696 }
697 }
698 }
699
700 return '0.0.0.0';
701 }
702
703 /**
704 * Permission callback for feedback endpoint
705 *
706 * We return true here to allow the request to reach our handler,
707 * where we perform detailed authentication and authorization checks
708 * with custom error messages.
709 *
710 * @return bool
711 */
712 public function feedback_permission_callback()
713 {
714 // Return true to allow request to reach handler
715 // Actual auth checks are done in send_user_feedback_email()
716 return true;
717 }
718
719
720 public function register_feedback_email_endpoint()
721 {
722 register_rest_route('embedpress/v1', '/send-feedback', [
723 'methods' => 'POST',
724 'callback' => [$this, 'send_user_feedback_email'],
725 'permission_callback' => [$this, 'feedback_permission_callback'],
726 'args' => [
727 'email' => [
728 'required' => false,
729 'type' => 'string',
730 'sanitize_callback' => 'sanitize_email',
731 ],
732 'name' => [
733 'required' => false,
734 'type' => 'string',
735 'sanitize_callback' => 'sanitize_text_field',
736 ],
737 'rating' => [
738 'required' => true,
739 'type' => 'integer',
740 'validate_callback' => function($param) {
741 return is_numeric($param) && $param >= 1 && $param <= 5;
742 }
743 ],
744 'message' => [
745 'required' => false,
746 'type' => 'string',
747 'sanitize_callback' => 'sanitize_textarea_field',
748 ],
749 ]
750 ]);
751 }
752
753
754
755 /**
756 * Callback called right after the plugin has been activated.
757 *
758 * @return void
759 * @since 1.0.0
760 * @static
761 *
762 */
763 public static function onPluginActivationCallback()
764 {
765 $dirname = wp_get_upload_dir()['basedir'] . '/embedpress';
766 if (!file_exists($dirname)) {
767 mkdir($dirname, 0777);
768 }
769 flush_rewrite_rules();
770 embedpress_schedule_cache_cleanup();
771
772 // Trigger the setup wizard redirect only on a true fresh install. The
773 // 'embedpress_install_type' marker is set in EmbedpressSettings::__construct
774 // before any default settings are written, so it reliably distinguishes
775 // first-ever installs from re-activations after a plugin update.
776 $pro_active = apply_filters( 'embedpress/is_allow_rander', false );
777 $install_type = get_option( 'embedpress_install_type', false );
778
779 if ( ! $pro_active && $install_type !== 'existing' ) {
780 $settings = get_option( EMBEDPRESS_PLG_NAME, [] );
781 $settings['need_first_time_redirect'] = true;
782 update_option( EMBEDPRESS_PLG_NAME, $settings );
783
784 // Clear any previous redirect done flag
785 delete_option( 'embedpress_activation_redirect_done' );
786 }
787 }
788
789 /**
790 * Callback called right after the plugin has been deactivated.
791 *
792 * @return void
793 * @since 1.0.0
794 * @static
795 *
796 */
797 public static function onPluginDeactivationCallback()
798 {
799 flush_rewrite_rules();
800 embedpress_cache_cleanup();
801 $timestamp = wp_next_scheduled('embedpress_backup_cleanup_action');
802 if ($timestamp) {
803 wp_unschedule_event($timestamp, 'embedpress_backup_cleanup_action');
804 }
805 }
806
807 /**
808 * Method that retrieves all additional service providers defined in the ~<plugin_root_path>/providers.php file.
809 *
810 * @return array
811 * @since 1.0.0
812 * @static
813 *
814 */
815 public static function getAdditionalServiceProviders()
816 {
817 $additionalProvidersFilePath = EMBEDPRESS_PATH_BASE . 'providers.php';
818 if (file_exists($additionalProvidersFilePath)) {
819 include $additionalProvidersFilePath;
820
821 if (isset($additionalServiceProviders)) {
822 return apply_filters('embedpress_additional_service_providers', $additionalServiceProviders);
823 }
824 }
825
826 return apply_filters('embedpress_additional_service_providers', []);
827 }
828
829 /**
830 * Method that checks if an embed of a given service provider can be responsive.
831 *
832 * @param string $serviceProviderAlias The service's slug.
833 *
834 * @return boolean
835 * @since 1.0.0
836 * @static
837 *
838 */
839 public static function canServiceProviderBeResponsive($serviceProviderAlias)
840 {
841 return in_array($serviceProviderAlias, [
842 "dailymotion",
843 "kickstarter",
844 "rutube",
845 "ted",
846 "vimeo",
847 "youtube",
848 "ustream",
849 "google-docs",
850 "animatron",
851 "amcharts",
852 "on-aol-com",
853 "animoto",
854 "videojug",
855 'issuu',
856 ]);
857 }
858
859 /**
860 * Method that retrieves the plugin settings defined by the user.
861 *
862 * @return object
863 * @since 1.0.0
864 * @static
865 *
866 */
867 public static function getSettings()
868 {
869 // Fetch settings from the database
870 $settings = get_option(EMBEDPRESS_PLG_NAME);
871
872 // If the settings are not an array (it might return false), initialize as an empty array
873 if (!is_array($settings)) {
874 $settings = [];
875 }
876
877 // Default values if settings are missing
878 if (!isset($settings['enablePluginInAdmin'])) {
879 $settings['enablePluginInAdmin'] = true;
880 }
881
882 if (!isset($settings['enablePluginInFront'])) {
883 $settings['enablePluginInFront'] = true;
884 }
885
886 if (!isset($settings['enableGlobalEmbedResize'])) {
887 $settings['enableGlobalEmbedResize'] = false;
888 }
889
890 if (!isset($settings['enableEmbedResizeHeight'])) {
891 $settings['enableEmbedResizeHeight'] = 550; // old 552
892 }
893
894 if (!isset($settings['enableEmbedResizeWidth'])) {
895 $settings['enableEmbedResizeWidth'] = 600; // old 652
896 }
897
898 return (object) $settings;
899 }
900
901 /**
902 * Retrieve all registered plugins.
903 *
904 * @return array
905 * @since 1.4.0
906 * @static
907 *
908 */
909 public static function getPlugins()
910 {
911 return self::$plugins;
912 }
913
914 /**
915 * Handle links displayed below the plugin name in the WordPress Installed Plugins page.
916 *
917 * @return array
918 * @since 1.4.0
919 * @static
920 *
921 */
922 public static function handleActionLinks($links, $file)
923 {
924 $settingsLink = '<a href="' . admin_url('admin.php?page=embedpress') . '" aria-label="' . __(
925 'Open settings page',
926 'embedpress'
927 ) . '">' . __('Settings', 'embedpress') . '</a>';
928
929 array_unshift($links, $settingsLink);
930 if (!apply_filters('embedpress/is_allow_rander', false)) {
931 $links[] = '<a href="https://wpdeveloper.com/in/upgrade-embedpress" target="_blank" class="embedpress-go-pro-action" style="color: green">' . __('Go Pro', 'embedpress') . '</a>';
932 }
933 return $links;
934 }
935
936
937 /**
938 * Method that ensures the API's url are whitelisted to WordPress external requests.
939 *
940 * @param boolean $isAllowed
941 * @param string $host
942 * @param string $url
943 *
944 * @return boolean
945 * @since 1.4.0
946 * @static
947 *
948 */
949 public static function allowApiHost($isAllowed, $host, $url)
950 {
951 if ($host === EMBEDPRESS_LICENSES_API_HOST) {
952 $isAllowed = true;
953 }
954
955 return $isAllowed;
956 }
957
958 public function extended_mime_types($mimes)
959 {
960 $mimes['ppsx'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
961 return $mimes;
962 }
963 }
964