PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.6.2
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.6.2
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 2 months ago DynamicFieldResolver.php 1 month ago Elementor_Enhancer.php 6 months ago EmbedPress_Core_Installer.php 6 years ago EmbedPress_Notice.php 3 months ago EmbedPress_Plugin_Usage_Tracker.php 1 month ago Extend_CustomPlayer_Controls.php 2 months ago Extend_Elementor_Controls.php 1 year ago FeatureNoticeManager.php 3 weeks ago FeatureNotices.php 5 days ago FeaturePreviewModal.php 3 weeks ago Feature_Enhancer.php 1 month ago GoogleReviewsAdminPage.php 3 weeks ago GoogleReviewsApify.php 3 weeks ago GoogleReviewsManaged.php 3 weeks ago GoogleReviewsRenderer.php 3 weeks ago GoogleReviewsRestController.php 3 weeks ago GoogleReviewsStore.php 3 weeks ago Helper.php 1 week ago Pdf_Thumbnail_Handler.php 3 months ago PermalinkHelper.php 11 months ago View_Count_Display.php 1 month ago
Helper.php
1932 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 public static function get_pdf_renderer()
119 {
120 // $renderer = EMBEDPRESS_URL_ASSETS. 'pdf/web/viewer.html';
121
122 $renderer = admin_url('admin-ajax.php?action=get_viewer');
123
124 // @TODO; apply settings query args here
125 return $renderer;
126 }
127
128 public static function get_flipbook_renderer()
129 {
130 $renderer = admin_url('admin-ajax.php?action=get_flipbook_viewer');
131 return $renderer;
132 }
133
134 public static function get_extension_from_file_url($url)
135 {
136 $path = parse_url((string) $url, PHP_URL_PATH);
137 if (empty($path)) {
138 $path = (string) $url;
139 }
140 $urlSplit = explode(".", $path);
141 $ext = end($urlSplit);
142 return strtolower($ext);
143 }
144
145
146 public static function is_instagram_feed($url)
147 {
148 return (bool) preg_match('~(?:https?://)?(?:www\.)?instagram\.com/(?!p/|reel/|tv/|stories/)[^/]+/?$~i', (string) $url);
149 }
150
151 public static function is_file_url($url)
152 {
153 $pattern = '/\.([0-9a-z]+)(?=[?#])|(\.)(?:[\w]+)$/i';
154 return preg_match($pattern, $url) === 1;
155 }
156
157 public static function is_opensea($url)
158 {
159 return strpos($url, "opensea.io") !== false;
160 }
161
162 /**
163 * Reject the three internal IPv4 ranges that WordPress core's own SSRF guard
164 * (wp_http_validate_url) leaves reachable: link-local (169.254/16, incl. the
165 * 169.254.169.254 cloud-metadata endpoint), CGNAT (100.64/10) and benchmark
166 * (198.18/15). Core already rejects loopback, RFC1918 and every IPv6 form, so
167 * this only fills the IPv4 gap — using the same octet check core uses itself.
168 *
169 * Core's `http_request_host_is_external` filter only fires for IPs core
170 * already suspects, so it can't cover these; `pre_http_request` fires for every
171 * request, guarding every wp_remote / wp_safe_remote call (including redirect
172 * hops). Resolving the host with gethostbyname mirrors core and normalises
173 * hostnames + integer-encoded literals to a dotted quad.
174 *
175 * @param false|array|\WP_Error $preempt Short-circuit value (false to proceed).
176 * @param array $args Request args (unused).
177 * @param string $url The request URL.
178 * @return false|\WP_Error
179 */
180 public static function block_internal_http_requests($preempt, $args, $url)
181 {
182 if (false !== $preempt) {
183 return $preempt; // a prior hook already decided
184 }
185
186 $host = wp_parse_url($url, PHP_URL_HOST);
187 if (empty($host)) {
188 return $preempt;
189 }
190 $host = rtrim(trim($host, '[]'), '.'); // drop IPv6 brackets + trailing FQDN dot
191
192 // Resolve to an IPv4 address, exactly as core's wp_http_validate_url does.
193 $ip = preg_match('/^(\d{1,3}\.){3}\d{1,3}$/', $host) ? $host : gethostbyname($host);
194 if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
195 return $preempt; // not IPv4 (IPv6 is core's job) or unresolved
196 }
197
198 $p = array_map('intval', explode('.', $ip));
199 $internal =
200 (169 === $p[0] && 254 === $p[1]) // link-local 169.254/16
201 || (100 === $p[0] && 64 <= $p[1] && 127 >= $p[1]) // CGNAT 100.64/10
202 || (198 === $p[0] && (18 === $p[1] || 19 === $p[1])); // benchmark 198.18/15
203
204 if ($internal) {
205 return new \WP_Error(
206 'http_request_blocked',
207 __('Requests to internal hosts are not allowed.', 'embedpress')
208 );
209 }
210
211 return $preempt;
212 }
213
214 public static function is_youtube_channel($url)
215 {
216 return (bool) (preg_match('~(?:https?:\/\/)?(?:www\.)?(?:youtube.com\/)(?:channel\/|c\/|user\/|@)(\w+)~i', (string) $url));
217 }
218
219 public static function is_youtube_playlist($url)
220 {
221 // /playlist?list=PL… OR /watch?…&list=… (Mix radio, video-in-playlist).
222 $u = str_replace(['&#038;', '&amp;'], '&', (string) $url);
223 return (bool) (preg_match('~^https?://(?:www\.)?youtube\.com/(?:playlist|watch)\?(?:[^#]*&)?list=([\w-]+)~i', $u));
224 }
225
226 public static function is_youtube_channel_or_playlist($url)
227 {
228 return self::is_youtube_channel($url) || self::is_youtube_playlist($url);
229 }
230
231 public static function is_youtube($url)
232 {
233 return (bool) (preg_match('~(?:https?://)?(?:www\.)?(?:youtube\.com|youtu\.be)/watch\?v=([^&]+)~i', (string) $url));
234 }
235
236 // Saved sources data temporary in wp_options table
237 public static function get_source_data($blockid, $source_url, $source_option_name, $source_temp_option_name)
238 {
239 if (self::is_youtube_channel($source_url)) {
240 $source_name = 'YoutubeChannel';
241 } else if (self::is_youtube($source_url)) {
242 $source_name = 'Youtube';
243 } else if (!empty(self::is_file_url($source_url))) {
244 $source_name = 'document_' . self::get_extension_from_file_url($source_url);
245 } else if (self::is_opensea($source_url)) {
246 $source_name = 'OpenSea';
247 } else if (self::is_instagram_feed($source_url)) {
248 $source_name = 'InstagramFeed';
249 } else {
250 Shortcode::get_embera_instance();
251 $collectios = Shortcode::get_collection();
252 $provider = $collectios->findProviders($source_url);
253
254 if (!empty($provider[$source_url])) {
255 $source_name = $provider[$source_url]->getProviderName();
256 } else {
257 $host = parse_url($source_url, PHP_URL_HOST);
258 if ($host) {
259 $parts = explode('.', $host);
260 if (count($parts) > 1) {
261 $source_name = $parts[1];
262 } else {
263 // Handle the case where the host doesn't have at least two parts
264 $source_name = $host;
265 }
266 } else {
267 // Handle the case where parse_url fails
268 $source_name = 'unknown';
269 }
270 }
271 }
272
273 if (!empty($blockid) && $blockid != 'undefined') {
274 $sources = json_decode(get_option($source_temp_option_name), true);
275
276 if (!$sources) {
277 $sources = array();
278 }
279 $exists = false;
280
281 foreach ($sources as $i => $source) {
282 if ($source['id'] === $blockid) {
283 $sources[$i]['source']['name'] = $source_name;
284 $sources[$i]['source']['url'] = $source_url;
285 $exists = true;
286 break;
287 }
288 }
289
290 if (!$exists) {
291 $sources[] = array('id' => $blockid, 'source' => array('name' => $source_name, 'url' => $source_url, 'count' => 1));
292 }
293
294 update_option($source_temp_option_name, json_encode($sources));
295 }
296 }
297
298 /**
299 * One-time scan of existing post content to seed the source-data options.
300 *
301 * Uses two SQL-prefiltered queries (post_content + _elementor_data postmeta)
302 * so a site with thousands of posts only loads the rows that actually
303 * mention "embedpress" — avoiding the N+1 round-trips of get_posts() +
304 * per-post get_post_field() / get_post_meta().
305 */
306 public static function seed_source_data_from_existing_content()
307 {
308 if (get_option('embedpress_source_data_seeded')) {
309 return;
310 }
311
312 global $wpdb;
313
314 $limit = (int) apply_filters('embedpress_seed_source_data_limit', 2000);
315 $like = '%' . $wpdb->esc_like('embedpress') . '%';
316
317 // Posts whose content mentions embedpress (blocks or shortcodes).
318 $content_rows = $wpdb->get_results($wpdb->prepare(
319 "SELECT ID, post_content
320 FROM {$wpdb->posts}
321 WHERE post_status IN ('publish','draft','private','future','pending')
322 AND post_type NOT IN ('revision','attachment','nav_menu_item','customize_changeset','oembed_cache')
323 AND post_content LIKE %s
324 LIMIT %d",
325 $like,
326 $limit
327 ));
328
329 if ($content_rows) {
330 foreach ($content_rows as $row) {
331 $post_id = (int) $row->ID;
332 $content = (string) $row->post_content;
333
334 // Gutenberg: <!-- wp:embedpress/* {"url":"..."} -->
335 if (preg_match_all('/wp:embedpress[^>]*?"url"\s*:\s*"([^"]+)"/', $content, $m)) {
336 foreach ($m[1] as $i => $url) {
337 self::get_source_data(
338 md5("seed-{$post_id}-g-{$i}"),
339 wp_unslash($url),
340 'gutenberg_source_data',
341 'gutenberg_temp_source_data'
342 );
343 }
344 }
345
346 // Shortcode: [embedpress ...]URL[/embedpress]
347 if (preg_match_all('/\[embedpress[^\]]*\](.*?)\[\/embedpress\]/s', $content, $m)) {
348 foreach ($m[1] as $i => $url) {
349 $url = trim(wp_unslash($url));
350 if ($url === '') continue;
351 self::get_source_data(
352 md5("seed-{$post_id}-s-{$i}"),
353 $url,
354 'gutenberg_source_data',
355 'gutenberg_temp_source_data'
356 );
357 }
358 }
359 }
360 }
361
362 // Elementor widgets — postmeta-level prefilter.
363 $meta_rows = $wpdb->get_results($wpdb->prepare(
364 "SELECT post_id, meta_value
365 FROM {$wpdb->postmeta}
366 WHERE meta_key = %s
367 AND meta_value LIKE %s
368 LIMIT %d",
369 '_elementor_data',
370 $like,
371 $limit
372 ));
373
374 if ($meta_rows) {
375 foreach ($meta_rows as $row) {
376 $post_id = (int) $row->post_id;
377 $el = (string) $row->meta_value;
378 if (preg_match_all('/"widgetType":"embedpress[^"]*".*?"(?:embedpress_pro_embeded_link|embed_url|url|pdf_source|src)":"([^"]+)"/', $el, $m)) {
379 foreach ($m[1] as $i => $url) {
380 self::get_source_data(
381 md5("seed-{$post_id}-e-{$i}"),
382 stripslashes($url),
383 'elementor_source_data',
384 'elementor_temp_source_data'
385 );
386 }
387 }
388 }
389 }
390
391 // Promote temp → real without needing save_post.
392 self::get_save_source_data_on_post_update('gutenberg_source_data', 'gutenberg_temp_source_data');
393 self::get_save_source_data_on_post_update('elementor_source_data', 'elementor_temp_source_data');
394
395 update_option('embedpress_source_data_seeded', time(), false);
396 }
397
398 // Saved source data when post updated
399 public static function get_save_source_data_on_post_update($source_option_name, $source_temp_option_name)
400 {
401
402 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
403 return;
404 }
405 $temp_data = json_decode(get_option($source_temp_option_name), true);
406 $source_data = json_decode(get_option($source_option_name), true);
407 if (!$temp_data) {
408 $temp_data = array();
409 }
410 if (!$source_data) {
411 $source_data = array();
412 }
413
414 $sources = array_merge($temp_data, $source_data);
415
416 $unique_sources = array();
417 foreach ($sources as $source) {
418 $unique_sources[$source['id']] = $source;
419 }
420
421 $unique_sources = array_values($unique_sources);
422
423 delete_option($source_temp_option_name);
424
425 update_option($source_option_name, json_encode($unique_sources));
426 }
427
428 //Delete source temporary data when reload without update or publish
429 public static function get_delete_source_temp_data_on_reload($source_temp_option_name)
430 {
431 $source_temp_data = json_decode(get_option($source_temp_option_name), true);
432 if ($source_temp_data) {
433 delete_option($source_temp_option_name);
434 }
435 }
436
437 public static function get_file_title($url)
438 {
439 return get_the_title(attachment_url_to_postid($url));
440 }
441
442 public static function get_hash()
443 {
444 $hash_key = get_option(EMBEDPRESS_PLG_NAME . '_hash_key');
445 if (!$hash_key) {
446 $hash_key = wp_hash_password(wp_generate_password(30));
447 update_option(EMBEDPRESS_PLG_NAME . '_hash_key', $hash_key);
448 }
449 return $hash_key;
450 }
451
452 public function lock_content_form_handler() {
453 // print_r($embedHTML);
454
455 $client_id = isset($_POST['client_id']) ? sanitize_text_field($_POST['client_id']) : '';
456 $password = isset($_POST['password']) ? sanitize_text_field($_POST['password']) : '';
457 $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
458
459 $epbase64 = get_post_meta( $post_id, 'ep_base_' .$client_id, true );
460 $hash_key = get_post_meta( $post_id, 'hash_key_' .$client_id, true );
461
462 // Set the decryption key and initialization vector (IV)
463 $key = Helper::get_hash();
464
465 // Decode the base64 encoded cipher
466 $cipher = base64_decode($epbase64);
467 // Decrypt the cipher using AES-128-CBC encryption
468
469 $wp_pass_key = hash('sha256', wp_salt(32) . md5($password));
470 $iv = substr($wp_pass_key, 0, 16);
471
472 if ($wp_pass_key === $hash_key) {
473
474 $embed = openssl_decrypt($cipher, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv) . '<script>
475 var now = new Date();
476 var time = now.getTime();
477 var expireTime = time + 1000 * 60 * 60 * 24 * 30;
478 now.setTime(expireTime);
479 document.cookie = "password_correct_'.esc_js($client_id).'='.esc_js($hash_key).'; expires=" + now.toUTCString() + "; path=/";
480 </script>';
481
482 }
483 else{
484 $embed = 0;
485 }
486
487 // Process the form data and return a response
488 $response = array(
489 'success' => true,
490 'password' => $password,
491 'embedHtml' => $embed,
492 'post_id' => $post_id
493 );
494
495 wp_send_json($response);
496
497 }
498
499 public function gutenberg_password_check()
500 {
501 $client_id = isset($_POST['client_id']) ? sanitize_text_field($_POST['client_id']) : '';
502 $password = isset($_POST['password']) ? sanitize_text_field($_POST['password']) : '';
503 $content_password = isset($_POST['content_password']) ? sanitize_text_field($_POST['content_password']) : '';
504
505 // Verify password
506 if ($password === $content_password) {
507 // Set cookie for authentication
508 $hash_pass = hash('sha256', wp_salt(32) . md5($password));
509 setcookie("password_correct_" . $client_id, $hash_pass, time() + 3600, '/');
510
511 $response = array(
512 'success' => true,
513 'message' => 'Password correct'
514 );
515 } else {
516 $response = array(
517 'success' => false,
518 'message' => 'Incorrect password'
519 );
520 }
521
522 wp_send_json($response);
523 }
524
525 public static function display_password_form($client_id = '', $embedHtml = '', $pass_hash_key = '', $attributes = [])
526 {
527 $lock_heading = !empty($attributes['lockHeading']) ? sanitize_text_field($attributes['lockHeading']) : '';
528 $lock_subheading = !empty($attributes['lockSubHeading']) ? sanitize_text_field($attributes['lockSubHeading']) : '';
529 $lock_error_message = !empty($attributes['lockErrorMessage']) ? sanitize_text_field($attributes['lockErrorMessage']) : '';
530 $footer_message = !empty($attributes['footerMessage']) ? sanitize_text_field($attributes['footerMessage']) : '';
531 $password_placeholder = !empty($attributes['passwordPlaceholder']) ? sanitize_text_field($attributes['passwordPlaceholder']) : '';
532 $button_text = !empty($attributes['submitButtonText']) ? sanitize_text_field($attributes['submitButtonText']) : '';
533 $unlocking_text = !empty($attributes['submitUnlockingText']) ? sanitize_text_field($attributes['submitUnlockingText']) : '';
534 $enable_footer_message = !empty($attributes['enableFooterMessage']) ? sanitize_text_field($attributes['enableFooterMessage']) : '';
535
536
537 // Set the encryption key and initialization vector (IV)
538 $key = self::get_hash();
539
540 $salt = wp_salt(32);
541 $wp_hash_key = hash('sha256', $salt . $pass_hash_key);
542 $iv = substr($wp_hash_key, 0, 16);
543
544
545 // Encrypt the plaintext using AES-128-CBC encryption
546 $cipher = openssl_encrypt($embedHtml, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
547
548 // Base64 encode the encrypted cipher
549 $encrypted_data = base64_encode($cipher);
550
551 update_post_meta(get_the_ID(), 'ep_base_' . $client_id, $encrypted_data);
552 update_post_meta(get_the_ID(), 'hash_key_' . $client_id, $wp_hash_key);
553
554 $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>';
555
556 echo '
557 <div class="password-form-container sd">
558 <h2>' . esc_html($lock_heading) . '</h2>
559 <p>' . esc_html($lock_subheading) . ' </p>
560 <form class="password-form" method="post" class="password-form" data-unlocking-text="' . esc_attr($unlocking_text) . '">
561
562 <div class="password-field">
563 <span class="lock-icon">' . $lock_icon . '</span>
564 <input type="password" name="pass_' . esc_attr($client_id) . '" placeholder="' . esc_attr($password_placeholder) . '" required>
565 </div>
566 <input type="hidden" name="ep_client_id" value="' . esc_attr($client_id) . '">
567 <input type="hidden" name="post_id" value="' . esc_attr(get_the_ID()) . '">
568
569 <input type="submit" name="password_submit" value="' . esc_attr($button_text) . '">
570 <div class="error-message hidden">' . esc_html($lock_error_message) . '</div>
571 </form>
572 ' . (!empty($enable_footer_message) ? '<p class="need-access-message">' . esc_html($footer_message) . '</p>' : '') . '
573 </div>
574 ';
575 }
576
577 // Check if the user has already entered the correct password
578 public static function is_password_correct($client_id)
579 {
580 if (isset($_COOKIE['password_correct_' . $client_id])) {
581 return $_COOKIE['password_correct_' . $client_id];
582 } else {
583 return false;
584 }
585 }
586
587 public static function customLogo($embedHTML, $atts)
588 {
589 $x = !empty($atts['logoX']) ? $atts['logoX'] : 0;
590 $y = !empty($atts['logoY']) ? $atts['logoY'] : 0;
591 $uniqid = !empty($atts['url']) ? '.ose-uid-' . md5($atts['url']) : '';
592
593 $brandUrl = !empty($atts['customlogoUrl']) ? $atts['customlogoUrl'] : '';
594 $opacity = !empty($atts['logoOpacity']) ? $atts['logoOpacity'] : '';
595
596 $cssClass = !empty($atts['url']) ? '.ose-uid-' . md5($atts['url']) : '.ose-youtube';
597
598
599
600 ob_start(); ?>
601 <style type="text/css">
602 <?php echo esc_html($cssClass); ?> {
603 position: relative;
604 }
605
606 <?php echo esc_html($cssClass); ?>.watermark {
607 border: 0;
608 position: absolute;
609 bottom: <?php echo esc_html($y); ?>%;
610 right: <?php echo esc_html($x); ?>%;
611 max-width: 150px;
612 max-height: 75px;
613 opacity: 0.25;
614 z-index: 5;
615 -o-transition: opacity 0.5s ease-in-out;
616 -moz-transition: opacity 0.5s ease-in-out;
617 -webkit-transition: opacity 0.5s ease-in-out;
618 transition: opacity 0.5s ease-in-out;
619 opacity: <?php echo esc_html($opacity); ?>;
620 }
621
622 <?php echo esc_html($cssClass); ?>.watermark:hover {
623 opacity: 1;
624 }
625 </style>
626 <?php
627
628
629 $style = ob_get_clean();
630
631 if (!class_exists('\simple_html_dom')) {
632 include_once EMBEDPRESS_PATH_CORE . 'simple_html_dom.php';
633 }
634
635 $cta = '';
636 $img = '';
637
638 if (!empty($atts['customlogo'])) {
639 $img = '<img src="' . esc_url($atts['customlogo']) . '"/>';
640
641 $imgDom = str_get_html($img);
642 $imgDom = $imgDom->find('img', 0);
643 $imgDom->setAttribute('class', 'watermark ep-custom-logo');
644 $imgDom->removeAttribute('style');
645 $imgDom->setAttribute('width', 'auto');
646 $imgDom->setAttribute('height', 'auto');
647 ob_start();
648 echo $imgDom;
649
650 $cta .= ob_get_clean();
651
652 $imgDom->clear();
653 unset($img, $imgDom);
654
655 if (!empty($brandUrl)) {
656 $cta = '<a href="' . esc_url($brandUrl) . '" target="_blank">' . $cta . '</a>';
657 }
658 $dom = str_get_html($embedHTML);
659
660 $wrapDiv = $dom->find($uniqid, 0);
661
662 if (!empty($wrapDiv) && is_object($wrapDiv)) {
663 $wrapDiv->innertext .= $cta;
664 }
665
666 ob_start();
667 echo $wrapDiv;
668
669 $markup = ob_get_clean();
670
671 $dom->clear();
672 unset($dom, $wrapDiv);
673
674 $embedHTML = $style . $markup;
675 }
676
677 return $embedHTML;
678 }
679
680
681 public static function embed_content_share($content_id = '', $attributes = [])
682 {
683 $share_position = !empty($attributes['sharePosition']) ? $attributes['sharePosition'] : 'right';
684 $custom_thumnail = !empty($attributes['customThumbnail']) ? $attributes['customThumbnail'] : '';
685 $custom_title = !empty($attributes['customTitle']) ? $attributes['customTitle'] : '';
686 $custom_description = !empty($attributes['customDescription']) ? $attributes['customDescription'] : '';
687
688 // Create a unique hash based on content attributes
689 $content_hash = md5($custom_thumnail . $custom_title . $custom_description . $content_id);
690
691 // Encode for URL usage
692 $custom_thumnail = urlencode($custom_thumnail);
693 $custom_title = urlencode($custom_title);
694 $custom_description = urlencode($custom_description);
695
696 // Get social share options with defaults
697 $facebook_enabled = isset($attributes['shareFacebook']) ? $attributes['shareFacebook'] !== false : true;
698 $twitter_enabled = isset($attributes['shareTwitter']) ? $attributes['shareTwitter'] !== false : true;
699 $pinterest_enabled = isset($attributes['sharePinterest']) ? $attributes['sharePinterest'] !== false : true;
700 $linkedin_enabled = isset($attributes['shareLinkedin']) ? $attributes['shareLinkedin'] !== false : true;
701 $style = isset($attributes['width']) ? 'style="max-width: ' . esc_attr($attributes['width']) . 'px;"' : '';
702
703
704 $page_url = urlencode(get_permalink() . '?hash=' . $content_id . '&unique=' . $content_hash);
705
706 $social_icons = '<div class="ep-social-share-wraper"' . $style . ' ><div class="ep-social-share share-position-' . esc_attr($share_position) . '">';
707
708
709 // Facebook
710 if ($facebook_enabled) {
711 $social_icons .= '<a href="https://www.facebook.com/sharer/sharer.php?u=' . $page_url . '" class="ep-social-icon facebook" target="_blank">
712 <svg width="64px" height="64px" fill="#000000" viewBox="0 -6 512 512" xmlns="http://www.w3.org/2000/svg">
713 <path d="M0 0h512v500H0z" fill="#475a96"/>
714 <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"/>
715 </svg>
716 </a>';
717 }
718
719 // Twitter
720 if ($twitter_enabled) {
721 $social_icons .= '<a href="https://twitter.com/intent/tweet?url=' . $page_url . '&text=' . $custom_title . '" class="ep-social-icon twitter" target="_blank">
722 <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>
723 </a>';
724 }
725
726 // Pinterest
727 if ($pinterest_enabled) {
728 $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">
729
730 <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>
731
732 </a>';
733 }
734
735 // LinkedIn
736 if ($linkedin_enabled) {
737 $social_icons .= '<a href="https://www.linkedin.com/shareArticle?mini=true&url=' . $page_url . '" class="ep-social-icon linkedin" target="_blank">
738
739 <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"
740 viewBox="0 0 310 310" xml:space="preserve">
741 <g id="XMLID_801_">
742 <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
743 C77.16,101.969,74.922,99.73,72.16,99.73z"/>
744 <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
745 c22.626,0,41.033-18.41,41.033-41.038C82.1,18.743,63.692,0.341,41.066,0.341z"/>
746 <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
747 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
748 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
749 C310,145.43,300.549,94.761,230.454,94.761z"/>
750 </g>
751 </svg>
752 </a>';
753 }
754
755 $social_icons .= '</div></div>';
756
757 return $social_icons;
758 }
759
760 public static function ep_get_elementor_widget_settings($page_settings = '', $id = '', $widgetType = '')
761 {
762 if (empty($page_settings)) {
763 return [];
764 }
765
766 // Handle both string and array cases for elementor data
767 if (is_string($page_settings)) {
768 $data = json_decode($page_settings, true);
769 } else {
770 // Data might already be an array in older versions
771 $data = $page_settings;
772 }
773 $element_setting = null;
774
775 Plugin::$instance->db->iterate_data($data, function ($element) use (&$element_setting, $widgetType, $id) {
776
777 if ($element['id'] == $id && $element['elType'] == 'widget' && $element['widgetType'] == $widgetType) {
778 $element_setting[] = $element['settings'];
779 }
780 });
781
782 return $element_setting;
783 }
784
785
786
787 public static function ep_get_popup_icon()
788 {
789 $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>';
790
791 return $svg;
792 }
793 public static function ep_get_download_icon()
794 {
795 $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>';
796
797 return $svg;
798 }
799
800 public static function ep_get_print_icon()
801 {
802 $svg = '<div class="ep-doc-print-icon" ><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24">
803 <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"/>
804 </svg></div>';
805
806 return $svg;
807 }
808
809 public static function ep_get_fullscreen_icon()
810 {
811 $svg = '<div class="ep-doc-fullscreen-icon"><svg width="25" height="25" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
812 <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"/>
813 </svg></div>';
814
815 return $svg;
816 }
817 public static function ep_get_minimize_icon()
818 {
819 $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>';
820
821 return $svg;
822 }
823 public static function ep_get_draw_icon()
824 {
825 $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>';
826
827 return $svg;
828 }
829
830 public static function get_insta_video_icon()
831 {
832 $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">
833 <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>';
834
835 return $svg;
836 }
837
838 public static function get_insta_image_carousel_icon()
839 {
840 $svg = '<svg aria-label="Carousel" class="x1lliihq x1n2onr6" color="#FFF" fill="#FFF" height="25" viewBox="0 0 43.636 43.636" width="25">
841 <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>';
842
843 return $svg;
844 }
845
846 public static function get_insta_image_icon()
847 {
848 $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>';
849
850 return $svg;
851 }
852
853 public static function get_insta_like_icon()
854 {
855 $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>';
856
857 return $svg;
858 }
859 public static function get_insta_comment_icon()
860 {
861 $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>';
862
863 return $svg;
864 }
865 public static function get_instagram_icon()
866 {
867 $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>';
868
869 return $svg;
870 }
871
872 public static function get_google_presentation_url($embedded_url)
873 {
874 $parsed_url = parse_url($embedded_url);
875 $base_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
876 $base_url = strtok($base_url, '?');
877 $base_url = rtrim($base_url, '/');
878 return $base_url;
879 }
880
881 public static function check_media_format($url)
882 {
883 // Strip query/hash so signed streaming URLs (Mux, CloudFront) match.
884 $path = preg_replace('/[?#].*$/', '', (string) $url);
885 $pattern1 = '/\.(mp4|mov|avi|wmv|flv|mkv|webm|mpeg|mpg|m3u8|mpd)$/i';
886 $pattern2 = '/\.(mp3|wav|ogg|aac)$/i';
887
888 $isVideo = preg_match($pattern1, $path);
889 $isAudio = preg_match($pattern2, $path);
890
891 $is_self_hosted = false;
892 $format = '';
893
894 if (!empty($isVideo) || !empty($isAudio)) {
895 $is_self_hosted = true;
896 if (!empty($isVideo)) {
897 $format = 'video';
898 } else if (!empty($isAudio)) {
899 $format = 'audio';
900 }
901 }
902
903 if (!$is_self_hosted) {
904 return [
905 'selhosted' => false,
906 ];
907 }
908
909 return [
910 'selhosted' => true,
911 'format' => $format,
912 ];
913 }
914
915 // Ajax Methods get instagram feed data
916 public function loadmore_data_handler()
917 {
918 $connected_account_type = isset($_POST['connected_account_type']) ? sanitize_text_field($_POST['connected_account_type']) : 'personal';
919 $hashtag_id = isset($_POST['hashtag_id']) ? sanitize_text_field($_POST['hashtag_id']) : '';
920 $feed_type = isset($_POST['feed_type']) ? sanitize_text_field($_POST['feed_type']) : 'user_aacount_type';
921 $user_id = isset($_POST['user_id']) ? sanitize_text_field($_POST['user_id']) : '';
922 $loadmore_key = isset($_POST['loadmore_key']) ? sanitize_text_field($_POST['loadmore_key']) : '';
923 $nonce = isset($_POST['_nonce']) ? sanitize_text_field($_POST['_nonce']) : '';
924 $params = isset($_POST['params']) ? sanitize_text_field($_POST['params']) : '';
925 // $params = json_decode($params, true);
926 $params = stripslashes($params); // Remove extra backslashes
927 $params = json_decode($params, true);
928
929 // Verify nonce
930 if (!wp_verify_nonce($nonce, 'ep_nonce')) {
931 wp_send_json_error('Invalid nonce');
932 wp_die(); // Terminate script execution if nonce is invalid
933 }
934
935 $feeds_data = get_option('ep_instagram_feed_data');
936 $user_data = $feeds_data[$user_id]['feed_userinfo'];
937
938 if ($feed_type == 'hashtag_type') {
939 $feeds_data = get_option('ep_instagram_hashtag_feed');
940 }
941
942
943 $profile_picture_url = isset($user_data['profile_picture_url']) ? $user_data['profile_picture_url'] : '';
944
945 if ($feed_type === 'user_account_type' && isset($feeds_data[$loadmore_key]['feed_posts'])) {
946 $feed_posts = $feeds_data[$loadmore_key]['feed_posts'];
947 } else if ($feed_type === 'hashtag_type' && isset($feeds_data[$loadmore_key])) {
948 $feed_posts = $feeds_data[$loadmore_key];
949 } else {
950 $feed_posts = ['error'];
951 }
952
953
954 if (is_array($feed_posts) && count($feed_posts) > 0) {
955 $loaded_posts = isset($_POST['loaded_posts']) ? intval($_POST['loaded_posts']) : 0;
956 $posts_per_page = isset($_POST['posts_per_page']) ? intval($_POST['posts_per_page']) : 0;
957
958 $post_index = $loaded_posts + 1;
959 $start_index = $loaded_posts;
960
961 $next_posts = array_slice($feed_posts, $start_index, $posts_per_page);
962
963 ob_start();
964
965 if (is_array($next_posts) && count($next_posts) > 0) :
966 foreach ($next_posts as $post) :
967 $caption = !empty($post['caption']) ? $post['caption'] : '';
968 $media_type = !empty($post['media_type']) ? $post['media_type'] : '';
969 $media_url = !empty($post['media_url']) ? $post['media_url'] : '';
970 $permalink = !empty($post['permalink']) ? $post['permalink'] : '';
971 $timestamp = !empty($post['timestamp']) ? $post['timestamp'] : '';
972 $username = !empty($post['username']) ? $post['username'] : '';
973 $like_count = !empty($post['like_count']) ? $post['like_count'] : 0;
974 $comments_count = !empty($post['comments_count']) ? $post['comments_count'] : 0;
975
976 $post['profile_picture_url'] = $profile_picture_url;
977 $post['show_likes_count'] = isset($params['show_likes_count']) ? $params['show_likes_count'] : false;
978 $post['show_comments_count'] = isset($params['show_comments_count']) ? $params['show_comments_count'] : false;
979 $post['popup_follow_button'] = isset($params['popup_follow_button']) ? $params['popup_follow_button'] : true;
980 $post['popup_follow_button_text'] = isset($params['popup_follow_button_text']) ? $params['popup_follow_button_text'] : 'Follow';
981 ?>
982
983 <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); ?>">
984 <?php
985
986 if (!empty($hashtag_id) && $media_type == 'CAROUSEL_ALBUM') {
987 if (isset($post['children']['data'][0]['media_url'])) {
988 $hashtag_media_url = $post['children']['data'][0]['media_url'];
989 $hashtag_media_type = $post['children']['data'][0]['media_type'];
990
991 if ($hashtag_media_type == 'VIDEO') {
992 echo '<video class="insta-gallery-image" src="' . esc_url($hashtag_media_url) . '"></video>';
993 } else {
994 echo ' <img class="insta-gallery-image" src="' . esc_url($hashtag_media_url) . '" alt="' . esc_attr('image') . '">';
995 }
996 }
997 } else {
998 if ($media_type == 'VIDEO') {
999 echo '<video class="insta-gallery-image" src="' . esc_url($media_url) . '"></video>';
1000 } else {
1001 echo ' <img class="insta-gallery-image" src="' . esc_url($media_url) . '" alt="' . esc_attr('image') . '">';
1002 }
1003 }
1004 ?>
1005
1006 <div class="insta-gallery-item-type">
1007 <div class="insta-gallery-item-type-icon">
1008 <?php
1009 if ($media_type == 'VIDEO') {
1010 echo Helper::get_insta_video_icon();
1011 } else if ($media_type == 'CAROUSEL_ALBUM') {
1012 echo Helper::get_insta_image_carousel_icon();
1013 } else {
1014 echo Helper::get_insta_image_icon();
1015 }
1016 ?>
1017 </div>
1018 </div>
1019 <div class="insta-gallery-item-info">
1020 <?php if (apply_filters('embedpress/is_allow_rander', false)): ?>
1021 <div class="insta-item-reaction-count">
1022 <div class="insta-gallery-item-likes">
1023 <?php echo Helper::get_insta_like_icon();
1024 echo esc_html($like_count); ?>
1025 </div>
1026 <div class="insta-gallery-item-comments">
1027 <?php echo Helper::get_insta_comment_icon();
1028 echo esc_html($comments_count); ?>
1029 </div>
1030 </div>
1031 <?php else : ?>
1032 <div class="insta-gallery-item-permalink">
1033 <?php echo Helper::get_instagram_icon(); ?>
1034 </div>
1035 <?php endif; ?>
1036 </div>
1037 </div>
1038
1039 <?php $post_index++;
1040 endforeach;
1041 endif;
1042
1043 $feed_item = ob_get_clean();
1044
1045 $next_start_index = $start_index + count($next_posts);
1046
1047 wp_send_json(array(
1048 'html' => $feed_item,
1049 'next_post_index' => $next_start_index,
1050 'total_feed_posts' => count($feed_posts)
1051 ));
1052 } else {
1053 wp_send_json('');
1054 }
1055 }
1056
1057 public static function getCalendlyUuid($url)
1058 {
1059 $pattern = '/\/([0-9a-fA-F-]+)$/';
1060 if (preg_match($pattern, $url, $matches)) {
1061 $uuid = $matches[1];
1062 return $uuid;
1063 }
1064 return '';
1065 }
1066
1067 public static function getCalendlyUserInfo($access_token)
1068 {
1069 $transient_name = 'calendly_user_info_' . $access_token;
1070 $user_info = get_transient($transient_name);
1071 if (false === $user_info) {
1072 $user_endpoint = 'https://api.calendly.com/users/me';
1073 $headers = array(
1074 'Authorization' => "Bearer $access_token",
1075 'Content-Type' => 'application/json',
1076 );
1077 $args = array(
1078 'headers' => $headers,
1079 );
1080 $response = wp_remote_get($user_endpoint, $args);
1081 if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
1082 $user_info = wp_remote_retrieve_body($response);
1083 set_transient($transient_name, $user_info, 3600);
1084 } else {
1085 return false;
1086 }
1087 }
1088
1089 return $user_info;
1090 }
1091
1092
1093
1094 public static function getCalaendlyEventTypes($user_uri, $access_token)
1095 {
1096 // Attempt to retrieve the data from the transient
1097 $events_list = get_transient('calendly_events_list_' . md5($access_token));
1098
1099 if (false === $events_list) {
1100 // If the data is not in the transient, fetch it from the API
1101 $events_endpoint = "https://api.calendly.com/event_types?user=$user_uri";
1102
1103 $headers = array(
1104 'Authorization' => "Bearer $access_token",
1105 'Content-Type' => 'application/json',
1106 );
1107
1108 $args = array(
1109 'headers' => $headers,
1110 );
1111
1112 $response = wp_remote_get($events_endpoint, $args);
1113
1114 if (!is_wp_error($response)) {
1115 $body = wp_remote_retrieve_body($response);
1116 $events_list = json_decode($body, true);
1117
1118 // Store the data in a transient for a specified time (e.g., 1 hour)
1119 set_transient('calendly_events_list', $events_list, HOUR_IN_SECONDS);
1120
1121 return $events_list;
1122 }
1123 }
1124
1125 return $events_list;
1126 }
1127
1128 public static function getListEventInvitee($uuid, $access_token)
1129 {
1130 // Attempt to retrieve the data from the transient
1131 $invitee_list = get_transient('calendly_invitee_list_' . md5($access_token));
1132
1133 if (false === $invitee_list) {
1134 // If the data is not in the transient, fetch it from the API
1135 $events_endpoint = "https://api.calendly.com/scheduled_events/$uuid/invitees";
1136
1137 $headers = array(
1138 'Authorization' => "Bearer $access_token",
1139 'Content-Type' => 'application/json',
1140 );
1141
1142 $args = array(
1143 'headers' => $headers,
1144 );
1145
1146 $response = wp_remote_get($events_endpoint, $args);
1147
1148 if (!is_wp_error($response)) {
1149 $body = wp_remote_retrieve_body($response);
1150 $invitee_list = json_decode($body, true);
1151
1152 // Store the data in a transient for a specified time (e.g., 1 hour)
1153 set_transient('calendly_invitee_list', $invitee_list, HOUR_IN_SECONDS);
1154
1155 return $invitee_list;
1156 }
1157 }
1158
1159 return $invitee_list;
1160 }
1161
1162 public static function getCalaendlyScheduledEvents($user_uri, $access_token)
1163 {
1164 // Attempt to retrieve the data from the transient
1165 $events_list = get_transient('calendly_events_list_' . md5($access_token));
1166
1167 if (false === $events_list) {
1168 // If the data is not in the transient, fetch it from the API
1169 $events_endpoint = "https://api.calendly.com/scheduled_events?user=$user_uri";
1170
1171 $headers = array(
1172 'Authorization' => "Bearer $access_token",
1173 'Content-Type' => 'application/json',
1174 );
1175
1176 $args = array(
1177 'headers' => $headers,
1178 );
1179
1180 $response = wp_remote_get($events_endpoint, $args);
1181
1182 if (!is_wp_error($response)) {
1183 $body = wp_remote_retrieve_body($response);
1184 $events_list = json_decode($body, true);
1185
1186 // Store the data in a transient for a specified time (e.g., 1 hour)
1187 set_transient('calendly_events_list', $events_list, HOUR_IN_SECONDS);
1188
1189 return $events_list;
1190 }
1191 }
1192
1193 return $events_list;
1194 }
1195
1196
1197 public static function parseDuration($durationString)
1198 {
1199 list($minutes, $seconds) = explode(':', $durationString);
1200 return intval($minutes) * 60 + intval($seconds);
1201 }
1202
1203
1204
1205
1206 public static function is_pro_active()
1207 {
1208 if (defined('EMBEDPRESS_SL_ITEM_SLUG')) {
1209 return true;
1210 }
1211 return false;
1212 }
1213
1214 /**
1215 * Check if pro features should be enabled based on license status
1216 *
1217 * @return bool True if pro features should be enabled, false otherwise
1218 */
1219 public static function is_pro_features_enabled()
1220 {
1221 // First check if pro plugin is active
1222 if (!self::is_pro_active()) {
1223 return false;
1224 }
1225
1226 // Get license status
1227 $license_status = get_option('embedpress_pro_software__license_status', '');
1228
1229 // Pro features are enabled only if license is valid
1230 return $license_status === 'valid';
1231 }
1232
1233 /**
1234 * Get detailed license information
1235 *
1236 * @return array License information including status, key, and data
1237 */
1238 public static function get_license_info()
1239 {
1240 $is_pro_active = self::is_pro_active();
1241 $license_status = '';
1242 $license_key = '';
1243 $license_data = [];
1244 $is_features_enabled = false;
1245
1246 if ($is_pro_active) {
1247 $license_status = get_option('embedpress_pro_software__license_status', '');
1248 $license_data_raw = get_transient('embedpress_pro_software__license_data');
1249
1250 if ($license_data_raw) {
1251 $license_data = json_decode(json_encode($license_data_raw), true);
1252 }
1253
1254 // The license STATUS is a persistent option, but the license_data
1255 // TRANSIENT (which carries the key) expires — so reading the key
1256 // only from the transient leaves an "Active" license with an empty
1257 // key field once it lapses. The Pro LicenseManager also persists the
1258 // full key in the `embedpress_pro_software__license` option, so use
1259 // that as the authoritative source; fall back to the transient's
1260 // (already-masked) key only if the option is somehow empty.
1261 $license_key = get_option('embedpress_pro_software__license', '');
1262 if ($license_key === '' && isset($license_data['license_key'])) {
1263 $license_key = $license_data['license_key'];
1264 }
1265
1266 $is_features_enabled = ($license_status === 'valid');
1267 }
1268
1269 return [
1270 'is_pro_active' => $is_pro_active,
1271 'license_status' => $license_status,
1272 'license_key' => $license_key,
1273 'license_data' => $license_data,
1274 'is_features_enabled' => $is_features_enabled,
1275 'status_message' => self::get_license_status_message($is_pro_active, $license_status)
1276 ];
1277 }
1278
1279 /**
1280 * Mask a license key for display, keeping only the head and tail visible.
1281 *
1282 * The full key must never render into the DOM of the settings page (it sits
1283 * in a disabled input, readable via view-source / dev-tools). This keeps the
1284 * real key's first 4 and last 5 characters and replaces every character in
1285 * between with an asterisk — so the mask length matches the true key length
1286 * (e.g. a 29-char key → "2343********************32432"). Safe to echo into
1287 * the visible input's value.
1288 *
1289 * Short keys (<= head+tail chars) are fully masked so nothing meaningful
1290 * leaks. Empty input returns empty.
1291 *
1292 * @param string $key The raw license key.
1293 * @param int $head Visible leading chars. Default 4.
1294 * @param int $tail Visible trailing chars. Default 5.
1295 * @return string Masked key, or '' when $key is empty.
1296 */
1297 public static function mask_license_key($key, $head = 4, $tail = 5)
1298 {
1299 $key = (string) $key;
1300 $len = strlen($key);
1301
1302 if ($len === 0) {
1303 return '';
1304 }
1305
1306 // Too short to reveal head+tail without overlap — mask entirely.
1307 if ($len <= ($head + $tail)) {
1308 return str_repeat('*', $len);
1309 }
1310
1311 // Star out the real middle so the masked length equals the key length.
1312 return substr($key, 0, $head) . str_repeat('*', $len - $head - $tail) . substr($key, -$tail);
1313 }
1314
1315 /**
1316 * Display license warning notice for pro features
1317 *
1318 * @param string $context Context where the notice is displayed (e.g., 'analytics', 'branding')
1319 * @return string HTML for the license warning notice
1320 */
1321 public static function get_pro_feature_notice($context = '')
1322 {
1323 $license_info = self::get_license_info();
1324
1325 if (!$license_info['is_pro_active']) {
1326 return '<div class="embedpress-pro-notice embedpress-notice-warning">
1327 <p><strong>' . __('EmbedPress Pro Required', 'embedpress') . '</strong></p>
1328 <p>' . __('This feature requires EmbedPress Pro. Please install and activate EmbedPress Pro to access this feature.', 'embedpress') . '</p>
1329 </div>';
1330 }
1331
1332 if (!$license_info['is_features_enabled']) {
1333 $message = $license_info['status_message'];
1334 $action_text = '';
1335 $action_link = admin_url('admin.php?page=embedpress&page_type=license');
1336
1337 switch ($license_info['license_status']) {
1338 case 'expired':
1339 $action_text = __('Renew License', 'embedpress');
1340 break;
1341 case 'invalid':
1342 case 'site_inactive':
1343 case 'disabled':
1344 case 'revoked':
1345 $action_text = __('Activate License', 'embedpress');
1346 break;
1347 default:
1348 $action_text = __('Activate License', 'embedpress');
1349 break;
1350 }
1351
1352 return '<div class="embedpress-pro-notice embedpress-notice-error">
1353 <p><strong>' . __('Pro Features Disabled', 'embedpress') . '</strong></p>
1354 <p>' . esc_html($message) . '</p>
1355 <p><a href="' . esc_url($action_link) . '" class="button button-primary">' . esc_html($action_text) . '</a></p>
1356 </div>';
1357 }
1358
1359 return '';
1360 }
1361
1362 /**
1363 * Initialize license checking hooks
1364 */
1365 public static function init_license_hooks()
1366 {
1367 // Add filter to disable pro features when license is not valid
1368 add_filter('embedpress_pro_features_enabled', [__CLASS__, 'is_pro_features_enabled']);
1369
1370 // Add action to show license notices in admin
1371 add_action('admin_notices', [__CLASS__, 'show_license_admin_notices']);
1372
1373 // Add styles for license notices
1374 add_action('admin_head', [__CLASS__, 'add_license_notice_styles']);
1375 }
1376
1377 /**
1378 * Show license admin notices
1379 */
1380 public static function show_license_admin_notices()
1381 {
1382 // Only show on EmbedPress admin pages
1383 if (!isset($_GET['page']) || $_GET['page'] !== 'embedpress') {
1384 return;
1385 }
1386
1387 $license_info = self::get_license_info();
1388
1389 if ($license_info['is_pro_active'] && !$license_info['is_features_enabled']) {
1390 $message = $license_info['status_message'];
1391 $action_link = admin_url('admin.php?page=embedpress&page_type=license');
1392 $action_text = $license_info['license_status'] === 'expired' ? __('Renew License', 'embedpress') : __('Activate License', 'embedpress');
1393
1394 echo '<div class="notice notice-warning is-dismissible">
1395 <p><strong>' . __('EmbedPress Pro Features Disabled', 'embedpress') . '</strong></p>
1396 <p>' . esc_html($message) . '</p>
1397 <p><a href="' . esc_url($action_link) . '" class="button button-primary">' . esc_html($action_text) . '</a></p>
1398 </div>';
1399 }
1400 }
1401
1402 /**
1403 * Add CSS styles for license notices
1404 */
1405 public static function add_license_notice_styles()
1406 {
1407 echo '<style>
1408 .embedpress-pro-notice {
1409 padding: 15px;
1410 margin: 15px 0;
1411 border-left: 4px solid #dc3545;
1412 background: #fff;
1413 box-shadow: 0 1px 1px rgba(0,0,0,.04);
1414 }
1415 .embedpress-pro-notice.embedpress-notice-warning {
1416 border-left-color: #ffb900;
1417 }
1418 .embedpress-pro-notice.embedpress-notice-error {
1419 border-left-color: #dc3545;
1420 }
1421 .embedpress-pro-notice p {
1422 margin: 0.5em 0;
1423 }
1424 .embedpress-pro-notice strong {
1425 color: #23282d;
1426 }
1427 .embedpress-text-error {
1428 color: #dc3545 !important;
1429 }
1430 .embedpress-text-warning {
1431 color: #ffb900 !important;
1432 }
1433 </style>';
1434 }
1435
1436 /**
1437 * Get user-friendly license status message
1438 *
1439 * @param bool $is_pro_active Whether pro plugin is active
1440 * @param string $license_status Current license status
1441 * @return string User-friendly status message
1442 */
1443 public static function get_license_status_message($is_pro_active, $license_status)
1444 {
1445 if (!$is_pro_active) {
1446 return __('EmbedPress Pro is not installed or activated.', 'embedpress');
1447 }
1448
1449 switch ($license_status) {
1450 case 'valid':
1451 return __('Your license is active and valid.', 'embedpress');
1452 case 'expired':
1453 return __('Your license has expired. Please renew to continue receiving updates and support.', 'embedpress');
1454 case 'invalid':
1455 case 'site_inactive':
1456 return __('Your license is not active for this URL. Please activate your license.', 'embedpress');
1457 case 'disabled':
1458 case 'revoked':
1459 return __('Your license key has been disabled or revoked.', 'embedpress');
1460 case 'missing':
1461 return __('License key is missing. Please enter your license key.', 'embedpress');
1462 case 'http_error':
1463 return __('Unable to verify license due to connection issues. Please try again later.', 'embedpress');
1464 case '':
1465 case false:
1466 default:
1467 return __('Please activate your license key to enable EmbedPress Pro features.', 'embedpress');
1468 }
1469 }
1470
1471
1472 public static function getInstagramUserInfo($accessToken, $accountType, $userId, $is_sync = false)
1473 {
1474 if ($is_sync) {
1475 // If $is_sync is true, don't use transient
1476 $use_transient = false;
1477 } else {
1478 // If $is_sync is false, use transient
1479 $transient_key = 'instagram_user_info_' . $userId;
1480 $use_transient = true;
1481 }
1482
1483 if ($use_transient && false !== ($userInfo = get_transient($transient_key))) {
1484 // If transient exists, return cached user info
1485 return $userInfo;
1486 }
1487
1488 if (strtolower($accountType) === 'personal') {
1489 $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;
1490 } else {
1491 $api_url = 'https://graph.facebook.com/' . $userId . '?fields=biography,id,username,website,followers_count,media_count,profile_picture_url,name&access_token=' . $accessToken;
1492 }
1493
1494 $connected_account_type = $accountType;
1495
1496 $userInfoResponse = wp_remote_get($api_url);
1497
1498 if (is_wp_error($userInfoResponse)) {
1499 echo 'Error: Unable to retrieve Instagram user information.';
1500 } else {
1501 $userInfoBody = wp_remote_retrieve_body($userInfoResponse);
1502 $userInfo = json_decode($userInfoBody, true);
1503
1504 $userInfo['connected_account_type'] = $connected_account_type;
1505 $userInfo['access_token'] = $accessToken;
1506
1507
1508 if (!isset($userInfo['profile_picture_url'])) {
1509 $userInfo['profile_picture_url'] = '';
1510 }
1511
1512 // If not using transient, cache the user info for an hour
1513 if ($use_transient) {
1514 set_transient($transient_key, $userInfo, HOUR_IN_SECONDS);
1515 }
1516
1517 return $userInfo;
1518 }
1519 }
1520
1521
1522 // Get Instagram posts, videos, reels
1523 public static function getInstagramPosts($access_token, $account_type, $userId, $limit = 100, $is_sync = false)
1524 {
1525 if ($is_sync) {
1526 // If $is_sync is true, don't use transient
1527 $use_transient = false;
1528 } else {
1529 // If $is_sync is false, use transient
1530 $transient_key = 'instagram_posts_' . $userId;
1531 $use_transient = true;
1532 }
1533
1534 if ($use_transient && false !== ($posts = get_transient($transient_key))) {
1535 // If transient exists, return cached posts
1536 return $posts;
1537 }
1538
1539 if (strtolower($account_type) === 'personal') {
1540 $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;
1541 } else {
1542 $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;
1543 }
1544
1545 $postsResponse = wp_remote_get($api_url);
1546
1547 if (is_wp_error($postsResponse)) {
1548 echo 'Error: Unable to retrieve Instagram posts.';
1549 } else {
1550 $postsBody = wp_remote_retrieve_body($postsResponse);
1551 $posts = json_decode($postsBody, true);
1552
1553 if (empty($posts['data'])) {
1554 return 'Please add Instagram Access Token';
1555 }
1556
1557 // If not using transient, cache the posts for an hour
1558 if ($use_transient) {
1559 set_transient($transient_key, $posts['data'], HOUR_IN_SECONDS);
1560 }
1561
1562 return $posts['data'];
1563 }
1564 }
1565
1566 public static function get_enable_settings_data_for_scripts($settings)
1567 {
1568 $settings_data = [
1569 'enabled_ads' => isset($settings['adManager']) && $settings['adManager'] === 'yes' ? 'yes' : '',
1570
1571 'enabled_custom_player' => isset($settings['emberpress_custom_player']) && $settings['emberpress_custom_player'] === 'yes' ? 'yes' : '',
1572
1573 'enabled_instafeed' => isset($settings['embedpress_pro_embeded_source']) && $settings['embedpress_pro_embeded_source'] === 'instafeed' ? 'yes' : '',
1574
1575 'enabled_docs_custom_viewer' => isset($settings['embedpress_document_viewer']) && $settings['embedpress_document_viewer'] === 'custom' ? 'yes' : '',
1576 ];
1577
1578 update_option('enabled_elementor_scripts', $settings_data);
1579 }
1580
1581 public static function get_options_value($key)
1582 {
1583 $g_settings = get_option(EMBEDPRESS_PLG_NAME);
1584
1585 if (isset($g_settings['enableEmbedResizeWidth']) && $g_settings['enableEmbedResizeWidth'] == 1) {
1586 $g_settings['enableEmbedResizeWidth'] = 600;
1587 update_option(EMBEDPRESS_PLG_NAME, $g_settings);
1588 }
1589 if (isset($g_settings['enableEmbedResizeHeight']) && $g_settings['enableEmbedResizeHeight'] == 1) {
1590 $g_settings['enableEmbedResizeHeight'] = 600;
1591 update_option(EMBEDPRESS_PLG_NAME, $g_settings);
1592 }
1593
1594 if (isset($g_settings[$key])) {
1595 return $g_settings[$key];
1596 }
1597
1598 return '';
1599 }
1600
1601
1602
1603 public static function get_branding_value($key, $provider)
1604 {
1605 $settings = get_option(EMBEDPRESS_PLG_NAME . ':' . $provider, []);
1606
1607 // Check if provider has custom branding enabled and the specific key set
1608 if (isset($settings['branding']) && $settings['branding'] === 'yes') {
1609 // If provider has custom logo, use it
1610 if (isset($settings[$key]) && !empty($settings[$key])) {
1611 return $settings[$key];
1612 }
1613
1614 // If branding is enabled but no custom logo, use global brand as fallback
1615 if ($key === 'logo_url') {
1616 $global_logo = self::get_global_brand_logo_url();
1617 if (!empty($global_logo)) {
1618 return $global_logo;
1619 }
1620 }
1621 }
1622
1623 return '';
1624 }
1625
1626 /**
1627 * Get global brand logo URL
1628 *
1629 * @return string
1630 */
1631 public static function get_global_brand_logo_url()
1632 {
1633 $global_brand_settings = get_option(EMBEDPRESS_PLG_NAME . ':global_brand', []);
1634 return isset($global_brand_settings['logo_url']) ? $global_brand_settings['logo_url'] : '';
1635 }
1636
1637 /**
1638 * Get global brand logo ID
1639 *
1640 * @return int
1641 */
1642 public static function get_global_brand_logo_id()
1643 {
1644 $global_brand_settings = get_option(EMBEDPRESS_PLG_NAME . ':global_brand', []);
1645 return isset($global_brand_settings['logo_id']) ? intval($global_brand_settings['logo_id']) : 0;
1646 }
1647
1648
1649 public static function format_number($number)
1650 {
1651 if ($number >= 1000000000) {
1652 return number_format($number / 1000000000, 1) . 'b';
1653 } elseif ($number >= 1000000) {
1654 return number_format($number / 1000000, 1) . 'm';
1655 } elseif ($number >= 1000) {
1656 return number_format($number / 1000, 1) . 'k';
1657 } else {
1658 return $number;
1659 }
1660 }
1661
1662 public static function get_id($item)
1663 {
1664 $vid = isset($item->snippet->resourceId->videoId) ? $item->snippet->resourceId->videoId : null;
1665 $vid = $vid ? $vid : (isset($item->id->videoId) ? $item->id->videoId : null);
1666 $vid = $vid ? $vid : (isset($item->id) ? $item->id : null);
1667 return $vid;
1668 }
1669
1670 public static function clean_api_error($raw_message)
1671 {
1672 return htmlspecialchars(strip_tags(preg_replace('@&key=[^& ]+@i', '&key=*******', $raw_message)));
1673 }
1674
1675 public static function clean_api_error_html($raw_message)
1676 {
1677 $clean_html = '';
1678 if ((defined('REST_REQUEST') && REST_REQUEST) || current_user_can('manage_options')) {
1679 $clean_html = '<div>' . __('EmbedPress: ', 'embedpress') . self::clean_api_error($raw_message) . '</div>';
1680 }
1681 return $clean_html;
1682 }
1683
1684 public static function get_thumbnail_url($item, $quality, $privacyStatus)
1685 {
1686 $url = "";
1687 if ($privacyStatus == 'private') {
1688 $url = EMBEDPRESS_URL_ASSETS . 'images/youtube/private.png';
1689 } elseif (isset($item->snippet->thumbnails->{$quality}->url)) {
1690 $url = $item->snippet->thumbnails->{$quality}->url;
1691 } elseif (isset($item->snippet->thumbnails->medium->url)) {
1692 $url = $item->snippet->thumbnails->medium->url;
1693 } elseif (isset($item->snippet->thumbnails->default->url)) {
1694 $url = $item->snippet->thumbnails->default->url;
1695 } elseif (isset($item->snippet->thumbnails->high->url)) {
1696 $url = $item->snippet->thumbnails->high->url;
1697 } else {
1698 $url = EMBEDPRESS_URL_ASSETS . 'images/youtube/deleted-video-thumb.png';
1699 }
1700 return $url;
1701 }
1702
1703 public static function compare_vid_date($a, $b)
1704 {
1705 $dateA = strtotime($a->snippet->publishedAt);
1706 $dateB = strtotime($b->snippet->publishedAt);
1707
1708 // Sort in descending order (newest first)
1709 return $dateB - $dateA;
1710 }
1711
1712
1713 public static function get_youtube_video_data($api_key, $video_id)
1714 {
1715 // Set a unique transient name based on the video ID
1716 $transient_name = 'youtube_video_data_' . $video_id;
1717
1718 // Try to get data from the transient cache
1719 $video_data = get_transient($transient_name);
1720
1721 // If no cached data, fetch from the API
1722 if ($video_data === false) {
1723 // YouTube Data API URL
1724 $url = "https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics&id={$video_id}&key={$api_key}";
1725
1726 // Fetch the data from the API
1727 $response = wp_remote_get($url);
1728
1729 if (is_wp_error($response)) {
1730 return false; // Return false if there is an error
1731 }
1732
1733 $body = wp_remote_retrieve_body($response);
1734 $video_data = json_decode($body, true);
1735
1736 if (isset($video_data['items'][0])) {
1737 $video_data = $video_data['items'][0];
1738
1739 // Cache the data in a transient for 12 hours
1740 set_transient($transient_name, $video_data, 24 * HOUR_IN_SECONDS);
1741 } else {
1742 return false; // Return false if no data found
1743 }
1744 }
1745
1746 return $video_data;
1747 }
1748
1749 public static function trimTitle($title, $wordCount)
1750 {
1751 $words = explode(' ', $title);
1752
1753 if (count($words) <= $wordCount) {
1754 return $title; // No trimming needed
1755 }
1756
1757 $trimmedWords = array_slice($words, 0, $wordCount);
1758
1759 return implode(' ', $trimmedWords) . ' ...';
1760 }
1761
1762 public static function timeAgo($datetime)
1763 {
1764 $now = new \DateTime();
1765 $date = new \DateTime($datetime);
1766 $interval = $now->diff($date);
1767
1768 if ($interval->y > 0) {
1769 return $interval->y . ' year' . ($interval->y > 1 ? 's' : '') . ' ago';
1770 } elseif ($interval->m > 0) {
1771 return $interval->m . ' month' . ($interval->m > 1 ? 's' : '') . ' ago';
1772 } elseif ($interval->d > 0) {
1773 return $interval->d . ' day' . ($interval->d > 1 ? 's' : '') . ' ago';
1774 } elseif ($interval->h > 0) {
1775 return $interval->h . ' hour' . ($interval->h > 1 ? 's' : '') . ' ago';
1776 } elseif ($interval->i > 0) {
1777 return $interval->i . ' minute' . ($interval->i > 1 ? 's' : '') . ' ago';
1778 } else {
1779 return 'just now';
1780 }
1781 }
1782
1783 public static function removeQuote($attributes)
1784 {
1785 $parsedAttributes = [];
1786
1787 $regex = '/^on.*/i';
1788
1789 foreach ($attributes as $key => $value) {
1790 if (is_string($value)) {
1791 $cleanValue = str_replace(['"', "'"], '', $value);
1792 } else {
1793 $cleanValue = $value;
1794 }
1795
1796 if (!preg_match($regex, $key)) {
1797 $parsedAttributes[$key] = $cleanValue;
1798 }
1799 }
1800
1801 return $parsedAttributes;
1802 }
1803
1804 public static function getBooleanParam($param, $default = false)
1805 {
1806 if (in_array($param, [true, 'true', 'yes', 1, '1'], true)) {
1807 return true;
1808 }
1809 if (in_array($param, [false, 'false', 'no', 0, '0'], true)) {
1810 return false;
1811 }
1812 return (bool) $default;
1813 }
1814
1815 public static function has_allowed_roles($allowed_roles = [])
1816 {
1817
1818 if (empty($allowed_roles) || (count($allowed_roles) == 1 && empty($allowed_roles[0]))) {
1819 return true;
1820 }
1821 $current_user = wp_get_current_user();
1822 $user_roles = $current_user->roles;
1823
1824 return !empty(array_intersect($user_roles, $allowed_roles));
1825 }
1826
1827 public static function get_elementor_global_color($settings, $key)
1828 {
1829
1830 $global_color = $settings[$key];
1831
1832 if (isset($settings['__globals__'][$key])) {
1833 $color_setting = $settings['__globals__'][$key];
1834
1835 if (strpos($color_setting, 'globals/colors?id=') === 0) {
1836 // It's a global color reference
1837 $global_id = str_replace('globals/colors?id=', '', $color_setting);
1838
1839 $kit = Plugin::$instance->kits_manager->get_current_settings();
1840
1841 $system_colors = isset($kit['system_colors']) ? $kit['system_colors'] : [];
1842 $custom_colors = isset($kit['custom_colors']) ? $kit['custom_colors'] : [];
1843 $global_colors = array_merge($system_colors, $custom_colors);
1844
1845 foreach ($global_colors as $color) {
1846
1847 if ($color['_id'] === $global_id) {
1848 $global_color = $color['color']; // Found a match, set the color
1849 break;
1850 }
1851 }
1852 }
1853 }
1854 return $global_color;
1855 }
1856
1857 public static function get_provider_name($source_url)
1858 {
1859 if (self::is_youtube_channel($source_url)) {
1860 $source_name = 'YoutubeChannel';
1861 } else if (self::is_youtube($source_url)) {
1862 $source_name = 'Youtube';
1863 } else if (!empty(self::is_file_url($source_url))) {
1864 $source_name = 'document_' . self::get_extension_from_file_url($source_url);
1865 } else if (self::is_opensea($source_url)) {
1866 $source_name = 'OpenSea';
1867 } else if (self::is_instagram_feed($source_url)) {
1868 $source_name = 'InstagramFeed';
1869 }else {
1870 Shortcode::get_embera_instance();
1871 $collectios = Shortcode::get_collection();
1872 $provider = $collectios->findProviders($source_url);
1873
1874 if (!empty($provider[$source_url])) {
1875 $source_name = $provider[$source_url]->getProviderName();
1876 } else {
1877 $host = parse_url($source_url, PHP_URL_HOST);
1878 if ($host) {
1879 $parts = explode('.', $host);
1880 if (count($parts) > 1) {
1881 $source_name = $parts[1];
1882 } else {
1883 // Handle the case where the host doesn't have at least two parts
1884 $source_name = $host;
1885 }
1886 } else {
1887 // Handle the case where parse_url fails
1888 $source_name = '';
1889 }
1890 }
1891 }
1892
1893 return $source_name;
1894 }
1895
1896 public static function get_user_roles()
1897 {
1898 global $wp_roles; // Access global roles object
1899 $user_roles = [];
1900
1901 if (isset($wp_roles->roles) && is_array($wp_roles->roles)) {
1902 foreach ($wp_roles->roles as $role_key => $role_data) {
1903 $user_roles[] = [
1904 'value' => $role_key, // Machine-readable key
1905 'label' => $role_data['name'], // Human-readable name
1906 ];
1907 }
1908 }
1909
1910 return $user_roles;
1911 }
1912
1913 public static function has_content_allowed_roles($allowed_roles = [])
1914 {
1915 // Ensure it's always an array
1916 if (!is_array($allowed_roles)) {
1917 $allowed_roles = [];
1918 }
1919
1920 if (count($allowed_roles) === 1 && empty($allowed_roles[0])) {
1921 return true;
1922 }
1923
1924 $current_user = wp_get_current_user();
1925 $user_roles = $current_user->roles;
1926
1927 return !empty(array_intersect($user_roles, $allowed_roles));
1928 }
1929
1930
1931 }
1932