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