PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.6.3
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.6.3
4.6.3 4.6.2 4.6.1 4.6.0 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 / Includes / Classes / Helper.php
embedpress / EmbedPress / Includes / Classes Last commit date
Analytics 1 month ago Database 3 months ago DynamicFieldResolver.php 2 months ago Elementor_Enhancer.php 7 months ago EmbedPress_Core_Installer.php 6 years ago EmbedPress_Notice.php 4 months ago EmbedPress_Plugin_Usage_Tracker.php 2 months ago Extend_CustomPlayer_Controls.php 3 months ago Extend_Elementor_Controls.php 1 year ago FeatureNoticeManager.php 1 month ago FeatureNotices.php 2 weeks ago FeaturePreviewModal.php 1 month ago Feature_Enhancer.php 2 months ago GoogleReviewsAdminPage.php 1 month ago GoogleReviewsApify.php 1 month ago GoogleReviewsManaged.php 5 days ago GoogleReviewsRenderer.php 5 days ago GoogleReviewsRestController.php 5 days ago GoogleReviewsStore.php 5 days ago Helper.php 5 days ago Pdf_Thumbnail_Handler.php 5 days ago PermalinkHelper.php 11 months ago View_Count_Display.php 1 month ago
Helper.php
2086 lines
1 <?php
2
3 namespace EmbedPress\Includes\Classes;
4
5 use EmbedPress\Providers\TemplateLayouts\YoutubeLayout;
6 use EmbedPress\Shortcode;
7
8 use Elementor\Plugin;
9
10
11 if (!defined('ABSPATH')) {
12 exit;
13 } // Exit if accessed directly
14
15 class Helper
16 {
17
18 /**
19 * Parse a query string into an associative array.
20 *
21 * If multiple values are found for the same key, the value of that key
22 * value pair will become an array. This function does not parse nested
23 * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
24 * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
25 *
26 * @param string $str Query string to parse
27 * @param int|bool $urlEncoding How the query string is encoded
28 *
29 * @return array
30 */
31
32 public function __construct()
33 {
34 add_action('wp_ajax_lock_content_form_handler', [$this, 'lock_content_form_handler']);
35 add_action('wp_ajax_nopriv_lock_content_form_handler', [$this, 'lock_content_form_handler']);
36
37 add_action('wp_ajax_embedpress_gutenberg_password_check', [$this, 'gutenberg_password_check']);
38 add_action('wp_ajax_nopriv_embedpress_gutenberg_password_check', [$this, 'gutenberg_password_check']);
39
40
41 add_action('wp_ajax_loadmore_data_handler', [$this, 'loadmore_data_handler']);
42 add_action('wp_ajax_nopriv_loadmore_data_handler', [$this, 'loadmore_data_handler']);
43
44
45 add_action('wp_ajax_fetch_video_description', [$this, 'ajax_video_popup_description']);
46 add_action('wp_ajax_nopriv_fetch_video_description', [$this, 'ajax_video_popup_description']);
47 }
48
49 public function ajax_video_popup_description()
50 {
51 if (isset($_POST['vid'])) {
52 $api_key = self::get_api_key();
53 $vid = sanitize_text_field($_POST['vid']);
54
55 $video_data = Helper::get_youtube_video_data($api_key, $vid);
56
57 if ($video_data) {
58 ob_start();
59 ?>
60 <div class="video-description">
61 <?php echo YoutubeLayout::generate_youtube_video_description($video_data); ?>
62 </div>
63 <?php
64 $description_html = ob_get_clean();
65 wp_send_json_success(['description' => $description_html]);
66 } else {
67 wp_send_json_error(['error' => 'Failed to fetch video data.']);
68 }
69 } else {
70 wp_send_json_error(['error' => 'Invalid video ID.']);
71 }
72 }
73
74 public static function get_api_key()
75 {
76 $settings = (array) get_option(EMBEDPRESS_PLG_NAME . ':youtube', []);
77 return !empty($settings['api_key']) ? $settings['api_key'] : '';
78 }
79
80 public static function parse_query($str, $urlEncoding = true)
81 {
82 $result = [];
83
84 if ($str === '') {
85 return $result;
86 }
87
88 if ($urlEncoding === true) {
89 $decoder = function ($value) {
90 return rawurldecode(str_replace('+', ' ', $value));
91 };
92 } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
93 $decoder = 'rawurldecode';
94 } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
95 $decoder = 'urldecode';
96 } else {
97 $decoder = function ($str) {
98 return $str;
99 };
100 }
101
102 foreach (explode('&', $str) as $kvp) {
103 $parts = explode('=', $kvp, 2);
104 $key = $decoder($parts[0]);
105 $value = isset($parts[1]) ? $decoder($parts[1]) : null;
106 if (!isset($result[$key])) {
107 $result[$key] = $value;
108 } else {
109 if (!is_array($result[$key])) {
110 $result[$key] = [$result[$key]];
111 }
112 $result[$key][] = $value;
113 }
114 }
115
116 return $result;
117 }
118 /**
119 * URL of the PDF.js ("modern") viewer document.
120 *
121 * Same defect and same fix as get_flipbook_renderer() below — the viewer
122 * is a static HTML file, but routing it through admin-ajax.php paid a full
123 * WordPress bootstrap on every embed (measured 111ms vs 0ms served
124 * directly, on an idle install; far worse under load). This is the DEFAULT
125 * viewer style, so it affects more embeds than the flipbook does.
126 * See FB #84166.
127 *
128 * The viewer's asset references are relative to its own directory
129 * (viewer.css, viewer.js, locale/, and a ../build/ hop), so they resolve
130 * natively from here without the <base href> rewrite admin-ajax injected.
131 *
132 * The admin-ajax actions stay registered as a fallback for hosts that
133 * block direct .html access under wp-content; filter
134 * `embedpress_pdf_use_ajax_renderer` to true to force the old path.
135 *
136 * @return string
137 */
138 public static function get_pdf_renderer()
139 {
140 if (apply_filters('embedpress_pdf_use_ajax_renderer', false)) {
141 return admin_url('admin-ajax.php?action=get_viewer');
142 }
143
144 // Trailing '?' is REQUIRED, not cosmetic. The admin-ajax URL this
145 // replaces always carried a query string, and callers append their
146 // params with a bare '&file=...' — several of them (the lightbox and
147 // PDF-gallery front-end scripts) with no "is there a ? yet" check at
148 // all. Returning a bare .html URL makes those produce
149 // "viewer.html&file=..." which Apache serves as 404. Ending on '?'
150 // keeps every existing caller correct without touching them.
151 $renderer = EMBEDPRESS_URL_ASSETS . 'pdf/web/viewer.html?';
152
153 // EMBEDPRESS_URL_ASSETS is built once at load time and can carry a
154 // stale http:// scheme on a site now served over https; an http://
155 // iframe inside an https page is blocked as mixed content.
156 if (is_ssl() && 0 === strpos($renderer, 'http://')) {
157 $renderer = set_url_scheme($renderer, 'https');
158 }
159
160 return apply_filters('embedpress_pdf_renderer_url', $renderer);
161 }
162
163 /**
164 * URL of the 3D flipbook viewer document.
165 *
166 * The viewer is a plain static HTML file. Serving it through
167 * admin-ajax.php pays a full WordPress bootstrap for every embed, and the
168 * viewer then has to fetch its template through a *second* admin-ajax
169 * request that the browser cannot even discover until the first one
170 * returns. Measured on an idle install that is ~120ms warm per request;
171 * on a loaded production site it has been reported at 1.98s + 2.27s,
172 * serialized in front of ~2.2MB of viewer JS. See FB #84166.
173 *
174 * Serving the file directly removes both round-trips. The viewer's own
175 * asset references (js/, css/, templates/, and the ../../../../../
176 * hop to wp-includes for jQuery) are written relative to its real
177 * directory, so they resolve natively from here without the <base href>
178 * rewrite the admin-ajax path had to inject.
179 *
180 * The admin-ajax actions remain registered as a fallback for hosts that
181 * block direct .html access under wp-content; filter
182 * `embedpress_flipbook_use_ajax_renderer` to true to force the old path.
183 *
184 * @return string
185 */
186 public static function get_flipbook_renderer()
187 {
188 if (apply_filters('embedpress_flipbook_use_ajax_renderer', false)) {
189 return admin_url('admin-ajax.php?action=get_flipbook_viewer');
190 }
191
192 // Trailing '?' is REQUIRED — see the note in get_pdf_renderer().
193 // static/js/ep-pdf-lightbox.js and static/js/pdf-gallery.js append a
194 // bare '&file=' with no separator check, so a URL without a query
195 // string yields "viewer.html&file=..." → 404.
196 $renderer = EMBEDPRESS_URL_ASSETS . 'pdf-flip-book/viewer.html?';
197
198 // EMBEDPRESS_URL_ASSETS is built once at load time and can carry a
199 // stale http:// scheme on a site that is now served over https (it is
200 // http:// on our own test install). An http:// iframe inside an https
201 // page is blocked outright as mixed content, so realign the scheme
202 // with the site's. set_url_scheme() alone is not enough here — it
203 // keys off the current request context, not the site's own scheme.
204 if (is_ssl() && 0 === strpos($renderer, 'http://')) {
205 $renderer = set_url_scheme($renderer, 'https');
206 }
207
208 return apply_filters('embedpress_flipbook_renderer_url', $renderer);
209 }
210
211 public static function get_extension_from_file_url($url)
212 {
213 $path = parse_url((string) $url, PHP_URL_PATH);
214 if (empty($path)) {
215 $path = (string) $url;
216 }
217 $urlSplit = explode(".", $path);
218 $ext = end($urlSplit);
219 return strtolower($ext);
220 }
221
222 /**
223 * Poster image for a PDF, resolved server-side.
224 *
225 * Without this the lightbox/trigger markup ships a bare
226 * <canvas data-pdf-url> and static/js/ep-pdf-lightbox.js renders page 1
227 * with PDF.js in the visitor's browser on EVERY page load — no cache.
228 * Measured on an 18.1MB/29-page PDF that cost 18 requests, 19.16MB
229 * transferred and ~7s before the preview appeared, for every visitor.
230 * See FB #84167.
231 *
232 * WordPress already rasterises page 1 to JPEG at upload time for PDFs in
233 * the media library (the same previews the media grid shows), so in the
234 * common case this is a metadata lookup and costs nothing to generate.
235 * The 'large' preview of that same file is 161KB — ~119x smaller than
236 * making each visitor download the document.
237 *
238 * Returns '' when the PDF is not a library attachment (external URL) or
239 * has no generated preview; callers then fall back to the canvas path.
240 *
241 * @param string $url Absolute URL of the PDF.
242 * @param string $size Registered image size to prefer.
243 * @return string Image URL, or '' when none is available.
244 */
245 public static function get_pdf_poster_url($url, $size = 'large')
246 {
247 if (empty($url)) {
248 return '';
249 }
250
251 $attachment_id = attachment_url_to_postid($url);
252 if (!$attachment_id) {
253 return '';
254 }
255
256 if ('application/pdf' !== get_post_mime_type($attachment_id)) {
257 return '';
258 }
259
260 // A thumbnail previously generated by Pdf_Thumbnail_Handler (Imagick
261 // fallback path) is stored as a separate attachment; prefer it, since
262 // its existence means WP's own preview was missing at the time.
263 $cached_id = get_post_meta($attachment_id, '_ep_pdf_thumbnail_id', true);
264 if ($cached_id) {
265 $cached_url = wp_get_attachment_url($cached_id);
266 if ($cached_url) {
267 return apply_filters('embedpress_pdf_poster_url', $cached_url, $url, $attachment_id);
268 }
269 // Stale pointer — the thumbnail attachment is gone.
270 delete_post_meta($attachment_id, '_ep_pdf_thumbnail_id');
271 }
272
273 // WP's upload-time preview. Falls back through progressively smaller
274 // sizes; a PDF uploaded before the size was registered may only have
275 // some of them.
276 foreach ([$size, 'large', 'medium', 'thumbnail', 'full'] as $candidate) {
277 $src = wp_get_attachment_image_src($attachment_id, $candidate);
278 if ($src && !empty($src[0])) {
279 return apply_filters('embedpress_pdf_poster_url', $src[0], $url, $attachment_id);
280 }
281 }
282
283 // Nothing exists yet: this PDF predates upload-time poster generation,
284 // or its upload ran before the feature shipped. Generate once now so
285 // the render surfaces stop falling back to the in-browser <canvas>
286 // renderer, which tears/flips the preview on large documents
287 // (FB #83295). Rate-limited internally, so a host that cannot
288 // rasterise PDFs pays for this at most once a week per attachment.
289 $generated_id = Pdf_Thumbnail_Handler::get_or_create_poster($attachment_id);
290 if ($generated_id) {
291 $generated_url = wp_get_attachment_url($generated_id);
292 if ($generated_url) {
293 return apply_filters('embedpress_pdf_poster_url', $generated_url, $url, $attachment_id);
294 }
295 }
296
297 return apply_filters('embedpress_pdf_poster_url', '', $url, $attachment_id);
298 }
299
300 public static function is_instagram_feed($url)
301 {
302 return (bool) preg_match('~(?:https?://)?(?:www\.)?instagram\.com/(?!p/|reel/|tv/|stories/)[^/]+/?$~i', (string) $url);
303 }
304
305 public static function is_file_url($url)
306 {
307 $pattern = '/\.([0-9a-z]+)(?=[?#])|(\.)(?:[\w]+)$/i';
308 return preg_match($pattern, $url) === 1;
309 }
310
311 public static function is_opensea($url)
312 {
313 return strpos($url, "opensea.io") !== false;
314 }
315
316 /**
317 * Reject the three internal IPv4 ranges that WordPress core's own SSRF guard
318 * (wp_http_validate_url) leaves reachable: link-local (169.254/16, incl. the
319 * 169.254.169.254 cloud-metadata endpoint), CGNAT (100.64/10) and benchmark
320 * (198.18/15). Core already rejects loopback, RFC1918 and every IPv6 form, so
321 * this only fills the IPv4 gap — using the same octet check core uses itself.
322 *
323 * Core's `http_request_host_is_external` filter only fires for IPs core
324 * already suspects, so it can't cover these; `pre_http_request` fires for every
325 * request, guarding every wp_remote / wp_safe_remote call (including redirect
326 * hops). Resolving the host with gethostbyname mirrors core and normalises
327 * hostnames + integer-encoded literals to a dotted quad.
328 *
329 * @param false|array|\WP_Error $preempt Short-circuit value (false to proceed).
330 * @param array $args Request args (unused).
331 * @param string $url The request URL.
332 * @return false|\WP_Error
333 */
334 public static function block_internal_http_requests($preempt, $args, $url)
335 {
336 if (false !== $preempt) {
337 return $preempt; // a prior hook already decided
338 }
339
340 $host = wp_parse_url($url, PHP_URL_HOST);
341 if (empty($host)) {
342 return $preempt;
343 }
344 $host = rtrim(trim($host, '[]'), '.'); // drop IPv6 brackets + trailing FQDN dot
345
346 // Resolve to an IPv4 address, exactly as core's wp_http_validate_url does.
347 $ip = preg_match('/^(\d{1,3}\.){3}\d{1,3}$/', $host) ? $host : gethostbyname($host);
348 if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
349 return $preempt; // not IPv4 (IPv6 is core's job) or unresolved
350 }
351
352 $p = array_map('intval', explode('.', $ip));
353 $internal =
354 (169 === $p[0] && 254 === $p[1]) // link-local 169.254/16
355 || (100 === $p[0] && 64 <= $p[1] && 127 >= $p[1]) // CGNAT 100.64/10
356 || (198 === $p[0] && (18 === $p[1] || 19 === $p[1])); // benchmark 198.18/15
357
358 if ($internal) {
359 return new \WP_Error(
360 'http_request_blocked',
361 __('Requests to internal hosts are not allowed.', 'embedpress')
362 );
363 }
364
365 return $preempt;
366 }
367
368 public static function is_youtube_channel($url)
369 {
370 return (bool) (preg_match('~(?:https?:\/\/)?(?:www\.)?(?:youtube.com\/)(?:channel\/|c\/|user\/|@)(\w+)~i', (string) $url));
371 }
372
373 public static function is_youtube_playlist($url)
374 {
375 // /playlist?list=PL… OR /watch?…&list=… (Mix radio, video-in-playlist).
376 $u = str_replace(['&#038;', '&amp;'], '&', (string) $url);
377 return (bool) (preg_match('~^https?://(?:www\.)?youtube\.com/(?:playlist|watch)\?(?:[^#]*&)?list=([\w-]+)~i', $u));
378 }
379
380 public static function is_youtube_channel_or_playlist($url)
381 {
382 return self::is_youtube_channel($url) || self::is_youtube_playlist($url);
383 }
384
385 public static function is_youtube($url)
386 {
387 return (bool) (preg_match('~(?:https?://)?(?:www\.)?(?:youtube\.com|youtu\.be)/watch\?v=([^&]+)~i', (string) $url));
388 }
389
390 // Saved sources data temporary in wp_options table
391 public static function get_source_data($blockid, $source_url, $source_option_name, $source_temp_option_name)
392 {
393 if (self::is_youtube_channel($source_url)) {
394 $source_name = 'YoutubeChannel';
395 } else if (self::is_youtube($source_url)) {
396 $source_name = 'Youtube';
397 } else if (!empty(self::is_file_url($source_url))) {
398 $source_name = 'document_' . self::get_extension_from_file_url($source_url);
399 } else if (self::is_opensea($source_url)) {
400 $source_name = 'OpenSea';
401 } else if (self::is_instagram_feed($source_url)) {
402 $source_name = 'InstagramFeed';
403 } else {
404 Shortcode::get_embera_instance();
405 $collectios = Shortcode::get_collection();
406 $provider = $collectios->findProviders($source_url);
407
408 if (!empty($provider[$source_url])) {
409 $source_name = $provider[$source_url]->getProviderName();
410 } else {
411 $host = parse_url($source_url, PHP_URL_HOST);
412 if ($host) {
413 $parts = explode('.', $host);
414 if (count($parts) > 1) {
415 $source_name = $parts[1];
416 } else {
417 // Handle the case where the host doesn't have at least two parts
418 $source_name = $host;
419 }
420 } else {
421 // Handle the case where parse_url fails
422 $source_name = 'unknown';
423 }
424 }
425 }
426
427 if (!empty($blockid) && $blockid != 'undefined') {
428 $sources = json_decode(get_option($source_temp_option_name), true);
429
430 if (!$sources) {
431 $sources = array();
432 }
433 $exists = false;
434
435 foreach ($sources as $i => $source) {
436 if ($source['id'] === $blockid) {
437 $sources[$i]['source']['name'] = $source_name;
438 $sources[$i]['source']['url'] = $source_url;
439 $exists = true;
440 break;
441 }
442 }
443
444 if (!$exists) {
445 $sources[] = array('id' => $blockid, 'source' => array('name' => $source_name, 'url' => $source_url, 'count' => 1));
446 }
447
448 update_option($source_temp_option_name, json_encode($sources));
449 }
450 }
451
452 /**
453 * One-time scan of existing post content to seed the source-data options.
454 *
455 * Uses two SQL-prefiltered queries (post_content + _elementor_data postmeta)
456 * so a site with thousands of posts only loads the rows that actually
457 * mention "embedpress" — avoiding the N+1 round-trips of get_posts() +
458 * per-post get_post_field() / get_post_meta().
459 */
460 public static function seed_source_data_from_existing_content()
461 {
462 if (get_option('embedpress_source_data_seeded')) {
463 return;
464 }
465
466 global $wpdb;
467
468 $limit = (int) apply_filters('embedpress_seed_source_data_limit', 2000);
469 $like = '%' . $wpdb->esc_like('embedpress') . '%';
470
471 // Posts whose content mentions embedpress (blocks or shortcodes).
472 $content_rows = $wpdb->get_results($wpdb->prepare(
473 "SELECT ID, post_content
474 FROM {$wpdb->posts}
475 WHERE post_status IN ('publish','draft','private','future','pending')
476 AND post_type NOT IN ('revision','attachment','nav_menu_item','customize_changeset','oembed_cache')
477 AND post_content LIKE %s
478 LIMIT %d",
479 $like,
480 $limit
481 ));
482
483 if ($content_rows) {
484 foreach ($content_rows as $row) {
485 $post_id = (int) $row->ID;
486 $content = (string) $row->post_content;
487
488 // Gutenberg: <!-- wp:embedpress/* {"url":"..."} -->
489 if (preg_match_all('/wp:embedpress[^>]*?"url"\s*:\s*"([^"]+)"/', $content, $m)) {
490 foreach ($m[1] as $i => $url) {
491 self::get_source_data(
492 md5("seed-{$post_id}-g-{$i}"),
493 wp_unslash($url),
494 'gutenberg_source_data',
495 'gutenberg_temp_source_data'
496 );
497 }
498 }
499
500 // Shortcode: [embedpress ...]URL[/embedpress]
501 if (preg_match_all('/\[embedpress[^\]]*\](.*?)\[\/embedpress\]/s', $content, $m)) {
502 foreach ($m[1] as $i => $url) {
503 $url = trim(wp_unslash($url));
504 if ($url === '') continue;
505 self::get_source_data(
506 md5("seed-{$post_id}-s-{$i}"),
507 $url,
508 'gutenberg_source_data',
509 'gutenberg_temp_source_data'
510 );
511 }
512 }
513 }
514 }
515
516 // Elementor widgets — postmeta-level prefilter.
517 $meta_rows = $wpdb->get_results($wpdb->prepare(
518 "SELECT post_id, meta_value
519 FROM {$wpdb->postmeta}
520 WHERE meta_key = %s
521 AND meta_value LIKE %s
522 LIMIT %d",
523 '_elementor_data',
524 $like,
525 $limit
526 ));
527
528 if ($meta_rows) {
529 foreach ($meta_rows as $row) {
530 $post_id = (int) $row->post_id;
531 $el = (string) $row->meta_value;
532 if (preg_match_all('/"widgetType":"embedpress[^"]*".*?"(?:embedpress_pro_embeded_link|embed_url|url|pdf_source|src)":"([^"]+)"/', $el, $m)) {
533 foreach ($m[1] as $i => $url) {
534 self::get_source_data(
535 md5("seed-{$post_id}-e-{$i}"),
536 stripslashes($url),
537 'elementor_source_data',
538 'elementor_temp_source_data'
539 );
540 }
541 }
542 }
543 }
544
545 // Promote temp → real without needing save_post.
546 self::get_save_source_data_on_post_update('gutenberg_source_data', 'gutenberg_temp_source_data');
547 self::get_save_source_data_on_post_update('elementor_source_data', 'elementor_temp_source_data');
548
549 update_option('embedpress_source_data_seeded', time(), false);
550 }
551
552 // Saved source data when post updated
553 public static function get_save_source_data_on_post_update($source_option_name, $source_temp_option_name)
554 {
555
556 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
557 return;
558 }
559 $temp_data = json_decode(get_option($source_temp_option_name), true);
560 $source_data = json_decode(get_option($source_option_name), true);
561 if (!$temp_data) {
562 $temp_data = array();
563 }
564 if (!$source_data) {
565 $source_data = array();
566 }
567
568 $sources = array_merge($temp_data, $source_data);
569
570 $unique_sources = array();
571 foreach ($sources as $source) {
572 $unique_sources[$source['id']] = $source;
573 }
574
575 $unique_sources = array_values($unique_sources);
576
577 delete_option($source_temp_option_name);
578
579 update_option($source_option_name, json_encode($unique_sources));
580 }
581
582 //Delete source temporary data when reload without update or publish
583 public static function get_delete_source_temp_data_on_reload($source_temp_option_name)
584 {
585 $source_temp_data = json_decode(get_option($source_temp_option_name), true);
586 if ($source_temp_data) {
587 delete_option($source_temp_option_name);
588 }
589 }
590
591 public static function get_file_title($url)
592 {
593 return get_the_title(attachment_url_to_postid($url));
594 }
595
596 public static function get_hash()
597 {
598 $hash_key = get_option(EMBEDPRESS_PLG_NAME . '_hash_key');
599 if (!$hash_key) {
600 $hash_key = wp_hash_password(wp_generate_password(30));
601 update_option(EMBEDPRESS_PLG_NAME . '_hash_key', $hash_key);
602 }
603 return $hash_key;
604 }
605
606 public function lock_content_form_handler() {
607 // print_r($embedHTML);
608
609 $client_id = isset($_POST['client_id']) ? sanitize_text_field($_POST['client_id']) : '';
610 $password = isset($_POST['password']) ? sanitize_text_field($_POST['password']) : '';
611 $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
612
613 $epbase64 = get_post_meta( $post_id, 'ep_base_' .$client_id, true );
614 $hash_key = get_post_meta( $post_id, 'hash_key_' .$client_id, true );
615
616 // Set the decryption key and initialization vector (IV)
617 $key = Helper::get_hash();
618
619 // Decode the base64 encoded cipher
620 $cipher = base64_decode($epbase64);
621 // Decrypt the cipher using AES-128-CBC encryption
622
623 $wp_pass_key = hash('sha256', wp_salt(32) . md5($password));
624 $iv = substr($wp_pass_key, 0, 16);
625
626 if ($wp_pass_key === $hash_key) {
627
628 $embed = openssl_decrypt($cipher, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv) . '<script>
629 var now = new Date();
630 var time = now.getTime();
631 var expireTime = time + 1000 * 60 * 60 * 24 * 30;
632 now.setTime(expireTime);
633 document.cookie = "password_correct_'.esc_js($client_id).'='.esc_js($hash_key).'; expires=" + now.toUTCString() + "; path=/";
634 </script>';
635
636 }
637 else{
638 $embed = 0;
639 }
640
641 // Process the form data and return a response
642 $response = array(
643 'success' => true,
644 'password' => $password,
645 'embedHtml' => $embed,
646 'post_id' => $post_id
647 );
648
649 wp_send_json($response);
650
651 }
652
653 public function gutenberg_password_check()
654 {
655 $client_id = isset($_POST['client_id']) ? sanitize_text_field($_POST['client_id']) : '';
656 $password = isset($_POST['password']) ? sanitize_text_field($_POST['password']) : '';
657 $content_password = isset($_POST['content_password']) ? sanitize_text_field($_POST['content_password']) : '';
658
659 // Verify password
660 if ($password === $content_password) {
661 // Set cookie for authentication
662 $hash_pass = hash('sha256', wp_salt(32) . md5($password));
663 setcookie("password_correct_" . $client_id, $hash_pass, time() + 3600, '/');
664
665 $response = array(
666 'success' => true,
667 'message' => 'Password correct'
668 );
669 } else {
670 $response = array(
671 'success' => false,
672 'message' => 'Incorrect password'
673 );
674 }
675
676 wp_send_json($response);
677 }
678
679 public static function display_password_form($client_id = '', $embedHtml = '', $pass_hash_key = '', $attributes = [])
680 {
681 $lock_heading = !empty($attributes['lockHeading']) ? sanitize_text_field($attributes['lockHeading']) : '';
682 $lock_subheading = !empty($attributes['lockSubHeading']) ? sanitize_text_field($attributes['lockSubHeading']) : '';
683 $lock_error_message = !empty($attributes['lockErrorMessage']) ? sanitize_text_field($attributes['lockErrorMessage']) : '';
684 $footer_message = !empty($attributes['footerMessage']) ? sanitize_text_field($attributes['footerMessage']) : '';
685 $password_placeholder = !empty($attributes['passwordPlaceholder']) ? sanitize_text_field($attributes['passwordPlaceholder']) : '';
686 $button_text = !empty($attributes['submitButtonText']) ? sanitize_text_field($attributes['submitButtonText']) : '';
687 $unlocking_text = !empty($attributes['submitUnlockingText']) ? sanitize_text_field($attributes['submitUnlockingText']) : '';
688 $enable_footer_message = !empty($attributes['enableFooterMessage']) ? sanitize_text_field($attributes['enableFooterMessage']) : '';
689
690
691 // Set the encryption key and initialization vector (IV)
692 $key = self::get_hash();
693
694 $salt = wp_salt(32);
695 $wp_hash_key = hash('sha256', $salt . $pass_hash_key);
696 $iv = substr($wp_hash_key, 0, 16);
697
698
699 // Encrypt the plaintext using AES-128-CBC encryption
700 $cipher = openssl_encrypt($embedHtml, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
701
702 // Base64 encode the encrypted cipher
703 $encrypted_data = base64_encode($cipher);
704
705 update_post_meta(get_the_ID(), 'ep_base_' . $client_id, $encrypted_data);
706 update_post_meta(get_the_ID(), 'hash_key_' . $client_id, $wp_hash_key);
707
708 $lock_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g fill="#6354a5" class="color134563 svgShape"><path d="M46.3 28.7h-3v-6.4C43.3 16.1 38.2 11 32 11c-6.2 0-11.3 5.1-11.3 11.3v6.4h-3v-6.4C17.7 14.4 24.1 8 32 8s14.3 6.4 14.3 14.3v6.4" fill="#6354a5" class="color000000 svgShape"></path><path d="M44.8 55.9H19.2c-2.6 0-4.8-2.2-4.8-4.8V31.9c0-2.6 2.2-4.8 4.8-4.8h25.6c2.6 0 4.8 2.2 4.8 4.8v19.2c0 2.7-2.2 4.8-4.8 4.8zM19.2 30.3c-.9 0-1.6.7-1.6 1.6v19.2c0 .9.7 1.6 1.6 1.6h25.6c.9 0 1.6-.7 1.6-1.6V31.9c0-.9-.7-1.6-1.6-1.6H19.2z" fill="#6354a5" class="color000000 svgShape"></path><path d="M35.2 36.7c0 1.8-1.4 3.2-3.2 3.2s-3.2-1.4-3.2-3.2 1.4-3.2 3.2-3.2 3.2 1.5 3.2 3.2" fill="#6354a5" class="color000000 svgShape"></path><path d="M32.8 36.7h-1.6l-1.6 9.6h4.8l-1.6-9.6" fill="#6354a5" class="color000000 svgShape"></path></g></svg>';
709
710 echo '
711 <div class="password-form-container sd">
712 <h2>' . esc_html($lock_heading) . '</h2>
713 <p>' . esc_html($lock_subheading) . ' </p>
714 <form class="password-form" method="post" class="password-form" data-unlocking-text="' . esc_attr($unlocking_text) . '">
715
716 <div class="password-field">
717 <span class="lock-icon">' . $lock_icon . '</span>
718 <input type="password" name="pass_' . esc_attr($client_id) . '" placeholder="' . esc_attr($password_placeholder) . '" required>
719 </div>
720 <input type="hidden" name="ep_client_id" value="' . esc_attr($client_id) . '">
721 <input type="hidden" name="post_id" value="' . esc_attr(get_the_ID()) . '">
722
723 <input type="submit" name="password_submit" value="' . esc_attr($button_text) . '">
724 <div class="error-message hidden">' . esc_html($lock_error_message) . '</div>
725 </form>
726 ' . (!empty($enable_footer_message) ? '<p class="need-access-message">' . esc_html($footer_message) . '</p>' : '') . '
727 </div>
728 ';
729 }
730
731 // Check if the user has already entered the correct password
732 public static function is_password_correct($client_id)
733 {
734 if (isset($_COOKIE['password_correct_' . $client_id])) {
735 return $_COOKIE['password_correct_' . $client_id];
736 } else {
737 return false;
738 }
739 }
740
741 public static function customLogo($embedHTML, $atts)
742 {
743 $x = !empty($atts['logoX']) ? $atts['logoX'] : 0;
744 $y = !empty($atts['logoY']) ? $atts['logoY'] : 0;
745 $uniqid = !empty($atts['url']) ? '.ose-uid-' . md5($atts['url']) : '';
746
747 $brandUrl = !empty($atts['customlogoUrl']) ? $atts['customlogoUrl'] : '';
748 $opacity = !empty($atts['logoOpacity']) ? $atts['logoOpacity'] : '';
749
750 $cssClass = !empty($atts['url']) ? '.ose-uid-' . md5($atts['url']) : '.ose-youtube';
751
752
753
754 ob_start(); ?>
755 <style type="text/css">
756 <?php echo esc_html($cssClass); ?> {
757 position: relative;
758 }
759
760 <?php echo esc_html($cssClass); ?>.watermark {
761 border: 0;
762 position: absolute;
763 bottom: <?php echo esc_html($y); ?>%;
764 right: <?php echo esc_html($x); ?>%;
765 max-width: 150px;
766 max-height: 75px;
767 opacity: 0.25;
768 z-index: 5;
769 -o-transition: opacity 0.5s ease-in-out;
770 -moz-transition: opacity 0.5s ease-in-out;
771 -webkit-transition: opacity 0.5s ease-in-out;
772 transition: opacity 0.5s ease-in-out;
773 opacity: <?php echo esc_html($opacity); ?>;
774 }
775
776 <?php echo esc_html($cssClass); ?>.watermark:hover {
777 opacity: 1;
778 }
779 </style>
780 <?php
781
782
783 $style = ob_get_clean();
784
785 if (!class_exists('\simple_html_dom')) {
786 include_once EMBEDPRESS_PATH_CORE . 'simple_html_dom.php';
787 }
788
789 $cta = '';
790 $img = '';
791
792 if (!empty($atts['customlogo'])) {
793 $img = '<img src="' . esc_url($atts['customlogo']) . '"/>';
794
795 $imgDom = str_get_html($img);
796 $imgDom = $imgDom->find('img', 0);
797 $imgDom->setAttribute('class', 'watermark ep-custom-logo');
798 $imgDom->removeAttribute('style');
799 $imgDom->setAttribute('width', 'auto');
800 $imgDom->setAttribute('height', 'auto');
801 ob_start();
802 echo $imgDom;
803
804 $cta .= ob_get_clean();
805
806 $imgDom->clear();
807 unset($img, $imgDom);
808
809 if (!empty($brandUrl)) {
810 $cta = '<a href="' . esc_url($brandUrl) . '" target="_blank">' . $cta . '</a>';
811 }
812 $dom = str_get_html($embedHTML);
813
814 $wrapDiv = $dom->find($uniqid, 0);
815
816 if (!empty($wrapDiv) && is_object($wrapDiv)) {
817 $wrapDiv->innertext .= $cta;
818 }
819
820 ob_start();
821 echo $wrapDiv;
822
823 $markup = ob_get_clean();
824
825 $dom->clear();
826 unset($dom, $wrapDiv);
827
828 $embedHTML = $style . $markup;
829 }
830
831 return $embedHTML;
832 }
833
834
835 public static function embed_content_share($content_id = '', $attributes = [])
836 {
837 $share_position = !empty($attributes['sharePosition']) ? $attributes['sharePosition'] : 'right';
838 $custom_thumnail = !empty($attributes['customThumbnail']) ? $attributes['customThumbnail'] : '';
839 $custom_title = !empty($attributes['customTitle']) ? $attributes['customTitle'] : '';
840 $custom_description = !empty($attributes['customDescription']) ? $attributes['customDescription'] : '';
841
842 // Create a unique hash based on content attributes
843 $content_hash = md5($custom_thumnail . $custom_title . $custom_description . $content_id);
844
845 // Encode for URL usage
846 $custom_thumnail = urlencode($custom_thumnail);
847 $custom_title = urlencode($custom_title);
848 $custom_description = urlencode($custom_description);
849
850 // Get social share options with defaults
851 $facebook_enabled = isset($attributes['shareFacebook']) ? $attributes['shareFacebook'] !== false : true;
852 $twitter_enabled = isset($attributes['shareTwitter']) ? $attributes['shareTwitter'] !== false : true;
853 $pinterest_enabled = isset($attributes['sharePinterest']) ? $attributes['sharePinterest'] !== false : true;
854 $linkedin_enabled = isset($attributes['shareLinkedin']) ? $attributes['shareLinkedin'] !== false : true;
855 $style = isset($attributes['width']) ? 'style="max-width: ' . esc_attr($attributes['width']) . 'px;"' : '';
856
857
858 $page_url = urlencode(get_permalink() . '?hash=' . $content_id . '&unique=' . $content_hash);
859
860 $social_icons = '<div class="ep-social-share-wraper"' . $style . ' ><div class="ep-social-share share-position-' . esc_attr($share_position) . '">';
861
862
863 // Facebook
864 if ($facebook_enabled) {
865 $social_icons .= '<a href="https://www.facebook.com/sharer/sharer.php?u=' . $page_url . '" class="ep-social-icon facebook" target="_blank">
866 <svg width="64px" height="64px" fill="#000000" viewBox="0 -6 512 512" xmlns="http://www.w3.org/2000/svg">
867 <path d="M0 0h512v500H0z" fill="#475a96"/>
868 <path d="m375.72 112.55h-237.43c-8.137 0-14.73 6.594-14.73 14.73v237.43c0 8.135 6.594 14.73 14.73 14.73h127.83v-103.36h-34.781v-40.28h34.781v-29.705c0-34.473 21.055-53.244 51.807-53.244 14.73 0 27.391 1.097 31.08 1.587v36.026l-21.328 0.01c-16.725 0-19.963 7.947-19.963 19.609v25.717h39.887l-5.193 40.28h-34.693v103.36h68.012c8.135 0 14.73-6.596 14.73-14.73v-237.43c-1e-3 -8.137-6.596-14.73-14.731-14.73z" fill="#fff"/>
869 </svg>
870 </a>';
871 }
872
873 // Twitter
874 if ($twitter_enabled) {
875 $social_icons .= '<a href="https://twitter.com/intent/tweet?url=' . $page_url . '&text=' . $custom_title . '" class="ep-social-icon twitter" target="_blank">
876 <svg viewBox="0 0 24 24" aria-hidden="true" fill="#fff" class="r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-lrsllp r-1nao33i r-16y2uox r-8kz0gk"><g><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path></g></svg>
877 </a>';
878 }
879
880 // Pinterest
881 if ($pinterest_enabled) {
882 $social_icons .= '<a href="http://pinterest.com/pin/create/button/?url=' . $page_url . '&media=' . $custom_thumnail . '&description=' . $custom_description . '" class="ep-social-icon pinterest" target="_blank">
883
884 <svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-36.42015 -60.8 315.6413 364.8"><path d="M121.5 0C54.4 0 0 54.4 0 121.5 0 173 32 217 77.2 234.7c-1.1-9.6-2-24.4.4-34.9 2.2-9.5 14.2-60.4 14.2-60.4s-3.6-7.3-3.6-18c0-16.9 9.8-29.5 22-29.5 10.4 0 15.4 7.8 15.4 17.1 0 10.4-6.6 26-10.1 40.5-2.9 12.1 6.1 22 18 22 21.6 0 38.2-22.8 38.2-55.6 0-29.1-20.9-49.4-50.8-49.4-34.6 0-54.9 25.9-54.9 52.7 0 10.4 4 21.6 9 27.7 1 1.2 1.1 2.3.8 3.5-.9 3.8-3 12.1-3.4 13.8-.5 2.2-1.8 2.7-4.1 1.6-15.2-7.1-24.7-29.2-24.7-47.1 0-38.3 27.8-73.5 80.3-73.5 42.1 0 74.9 30 74.9 70.2 0 41.9-26.4 75.6-63 75.6-12.3 0-23.9-6.4-27.8-14 0 0-6.1 23.2-7.6 28.9-2.7 10.6-10.1 23.8-15.1 31.9 11.4 3.5 23.4 5.4 36 5.4 67.1 0 121.5-54.4 121.5-121.5C243 54.4 188.6 0 121.5 0z" fill="#fff"/></svg>
885
886 </a>';
887 }
888
889 // LinkedIn
890 if ($linkedin_enabled) {
891 $social_icons .= '<a href="https://www.linkedin.com/shareArticle?mini=true&url=' . $page_url . '" class="ep-social-icon linkedin" target="_blank">
892
893 <svg fill="#ffffff" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
894 viewBox="0 0 310 310" xml:space="preserve">
895 <g id="XMLID_801_">
896 <path id="XMLID_802_" d="M72.16,99.73H9.927c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5H72.16c2.762,0,5-2.238,5-5V104.73
897 C77.16,101.969,74.922,99.73,72.16,99.73z"/>
898 <path id="XMLID_803_" d="M41.066,0.341C18.422,0.341,0,18.743,0,41.362C0,63.991,18.422,82.4,41.066,82.4
899 c22.626,0,41.033-18.41,41.033-41.038C82.1,18.743,63.692,0.341,41.066,0.341z"/>
900 <path id="XMLID_804_" d="M230.454,94.761c-24.995,0-43.472,10.745-54.679,22.954V104.73c0-2.761-2.238-5-5-5h-59.599
901 c-2.762,0-5,2.239-5,5v199.928c0,2.762,2.238,5,5,5h62.097c2.762,0,5-2.238,5-5v-98.918c0-33.333,9.054-46.319,32.29-46.319
902 c25.306,0,27.317,20.818,27.317,48.034v97.204c0,2.762,2.238,5,5,5H305c2.762,0,5-2.238,5-5V194.995
903 C310,145.43,300.549,94.761,230.454,94.761z"/>
904 </g>
905 </svg>
906 </a>';
907 }
908
909 $social_icons .= '</div></div>';
910
911 return $social_icons;
912 }
913
914 public static function ep_get_elementor_widget_settings($page_settings = '', $id = '', $widgetType = '')
915 {
916 if (empty($page_settings)) {
917 return [];
918 }
919
920 // Handle both string and array cases for elementor data
921 if (is_string($page_settings)) {
922 $data = json_decode($page_settings, true);
923 } else {
924 // Data might already be an array in older versions
925 $data = $page_settings;
926 }
927 $element_setting = null;
928
929 Plugin::$instance->db->iterate_data($data, function ($element) use (&$element_setting, $widgetType, $id) {
930
931 if ($element['id'] == $id && $element['elType'] == 'widget' && $element['widgetType'] == $widgetType) {
932 $element_setting[] = $element['settings'];
933 }
934 });
935
936 return $element_setting;
937 }
938
939
940
941 public static function ep_get_popup_icon()
942 {
943 $svg = '<div class="ep-doc-popup-icon" ><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" xml:space="preserve"><path fill="#fff" d="M5 3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-6l-2-2v8H5V5h8l-2-2H5zm9 0 2.7 2.7-7.5 7.5 1.7 1.7 7.5-7.5L21 10V3h-7z"/><path style="fill:none" d="M0 0h24v24H0z"/></svg></div>';
944
945 return $svg;
946 }
947 public static function ep_get_download_icon()
948 {
949 $svg = '<div class="ep-doc-download-icon" ><svg width="25" height="25" viewBox="0 0 0.6 0.6" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-rule="evenodd" d="M.525.4A.025.025 0 0 1 .55.422v.053A.075.075 0 0 1 .479.55H.125A.075.075 0 0 1 .05.479V.425A.025.025 0 0 1 .1.422v.053A.025.025 0 0 0 .122.5h.353A.025.025 0 0 0 .5.478V.425A.025.025 0 0 1 .525.4ZM.3.05a.025.025 0 0 1 .025.025v.24L.357.283A.025.025 0 0 1 .39.281l.002.002a.025.025 0 0 1 .002.033L.392.318.317.393.316.394.314.395.311.397.308.398.305.399.301.4H.295L.292.399.289.398.287.397.285.395A.025.025 0 0 1 .283.393L.208.318A.025.025 0 0 1 .241.281l.002.002.032.032v-.24A.025.025 0 0 1 .3.05Z"/></svg></div>';
950
951 return $svg;
952 }
953
954 public static function ep_get_print_icon()
955 {
956 $svg = '<div class="ep-doc-print-icon" ><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24">
957 <path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z" fill="#fff"/>
958 </svg></div>';
959
960 return $svg;
961 }
962
963 public static function ep_get_fullscreen_icon()
964 {
965 $svg = '<div class="ep-doc-fullscreen-icon"><svg width="25" height="25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
966 <path d="m3 15 .117.007a1 1 0 0 1 .876.876L4 16v4h4l.117.007a1 1 0 0 1 0 1.986L8 22H3l-.117-.007a1 1 0 0 1-.876-.876L2 21v-5l.007-.117a1 1 0 0 1 .876-.876L3 15Zm18 0a1 1 0 0 1 .993.883L22 16v5a1 1 0 0 1-.883.993L21 22h-5a1 1 0 0 1-.117-1.993L16 20h4v-4a1 1 0 0 1 .883-.993L21 15ZM8 2a1 1 0 0 1 .117 1.993L8 4H4v4a1 1 0 0 1-.883.993L3 9a1 1 0 0 1-.993-.883L2 8V3a1 1 0 0 1 .883-.993L3 2h5Zm13 0 .117.007a1 1 0 0 1 .876.876L22 3v5l-.007.117a1 1 0 0 1-.876.876L21 9l-.117-.007a1 1 0 0 1-.876-.876L20 8V4h-4l-.117-.007a1 1 0 0 1 0-1.986L16 2h5Z" fill="#fff"/>
967 </svg></div>';
968
969 return $svg;
970 }
971 public static function ep_get_minimize_icon()
972 {
973 $svg = '<div class="ep-doc-minimize-icon" style="display:none"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" style="enable-background:new 0 0 385.331 385.331" xml:space="preserve" width="20" height="20"><path fill="#fff" d="M13.751 8.131h5.62c0.355 0 0.619 -0.28 0.619 -0.634 0 -0.355 -0.265 -0.615 -0.619 -0.614h-4.995V1.878c0 -0.355 -0.27 -0.624 -0.624 -0.624s-0.624 0.27 -0.624 0.624v5.62c0 0.002 0.001 0.003 0.001 0.004 0 0.002 -0.001 0.003 -0.001 0.005 0 0.348 0.276 0.625 0.624 0.624zM6.244 1.259c-0.354 0 -0.614 0.265 -0.614 0.619v4.995H0.624c-0.355 0 -0.624 0.27 -0.624 0.624 0 0.355 0.27 0.624 0.624 0.624h5.62c0.002 0 0.003 -0.001 0.004 -0.001 0.002 0 0.003 0.001 0.005 0.001 0.348 0 0.624 -0.276 0.624 -0.624V1.878c0 -0.354 -0.28 -0.619 -0.634 -0.619zm0.005 10.61H0.629c-0.355 0.001 -0.619 0.28 -0.619 0.634 0 0.355 0.265 0.615 0.619 0.614h4.995v5.005c0 0.355 0.27 0.624 0.624 0.624 0.355 0 0.624 -0.27 0.624 -0.624V12.502c0 -0.002 -0.001 -0.003 -0.001 -0.004 0 -0.002 0.001 -0.003 0.001 -0.005 0 -0.348 -0.276 -0.624 -0.624 -0.624zm13.127 0H13.756c-0.002 0 -0.003 0.001 -0.004 0.001 -0.002 0 -0.003 -0.001 -0.005 -0.001 -0.348 0 -0.624 0.276 -0.624 0.624v5.62c0 0.355 0.28 0.619 0.634 0.619 0.354 0.001 0.614 -0.265 0.614 -0.619v-4.995H19.376c0.355 0 0.624 -0.27 0.624 -0.624s-0.27 -0.624 -0.624 -0.625z"/><g/><g/><g/><g/><g/><g/></svg></div>';
974
975 return $svg;
976 }
977 public static function ep_get_draw_icon()
978 {
979 $svg = '<div class="ep-doc-draw-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m15 7.5 2.5 2.5m-10 10L19.25 8.25c0.69 -0.69 0.69 -1.81 0 -2.5v0c-0.69 -0.69 -1.81 -0.69 -2.5 0L5 17.5V20h2.5Zm0 0h8.379C17.05 20 18 19.05 18 17.879v0c0 -0.563 -0.224 -1.103 -0.621 -1.5L17 16M4.5 5c2 -2 5.5 -1 5.5 1 0 2.5 -6 2.5 -6 5 0 0.876 0.533 1.526 1.226 2" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
980
981 return $svg;
982 }
983
984 public static function get_insta_video_icon()
985 {
986 $svg = '<svg class="insta-video-icon" aria-label="Clip" class="x1lliihq x1n2onr6" color="#FFF" fill="#FFF" height="20" viewBox="1.111 1.111 24.444 24.447" width="20">
987 <path d="m14.248 1.111 3.304 5.558h-6.2L8.408 1.146c.229-.014.466-.024.713-.03l.379-.005Zm2.586 0h.331c3.4 0 4.964.838 6.267 2.097a6.674 6.674 0 0 1 1.773 3.133l.078.328H20.14l-3.307-5.558ZM6.093 1.53l2.74 5.139H1.382a6.678 6.678 0 0 1 4.38-5.033ZM16.91 15.79l-5.05-2.916a1.01 1.01 0 0 0-1.507.742l-.009.133v5.831a1.011 1.011 0 0 0 1.394.933l.121-.059 5.05-2.916a1.01 1.01 0 0 0 .111-1.674l-.111-.076-5.05-2.916ZM1.132 8.891h24.404l.017.4.003.21v7.666c0 3.401-.839 4.966-2.098 6.267-1.279 1.238-2.778 2.062-5.922 2.121l-.371.003H9.501c-3.4 0-4.963-.839-6.267-2.099-1.238-1.278-2.06-2.776-2.12-5.922l-.003-.37V9.501l.003-.21Z" fill-rule="evenodd" /></svg>';
988
989 return $svg;
990 }
991
992 public static function get_insta_image_carousel_icon()
993 {
994 $svg = '<svg aria-label="Carousel" class="x1lliihq x1n2onr6" color="#FFF" fill="#FFF" height="25" viewBox="0 0 43.636 43.636" width="25">
995 <path d="M31.636 27V10a4.695 4.695 0 0 0-4.727-4.727H10A4.695 4.695 0 0 0 5.273 10v17A4.695 4.695 0 0 0 10 31.727h17c2.545-.091 4.636-2.182 4.636-4.727zm4-13.364v14.636c0 4.091-3.364 7.455-7.455 7.455H13.545c-.545 0-.818.636-.455 1 .909 1 2.182 1.636 3.727 1.636h12.182a9.35 9.35 0 0 0 9.364-9.364V16.818a5.076 5.076 0 0 0-1.636-3.727c-.455-.364-1.091 0-1.091.545z" /></svg>';
996
997 return $svg;
998 }
999
1000 public static function get_insta_image_icon()
1001 {
1002 $svg = '<svg width="22" height="22" viewBox="0 0 0.6 0.6" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M.175.05A.125.125 0 0 0 .05.175v.25A.125.125 0 0 0 .175.55h.25A.125.125 0 0 0 .55.425v-.25A.125.125 0 0 0 .425.05h-.25ZM.2.225a.025.025 0 1 1 .05 0 .025.025 0 0 1-.05 0ZM.225.15a.075.075 0 1 0 0 .15.075.075 0 0 0 0-.15Zm.138.205A.025.025 0 0 1 .398.351l.048.042A.025.025 0 1 0 .479.355L.43.312a.075.075 0 0 0-.107.011l-.04.05a.024.024 0 0 1-.032.005.074.074 0 0 0-.099.015L.118.432a.025.025 0 0 0 .038.033l.035-.04A.024.024 0 0 1 .223.42.074.074 0 0 0 .322.405l.04-.05Z" fill="#fff"/></svg>';
1003
1004 return $svg;
1005 }
1006
1007 public static function get_insta_like_icon()
1008 {
1009 $svg = '<svg version="1.1" id="Uploaded to svgrepo.com" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 0.8 0.8" xml:space="preserve"><path d="M.225.25C.225.264.214.275.2.275S.175.264.175.25.186.225.2.225.225.236.225.25zM.75.3C.75.453.589.582.485.65a1.06 1.06 0 0 1-.073.044.025.025 0 0 1-.024 0A1.049 1.049 0 0 1 .315.65C.211.582.05.453.05.3a.2.2 0 0 1 .2-.2.199.199 0 0 1 .15.068A.199.199 0 0 1 .55.1a.2.2 0 0 1 .2.2zM.25.25a.05.05 0 1 0-.1 0 .05.05 0 0 0 .1 0z" style="fill:#fff"/></svg>';
1010
1011 return $svg;
1012 }
1013 public static function get_insta_comment_icon()
1014 {
1015 $svg = '<svg fill="#fff" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 2.5 2.5" xml:space="preserve"><path d="M2.374.446a.063.063 0 0 0-.061-.057H.991a.063.063 0 0 0-.063.057H.927v.328h.559c.029 0 .053.022.056.051h.001v.731h.275l.162.162a.063.063 0 0 0 .116-.035v-.127h.217a.063.063 0 0 0 .06-.051h.002V.446h-.001z"/><path d="M1.361.899H.18A.056.056 0 0 0 .125.95v.946h.001a.057.057 0 0 0 .054.045h.194v.113a.057.057 0 0 0 .104.032l.145-.145h.738c.027 0 .05-.02.056-.045h.001V.95h-.001a.056.056 0 0 0-.056-.051z"/></svg>';
1016
1017 return $svg;
1018 }
1019 public static function get_instagram_icon()
1020 {
1021 $svg = '<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" xml:space="preserve" width="1285" height="400"><style>.st0{fill:none;stroke:#fff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}</style><path class="st0" d="M14.375 19.375h-8.75c-2.75 0-5-2.25-5-5v-8.75c0-2.75 2.25-5 5-5h8.75c2.75 0 5 2.25 5 5v8.75c0 2.75-2.25 5-5 5z"/><path class="st0" d="M14.375 10A4.375 4.375 0 0 1 10 14.375 4.375 4.375 0 0 1 5.625 10a4.375 4.375 0 0 1 8.75 0zm1.25-5.625A.625.625 0 0 1 15 5a.625.625 0 0 1-.625-.625.625.625 0 0 1 1.25 0z"/></svg>';
1022
1023 return $svg;
1024 }
1025
1026 public static function get_google_presentation_url($embedded_url)
1027 {
1028 $parsed_url = parse_url($embedded_url);
1029 $base_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
1030 $base_url = strtok($base_url, '?');
1031 $base_url = rtrim($base_url, '/');
1032 return $base_url;
1033 }
1034
1035 public static function check_media_format($url)
1036 {
1037 // Strip query/hash so signed streaming URLs (Mux, CloudFront) match.
1038 $path = preg_replace('/[?#].*$/', '', (string) $url);
1039 $pattern1 = '/\.(mp4|mov|avi|wmv|flv|mkv|webm|mpeg|mpg|m3u8|mpd)$/i';
1040 $pattern2 = '/\.(mp3|wav|ogg|aac)$/i';
1041
1042 $isVideo = preg_match($pattern1, $path);
1043 $isAudio = preg_match($pattern2, $path);
1044
1045 $is_self_hosted = false;
1046 $format = '';
1047
1048 if (!empty($isVideo) || !empty($isAudio)) {
1049 $is_self_hosted = true;
1050 if (!empty($isVideo)) {
1051 $format = 'video';
1052 } else if (!empty($isAudio)) {
1053 $format = 'audio';
1054 }
1055 }
1056
1057 if (!$is_self_hosted) {
1058 return [
1059 'selhosted' => false,
1060 ];
1061 }
1062
1063 return [
1064 'selhosted' => true,
1065 'format' => $format,
1066 ];
1067 }
1068
1069 // Ajax Methods get instagram feed data
1070 public function loadmore_data_handler()
1071 {
1072 $connected_account_type = isset($_POST['connected_account_type']) ? sanitize_text_field($_POST['connected_account_type']) : 'personal';
1073 $hashtag_id = isset($_POST['hashtag_id']) ? sanitize_text_field($_POST['hashtag_id']) : '';
1074 $feed_type = isset($_POST['feed_type']) ? sanitize_text_field($_POST['feed_type']) : 'user_aacount_type';
1075 $user_id = isset($_POST['user_id']) ? sanitize_text_field($_POST['user_id']) : '';
1076 $loadmore_key = isset($_POST['loadmore_key']) ? sanitize_text_field($_POST['loadmore_key']) : '';
1077 $nonce = isset($_POST['_nonce']) ? sanitize_text_field($_POST['_nonce']) : '';
1078 $params = isset($_POST['params']) ? sanitize_text_field($_POST['params']) : '';
1079 // $params = json_decode($params, true);
1080 $params = stripslashes($params); // Remove extra backslashes
1081 $params = json_decode($params, true);
1082
1083 // Verify nonce
1084 if (!wp_verify_nonce($nonce, 'ep_nonce')) {
1085 wp_send_json_error('Invalid nonce');
1086 wp_die(); // Terminate script execution if nonce is invalid
1087 }
1088
1089 $feeds_data = get_option('ep_instagram_feed_data');
1090 $user_data = $feeds_data[$user_id]['feed_userinfo'];
1091
1092 if ($feed_type == 'hashtag_type') {
1093 $feeds_data = get_option('ep_instagram_hashtag_feed');
1094 }
1095
1096
1097 $profile_picture_url = isset($user_data['profile_picture_url']) ? $user_data['profile_picture_url'] : '';
1098
1099 if ($feed_type === 'user_account_type' && isset($feeds_data[$loadmore_key]['feed_posts'])) {
1100 $feed_posts = $feeds_data[$loadmore_key]['feed_posts'];
1101 } else if ($feed_type === 'hashtag_type' && isset($feeds_data[$loadmore_key])) {
1102 $feed_posts = $feeds_data[$loadmore_key];
1103 } else {
1104 $feed_posts = ['error'];
1105 }
1106
1107
1108 if (is_array($feed_posts) && count($feed_posts) > 0) {
1109 $loaded_posts = isset($_POST['loaded_posts']) ? intval($_POST['loaded_posts']) : 0;
1110 $posts_per_page = isset($_POST['posts_per_page']) ? intval($_POST['posts_per_page']) : 0;
1111
1112 $post_index = $loaded_posts + 1;
1113 $start_index = $loaded_posts;
1114
1115 $next_posts = array_slice($feed_posts, $start_index, $posts_per_page);
1116
1117 ob_start();
1118
1119 if (is_array($next_posts) && count($next_posts) > 0) :
1120 foreach ($next_posts as $post) :
1121 $caption = !empty($post['caption']) ? $post['caption'] : '';
1122 $media_type = !empty($post['media_type']) ? $post['media_type'] : '';
1123 $media_url = !empty($post['media_url']) ? $post['media_url'] : '';
1124 $permalink = !empty($post['permalink']) ? $post['permalink'] : '';
1125 $timestamp = !empty($post['timestamp']) ? $post['timestamp'] : '';
1126 $username = !empty($post['username']) ? $post['username'] : '';
1127 $like_count = !empty($post['like_count']) ? $post['like_count'] : 0;
1128 $comments_count = !empty($post['comments_count']) ? $post['comments_count'] : 0;
1129
1130 $post['profile_picture_url'] = $profile_picture_url;
1131 $post['show_likes_count'] = isset($params['show_likes_count']) ? $params['show_likes_count'] : false;
1132 $post['show_comments_count'] = isset($params['show_comments_count']) ? $params['show_comments_count'] : false;
1133 $post['popup_follow_button'] = isset($params['popup_follow_button']) ? $params['popup_follow_button'] : true;
1134 $post['popup_follow_button_text'] = isset($params['popup_follow_button_text']) ? $params['popup_follow_button_text'] : 'Follow';
1135 ?>
1136
1137 <div class="insta-gallery-item cg-carousel__slide js-carousel__slide" data-insta-postid="<?php echo esc_attr($post['id']) ?>" data-postindex="<?php echo esc_attr($post_index); ?>" data-postdata="<?php echo htmlspecialchars(json_encode($post), ENT_QUOTES, 'UTF-8'); ?>" data-media-type="<?php echo esc_attr($media_type); ?>">
1138 <?php
1139
1140 if (!empty($hashtag_id) && $media_type == 'CAROUSEL_ALBUM') {
1141 if (isset($post['children']['data'][0]['media_url'])) {
1142 $hashtag_media_url = $post['children']['data'][0]['media_url'];
1143 $hashtag_media_type = $post['children']['data'][0]['media_type'];
1144
1145 if ($hashtag_media_type == 'VIDEO') {
1146 echo '<video class="insta-gallery-image" src="' . esc_url($hashtag_media_url) . '"></video>';
1147 } else {
1148 echo ' <img class="insta-gallery-image" src="' . esc_url($hashtag_media_url) . '" alt="' . esc_attr('image') . '">';
1149 }
1150 }
1151 } else {
1152 if ($media_type == 'VIDEO') {
1153 echo '<video class="insta-gallery-image" src="' . esc_url($media_url) . '"></video>';
1154 } else {
1155 echo ' <img class="insta-gallery-image" src="' . esc_url($media_url) . '" alt="' . esc_attr('image') . '">';
1156 }
1157 }
1158 ?>
1159
1160 <div class="insta-gallery-item-type">
1161 <div class="insta-gallery-item-type-icon">
1162 <?php
1163 if ($media_type == 'VIDEO') {
1164 echo Helper::get_insta_video_icon();
1165 } else if ($media_type == 'CAROUSEL_ALBUM') {
1166 echo Helper::get_insta_image_carousel_icon();
1167 } else {
1168 echo Helper::get_insta_image_icon();
1169 }
1170 ?>
1171 </div>
1172 </div>
1173 <div class="insta-gallery-item-info">
1174 <?php if (apply_filters('embedpress/is_allow_rander', false)): ?>
1175 <div class="insta-item-reaction-count">
1176 <div class="insta-gallery-item-likes">
1177 <?php echo Helper::get_insta_like_icon();
1178 echo esc_html($like_count); ?>
1179 </div>
1180 <div class="insta-gallery-item-comments">
1181 <?php echo Helper::get_insta_comment_icon();
1182 echo esc_html($comments_count); ?>
1183 </div>
1184 </div>
1185 <?php else : ?>
1186 <div class="insta-gallery-item-permalink">
1187 <?php echo Helper::get_instagram_icon(); ?>
1188 </div>
1189 <?php endif; ?>
1190 </div>
1191 </div>
1192
1193 <?php $post_index++;
1194 endforeach;
1195 endif;
1196
1197 $feed_item = ob_get_clean();
1198
1199 $next_start_index = $start_index + count($next_posts);
1200
1201 wp_send_json(array(
1202 'html' => $feed_item,
1203 'next_post_index' => $next_start_index,
1204 'total_feed_posts' => count($feed_posts)
1205 ));
1206 } else {
1207 wp_send_json('');
1208 }
1209 }
1210
1211 public static function getCalendlyUuid($url)
1212 {
1213 $pattern = '/\/([0-9a-fA-F-]+)$/';
1214 if (preg_match($pattern, $url, $matches)) {
1215 $uuid = $matches[1];
1216 return $uuid;
1217 }
1218 return '';
1219 }
1220
1221 public static function getCalendlyUserInfo($access_token)
1222 {
1223 $transient_name = 'calendly_user_info_' . $access_token;
1224 $user_info = get_transient($transient_name);
1225 if (false === $user_info) {
1226 $user_endpoint = 'https://api.calendly.com/users/me';
1227 $headers = array(
1228 'Authorization' => "Bearer $access_token",
1229 'Content-Type' => 'application/json',
1230 );
1231 $args = array(
1232 'headers' => $headers,
1233 );
1234 $response = wp_remote_get($user_endpoint, $args);
1235 if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
1236 $user_info = wp_remote_retrieve_body($response);
1237 set_transient($transient_name, $user_info, 3600);
1238 } else {
1239 return false;
1240 }
1241 }
1242
1243 return $user_info;
1244 }
1245
1246
1247
1248 public static function getCalaendlyEventTypes($user_uri, $access_token)
1249 {
1250 // Attempt to retrieve the data from the transient
1251 $events_list = get_transient('calendly_events_list_' . md5($access_token));
1252
1253 if (false === $events_list) {
1254 // If the data is not in the transient, fetch it from the API
1255 $events_endpoint = "https://api.calendly.com/event_types?user=$user_uri";
1256
1257 $headers = array(
1258 'Authorization' => "Bearer $access_token",
1259 'Content-Type' => 'application/json',
1260 );
1261
1262 $args = array(
1263 'headers' => $headers,
1264 );
1265
1266 $response = wp_remote_get($events_endpoint, $args);
1267
1268 if (!is_wp_error($response)) {
1269 $body = wp_remote_retrieve_body($response);
1270 $events_list = json_decode($body, true);
1271
1272 // Store the data in a transient for a specified time (e.g., 1 hour)
1273 set_transient('calendly_events_list', $events_list, HOUR_IN_SECONDS);
1274
1275 return $events_list;
1276 }
1277 }
1278
1279 return $events_list;
1280 }
1281
1282 public static function getListEventInvitee($uuid, $access_token)
1283 {
1284 // Attempt to retrieve the data from the transient
1285 $invitee_list = get_transient('calendly_invitee_list_' . md5($access_token));
1286
1287 if (false === $invitee_list) {
1288 // If the data is not in the transient, fetch it from the API
1289 $events_endpoint = "https://api.calendly.com/scheduled_events/$uuid/invitees";
1290
1291 $headers = array(
1292 'Authorization' => "Bearer $access_token",
1293 'Content-Type' => 'application/json',
1294 );
1295
1296 $args = array(
1297 'headers' => $headers,
1298 );
1299
1300 $response = wp_remote_get($events_endpoint, $args);
1301
1302 if (!is_wp_error($response)) {
1303 $body = wp_remote_retrieve_body($response);
1304 $invitee_list = json_decode($body, true);
1305
1306 // Store the data in a transient for a specified time (e.g., 1 hour)
1307 set_transient('calendly_invitee_list', $invitee_list, HOUR_IN_SECONDS);
1308
1309 return $invitee_list;
1310 }
1311 }
1312
1313 return $invitee_list;
1314 }
1315
1316 public static function getCalaendlyScheduledEvents($user_uri, $access_token)
1317 {
1318 // Attempt to retrieve the data from the transient
1319 $events_list = get_transient('calendly_events_list_' . md5($access_token));
1320
1321 if (false === $events_list) {
1322 // If the data is not in the transient, fetch it from the API
1323 $events_endpoint = "https://api.calendly.com/scheduled_events?user=$user_uri";
1324
1325 $headers = array(
1326 'Authorization' => "Bearer $access_token",
1327 'Content-Type' => 'application/json',
1328 );
1329
1330 $args = array(
1331 'headers' => $headers,
1332 );
1333
1334 $response = wp_remote_get($events_endpoint, $args);
1335
1336 if (!is_wp_error($response)) {
1337 $body = wp_remote_retrieve_body($response);
1338 $events_list = json_decode($body, true);
1339
1340 // Store the data in a transient for a specified time (e.g., 1 hour)
1341 set_transient('calendly_events_list', $events_list, HOUR_IN_SECONDS);
1342
1343 return $events_list;
1344 }
1345 }
1346
1347 return $events_list;
1348 }
1349
1350
1351 public static function parseDuration($durationString)
1352 {
1353 list($minutes, $seconds) = explode(':', $durationString);
1354 return intval($minutes) * 60 + intval($seconds);
1355 }
1356
1357
1358
1359
1360 public static function is_pro_active()
1361 {
1362 if (defined('EMBEDPRESS_SL_ITEM_SLUG')) {
1363 return true;
1364 }
1365 return false;
1366 }
1367
1368 /**
1369 * Check if pro features should be enabled based on license status
1370 *
1371 * @return bool True if pro features should be enabled, false otherwise
1372 */
1373 public static function is_pro_features_enabled()
1374 {
1375 // First check if pro plugin is active
1376 if (!self::is_pro_active()) {
1377 return false;
1378 }
1379
1380 // Get license status
1381 $license_status = get_option('embedpress_pro_software__license_status', '');
1382
1383 // Pro features are enabled only if license is valid
1384 return $license_status === 'valid';
1385 }
1386
1387 /**
1388 * Get detailed license information
1389 *
1390 * @return array License information including status, key, and data
1391 */
1392 public static function get_license_info()
1393 {
1394 $is_pro_active = self::is_pro_active();
1395 $license_status = '';
1396 $license_key = '';
1397 $license_data = [];
1398 $is_features_enabled = false;
1399
1400 if ($is_pro_active) {
1401 $license_status = get_option('embedpress_pro_software__license_status', '');
1402 $license_data_raw = get_transient('embedpress_pro_software__license_data');
1403
1404 if ($license_data_raw) {
1405 $license_data = json_decode(json_encode($license_data_raw), true);
1406 }
1407
1408 // The license STATUS is a persistent option, but the license_data
1409 // TRANSIENT (which carries the key) expires — so reading the key
1410 // only from the transient leaves an "Active" license with an empty
1411 // key field once it lapses. The Pro LicenseManager also persists the
1412 // full key in the `embedpress_pro_software__license` option, so use
1413 // that as the authoritative source; fall back to the transient's
1414 // (already-masked) key only if the option is somehow empty.
1415 $license_key = get_option('embedpress_pro_software__license', '');
1416 if ($license_key === '' && isset($license_data['license_key'])) {
1417 $license_key = $license_data['license_key'];
1418 }
1419
1420 $is_features_enabled = ($license_status === 'valid');
1421 }
1422
1423 return [
1424 'is_pro_active' => $is_pro_active,
1425 'license_status' => $license_status,
1426 'license_key' => $license_key,
1427 'license_data' => $license_data,
1428 'is_features_enabled' => $is_features_enabled,
1429 'status_message' => self::get_license_status_message($is_pro_active, $license_status)
1430 ];
1431 }
1432
1433 /**
1434 * Mask a license key for display, keeping only the head and tail visible.
1435 *
1436 * The full key must never render into the DOM of the settings page (it sits
1437 * in a disabled input, readable via view-source / dev-tools). This keeps the
1438 * real key's first 4 and last 5 characters and replaces every character in
1439 * between with an asterisk — so the mask length matches the true key length
1440 * (e.g. a 29-char key → "2343********************32432"). Safe to echo into
1441 * the visible input's value.
1442 *
1443 * Short keys (<= head+tail chars) are fully masked so nothing meaningful
1444 * leaks. Empty input returns empty.
1445 *
1446 * @param string $key The raw license key.
1447 * @param int $head Visible leading chars. Default 4.
1448 * @param int $tail Visible trailing chars. Default 5.
1449 * @return string Masked key, or '' when $key is empty.
1450 */
1451 public static function mask_license_key($key, $head = 4, $tail = 5)
1452 {
1453 $key = (string) $key;
1454 $len = strlen($key);
1455
1456 if ($len === 0) {
1457 return '';
1458 }
1459
1460 // Too short to reveal head+tail without overlap — mask entirely.
1461 if ($len <= ($head + $tail)) {
1462 return str_repeat('*', $len);
1463 }
1464
1465 // Star out the real middle so the masked length equals the key length.
1466 return substr($key, 0, $head) . str_repeat('*', $len - $head - $tail) . substr($key, -$tail);
1467 }
1468
1469 /**
1470 * Display license warning notice for pro features
1471 *
1472 * @param string $context Context where the notice is displayed (e.g., 'analytics', 'branding')
1473 * @return string HTML for the license warning notice
1474 */
1475 public static function get_pro_feature_notice($context = '')
1476 {
1477 $license_info = self::get_license_info();
1478
1479 if (!$license_info['is_pro_active']) {
1480 return '<div class="embedpress-pro-notice embedpress-notice-warning">
1481 <p><strong>' . __('EmbedPress Pro Required', 'embedpress') . '</strong></p>
1482 <p>' . __('This feature requires EmbedPress Pro. Please install and activate EmbedPress Pro to access this feature.', 'embedpress') . '</p>
1483 </div>';
1484 }
1485
1486 if (!$license_info['is_features_enabled']) {
1487 $message = $license_info['status_message'];
1488 $action_text = '';
1489 $action_link = admin_url('admin.php?page=embedpress&page_type=license');
1490
1491 switch ($license_info['license_status']) {
1492 case 'expired':
1493 $action_text = __('Renew License', 'embedpress');
1494 break;
1495 case 'invalid':
1496 case 'site_inactive':
1497 case 'disabled':
1498 case 'revoked':
1499 $action_text = __('Activate License', 'embedpress');
1500 break;
1501 default:
1502 $action_text = __('Activate License', 'embedpress');
1503 break;
1504 }
1505
1506 return '<div class="embedpress-pro-notice embedpress-notice-error">
1507 <p><strong>' . __('Pro Features Disabled', 'embedpress') . '</strong></p>
1508 <p>' . esc_html($message) . '</p>
1509 <p><a href="' . esc_url($action_link) . '" class="button button-primary">' . esc_html($action_text) . '</a></p>
1510 </div>';
1511 }
1512
1513 return '';
1514 }
1515
1516 /**
1517 * Initialize license checking hooks
1518 */
1519 public static function init_license_hooks()
1520 {
1521 // Add filter to disable pro features when license is not valid
1522 add_filter('embedpress_pro_features_enabled', [__CLASS__, 'is_pro_features_enabled']);
1523
1524 // Add action to show license notices in admin
1525 add_action('admin_notices', [__CLASS__, 'show_license_admin_notices']);
1526
1527 // Add styles for license notices
1528 add_action('admin_head', [__CLASS__, 'add_license_notice_styles']);
1529 }
1530
1531 /**
1532 * Show license admin notices
1533 */
1534 public static function show_license_admin_notices()
1535 {
1536 // Only show on EmbedPress admin pages
1537 if (!isset($_GET['page']) || $_GET['page'] !== 'embedpress') {
1538 return;
1539 }
1540
1541 $license_info = self::get_license_info();
1542
1543 if ($license_info['is_pro_active'] && !$license_info['is_features_enabled']) {
1544 $message = $license_info['status_message'];
1545 $action_link = admin_url('admin.php?page=embedpress&page_type=license');
1546 $action_text = $license_info['license_status'] === 'expired' ? __('Renew License', 'embedpress') : __('Activate License', 'embedpress');
1547
1548 echo '<div class="notice notice-warning is-dismissible">
1549 <p><strong>' . __('EmbedPress Pro Features Disabled', 'embedpress') . '</strong></p>
1550 <p>' . esc_html($message) . '</p>
1551 <p><a href="' . esc_url($action_link) . '" class="button button-primary">' . esc_html($action_text) . '</a></p>
1552 </div>';
1553 }
1554 }
1555
1556 /**
1557 * Add CSS styles for license notices
1558 */
1559 public static function add_license_notice_styles()
1560 {
1561 echo '<style>
1562 .embedpress-pro-notice {
1563 padding: 15px;
1564 margin: 15px 0;
1565 border-left: 4px solid #dc3545;
1566 background: #fff;
1567 box-shadow: 0 1px 1px rgba(0,0,0,.04);
1568 }
1569 .embedpress-pro-notice.embedpress-notice-warning {
1570 border-left-color: #ffb900;
1571 }
1572 .embedpress-pro-notice.embedpress-notice-error {
1573 border-left-color: #dc3545;
1574 }
1575 .embedpress-pro-notice p {
1576 margin: 0.5em 0;
1577 }
1578 .embedpress-pro-notice strong {
1579 color: #23282d;
1580 }
1581 .embedpress-text-error {
1582 color: #dc3545 !important;
1583 }
1584 .embedpress-text-warning {
1585 color: #ffb900 !important;
1586 }
1587 </style>';
1588 }
1589
1590 /**
1591 * Get user-friendly license status message
1592 *
1593 * @param bool $is_pro_active Whether pro plugin is active
1594 * @param string $license_status Current license status
1595 * @return string User-friendly status message
1596 */
1597 public static function get_license_status_message($is_pro_active, $license_status)
1598 {
1599 if (!$is_pro_active) {
1600 return __('EmbedPress Pro is not installed or activated.', 'embedpress');
1601 }
1602
1603 switch ($license_status) {
1604 case 'valid':
1605 return __('Your license is active and valid.', 'embedpress');
1606 case 'expired':
1607 return __('Your license has expired. Please renew to continue receiving updates and support.', 'embedpress');
1608 case 'invalid':
1609 case 'site_inactive':
1610 return __('Your license is not active for this URL. Please activate your license.', 'embedpress');
1611 case 'disabled':
1612 case 'revoked':
1613 return __('Your license key has been disabled or revoked.', 'embedpress');
1614 case 'missing':
1615 return __('License key is missing. Please enter your license key.', 'embedpress');
1616 case 'http_error':
1617 return __('Unable to verify license due to connection issues. Please try again later.', 'embedpress');
1618 case '':
1619 case false:
1620 default:
1621 return __('Please activate your license key to enable EmbedPress Pro features.', 'embedpress');
1622 }
1623 }
1624
1625
1626 public static function getInstagramUserInfo($accessToken, $accountType, $userId, $is_sync = false)
1627 {
1628 if ($is_sync) {
1629 // If $is_sync is true, don't use transient
1630 $use_transient = false;
1631 } else {
1632 // If $is_sync is false, use transient
1633 $transient_key = 'instagram_user_info_' . $userId;
1634 $use_transient = true;
1635 }
1636
1637 if ($use_transient && false !== ($userInfo = get_transient($transient_key))) {
1638 // If transient exists, return cached user info
1639 return $userInfo;
1640 }
1641
1642 if (strtolower($accountType) === 'personal') {
1643 $api_url = 'https://graph.instagram.com/v24.0/me?fields=biography,id,username,website,followers_count,media_count,profile_picture_url,name&access_token=' . $accessToken;
1644 } else {
1645 $api_url = 'https://graph.facebook.com/' . $userId . '?fields=biography,id,username,website,followers_count,media_count,profile_picture_url,name&access_token=' . $accessToken;
1646 }
1647
1648 $connected_account_type = $accountType;
1649
1650 $userInfoResponse = wp_remote_get($api_url);
1651
1652 if (is_wp_error($userInfoResponse)) {
1653 echo 'Error: Unable to retrieve Instagram user information.';
1654 } else {
1655 $userInfoBody = wp_remote_retrieve_body($userInfoResponse);
1656 $userInfo = json_decode($userInfoBody, true);
1657
1658 $userInfo['connected_account_type'] = $connected_account_type;
1659 $userInfo['access_token'] = $accessToken;
1660
1661
1662 if (!isset($userInfo['profile_picture_url'])) {
1663 $userInfo['profile_picture_url'] = '';
1664 }
1665
1666 // If not using transient, cache the user info for an hour
1667 if ($use_transient) {
1668 set_transient($transient_key, $userInfo, HOUR_IN_SECONDS);
1669 }
1670
1671 return $userInfo;
1672 }
1673 }
1674
1675
1676 // Get Instagram posts, videos, reels
1677 public static function getInstagramPosts($access_token, $account_type, $userId, $limit = 100, $is_sync = false)
1678 {
1679 if ($is_sync) {
1680 // If $is_sync is true, don't use transient
1681 $use_transient = false;
1682 } else {
1683 // If $is_sync is false, use transient
1684 $transient_key = 'instagram_posts_' . $userId;
1685 $use_transient = true;
1686 }
1687
1688 if ($use_transient && false !== ($posts = get_transient($transient_key))) {
1689 // If transient exists, return cached posts
1690 return $posts;
1691 }
1692
1693 if (strtolower($account_type) === 'personal') {
1694 $api_url = 'https://graph.instagram.com/v24.0/me/media?fields=id,caption,media_type,media_url,children{media_url,id,media_type},permalink,timestamp,username,thumbnail_url,comments_count,like_count&limit=' . $limit . '&access_token=' . $access_token;
1695 } else {
1696 $api_url = 'https://graph.facebook.com/v17.0/' . $userId . '/media?fields=media_url,media_product_type,thumbnail_url,caption,id,media_type,timestamp,username,comments_count,like_count,permalink,children%7Bmedia_url,id,media_type,timestamp,permalink,thumbnail_url%7D&limit=' . $limit . '&access_token=' . $access_token;
1697 }
1698
1699 $postsResponse = wp_remote_get($api_url);
1700
1701 if (is_wp_error($postsResponse)) {
1702 echo 'Error: Unable to retrieve Instagram posts.';
1703 } else {
1704 $postsBody = wp_remote_retrieve_body($postsResponse);
1705 $posts = json_decode($postsBody, true);
1706
1707 if (empty($posts['data'])) {
1708 return 'Please add Instagram Access Token';
1709 }
1710
1711 // If not using transient, cache the posts for an hour
1712 if ($use_transient) {
1713 set_transient($transient_key, $posts['data'], HOUR_IN_SECONDS);
1714 }
1715
1716 return $posts['data'];
1717 }
1718 }
1719
1720 public static function get_enable_settings_data_for_scripts($settings)
1721 {
1722 $settings_data = [
1723 'enabled_ads' => isset($settings['adManager']) && $settings['adManager'] === 'yes' ? 'yes' : '',
1724
1725 'enabled_custom_player' => isset($settings['emberpress_custom_player']) && $settings['emberpress_custom_player'] === 'yes' ? 'yes' : '',
1726
1727 'enabled_instafeed' => isset($settings['embedpress_pro_embeded_source']) && $settings['embedpress_pro_embeded_source'] === 'instafeed' ? 'yes' : '',
1728
1729 'enabled_docs_custom_viewer' => isset($settings['embedpress_document_viewer']) && $settings['embedpress_document_viewer'] === 'custom' ? 'yes' : '',
1730 ];
1731
1732 update_option('enabled_elementor_scripts', $settings_data);
1733 }
1734
1735 public static function get_options_value($key)
1736 {
1737 $g_settings = get_option(EMBEDPRESS_PLG_NAME);
1738
1739 if (isset($g_settings['enableEmbedResizeWidth']) && $g_settings['enableEmbedResizeWidth'] == 1) {
1740 $g_settings['enableEmbedResizeWidth'] = 600;
1741 update_option(EMBEDPRESS_PLG_NAME, $g_settings);
1742 }
1743 if (isset($g_settings['enableEmbedResizeHeight']) && $g_settings['enableEmbedResizeHeight'] == 1) {
1744 $g_settings['enableEmbedResizeHeight'] = 600;
1745 update_option(EMBEDPRESS_PLG_NAME, $g_settings);
1746 }
1747
1748 if (isset($g_settings[$key])) {
1749 return $g_settings[$key];
1750 }
1751
1752 return '';
1753 }
1754
1755
1756
1757 public static function get_branding_value($key, $provider)
1758 {
1759 $settings = get_option(EMBEDPRESS_PLG_NAME . ':' . $provider, []);
1760
1761 // Check if provider has custom branding enabled and the specific key set
1762 if (isset($settings['branding']) && $settings['branding'] === 'yes') {
1763 // If provider has custom logo, use it
1764 if (isset($settings[$key]) && !empty($settings[$key])) {
1765 return $settings[$key];
1766 }
1767
1768 // If branding is enabled but no custom logo, use global brand as fallback
1769 if ($key === 'logo_url') {
1770 $global_logo = self::get_global_brand_logo_url();
1771 if (!empty($global_logo)) {
1772 return $global_logo;
1773 }
1774 }
1775 }
1776
1777 return '';
1778 }
1779
1780 /**
1781 * Get global brand logo URL
1782 *
1783 * @return string
1784 */
1785 public static function get_global_brand_logo_url()
1786 {
1787 $global_brand_settings = get_option(EMBEDPRESS_PLG_NAME . ':global_brand', []);
1788 return isset($global_brand_settings['logo_url']) ? $global_brand_settings['logo_url'] : '';
1789 }
1790
1791 /**
1792 * Get global brand logo ID
1793 *
1794 * @return int
1795 */
1796 public static function get_global_brand_logo_id()
1797 {
1798 $global_brand_settings = get_option(EMBEDPRESS_PLG_NAME . ':global_brand', []);
1799 return isset($global_brand_settings['logo_id']) ? intval($global_brand_settings['logo_id']) : 0;
1800 }
1801
1802
1803 public static function format_number($number)
1804 {
1805 if ($number >= 1000000000) {
1806 return number_format($number / 1000000000, 1) . 'b';
1807 } elseif ($number >= 1000000) {
1808 return number_format($number / 1000000, 1) . 'm';
1809 } elseif ($number >= 1000) {
1810 return number_format($number / 1000, 1) . 'k';
1811 } else {
1812 return $number;
1813 }
1814 }
1815
1816 public static function get_id($item)
1817 {
1818 $vid = isset($item->snippet->resourceId->videoId) ? $item->snippet->resourceId->videoId : null;
1819 $vid = $vid ? $vid : (isset($item->id->videoId) ? $item->id->videoId : null);
1820 $vid = $vid ? $vid : (isset($item->id) ? $item->id : null);
1821 return $vid;
1822 }
1823
1824 public static function clean_api_error($raw_message)
1825 {
1826 return htmlspecialchars(strip_tags(preg_replace('@&key=[^& ]+@i', '&key=*******', $raw_message)));
1827 }
1828
1829 public static function clean_api_error_html($raw_message)
1830 {
1831 $clean_html = '';
1832 if ((defined('REST_REQUEST') && REST_REQUEST) || current_user_can('manage_options')) {
1833 $clean_html = '<div>' . __('EmbedPress: ', 'embedpress') . self::clean_api_error($raw_message) . '</div>';
1834 }
1835 return $clean_html;
1836 }
1837
1838 public static function get_thumbnail_url($item, $quality, $privacyStatus)
1839 {
1840 $url = "";
1841 if ($privacyStatus == 'private') {
1842 $url = EMBEDPRESS_URL_ASSETS . 'images/youtube/private.png';
1843 } elseif (isset($item->snippet->thumbnails->{$quality}->url)) {
1844 $url = $item->snippet->thumbnails->{$quality}->url;
1845 } elseif (isset($item->snippet->thumbnails->medium->url)) {
1846 $url = $item->snippet->thumbnails->medium->url;
1847 } elseif (isset($item->snippet->thumbnails->default->url)) {
1848 $url = $item->snippet->thumbnails->default->url;
1849 } elseif (isset($item->snippet->thumbnails->high->url)) {
1850 $url = $item->snippet->thumbnails->high->url;
1851 } else {
1852 $url = EMBEDPRESS_URL_ASSETS . 'images/youtube/deleted-video-thumb.png';
1853 }
1854 return $url;
1855 }
1856
1857 public static function compare_vid_date($a, $b)
1858 {
1859 $dateA = strtotime($a->snippet->publishedAt);
1860 $dateB = strtotime($b->snippet->publishedAt);
1861
1862 // Sort in descending order (newest first)
1863 return $dateB - $dateA;
1864 }
1865
1866
1867 public static function get_youtube_video_data($api_key, $video_id)
1868 {
1869 // Set a unique transient name based on the video ID
1870 $transient_name = 'youtube_video_data_' . $video_id;
1871
1872 // Try to get data from the transient cache
1873 $video_data = get_transient($transient_name);
1874
1875 // If no cached data, fetch from the API
1876 if ($video_data === false) {
1877 // YouTube Data API URL
1878 $url = "https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics&id={$video_id}&key={$api_key}";
1879
1880 // Fetch the data from the API
1881 $response = wp_remote_get($url);
1882
1883 if (is_wp_error($response)) {
1884 return false; // Return false if there is an error
1885 }
1886
1887 $body = wp_remote_retrieve_body($response);
1888 $video_data = json_decode($body, true);
1889
1890 if (isset($video_data['items'][0])) {
1891 $video_data = $video_data['items'][0];
1892
1893 // Cache the data in a transient for 12 hours
1894 set_transient($transient_name, $video_data, 24 * HOUR_IN_SECONDS);
1895 } else {
1896 return false; // Return false if no data found
1897 }
1898 }
1899
1900 return $video_data;
1901 }
1902
1903 public static function trimTitle($title, $wordCount)
1904 {
1905 $words = explode(' ', $title);
1906
1907 if (count($words) <= $wordCount) {
1908 return $title; // No trimming needed
1909 }
1910
1911 $trimmedWords = array_slice($words, 0, $wordCount);
1912
1913 return implode(' ', $trimmedWords) . ' ...';
1914 }
1915
1916 public static function timeAgo($datetime)
1917 {
1918 $now = new \DateTime();
1919 $date = new \DateTime($datetime);
1920 $interval = $now->diff($date);
1921
1922 if ($interval->y > 0) {
1923 return $interval->y . ' year' . ($interval->y > 1 ? 's' : '') . ' ago';
1924 } elseif ($interval->m > 0) {
1925 return $interval->m . ' month' . ($interval->m > 1 ? 's' : '') . ' ago';
1926 } elseif ($interval->d > 0) {
1927 return $interval->d . ' day' . ($interval->d > 1 ? 's' : '') . ' ago';
1928 } elseif ($interval->h > 0) {
1929 return $interval->h . ' hour' . ($interval->h > 1 ? 's' : '') . ' ago';
1930 } elseif ($interval->i > 0) {
1931 return $interval->i . ' minute' . ($interval->i > 1 ? 's' : '') . ' ago';
1932 } else {
1933 return 'just now';
1934 }
1935 }
1936
1937 public static function removeQuote($attributes)
1938 {
1939 $parsedAttributes = [];
1940
1941 $regex = '/^on.*/i';
1942
1943 foreach ($attributes as $key => $value) {
1944 if (is_string($value)) {
1945 $cleanValue = str_replace(['"', "'"], '', $value);
1946 } else {
1947 $cleanValue = $value;
1948 }
1949
1950 if (!preg_match($regex, $key)) {
1951 $parsedAttributes[$key] = $cleanValue;
1952 }
1953 }
1954
1955 return $parsedAttributes;
1956 }
1957
1958 public static function getBooleanParam($param, $default = false)
1959 {
1960 if (in_array($param, [true, 'true', 'yes', 1, '1'], true)) {
1961 return true;
1962 }
1963 if (in_array($param, [false, 'false', 'no', 0, '0'], true)) {
1964 return false;
1965 }
1966 return (bool) $default;
1967 }
1968
1969 public static function has_allowed_roles($allowed_roles = [])
1970 {
1971
1972 if (empty($allowed_roles) || (count($allowed_roles) == 1 && empty($allowed_roles[0]))) {
1973 return true;
1974 }
1975 $current_user = wp_get_current_user();
1976 $user_roles = $current_user->roles;
1977
1978 return !empty(array_intersect($user_roles, $allowed_roles));
1979 }
1980
1981 public static function get_elementor_global_color($settings, $key)
1982 {
1983
1984 $global_color = $settings[$key];
1985
1986 if (isset($settings['__globals__'][$key])) {
1987 $color_setting = $settings['__globals__'][$key];
1988
1989 if (strpos($color_setting, 'globals/colors?id=') === 0) {
1990 // It's a global color reference
1991 $global_id = str_replace('globals/colors?id=', '', $color_setting);
1992
1993 $kit = Plugin::$instance->kits_manager->get_current_settings();
1994
1995 $system_colors = isset($kit['system_colors']) ? $kit['system_colors'] : [];
1996 $custom_colors = isset($kit['custom_colors']) ? $kit['custom_colors'] : [];
1997 $global_colors = array_merge($system_colors, $custom_colors);
1998
1999 foreach ($global_colors as $color) {
2000
2001 if ($color['_id'] === $global_id) {
2002 $global_color = $color['color']; // Found a match, set the color
2003 break;
2004 }
2005 }
2006 }
2007 }
2008 return $global_color;
2009 }
2010
2011 public static function get_provider_name($source_url)
2012 {
2013 if (self::is_youtube_channel($source_url)) {
2014 $source_name = 'YoutubeChannel';
2015 } else if (self::is_youtube($source_url)) {
2016 $source_name = 'Youtube';
2017 } else if (!empty(self::is_file_url($source_url))) {
2018 $source_name = 'document_' . self::get_extension_from_file_url($source_url);
2019 } else if (self::is_opensea($source_url)) {
2020 $source_name = 'OpenSea';
2021 } else if (self::is_instagram_feed($source_url)) {
2022 $source_name = 'InstagramFeed';
2023 }else {
2024 Shortcode::get_embera_instance();
2025 $collectios = Shortcode::get_collection();
2026 $provider = $collectios->findProviders($source_url);
2027
2028 if (!empty($provider[$source_url])) {
2029 $source_name = $provider[$source_url]->getProviderName();
2030 } else {
2031 $host = parse_url($source_url, PHP_URL_HOST);
2032 if ($host) {
2033 $parts = explode('.', $host);
2034 if (count($parts) > 1) {
2035 $source_name = $parts[1];
2036 } else {
2037 // Handle the case where the host doesn't have at least two parts
2038 $source_name = $host;
2039 }
2040 } else {
2041 // Handle the case where parse_url fails
2042 $source_name = '';
2043 }
2044 }
2045 }
2046
2047 return $source_name;
2048 }
2049
2050 public static function get_user_roles()
2051 {
2052 global $wp_roles; // Access global roles object
2053 $user_roles = [];
2054
2055 if (isset($wp_roles->roles) && is_array($wp_roles->roles)) {
2056 foreach ($wp_roles->roles as $role_key => $role_data) {
2057 $user_roles[] = [
2058 'value' => $role_key, // Machine-readable key
2059 'label' => $role_data['name'], // Human-readable name
2060 ];
2061 }
2062 }
2063
2064 return $user_roles;
2065 }
2066
2067 public static function has_content_allowed_roles($allowed_roles = [])
2068 {
2069 // Ensure it's always an array
2070 if (!is_array($allowed_roles)) {
2071 $allowed_roles = [];
2072 }
2073
2074 if (count($allowed_roles) === 1 && empty($allowed_roles[0])) {
2075 return true;
2076 }
2077
2078 $current_user = wp_get_current_user();
2079 $user_roles = $current_user->roles;
2080
2081 return !empty(array_intersect($user_roles, $allowed_roles));
2082 }
2083
2084
2085 }
2086