PluginProbe
Lawwwing | Textos legales web y Banner de cookies / trunk
Lawwwing | Textos legales web y Banner de cookies vtrunk
1.5.3 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 All 59 releases
ibamu / shortcodes / shortcodes.php

shortcodes.php in Lawwwing | Textos legales web y Banner de cookies trunk, at shortcodes/shortcodes.php

750 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Lawwwing legal documents shortcodes.
4 */
5
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 /**
11 * Returns the supported document types and labels.
12 *
13 * Document types are dynamically resolved from API payload,
14 * but these are the commonly expected ones with default labels.
15 *
16 * @return array
17 */
18 function lawwwing_get_documents_map() {
19 $documents = array(
20 'privacy' => array(
21 'label' => __('Politica de Privacidad', 'ibamu-plugin-domain'),
22 ),
23 'cookies' => array(
24 'label' => __('Politica de Cookies', 'ibamu-plugin-domain'),
25 ),
26 'cookie-table' => array(
27 'label' => __('Tabla de Cookies', 'ibamu-plugin-domain'),
28 ),
29 'notice' => array(
30 'label' => __('Aviso Legal', 'ibamu-plugin-domain'),
31 ),
32 'terms' => array(
33 'label' => __('Terminos y Condiciones', 'ibamu-plugin-domain'),
34 ),
35 'purchase' => array(
36 'label' => __('Condiciones de Compra', 'ibamu-plugin-domain'),
37 ),
38 );
39
40 return apply_filters('lawwwing_shortcode_documents_map', $documents);
41 }
42
43 /**
44 * Returns the endpoint template used to load legal documents JSON.
45 *
46 * The template must contain one `%s` placeholder for the Plugin ID.
47 *
48 * @return string
49 */
50 function lawwwing_get_documents_endpoint_template() {
51 $api_host = defined('LW_API_HOST') ? rtrim(LW_API_HOST, '/') : '';
52
53 return apply_filters(
54 'lawwwing_documents_endpoint_template',
55 $api_host . '/api/documents/widget/%s/'
56 );
57 }
58
59 /**
60 * Rewrite a document URL to use the configured rendering host.
61 *
62 * @param string $url
63 * @return string|false
64 */
65 function lawwwing_get_render_document_url($url) {
66 $safe_url = esc_url_raw($url);
67 if (empty($safe_url)) {
68 return false;
69 }
70
71 if (!defined('LW_DOCUMENTS_BASE_HOST') || LW_DOCUMENTS_BASE_HOST === '') {
72 return $safe_url;
73 }
74
75 $base_parts = wp_parse_url(LW_DOCUMENTS_BASE_HOST);
76 $url_parts = wp_parse_url($safe_url);
77
78 if (!is_array($base_parts) || !is_array($url_parts) || empty($base_parts['scheme']) || empty($base_parts['host'])) {
79 return $safe_url;
80 }
81
82 $rewritten_url = $base_parts['scheme'] . '://';
83
84 if (!empty($base_parts['user'])) {
85 $rewritten_url .= $base_parts['user'];
86 if (!empty($base_parts['pass'])) {
87 $rewritten_url .= ':' . $base_parts['pass'];
88 }
89 $rewritten_url .= '@';
90 }
91
92 $rewritten_url .= $base_parts['host'];
93
94 if (!empty($base_parts['port'])) {
95 $rewritten_url .= ':' . $base_parts['port'];
96 }
97
98 $rewritten_url .= !empty($url_parts['path']) ? $url_parts['path'] : '/';
99
100 if (!empty($url_parts['query'])) {
101 $rewritten_url .= '?' . $url_parts['query'];
102 }
103
104 if (!empty($url_parts['fragment'])) {
105 $rewritten_url .= '#' . $url_parts['fragment'];
106 }
107
108 return $rewritten_url;
109 }
110
111 /**
112 * Normalize category aliases returned by API to plugin document keys.
113 *
114 * Maps API category names to internal document type keys.
115 * Returns both forward (api -> plugin) and reverse (plugin -> api) mappings.
116 *
117 * @return array Array with 'forward' and 'reverse' keys.
118 */
119 function lawwwing_get_category_aliases() {
120 $forward = apply_filters(
121 'lawwwing_documents_category_aliases_forward',
122 array(
123 // Aviso Legal.
124 'notice' => 'notice',
125 'legal_notice' => 'notice',
126
127 // Politica de Privacidad (manual key can be priv or privacy).
128 'priv' => 'privacy',
129 'privacy' => 'privacy',
130 'privacy_policy' => 'privacy',
131
132 // Politica de Cookies (manual key can be cookies or privacy).
133 'cookies' => 'cookies',
134 'cookie_policy' => 'cookies',
135 'privacy_and_cookies' => 'privacy',
136
137 // Tabla de Cookies.
138 'cookie-table' => 'cookie-table',
139 'cookie_table' => 'cookie-table',
140 'cookies_table' => 'cookie-table',
141
142 // Terminos y Condiciones / Condiciones de Compra (manual keys interchangeable).
143 'terms' => 'terms',
144 'terms_conditions' => 'terms',
145 'terms_and_conditions' => 'terms',
146 'purchase' => 'purchase',
147 'purchase_conditions' => 'purchase',
148 )
149 );
150
151 $reverse = array_flip($forward);
152
153 return array(
154 'forward' => $forward,
155 'reverse' => $reverse,
156 );
157 }
158
159 /**
160 * Build endpoint URL using the configured plugin ID.
161 *
162 * @param string $plugin_id
163 * @return string|false
164 */
165 function lawwwing_get_documents_endpoint_url($plugin_id) {
166 if (empty($plugin_id)) {
167 return false;
168 }
169
170 $template = lawwwing_get_documents_endpoint_template();
171
172 if (strpos($template, '%s') === false) {
173 return false;
174 }
175
176 return sprintf($template, rawurlencode($plugin_id));
177 }
178
179 /**
180 * Detect WordPress site language code.
181 *
182 * Returns the current site language or a fallback.
183 *
184 * @return string Language code (e.g., 'es', 'en', 'fr').
185 */
186 function lawwwing_get_site_language() {
187 $lang = get_locale();
188
189 if (!empty($lang)) {
190 $lang_parts = explode('_', $lang);
191 if (!empty($lang_parts[0])) {
192 return strtolower($lang_parts[0]);
193 }
194 }
195
196 return 'en';
197 }
198
199 /**
200 * Get URL from a language variant, with fallback.
201 *
202 * @param array $language_variants Array of language keys to objects with 'url'.
203 * @param string $preferred_lang Preferred language code.
204 * @return string|false
205 */
206 function lawwwing_extract_url_from_language_variants($language_variants, $preferred_lang) {
207 if (!is_array($language_variants) || empty($language_variants)) {
208 return false;
209 }
210
211 if (isset($language_variants[$preferred_lang]['url'])) {
212 $url = $language_variants[$preferred_lang]['url'];
213 $safe_url = esc_url_raw($url);
214 if (!empty($safe_url)) {
215 return $safe_url;
216 }
217 }
218
219 foreach ($language_variants as $lang_code => $lang_data) {
220 if (is_array($lang_data) && !empty($lang_data['url'])) {
221 $url = $lang_data['url'];
222 $safe_url = esc_url_raw($url);
223 if (!empty($safe_url)) {
224 return $safe_url;
225 }
226 }
227 }
228
229 return false;
230 }
231
232 /**
233 * Parse API payload into a normalized map: {type => url}.
234 *
235 * Handles the nested language structure:
236 * {
237 * "notice": {
238 * "es": {"url": "...", "title": "...", ...},
239 * "en": {"url": "...", "title": "...", ...}
240 * },
241 * "privacy": { ... }
242 * }
243 *
244 * @param array $payload
245 * @return array
246 */
247 function lawwwing_parse_documents_payload($payload) {
248 $aliases = lawwwing_get_category_aliases();
249 $forward_aliases = $aliases['forward'];
250 $resolved = array();
251
252 if (!is_array($payload)) {
253 return $resolved;
254 }
255
256 $preferred_lang = lawwwing_get_site_language();
257
258 foreach ($payload as $category_key => $category_data) {
259 if (!is_array($category_data)) {
260 continue;
261 }
262
263 $normalized_category = sanitize_key($category_key);
264 if (!isset($forward_aliases[$normalized_category])) {
265 continue;
266 }
267
268 $document_type = $forward_aliases[$normalized_category];
269 $url = lawwwing_extract_url_from_language_variants($category_data, $preferred_lang);
270
271 if ($url) {
272 $resolved[$document_type] = $url;
273 }
274 }
275
276 // Manual configuration compatibility:
277 // - Politica de cookies can be keyed as `cookies` or `privacy`.
278 // - Terms and purchase can be keyed as `terms` or `purchase`.
279 if (empty($resolved['cookies']) && !empty($resolved['privacy'])) {
280 $resolved['cookies'] = $resolved['privacy'];
281 }
282
283 if (empty($resolved['privacy']) && !empty($resolved['cookies'])) {
284 $resolved['privacy'] = $resolved['cookies'];
285 }
286
287 if (empty($resolved['terms']) && !empty($resolved['purchase'])) {
288 $resolved['terms'] = $resolved['purchase'];
289 }
290
291 if (empty($resolved['purchase']) && !empty($resolved['terms'])) {
292 $resolved['purchase'] = $resolved['terms'];
293 }
294
295 return $resolved;
296 }
297
298 /**
299 * Fetch and cache legal documents map from API.
300 *
301 * @return array
302 */
303 function lawwwing_get_remote_documents_map($force_refresh = false) {
304 $plugin_id = lawwwing_get_plugin_id();
305
306 if (empty($plugin_id)) {
307 return array();
308 }
309
310 $cache_key = 'lawwwing_docs_' . md5($plugin_id);
311 $cached = get_transient($cache_key);
312
313 if (!$force_refresh && is_array($cached)) {
314 return $cached;
315 }
316
317 $endpoint = lawwwing_get_documents_endpoint_url($plugin_id);
318 if (!$endpoint) {
319 return array();
320 }
321
322 $response = wp_remote_get(
323 $endpoint,
324 array(
325 'timeout' => 8,
326 'headers' => array(
327 'Accept' => 'application/json',
328 ),
329 )
330 );
331
332 if (is_wp_error($response)) {
333 return array();
334 }
335
336 $status = wp_remote_retrieve_response_code($response);
337 if ((int) $status !== 200) {
338 return array();
339 }
340
341 $body = wp_remote_retrieve_body($response);
342 if (empty($body)) {
343 return array();
344 }
345
346 $payload = json_decode($body, true);
347 if (!is_array($payload)) {
348 return array();
349 }
350
351 $documents = lawwwing_parse_documents_payload($payload);
352 set_transient($cache_key, $documents, 30 * MINUTE_IN_SECONDS);
353
354 return $documents;
355 }
356
357 /**
358 * Delete cached documents map by plugin ID.
359 *
360 * @param string $plugin_id
361 * @return void
362 */
363 function lawwwing_delete_documents_cache($plugin_id) {
364 if (empty($plugin_id)) {
365 return;
366 }
367
368 delete_transient('lawwwing_docs_' . md5($plugin_id));
369 }
370
371 /**
372 * Invalidate documents cache when plugin options are updated.
373 *
374 * @param mixed $old_value
375 * @param mixed $new_value
376 * @return void
377 */
378 function lawwwing_invalidate_documents_cache($old_value, $new_value) {
379 $old_id = '';
380 $new_id = '';
381
382 if (is_array($old_value) && !empty($old_value['ibamu_widget_uuid'])) {
383 $old_id = $old_value['ibamu_widget_uuid'];
384 }
385
386 if (is_array($new_value) && !empty($new_value['ibamu_widget_uuid'])) {
387 $new_id = $new_value['ibamu_widget_uuid'];
388 }
389
390 lawwwing_delete_documents_cache($old_id);
391 lawwwing_delete_documents_cache($new_id);
392 }
393 add_action('update_option_ibamu_options', 'lawwwing_invalidate_documents_cache', 20, 2);
394
395 /**
396 * Resolve a document type to the URL obtained from the API.
397 *
398 * @param string $type Document type key.
399 * @return string|false
400 */
401 function lawwwing_get_document_url_by_type($type) {
402 $documents = lawwwing_get_remote_documents_map();
403 $url = isset($documents[$type]) ? $documents[$type] : false;
404
405 // Retry once bypassing cache in case API content changed after transient creation.
406 if (empty($url)) {
407 $documents = lawwwing_get_remote_documents_map(true);
408 $url = isset($documents[$type]) ? $documents[$type] : false;
409 }
410
411 return !empty($url) ? lawwwing_get_render_document_url($url) : false;
412 }
413
414 /**
415 * Get debug information about shortcode resolution.
416 *
417 * @param string $type Document type.
418 * @return array
419 */
420 function lawwwing_get_shortcode_debug_info($type) {
421 $plugin_id = lawwwing_get_plugin_id();
422 $endpoint = lawwwing_get_documents_endpoint_url($plugin_id);
423 $documents = lawwwing_get_remote_documents_map();
424 $url = lawwwing_get_document_url_by_type($type);
425 $site_lang = lawwwing_get_site_language();
426
427 return array(
428 'type' => $type,
429 'plugin_id' => $plugin_id,
430 'plugin_id_set' => !empty($plugin_id),
431 'endpoint' => $endpoint,
432 'site_language' => $site_lang,
433 'documents_available' => array_keys($documents),
434 'requested_url' => $url,
435 'found' => !empty($url),
436 'raw_documents_map' => $documents,
437 );
438 }
439
440 /**
441 * Extract the body contents from an HTML document.
442 *
443 * @param string $html
444 * @return string
445 */
446 function lawwwing_extract_html_body($html) {
447 if (!is_string($html) || $html === '') {
448 return '';
449 }
450
451 if (preg_match('/<body[^>]*>(.*)<\/body>/is', $html, $matches)) {
452 return $matches[1];
453 }
454
455 return $html;
456 }
457
458 /**
459 * Convert a raw text response into basic HTML paragraphs.
460 *
461 * @param string $text
462 * @return string
463 */
464 function lawwwing_format_inline_text_content($text) {
465 if (!is_string($text)) {
466 return '';
467 }
468
469 $text = trim($text);
470 if ($text === '') {
471 return '';
472 }
473
474 return wpautop(esc_html($text));
475 }
476
477 /**
478 * Build the raw URL used for inline document rendering.
479 *
480 * @param string $url
481 * @return string|false
482 */
483 function lawwwing_get_inline_raw_url($url) {
484 $url_parts = wp_parse_url($url);
485 if (!is_array($url_parts) || empty($url_parts['scheme']) || empty($url_parts['host'])) {
486 return false;
487 }
488
489 $raw_url = $url_parts['scheme'] . '://';
490 if (!empty($url_parts['user'])) {
491 $raw_url .= $url_parts['user'];
492 if (!empty($url_parts['pass'])) {
493 $raw_url .= ':' . $url_parts['pass'];
494 }
495 $raw_url .= '@';
496 }
497
498 $raw_url .= $url_parts['host'];
499
500 if (!empty($url_parts['port'])) {
501 $raw_url .= ':' . $url_parts['port'];
502 }
503
504 $raw_url .= !empty($url_parts['path']) ? $url_parts['path'] : '/';
505 $raw_url .= '?raw=True';
506
507 if (!empty($url_parts['fragment'])) {
508 $raw_url .= '#' . $url_parts['fragment'];
509 }
510
511 return $raw_url;
512 }
513
514 /**
515 * Fetch a document HTML and return sanitized inline markup.
516 *
517 * @param string $url
518 * @param bool $force_refresh
519 * @return string|false
520 */
521 function lawwwing_get_inline_document_content($url, $force_refresh = false) {
522 if (empty($url)) {
523 return false;
524 }
525
526 $raw_url = lawwwing_get_inline_raw_url($url);
527 if (!$raw_url) {
528 return false;
529 }
530
531 $cache_key = 'lawwwing_doc_html_' . md5($raw_url);
532 $cached = get_transient($cache_key);
533
534 if (!$force_refresh && is_string($cached) && $cached !== '') {
535 return $cached;
536 }
537
538 $response = wp_remote_get(
539 $raw_url,
540 array(
541 'timeout' => 12,
542 'headers' => array(
543 'Accept' => 'text/html,application/xhtml+xml',
544 ),
545 )
546 );
547
548 if (is_wp_error($response)) {
549 return false;
550 }
551
552 if ((int) wp_remote_retrieve_response_code($response) !== 200) {
553 return false;
554 }
555
556 $body = wp_remote_retrieve_body($response);
557 if (!is_string($body) || $body === '') {
558 return false;
559 }
560
561 $inline_html = trim(lawwwing_extract_html_body($body));
562
563 $allowed_tags = wp_kses_allowed_html('post');
564 $allowed_tags['style'] = array();
565 $allowed_tags['link'] = array(
566 'rel' => true,
567 'href' => true,
568 'type' => true,
569 'media' => true,
570 );
571 $allowed_tags['meta'] = array(
572 'charset' => true,
573 'name' => true,
574 'content' => true,
575 'http-equiv' => true,
576 );
577
578 $inline_html = wp_kses($inline_html, $allowed_tags);
579
580 if ($inline_html === '') {
581 $inline_html = lawwwing_format_inline_text_content(wp_strip_all_tags($body));
582 }
583
584 if ($inline_html === '') {
585 return false;
586 }
587
588 set_transient($cache_key, $inline_html, 30 * MINUTE_IN_SECONDS);
589
590 return $inline_html;
591 }
592
593 /**
594 * Generic shortcode handler.
595 *
596 * Usage:
597 * [lawwwing_document type="privacy" mode="embed"]
598 * [lawwwing_document type="cookies" mode="link" label="Ver politica"]
599 *
600 * @param array $atts
601 * @return string
602 */
603 function lawwwing_document_shortcode($atts) {
604 $atts = shortcode_atts(
605 array(
606 'type' => 'privacy',
607 'mode' => 'embed', // embed | link | inline
608 'label' => '',
609 'class' => '',
610 'target' => '_blank',
611 'height' => '1200',
612 'title' => '',
613 'debug' => '0',
614 ),
615 $atts,
616 'lawwwing_document'
617 );
618
619 $type = sanitize_key($atts['type']);
620 $mode = sanitize_key($atts['mode']);
621 $debug_enabled = ($atts['debug'] === '1' || $atts['debug'] === 'true');
622 $documents = lawwwing_get_documents_map();
623
624 if (!isset($documents[$type])) {
625 if (WP_DEBUG) {
626 return '<!-- Lawwwing: Document type "' . esc_attr($type) . '" not supported. Available: ' . implode(', ', array_keys($documents)) . ' -->';
627 }
628 return '';
629 }
630
631 $url = lawwwing_get_document_url_by_type($type);
632
633 if (!$url) {
634 if ($debug_enabled && current_user_can('manage_options')) {
635 $debug = lawwwing_get_shortcode_debug_info($type);
636 $message = 'Lawwwing shortcode debug - type: ' . esc_html($type);
637 $message .= ' | plugin_id: ' . esc_html($debug['plugin_id'] ? $debug['plugin_id'] : 'NOT SET');
638 $message .= ' | endpoint: ' . esc_html($debug['endpoint'] ? $debug['endpoint'] : 'INVALID');
639 $message .= ' | site_language: ' . esc_html($debug['site_language']);
640 $message .= ' | available: ' . esc_html(empty($debug['documents_available']) ? 'NONE' : implode(', ', $debug['documents_available']));
641
642 return '<div class="lawwwing-shortcode-debug" style="border:1px solid #f1c40f;background:#fff9db;padding:10px;margin:10px 0;font-size:12px;">' . $message . '</div>';
643 }
644
645 if (WP_DEBUG) {
646 $debug = lawwwing_get_shortcode_debug_info($type);
647 $debug_msg = 'Plugin ID: ' . ($debug['plugin_id'] ?: 'NOT SET') . '; ';
648 $debug_msg .= 'Available: ' . (empty($debug['documents_available']) ? 'NONE' : implode(', ', $debug['documents_available'])) . '; ';
649 $debug_msg .= 'Site Language: ' . $debug['site_language'];
650 return '<!-- Lawwwing: No URL found for type "' . esc_attr($type) . '". ' . esc_attr($debug_msg) . ' -->';
651 }
652 return '';
653 }
654
655 $label = !empty($atts['label']) ? sanitize_text_field($atts['label']) : $documents[$type]['label'];
656 $title = !empty($atts['title']) ? sanitize_text_field($atts['title']) : $label;
657 $css_class = 'lawwwing-document-link';
658 if (!empty($atts['class'])) {
659 $raw_classes = preg_split('/\s+/', $atts['class']);
660 $safe_classes = array();
661
662 foreach ($raw_classes as $raw_class) {
663 $safe_class = sanitize_html_class($raw_class);
664 if (!empty($safe_class)) {
665 $safe_classes[] = $safe_class;
666 }
667 }
668
669 if (!empty($safe_classes)) {
670 $css_class = implode(' ', $safe_classes);
671 }
672 }
673 $height = absint($atts['height']);
674 $height = $height > 0 ? $height : 1200;
675
676 if ($mode === 'link') {
677 return sprintf(
678 '<a href="%1$s" class="%2$s" target="%3$s" rel="noopener noreferrer">%4$s</a>',
679 esc_url($url),
680 esc_attr($css_class),
681 esc_attr($atts['target']),
682 esc_html($label)
683 );
684 }
685
686 if ($mode === 'inline') {
687 $inline_html = lawwwing_get_inline_document_content($url);
688
689 if (!$inline_html) {
690 $inline_html = lawwwing_get_inline_document_content($url, true);
691 }
692
693 if (!$inline_html) {
694 if ($debug_enabled && current_user_can('manage_options')) {
695 $raw_url = lawwwing_get_inline_raw_url($url);
696 return '<div class="lawwwing-shortcode-debug" style="border:1px solid #e67e22;background:#fef5e7;padding:10px;margin:10px 0;font-size:12px;">Lawwwing inline render failed for ' . esc_html($type) . '. raw_url: ' . esc_html($raw_url ? $raw_url : 'INVALID') . '</div>';
697 }
698
699 return '';
700 }
701
702 return sprintf(
703 '<div class="lawwwing-document-inline %1$s" data-lawwwing-document="%2$s">%3$s</div>',
704 esc_attr($css_class),
705 esc_attr($type),
706 $inline_html
707 );
708 }
709
710 return sprintf(
711 '<iframe src="%1$s" class="lawwwing-document-embed %2$s" title="%3$s" loading="lazy" style="width:100%%;height:%4$dpx;border:0;"></iframe>',
712 esc_url($url),
713 esc_attr($css_class),
714 esc_attr($title),
715 $height
716 );
717 }
718 add_shortcode('lawwwing_document', 'lawwwing_document_shortcode');
719
720 /**
721 * Registers explicit shortcodes for each legal document.
722 */
723 function lawwwing_register_documents_shortcodes() {
724 $documents = lawwwing_get_documents_map();
725
726 foreach (array_keys($documents) as $type) {
727 add_shortcode('lawwwing_' . $type, 'lawwwing_document_type_shortcode');
728 }
729 }
730 add_action('init', 'lawwwing_register_documents_shortcodes');
731
732 /**
733 * Wrapper for document-specific shortcodes.
734 *
735 * @param array $atts Shortcode attributes.
736 * @param string $content Content enclosed by shortcode.
737 * @param string $tag Shortcode tag name.
738 * @return string
739 */
740 function lawwwing_document_type_shortcode($atts, $content = '', $tag = '') {
741 if (strpos($tag, 'lawwwing_') !== 0) {
742 return '';
743 }
744
745 $type = str_replace('lawwwing_', '', $tag);
746 $atts['type'] = $type;
747
748 return lawwwing_document_shortcode($atts);
749 }
750