PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.3.0
Kirki – Freeform Page Builder, Website Builder & Customizer v6.3.0
6.3.0 6.2.5 6.2.4 6.2.3 6.2.2 6.2.1 6.2.0 6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / includes / HelperFunctions.php
kirki / includes Last commit date
API 4 days ago Admin 2 months ago Ajax 4 days ago ExportImport 2 weeks ago FormValidator 4 months ago Frontend 4 days ago Manager 2 weeks ago API.php 1 month ago Admin.php 4 months ago Ajax.php 4 days ago Apps.php 2 months ago ContentManager.php 4 months ago DbQueryUtils.php 3 months ago ElementVisibilityConditions.php 4 months ago Frontend.php 4 months ago HelperFunctions.php 4 days ago KirkiBase.php 2 months ago PostsQueryUtils.php 4 months ago Staging.php 1 month ago View.php 2 months ago
HelperFunctions.php
4925 lines
1 <?php
2 /**
3 * Helper class for kirki project
4 *
5 * @package kirki
6 */
7
8 namespace Kirki;
9
10 if (!defined('ABSPATH')) {
11 exit; // Exit if accessed directly.
12 }
13
14 use DateTime;
15 use Kirki\Ajax\Collaboration\Collaboration;
16 use Kirki\Ajax\Page;
17 use Kirki\Ajax\RBAC;
18 use Kirki\Staging;
19 use Kirki\Ajax\Symbol;
20 use Kirki\Ajax\UserData;
21 use Kirki\Ajax\Users;
22 use Kirki\Ajax\WpAdmin;
23 use Kirki\API\ContentManager\ContentManagerHelper;
24 use Kirki\App\Supports\Facades\Page as FacadesPage;
25 use Kirki\App\Supports\FileHandler;
26 use Kirki\Frontend\Preview\DataHelper;
27 use Kirki\Frontend\Preview\Preview;
28 use WP_Post;
29 use WP_Query;
30 use WP_Term;
31 use WP_User;
32
33 /**
34 * HelperFunctions Class
35 */
36 class HelperFunctions
37 {
38
39 public static $custom_sections = [];
40 public static $global_session_id = false;
41
42 private static $posts_where_filter_params = array();
43 /**
44 * Load assets for Editor
45 *
46 * @param string $for TheFrontend | null.
47 * @return false || array
48 */
49 public static function is_kirki_type_data($post_id = false, $staging_version = false)
50 {
51 if (!$post_id) {
52 $post_id = self::get_post_id_if_possible_from_url();
53 if (!$post_id)
54 return false;
55 }
56
57 if (!self::is_editor_mode_is_kirki($post_id)) {
58 return false;
59 }
60 if ($staging_version) {
61 return Staging::get_page_staging_data($post_id, $staging_version);
62 }
63
64 $kirki_data = get_post_meta($post_id, 'kirki', true);
65 if (!$kirki_data) {
66 $kirki_data = array();
67 $kirki_data['blocks'] = null;
68 }
69
70 $styles = self::get_page_styleblocks($post_id, $staging_version);
71
72 $kirki_data['styles'] = isset($styles) ? $styles : '';
73
74 return $kirki_data;
75 }
76
77 /**
78 * @deprecated
79 * @see \Kirki\App\Utils\PostUtil::get_post_id_from_url()
80 */
81 public static function get_post_id_if_possible_from_url()
82 {
83 if (isset($GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_child_post'])) {
84 return $GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_child_post'];
85 } else if (isset($GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_parent_post']) && false) {//disable content manager archive page logic
86 return $GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_parent_post'];
87 }
88
89 $post_id = get_the_ID();
90 $post_id = self::sanitize_text(isset($_GET['post_id']) ? $_GET['post_id'] : $post_id);
91 $post_id = self::sanitize_text(isset($_POST['post_id']) ? $_POST['post_id'] : $post_id);
92 $post_id = self::sanitize_text(isset($_GET['p']) ? $_GET['p'] : $post_id);
93
94 return (int) $post_id;
95 }
96
97 /**
98 * Get author/user id if possible from url
99 */
100 public static function get_user_id_if_possible_from_url()
101 {
102 if (isset($GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_user'])) {
103 return $GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_user'];
104 }
105
106 $user_id = '';
107 if (isset($GLOBALS['wp']->query_vars['author_name'])) {
108 $user = get_user_by('slug', $GLOBALS['wp']->query_vars['author_name']);
109 if ($user instanceof WP_User) {
110 $user_id = $user->ID;
111 }
112 } else {
113 $user_id = get_current_user_id();
114 }
115
116 return (int) $user_id;
117 }
118
119
120 /**
121 * Get all user roles by access levels
122 * @param array $access_levels access levels => array(KIRKI_ACCESS_LEVELS['FULL_ACCESS'], KIRKI_ACCESS_LEVELS['CONTENT_ACCESS']);
123 *
124 * @return array roles => array ('editor', 'author');
125 */
126 public static function get_all_user_roles_by_access_levels($access_levels = array())
127 {
128 $all_roles = RBAC::get_all_roles();
129 $roles_with_access = RBAC::get_access_level($all_roles);
130
131 $filtered_roles = [];
132
133 $filtered_roles = array_filter($roles_with_access, function ($level) use ($access_levels) {
134 return in_array($level, $access_levels);
135 });
136
137 return array_keys($filtered_roles);
138 }
139
140 /**
141 * Get term or tag id if possible from url
142 */
143 public static function get_term_id_if_possible_from_url()
144 {
145 if (isset($GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_term'])) {
146 return $GLOBALS['wp']->query_vars[KIRKI_CONTENT_MANAGER_PREFIX . '_term'];
147 }
148
149 // http://kirki.test/tag/wp-tag-2/
150 $term_id = get_queried_object_id();
151 $term = null;
152
153 if ($term_id) {
154 $term = get_term($term_id);
155 } else if (isset($GLOBALS['wp']->query_vars['tag'])) {
156 $term = get_term_by('slug', $GLOBALS['wp']->query_vars['tag'], 'post_tag');
157 }
158
159 if ($term instanceof WP_Term) {
160 $term_id = $term->term_id;
161 }
162
163 if (!$term_id) {
164 // get all terms and return the first one
165 $term = get_terms(array(
166 'taxonomy' => 'category',
167 'hide_empty' => false,
168 'number' => 1,
169 ));
170
171 $term_id = isset($term[0], $term[0]->term_id) ? $term[0]->term_id : 1;
172 }
173
174 return (int) $term_id;
175 }
176
177 /**
178 * @deprecated
179 * @see Kirki\App\Services\PageService::save_page_data()
180 * @see \Kirki\App\Services\GlobalDataService::save_styles()
181 */
182 public static function save_kirki_data_to_db($post_id, $page_data, $is_staging = false)
183 {
184 $version_where_saved = false;
185 if ($is_staging) {
186 $data = Staging::save_page_staging_data_to_db($post_id, $page_data);
187 $page_data = $data['page_data'];
188 $version_where_saved = $data['version'];
189 }
190
191 if (isset($page_data['styles'])) {
192 $global_style_blocks_for_collaboration = [];
193 foreach ($page_data['styles'] as $key => $sb) {
194 if ((isset($sb['isDefault']) && $sb['isDefault'] === true) || (isset($sb['isGlobal']) && $sb['isGlobal'] === true)) {
195 if (isset($sb['fromStage'])) {
196 unset($page_data['styles'][$key]['fromStage']);
197 $global_style_blocks_for_collaboration[$key] = $page_data['styles'][$key];
198 } else
199 unset($page_data['styles'][$key]);
200 }
201 }
202 if (count($global_style_blocks_for_collaboration) > 0) {
203 $session_id = self::sanitize_text(isset($_REQUEST['session_id']) ? $_REQUEST['session_id'] : '');
204 if ($session_id && $is_staging === false) {
205 // cause template import also called this method without session_id
206 $data = array(
207 'type' => 'COLLABORATION_UPDATE_GLOBAL_STYLE',
208 'payload' => array('styleBlock' => $global_style_blocks_for_collaboration),
209 );
210 Collaboration::save_action_to_db('global', 0, $data, 1, $session_id);
211 }
212 }
213
214 self::update_page_styleblocks($post_id, $page_data['styles']);
215 unset($page_data['styles']);
216 }
217
218 if (isset($page_data['usedStyles'])) {
219 update_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS, $page_data['usedStyles']);
220 unset($page_data['usedStyles']);
221 }
222
223 if (isset($page_data['usedStyleIdsRandom'])) {
224 update_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS . '_random', $page_data['usedStyleIdsRandom']);
225 unset($page_data['usedStyleIdsRandom']);
226 }
227
228 if (isset($page_data['usedFonts'])) {
229 update_post_meta($post_id, KIRKI_META_NAME_FOR_USED_FONT_LIST, $page_data['usedFonts']);
230 unset($page_data['usedFonts']);
231 }
232
233 if (isset($page_data['customFonts'])) {
234 //save others data if isset. this is for template import
235 $custom_fonts = self::get_global_data_using_key(KIRKI_USER_CUSTOM_FONTS_META_KEY);
236 foreach ($page_data['customFonts'] as $key => $cf) {
237 $custom_fonts[$key] = $cf;
238 }
239 self::update_global_data_using_key(KIRKI_USER_CUSTOM_FONTS_META_KEY, $custom_fonts);
240 unset($page_data['customFonts']);
241 }
242
243 if (isset($page_data['viewportList'])) {
244 //save others data if isset. this is for template import
245 $controller_data = self::get_global_data_using_key(KIRKI_USER_CONTROLLER_META_KEY);
246 if (!$controller_data) {
247 $init = array(
248 'active' => 'md',
249 'defaults' => ["md", "tablet", "mobileLandscape", "mobile"],
250 'list' => $page_data['viewportList'],
251 'mdWidth' => 1200,
252 "scale" => 1,
253 "width" => 2484,
254 "zoom" => 1
255 );
256 $controller_data = array('viewport' => $init);
257 } else if (isset($controller_data['viewport'], $controller_data['viewport']['list'])) {
258 $controller_data['viewport']['list'] = $page_data['viewportList'];
259 }
260
261 HelperFunctions::update_global_data_using_key(KIRKI_USER_CONTROLLER_META_KEY, $controller_data);
262 unset($page_data['viewportList']);
263 }
264
265 if (isset($page_data['blocks'])) {
266 update_post_meta($post_id, 'kirki', array('blocks' => $page_data['blocks']));
267 // $data = array(
268 // 'type' => 'COLLABORATION_PAGE_DATA',
269 // 'payload' => array( 'data' => $page_data['blocks'] ),
270 // );
271 //Collaboration::save_action_to_db( 'post', $post_id, $data, 1 );
272 }
273
274
275
276 update_post_meta($post_id, KIRKI_META_NAME_FOR_POST_EDITOR_MODE, 'kirki');
277 return $version_where_saved;
278 }
279
280 /**
281 *
282 */
283 function abc()
284 {
285
286 }
287
288 /**
289 * This function will return page style blocks from option meta and post meta
290 * post meta for migration and option meta for global style block
291 *
292 * @param int $post_id post id.
293 * @return object
294 *
295 * @todo:
296 *
297 * @deprecated
298 * @see Kirki\App\Managers\PageManager::get_page_styleblocks()
299 */
300 public static function get_page_styleblocks($post_id, $stage_version = false)
301 {
302 return FacadesPage::get_page_styleblocks($post_id, $stage_version);
303
304 // $random_style_blocks = get_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY . '_random', true);
305 // $global_style_blocks = self::get_global_data_using_key(KIRKI_GLOBAL_STYLE_BLOCK_META_KEY);
306
307 // $random_style_blocks = self::fix_duplicate_class_name_from_random_sbs($random_style_blocks, $global_style_blocks);
308
309 // $merged_style_blocks = array();
310 // if ($random_style_blocks) {
311 // $merged_style_blocks = array_merge($merged_style_blocks, $random_style_blocks);
312 // }
313 // if ($global_style_blocks) {
314 // $merged_style_blocks = array_merge($merged_style_blocks, $global_style_blocks);
315 // }
316
317 // $published_version = Staging::get_published_stage_version($post_id);
318 // if ($published_version && $stage_version !== $published_version) {
319 // $staging_style_blocks = array();
320 // $meta_key = Staging::get_staged_meta_name(KIRKI_GLOBAL_STYLE_BLOCK_META_KEY, $post_id, $stage_version);
321 // $stage_style = get_post_meta($post_id, $meta_key, true);
322 // if ($stage_style)
323 // $staging_style_blocks = array_merge($staging_style_blocks, $stage_style);
324
325 // $meta_key = $meta_key . '_random';
326 // $stage_style = get_post_meta($post_id, $meta_key, true);
327 // if ($stage_style)
328 // $staging_style_blocks = array_merge($staging_style_blocks, $stage_style);
329 // if ($stage_version)
330 // $merged_style_blocks = self::merge_style_blocks($merged_style_blocks, $staging_style_blocks);
331 // else
332 // $merged_style_blocks = self::merge_style_blocks($staging_style_blocks, $merged_style_blocks);
333 // }
334
335 // return $merged_style_blocks;
336 }
337
338 /**
339 * @deprecated
340 * @see Kirki\App\Managers\PageManager::merge_style_blocks()
341 */
342 public static function merge_style_blocks($a, $b)
343 {
344 $names_in_a = [];
345 foreach ($a as $val) {
346 if (!empty($val['name']) && is_string($val['name'])) {
347 $names_in_a[strtolower($val['name'])] = true;
348 }
349 }
350
351 $names_in_b = [];
352 foreach ($b as $val) {
353 if (!empty($val['name']) && is_string($val['name'])) {
354 $names_in_b[strtolower($val['name'])] = true;
355 }
356 }
357
358 foreach ($b as $id_b => &$value_b) {
359 if (empty($value_b['name']) || !is_string($value_b['name'])) {
360 continue;
361 }
362
363 $b_name = strtolower($value_b['name']);
364
365 // If same ID exists in A, remove it first (old behavior)
366 if (isset($a[$id_b])) {
367 unset($a[$id_b]);
368 if (isset($names_in_a[$b_name])) {
369 unset($names_in_a[$b_name]);
370 }
371 }
372
373 // If name already exists in A, make it unique
374 if (isset($names_in_a[$b_name])) {
375 $i = 1;
376 while (isset($names_in_a[$b_name . '_' . $i]) || isset($names_in_b[$b_name . '_' . $i])) {
377 $i++;
378 }
379 $new_name = $value_b['name'] . '_' . $i;
380
381 foreach ($b as &$v) {
382 if (isset($v['name']) && is_array($v['name'])) {
383 $v['name'] = array_map(fn($item) => $item === $value_b['name'] ? $new_name : $item, $v['name']);
384 }
385 }
386
387 $value_b['name'] = $new_name;
388 unset($names_in_b[$b_name]);
389 $names_in_b[strtolower($new_name)] = true;
390 }
391 }
392 unset($value_b);
393
394 // Use array_merge to keep old semantics
395 return array_merge($a, $b);
396 }
397
398
399 /**
400 * @deprecated
401 * @see Kirki\App\Managers\PageManager::resolve_duplicate_current_style_block_names()
402 */
403 private static function fix_duplicate_class_name_from_random_sbs($random_style_blocks, $global_style_blocks)
404 {
405 $global_class_names = [];
406 $random_class_names = [];
407 if ($global_style_blocks) {
408 foreach ($global_style_blocks as $key => $value) {
409 if (isset($value['name']) && is_string($value['name'])) {
410 $global_class_names[self::get_class_name_from_string($value['name'])] = true;
411 }
412 }
413 }
414 if ($random_style_blocks) {
415 foreach ($random_style_blocks as $key => $value) {
416 if (isset($value['name']) && is_string($value['name'])) {
417 $random_class_names[self::get_class_name_from_string($value['name'])] = true;
418 }
419 }
420 }
421
422 $class_match = [];
423 foreach ($random_class_names as $key => $value) {
424 if (isset($global_class_names[$key])) {
425 $class_match[$key] = true;
426 }
427 }
428
429 $class_match = self::check_or_generate_new_class_names($class_match, $global_class_names, $random_class_names);
430
431 if (count($class_match) > 0) {
432 foreach ($random_style_blocks as $key => $value) {
433 if (isset($value['name']) && is_string($value['name'])) {
434 $class_name = self::get_class_name_from_string($value['name']);
435 if (isset($class_match[$class_name])) {
436 $random_style_blocks[$key]['name'] = $class_match[$class_name];
437 }
438 } else if (isset($value['name']) && is_array($value['name'])) {
439 //array
440 foreach ($value['name'] as $key2 => $v2) {
441 $class_name = self::get_class_name_from_string($v2);
442 if (isset($class_match[$class_name])) {
443 $random_style_blocks[$key]['name'][$key2] = $class_match[$class_name];
444 }
445 }
446 }
447 }
448 }
449 return $random_style_blocks;
450 }
451
452 /**
453 * @deprecated
454 * @see Kirki\App\Managers\PageManager::make_duplicate_classes_to_unique()
455 */
456 private static function check_or_generate_new_class_names($class_match, $global_class_names, $random_class_names)
457 {
458 foreach ($class_match as $key => $value) {
459 $temp_class = $key;
460 $found = true;
461 while ($found) {
462 if (isset($global_class_names[$temp_class]) || isset($random_class_names[$temp_class])) {
463 $temp_class = $temp_class . '-copy';
464 } else {
465 $found = false;
466 }
467 }
468 $class_match[$key] = $temp_class;
469 }
470 return $class_match;
471 }
472
473 /**
474 * @deprecated
475 * @see Kirki\App\Managers\PageManager::normalize_style_block_name()
476 */
477 public static function get_class_name_from_string($s)
478 {
479 $s = strtolower(str_replace(' ', '-', $s));
480 return $s;
481 }
482
483 public static function get_selector_from_sb_name($name)
484 {
485 if (!$name)
486 return '';
487 $class_name = '';
488 if (is_string($name)) {
489 $class_name = '.' . self::get_class_name_from_string($name);
490 } else {
491 foreach ($name as $key2 => $cn) {
492 $class_name .= '.' . self::get_class_name_from_string($cn);
493 }
494 }
495 return $class_name;
496 }
497
498 /**
499 * This function will update page style blocks into option meta and post meta
500 * post meta for migration and option meta for global style block
501 *
502 * @param int $post_id post id.
503 * @param object $style_blocks styleblocks.
504 *
505 * @deprecated the method is not used anymore. handle separately in GlobalDataManager and PageManager
506 */
507 public static function update_page_styleblocks($post_id, $style_blocks)
508 {
509 $prev_style_blocks = self::get_page_styleblocks($post_id);
510 $style_blocks = array_merge($prev_style_blocks, $style_blocks);
511
512 $global_style_blocks = array();
513 foreach ($style_blocks as $key => $sb) {
514 if ((isset($sb['isDefault']) && $sb['isDefault'] === true) || (isset($sb['isGlobal']) && $sb['isGlobal'] === true)) {
515 $global_style_blocks[$sb['id']] = $sb;
516 unset($style_blocks[$key]);
517 }
518 }
519
520 self::save_global_style_blocks($global_style_blocks);
521 self::save_random_style_blocks($post_id, $style_blocks);
522 }
523
524 /**
525 * Save global style blocks in option table. Also save collaboration data.
526 *
527 * @param array $style //take styleblocs if isDefault and isGlobal key is true.
528 * @return void
529 * @deprecated
530 * @see Kirki\App\Managers\GlobalDataManager::update_deprecated_global_style_blocks()
531 */
532 public static function save_global_style_blocks($style)
533 {
534 // $session_id = self::sanitize_text(isset($_REQUEST['session_id']) ? $_REQUEST['session_id'] : '');
535 self::update_global_data_using_key(KIRKI_GLOBAL_STYLE_BLOCK_META_KEY, $style);
536
537 // if($session_id){
538 // // cause template import also called this method without session_id
539 // $data = array(
540 // 'type' => 'COLLABORATION_UPDATE_GLOBAL_STYLE',
541 // 'payload' => array( 'styleBlock' => $style ),
542 // );
543 // Collaboration::save_action_to_db( 'global', 0, $data, 1, $session_id);
544 // }
545 }
546
547 /**
548 * Save post related random style blocks in option table. Also save collaboration data.
549 *
550 * @param array $post_id //current post id.
551 * @param array $style //take styleblocs if not isDefault and isGlobal key is true.
552 * @return void
553 * @deprecated
554 * @see Kirki\App\Managers\PageManager::save_style_blocks()
555 */
556 public static function save_random_style_blocks($post_id, $style)
557 {
558 update_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY . '_random', $style);
559 // $data = array(
560 // 'type' => 'COLLABORATION_UPDATE_GLOBAL_STYLE',
561 // 'payload' => array( 'styleBlock' => $style ),
562 // );
563 //Collaboration::save_action_to_db( 'post', $post_id, $data, 1 );
564 }
565
566 /**
567 * Save staged style blocks for a specific post and staged meta key.
568 *
569 * @param int $post_id Post ID.
570 * @param string $meta_key Staged meta key (can be normal or random).
571 * @param array $styles Styles array to save.
572 *
573 * @deprecated
574 * @see \Kirki\App\Managers\PageManager::save_deprecated_global_style_blocks()
575 * @see \Kirki\App\Managers\PageManager::save_style_blocks()
576 */
577 public static function save_staged_style_blocks($post_id, $meta_key, $styles)
578 {
579 update_post_meta($post_id, $meta_key, $styles);
580 // $data = array(
581 // 'type' => 'COLLABORATION_UPDATE_GLOBAL_STYLE',
582 // 'payload' => array('styleBlock' => $styles),
583 // );
584 // Collaboration::save_action_to_db('post', $post_id, $data, 1);
585 }
586
587 /**
588 * No module import this method right now
589 *
590 * @return array
591 */
592 public static function get_custom_code_block_element()
593 {
594 return array(
595 'name' => 'custom-code',
596 'title' => 'Code',
597 'visibility' => true,
598 'properties' => array(
599 'tag' => 'div',
600 'content' => '',
601 'data-type' => 'code',
602 ),
603 'styleIds' => array(),
604 'className' => '',
605 'id' => 'kirki' . uniqid(),
606 'parentId' => 'body',
607 );
608 }
609 /**
610 * Get current editor mode is kirki or others
611 *
612 * @param int $post_id post id.
613 * @return bool true if kirki.
614 * @deprecated
615 * @see Kirki\App\Managers\PageManager::is_kirki_editor_mode()
616 */
617 public static function is_editor_mode_is_kirki($post_id)
618 {
619 $editor_mode = get_post_meta($post_id, KIRKI_META_NAME_FOR_POST_EDITOR_MODE, true);
620 if ('kirki' === $editor_mode) {
621 return true;
622 }
623 return false;
624 }
625
626 /**
627 * Get post url arr from post id
628 * preview_url, iframe_url, post_url
629 *
630 * @param int $post_id post id.
631 * @return array
632 *
633 * @deprecated
634 * @see \Kirki\App\Supports\PageUrl::class
635 */
636 public static function get_post_url_arr_from_post_id($post_id, $options = array())
637 {
638 $post_perma_link = self::get_page_perma_url($post_id);
639
640 $obj = ['post_url' => $post_perma_link, 'post_id' => $post_id];
641 if (isset($options['ajax_url']) && $options['ajax_url']) {
642 $protocol = strpos(home_url(), 'https://') !== false ? 'https' : 'http';
643 $obj['ajax_url'] = admin_url('admin-ajax.php', $protocol);
644 }
645
646 if (isset($options['preview_url']) && $options['preview_url']) {
647 $preview_url = self::get_page_preview_url($post_id);
648 $obj['preview_url'] = $preview_url;
649 }
650
651 if (isset($options['editor_url']) && $options['editor_url']) {
652 $obj['editor_url'] = add_query_arg(
653 array(
654 'action' => KIRKI_EDITOR_ACTION,
655 ),
656 $post_perma_link
657 );
658
659 if (HelperFunctions::is_api_call_from_editor_preview() && HelperFunctions::is_api_header_post_editor_preview_token_valid()) {
660 $headers = self::getallheaders();
661 $obj['editor_url'] = add_query_arg(
662 array(
663 'editor-preview-token' => isset($headers['Editor-Preview-Token']) ? $headers['Editor-Preview-Token'] : null,
664 ),
665 $obj['editor_url']
666 );
667 }
668 }
669 if (isset($options['iframe_url']) && $options['iframe_url']) {
670 $iframe_url_params = array(
671 'action' => KIRKI_EDITOR_ACTION,
672 'load_for' => 'kirki-iframe',
673 'post_id' => $post_id,
674 );
675 if (isset($_GET['editor-preview-token'])) {
676 $iframe_url_params['editor-preview-token'] = self::sanitize_text($_GET['editor-preview-token']);
677 }
678 $obj['iframe_url'] = add_query_arg(
679 $iframe_url_params,
680 $post_perma_link
681 );
682 }
683
684 if (isset($options['nonce']) && $options['nonce']) {
685 $obj['nonce'] = wp_create_nonce('wp_rest');
686 }
687
688 if (isset($options['editor_preview_token']) && $options['editor_preview_token']) {
689 $obj['editor_preview_token'] = isset($_GET['editor-preview-token']) ? self::sanitize_text($_GET['editor-preview-token']) : false;
690 }
691
692 if (isset($options['site_url']) && $options['site_url']) {
693 $obj['site_url'] = get_site_url();
694 }
695
696 if (isset($options['admin_url']) && $options['admin_url']) {
697 $obj['admin_url'] = get_admin_url();
698 }
699
700 if (isset($options['core_plugin_url']) && $options['core_plugin_url']) {
701 $obj['core_plugin_url'] = KIRKI_CORE_PLUGIN_URL;
702 }
703
704 if (isset($options['rest_url']) && $options['rest_url']) {
705 try {
706 $rest_url = rest_url();
707 $obj['rest_url'] = $rest_url;
708 } catch (\Throwable $th) {
709 $obj['rest_url'] = '';
710 }
711 }
712
713 return $obj;
714 }
715
716 /**
717 * @deprecated
718 * @see \Kirki\App\Supports\PageUrl::get_preview_url()
719 */
720 private static function get_page_preview_url($post_id)
721 {
722 $post = get_post($post_id);
723 if ($post && $post->post_type === 'kirki_template') {
724 $conditions = get_post_meta($post->ID, 'kirki_template_conditions', true);
725
726 $d = self::get_collection_items_from_conditions($conditions);
727 if ($d['type'] === 'post' && count($d['data']) > 0) {
728 return self::get_page_perma_url($d['data'][0]['ID']);
729 }
730 if ($d['type'] === 'user' && count($d['data']) > 0) {
731 return get_author_posts_url($d['data'][0]['ID']);
732 }
733 if ($d['type'] === 'term' && count($d['data']) > 0) {
734 return get_term_link($d['data'][0]['ID']);
735 }
736 }
737 return self::get_page_perma_url($post_id);
738 }
739
740 /**
741 * @deprecated
742 * @see \Kirki\App\Supports\PageUrl::get_page_permalink()
743 */
744 private static function get_page_perma_url($post_id)
745 {
746 $post_perma_link = get_permalink($post_id);
747 $protocol = strpos(home_url(), 'https://') !== false ? 'https' : 'http';
748 if ($protocol === 'https') {
749 $post_perma_link = str_replace('http://', 'https://', $post_perma_link);
750 } else {
751 $post_perma_link = str_replace('https://', 'http://', $post_perma_link);
752 }
753
754 return $post_perma_link;
755 }
756
757 /**
758 * @deprecated
759 * @see \Kirki\App\Supports\CollectionItem::get_items_from_condition()
760 */
761 public static function get_collection_items_from_conditions($conditions, $query = '')
762 {
763 $data = [];
764 $type = 'post';
765 $post_type = 'post';
766 $role = '';
767
768 if (is_array($conditions) && count($conditions) > 0) {
769 if (isset($conditions[0]['type'])) {
770 $type = $conditions[0]['type'];
771
772 if ($type === 'post') {
773 $post_type = $conditions[0]['post_type'];
774
775 // if the conditions has from key then it is term. then it will be term type.
776 if (isset($conditions[0]['from']) && $conditions[0]['from'] === 'term') {
777 $type = 'term';
778 }
779 }
780 if ($type === 'user') {
781 $role = $conditions[0]['to'];
782 }
783 } else {
784 //legacy support
785 $post_type = $conditions[0]['category'];
786 }
787 }
788
789
790 $numberposts = 20;
791 $post_status = array('publish', 'draft', 'future');
792
793 if ($type === 'post' && HelperFunctions::user_has_post_edit_access()) { // type will be wordpress post type
794 $arg = array(
795 'post_type' => $post_type,
796 'post_status' => $post_status,
797 'numberposts' => $numberposts,
798 'orderby' => 'ID',
799 'order' => 'DESC',
800 );
801 if ($query) {
802 $arg['s'] = $query;
803 }
804 $posts = get_posts($arg);
805 foreach ($posts as $key => $post) {
806 $data[] = array(
807 'ID' => $post->ID,
808 'title' => $post->post_title,
809 );
810 }
811 }
812
813 if ($type === 'user' && HelperFunctions::user_has_post_edit_access()) {
814 // get all users
815 $arg = array(
816 'role' => $role === '*' ? '' : $role,
817 'number' => $numberposts,
818 'orderby' => 'ID',
819 'order' => 'DESC',
820 );
821
822 if ($query) {
823 $arg['search'] = '*' . $query . '*';
824 }
825
826 $users = get_users($arg);
827 foreach ($users as $key => $user) {
828 $data[] = array(
829 'ID' => $user->ID,
830 'title' => $user->display_name,
831 );
832 }
833 }
834
835 if ($type === 'term' && HelperFunctions::user_has_post_edit_access()) {
836 // conditions
837 $taxonomy = [];
838
839 foreach ($conditions as $key => $condition) {
840 if ($condition['from'] === 'term') {
841 $taxonomy[] = $condition['where'];
842 }
843 }
844
845 $arg = array(
846 'taxonomy' => $taxonomy,
847 'number' => $numberposts,
848 'orderby' => 'ID',
849 'order' => 'DESC',
850 );
851
852 if ($query) {
853 $arg['search'] = $query;
854 }
855
856 $terms = get_terms($arg);
857
858 if (!is_wp_error($terms) && is_array($terms)) {
859 foreach ($terms as $term) {
860 // Handle both object and array cases safely
861 $term_id = is_object($term) ? $term->term_id : ($term['term_id'] ?? null);
862 $term_name = is_object($term) ? $term->name : ($term['name'] ?? null);
863
864 $data[] = [
865 'ID' => $term_id,
866 'title' => $term_name,
867 ];
868 }
869 }
870 }
871
872 return ['data' => $data, 'type' => $type];
873 }
874 /**
875 * Get post content from content value. like => id, title, description, content
876 *
877 * @param string $content_value for post dynamic content.
878 * @param object $post post object.
879 *
880 * @return string
881 */
882 public static function get_post_dynamic_content($content_value, $post = null, $meta_name = '', $dynamic_options = [])
883 {
884 if (isset($post) && !empty($post)) {
885 $post_id = $post->ID;
886 }
887
888 if (empty($post_id)) {
889 $post_id = self::get_post_id_if_possible_from_url();
890 }
891
892 $content = null;
893
894 switch ($content_value) {
895 case 'post_id': {
896 $content = $post_id;
897 break;
898 }
899
900 case 'post_title': {
901 $content = get_the_title($post_id);
902 break;
903 }
904
905 case 'post_excerpt': {
906
907 // Excerpt length global variable is only for kirki editor but not for the frontend.
908 if (isset($GLOBALS['kirki_post_excerpt_length']) && $GLOBALS['kirki_post_excerpt_length'] > 0) {
909 $content = wp_trim_words(get_the_excerpt($post_id), $GLOBALS['kirki_post_excerpt_length'], '[...]');
910 } else {
911 $content = get_the_excerpt($post_id);
912 }
913 break;
914 }
915
916 case 'post_author': {
917 $post = get_post($post_id);
918 if (!$post)
919 return "";
920 $content = get_the_author_meta('display_name', $post->post_author);
921 break;
922 }
923
924 case 'post_date': {
925 $content = get_the_date('', $post_id);
926 if (isset($dynamic_options['format'])) {
927 $content = HelperFunctions::format_date($content, $dynamic_options['format']);
928 }
929 break;
930 }
931
932 case 'post_time': {
933 $content = get_the_time('', $post_id);
934 if (isset($dynamic_options['timeFormat'])) {
935 $content = HelperFunctions::convert_time_format($content, $dynamic_options['timeFormat']);
936 }
937 break;
938 }
939
940 case 'post_content': {
941 $content = self::retrieve_post_content($post_id);
942 break;
943 }
944
945 case 'post_status': {
946 $content = get_post_status($post_id);
947 break;
948 }
949
950 case 'featured_image': {
951 $content = array(
952 'wp_attachment_id' => get_post_thumbnail_id($post_id),
953 'src' => get_the_post_thumbnail_url($post_id)
954 );
955 break;
956 }
957
958 case 'site_name': {
959 $content = get_bloginfo('name');
960 break;
961 }
962 case 'site_description': {
963 $content = get_bloginfo('description');
964 break;
965 }
966 case 'site_url': {
967 $content = get_site_url();
968 break;
969 }
970
971 case 'site_logo': {
972 $content = '';
973
974 // Try site icon first
975 $site_icon_id = get_option('site_icon');
976 if ($site_icon_id) {
977 $content = wp_get_attachment_url($site_icon_id);
978 }
979
980 // If no site icon, try get_site_icon_url()
981 if (!$content) {
982 $content = get_site_icon_url(512);
983 }
984
985 // If still nothing, try custom logo
986 if (!$content) {
987 $custom_logo_id = get_theme_mod('custom_logo');
988 if ($custom_logo_id) {
989 $imgs = wp_get_attachment_image_src($custom_logo_id, 'full');
990 if ($imgs && isset($imgs[0])) {
991 $content = $imgs[0];
992 }
993 }
994 }
995
996 break;
997 }
998
999 case 'author_profile_picture': {
1000 $post = get_post($post_id);
1001
1002 if (!empty($post)) {
1003 $post_author = $post->post_author;
1004 $content = get_avatar_url($post_author);
1005 } else {
1006 $content = 'Something wrong!';
1007 }
1008
1009 break;
1010 }
1011
1012 case 'user_profile_picture': {
1013 $content = get_avatar_url(get_current_user_id());
1014 break;
1015 }
1016
1017 case 'post_page_link': {
1018 $url = \get_permalink($post_id);
1019 $content = !empty($url) ? $url : '';
1020 break;
1021 }
1022
1023 case 'author_posts_page_link': {
1024 $post = \get_post($post_id);
1025 $url = \get_author_posts_url($post->post_author);
1026 $content = !empty($url) ? $url : '';
1027 break;
1028 }
1029
1030 case 'post_meta': {
1031 if (!empty($meta_name)) {
1032 $meta = get_post_meta($post_id, $meta_name, true);
1033
1034 // For now, only string is supported!
1035 if (is_string($meta)) {
1036 // Post meta is arbitrary, low-privilege-authored data: always escape.
1037 $content = self::escape_post_meta_value($meta);
1038 }
1039 }
1040
1041 break;
1042 }
1043
1044 default: {
1045 $post = get_post($post_id);
1046 if (isset($post) && 0 !== strpos(KIRKI_CONTENT_MANAGER_PREFIX, $post->post_type)) {
1047 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post->post_parent, $content_value);
1048 $content = get_post_meta($post->ID, $meta_key, true);
1049 $fields = ContentManagerHelper::get_post_type_custom_field_keys($post->post_parent);
1050
1051 if (!$content && isset($fields[$content_value], $fields[$content_value]['default_value'])) {
1052 $content = $fields[$content_value]['default_value'];
1053 }
1054
1055 if (isset($fields[$content_value]) && $fields[$content_value]['type'] === 'date') {
1056 $content = self::format_date($content, $fields[$content_value]['default_format']); //TODO: need to check with editor format
1057 }
1058 if (isset($fields[$content_value]['type']) && $fields[$content_value]['type'] === 'image') {
1059 $content = array(
1060 'wp_attachment_id' => $content['id'] ?? '',
1061 'src' => $content['url'] ?? '',
1062 );
1063 }
1064 if (isset($fields[$content_value]['type']) && $fields[$content_value]['type'] === 'time') {
1065 $time = "";
1066
1067 if (isset($fields[$content_value], $fields[$content_value]['default_value']) && $fields[$content_value]['default_value']) {
1068 $default_time = $fields[$content_value]['default_value'];
1069
1070 $value = isset($default_time['value']) ? $default_time['value'] : '00:00';
1071 $unit = isset($default_time['unit']) ? strtolower($default_time['unit']) : 'am';
1072
1073 $time = $value . ' ' . $unit;
1074 }
1075
1076 $content = $content ? $content['value'] . ' ' . $content['unit'] : $time;
1077
1078 if (isset($dynamic_options['timeFormat']) && $dynamic_options['timeFormat']) {
1079 $content = HelperFunctions::convert_time_format($content, $dynamic_options['timeFormat']);
1080 }
1081 }
1082
1083 } else {
1084 $content = 'Not Implemented';
1085 }
1086
1087 break;
1088 }
1089 }
1090
1091 return $content ? $content : '';
1092 }
1093
1094 /**
1095 * The function `get_user_dynamic_content` retrieves specific dynamic content related to a user based
1096 * on the provided content value.
1097 */
1098 public static function get_user_dynamic_content($content_value, $user_id = null, $meta_name = '', $dynamic_options = [])
1099 {
1100 $content = '';
1101
1102 // Get the user by ID
1103 $user = get_user_by('id', $user_id);
1104
1105 // fall back to the current user
1106 if (empty($user)) {
1107 $user = get_user_by('id', get_current_user_id());
1108 }
1109
1110
1111 if (empty($user)) {
1112 return $content;
1113 }
1114
1115 // Switch statement to handle dynamic content based on the content_value
1116 switch ($content_value) {
1117 case 'display_name':
1118 return $user->display_name;
1119
1120 case 'user_email':
1121 return $user->user_email;
1122
1123 case 'user_nicename':
1124 return $user->user_nicename;
1125
1126 case 'registered_date': {
1127 $date = $user->user_registered;
1128
1129 if (isset($dynamic_options['format'])) {
1130 return HelperFunctions::format_date($date, $dynamic_options['format']);
1131 }
1132
1133 $date = new DateTime($date);
1134
1135 return $date->format('F j, Y');
1136 }
1137
1138 case 'registered_time': {
1139 $date = new DateTime($user->user_registered);
1140 return $date->format('H:i:a');
1141 }
1142
1143 case 'user_url':
1144 return get_author_posts_url($user->ID);
1145
1146 case 'profile_image':
1147 return get_avatar_url($user->ID);
1148
1149 case 'user_meta': {
1150 if (!empty($meta_name)) {
1151 $meta = get_user_meta($user->ID, $meta_name, true);
1152
1153 if (is_string($meta)) {
1154 return $meta;
1155 }
1156 return '';
1157 }
1158 }
1159
1160 case 'initials': {
1161 $first_name = isset($user->first_name) ? $user->first_name : '';
1162 $last_name = isset($user->last_name) ? $user->last_name : '';
1163
1164 $initials = '';
1165 if ($first_name) {
1166 $initials .= strtoupper(substr($first_name, 0, 1));
1167 }
1168
1169 if ($last_name) {
1170 $initials .= strtoupper(substr($last_name, 0, 1));
1171 }
1172
1173 // if initials are lenght 1 or empty, then the first two letters of the username
1174 if (empty($initials) || strlen($initials) < 2) {
1175 $username = isset($user->user_login) ? $user->user_login : '';
1176 $initials = strtoupper(substr($username, 0, 2));
1177 }
1178
1179 return $initials;
1180 }
1181
1182 default:
1183 return '';
1184 }
1185 }
1186
1187 private static function get_value($data, $key)
1188 {
1189 if (is_array($data)) {
1190 return $data[$key] ?? '';
1191 }
1192
1193 if (is_object($data)) {
1194 return $data->$key ?? '';
1195 }
1196
1197 return '';
1198 }
1199
1200 public static function get_term_dynamic_content($content_value, $term_id = null, $meta_name = '')
1201 {
1202 $term = get_term($term_id);
1203
1204 if (empty($term)) {
1205 return '';
1206 }
1207
1208 switch ($content_value) {
1209 case 'name':
1210 return self::get_value($term, 'name');
1211
1212 case 'description':
1213 return self::get_value($term, 'description');
1214
1215 case 'slug':
1216 return self::get_value($term, 'slug');
1217
1218 case 'count':
1219 return self::get_value($term, 'count');
1220
1221 case 'meta':
1222 if (!empty($meta_name)) {
1223 $term_id = self::get_value($term, 'term_id');
1224 $meta = get_term_meta($term_id, $meta_name, true);
1225
1226 return is_scalar($meta) ? (string) $meta : '';
1227 }
1228 return '';
1229
1230 default:
1231 return '';
1232 }
1233 }
1234
1235 public static function retrieve_post_content($post_id)
1236 {
1237 $content = apply_filters('the_content', get_the_content(null, false, $post_id));
1238
1239 $load_for = HelperFunctions::sanitize_text(isset($_GET['load_for']) ? $_GET['load_for'] : '');
1240
1241 if ($load_for !== 'kirki-iframe' && $kirki_data = HelperFunctions::is_kirki_type_data($post_id)) {
1242 $params = array(
1243 'blocks' => $kirki_data['blocks'],
1244 'style_blocks' => $kirki_data['styles'],
1245 'root' => 'root',
1246 'post_id' => $post_id,
1247 );
1248 $content = apply_filters('the_content', HelperFunctions::get_html_using_preview_script($params));
1249 }
1250
1251 return $content;
1252 }
1253
1254 /**
1255 * This methos is for if Theme enque some style and css then it will remove those styel and css codes.
1256 *
1257 * @return void
1258 */
1259 public static function remove_theme_style()
1260 {
1261 $theme = wp_get_theme();
1262 $parent_style = $theme->stylesheet . '-style';
1263 wp_dequeue_style($parent_style);
1264 wp_deregister_style($parent_style);
1265 wp_dequeue_style($parent_style . '-css');
1266 wp_deregister_style($parent_style . '-css');
1267 }
1268
1269 public static function dequeue_all_except_my_plugin()
1270 {
1271
1272 global $wp_scripts, $wp_styles, $kirki_editor_assets;
1273
1274 foreach ($wp_scripts->queue as $handle) {
1275
1276 // Keep WordPress media scripts
1277 if (
1278 str_starts_with($handle, 'media') ||
1279 str_starts_with($handle, 'wp-media') ||
1280 in_array($handle, ['underscore', 'backbone', 'jquery', 'wp-util'])
1281 ) {
1282 continue;
1283 }
1284
1285 wp_dequeue_script($handle);
1286 }
1287
1288 foreach ($wp_styles->queue as $handle) {
1289
1290 // Keep media related styles
1291 if (
1292 str_starts_with($handle, 'media') ||
1293 in_array($handle, ['buttons', 'dashicons', 'imgareaselect'])
1294 ) {
1295 continue;
1296 }
1297
1298 wp_dequeue_style($handle);
1299 }
1300
1301 if (!empty($kirki_editor_assets['scripts'])) {
1302 foreach ($kirki_editor_assets['scripts'] as $script_handle) {
1303 wp_enqueue_script($script_handle);
1304 }
1305 }
1306
1307 if (!empty($kirki_editor_assets['styles'])) {
1308 foreach ($kirki_editor_assets['styles'] as $style_handle) {
1309 wp_enqueue_style($style_handle);
1310 }
1311 }
1312 }
1313
1314 /**
1315 * Generate html using Preview script.
1316 *
1317 * @param array $data blocks.
1318 * @param array $style_blocks style_blocks.
1319 * @param string $root data root.
1320 * @param int $id if need prefix like symbol or popup post_id.
1321 * @return string the html string.
1322 */
1323 public static function get_html_using_preview_script(array $params = [])
1324 {
1325
1326 $blocks = $params['blocks'] ?? null;
1327 $style_blocks = $params['style_blocks'] ?? null;
1328 $root = $params['root'] ?? null;
1329 $options = $params['options'] ?? [];
1330 $post_id = $params['post_id'] ?? null;
1331 $get_style = $params['get_style'] ?? true;
1332 $get_variable = $params['get_variable'] ?? true;
1333 $should_take_app_script = $params['should_take_app_script'] ?? true;
1334 $prefix = $params['prefix'] ?? false;
1335 $get_all_style_forcefully_if_get_style_true = $params['get_all_style_forcefully_if_get_style_true'] ?? false;
1336
1337 if ($blocks) {
1338 $options['search_related_collection_ids'] = isset($params['search_related_collection_ids']) ? $params['search_related_collection_ids'] : self::collect_search_related_collection_ids($blocks);
1339 }
1340
1341 //set initial context data start
1342 if (!isset($options['post'])) {
1343 $post = get_post(get_the_ID());
1344 if ($post) {
1345 $options['post'] = $post;
1346 $options['itemType'] = 'post';
1347 }
1348 }
1349 if (!isset($options['user'])) {
1350 $user = get_user_by('id', get_current_user_id());
1351 if ($user) {
1352 $options['user'] = Users::get_format_single_user_data($user);
1353 }
1354 }
1355 //set initial context data end
1356
1357 $preview = new Preview($blocks, $style_blocks, $root, $post_id, $prefix);
1358 $html = $preview->getHtml($options);// this method need to call first cause after that only used style block array construct.
1359 $only_used_style_blocks = $get_all_style_forcefully_if_get_style_true ? $style_blocks : $preview->get_only_used_style_blocks();
1360 $s = '';
1361
1362 if ($get_style) {
1363 //style will be false when it calls from collection single item. only first item will generate style. others item will be same.
1364 $s .= $preview->getStyleTag($only_used_style_blocks);
1365 }
1366
1367 $s .= $preview->get_interaction_set_as_initial_css();
1368
1369
1370
1371 $s .= $html;
1372 $s .= $preview->getScriptTag($should_take_app_script);
1373
1374 $s = self::decode_entities_without_creating_markup($s);
1375 return $s;
1376 }
1377
1378 /**
1379 * Decode HTML entities in already-rendered page markup without ever turning
1380 * escaped text back into live tags.
1381 *
1382 * The rendered document mixes trusted markup (elements the builder emitted,
1383 * including admin authored custom code) with escaped text nodes. A blanket
1384 * html_entity_decode() over that mix undoes the escaping applied while
1385 * rendering, so `&lt;script&gt;` stored in any text value — a visitor's
1386 * comment, for instance — becomes an executable `<script>` tag.
1387 *
1388 * Angle-bracket entities are therefore held back while everything else is
1389 * decoded as before, then restored verbatim. Entities such as `&amp;`,
1390 * `&nbsp;` and named HTML5 entities keep decoding exactly as they used to;
1391 * markup emitted by the renderer is unaffected because it contains literal
1392 * `<`/`>`, not entities.
1393 *
1394 * @param string $content Rendered page markup.
1395 * @return string
1396 */
1397 private static function decode_entities_without_creating_markup( $content ) {
1398 if ( ! is_string( $content ) || '' === $content ) {
1399 return $content;
1400 }
1401
1402 $held = array();
1403
1404 // `&lt;` `&gt;` and their numeric/hex forms, in any zero-padded spelling.
1405 $content = preg_replace_callback(
1406 '/&(?:lt|gt|#0*(?:60|62)|#[xX]0*3[ceCE]);/i',
1407 function ( $matches ) use ( &$held ) {
1408 $key = "\x02kirki-entity-" . count( $held ) . "\x03";
1409 $held[ $key ] = $matches[0];
1410 return $key;
1411 },
1412 $content
1413 );
1414
1415 $content = html_entity_decode( $content, ENT_NOQUOTES | ENT_HTML5, 'UTF-8' );
1416
1417 return strtr( $content, $held );
1418 }
1419
1420 private static function collect_search_related_collection_ids($data)
1421 {
1422 $result = array();
1423 foreach ($data as $item) {
1424 if (
1425 isset($item['properties']['dynamicContent']['related']) &&
1426 $item['properties']['dynamicContent']['related'] === true &&
1427 !empty($item['properties']['dynamicContent']['relatedCollection'])
1428 ) {
1429 $result[$item['properties']['dynamicContent']['relatedCollection']] = true;
1430 }
1431 }
1432 return $result;
1433 }
1434
1435 public static function get_custom_fonts_tags()
1436 {
1437 $custom_fonts = HelperFunctions::get_global_data_using_key(KIRKI_USER_CUSTOM_FONTS_META_KEY);
1438 if ($custom_fonts) {
1439 $s = '';
1440 foreach ($custom_fonts as $key => $fonts_data) {
1441 $s .= self::getFontsHTMLMarkup($fonts_data);
1442 }
1443 return $s;
1444 }
1445 }
1446
1447 public static function getFontsHTMLMarkup($fonts_data)
1448 {
1449 if (isset($fonts_data['fontUrl'])) {
1450 $font_url = isset($fonts_data['localUrl']) ? $fonts_data['localUrl'] : $fonts_data['fontUrl'];
1451 //phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
1452 return '<link class="' . 'kirki-custom-fonts-link" href="' . $font_url . '" rel="stylesheet">';
1453 }
1454 return '';
1455 }
1456
1457 /**
1458 * Generate new id and html string for: symbol, collection etc.
1459 *
1460 * @param array $data blocks.
1461 * @param array $style_blocks style_blocks.
1462 * @param string $root data root.
1463 */
1464 public static function rec_update_data_id_then_return_new_html($data, $style_blocks, $root = 'body', $options = [], $get_style = true)
1465 {
1466 $data_helper = new DataHelper();
1467 $data_helper->rec_update_data_id_to_new_id($data, $style_blocks, $root, null);
1468 $data = $data_helper->temp_data;
1469 $style_blocks = $data_helper->temp_styles;
1470 $root = isset($data_helper->temp_ids[$root]) ? $data_helper->temp_ids[$root] : false;
1471
1472 $params = array(
1473 'blocks' => $data,
1474 'style_blocks' => $style_blocks,
1475 'root' => $root,
1476 'post_id' => null,
1477 'options' => $options,
1478 'get_style' => $get_style,
1479 'get_all_style_forcefully_if_get_style_true' => true,//this will generate collection first item all style
1480 );
1481
1482 return self::get_html_using_preview_script($params);
1483 }
1484
1485 // hook
1486 public static function kirki_html_generator($s, $post_id, $staging_version = false)
1487 {
1488 $d = self::is_kirki_type_data($post_id, $staging_version);
1489 if ($d) {
1490 $params = array(
1491 'blocks' => $d['blocks'],
1492 'style_blocks' => $d['styles'],
1493 'root' => 'root',
1494 'post_id' => $post_id,
1495 );
1496 return self::get_html_using_preview_script($params);
1497 }
1498 return false;
1499 }
1500
1501 /**
1502 * Find symbol for post id using condition
1503 * it will find and return selected symbols html and css;
1504 *
1505 * @param string $type : post | user
1506 * @param array $context : post object or user object
1507 * @return symbol || bool(false)
1508 */
1509 public static function find_template_for_this_context($type = 'post', $context = null)
1510 {
1511 $templates = Page::fetch_list('kirki_template', true, array('publish'));
1512 if (!$templates || !$context)
1513 return false;
1514 foreach ($templates as $key => $template) {
1515 if (isset($template['conditions'])) {
1516 $conditions = $template['conditions'];
1517 if ($type === 'user') {
1518 if (self::check_all_conditions_for_this_user($context, $conditions)) {
1519 return $template;
1520 }
1521 } else if ($type === 'post') {
1522 if (self::check_all_conditions_for_this_post($context, $conditions)) {
1523 return $template;
1524 }
1525 } else if ($type === 'term') {
1526 if (self::check_all_conditions_for_this_term($context, $conditions)) {
1527 return $template;
1528 }
1529 }
1530 }
1531 }
1532 return false;
1533 }
1534
1535 /**
1536 * Generate popup html
1537 *
1538 * @return strint
1539 */
1540 public static function get_page_popups_html()
1541 {
1542 global $post;
1543 if ($post) {
1544 $popups = self::get_page_popups();
1545 if (count($popups) > 0) {
1546 $s = '';
1547 foreach ($popups as $key => $popup) {
1548 $params = array(
1549 'blocks' => $popup['blocks'],
1550 'style_blocks' => $popup['styleBlocks'],
1551 'root' => $popup['root'],
1552 'post_id' => $popup['id'],
1553 );
1554 $s .= self::get_html_using_preview_script($params);
1555 }
1556 return do_shortcode($s);
1557 }
1558 }
1559 return '';
1560 }
1561
1562 /**
1563 * Get Custom popups
1564 * it will find and return selected popups;
1565 *
1566 * @return array
1567 */
1568 public static function get_page_popups()
1569 {
1570 global $post;
1571 if ($post) {
1572 $popups = Page::fetch_list('kirki_popup', true, array('publish'));
1573 return self::find_popups_for_this_post($popups, $post);
1574 }
1575 return array();
1576 }
1577
1578 /**
1579 * Find popups for post id using condition
1580 * it will find and return selected popups arr;
1581 *
1582 * @param object $popups popup block object.
1583 * @param object $post post object.
1584 * @return popups || [];
1585 */
1586 public static function find_popups_for_this_post($popups, $post)
1587 {
1588 $arr = array();
1589 $post_id = $post->ID;
1590 foreach ($popups as $key => $popup) {
1591 $popup = self::format_single_popup_data_for_html_print($popup);
1592 if (self::check_popup_pagearr_logic($popup, $post_id)) {
1593 $arr[] = $popup;
1594 }
1595 }
1596 return $arr;
1597 }
1598
1599 private static function format_single_popup_data_for_html_print($popup)
1600 {
1601 if (!$popup['blocks'])
1602 return $popup;
1603 $root = false;
1604 foreach ($popup['blocks'] as $key2 => &$b) {
1605 if ('root' === $b['parentId']) {
1606 $root = $b['id'];
1607
1608 if (isset($b['properties']['attributes'])) {
1609 $b['properties']['attributes']['popup-id'] = $popup['id'];
1610 } else {
1611 $b['properties']['attributes'] = array(
1612 'popup-id' => $popup['id'],
1613 );
1614 }
1615 $popup['root'] = $root;
1616 }
1617 }
1618
1619 return $popup;
1620 }
1621
1622 /**
1623 * Check popup is active apply for this post or not.
1624 *
1625 * @param array $popup popup from Page::fetch_list.
1626 * @param int $post_id wp post id.
1627 * @return bool
1628 */
1629 private static function check_popup_pagearr_logic($popup, $post_id)
1630 {
1631 if (!isset($popup['root']))
1632 return false;
1633 $root = $popup['root'];
1634 if ($root && isset($popup['blocks'][$root]['properties']['popup']['visibilityConditions'])) {
1635 $conditions = $popup['blocks'][$root]['properties']['popup']['visibilityConditions'];
1636 $post = get_post($post_id);
1637 if (self::check_all_conditions_for_this_post($post, $conditions) || in_array($popup['id'], Preview::$only_used_popup_id_array, true)) {
1638 return true;
1639 }
1640 }
1641 return false;
1642 }
1643
1644 /**
1645 * Check all conditon for this post
1646 * This method will match all the condition and return true or false
1647 * category = * || post_type
1648 * taxonomy = 1=single post || * = all post || taxonomy slug
1649 * apply[to] = * = all || id = post id || term id
1650 * visibility = show || hide
1651 *
1652 * @param object $post post object.
1653 * @param object $conditions symbol visibility condiotions object.
1654 *
1655 * @return Boolean
1656 */
1657 public static function check_all_conditions_for_this_post($post, $conditions)
1658 {
1659 $show_flag = false;
1660 $hide_flag = false;
1661
1662 foreach ($conditions as $key => $condition) {
1663 if (!isset($condition['from'])) {
1664 $condition['from'] = 'post';
1665 }
1666 if ($condition['from'] === 'term') {
1667 return false;
1668 }
1669 if (isset($condition['category'])) {
1670 //legacy
1671 if ($condition['category'] === '*') {
1672 $condition['type'] = '*';
1673 } else {
1674 $condition['post_type'] = $condition['category'];
1675 $condition['type'] = 'post';
1676 }
1677 }
1678
1679 if (!isset($condition['visibility'])) {
1680 $condition['visibility'] = 'show';
1681 }
1682
1683 if ($condition['type'] === '*') {
1684 // Entire site
1685 $show_flag = $condition['visibility'] === 'show';
1686 } elseif ($condition['type'] === 'post') {
1687 if ($condition['post_type'] === $post->post_type) {
1688 // Post type related
1689 if ($condition['where'] == '*') {
1690 // All posts
1691 $show_flag = $condition['visibility'] === 'show';
1692 } elseif ($condition['where'] == 'single') {
1693 // Single post
1694 if ($condition['to'] === $post->ID) {
1695 $show_flag = $condition['visibility'] === 'show';
1696 $hide_flag = $condition['visibility'] === 'hide';
1697 }
1698 } else {
1699 // Taxonomy
1700 $taxonomy = $condition['where'];
1701 $term = $condition['to'];
1702 if ($term === '*') {
1703 // All terms
1704 $show_flag = $condition['visibility'] === 'show';
1705 } else {
1706 if (has_term($term, $taxonomy, $post->ID)) {
1707 $show_flag = $condition['visibility'] === 'show';
1708 $hide_flag = $condition['visibility'] === 'hide';
1709 }
1710 }
1711 }
1712 }
1713 }
1714
1715 // If hide flag is set, stop and return false
1716 if ($hide_flag) {
1717 return false;
1718 }
1719 }
1720 return $show_flag;
1721 }
1722
1723
1724 public static function check_all_conditions_for_this_term($term, $conditions)
1725 {
1726 $show_flag = false;
1727 $hide_flag = false;
1728 foreach ($conditions as $key => $condition) {
1729 if (!isset($condition['visibility'])) {
1730 $condition['visibility'] = 'show';
1731 }
1732 if (!isset($condition['from'])) {
1733 $condition['from'] = 'post';
1734 }
1735
1736 if ($condition['from'] !== 'term') {
1737 return false;
1738 }
1739
1740 if ($condition['type'] === '*') {
1741 // Entire site
1742 $show_flag = $condition['visibility'] === 'show';
1743 } elseif ($condition['type'] === 'post') {
1744
1745 if ($condition['post_type'] === $term['post_type']) {
1746 // Post type related
1747 if ($condition['where'] == '*') {
1748 // All posts
1749 $show_flag = $condition['visibility'] === 'show';
1750 } else {
1751 // Taxonomy
1752 $taxonomy = $condition['where'];
1753 $con_term = $condition['to'];
1754 if ($con_term === '*') {
1755 // All terms
1756 $show_flag = $condition['visibility'] === 'show';
1757 } else {
1758 if ($term['taxonomy'] == $con_term) {
1759 $show_flag = $condition['visibility'] === 'show';
1760 $hide_flag = $condition['visibility'] === 'hide';
1761 }
1762 }
1763 }
1764 }
1765 }
1766
1767 // If hide flag is set, stop and return false
1768 if ($hide_flag) {
1769 return false;
1770 }
1771 }
1772 return $show_flag;
1773 }
1774
1775 public static function check_all_conditions_for_this_user($user, $conditions)
1776 {
1777 $show_flag = false;
1778 $hide_flag = false;
1779 foreach ($conditions as $key => $condition) {
1780
1781 if (isset($condition['category'])) {
1782 //legacy
1783 if ($condition['category'] === '*') {
1784 $condition['type'] = '*';
1785 } else {
1786 $condition['post_type'] = $condition['category'];
1787 $condition['type'] = 'post';
1788 }
1789 }
1790
1791 if (!isset($condition['visibility'])) {
1792 $condition['visibility'] = 'show';
1793 }
1794
1795 if ($condition['type'] === '*') {
1796 // Entire site
1797 $show_flag = $condition['visibility'] === 'show';
1798 } elseif ($condition['type'] === 'user') {
1799 if ($condition['where'] === '*') {
1800 $show_flag = $condition['visibility'] === 'show';
1801 } elseif ($condition['where'] === 'role') {
1802 $user_role = $condition['to'];
1803
1804 if ($user_role === '*') {
1805 $show_flag = $condition['visibility'] === 'show';
1806 } else {
1807 $curr_user = $user;
1808 if (in_array($user_role, $curr_user['roles'])) {
1809 $show_flag = $condition['visibility'] === 'show';
1810 }
1811 }
1812 }
1813 }
1814
1815 if ($hide_flag) {
1816 return false;
1817 }
1818 }
1819
1820 return $show_flag;
1821 }
1822
1823
1824 private static function attribute_in_post_table($attr = '')
1825 {
1826 $post_table_attributes = array(
1827 'ID',
1828 'post_author',
1829 'post_date',
1830 'post_date_gmt',
1831 'post_content',
1832 'post_title',
1833 'post_excerpt',
1834 'post_status',
1835 'post_name',
1836 'post_type',
1837 'post_category',
1838 'term'
1839 );
1840
1841 return in_array($attr, $post_table_attributes, true);
1842 }
1843
1844 private static function sort_filters_by_relation($filter_items = array())
1845 {
1846 $sorted_array = array();
1847
1848 if (is_array($filter_items)) {
1849 array_walk($filter_items, function ($item) use (&$sorted_array) {
1850 $relation_raw = isset($item['relation']) ? $item['relation'] : 'OR';
1851 $relation = in_array(strtoupper($relation_raw), ['AND', 'OR'], true) ? strtoupper($relation_raw) : 'OR';
1852
1853 if (!isset($sorted_array[$relation])) {
1854 $sorted_array[$relation] = array();
1855 }
1856
1857 unset($item['relation']);
1858
1859 $sorted_array[$relation][] = $item;
1860 });
1861 }
1862
1863 return $sorted_array;
1864 }
1865
1866 /**
1867 * text, number, date/time, options, switch
1868 */
1869 private static function post_table_filter_query($filter_item, $data_type)
1870 {
1871 $field_name = $filter_item['id'];
1872 $sorted_array = self::sort_filters_by_relation($filter_item['items']);
1873
1874 $where_sql = '';
1875
1876 array_walk($sorted_array, function ($sorted_array_item, $condition) use ($data_type, $field_name, &$where_sql) {
1877 global $wpdb;
1878
1879 $conditions = array();
1880 $column_name = "$wpdb->posts.$field_name";
1881
1882 array_walk($sorted_array_item, function ($filter_condition_item) use ($data_type, $column_name, &$conditions) {
1883 switch ($data_type) {
1884 case 'text': {
1885 $conditions[] = PostsQueryUtils::post_table_text_query($column_name, $filter_condition_item['condition'], $filter_condition_item['value']);
1886 break;
1887 }
1888
1889 // case 'date': {
1890 // $conditions[] = PostsQueryUtils::post_table_text_query($column_name, $filter_condition_item['condition'], $filter_condition_item['value']);
1891 // break;
1892 // }
1893
1894 // case 'number': {
1895 // $conditions[] = PostsQueryUtils::post_table_number_query($column_name, $filter_condition_item['condition'], $filter_condition_item['value']);
1896 // break;
1897 // }
1898
1899 // case 'option': {
1900 // $conditions[] = PostsQueryUtils::post_table_options_query($column_name, $filter_condition_item['condition'], $filter_condition_item['value']);
1901 // break;
1902 // }
1903
1904 // case 'switch': {
1905 // $conditions[] = PostsQueryUtils::post_table_switch_query($column_name, $filter_condition_item['condition'], $filter_condition_item['value']);
1906 // break;
1907 // }
1908
1909 default: {
1910 break;
1911 }
1912 }
1913 });
1914
1915 if (empty($conditions)) {
1916 return;
1917 }
1918
1919 $condition_sql = implode(" {$condition} ", $conditions);
1920
1921 if ('OR' === $condition) {
1922 $condition_sql = "({$condition_sql})";
1923 }
1924
1925 /**
1926 * A query to
1927 * [x start with 'JoomShaper' or 'IcoFont' or 'Kirki'
1928 * and contains 'website' and ends with 'Ollyo']
1929 * will be like below:
1930 *
1931 * AND (x LIKE 'JoomShaper%' OR x LIKE 'IcoFont%' OR x LIKE 'Kirki%') AND x LIKE '%website%' AND x LIKE '%Ollyo'
1932 */
1933 $where_sql .= " AND $condition_sql";
1934 });
1935
1936 if (empty($where_sql)) {
1937 return null;
1938 }
1939
1940 $callback = function ($where) use ($where_sql) {
1941 $where .= $where_sql;
1942 return $where;
1943 };
1944
1945 add_filter('posts_where', $callback);
1946
1947 return $callback;
1948 }
1949
1950 /**
1951 * text, number, date/time, options, switch
1952 * help: https://wordpress.stackexchange.com/questions/159426/meta-query-with-string-starting-like-pattern
1953 */
1954
1955 private static function post_meta_table_filter_query($filter_item, $key, $data_type)
1956 {
1957 $sorted_array = self::sort_filters_by_relation($filter_item['items']);
1958 $meta_query = array();
1959
1960 array_walk($sorted_array, function ($sorted_array_item, $condition) use (&$meta_query, $key, $data_type) {
1961 if (count($sorted_array_item) > 0) {
1962 $condition_arr = array('relation' => $condition);
1963
1964 array_walk($sorted_array_item, function ($filter_condition_item) use (&$condition_arr, $key, $data_type) {
1965
1966 switch ($data_type) {
1967 case 'text': {
1968 $condition_arr[] = PostsQueryUtils::post_meta_table_text_query($key, $filter_condition_item['condition'], $filter_condition_item['value']);
1969 break;
1970 }
1971
1972 case 'date': {
1973 $condition_arr[] = PostsQueryUtils::post_meta_table_date_query($key, $filter_condition_item); // ['start-date' => '', 'end-date' => '']);
1974 break;
1975 }
1976
1977 case 'number': {
1978 $condition_arr[] = PostsQueryUtils::post_meta_table_number_query($key, $filter_condition_item['condition'], $filter_condition_item['value']);
1979 break;
1980 }
1981
1982 case 'option': {
1983 $condition_arr[] = PostsQueryUtils::post_meta_table_options_query($key, $filter_condition_item['condition'], $filter_condition_item['values']);
1984 break;
1985 }
1986
1987 case 'switch': {
1988 $condition_arr[] = PostsQueryUtils::post_meta_table_switch_query($key, $filter_condition_item['condition']);
1989 break;
1990 }
1991
1992 default: {
1993 break;
1994 }
1995 }
1996 });
1997
1998 $meta_query[] = $condition_arr;
1999 }
2000 });
2001
2002 return $meta_query;
2003 }
2004
2005 /**
2006 * filter query for reference table
2007 *
2008 * @param object $filter_item filter item.
2009 * @param string $key field meta key.
2010 * @param array $args args.
2011 *
2012 * @return array args
2013 */
2014
2015 private static function cm_reference_table_filter_query($filter_item, $key, $args)
2016 {
2017 global $wpdb;
2018
2019 $sorted_array = self::sort_filters_by_relation($filter_item['items']);
2020
2021 $post_ids_in = [];
2022 $post_ids_not_in = [];
2023
2024 $has_in_condition = false;
2025 $has_not_in_condition = false;
2026
2027 foreach ($sorted_array as $relation_str => $filters_by_relation) {
2028 foreach ($filters_by_relation as $filter_condition_item) {
2029 $condition = $filter_condition_item['condition'];
2030 $value = isset($filter_condition_item['value']) ? (int) $filter_condition_item['value'] : null;
2031
2032 if (!$value)
2033 continue;
2034
2035 $results = $wpdb->get_col($wpdb->prepare(
2036 "SELECT post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id = %d",
2037 $key,
2038 $value
2039 ));
2040
2041 $results = array_map('intval', $results);
2042
2043 if ($condition === 'in') {
2044 $has_in_condition = true;
2045 if ($relation_str === 'AND') {
2046 $post_ids_in[] = $results;
2047 } else {
2048 $post_ids_in = array_merge($post_ids_in, $results);
2049 }
2050 } elseif ($condition === 'not-in') {
2051 $has_not_in_condition = true;
2052 if ($relation_str === 'AND') {
2053 $post_ids_not_in[] = $results;
2054 } else {
2055 $post_ids_not_in = array_merge($post_ids_not_in, $results);
2056 }
2057 }
2058 }
2059 }
2060
2061 // Handle post__in only if there was an 'in' condition
2062 if ($has_in_condition) {
2063 if (!empty($post_ids_in)) {
2064 $post_ids_in = is_array(reset($post_ids_in))
2065 ? array_reduce($post_ids_in, 'array_intersect', array_shift($post_ids_in)) // ids-> [[1,2], [2,3]] -> array_reduce($ids, 'array_intersect', [1,2])
2066 : $post_ids_in;
2067
2068 $args['post__in'] = isset($args['post__in'])
2069 ? array_intersect($args['post__in'], $post_ids_in)
2070 : $post_ids_in;
2071
2072 if (empty($args['post__in'])) {
2073 $args['post__in'] = [0];
2074 }
2075 } else {
2076 // 'in' condition was given, but returned nothing
2077 $args['post__in'] = [0];
2078 }
2079 }
2080
2081 // Handle post__not_in normally
2082 if ($has_not_in_condition && !empty($post_ids_not_in)) {
2083 $post_ids_not_in = is_array(reset($post_ids_not_in))
2084 ? array_merge(...$post_ids_not_in)
2085 : $post_ids_not_in;
2086
2087 $args['post__not_in'] = isset($args['post__not_in'])
2088 ? array_merge($args['post__not_in'], $post_ids_not_in)
2089 : $post_ids_not_in;
2090 }
2091
2092 return $args;
2093 }
2094
2095 /**
2096 * handle legacy filter data
2097 *
2098 * @param object $params filter array.
2099 *
2100 * @return array filter array
2101 */
2102 public static function handle_legacy_filter_to_new_filter($filters)
2103 {
2104 $new_filters = array();
2105
2106 if (is_array($filters)) {
2107 foreach ($filters as $key => $item) {
2108 if (!isset($item['id']) && isset($item['type']) && $item['type']) {
2109 switch ($item['type']) {
2110 case 'date': {
2111 $new_filters[] = array(
2112 'type' => 'post_date',
2113 'id' => 'post_date',
2114 'title' => 'Post Date',
2115 'items' => [
2116 array(
2117 'start-date' => isset($item['start-date']) ? $item['start-date'] : '',
2118 'end-date' => isset($item['end-date']) ? $item['end-date'] : '',
2119 'relation' => 'OR',
2120 )
2121 ],
2122 );
2123
2124 break;
2125 }
2126
2127 case 'author': {
2128 $new_filters[] = array(
2129 'type' => 'post_author',
2130 'id' => 'post_author',
2131 'title' => 'Author',
2132 'items' => [
2133 array(
2134 'condition' => 'in',
2135 'values' => $item['values'],
2136 'relation' => 'OR',
2137 )
2138 ],
2139 );
2140
2141 break;
2142 }
2143
2144 case 'category': {
2145 $new_filters[] = array(
2146 'type' => 'post_category',
2147 'id' => 'post_category',
2148 'title' => 'Category',
2149 'items' => [
2150 array(
2151 'condition' => 'in',
2152 'values' => $item['values'],
2153 'relation' => 'OR',
2154 )
2155 ],
2156 );
2157 break;
2158 }
2159
2160 default: {
2161 break;
2162 }
2163 }
2164 } else {
2165 $new_filters[] = $item;
2166 }
2167 }
2168 }
2169 return $new_filters;
2170 }
2171
2172 /**
2173 * Static callback for posts_where filter to allow removal with remove_filter.
2174 *
2175 * @param string $where The WHERE clause.
2176 * @return string Modified WHERE clause.
2177 */
2178 public static function posts_where_filter_callback($where)
2179 {
2180 global $wpdb;
2181
2182 $params = self::$posts_where_filter_params;
2183 if (empty($params)) {
2184 return $where;
2185 }
2186
2187 $query = $params['query'];
2188 $reference_where_sql = $params['reference_where_sql'];
2189 $post_parent = $params['post_parent'];
2190
2191 $search = esc_sql($wpdb->esc_like($query));
2192
2193 $where .= $wpdb->prepare(
2194 " OR (
2195 ({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s)
2196 AND {$wpdb->posts}.post_parent = %d
2197 )",
2198 "%{$search}%",
2199 "%{$search}%",
2200 $post_parent
2201 );
2202
2203 if (!empty($reference_where_sql)) {
2204 $where .= " {$reference_where_sql}";
2205 }
2206
2207 return $where;
2208 }
2209
2210 public static function get_kirki_cms_inherit_post_filters( $filters, $related_post_parent, $post_parent ) {
2211 global $wpdb;
2212
2213 if ( empty( $related_post_parent ) ) {
2214 return $filters;
2215 }
2216
2217 $related_post_parent = (int) $related_post_parent;
2218
2219 $related_post_parent_obj = get_post($related_post_parent);
2220 if($related_post_parent_obj && str_contains( $related_post_parent_obj->post_type, 'kirki_cm_' )){
2221 $related_fields = ContentManagerHelper::get_post_type_custom_field_keys( $post_parent );
2222
2223 if ( empty( $related_fields ) || ! is_array( $related_fields ) ) {
2224 return $filters;
2225 }
2226
2227 if ( ! is_array( $filters ) ) {
2228 $filters = [];
2229 }
2230
2231 foreach ( $related_fields as $field ) {
2232 if ( ! isset( $field['type'] ) || ! in_array( $field['type'], [ 'multi-reference', 'reference' ], true ) ) {
2233 continue;
2234 }
2235
2236 $field_id = $field['id'] ?? null;
2237 if ( ! $field_id ) {
2238 continue;
2239 }
2240
2241 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id( $post_parent, $field_id );
2242
2243 $has_references = $wpdb->get_var(
2244 $wpdb->prepare(
2245 "SELECT COUNT(*) FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id = %d",
2246 $meta_key,
2247 $related_post_parent
2248 )
2249 );
2250
2251 if ( empty( $has_references ) ) {
2252 continue;
2253 }
2254
2255 $filter_exists = false;
2256 $filter_index = null;
2257
2258 foreach ( $filters as $index => $existing_filter ) {
2259 if ( isset( $existing_filter['id'] ) && $existing_filter['id'] === $field_id ) {
2260 $filter_exists = true;
2261 $filter_index = $index;
2262 break;
2263 }
2264 }
2265
2266 if ( ! $filter_exists ) {
2267 $filters[] = [
2268 'type' => $field['type'],
2269 'id' => $field_id,
2270 'title' => $field['label'] ?? $field_id,
2271 'items' => [],
2272 ];
2273 $filter_index = count( $filters ) - 1;
2274 }
2275
2276 if ( ! isset( $filters[ $filter_index ]['items'] ) || ! is_array( $filters[ $filter_index ]['items'] ) ) {
2277 $filters[ $filter_index ]['items'] = [];
2278 }
2279
2280 $new_item = [
2281 'condition' => 'in',
2282 'value' => $related_post_parent,
2283 'relation' => 'OR',
2284 ];
2285
2286 $item_exists = false;
2287 foreach ( $filters[ $filter_index ]['items'] as $item ) {
2288 if (
2289 isset( $item['condition'], $item['value'], $item['relation'] ) &&
2290 $item['condition'] === $new_item['condition'] &&
2291 (int) $item['value'] === $new_item['value'] &&
2292 $item['relation'] === $new_item['relation']
2293 ) {
2294 $item_exists = true;
2295 break;
2296 }
2297 }
2298
2299 if ( ! $item_exists ) {
2300 $filters[ $filter_index ]['items'][] = $new_item;
2301 }
2302 }
2303 }
2304
2305
2306 return $filters;
2307 }
2308
2309 /**
2310 * Get dynamic collection data
2311 *
2312 * @param object $params query object.
2313 *
2314 * @return array post array
2315 */
2316 public static function get_posts($params)
2317 {
2318 $name = isset($params['name']) ? $params['name'] : null;
2319 $sorting = isset($params['sorting']) ? $params['sorting'] : null;
2320 $filters = isset($params['filters']) ? $params['filters'] : null;
2321 $inherit = (bool) ($params['inherit'] ?? false);
2322 $related = (bool) ($params['related'] ?? false);
2323 $post_parent = (int) ($params['post_parent'] ?? 0);
2324 $post_status = isset($params['post_status']) ? $params['post_status'] : 'publish';
2325 $query = isset($params['q']) ? $params['q'] : '';
2326 $IDs = isset($params['IDs']) ? $params['IDs'] : [];
2327 $related_post_parent = isset($params['related_post_parent']) ? $params['related_post_parent'] : self::get_post_id_if_possible_from_url();
2328
2329 // add new
2330 $current_page = isset($params['current_page']) ? $params['current_page'] : 1;
2331 $item_per_page = isset($params['item_per_page']) ? $params['item_per_page'] : 3;
2332 $offset = isset($params['offset']) ? $params['offset'] : 0;
2333 $context = isset($params['context']) ? $params['context'] : null;
2334 $tax_query = [
2335 'relation' => 'AND',
2336 ];
2337
2338 // Calculate the offset
2339 $offset = ($current_page - 1) * $item_per_page + $offset;
2340
2341 $args = array(
2342 'posts_per_page' => $item_per_page,
2343 'paged' => $current_page,
2344 'offset' => $offset,
2345 'post_type' => $name,
2346 'suppress_filters' => true,
2347 'post_status' => $post_status,
2348 's' => $query,
2349 );
2350
2351 if (!empty($query)) {
2352 self::search_posts_by_query($name, $query, $post_parent, $args);
2353 } else {
2354 remove_filter('posts_where', [HelperFunctions::class, 'posts_where_filter_callback']);
2355 }
2356
2357 $filters = self::handle_legacy_filter_to_new_filter($filters);
2358 if($inherit && $related_post_parent && str_contains( $name, 'kirki_cm_' )){
2359 $filters = self::get_kirki_cms_inherit_post_filters($filters, $related_post_parent, $post_parent);
2360 }
2361 $added_filters = array();
2362
2363 /**
2364 * Combine search and filters with AND logic
2365 *
2366 * When both search query and filters are present, we need to ensure they work together:
2367 * - Search creates meta_query with 'OR' relation (matches any custom field)
2368 * - Filters add additional conditions
2369 * - Final structure: AND(search_conditions, filter_conditions)
2370 *
2371 * This makes filters compulsory when searching, narrowing results further.
2372 */
2373 $search_meta_query = isset($args['meta_query']) ? $args['meta_query'] : null;
2374 $has_search = !empty($query) && $search_meta_query !== null;
2375 $has_filters = !empty($filters) && is_array($filters);
2376
2377 // Reset meta_query if both search and filters exist to rebuild with AND relation
2378 if ($has_search && $has_filters) {
2379 $args['meta_query'] = [
2380 'relation' => 'AND',
2381 $search_meta_query, // Search conditions (with OR relation internally)
2382 ];
2383 }
2384
2385 if (isset($filters) && is_array($filters)) {
2386 foreach ($filters as $filter_item) {
2387 if (isset($filter_item['parent']) && $filter_item['parent']) {
2388 $filter_item['id'] = 'term';
2389 }
2390 $field_name = isset($filter_item['id']) ? $filter_item['id'] : '';
2391
2392 if (!$field_name) {
2393 continue;
2394 }
2395
2396 if (self::attribute_in_post_table($filter_item['id']) && is_array($filter_item['items'])) {
2397
2398 switch ($field_name) {
2399 case 'post_excerpt':
2400 case 'post_content':
2401 case 'post_title': {
2402 $callback = self::post_table_filter_query($filter_item, 'text');
2403 if ($callback) {
2404 $added_filters[] = $callback;
2405 }
2406 break;
2407 }
2408
2409 case 'post_date':
2410 case 'post_date_gmt': {
2411 /**
2412 * $filter_item['items'] max contain one array.
2413 * in array may contain start-date, end-date
2414 * Like: [{"start-date": "2020-01-01","end-date": "2020-01-02"}]
2415 */
2416
2417 if (isset($filter_item['items'], $filter_item['items'][0])) {
2418 $items = $filter_item['items'];
2419 $item = $items[0]; // Get first item in array.
2420
2421 $date_query = array('column' => $field_name);
2422 $date_query['inclusive'] = true;
2423
2424 if (!empty($item['start-date'])) {
2425 $date_query['after'] = $item['start-date'];
2426 }
2427
2428 if (!empty($item['end-date'])) {
2429 $date_query['before'] = $item['end-date'];
2430 }
2431
2432 $args['date_query'] = $date_query;
2433 }
2434
2435 break;
2436 }
2437
2438 case 'post_author': {
2439
2440 /**
2441 * $filter_item['items'] must not contain more than 2 array of conditions.
2442 * 1 array may contain 'in' conditions and another for 'not-in' conditions
2443 * And values of 'in' and 'not-in' conditions should not collide
2444 * Like: the condition should not be author 'in' [1, 2, 3] and 'not-in' [2, 4, 5]
2445 */
2446 $items = $filter_item['items'];
2447
2448 foreach ($items as $item) {
2449 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
2450 if ($item['condition'] === 'in') {
2451 $args['author__in'] = $item['values'];
2452 }
2453
2454 if ($item['condition'] === 'not-in') {
2455 $args['author__not_in'] = $item['values'];
2456 }
2457 }
2458 }
2459
2460 break;
2461 }
2462
2463 case 'term': {
2464 $items = $filter_item['items'];
2465
2466
2467 foreach ($items as $item) {
2468 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
2469
2470 if ($item['condition'] === 'in' && !empty($item['values'])) {
2471 array_push($tax_query, [
2472 'taxonomy' => $filter_item['type'],
2473 'field' => 'term_id',
2474 'terms' => $item['values'],
2475 'operator' => 'IN',
2476 ]);
2477 }
2478
2479 if ($item['condition'] === 'not-in' && !empty($item['values'])) {
2480 array_push($tax_query, [
2481 'taxonomy' => $filter_item['type'],
2482 'field' => 'term_id',
2483 'terms' => $item['values'],
2484 'operator' => 'NOT IN',
2485 ]);
2486 }
2487 }
2488 }
2489 break;
2490 }
2491 }
2492 } else {
2493 $key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $field_name);
2494 $data_type = $filter_item['type'] ?? 'text';
2495
2496 if (!isset($args['meta_query'])) {
2497 $args['meta_query'] = array();
2498 } elseif (!is_array($args['meta_query'])) {
2499 $args['meta_query'] = array();
2500 }
2501
2502 // Ensure meta_query has proper structure when combining search + filters
2503 if ($has_search && !isset($args['meta_query']['relation'])) {
2504 $args['meta_query']['relation'] = 'AND';
2505 }
2506
2507 switch ($data_type) {
2508 default:
2509 case 'rich_text':
2510 case 'text':
2511 case 'phone':
2512 case 'url':
2513 case 'email': {
2514 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'text');
2515 break;
2516 }
2517
2518 case 'date': {
2519 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'date');
2520 break;
2521 }
2522
2523 case 'number': {
2524 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'number');
2525 break;
2526 }
2527
2528 case 'option': {
2529 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'option');
2530 break;
2531 }
2532
2533 case 'switch': {
2534 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'switch');
2535 break;
2536 }
2537
2538 case 'taxonomy': {
2539 if (empty($args['tax_query'])) {
2540 $tax_query = array(
2541 'relation' => 'AND'
2542 );
2543 }
2544
2545 if (
2546 isset($filter_item['taxonomy'], $filter_item['terms']) &&
2547 is_array($filter_item['terms'])
2548 ) {
2549 $operators = array('NOT IN', 'IN');
2550
2551 $operator = 'IN';
2552
2553 if (isset($filter_item['operator']) && in_array($filter_item['operator'], $operators, true)) {
2554 $operator = $filter_item['operator'];
2555 }
2556
2557 array_push($tax_query, [
2558 'taxonomy' => $filter_item['taxonomy'],
2559 'field' => 'term_id', // So far this is fixed
2560 'terms' => $filter_item['terms'],
2561 'operator' => $operator,
2562 ]);
2563 }
2564 break;
2565 }
2566
2567 case 'author': {
2568 if (isset($filter_item['condition'], $filter_item['values']) && is_array($filter_item['values'])) {
2569 if ($filter_item['condition'] === 'is-equal') {
2570 $args['author__in'] = $filter_item['values'];
2571 }
2572
2573 if ($filter_item['condition'] === 'is-not-equal') {
2574 $args['author__not_in'] = $filter_item['values'];
2575 }
2576 }
2577 break;
2578 }
2579
2580 case 'multi-reference':
2581 case 'reference': {
2582 $args = self::cm_reference_table_filter_query($filter_item, $key, $args);
2583 break;
2584 }
2585 }
2586 }
2587 }
2588 }
2589
2590 if (count($IDs) > 0) {
2591 $args['post__in'] = $IDs;
2592 unset($args['post_parent']);
2593 $args['post_type'] = 'any';
2594 $inherit = false;
2595 $post_parent = false;
2596 }
2597
2598 if (count($tax_query) > 1) {
2599 $args['tax_query'] = $tax_query;
2600 }
2601
2602 // Set default orderby and order if not explicitly specified
2603 $order = 'DESC';
2604 $orderby = 'date';
2605
2606 if (isset($sorting) && is_array($sorting) && !empty($sorting)) {
2607 if (!empty($sorting['order'])) {
2608 $order = $sorting['order'];
2609 } elseif (!empty($sorting['type'])) {
2610 $order = $sorting['type'];
2611 }
2612
2613 if (!empty($sorting['orderby'])) {
2614 $orderby = $sorting['orderby'];
2615 } elseif (!empty($sorting['value'])) {
2616 $orderby = $sorting['value'];
2617 }
2618 }
2619
2620 $args['order'] = $order;
2621
2622 if ('none' !== $orderby) {
2623 if (isset($name) && str_contains($name, KIRKI_CONTENT_MANAGER_PREFIX) && !in_array($orderby, KIRKI_WORDPRESS_SORT_BY_OPTIONS, true)) {
2624 $args['orderby'] = 'meta_value'; // Use 'meta_value' or 'meta_value_num' as needed
2625 $args['meta_key'] = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $orderby);
2626 } elseif ('date' === $orderby) {
2627 $args['orderby'] = array('date' => $order, 'ID' => $order);
2628 } else {
2629 $args['orderby'] = $orderby;
2630 }
2631 }
2632
2633 if ($inherit || $post_parent) {
2634 //TODO: if terms page then show terms post only. like tag, category.
2635 $args['post_parent'] = $post_parent;
2636 }
2637
2638 if (!empty($context) && $inherit && isset( $args['post_type']) && !str_contains( $args['post_type'], 'kirki_cm') ) {
2639 if ($context['collectionType'] == 'user') {
2640 $args['author'] = $context['id'];
2641 unset($args['post_parent']);
2642 }
2643 if ($context['collectionType'] == 'term') {
2644 $args['tax_query'] = array(
2645 array(
2646 'taxonomy' => $context['taxonomy'], // Replace 'category' with your taxonomy
2647 'field' => 'term_id', // Use 'slug' if you want to query by slug
2648 'terms' => $context['id'], // Replace 123 with your term ID
2649 )
2650 );
2651 unset($args['post_parent']);
2652 }
2653 }
2654
2655 if ($related) {
2656 $post = get_post($related_post_parent);
2657
2658 if ($post) {
2659 $args['post_type'] = $post->post_type;
2660 if (str_contains($post->post_type, 'kirki_cm_')) {
2661 // filter related posts for content manager post
2662 $referenced_post_ids = self::get_referenced_post_ids($post_parent, $post);
2663 $args['post__in'] = array_map('intval', $referenced_post_ids);
2664 } else {
2665 $args['tax_query'] = self::buildTaxonomyForRelatedPosts($post);
2666 $args['post__not_in'] = [$post->ID];
2667 }
2668
2669 }
2670 }
2671
2672 // Disable suppress_filters when post_table text filters (post_title, post_content, post_excerpt)
2673 // are present, since they rely on posts_where hooks to modify the SQL query.
2674 if (!empty($added_filters)) {
2675 $args['suppress_filters'] = false;
2676 }
2677
2678 // Run the WP_Query
2679 $query = new WP_Query($args);
2680 foreach ($added_filters as $callback) {
2681 remove_filter('posts_where', $callback);
2682 }
2683
2684 $posts = $query->posts;
2685
2686
2687 $custom_logo_id = get_theme_mod('custom_logo');
2688 $image = wp_get_attachment_image_src($custom_logo_id, 'full');
2689
2690 $kirki_content_manager_post_type_fields = array();
2691
2692 if (isset($args['post_type']) && KIRKI_CONTENT_MANAGER_PREFIX === $args['post_type']) {
2693 $post_parent = $args['post_parent'];
2694 $kirki_content_manager_post_type_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2695 }
2696
2697 foreach ($posts as $key => &$post) {
2698 if (is_null($post)) {
2699 unset($posts[$key]);
2700 continue;
2701 }
2702 if (
2703 KIRKI_CONTENT_MANAGER_PREFIX === $post->post_type && is_array($kirki_content_manager_post_type_fields)
2704 ) {
2705 foreach ($kirki_content_manager_post_type_fields as $field_key) {
2706 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post->post_parent, $field_key['id']);
2707 $post->{$field_key['id']} = get_post_meta($post->ID, $meta_key, true);
2708
2709 if (
2710 isset($field_key['type']) &&
2711 $field_key['type'] === 'image' &&
2712 $post->{$field_key['id']}
2713 ) {
2714 $post->{$field_key['id']} = array(
2715 'wp_attachment_id' => $post->{$field_key['id']}['id'],
2716 'src' => $post->{$field_key['id']}['url'],
2717 );
2718 }
2719 }
2720 }
2721
2722
2723 $post->post_id = $post->ID;
2724 $post->author_profile_picture = array(
2725 'src' => get_avatar_url($post->post_author)
2726 );
2727 $post->post_author = get_the_author_meta('display_name', $post->post_author);
2728 $post->post_time = get_the_time('', $post->ID);
2729 $post->featured_image = array(
2730 'wp_attachment_id' => get_post_thumbnail_id($post->ID),
2731 'src' => get_the_post_thumbnail_url($post->ID)
2732 );
2733 $post->site_logo = isset($image[0]) ? $image[0] : '';
2734 $post->post_page_link = \get_permalink($post->ID);
2735 $post->author_posts_page_link = \get_author_posts_url($post->post_author);
2736
2737 unset($post->post_excerpt);
2738 }
2739 ;
2740
2741 // Get total posts and total pages for pagination
2742 $total_posts = $query->found_posts;
2743 $total_posts_updated = max(0, $total_posts - $offset);
2744 $total_pages = ceil($total_posts_updated / $item_per_page);
2745
2746 // Calculate previous and next page numbers
2747 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
2748 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
2749
2750 // Return the query and pagination info
2751 return array(
2752 'data' => $posts,
2753 'pagination' => array(
2754 'per_page' => $item_per_page,
2755 'current_page' => $current_page,
2756 'total_pages' => $total_pages,
2757 'total_count' => $total_posts,
2758 'previous' => $prev_page,
2759 'next' => $next_page,
2760 ),
2761 );
2762 }
2763
2764
2765 public static function search_posts_by_query($name, $query, $post_parent, &$args)
2766 {
2767 global $wpdb;
2768
2769 if (!str_contains($name, 'kirki_cm_')) {
2770 return;
2771 }
2772
2773 unset($args['s']);
2774
2775 $all_custom_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2776 $meta_query_args = ['relation' => 'OR'];
2777 $reference_where_sql = '';
2778
2779 foreach ($all_custom_fields as $data) {
2780 if (!$data)
2781 continue;
2782
2783 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $data['id']);
2784
2785 if (in_array($data['type'], ['text'], true)) {
2786 $meta_query_args[] = [
2787 'key' => $meta_key,
2788 'value' => $query,
2789 'compare' => 'LIKE',
2790 ];
2791 }
2792
2793 if (in_array($data['type'], ['reference'], true)) {
2794 $matched_post_ids = self::get_matched_post_ids_recursive($data['ref_collection'], $query);
2795
2796 if (!empty($matched_post_ids)) {
2797 $ids = implode(',', array_map('intval', $matched_post_ids));
2798 $reference_where_sql .= $wpdb->prepare(
2799 " OR {$wpdb->posts}.ID IN (
2800 SELECT post_id
2801 FROM {$wpdb->prefix}kirki_cm_reference
2802 WHERE field_meta_key = %s
2803 AND ref_post_id IN ($ids)
2804 )",
2805 $meta_key
2806 );
2807 }
2808 }
2809 }
2810
2811 if (count($meta_query_args) > 1) {
2812 $args['meta_query'] = $meta_query_args;
2813 }
2814
2815 // Store filter parameters for the callback
2816 self::$posts_where_filter_params = [
2817 'query' => $query,
2818 'reference_where_sql' => $reference_where_sql,
2819 'post_parent' => $post_parent,
2820 ];
2821
2822 add_filter('posts_where', [__CLASS__, 'posts_where_filter_callback']);
2823 }
2824
2825 public static function get_matched_post_ids_recursive($post_parent, $query, $depth = 0, $max_depth = 5)
2826 {
2827 global $wpdb;
2828
2829 if ($depth > $max_depth) {
2830 return [];
2831 }
2832
2833 $post_type = 'kirki_cm_' . $post_parent;
2834
2835 $matched_post_ids = $wpdb->get_col(
2836 $wpdb->prepare(
2837 "SELECT ID FROM {$wpdb->posts}
2838 WHERE post_title LIKE %s
2839 AND post_status = 'publish'
2840 AND post_type = %s",
2841 '%' . $wpdb->esc_like($query) . '%',
2842 $post_type
2843 )
2844 );
2845
2846 $ref_custom_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2847 $meta_conditions = [];
2848
2849 foreach ($ref_custom_fields as $ref_field) {
2850 if (!$ref_field)
2851 continue;
2852
2853 if (in_array($ref_field['type'], ['text'], true)) {
2854 $ref_meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $ref_field['id']);
2855 $meta_conditions[] = $wpdb->prepare(
2856 "(meta_key = %s AND meta_value LIKE %s)",
2857 $ref_meta_key,
2858 "%{$search}%"
2859 );
2860 }
2861 }
2862
2863 if (!empty($meta_conditions)) {
2864 $where_meta = implode(' OR ', $meta_conditions);
2865 $meta_post_ids = $wpdb->get_col("SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE {$where_meta}"); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2866 $matched_post_ids = array_merge($matched_post_ids, $meta_post_ids);
2867 }
2868
2869 foreach ($ref_custom_fields as $ref_field) {
2870 if (!$ref_field || !in_array($ref_field['type'], ['reference'], true)) {
2871 continue;
2872 }
2873
2874 $ref_post_parent = $ref_field['ref_collection'];
2875 $nested_matched_ids = self::get_matched_post_ids_recursive($ref_post_parent, $query, $depth + 1, $max_depth);
2876
2877 if (!empty($nested_matched_ids)) {
2878 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $ref_field['id']);
2879 $ids = implode(',', array_map('intval', $nested_matched_ids));
2880 $ref_post_ids = $wpdb->get_col($wpdb->prepare("SELECT post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id IN ($ids)", $meta_key)); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
2881 $matched_post_ids = array_merge($matched_post_ids, $ref_post_ids);
2882 }
2883 }
2884
2885 return array_unique(array_map('intval', $matched_post_ids));
2886 }
2887
2888 public static function get_referenced_post_ids($post_parent, $post)
2889 {
2890 global $wpdb;
2891
2892 $allData = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2893 $post_ids = [];
2894
2895 foreach ($allData as $data) {
2896 if ($data && in_array($data['type'], ['reference', 'multi-reference'], true)) {
2897 $meta_key = 'kirki_cm_field_' . $post_parent . '_' . $data['id'];
2898
2899 $results = $wpdb->get_results(
2900 $wpdb->prepare(
2901 "SELECT ref_post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND post_id = %d",
2902 $meta_key,
2903 $post->ID
2904 ),
2905 ARRAY_A
2906 );
2907
2908 foreach ($results as $id) {
2909 $related_posts = $wpdb->get_results(
2910 $wpdb->prepare(
2911 "SELECT post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id = %d",
2912 $meta_key,
2913 (int) $id['ref_post_id']
2914 ),
2915 ARRAY_A
2916 );
2917
2918 foreach ($related_posts as $related) {
2919 $related_id = (int) $related['post_id'];
2920
2921 if ($related_id !== (int) $post->ID) {
2922 $post_ids[] = $related_id;
2923 }
2924 }
2925
2926
2927 }
2928 }
2929 }
2930 $post_ids = array_values(array_unique($post_ids));
2931
2932 return !empty($post_ids) ? $post_ids : [0];
2933 }
2934
2935 public static function get_terms($params)
2936 {
2937 $terms_array = [];
2938 $current_page = isset($params['current_page']) ? (int) $params['current_page'] : 1;
2939 $item_per_page = isset($params['item_per_page']) ? (int) $params['item_per_page'] : 3;
2940 $offset = isset($params['offset']) ? (int) $params['offset'] : 0;
2941
2942 if (!empty($params['inherit'])) {
2943 $terms_array = get_the_terms($params['post_parent'], $params['taxonomy']);
2944 if (is_array($terms_array)) {
2945 // Convert WP_Term objects to arrays only if needed
2946 $terms_array = array_map(function ($term) {
2947 return is_object($term) && method_exists($term, 'to_array')
2948 ? $term->to_array()
2949 : (array) $term;
2950 }, $terms_array);
2951
2952 $calculated_offset = (($current_page - 1) * $item_per_page) + $offset;
2953 $terms_array = array_slice($terms_array, $calculated_offset, $item_per_page);
2954 } else {
2955 $terms_array = [];
2956 }
2957 } else {
2958 $params['offset'] = (($current_page - 1) * $item_per_page) + $offset;
2959 $params['number'] = $item_per_page;
2960
2961 $terms_array = get_terms($params);
2962
2963 if (is_array($terms_array)) {
2964 foreach ($terms_array as &$item) {
2965 if (is_object($item) && method_exists($item, 'to_array')) {
2966 $item = $item->to_array();
2967 } elseif (is_object($item)) {
2968 $item = (array) $item;
2969 }
2970 }
2971 } else {
2972 $terms_array = [];
2973 }
2974 }
2975
2976 // Count total terms
2977 $total_terms = 0;
2978 if (!empty($params['inherit'])) {
2979 $t = get_the_terms($params['post_parent'], $params['taxonomy']);
2980 if (is_array($t)) {
2981 $total_terms = count($t);
2982 } else {
2983 $total_terms = wp_count_terms(['taxonomy' => $params['taxonomy']]);
2984 }
2985 } else {
2986 $total_terms = wp_count_terms(['taxonomy' => $params['taxonomy']]);
2987 }
2988
2989 $total_terms = is_numeric($total_terms) ? (int) $total_terms : 0;
2990 $total_pages = ($item_per_page > 0) ? ceil($total_terms / $item_per_page) : 1;
2991 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
2992 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
2993
2994 return [
2995 'data' => $terms_array,
2996 'pagination' => [
2997 'per_page' => $item_per_page,
2998 'current_page' => $current_page,
2999 'total_pages' => $total_pages,
3000 'total_count' => $total_terms,
3001 'previous' => $prev_page,
3002 'next' => $next_page,
3003 ],
3004 ];
3005 }
3006
3007 public static function buildTaxonomyForRelatedPosts(\WP_Post $post)
3008 {
3009 $taxonomies = get_object_taxonomies($post->post_type);
3010 $taxQuery = [
3011 'relation' => 'OR',
3012 ];
3013
3014 foreach ($taxonomies as $taxonomy) {
3015 $taxQuery[] = [
3016 'taxonomy' => $taxonomy,
3017 'field' => 'slug',
3018 'terms' => array_filter(wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'slugs']), function ($termSlug) {
3019 return strtolower($termSlug) !== 'uncategorized';
3020 }),
3021 ];
3022 }
3023
3024
3025 return $taxQuery;
3026 }
3027
3028
3029 /**
3030 * Get dynamic collectiond data
3031 *
3032 * @param object $params query object.
3033 *
3034 * @return array post array
3035 */
3036 public static function get_comments($params)
3037 {
3038 $parent = (int) ($params['parent'] ?? 0);
3039 $post_id = (int) ($params['post_id'] ?? 0);
3040 $type = ($params['type'] ?? 'comment');
3041 $sorting = isset($params['sorting']) ? $params['sorting'] : null;
3042 $filters = isset($params['filters']) ? $params['filters'] : null;
3043 // add new
3044 $current_page = isset($params['current_page']) ? $params['current_page'] : 1;
3045 $item_per_page = isset($params['item_per_page']) ? $params['item_per_page'] : 3;
3046 $offset = isset($params['offset']) ? $params['offset'] : 0;
3047
3048 // Calculate the offset
3049 $offset_cal = ($current_page - 1) * $item_per_page + $offset;
3050
3051 $args = array(
3052 'parent' => $parent,
3053 'post_id' => $post_id,
3054 'type' => $type,
3055 'status' => 'approve',
3056 'number' => $item_per_page,
3057 'paged' => $current_page,
3058 'offset' => $offset_cal,
3059 'count' => false
3060 );
3061
3062 if (isset($filters) && is_array($filters)) {
3063 foreach ($filters as $filter_item) {
3064 $field_name = isset($filter_item['id']) ? $filter_item['id'] : '';
3065
3066 if (!$field_name) {
3067 continue;
3068 }
3069 switch ($field_name) {
3070 case 'comment_date':
3071 case 'comment_date_gmt': {
3072 /**
3073 * $filter_item['items'] max contain one array.
3074 * in array may contain start-date, end-date
3075 * Like: [{"start-date": "2020-01-01","end-date": "2020-01-02"}]
3076 */
3077 if (isset($filter_item['items'], $filter_item['items'][0])) {
3078 $items = $filter_item['items'];
3079 $item = $items[0]; // Get first item in array.
3080
3081 $date_query = array('column' => $field_name);
3082 $date_query['inclusive'] = true;
3083
3084 if (!empty($item['start-date'])) {
3085 $date_query['after'] = $item['start-date'];
3086 }
3087
3088 if (!empty($item['end-date'])) {
3089 $date_query['before'] = $item['end-date'];
3090 }
3091
3092 $args['date_query'] = $date_query;
3093 }
3094
3095 break;
3096 }
3097
3098 case 'comment_author': {
3099 $items = $filter_item['items']; // $items['items'] max contain one array.
3100
3101 foreach ($items as $item) {
3102 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
3103 if ($item['condition'] === 'in') {
3104 $args['author__in'] = $item['values'];
3105 }
3106
3107 if ($item['condition'] === 'not-in') {
3108 $args['author__not_in'] = $item['values'];
3109 }
3110 }
3111 }
3112
3113 break;
3114 }
3115
3116 case 'comment_approved': {
3117 $items = $filter_item['items']; // $items['items'] max contain one array.
3118
3119 foreach ($items as $item) {
3120 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
3121 if ($item['condition'] === 'in') {
3122 $args['status'] = $item['values'];
3123 }
3124 }
3125 }
3126 }
3127 }
3128 }
3129 }
3130
3131 if (isset($sorting)) {
3132 // Set the sort order (ASC/DESC)
3133 if (isset($sorting['order'])) {
3134 $args['order'] = $sorting['order'];
3135 }
3136
3137 if (isset($sorting['orderby']) && !empty($sorting['orderby'])) {
3138 $args['orderby'] = $sorting['orderby'];
3139 }
3140 }
3141
3142 $comments = get_comments($args);
3143 unset($args['number']);
3144 unset($args['paged']);
3145
3146 if (is_array($comments)) {
3147 foreach ($comments as &$comment) {
3148 $comment = (object) (array) $comment;
3149
3150 $author_posts_page_link = $comment->comment_author_url;
3151
3152 if (!$author_posts_page_link) {
3153 $author_posts_page_link = \get_author_posts_url($comment->user_id);
3154 }
3155
3156 $comment->author_profile_picture = array(
3157 'src' => get_avatar_url($comment->user_id)
3158 );
3159 $comment->author_posts_page_link = $author_posts_page_link;
3160 }
3161 }
3162
3163 // Get total comments count
3164 $total_comments = get_comments(array_merge($args, array('count' => true)));
3165 $total_comments = $total_comments - $offset;
3166
3167 // Calculate total pages
3168 $total_pages = ceil($total_comments / $item_per_page);
3169
3170 // Calculate previous and next pages
3171 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
3172 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
3173
3174 // return $comments;
3175 return array(
3176 'data' => $comments, // Raw comments data
3177 'pagination' => array(
3178 'per_page' => $item_per_page,
3179 'current_page' => $current_page,
3180 'total_pages' => $total_pages,
3181 'total_count' => $total_comments,
3182 'previous' => $prev_page,
3183 'next' => $next_page,
3184 ),
3185 );
3186 }
3187
3188
3189 /**
3190 * Remove all default assets
3191 *
3192 * @return void
3193 */
3194 public static function remove_wp_assets()
3195 {
3196 /*
3197 // Remove all WordPress actions
3198 // remove_all_actions('wp_head');
3199 // remove_all_actions('wp_print_styles');
3200 // remove_all_actions('wp_print_head_scripts');
3201 // remove_all_actions('wp_footer');
3202
3203 // // Handle `wp_head`
3204 // add_action('wp_head', 'wp_enqueue_scripts', 1);
3205 // add_action('wp_head', 'wp_print_styles', 8);
3206 // add_action('wp_head', 'wp_print_head_scripts', 9);
3207 // add_action('wp_head', 'wp_site_icon');
3208
3209 // // Handle `wp_footer`
3210 // add_action('wp_footer', 'wp_print_footer_scripts', 20);
3211
3212 // // Handle `wp_enqueue_scripts`
3213 // remove_all_actions('wp_enqueue_scripts');
3214
3215 // // Also remove all scripts hooked into after_wp_tiny_mce.
3216 // remove_all_actions('after_wp_tiny_mce');
3217 */
3218 // remove admin-bar.
3219 add_filter('show_admin_bar', '__return_false', PHP_INT_MAX);
3220 }
3221
3222 /**
3223 * Get server protocol
3224 * currently not in use
3225 *
3226 * @return string protocol name.
3227 */
3228 public static function get_protocol()
3229 {
3230 $protocol = isset($_SERVER['HTTPS']) ? 'https' : 'http';
3231 return $protocol;
3232 }
3233
3234 /**
3235 * Check if the current user has specific role ($role)
3236 *
3237 * @param string $role The role to check.
3238 * @return boolean
3239 */
3240 public static function user_is($role)
3241 {
3242 $user = wp_get_current_user();
3243 $roles = $user->roles;
3244
3245 return is_array($roles) && count($roles) && in_array($role, $roles, true) ? true : false;
3246 }
3247
3248 /**
3249 * Check if the user has access to edit/create specific/all post
3250 *
3251 * @param int $post_id post id.
3252 * @return boolean
3253 *
3254 * @deprecated
3255 * @see Kirki\App\Wordpress\User::has_edit_access()
3256 */
3257 public static function user_has_post_edit_access()
3258 {
3259 return self::has_access(
3260 array(
3261 KIRKI_ACCESS_LEVELS['FULL_ACCESS'],
3262 KIRKI_ACCESS_LEVELS['CONTENT_ACCESS'],
3263 )
3264 );
3265 }
3266
3267 /**
3268 * Check if the user has access to editor
3269 *
3270 * @return boolean
3271 */
3272 public static function user_has_editor_access()
3273 {
3274 if (isset($_GET['editor-preview-token'])) {
3275 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3276 $editor_preview_token = self::sanitize_text(isset($_GET['editor-preview-token']) ? $_GET['editor-preview-token'] : '');
3277 $is_valid_token = self::is_post_editor_preview_token_valid($editor_preview_token);
3278
3279 // Token rides in the URL for this one entry request (a clicked link
3280 // can't set headers) — stop it leaking onward via the Referer header
3281 // on any request this page makes to third-party resources.
3282 if ($is_valid_token && !headers_sent()) {
3283 header('Referrer-Policy: no-referrer', false);
3284 }
3285
3286 return $is_valid_token;
3287 }
3288 return self::has_access(
3289 array(
3290 KIRKI_ACCESS_LEVELS['FULL_ACCESS'],
3291 KIRKI_ACCESS_LEVELS['CONTENT_ACCESS'],
3292 KIRKI_ACCESS_LEVELS['VIEW_ACCESS'],
3293 )
3294 );
3295 }
3296
3297 public static function getallheaders()
3298 {
3299 $headers = [];
3300
3301 foreach ($_SERVER as $name => $value) {
3302 if (strpos($name, 'HTTP_') === 0) {
3303 $key = substr($name, 5);
3304 } elseif (in_array($name, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'])) {
3305 $key = $name;
3306 } else {
3307 continue;
3308 }
3309
3310 // Convert HEADER_NAME → Header-Name
3311 $key = str_replace('_', ' ', strtolower($key));
3312 $key = ucwords($key);
3313 $key = str_replace(' ', '-', $key);
3314
3315 $headers[$key] = $value;
3316 }
3317
3318 return $headers;
3319 }
3320
3321 /**
3322 * Check if the request is from the editor preview.
3323 *
3324 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3325 * @see \Kirki\App\Supports\EditorPreview::has_valid_token()
3326 * @return bool
3327 */
3328 public static function is_api_call_from_editor_preview()
3329 {
3330 // Check the Editor-Preview-Token header
3331 $headers = self::getallheaders();
3332 $editor_preview_token = isset($headers['Editor-Preview-Token']) ? $headers['Editor-Preview-Token'] : null;
3333 if ($editor_preview_token && HelperFunctions::is_post_editor_preview_token_valid($editor_preview_token)) {
3334 return true;
3335 }
3336 return false;
3337 }
3338
3339 /**
3340 * Check if the Editor-Preview-Token header is valid
3341 *
3342 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3343 * @see Kirki\App\Supports\EditorPreview::has_valid_token()
3344 * @return bool
3345 */
3346 public static function is_api_header_post_editor_preview_token_valid()
3347 {
3348 // Check the Editor-Preview-Token header
3349 $headers = self::getallheaders();
3350 $editor_preview_token = isset($headers['Editor-Preview-Token']) ? $headers['Editor-Preview-Token'] : null;
3351 if (HelperFunctions::is_post_editor_preview_token_valid($editor_preview_token)) {
3352 return true;
3353 }
3354 return false;
3355 }
3356
3357
3358 /**
3359 * Check if the Editor-Preview-Token header is valid
3360 *
3361 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3362 * @see Kirki\App\Supports\EditorPreview::has_valid_token()
3363 * @return bool
3364 */
3365 public static function is_post_editor_preview_token_valid($token)
3366 {
3367 $status = HelperFunctions::get_global_data_using_key('kirki_editor_read_only_access_status');
3368 if ($status) {
3369 $kirki_editor_read_only_access_token = HelperFunctions::get_global_data_using_key('kirki_editor_read_only_access_token');
3370 if ($kirki_editor_read_only_access_token && hash_equals((string) $kirki_editor_read_only_access_token, (string) $token)) {
3371 return true;
3372 }
3373 }
3374 return false;
3375 }
3376
3377 /**
3378 * Check if the current user has specific access
3379 *
3380 * @deprecated Use Kirki\App\Wordpress\User::has_access
3381 * @see Kirki\App\Wordpress\User
3382 * @param string|string[] $access_level The access level to check access.
3383 *
3384 */
3385 public static function has_access($access_level)
3386 {
3387 if (!function_exists('wp_get_current_user')) {
3388 return false;
3389 }
3390
3391 $user = wp_get_current_user();
3392 $roles = $user->roles;
3393 $has_access = false;
3394
3395 if (is_array($access_level)) {
3396 foreach ($roles as $role) {
3397 $access = get_option('kirki_' . $role);
3398 if (!empty($access) && in_array($access, $access_level, true)) {
3399 $has_access = true;
3400 break;
3401 }
3402 }
3403 } elseif (is_string($access_level)) {
3404 foreach ($roles as $role) {
3405 $access = get_option('kirki_' . $role);
3406 if (!empty($access) && $access === $access_level) {
3407 $has_access = true;
3408 break;
3409 }
3410 }
3411 }
3412
3413 return $has_access;
3414 }
3415
3416 /**
3417 * This method will collect license info from kirki.com
3418 *
3419 * @param string $license_key user license key.
3420 * @return array license info.
3421 */
3422 public static function get_my_license_info($license_key)
3423 {
3424 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3425 $info = self::http_get(KIRKI_CORE_PLUGIN_URL . '/?license_key=' . $license_key . '&host=' . rawurlencode(self::sanitize_text(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null)));
3426 $info = json_decode($info, true);
3427 if ($info && isset($info['success'])) {
3428 return $info['data'];
3429 } else {
3430 return array('key' => $license_key);
3431 }
3432 }
3433
3434 /**
3435 * HTTP get
3436 *
3437 * @param string $url api endpoint url.
3438 * @return string|bool response.
3439 *
3440 * @deprecated
3441 * @see \Kirki\Framework\Supports\Facades\Http::get()
3442 */
3443 public static function http_get($url, $args = array())
3444 {
3445 try {
3446 $response = wp_remote_get($url, $args);
3447
3448 if ((!is_wp_error($response)) && (200 === wp_remote_retrieve_response_code($response))) {
3449 $responseBody = $response['body'];
3450
3451 return $responseBody;
3452 }
3453
3454 return false;
3455 } catch (\Exception $ex) {
3456 return false;
3457 }
3458 }
3459
3460 /**
3461 * HTTP post
3462 *
3463 * @param string $url api endpoint url.
3464 * @param array $options options.
3465 * @return array|WP_Error response.
3466 *
3467 * @deprecated
3468 * @see \Kirki\Framework\Supports\Facades\Http::post()
3469 */
3470 public static function http_post($url, $options)
3471 {
3472 $res = wp_remote_post($url, $options);
3473 return $res;
3474 }
3475
3476 /**
3477 * Text domain load hooks
3478 *
3479 * @param string $handle kirki handle.
3480 * @return void
3481 */
3482 public static function load_script_text_domain($handle)
3483 {
3484 wp_set_script_translations($handle, 'kirki', KIRKI_PLUGIN_PATH . 'languages/');
3485 }
3486
3487 /**
3488 * Delete kirki related meta if a post is deleted.
3489 *
3490 * @param int $post_id post id.
3491 * @return void
3492 */
3493 public static function delete_post_with_meta_key($post_id)
3494 {
3495 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS);
3496 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS . '_random');
3497 delete_post_meta($post_id, 'kirki');
3498 delete_post_meta($post_id, KIRKI_META_NAME_FOR_POST_EDITOR_MODE);
3499 delete_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY);
3500 delete_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY . '_random');
3501 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_FONT_LIST);
3502 }
3503 /**
3504 * Get the query string for the media type
3505 *
3506 * @param string $type media type.
3507 * @return string The query string.
3508 * @example HelperFunctions::get_media_type_query_string('image') => 'image/jpeg, image/png, image/gif'
3509 */
3510 public static function get_media_type_query_string($type)
3511 {
3512 return implode(
3513 ', ',
3514 array_map(
3515 function ($v) {
3516 return "'" . $v . "'";
3517 },
3518 KIRKI_SUPPORTED_MEDIA_TYPES[$type]
3519 )
3520 );
3521 }
3522
3523 /**
3524 * This is for component configuration/object javascript variable.
3525 *
3526 * @return string script tag
3527 */
3528 public static function get_empty_variables()
3529 {
3530 $s = "<script id='kirki-elements-property-empty-vars'>";
3531 $s .= 'var ' . 'kirkiSliders = [], ' . 'kirkiMaps = [], ' . 'kirkiLotties = [], ' . 'kirkiPopups = [], ' . 'kirkiLightboxes = [], ' . 'kirkiReCaptchas = [], ' . 'kirkiVideos = [], ' . 'kirkiTabs = [], ' . 'kirkiInteractions = [], ' . 'kirkiCollections = [], ' . 'kirkiDropdown = [], ' . 'kirkiForms = [];';
3532 $s .= '</script>';
3533 return $s;
3534 }
3535
3536 /**
3537 * Check kirki and kirki pro is active or not
3538 *
3539 * @param string $plugin_main_file plugin main PHP file name.
3540 * @return boolean
3541 */
3542 public static function is_plugin_activate($plugin_main_file)
3543 {
3544 if (in_array($plugin_main_file, apply_filters('active_plugins', get_option('active_plugins')), true)) {
3545 // plugin is activated.
3546 return true;
3547 }
3548 return false;
3549 }
3550
3551 /**
3552 * This function will verify nonce
3553 * ACT like API calls auth middleware
3554 *
3555 * @param string $action ajax action name.
3556 *
3557 * @return void
3558 */
3559 public static function verify_nonce($action = -1)
3560 {
3561 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3562 $headers = static::getallheaders();
3563 $header_nonce = isset($headers['X-Wp-Nonce']) ? static::sanitize_text($headers['X-Wp-Nonce']) : '';
3564 $nonce = $header_nonce ? $header_nonce : static::sanitize_text(isset($_GET['_wpnonce']) ? $_GET['_wpnonce'] : '');
3565
3566 if (!wp_verify_nonce($nonce, $action)) {
3567 wp_send_json_error('Not authorized');
3568 exit;
3569 }
3570 }
3571
3572 /**
3573 * Unslash and sanitize text
3574 *
3575 * @param string $v text.
3576 * @return string sanitized text.
3577 */
3578 public static function sanitize_text($v)
3579 {
3580 return sanitize_text_field(wp_unslash($v));
3581 }
3582
3583 /**
3584 * Get current WordPress session ID.
3585 * This method generates a unique session ID if none exists.
3586 *
3587 * @deprecated
3588 * @see \Kirki\App\Supports\Session::get_session_id()
3589 *
3590 * @return string Session ID.
3591 */
3592 public static function get_session_id()
3593 {
3594
3595 // First, check if a session ID is already stored in the static variable.
3596 if (self::$global_session_id) {
3597 return self::$global_session_id;
3598 }
3599
3600 // Check if a session ID exists in a cookie.
3601 if (isset($_COOKIE['kirki_session_id'])) {
3602 self::$global_session_id = sanitize_text_field($_COOKIE['kirki_session_id']);
3603 return self::$global_session_id;
3604 }
3605
3606 // Generate a new session ID.
3607 self::$global_session_id = wp_generate_uuid4();
3608 // Set the session ID in a cookie.
3609 setcookie('kirki_session_id', self::$global_session_id, time() + (DAY_IN_SECONDS * 7), COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
3610
3611 return self::$global_session_id;
3612 }
3613
3614 /**
3615 * Get session data by key using WordPress transients.
3616 *
3617 * @deprecated
3618 * @see \Kirki\App\Supports\Session::get()
3619 *
3620 * @param string $key The key of the session data to retrieve.
3621 * @return mixed|null The session data if found, null otherwise.
3622 */
3623 public static function get_session_data($key)
3624 {
3625 // Get the current session ID.
3626 $session_id = self::get_session_id();
3627
3628 // Retrieve the session data.
3629 $session_data = get_transient('kirki_session_' . $session_id);
3630
3631 if (isset($session_data[$key])) {
3632 return $session_data[$key];
3633 }
3634
3635 return null;
3636 }
3637
3638 /**
3639 * Add or update session data using WordPress transients.
3640 *
3641 * @deprecated
3642 * @see \Kirki\App\Supports\Session::put()
3643 *
3644 * @param string $key The key of the session data.
3645 * @param mixed $value The value of the session data.
3646 * @return void
3647 */
3648 public static function set_session_data($key, $value)
3649 {
3650 // Get the current session ID.
3651 $session_id = self::get_session_id();
3652
3653 // Retrieve existing session data.
3654 $session_data = get_transient('kirki_session_' . $session_id) ?: array();
3655
3656 // Update the session data.
3657 $session_data[$key] = $value;
3658
3659 // Save the updated session data with a 24-hour expiration time.
3660 set_transient('kirki_session_' . $session_id, $session_data, DAY_IN_SECONDS);
3661 }
3662
3663 /**
3664 * Delete session data by key using WordPress transients.
3665 *
3666 * @deprecated
3667 * @see \Kirki\App\Supports\Session::forget()
3668 *
3669 * @param string $key The key of the session data to delete.
3670 * @return void
3671 */
3672 public static function delete_session_data($key)
3673 {
3674 // Get the current session ID.
3675 $session_id = self::get_session_id();
3676
3677 // Retrieve existing session data.
3678 $session_data = get_transient('kirki_session_' . $session_id);
3679
3680 if (isset($session_data[$key])) {
3681 unset($session_data[$key]);
3682
3683 // Save the updated session data or delete the transient if empty.
3684 if (!empty($session_data)) {
3685 set_transient('kirki_session_' . $session_id, $session_data, DAY_IN_SECONDS);
3686 } else {
3687 delete_transient('kirki_session_' . $session_id);
3688 }
3689 }
3690 }
3691
3692 /**
3693 * Escape a post meta value for safe output.
3694 *
3695 * The front-end content pipeline (TheFrontend::replace_content) applies a
3696 * single html_entity_decode() pass after core has processed shortcodes,
3697 * which would undo a plain esc_html(). Re-encoding the ampersands (with
3698 * double_encode enabled) yields a single-escaped value in the final output
3699 * while still neutralizing markup authored by low-privileged users.
3700 *
3701 * @param mixed $value The raw post meta value.
3702 * @return string Escaped value.
3703 */
3704 public static function escape_post_meta_value($value)
3705 {
3706 if (!is_scalar($value)) {
3707 return '';
3708 }
3709
3710 return htmlspecialchars(esc_html((string) $value), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', true);
3711 }
3712
3713 /**
3714 * Is Pro user checking function.
3715 *
3716 * @return bool
3717 */
3718 public static function is_pro_user()
3719 {
3720 $common_data = WpAdmin::get_common_data(true);
3721
3722 $bool = isset($common_data['license_key']['valid']) && boolval($common_data['license_key']['valid']) === true;
3723
3724 return $bool;
3725 }
3726
3727
3728 /**
3729 * Get all view port lists
3730 *
3731 * @return string viewports list variable in script markup.
3732 */
3733 public static function get_view_port_lists()
3734 {
3735 $s = '';
3736 $list = UserData::get_view_port_list();
3737 if ($list) {
3738 $s .= "<script id='kirki-viewport-lists'>";
3739 $s .= 'var ' . 'kirkiViewports = ' . wp_json_encode($list) . ';';
3740 $s .= '</script>';
3741 }
3742 return $s;
3743 }
3744
3745 /**
3746 * Get all css variables
3747 *
3748 * @return string variables in script markup.
3749 */
3750 public static function get_kirki_css_variables_data()
3751 {
3752 $s = '';
3753 $variableData = UserData::get_kirki_variable_data();
3754 if ($variableData) {
3755 $s .= "<script id='kirki-variable-lists'>";
3756 $s .= 'var ' . 'kirkiCSSVariable = ' . wp_json_encode($variableData) . ';';
3757 $s .= '</script>';
3758 }
3759 return $s;
3760 }
3761
3762 /**
3763 * Get smooth scroll script
3764 *
3765 * @return string script markup.
3766 */
3767 public static function get_smooth_scroll_script()
3768 {
3769 $common_data = WpAdmin::get_common_data(true);
3770 $smooth_scroll_enabled = isset($common_data['smooth_scroll'], $common_data['smooth_scroll']['enabled']) ? $common_data['smooth_scroll']['enabled'] : false;
3771
3772 $smooth_scroll_value = isset($common_data['smooth_scroll'], $common_data['smooth_scroll']['value']) ? $common_data['smooth_scroll']['value'] : 1;
3773
3774 /**
3775 * User value 1 to 200
3776 *
3777 * Min duration 1s
3778 * Max duration 12s
3779 *
3780 */
3781 $duration = ceil(($smooth_scroll_value / 200) * 12);
3782
3783 $s = '';
3784
3785 if ($smooth_scroll_enabled) {
3786 $s .= "<script id='kirki-smooth-scroll'>";
3787 $s .= "
3788 window.document.addEventListener('DOMContentLoaded', function () {
3789 if (typeof KirkiSmoothScroll !== 'undefined') {
3790 const params = {
3791 autoRaf: true,
3792 anchors: true,
3793 allowNestedScroll: true,
3794 duration: $duration,
3795 };
3796
3797 const kirkiSmoothScroll = new KirkiSmoothScroll(params);
3798
3799 kirkiSmoothScroll.on('scroll');
3800 }
3801 });
3802 ";
3803 $s .= '</script>';
3804 }
3805
3806 return $s;
3807 }
3808
3809 /**
3810 * Format the date with date format
3811 *
3812 * @return string
3813 */
3814 public static function format_date($date, $format)
3815 {
3816 if ($date && $format) {
3817 $date_formats_arr = [
3818 'DD/MM/YYYY' => 'd/m/Y',
3819 'DD-MM-YYYY' => 'd-m-Y',
3820 'DD.MM.YYYY' => 'd.m.Y',
3821 'MM/DD/YYYY' => 'm/d/Y',
3822 'MM-DD-YYYY' => 'm-d-Y',
3823 'MM.DD.YYYY' => 'm.d.Y',
3824 'MMMM DD, YYYY' => 'F j, Y',
3825 'MMM DD, YYYY' => 'M j, Y',
3826 'YYYY-MM-DD' => 'Y-m-d',
3827 'YYYY/MM/DD' => 'Y/m/d',
3828 'YY.MM.DD' => 'y.m.d',
3829 'YY/MM/DD' => 'y/m/d',
3830 'YY-MM-DD' => 'y-m-d',
3831 ];
3832
3833 $timestamp = strtotime($date);
3834 if ($timestamp === false) {
3835 return $date; // fallback (avoid fatal)
3836 }
3837
3838 $datetime = (new \DateTime())->setTimestamp($timestamp);
3839 return $datetime->format($date_formats_arr[$format] ?? $format);
3840 }
3841
3842 return $date;
3843 }
3844
3845 /**
3846 * Format the time with time format
3847 *
3848 * @return string
3849 */
3850 public static function convert_time_format($timeString, $format = 'h:i a')
3851 {
3852 $dateTime = null;
3853
3854 try {
3855 $dateTime = new \DateTime($timeString);
3856 if ($dateTime && $timeString) {
3857 return $dateTime->format($format);
3858 }
3859 } catch (\Exception $e) {
3860 // if timeString is an invalid time format
3861 $parts = explode(' ', $timeString);
3862 if (count($parts) > 1) {
3863 // Remove the last part (am/pm)
3864 $time_value = $parts[0];
3865 try {
3866 $dateTime = new \DateTime($time_value);
3867 if ($dateTime && $timeString) {
3868 return $dateTime->format($format);
3869 }
3870 } catch (\Exception $e2) {
3871 return $timeString;
3872 }
3873 }
3874 return $timeString;
3875 }
3876
3877 return $timeString;
3878 }
3879
3880 /**
3881 * Get single post if has a kirki type post
3882 *
3883 * @return object|bool
3884 */
3885 public static function get_last_edited_kirki_editor_type_page()
3886 {
3887 $args = array(
3888 'post_type' => 'page', // Change to 'post' if you want to search for posts
3889 'post_status' => ['publish', 'draft'],
3890 'numberposts' => 1, // Number of results to retrieve (change as needed)
3891 'meta_key' => 'kirki_editor_mode',
3892 'meta_value' => 'kirki',
3893 'orderby' => 'modified', // Order by post date
3894 'order' => 'DESC', // Sort in descending order
3895 );
3896
3897 $pages = get_posts($args);
3898 if (count($pages) > 0) {
3899 return $pages[0];
3900 }
3901 return false;
3902 }
3903
3904 public static function get_kirki_version_from_db()
3905 {
3906 $version = wp_cache_get('kirki_version', 'kirki');
3907
3908 if (false === $version) {
3909 $version = get_option('kirki_version', '');
3910
3911 if (!empty($version)) {
3912 wp_cache_set('kirki_version', $version, 'kirki');
3913 }
3914 }
3915
3916 return $version;
3917 }
3918
3919 public static function set_kirki_version_in_db()
3920 {
3921 $version = self::get_kirki_version_from_db();
3922
3923 if ($version && version_compare($version, KIRKI_VERSION, '==')) {
3924 // No need to update the version if it's already equal to the current version.
3925 return;
3926 }
3927
3928 update_option('kirki_version', KIRKI_VERSION, false);
3929 wp_cache_set('kirki_version', KIRKI_VERSION, 'kirki');
3930 }
3931
3932 public static function accepted_file_types_by_plugin($accepted_media_types = KIRKI_SUPPORTED_MEDIA_TYPES)
3933 {
3934 $result = array();
3935
3936 foreach ($accepted_media_types as $value) {
3937 if (is_array($value)) {
3938 $result = array_merge($result, self::accepted_file_types_by_plugin($value));
3939 } else {
3940 $result[] = $value;
3941 }
3942 }
3943
3944 return $result;
3945 }
3946
3947 public static function content_manager_link_filter($dynamic_content = array(), $href = "#")
3948 {
3949 $current_post = get_post(self::get_post_id_if_possible_from_url());
3950
3951 if ($current_post->post_type === KIRKI_CONTENT_MANAGER_PREFIX) {
3952 $fields = ContentManagerHelper::get_post_type_custom_field_keys($current_post->post_parent);
3953
3954 if (isset($fields[$dynamic_content['value']]) && is_array($fields[$dynamic_content['value']])) {
3955 if ('email' === $fields[$dynamic_content['value']]['type']) {
3956 $href = "mailto:$href";
3957 } else if ('phone' === $fields[$dynamic_content['value']]['type']) {
3958 $href = "tel:$href";
3959 }
3960 }
3961 }
3962
3963 return $href;
3964 }
3965
3966 public static function check_string_has_this_tags($string, $tag)
3967 {
3968 // Check if the string contains either a <p> tag or an <h1> tag
3969 return preg_match("/<" . $tag . "[^>]*>/i", $string) === 1;
3970 }
3971
3972 /**
3973 * @deprecated
3974 * @see Kirki\App\Managers\GlobalDataManager::get_post_id()
3975 */
3976 private static function get_global_data_post_id()
3977 {
3978 $post_id = get_option('KIRKI_GLOBAL_DATA_POST_TYPE_ID', get_option('DROIP_GLOBAL_DATA_POST_TYPE_ID', false));
3979 if ($post_id) {
3980 return $post_id;
3981 } else {
3982 //this block will run only once
3983 $posts = get_posts(array(
3984 'post_type' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3985 'numberposts' => 1,
3986 ));
3987 if ($posts) {
3988 $post_id = $posts[0]->ID;
3989 } else {
3990 //create new post
3991 $post = array(
3992 'post_title' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3993 'post_type' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3994 'post_status' => 'draft'
3995 );
3996 $post_id = wp_insert_post($post);
3997 }
3998 update_option('KIRKI_GLOBAL_DATA_POST_TYPE_ID', $post_id, true);
3999 }
4000
4001 return $post_id;
4002 }
4003
4004 /**
4005 * Get global data using key
4006 *
4007 * @deprecated
4008 * @see Kirki\App\Managers\GlobalDataManager::get()
4009 */
4010 public static function get_global_data_using_key($key)
4011 {
4012 //first get post using KIRKI_GLOBAL_DATA_POST_TYPE_NAME post_type name. if not found then create new one.
4013 $post_id = self::get_global_data_post_id();
4014 if (metadata_exists('post', $post_id, $key)) {
4015 return get_post_meta($post_id, $key, true);
4016 }
4017
4018 // this block will run only once for a legacy option key.
4019 $value = get_option($key, null);
4020 if (null !== $value) {
4021 update_post_meta($post_id, $key, $value);
4022 delete_option($key);
4023 }
4024
4025 return $value;
4026 }
4027
4028 /**
4029 * Get global data using key
4030 *
4031 * @deprecated
4032 * @see Kirki\App\Managers\GlobalDataManager::update()
4033 */
4034 public static function update_global_data_using_key($key, $value)
4035 {
4036 $post_id = self::get_global_data_post_id();
4037 update_post_meta($post_id, $key, $value);
4038
4039 }
4040
4041 public static function get_template_data_if_current_page_is_kirki_template()
4042 {
4043 $custom_data = get_query_var('kirki_custom_data');
4044 $data = false;
4045 $builder_div = '';
4046 if ($custom_data && isset($custom_data['kirki_template_content'])) {
4047 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : null);
4048 $load_for = HelperFunctions::sanitize_text(isset($_GET['load_for']) ? $_GET['load_for'] : '');
4049
4050 if ($action === KIRKI_EDITOR_ACTION && $load_for === 'kirki-iframe' && !str_contains($custom_data['kirki_template_content'], 'kirki-builder')) {
4051 $template_edit_url = HelperFunctions::get_post_url_arr_from_post_id($custom_data['kirki_template_id'], ['editor_url' => true])['editor_url'];
4052 $builder_div = '<div id="' . 'kirki-builder' . '" template-error="' . $template_edit_url . '"></div>';
4053 }
4054
4055 $data = array(
4056 'content' => $custom_data['kirki_template_content'] . $builder_div,
4057 'template_id' => $custom_data['kirki_template_id']
4058 );
4059 }
4060 return $data;
4061 }
4062
4063 public static function get_custom_data_if_current_page_is_kirki_custom_post()
4064 {
4065 $custom_data = get_query_var('kirki_custom_data');
4066 $data = false;
4067 $builder_div = '';
4068 if ($custom_data && isset($custom_data['kirki_custom_post_content'])) {
4069 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : null);
4070 $load_for = HelperFunctions::sanitize_text(isset($_GET['load_for']) ? $_GET['load_for'] : '');
4071
4072 if ($action === KIRKI_EDITOR_ACTION && $load_for === 'kirki-iframe' && !str_contains($custom_data['kirki_custom_post_content'], 'kirki-builder')) {
4073 $template_edit_url = HelperFunctions::get_post_url_arr_from_post_id($custom_data['kirki_custom_post_id'], ['editor_url' => true])['editor_url'];
4074 $builder_div = '<div id="' . 'kirki-builder' . '" template-error="' . $template_edit_url . '"></div>';
4075 }
4076
4077 $data = array(
4078 'content' => $custom_data['kirki_custom_post_content'] . $builder_div,
4079 'post_id' => $custom_data['kirki_custom_post_id']
4080 );
4081 }
4082 return $data;
4083 }
4084
4085 /**
4086 * Validate slug for a post.
4087 *
4088 * @param int|null $post_id
4089 * @param string $post_type
4090 * @param string $post_name
4091 * @return bool
4092 *
4093 * @deprecated Use Kirki\App\Supports\ContentManager::validate_slug() instead.
4094 * @see Kirki\App\Supports\ContentManager
4095 */
4096 public static function validate_slug($post_id, $post_type, $post_name)
4097 {
4098 global $wpdb;
4099 // Execute the query
4100 $result = $wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d", $post_name, $post_type, $post_id ? $post_id : 0));
4101
4102 // If a post with the same slug exists, return false
4103 if ($result) {
4104 return false;
4105 }
4106
4107 // If no post with the same slug exists, return true
4108 return true;
4109 }
4110
4111 /**
4112 * Summary of find_utility_page_for_this_context
4113 * @param mixed $type
4114 * @param mixed $get_by id | type.
4115 * @return mixed
4116 */
4117 public static function find_utility_page_for_this_context($value = '404', $get_by = 'type')
4118 {
4119 $utility_pages = Page::fetch_list('kirki_utility', true, array('publish'));
4120 if (count($utility_pages) > 0) {
4121 foreach ($utility_pages as $key => $page) {
4122 if ($get_by === 'type') {
4123 if ($page['utility_page_type'] === $value) {
4124 return $page;
4125 }
4126 } else if ($get_by === 'id') {
4127 if ($page['id'] === (int) $value) {
4128 return $page;
4129 }
4130 }
4131 }
4132 }
4133 return false;
4134 }
4135 public static function get_current_page_context()
4136 {
4137 $context = array(); // {id, type}
4138
4139 $obj = get_queried_object();
4140
4141 if (is_404()) {
4142 $context['type'] = '404';
4143 } else if ($obj instanceof WP_Post) {
4144 $context['id'] = $obj->ID;
4145 $context['type'] = 'post';
4146 } else if ($obj instanceof WP_User) {
4147 $context['id'] = $obj->ID;
4148 $context['type'] = 'user';
4149 } elseif ($obj instanceof WP_Term) {
4150 $context['id'] = $obj->term_id;
4151 $context['type'] = 'term';
4152 }
4153 // elseif ($obj instanceof WP_Post_Type) {
4154 // echo 'This is a WP_Post_Type object';
4155 // } elseif (is_null($obj)) {
4156 // echo 'No queried object (null)';
4157 // }
4158 else {
4159 $kirki_utility_page_type = get_query_var('kirki_utility_page_type');
4160 $kirki_utility_page_id = get_query_var('kirki_utility_page_id');
4161 if (!empty($kirki_utility_page_type)) {
4162 if (!self::check_utility_page_visibility_condition($kirki_utility_page_type)) {
4163 $context['type'] = '404';
4164 } else {
4165 $context['type'] = 'kirki_utility';
4166 $context['kirki_utility_page_type'] = $kirki_utility_page_type;
4167 $context['kirki_utility_page_id'] = $kirki_utility_page_id;
4168 }
4169 }
4170 }
4171 return $context;
4172 }
4173
4174
4175 /**
4176 * Get utility page slug using type.
4177 * { type: 'login', title: 'Login' },
4178 * { type: 'sign_up', title: 'Registration' },
4179 * { type: 'forgot_password', title: 'Forgot Password' },
4180 * { type: 'reset_password', title: 'Reset Password' },
4181 * { type: 'retrive_username', title: 'Retrive Username' },
4182 * { type: '404', title: '404' },
4183 *
4184 * @param string $type //utility page type.
4185 * @return string||bool //string or false.
4186 */
4187 public static function get_utility_page_url($type)
4188 {
4189 $utility_pages = Page::fetch_list('kirki_utility', true, array('publish'));
4190 foreach ($utility_pages as $key => $page) {
4191 $utility_page_type = $page['utility_page_type'];
4192
4193 $slug = $page['slug'];
4194 if ($utility_page_type === $type) {
4195 return home_url('/' . $slug);
4196 }
4197 }
4198 return false;
4199 }
4200 public static function check_utility_page_visibility_condition($type)
4201 {
4202 if ($type === 'login' || $type === 'sign_up' || $type === 'forgot_password' || $type === 'reset_password' || $type === 'retrive_username') {
4203 // Check if the user is already logged in
4204 if (is_user_logged_in()) {
4205 return false; // User is logged in, so the page should not be visible
4206 }
4207 // Add other conditions based on the type if needed
4208 if ($type === 'signup') {
4209 // Example: Check if registrations are enabled in WordPress
4210 if (!get_option('users_can_register')) {
4211 return false; // Registration is disabled
4212 }
4213 }
4214 // If the user is not logged in and other conditions pass, allow the page to be visible
4215 return true;
4216 }
4217 // If the type is not 'login' or 'signup', return false by default
4218 return true;
4219 }
4220
4221 /**
4222 * @deprecated
4223 * @see \Kirki\Framework\Supports\Facades\File::delete()
4224 */
4225 public static function delete_directory($dirname)
4226 {
4227 global $wp_filesystem;
4228 if (empty($wp_filesystem)) {
4229 require_once ABSPATH . 'wp-admin/includes/file.php';
4230 WP_Filesystem();
4231 }
4232
4233 if ($wp_filesystem->exists($dirname)) {
4234 return $wp_filesystem->delete($dirname, true);
4235 }
4236
4237 return false;
4238 }
4239
4240 /**
4241 * @deprecated
4242 * @see \Kirki\App\Supports\FileHandler::get_temp_folder_path()
4243 */
4244 public static function get_temp_folder_path()
4245 {
4246 $upload_dir = wp_upload_dir();
4247 $temp_folder = 'kirki_temp';
4248 $temp_folder_path = $upload_dir['basedir'] . '/' . $temp_folder;
4249
4250 return $temp_folder_path;
4251 }
4252
4253 /**
4254 * @deprecated
4255 * @see \Kirki\App\Managers\GlobalDataManager::get_initial_viewports()
4256 */
4257 public static function get_initial_view_ports()
4258 {
4259 return json_decode('{
4260 "active":"md",
4261 "scale":1,
4262 "zoom":1,
4263 "width":1200,
4264 "mdWidth":"",
4265 "defaults":[
4266 "md",
4267 "tablet",
4268 "mobileLandscape",
4269 "mobile"
4270 ],
4271 "list":{
4272 "md":{
4273 "value":1200,
4274 "scale":1,
4275 "minWidth":1200,
4276 "maxWidth":1200,
4277 "title":"Desktop",
4278 "icon":"desktop",
4279 "activeIcon":"desktop-hover"
4280 },
4281 "tablet":{
4282 "value":991,
4283 "scale":1,
4284 "minWidth":991,
4285 "maxWidth":991,
4286 "title":"Tablet",
4287 "icon":"tablet-default",
4288 "activeIcon":"tablet-hover"
4289 },
4290 "mobileLandscape":{
4291 "value":767,
4292 "scale":1,
4293 "minWidth":767,
4294 "maxWidth":767,
4295 "title":"Landscape",
4296 "icon":"phone-hr-default",
4297 "activeIcon":"phone-hr-hover"
4298 },
4299 "mobile":{
4300 "value":575,
4301 "scale":1,
4302 "minWidth":575,
4303 "maxWidth":575,
4304 "title":"Mobile",
4305 "icon":"phone-vr-default",
4306 "activeIcon":"phone-vr-hover"
4307 }
4308 }
4309 }', true);
4310 }
4311
4312 /**
4313 * @deprecated
4314 * @see \Kirki\App\Supports\FileHandler::download_zip_from_remote()
4315 */
4316 public static function download_zip_from_remote($remote_file, $new_name)
4317 {
4318 return FileHandler::download_zip_from_remote($remote_file, $new_name);
4319 // $file_ext = explode('.', $remote_file); // ['file', 'ext']
4320 // $file_ext = strtolower(end($file_ext)); // 'ext'
4321 // $allowed = ['zip'];
4322 // if (!in_array($file_ext, $allowed)) {
4323 // return false;
4324 // }
4325
4326 // try {
4327 // // error_reporting(E_ALL);
4328 // // ini_set('display_errors', 1);
4329 // // Download the file from the remote server.
4330 // // Create a stream context to disable SSL verification
4331 // $options = [
4332 // "http" => [
4333 // "method" => "GET",
4334 // "header" => "User-Agent: WordPress\r\n"
4335 // ],
4336 // "ssl" => [
4337 // "verify_peer" => false, // Disable verification of the peer's certificate
4338 // "verify_peer_name" => false // Disable verification of the peer's name
4339 // ]
4340 // ];
4341 // $context = stream_context_create($options);
4342 // $file_contents = file_get_contents($remote_file, false, $context);
4343
4344 // // Save the file locally.
4345 // if ($file_contents !== false) {
4346 // // Local path to save the downloaded file.
4347 // $local_file = wp_upload_dir()['basedir'] . '/' . $new_name;
4348 // file_put_contents($local_file, $file_contents);
4349 // return $local_file;
4350 // }
4351 // } catch (\Throwable $th) {
4352 // // throw $th;
4353 // }
4354 // return false;
4355 }
4356
4357 public static function filterZipFile($zip, $zip_file_path)
4358 {
4359 // Temporary filtered ZIP path
4360 $filtered_zip_path = sys_get_temp_dir() . '/filtered.zip';
4361 $filtered_zip = new \ZipArchive;
4362
4363 if ($filtered_zip->open($filtered_zip_path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === TRUE) {
4364 // Loop through all files in the archive
4365 for ($i = 0; $i < $zip->numFiles; $i++) {
4366 $filename = $zip->getNameIndex($i);
4367 $file_path = 'zip://' . $zip_file_path . '#' . $filename;
4368
4369 // Get MIME type based on file extensioncm_f
4370 $file_mime = self::getMimeTypeByExtension($filename);
4371
4372 // Additional JSON validation
4373 if ($file_mime === 'application/json' && !self::isJsonFile($file_path)) {
4374 $file_mime = 'text/plain'; // Fallback if not valid JSON
4375 }
4376
4377 // Check if the file MIME type matches supported types
4378 $is_supported = false;
4379 foreach (KIRKI_SUPPORTED_MEDIA_TYPES as $types) {
4380 if (in_array($file_mime, $types)) {
4381 $is_supported = true;
4382 break;
4383 }
4384 }
4385
4386 // Add the file to the filtered archive if supported
4387 if ($is_supported) {
4388 $file_contents = $zip->getFromIndex($i);
4389 $filtered_zip->addFromString($filename, $file_contents);
4390 }
4391 }
4392
4393 $filtered_zip->close();
4394 return $filtered_zip_path;
4395 } else {
4396 return false;
4397 }
4398 }
4399
4400 // Helper function to get MIME type by file extension
4401 private static function getMimeTypeByExtension($filename)
4402 {
4403 $extension_to_mime = [
4404 'json' => 'application/json',
4405 'jpg' => 'image/jpeg',
4406 'jpeg' => 'image/jpeg',
4407 'png' => 'image/png',
4408 'gif' => 'image/gif',
4409 'webp' => 'image/webp',
4410 'svg' => 'image/svg+xml',
4411 'pdf' => 'application/pdf',
4412 'mp4' => 'video/mp4',
4413 'ogg' => 'audio/ogg',
4414 'lottie' => 'text/plain',
4415 'mov' => 'video/quicktime',
4416 'mp3' => 'audio/mpeg',
4417 'wav' => 'audio/wav',
4418
4419 // Add more extensions as needed
4420 ];
4421
4422 $ext = pathinfo($filename, PATHINFO_EXTENSION);
4423 return $extension_to_mime[strtolower($ext)] ?? 'application/octet-stream';
4424 }
4425
4426 // Helper function to validate JSON file content
4427 private static function isJsonFile($file_path)
4428 {
4429 $file_contents = @file_get_contents($file_path);
4430 $trimmed = trim($file_contents);
4431 return $trimmed[0] === '{' || $trimmed[0] === '[';
4432 }
4433
4434 public static function is_remote_url($url)
4435 {
4436 // Parse the URL to get components
4437 $parsed_url = wp_parse_url($url);
4438 return isset($parsed_url['scheme']);
4439 }
4440
4441 public static function is_element_accessible($access)
4442 {
4443 switch ($access) {
4444 case 'all':
4445 return true; // Accessible to everyone
4446
4447 case 'guest':
4448 return !is_user_logged_in();
4449
4450 case 'logged-in':
4451 return is_user_logged_in(); // Accessible to any logged-in user
4452
4453 case 'admin':
4454 // Administrators and Super Admins
4455 return current_user_can('manage_options');
4456
4457 case 'editor':
4458 // Editors, Admins, and Super Admins can see this
4459 return current_user_can('edit_pages');
4460
4461 case 'author':
4462 // Authors, Editors, Admins, etc.
4463 return current_user_can('publish_posts');
4464
4465 case 'subscriber':
4466 // Subscribers and EVERY logged-in user above them
4467 return current_user_can('read');
4468
4469 default:
4470 return false; // Safely hide if the access rule is unrecognized
4471 }
4472 }
4473
4474 /**
4475 * Find symbol for post id using condition
4476 * it will find and return selected symbols html and css;
4477 *
4478 * @param string $type : symbol type.
4479 * @param string $post : post object.
4480 * @return symbol || bool(false)
4481 */
4482 public static function find_symbol_for_this_page($type)
4483 {
4484 $all_symbols = Symbol::fetch_list(true, false);
4485 foreach ($all_symbols as $key => $symbol) {
4486 if (isset($symbol['setAs']) && $symbol['setAs'] === $type) {
4487 return $symbol;
4488 }
4489 }
4490 return false;
4491 }
4492 /**
4493 * Get Custom Header
4494 * it will find and return selected symbols html and css;
4495 *
4496 * @param string $type stymbol type header|footer.
4497 * @param string $html if true the function will return html otherwise return symbol object.
4498 * @return string|object custom section html or stymbol object.
4499 */
4500 public static function get_page_custom_section($type, $html = true)
4501 {
4502 $show = apply_filters('kirki_show_custom_section_' . $type, true);
4503 if (!$show) {
4504 return '';
4505 }
4506
4507
4508 $symbol = self::find_symbol_for_this_page($type);
4509 if (!$html) {
4510 return $symbol;
4511 }
4512
4513 if (isset(self::$custom_sections[$type])) {
4514 return self::$custom_sections[$type];
4515 }
4516
4517 $s = self::isShowWPThemeHeaderFooter() ? '' : ' '; // this is for disableing theme header footer forcefully if is_show_wp_theme_header_footer is false
4518
4519 if ($symbol) {
4520 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : '');
4521 $symbol_data = $symbol['symbolData'];
4522 $set_as = isset($symbol['setAs']) ? $symbol['setAs'] : '';
4523
4524 $post_id = self::get_post_id_if_possible_from_url();
4525 $template_data = self::get_template_data_if_current_page_is_kirki_template();
4526 if ($template_data)
4527 $post_id = $template_data['template_id'];
4528
4529
4530 $custom_page_data = self::get_custom_data_if_current_page_is_kirki_custom_post();
4531 if ($custom_page_data)
4532 $post_id = $custom_page_data['post_id'];
4533
4534 $is_page_symbol_disabled = get_post_meta($post_id, KIRKI_META_NAME_FOR_PAGE_HF_SYMBOL_DISABLE_STATUS, true);
4535 if (isset($is_page_symbol_disabled) && is_array($is_page_symbol_disabled) && isset($is_page_symbol_disabled[$type])) {
4536 $is_page_symbol_disabled = $is_page_symbol_disabled[$type];
4537 } else {
4538 $is_page_symbol_disabled = false;
4539 }
4540
4541 $params = array(
4542 'blocks' => $symbol_data['data'],
4543 'style_blocks' => $symbol_data['styleBlocks'],
4544 'root' => $symbol_data['root'],
4545 'post_id' => $symbol['id'],
4546 'options' => [],
4547 'get_style' => true,
4548 'get_variable' => false,
4549 'should_take_app_script' => false,
4550 'prefix' => 'kirki-s' . $symbol['id']
4551 );
4552 if ($action === KIRKI_EDITOR_ACTION) {
4553 $extra_attr_for_hf_symbol = '';
4554 if ($is_page_symbol_disabled)
4555 $extra_attr_for_hf_symbol = ' style="display:none;"';
4556 $s = '<' . $type . $extra_attr_for_hf_symbol . ' data-kirki-symbol_set_as="' . $set_as . '" data-kirki-symbol="' . $symbol['id'] . '" data-kirki="' . $type . '">' . self::get_html_using_preview_script($params) . '</' . $type . '>'; //added data-kirki="$type" => We removed theme header and footer using preg_replace in TheFrontendHooks.php file
4557 } else if (!$is_page_symbol_disabled) {
4558 $params['should_take_app_script'] = true;
4559 $params['get_variable'] = true;
4560 $s = self::get_html_using_preview_script($params);
4561 } else if ($is_page_symbol_disabled) {
4562 $s = '<!-- ' . $type . ' is disabled -->';
4563 }
4564 }
4565
4566 $s = do_shortcode($s);
4567
4568 self::$custom_sections[$type] = $s;
4569
4570 return $s;
4571 }
4572
4573 public static function isShowWPThemeHeaderFooter()
4574 {
4575 $common_data = WpAdmin::get_common_data(true);
4576 return $common_data['is_show_wp_theme_header_footer'];
4577 }
4578
4579 /**
4580 * Check if a value is considered true or false.
4581 *
4582 * @param mixed $value The value to check.
4583 * @return bool Returns true if the value is considered "truthy", otherwise false.
4584 *
4585 * @deprecated
4586 * @see function \Kirki\App\is_truthy()
4587 */
4588 public static function isTruthy($value): bool
4589 {
4590 return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
4591 }
4592
4593 /**
4594 * Get the upload directory path has upload or write permission.
4595 *
4596 * @deprecated
4597 * @see Kirki\Framework\Supports\Facades\File::is_writable()
4598 * @see function Kirki\App\get_upload_directory()
4599 */
4600 public static function get_upload_dir_has_write_permission()
4601 {
4602 if (!function_exists('request_filesystem_credentials')) {
4603 require_once ABSPATH . 'wp-admin/includes/file.php';
4604 }
4605
4606 if (WP_Filesystem()) {
4607 global $wp_filesystem;
4608 $upload_dir = wp_upload_dir();
4609 return $wp_filesystem->is_writable($upload_dir['basedir']);
4610 }
4611
4612 return false;
4613 }
4614
4615 /**
4616 * $name = [] or string
4617 * @deprecated
4618 * @see Kirki\App\Supports\Template::add_prefix_to_class_name()
4619 */
4620 public static function add_prefix_to_class_name($prefix, $name)
4621 {
4622 if (is_array($name)) {
4623 foreach ($name as $key => $c) {
4624 $c = strtolower($c);
4625 if (in_array($c, KIRKI_PRESERVED_CLASS_LIST)) {
4626 $name[$key] = $c;
4627 } else {
4628 $name[$key] = $prefix ? strtolower($prefix) . '-' . $c : $c;
4629 }
4630 }
4631 } else {
4632 $name = strtolower($name);
4633 if (!in_array($name, KIRKI_PRESERVED_CLASS_LIST)) {
4634 $name = $prefix ? strtolower($prefix) . '-' . $name : $name;
4635 }
4636 }
4637 return $name;
4638 }
4639
4640 public static function checkVisibilityConditions($element, $options)
4641 {
4642 $conditions = $element['properties']['visibilityConditions'] ?? [];
4643 if (!count($conditions))
4644 return true;
4645 foreach ($conditions as $and_group) {
4646 $and_result = true;
4647 foreach ($and_group as $condition) {
4648 $source = (string) ($condition['source'] ?? 'kirki');
4649 $condition_result = apply_filters('kirki_visibility_condition_check_' . $source, false, $condition, $options);
4650 if (!$condition_result) {
4651 $and_result = false;
4652 break; // If any condition fails in AND group
4653 }
4654 }
4655 if ($and_result) {
4656 return true; // If any OR group passes
4657 }
4658 }
4659 return false; // No group passed
4660 }
4661
4662 private static function update_slider_style_blocks($blocks, $styles)
4663 {
4664
4665 $slider_mask_styleIds = [];
4666 $slider_item_styleIds = [];
4667
4668
4669 // slider_item
4670 foreach ($blocks as $id => $block) {
4671 // Check if the block has a 'name' key
4672 if (isset($block['name'])) {
4673 if (isset($block['name']) && $block['name'] === 'slider_mask') {
4674 // Loop through each item in $block['styleIds']
4675 foreach ($block['styleIds'] as $styleId) {
4676 // Check if the styleId is NOT already in the $slider_mask_styleIds array
4677 if (!in_array($styleId, $slider_mask_styleIds)) {
4678 // If not present, push it into the array
4679 $slider_mask_styleIds[] = $styleId;
4680 }
4681 }
4682 }
4683
4684 if (isset($block['name']) && $block['name'] === 'slider_item') {
4685 // Loop through each item in $block['styleIds']
4686 foreach ($block['styleIds'] as $styleId) {
4687 // Check if the styleId is NOT already in the $slider_item_styleIds array
4688 if (!in_array($styleId, $slider_item_styleIds)) {
4689 // If not present, push it into the array
4690 $slider_item_styleIds[] = $styleId;
4691 }
4692 }
4693 }
4694 }
4695 }
4696
4697 if (count($slider_mask_styleIds) > 0) {
4698 foreach ($slider_mask_styleIds as $styleId) {
4699 if ($styles[$styleId]) {
4700 $style = $styles[$styleId];
4701 $style_variants = $style['variant'];
4702
4703 $styleVariants = [];
4704
4705 if ($style_variants && count($style_variants) > 0) {
4706 foreach ($style_variants as $key => $css) {
4707 $css = preg_replace('/overflow\s*:\s*hidden;?/', '', $css);
4708 $css = preg_replace('/pointer-events\s*:\s*none;?/', '', $css);
4709
4710 $css = trim($css);
4711 $styleVariants[$key] = $css;
4712 }
4713 }
4714 $styles[$styleId]['variant'] = $styleVariants;
4715 }
4716 }
4717 }
4718
4719
4720 if (count($slider_item_styleIds) > 0) {
4721 foreach ($slider_item_styleIds as $styleId) {
4722 $style = $styles[$styleId];
4723 $style_variants = $style['variant'];
4724
4725 $styleVariants = [];
4726
4727 if ($style_variants && count($style_variants) > 0) {
4728 foreach ($style_variants as $key => $css) {
4729 $css = preg_replace('/position\s*:\s*absolute;?/', '', $css);
4730 $css = preg_replace('/display\s*:\s*none;?/', '', $css);
4731
4732 $css = trim($css);
4733 $styleVariants[$key] = $css;
4734 }
4735
4736 $styles[$styleId]['variant'] = $styleVariants;
4737 }
4738 }
4739 }
4740
4741 return $styles;
4742 }
4743
4744 public static function handle_legacy_slider_class()
4745 {
4746 $data = Page::get_all_data_by_kirki_meta_key();
4747
4748 foreach ($data as $key => $value) {
4749 $post_id = $value['post_id']; // ID of the post
4750 $meta_key = $value['meta_key']; // Meta key name
4751 $meta_value = $value['meta_value']; // Serialized meta value
4752
4753 $meta_value = unserialize($meta_value, ['allowed_classes' => false]); // Convert serialized data to array
4754
4755 // If the meta value has a 'blocks' key, handle it as a full page data
4756 if (isset($meta_value['blocks'])) {
4757 $blocks = $meta_value['blocks']; // Get the blocks array
4758 $styles = self::get_page_styleblocks($post_id);
4759 $updated_styles = self::update_slider_style_blocks($blocks, $styles); // Update block names
4760
4761 self::update_page_styleblocks($post_id, $updated_styles);
4762 } else {
4763 // If no 'blocks' key, treat entire value as blocks array
4764 $blocks = $meta_value;
4765 $post = get_post($post_id);
4766
4767
4768 if (isset($post->post_type) && $post->post_type === 'kirki_symbol') {
4769 $styles = $blocks['styleBlocks'];
4770 $updated_styles = self::update_slider_style_blocks($blocks['data'], $styles);
4771 $blocks['styleBlocks'] = $updated_styles;
4772
4773 update_post_meta($post_id, $meta_key, $blocks);
4774 }
4775 }
4776 }
4777 }
4778
4779 public static function handle_legacy_slider_default_class()
4780 {
4781 $styles = self::get_global_data_using_key(KIRKI_GLOBAL_STYLE_BLOCK_META_KEY);
4782 if (!$styles) {
4783 $styles = array();
4784 }
4785 if (count($styles) > 0) {
4786 if (isset($styles['kirki_slider_slide'])) {
4787 // Handle kirki_slider_mask
4788 if (isset($styles['kirki_slider_mask'])) {
4789 $variants = $styles['kirki_slider_mask']['variant'];
4790 if (count($variants) > 0) {
4791 $styleVariants = [];
4792 foreach ($variants as $key => $css) {
4793 $css = preg_replace('/overflow\s*:\s*hidden;?/', '', $css);
4794 $css = preg_replace('/pointer-events\s*:\s*none;?/', '', $css);
4795 $css = trim($css);
4796 $styleVariants[$key] = $css;
4797 }
4798 $styles['kirki_slider_mask']['variant'] = $styleVariants;
4799 }
4800 }
4801
4802 // Handle kirki_slider_slide
4803 if (isset($styles['kirki_slider_slide'])) {
4804 $variants = $styles['kirki_slider_slide']['variant'];
4805 if (count($variants) > 0) {
4806 $styleVariants = [];
4807 foreach ($variants as $key => $css) {
4808 $css = preg_replace('/position\s*:\s*absolute;?/', '', $css);
4809 $css = preg_replace('/display\s*:\s*none;?/', '', $css);
4810 $css = trim($css);
4811 $styleVariants[$key] = $css;
4812 }
4813 $styles['kirki_slider_slide']['variant'] = $styleVariants;
4814 }
4815 }
4816 }
4817 }
4818
4819 }
4820
4821 public static function get_current_item_index($index, $options)
4822 {
4823 $pagination = isset($options['pagination']) ? $options['pagination'] : false;
4824 $items_per_page = isset($options['items_per_page']) ? $options['items_per_page'] : 3;
4825 $page_no = isset($options['page_no']) ? $options['page_no'] : 1;
4826
4827 if ($pagination && $items_per_page > 0 && $page_no > 0) {
4828 // Calculate the start index based on the current page and items per page
4829 $index = (($page_no - 1) * $items_per_page) + $index;
4830 }
4831
4832 return $index;
4833 }
4834
4835 public static function convertToBytes($val)
4836 {
4837 $val = trim($val);
4838 $last = strtolower($val[strlen($val) - 1]);
4839 $val = (int) $val;
4840 switch ($last) {
4841 case 'g':
4842 $val *= 1024;
4843 case 'm':
4844 $val *= 1024;
4845 case 'k':
4846 $val *= 1024;
4847 }
4848 return $val;
4849 }
4850
4851 /**
4852 * @deprecated
4853 * @see Kirki\App\Supports\Canvas::get_full_canvas_template_path()
4854 */
4855 public static function get_kirki_full_canvas_template_path()
4856 {
4857 return self::normalize_kirki_full_canvas_template_path(KIRKI_FULL_CANVAS_TEMPLATE_PATH);
4858 }
4859
4860 /**
4861 * @deprecated
4862 * @see Kirki\App\Supports\Canvas::normalize_full_canvas_template_path()
4863 */
4864 public static function normalize_kirki_full_canvas_template_path($path)
4865 {
4866 // replace if has kirki-pro => kirki
4867 if (strpos($path, 'kirki-pro') !== false) {
4868 $path = str_replace('kirki-pro', 'kirki', $path);
4869 }
4870 return $path;
4871 }
4872 public static function normalize_variable_mode($mode)
4873 {
4874 if (!$mode) {
4875 return ['color' => 'inherit', 'size' => 'inherit', 'text-style' => 'inherit', 'font-family' => 'inherit'];
4876 }
4877 // if v is string
4878 if (is_string($mode)) {
4879 return ['color' => $mode, 'size' => $mode, 'text-style' => $mode, 'font-family' => $mode];
4880 }
4881 if (is_array($mode)) {
4882 return $mode;
4883 }
4884 return $mode;
4885 }
4886
4887 /**
4888 * Whether the target url is a safe, externally reachable http(s) URL.
4889 *
4890 * Rejects loopback, private, link-local and cloud-metadata addresses so a
4891 * planted form config cannot be used to probe the server's own network.
4892 *
4893 * @param string $url The URL.
4894 * @return bool
4895 */
4896 public static function is_safe_url($url) {
4897 if (!is_string($url) || filter_var($url, FILTER_VALIDATE_URL) === false) {
4898 return false;
4899 }
4900
4901 $scheme = wp_parse_url($url, PHP_URL_SCHEME);
4902 $host = wp_parse_url($url, PHP_URL_HOST);
4903
4904 if (!is_string($scheme) || !in_array(strtolower($scheme), array('http', 'https'), true)) {
4905 return false;
4906 }
4907
4908 if (!is_string($host) || '' === $host) {
4909 return false;
4910 }
4911
4912 if (filter_var($host, FILTER_VALIDATE_IP)) {
4913 return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
4914 }
4915
4916 $ip = gethostbyname($host);
4917
4918 if (!filter_var($ip, FILTER_VALIDATE_IP) || $ip === $host) {
4919 return false;
4920 }
4921
4922 return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
4923 }
4924 }
4925