PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.2.2
Kirki – Freeform Page Builder, Website Builder & Customizer v6.2.2
6.2.3 6.2.2 6.2.1 6.2.0 6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / includes / HelperFunctions.php
kirki / includes Last commit date
API 1 week ago Admin 1 month ago Ajax 4 days ago ExportImport 3 weeks ago FormValidator 3 months ago Frontend 4 days ago Manager 1 week ago API.php 3 weeks ago Admin.php 3 months ago Ajax.php 2 weeks ago Apps.php 1 month ago ContentManager.php 3 months ago DbQueryUtils.php 3 months ago ElementVisibilityConditions.php 3 months ago Frontend.php 3 months ago HelperFunctions.php 4 days ago KirkiBase.php 1 month ago PostsQueryUtils.php 3 months ago Staging.php 2 weeks ago View.php 2 months ago
HelperFunctions.php
4916 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 public static function get_kirki_cms_inherit_post_filters( $filters, $related_post_parent, $post_parent ) {
2220 global $wpdb;
2221
2222 if ( empty( $related_post_parent ) ) {
2223 return $filters;
2224 }
2225
2226 $related_post_parent = (int) $related_post_parent;
2227
2228 $related_post_parent_obj = get_post($related_post_parent);
2229 if($related_post_parent_obj && str_contains( $related_post_parent_obj->post_type, 'kirki_cm_' )){
2230 $related_fields = ContentManagerHelper::get_post_type_custom_field_keys( $post_parent );
2231
2232 if ( empty( $related_fields ) || ! is_array( $related_fields ) ) {
2233 return $filters;
2234 }
2235
2236 if ( ! is_array( $filters ) ) {
2237 $filters = [];
2238 }
2239
2240 foreach ( $related_fields as $field ) {
2241 if ( ! isset( $field['type'] ) || ! in_array( $field['type'], [ 'multi-reference', 'reference' ], true ) ) {
2242 continue;
2243 }
2244
2245 $field_id = $field['id'] ?? null;
2246 if ( ! $field_id ) {
2247 continue;
2248 }
2249
2250 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id( $post_parent, $field_id );
2251
2252 $has_references = $wpdb->get_var(
2253 $wpdb->prepare(
2254 "SELECT COUNT(*) FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id = %d",
2255 $meta_key,
2256 $related_post_parent
2257 )
2258 );
2259
2260 if ( empty( $has_references ) ) {
2261 continue;
2262 }
2263
2264 $filter_exists = false;
2265 $filter_index = null;
2266
2267 foreach ( $filters as $index => $existing_filter ) {
2268 if ( isset( $existing_filter['id'] ) && $existing_filter['id'] === $field_id ) {
2269 $filter_exists = true;
2270 $filter_index = $index;
2271 break;
2272 }
2273 }
2274
2275 if ( ! $filter_exists ) {
2276 $filters[] = [
2277 'type' => $field['type'],
2278 'id' => $field_id,
2279 'title' => $field['label'] ?? $field_id,
2280 'items' => [],
2281 ];
2282 $filter_index = count( $filters ) - 1;
2283 }
2284
2285 if ( ! isset( $filters[ $filter_index ]['items'] ) || ! is_array( $filters[ $filter_index ]['items'] ) ) {
2286 $filters[ $filter_index ]['items'] = [];
2287 }
2288
2289 $new_item = [
2290 'condition' => 'in',
2291 'value' => $related_post_parent,
2292 'relation' => 'OR',
2293 ];
2294
2295 $item_exists = false;
2296 foreach ( $filters[ $filter_index ]['items'] as $item ) {
2297 if (
2298 isset( $item['condition'], $item['value'], $item['relation'] ) &&
2299 $item['condition'] === $new_item['condition'] &&
2300 (int) $item['value'] === $new_item['value'] &&
2301 $item['relation'] === $new_item['relation']
2302 ) {
2303 $item_exists = true;
2304 break;
2305 }
2306 }
2307
2308 if ( ! $item_exists ) {
2309 $filters[ $filter_index ]['items'][] = $new_item;
2310 }
2311 }
2312 }
2313
2314
2315 return $filters;
2316 }
2317
2318 /**
2319 * Get dynamic collection data
2320 *
2321 * @param object $params query object.
2322 *
2323 * @return array post array
2324 */
2325 public static function get_posts($params)
2326 {
2327 $name = isset($params['name']) ? $params['name'] : null;
2328 $sorting = isset($params['sorting']) ? $params['sorting'] : null;
2329 $filters = isset($params['filters']) ? $params['filters'] : null;
2330 $inherit = (bool) ($params['inherit'] ?? false);
2331 $related = (bool) ($params['related'] ?? false);
2332 $post_parent = (int) ($params['post_parent'] ?? 0);
2333 $post_status = isset($params['post_status']) ? $params['post_status'] : 'publish';
2334 $query = isset($params['q']) ? $params['q'] : '';
2335 $IDs = isset($params['IDs']) ? $params['IDs'] : [];
2336 $related_post_parent = isset($params['related_post_parent']) ? $params['related_post_parent'] : self::get_post_id_if_possible_from_url();
2337
2338 // add new
2339 $current_page = isset($params['current_page']) ? $params['current_page'] : 1;
2340 $item_per_page = isset($params['item_per_page']) ? $params['item_per_page'] : 3;
2341 $offset = isset($params['offset']) ? $params['offset'] : 0;
2342 $context = isset($params['context']) ? $params['context'] : null;
2343 $tax_query = [
2344 'relation' => 'AND',
2345 ];
2346
2347 // Calculate the offset
2348 $offset = ($current_page - 1) * $item_per_page + $offset;
2349
2350 $args = array(
2351 'posts_per_page' => $item_per_page,
2352 'paged' => $current_page,
2353 'offset' => $offset,
2354 'post_type' => $name,
2355 'suppress_filters' => true,
2356 'post_status' => $post_status,
2357 's' => $query,
2358 );
2359
2360 if (!empty($query)) {
2361 self::search_posts_by_query($name, $query, $post_parent, $args);
2362 } else {
2363 remove_filter('posts_where', [HelperFunctions::class, 'posts_where_filter_callback']);
2364 }
2365
2366 $filters = self::handle_legacy_filter_to_new_filter($filters);
2367 if($inherit && $related_post_parent && str_contains( $name, 'kirki_cm_' )){
2368 $filters = self::get_kirki_cms_inherit_post_filters($filters, $related_post_parent, $post_parent);
2369 }
2370 $added_filters = array();
2371
2372 /**
2373 * Combine search and filters with AND logic
2374 *
2375 * When both search query and filters are present, we need to ensure they work together:
2376 * - Search creates meta_query with 'OR' relation (matches any custom field)
2377 * - Filters add additional conditions
2378 * - Final structure: AND(search_conditions, filter_conditions)
2379 *
2380 * This makes filters compulsory when searching, narrowing results further.
2381 */
2382 $search_meta_query = isset($args['meta_query']) ? $args['meta_query'] : null;
2383 $has_search = !empty($query) && $search_meta_query !== null;
2384 $has_filters = !empty($filters) && is_array($filters);
2385
2386 // Reset meta_query if both search and filters exist to rebuild with AND relation
2387 if ($has_search && $has_filters) {
2388 $args['meta_query'] = [
2389 'relation' => 'AND',
2390 $search_meta_query, // Search conditions (with OR relation internally)
2391 ];
2392 }
2393
2394 if (isset($filters) && is_array($filters)) {
2395 foreach ($filters as $filter_item) {
2396 if (isset($filter_item['parent']) && $filter_item['parent']) {
2397 $filter_item['id'] = 'term';
2398 }
2399 $field_name = isset($filter_item['id']) ? $filter_item['id'] : '';
2400
2401 if (!$field_name) {
2402 continue;
2403 }
2404
2405 if (self::attribute_in_post_table($filter_item['id']) && is_array($filter_item['items'])) {
2406
2407 switch ($field_name) {
2408 case 'post_excerpt':
2409 case 'post_content':
2410 case 'post_title': {
2411 $callback = self::post_table_filter_query($filter_item, 'text');
2412 if ($callback) {
2413 $added_filters[] = $callback;
2414 }
2415 break;
2416 }
2417
2418 case 'post_date':
2419 case 'post_date_gmt': {
2420 /**
2421 * $filter_item['items'] max contain one array.
2422 * in array may contain start-date, end-date
2423 * Like: [{"start-date": "2020-01-01","end-date": "2020-01-02"}]
2424 */
2425
2426 if (isset($filter_item['items'], $filter_item['items'][0])) {
2427 $items = $filter_item['items'];
2428 $item = $items[0]; // Get first item in array.
2429
2430 $date_query = array('column' => $field_name);
2431 $date_query['inclusive'] = true;
2432
2433 if (!empty($item['start-date'])) {
2434 $date_query['after'] = $item['start-date'];
2435 }
2436
2437 if (!empty($item['end-date'])) {
2438 $date_query['before'] = $item['end-date'];
2439 }
2440
2441 $args['date_query'] = $date_query;
2442 }
2443
2444 break;
2445 }
2446
2447 case 'post_author': {
2448
2449 /**
2450 * $filter_item['items'] must not contain more than 2 array of conditions.
2451 * 1 array may contain 'in' conditions and another for 'not-in' conditions
2452 * And values of 'in' and 'not-in' conditions should not collide
2453 * Like: the condition should not be author 'in' [1, 2, 3] and 'not-in' [2, 4, 5]
2454 */
2455 $items = $filter_item['items'];
2456
2457 foreach ($items as $item) {
2458 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
2459 if ($item['condition'] === 'in') {
2460 $args['author__in'] = $item['values'];
2461 }
2462
2463 if ($item['condition'] === 'not-in') {
2464 $args['author__not_in'] = $item['values'];
2465 }
2466 }
2467 }
2468
2469 break;
2470 }
2471
2472 case 'term': {
2473 $items = $filter_item['items'];
2474
2475
2476 foreach ($items as $item) {
2477 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
2478
2479 if ($item['condition'] === 'in' && !empty($item['values'])) {
2480 array_push($tax_query, [
2481 'taxonomy' => $filter_item['type'],
2482 'field' => 'term_id',
2483 'terms' => $item['values'],
2484 'operator' => 'IN',
2485 ]);
2486 }
2487
2488 if ($item['condition'] === 'not-in' && !empty($item['values'])) {
2489 array_push($tax_query, [
2490 'taxonomy' => $filter_item['type'],
2491 'field' => 'term_id',
2492 'terms' => $item['values'],
2493 'operator' => 'NOT IN',
2494 ]);
2495 }
2496 }
2497 }
2498 break;
2499 }
2500 }
2501 } else {
2502 $key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $field_name);
2503 $data_type = $filter_item['type'] ?? 'text';
2504
2505 if (!isset($args['meta_query'])) {
2506 $args['meta_query'] = array();
2507 } elseif (!is_array($args['meta_query'])) {
2508 $args['meta_query'] = array();
2509 }
2510
2511 // Ensure meta_query has proper structure when combining search + filters
2512 if ($has_search && !isset($args['meta_query']['relation'])) {
2513 $args['meta_query']['relation'] = 'AND';
2514 }
2515
2516 switch ($data_type) {
2517 default:
2518 case 'rich_text':
2519 case 'text':
2520 case 'phone':
2521 case 'url':
2522 case 'email': {
2523 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'text');
2524 break;
2525 }
2526
2527 case 'date': {
2528 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'date');
2529 break;
2530 }
2531
2532 case 'number': {
2533 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'number');
2534 break;
2535 }
2536
2537 case 'option': {
2538 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'option');
2539 break;
2540 }
2541
2542 case 'switch': {
2543 $args['meta_query'][] = self::post_meta_table_filter_query($filter_item, $key, 'switch');
2544 break;
2545 }
2546
2547 case 'taxonomy': {
2548 if (empty($args['tax_query'])) {
2549 $tax_query = array(
2550 'relation' => 'AND'
2551 );
2552 }
2553
2554 if (
2555 isset($filter_item['taxonomy'], $filter_item['terms']) &&
2556 is_array($filter_item['terms'])
2557 ) {
2558 $operators = array('NOT IN', 'IN');
2559
2560 $operator = 'IN';
2561
2562 if (isset($filter_item['operator']) && in_array($filter_item['operator'], $operators, true)) {
2563 $operator = $filter_item['operator'];
2564 }
2565
2566 array_push($tax_query, [
2567 'taxonomy' => $filter_item['taxonomy'],
2568 'field' => 'term_id', // So far this is fixed
2569 'terms' => $filter_item['terms'],
2570 'operator' => $operator,
2571 ]);
2572 }
2573 break;
2574 }
2575
2576 case 'author': {
2577 if (isset($filter_item['condition'], $filter_item['values']) && is_array($filter_item['values'])) {
2578 if ($filter_item['condition'] === 'is-equal') {
2579 $args['author__in'] = $filter_item['values'];
2580 }
2581
2582 if ($filter_item['condition'] === 'is-not-equal') {
2583 $args['author__not_in'] = $filter_item['values'];
2584 }
2585 }
2586 break;
2587 }
2588
2589 case 'multi-reference':
2590 case 'reference': {
2591 $args = self::cm_reference_table_filter_query($filter_item, $key, $args);
2592 break;
2593 }
2594 }
2595 }
2596 }
2597 }
2598
2599 if (count($IDs) > 0) {
2600 $args['post__in'] = $IDs;
2601 unset($args['post_parent']);
2602 $args['post_type'] = 'any';
2603 $inherit = false;
2604 $post_parent = false;
2605 }
2606
2607 if (count($tax_query) > 1) {
2608 $args['tax_query'] = $tax_query;
2609 }
2610
2611 // Set default orderby and order if not explicitly specified
2612 $order = 'DESC';
2613 $orderby = 'date';
2614
2615 if (isset($sorting) && is_array($sorting) && !empty($sorting)) {
2616 if (!empty($sorting['order'])) {
2617 $order = $sorting['order'];
2618 } elseif (!empty($sorting['type'])) {
2619 $order = $sorting['type'];
2620 }
2621
2622 if (!empty($sorting['orderby'])) {
2623 $orderby = $sorting['orderby'];
2624 } elseif (!empty($sorting['value'])) {
2625 $orderby = $sorting['value'];
2626 }
2627 }
2628
2629 $args['order'] = $order;
2630
2631 if ('none' !== $orderby) {
2632 if (isset($name) && str_contains($name, KIRKI_CONTENT_MANAGER_PREFIX) && !in_array($orderby, KIRKI_WORDPRESS_SORT_BY_OPTIONS, true)) {
2633 $args['orderby'] = 'meta_value'; // Use 'meta_value' or 'meta_value_num' as needed
2634 $args['meta_key'] = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $orderby);
2635 } elseif ('date' === $orderby) {
2636 $args['orderby'] = array('date' => $order, 'ID' => $order);
2637 } else {
2638 $args['orderby'] = $orderby;
2639 }
2640 }
2641
2642 if ($inherit || $post_parent) {
2643 //TODO: if terms page then show terms post only. like tag, category.
2644 $args['post_parent'] = $post_parent;
2645 }
2646
2647 if (!empty($context) && $inherit && isset( $args['post_type']) && !str_contains( $args['post_type'], 'kirki_cm') ) {
2648 if ($context['collectionType'] == 'user') {
2649 $args['author'] = $context['id'];
2650 unset($args['post_parent']);
2651 }
2652 if ($context['collectionType'] == 'term') {
2653 $args['tax_query'] = array(
2654 array(
2655 'taxonomy' => $context['taxonomy'], // Replace 'category' with your taxonomy
2656 'field' => 'term_id', // Use 'slug' if you want to query by slug
2657 'terms' => $context['id'], // Replace 123 with your term ID
2658 )
2659 );
2660 unset($args['post_parent']);
2661 }
2662 }
2663
2664 if ($related) {
2665 $post = get_post($related_post_parent);
2666
2667 if ($post) {
2668 $args['post_type'] = $post->post_type;
2669 if (str_contains($post->post_type, 'kirki_cm_')) {
2670 // filter related posts for content manager post
2671 $referenced_post_ids = self::get_referenced_post_ids($post_parent, $post);
2672 $args['post__in'] = array_map('intval', $referenced_post_ids);
2673 } else {
2674 $args['tax_query'] = self::buildTaxonomyForRelatedPosts($post);
2675 $args['post__not_in'] = [$post->ID];
2676 }
2677
2678 }
2679 }
2680
2681 // Disable suppress_filters when post_table text filters (post_title, post_content, post_excerpt)
2682 // are present, since they rely on posts_where hooks to modify the SQL query.
2683 if (!empty($added_filters)) {
2684 $args['suppress_filters'] = false;
2685 }
2686
2687 // Run the WP_Query
2688 $query = new WP_Query($args);
2689 foreach ($added_filters as $callback) {
2690 remove_filter('posts_where', $callback);
2691 }
2692
2693 $posts = $query->posts;
2694
2695
2696 $custom_logo_id = get_theme_mod('custom_logo');
2697 $image = wp_get_attachment_image_src($custom_logo_id, 'full');
2698
2699 $kirki_content_manager_post_type_fields = array();
2700
2701 if (isset($args['post_type']) && KIRKI_CONTENT_MANAGER_PREFIX === $args['post_type']) {
2702 $post_parent = $args['post_parent'];
2703 $kirki_content_manager_post_type_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2704 }
2705
2706 foreach ($posts as $key => &$post) {
2707 if (is_null($post)) {
2708 unset($posts[$key]);
2709 continue;
2710 }
2711 if (
2712 KIRKI_CONTENT_MANAGER_PREFIX === $post->post_type && is_array($kirki_content_manager_post_type_fields)
2713 ) {
2714 foreach ($kirki_content_manager_post_type_fields as $field_key) {
2715 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post->post_parent, $field_key['id']);
2716 $post->{$field_key['id']} = get_post_meta($post->ID, $meta_key, true);
2717
2718 if (
2719 isset($field_key['type']) &&
2720 $field_key['type'] === 'image' &&
2721 $post->{$field_key['id']}
2722 ) {
2723 $post->{$field_key['id']} = array(
2724 'wp_attachment_id' => $post->{$field_key['id']}['id'],
2725 'src' => $post->{$field_key['id']}['url'],
2726 );
2727 }
2728 }
2729 }
2730
2731
2732 $post->post_id = $post->ID;
2733 $post->author_profile_picture = array(
2734 'src' => get_avatar_url($post->post_author)
2735 );
2736 $post->post_author = get_the_author_meta('display_name', $post->post_author);
2737 $post->post_time = get_the_time('', $post->ID);
2738 $post->featured_image = array(
2739 'wp_attachment_id' => get_post_thumbnail_id($post->ID),
2740 'src' => get_the_post_thumbnail_url($post->ID)
2741 );
2742 $post->site_logo = isset($image[0]) ? $image[0] : '';
2743 $post->post_page_link = \get_permalink($post->ID);
2744 $post->author_posts_page_link = \get_author_posts_url($post->post_author);
2745
2746 unset($post->post_excerpt);
2747 }
2748 ;
2749
2750 // Get total posts and total pages for pagination
2751 $total_posts = $query->found_posts;
2752 $total_posts_updated = max(0, $total_posts - $offset);
2753 $total_pages = ceil($total_posts_updated / $item_per_page);
2754
2755 // Calculate previous and next page numbers
2756 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
2757 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
2758
2759 // Return the query and pagination info
2760 return array(
2761 'data' => $posts,
2762 'pagination' => array(
2763 'per_page' => $item_per_page,
2764 'current_page' => $current_page,
2765 'total_pages' => $total_pages,
2766 'total_count' => $total_posts,
2767 'previous' => $prev_page,
2768 'next' => $next_page,
2769 ),
2770 );
2771 }
2772
2773
2774 public static function search_posts_by_query($name, $query, $post_parent, &$args)
2775 {
2776 global $wpdb;
2777
2778 if (!str_contains($name, 'kirki_cm_')) {
2779 return;
2780 }
2781
2782 unset($args['s']);
2783
2784 $all_custom_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2785 $meta_query_args = ['relation' => 'OR'];
2786 $reference_where_sql = '';
2787
2788 foreach ($all_custom_fields as $data) {
2789 if (!$data)
2790 continue;
2791
2792 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $data['id']);
2793
2794 if (in_array($data['type'], ['text'], true)) {
2795 $meta_query_args[] = [
2796 'key' => $meta_key,
2797 'value' => $query,
2798 'compare' => 'LIKE',
2799 ];
2800 }
2801
2802 if (in_array($data['type'], ['reference'], true)) {
2803 $matched_post_ids = self::get_matched_post_ids_recursive($data['ref_collection'], $query);
2804
2805 if (!empty($matched_post_ids)) {
2806 $ids = implode(',', array_map('intval', $matched_post_ids));
2807 $reference_where_sql .= " OR {$wpdb->posts}.ID IN (
2808 SELECT post_id
2809 FROM {$wpdb->prefix}kirki_cm_reference
2810 WHERE field_meta_key = '{$meta_key}'
2811 AND ref_post_id IN ($ids)
2812 )";
2813 }
2814 }
2815 }
2816
2817 if (count($meta_query_args) > 1) {
2818 $args['meta_query'] = $meta_query_args;
2819 }
2820
2821 // Store filter parameters for the callback
2822 self::$posts_where_filter_params = [
2823 'query' => $query,
2824 'reference_where_sql' => $reference_where_sql,
2825 'post_parent' => $post_parent,
2826 ];
2827
2828 add_filter('posts_where', [__CLASS__, 'posts_where_filter_callback']);
2829 }
2830
2831 public static function get_matched_post_ids_recursive($post_parent, $query, $depth = 0, $max_depth = 5)
2832 {
2833 global $wpdb;
2834
2835 if ($depth > $max_depth) {
2836 return [];
2837 }
2838
2839 $post_type = 'kirki_cm_' . $post_parent;
2840
2841 $matched_post_ids = $wpdb->get_col(
2842 $wpdb->prepare(
2843 "SELECT ID FROM {$wpdb->posts}
2844 WHERE post_title LIKE %s
2845 AND post_status = 'publish'
2846 AND post_type = %s",
2847 '%' . $wpdb->esc_like($query) . '%',
2848 $post_type
2849 )
2850 );
2851
2852 $ref_custom_fields = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2853 $meta_conditions = [];
2854
2855 foreach ($ref_custom_fields as $ref_field) {
2856 if (!$ref_field)
2857 continue;
2858
2859 if (in_array($ref_field['type'], ['text'], true)) {
2860 $ref_meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $ref_field['id']);
2861 $meta_conditions[] = $wpdb->prepare(
2862 "(meta_key = %s AND meta_value LIKE %s)",
2863 $ref_meta_key,
2864 "%{$search}%"
2865 );
2866 }
2867 }
2868
2869 if (!empty($meta_conditions)) {
2870 $where_meta = implode(' OR ', $meta_conditions);
2871 $meta_post_ids = $wpdb->get_col("SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE {$where_meta}"); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2872 $matched_post_ids = array_merge($matched_post_ids, $meta_post_ids);
2873 }
2874
2875 foreach ($ref_custom_fields as $ref_field) {
2876 if (!$ref_field || !in_array($ref_field['type'], ['reference'], true)) {
2877 continue;
2878 }
2879
2880 $ref_post_parent = $ref_field['ref_collection'];
2881 $nested_matched_ids = self::get_matched_post_ids_recursive($ref_post_parent, $query, $depth + 1, $max_depth);
2882
2883 if (!empty($nested_matched_ids)) {
2884 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id($post_parent, $ref_field['id']);
2885 $ids = implode(',', array_map('intval', $nested_matched_ids));
2886 $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
2887 $matched_post_ids = array_merge($matched_post_ids, $ref_post_ids);
2888 }
2889 }
2890
2891 return array_unique(array_map('intval', $matched_post_ids));
2892 }
2893
2894 public static function get_referenced_post_ids($post_parent, $post)
2895 {
2896 global $wpdb;
2897
2898 $allData = ContentManagerHelper::get_post_type_custom_field_keys($post_parent);
2899 $post_ids = [];
2900
2901 foreach ($allData as $data) {
2902 if ($data && in_array($data['type'], ['reference', 'multi-reference'], true)) {
2903 $meta_key = 'kirki_cm_field_' . $post_parent . '_' . $data['id'];
2904
2905 $results = $wpdb->get_results(
2906 $wpdb->prepare(
2907 "SELECT ref_post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND post_id = %d",
2908 $meta_key,
2909 $post->ID
2910 ),
2911 ARRAY_A
2912 );
2913
2914 foreach ($results as $id) {
2915 $related_posts = $wpdb->get_results(
2916 $wpdb->prepare(
2917 "SELECT post_id FROM {$wpdb->prefix}kirki_cm_reference WHERE field_meta_key = %s AND ref_post_id = %d",
2918 $meta_key,
2919 (int) $id['ref_post_id']
2920 ),
2921 ARRAY_A
2922 );
2923
2924 foreach ($related_posts as $related) {
2925 $related_id = (int) $related['post_id'];
2926
2927 if ($related_id !== (int) $post->ID) {
2928 $post_ids[] = $related_id;
2929 }
2930 }
2931
2932
2933 }
2934 }
2935 }
2936 $post_ids = array_values(array_unique($post_ids));
2937
2938 return !empty($post_ids) ? $post_ids : [0];
2939 }
2940
2941 public static function get_terms($params)
2942 {
2943 $terms_array = [];
2944 $current_page = isset($params['current_page']) ? (int) $params['current_page'] : 1;
2945 $item_per_page = isset($params['item_per_page']) ? (int) $params['item_per_page'] : 3;
2946 $offset = isset($params['offset']) ? (int) $params['offset'] : 0;
2947
2948 if (!empty($params['inherit'])) {
2949 $terms_array = get_the_terms($params['post_parent'], $params['taxonomy']);
2950 if (is_array($terms_array)) {
2951 // Convert WP_Term objects to arrays only if needed
2952 $terms_array = array_map(function ($term) {
2953 return is_object($term) && method_exists($term, 'to_array')
2954 ? $term->to_array()
2955 : (array) $term;
2956 }, $terms_array);
2957
2958 $calculated_offset = (($current_page - 1) * $item_per_page) + $offset;
2959 $terms_array = array_slice($terms_array, $calculated_offset, $item_per_page);
2960 } else {
2961 $terms_array = [];
2962 }
2963 } else {
2964 $params['offset'] = (($current_page - 1) * $item_per_page) + $offset;
2965 $params['number'] = $item_per_page;
2966
2967 $terms_array = get_terms($params);
2968
2969 if (is_array($terms_array)) {
2970 foreach ($terms_array as &$item) {
2971 if (is_object($item) && method_exists($item, 'to_array')) {
2972 $item = $item->to_array();
2973 } elseif (is_object($item)) {
2974 $item = (array) $item;
2975 }
2976 }
2977 } else {
2978 $terms_array = [];
2979 }
2980 }
2981
2982 // Count total terms
2983 $total_terms = 0;
2984 if (!empty($params['inherit'])) {
2985 $t = get_the_terms($params['post_parent'], $params['taxonomy']);
2986 if (is_array($t)) {
2987 $total_terms = count($t);
2988 } else {
2989 $total_terms = wp_count_terms(['taxonomy' => $params['taxonomy']]);
2990 }
2991 } else {
2992 $total_terms = wp_count_terms(['taxonomy' => $params['taxonomy']]);
2993 }
2994
2995 $total_pages = ($item_per_page > 0) ? ceil($total_terms / $item_per_page) : 1;
2996 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
2997 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
2998
2999 return [
3000 'data' => $terms_array,
3001 'pagination' => [
3002 'per_page' => $item_per_page,
3003 'current_page' => $current_page,
3004 'total_pages' => $total_pages,
3005 'total_count' => $total_terms,
3006 'previous' => $prev_page,
3007 'next' => $next_page,
3008 ],
3009 ];
3010 }
3011
3012 public static function buildTaxonomyForRelatedPosts(\WP_Post $post)
3013 {
3014 $taxonomies = get_object_taxonomies($post->post_type);
3015 $taxQuery = [
3016 'relation' => 'OR',
3017 ];
3018
3019 foreach ($taxonomies as $taxonomy) {
3020 $taxQuery[] = [
3021 'taxonomy' => $taxonomy,
3022 'field' => 'slug',
3023 'terms' => array_filter(wp_get_object_terms($post->ID, $taxonomy, ['fields' => 'slugs']), function ($termSlug) {
3024 return strtolower($termSlug) !== 'uncategorized';
3025 }),
3026 ];
3027 }
3028
3029
3030 return $taxQuery;
3031 }
3032
3033
3034 /**
3035 * Get dynamic collectiond data
3036 *
3037 * @param object $params query object.
3038 *
3039 * @return array post array
3040 */
3041 public static function get_comments($params)
3042 {
3043 $parent = (int) ($params['parent'] ?? 0);
3044 $post_id = (int) ($params['post_id'] ?? 0);
3045 $type = ($params['type'] ?? 'comment');
3046 $sorting = isset($params['sorting']) ? $params['sorting'] : null;
3047 $filters = isset($params['filters']) ? $params['filters'] : null;
3048 // add new
3049 $current_page = isset($params['current_page']) ? $params['current_page'] : 1;
3050 $item_per_page = isset($params['item_per_page']) ? $params['item_per_page'] : 3;
3051 $offset = isset($params['offset']) ? $params['offset'] : 0;
3052
3053 // Calculate the offset
3054 $offset_cal = ($current_page - 1) * $item_per_page + $offset;
3055
3056 $args = array(
3057 'parent' => $parent,
3058 'post_id' => $post_id,
3059 'type' => $type,
3060 'number' => $item_per_page,
3061 'paged' => $current_page,
3062 'offset' => $offset_cal,
3063 'count' => false
3064 );
3065
3066 if (isset($filters) && is_array($filters)) {
3067 foreach ($filters as $filter_item) {
3068 $field_name = isset($filter_item['id']) ? $filter_item['id'] : '';
3069
3070 if (!$field_name) {
3071 continue;
3072 }
3073 switch ($field_name) {
3074 case 'comment_date':
3075 case 'comment_date_gmt': {
3076 /**
3077 * $filter_item['items'] max contain one array.
3078 * in array may contain start-date, end-date
3079 * Like: [{"start-date": "2020-01-01","end-date": "2020-01-02"}]
3080 */
3081 if (isset($filter_item['items'], $filter_item['items'][0])) {
3082 $items = $filter_item['items'];
3083 $item = $items[0]; // Get first item in array.
3084
3085 $date_query = array('column' => $field_name);
3086 $date_query['inclusive'] = true;
3087
3088 if (!empty($item['start-date'])) {
3089 $date_query['after'] = $item['start-date'];
3090 }
3091
3092 if (!empty($item['end-date'])) {
3093 $date_query['before'] = $item['end-date'];
3094 }
3095
3096 $args['date_query'] = $date_query;
3097 }
3098
3099 break;
3100 }
3101
3102 case 'comment_author': {
3103 $items = $filter_item['items']; // $items['items'] max contain one array.
3104
3105 foreach ($items as $item) {
3106 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
3107 if ($item['condition'] === 'in') {
3108 $args['author__in'] = $item['values'];
3109 }
3110
3111 if ($item['condition'] === 'not-in') {
3112 $args['author__not_in'] = $item['values'];
3113 }
3114 }
3115 }
3116
3117 break;
3118 }
3119
3120 case 'comment_approved': {
3121 $items = $filter_item['items']; // $items['items'] max contain one array.
3122
3123 foreach ($items as $item) {
3124 if (isset($item['condition'], $item['values']) && is_array($item['values'])) {
3125 if ($item['condition'] === 'in') {
3126 $args['status'] = $item['values'];
3127 }
3128 }
3129 }
3130 }
3131 }
3132 }
3133 }
3134
3135 if (isset($sorting)) {
3136 // Set the sort order (ASC/DESC)
3137 if (isset($sorting['order'])) {
3138 $args['order'] = $sorting['order'];
3139 }
3140
3141 if (isset($sorting['orderby']) && !empty($sorting['orderby'])) {
3142 $args['orderby'] = $sorting['orderby'];
3143 }
3144 }
3145
3146 $comments = get_comments($args);
3147 unset($args['number']);
3148 unset($args['paged']);
3149
3150 if (is_array($comments)) {
3151 foreach ($comments as &$comment) {
3152 $comment = (object) (array) $comment;
3153
3154 $author_posts_page_link = $comment->comment_author_url;
3155
3156 if (!$author_posts_page_link) {
3157 $author_posts_page_link = \get_author_posts_url($comment->user_id);
3158 }
3159
3160 $comment->author_profile_picture = array(
3161 'src' => get_avatar_url($comment->user_id)
3162 );
3163 $comment->author_posts_page_link = $author_posts_page_link;
3164 }
3165 }
3166
3167 // Get total comments count
3168 $total_comments = get_comments(array_merge($args, array('count' => true)));
3169 $total_comments = $total_comments - $offset;
3170
3171 // Calculate total pages
3172 $total_pages = ceil($total_comments / $item_per_page);
3173
3174 // Calculate previous and next pages
3175 $prev_page = ($current_page > 1) ? $current_page - 1 : null;
3176 $next_page = ($current_page < $total_pages) ? $current_page + 1 : null;
3177
3178 // return $comments;
3179 return array(
3180 'data' => $comments, // Raw comments data
3181 'pagination' => array(
3182 'per_page' => $item_per_page,
3183 'current_page' => $current_page,
3184 'total_pages' => $total_pages,
3185 'total_count' => $total_comments,
3186 'previous' => $prev_page,
3187 'next' => $next_page,
3188 ),
3189 );
3190 }
3191
3192
3193 /**
3194 * Remove all default assets
3195 *
3196 * @return void
3197 */
3198 public static function remove_wp_assets()
3199 {
3200 /*
3201 // Remove all WordPress actions
3202 // remove_all_actions('wp_head');
3203 // remove_all_actions('wp_print_styles');
3204 // remove_all_actions('wp_print_head_scripts');
3205 // remove_all_actions('wp_footer');
3206
3207 // // Handle `wp_head`
3208 // add_action('wp_head', 'wp_enqueue_scripts', 1);
3209 // add_action('wp_head', 'wp_print_styles', 8);
3210 // add_action('wp_head', 'wp_print_head_scripts', 9);
3211 // add_action('wp_head', 'wp_site_icon');
3212
3213 // // Handle `wp_footer`
3214 // add_action('wp_footer', 'wp_print_footer_scripts', 20);
3215
3216 // // Handle `wp_enqueue_scripts`
3217 // remove_all_actions('wp_enqueue_scripts');
3218
3219 // // Also remove all scripts hooked into after_wp_tiny_mce.
3220 // remove_all_actions('after_wp_tiny_mce');
3221 */
3222 // remove admin-bar.
3223 add_filter('show_admin_bar', '__return_false', PHP_INT_MAX);
3224 }
3225
3226 /**
3227 * Get server protocol
3228 * currently not in use
3229 *
3230 * @return string protocol name.
3231 */
3232 public static function get_protocol()
3233 {
3234 $protocol = isset($_SERVER['HTTPS']) ? 'https' : 'http';
3235 return $protocol;
3236 }
3237
3238 /**
3239 * Check if the current user has specific role ($role)
3240 *
3241 * @param string $role The role to check.
3242 * @return boolean
3243 */
3244 public static function user_is($role)
3245 {
3246 $user = wp_get_current_user();
3247 $roles = $user->roles;
3248
3249 return is_array($roles) && count($roles) && in_array($role, $roles, true) ? true : false;
3250 }
3251
3252 /**
3253 * Check if the user has access to edit/create specific/all post
3254 *
3255 * @param int $post_id post id.
3256 * @return boolean
3257 *
3258 * @deprecated
3259 * @see Kirki\App\Wordpress\User::has_edit_access()
3260 */
3261 public static function user_has_post_edit_access()
3262 {
3263 return self::has_access(
3264 array(
3265 KIRKI_ACCESS_LEVELS['FULL_ACCESS'],
3266 KIRKI_ACCESS_LEVELS['CONTENT_ACCESS'],
3267 )
3268 );
3269 }
3270
3271 /**
3272 * Check if the user has access to editor
3273 *
3274 * @return boolean
3275 */
3276 public static function user_has_editor_access()
3277 {
3278 if (isset($_GET['editor-preview-token'])) {
3279 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3280 $editor_preview_token = self::sanitize_text(isset($_GET['editor-preview-token']) ? $_GET['editor-preview-token'] : '');
3281 return self::is_post_editor_preview_token_valid($editor_preview_token);
3282 }
3283 return self::has_access(
3284 array(
3285 KIRKI_ACCESS_LEVELS['FULL_ACCESS'],
3286 KIRKI_ACCESS_LEVELS['CONTENT_ACCESS'],
3287 KIRKI_ACCESS_LEVELS['VIEW_ACCESS'],
3288 )
3289 );
3290 }
3291
3292 public static function getallheaders()
3293 {
3294 $headers = [];
3295
3296 foreach ($_SERVER as $name => $value) {
3297 if (strpos($name, 'HTTP_') === 0) {
3298 $key = substr($name, 5);
3299 } elseif (in_array($name, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'])) {
3300 $key = $name;
3301 } else {
3302 continue;
3303 }
3304
3305 // Convert HEADER_NAME → Header-Name
3306 $key = str_replace('_', ' ', strtolower($key));
3307 $key = ucwords($key);
3308 $key = str_replace(' ', '-', $key);
3309
3310 $headers[$key] = $value;
3311 }
3312
3313 return $headers;
3314 }
3315
3316 /**
3317 * Check if the request is from the editor preview.
3318 *
3319 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3320 * @see \Kirki\App\Supports\EditorPreview::has_valid_token()
3321 * @return bool
3322 */
3323 public static function is_api_call_from_editor_preview()
3324 {
3325 // Check the Editor-Preview-Token header
3326 $headers = self::getallheaders();
3327 $editor_preview_token = isset($headers['Editor-Preview-Token']) ? $headers['Editor-Preview-Token'] : null;
3328 if ($editor_preview_token && HelperFunctions::is_post_editor_preview_token_valid($editor_preview_token)) {
3329 return true;
3330 }
3331 return false;
3332 }
3333
3334 /**
3335 * Check if the Editor-Preview-Token header is valid
3336 *
3337 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3338 * @see Kirki\App\Supports\EditorPreview::has_valid_token()
3339 * @return bool
3340 */
3341 public static function is_api_header_post_editor_preview_token_valid()
3342 {
3343 // Check the Editor-Preview-Token header
3344 $headers = self::getallheaders();
3345 $editor_preview_token = isset($headers['Editor-Preview-Token']) ? $headers['Editor-Preview-Token'] : null;
3346 if (HelperFunctions::is_post_editor_preview_token_valid($editor_preview_token)) {
3347 return true;
3348 }
3349 return false;
3350 }
3351
3352
3353 /**
3354 * Check if the Editor-Preview-Token header is valid
3355 *
3356 * @deprecated Use Kirki\App\Supports\EditorPreview::has_valid_token
3357 * @see Kirki\App\Supports\EditorPreview::has_valid_token()
3358 * @return bool
3359 */
3360 public static function is_post_editor_preview_token_valid($token)
3361 {
3362 $status = HelperFunctions::get_global_data_using_key('kirki_editor_read_only_access_status');
3363 if ($status) {
3364 $kirki_editor_read_only_access_token = HelperFunctions::get_global_data_using_key('kirki_editor_read_only_access_token');
3365 if ($kirki_editor_read_only_access_token && $kirki_editor_read_only_access_token === $token) {
3366 return true;
3367 }
3368 }
3369 return false;
3370 }
3371
3372 /**
3373 * Check if the current user has specific access
3374 *
3375 * @deprecated Use Kirki\App\Wordpress\User::has_access
3376 * @see Kirki\App\Wordpress\User
3377 * @param string|string[] $access_level The access level to check access.
3378 *
3379 */
3380 public static function has_access($access_level)
3381 {
3382 if (!function_exists('wp_get_current_user')) {
3383 return false;
3384 }
3385
3386 $user = wp_get_current_user();
3387 $roles = $user->roles;
3388 $has_access = false;
3389
3390 if (is_array($access_level)) {
3391 foreach ($roles as $role) {
3392 $access = get_option('kirki_' . $role);
3393 if (!empty($access) && in_array($access, $access_level, true)) {
3394 $has_access = true;
3395 break;
3396 }
3397 }
3398 } elseif (is_string($access_level)) {
3399 foreach ($roles as $role) {
3400 $access = get_option('kirki_' . $role);
3401 if (!empty($access) && $access === $access_level) {
3402 $has_access = true;
3403 break;
3404 }
3405 }
3406 }
3407
3408 return $has_access;
3409 }
3410
3411 /**
3412 * This method will collect license info from kirki.com
3413 *
3414 * @param string $license_key user license key.
3415 * @return array license info.
3416 */
3417 public static function get_my_license_info($license_key)
3418 {
3419 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3420 $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)));
3421 $info = json_decode($info, true);
3422 if ($info && isset($info['success'])) {
3423 return $info['data'];
3424 } else {
3425 return array('key' => $license_key);
3426 }
3427 }
3428
3429 /**
3430 * HTTP get
3431 *
3432 * @param string $url api endpoint url.
3433 * @return string|bool response.
3434 *
3435 * @deprecated
3436 * @see \Kirki\Framework\Supports\Facades\Http::get()
3437 */
3438 public static function http_get($url, $args = array())
3439 {
3440 try {
3441 $response = wp_remote_get($url, $args);
3442
3443 if ((!is_wp_error($response)) && (200 === wp_remote_retrieve_response_code($response))) {
3444 $responseBody = $response['body'];
3445
3446 return $responseBody;
3447 }
3448
3449 return false;
3450 } catch (\Exception $ex) {
3451 return false;
3452 }
3453 }
3454
3455 /**
3456 * HTTP post
3457 *
3458 * @param string $url api endpoint url.
3459 * @param array $options options.
3460 * @return array|WP_Error response.
3461 *
3462 * @deprecated
3463 * @see \Kirki\Framework\Supports\Facades\Http::post()
3464 */
3465 public static function http_post($url, $options)
3466 {
3467 $res = wp_remote_post($url, $options);
3468 return $res;
3469 }
3470
3471 /**
3472 * Text domain load hooks
3473 *
3474 * @param string $handle kirki handle.
3475 * @return void
3476 */
3477 public static function load_script_text_domain($handle)
3478 {
3479 wp_set_script_translations($handle, 'kirki', KIRKI_PLUGIN_PATH . 'languages/');
3480 }
3481
3482 /**
3483 * Delete kirki related meta if a post is deleted.
3484 *
3485 * @param int $post_id post id.
3486 * @return void
3487 */
3488 public static function delete_post_with_meta_key($post_id)
3489 {
3490 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS);
3491 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_STYLE_BLOCK_IDS . '_random');
3492 delete_post_meta($post_id, 'kirki');
3493 delete_post_meta($post_id, KIRKI_META_NAME_FOR_POST_EDITOR_MODE);
3494 delete_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY);
3495 delete_post_meta($post_id, KIRKI_GLOBAL_STYLE_BLOCK_META_KEY . '_random');
3496 delete_post_meta($post_id, KIRKI_META_NAME_FOR_USED_FONT_LIST);
3497 }
3498 /**
3499 * Get the query string for the media type
3500 *
3501 * @param string $type media type.
3502 * @return string The query string.
3503 * @example HelperFunctions::get_media_type_query_string('image') => 'image/jpeg, image/png, image/gif'
3504 */
3505 public static function get_media_type_query_string($type)
3506 {
3507 return implode(
3508 ', ',
3509 array_map(
3510 function ($v) {
3511 return "'" . $v . "'";
3512 },
3513 KIRKI_SUPPORTED_MEDIA_TYPES[$type]
3514 )
3515 );
3516 }
3517
3518 /**
3519 * This is for component configuration/object javascript variable.
3520 *
3521 * @return string script tag
3522 */
3523 public static function get_empty_variables()
3524 {
3525 $s = "<script id='kirki-elements-property-empty-vars'>";
3526 $s .= 'var ' . 'kirkiSliders = [], ' . 'kirkiMaps = [], ' . 'kirkiLotties = [], ' . 'kirkiPopups = [], ' . 'kirkiLightboxes = [], ' . 'kirkiReCaptchas = [], ' . 'kirkiVideos = [], ' . 'kirkiTabs = [], ' . 'kirkiInteractions = [], ' . 'kirkiCollections = [], ' . 'kirkiDropdown = [], ' . 'kirkiForms = [];';
3527 $s .= '</script>';
3528 return $s;
3529 }
3530
3531 /**
3532 * Check kirki and kirki pro is active or not
3533 *
3534 * @param string $plugin_main_file plugin main PHP file name.
3535 * @return boolean
3536 */
3537 public static function is_plugin_activate($plugin_main_file)
3538 {
3539 if (in_array($plugin_main_file, apply_filters('active_plugins', get_option('active_plugins')), true)) {
3540 // plugin is activated.
3541 return true;
3542 }
3543 return false;
3544 }
3545
3546 /**
3547 * This function will verify nonce
3548 * ACT like API calls auth middleware
3549 *
3550 * @param string $action ajax action name.
3551 *
3552 * @return void
3553 */
3554 public static function verify_nonce($action = -1)
3555 {
3556 //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
3557 $headers = static::getallheaders();
3558 $header_nonce = isset($headers['X-Wp-Nonce']) ? static::sanitize_text($headers['X-Wp-Nonce']) : '';
3559 $nonce = $header_nonce ? $header_nonce : static::sanitize_text(isset($_GET['_wpnonce']) ? $_GET['_wpnonce'] : '');
3560
3561 if (!wp_verify_nonce($nonce, $action)) {
3562 wp_send_json_error('Not authorized');
3563 exit;
3564 }
3565 }
3566
3567 /**
3568 * Unslash and sanitize text
3569 *
3570 * @param string $v text.
3571 * @return string sanitized text.
3572 */
3573 public static function sanitize_text($v)
3574 {
3575 return sanitize_text_field(wp_unslash($v));
3576 }
3577
3578 /**
3579 * Get current WordPress session ID.
3580 * This method generates a unique session ID if none exists.
3581 *
3582 * @deprecated
3583 * @see \Kirki\App\Supports\Session::get_session_id()
3584 *
3585 * @return string Session ID.
3586 */
3587 public static function get_session_id()
3588 {
3589
3590 // First, check if a session ID is already stored in the static variable.
3591 if (self::$global_session_id) {
3592 return self::$global_session_id;
3593 }
3594
3595 // Check if a session ID exists in a cookie.
3596 if (isset($_COOKIE['kirki_session_id'])) {
3597 self::$global_session_id = sanitize_text_field($_COOKIE['kirki_session_id']);
3598 return self::$global_session_id;
3599 }
3600
3601 // Generate a new session ID.
3602 self::$global_session_id = wp_generate_uuid4();
3603 // Set the session ID in a cookie.
3604 setcookie('kirki_session_id', self::$global_session_id, time() + (DAY_IN_SECONDS * 7), COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
3605
3606 return self::$global_session_id;
3607 }
3608
3609 /**
3610 * Get session data by key using WordPress transients.
3611 *
3612 * @deprecated
3613 * @see \Kirki\App\Supports\Session::get()
3614 *
3615 * @param string $key The key of the session data to retrieve.
3616 * @return mixed|null The session data if found, null otherwise.
3617 */
3618 public static function get_session_data($key)
3619 {
3620 // Get the current session ID.
3621 $session_id = self::get_session_id();
3622
3623 // Retrieve the session data.
3624 $session_data = get_transient('kirki_session_' . $session_id);
3625
3626 if (isset($session_data[$key])) {
3627 return $session_data[$key];
3628 }
3629
3630 return null;
3631 }
3632
3633 /**
3634 * Add or update session data using WordPress transients.
3635 *
3636 * @deprecated
3637 * @see \Kirki\App\Supports\Session::put()
3638 *
3639 * @param string $key The key of the session data.
3640 * @param mixed $value The value of the session data.
3641 * @return void
3642 */
3643 public static function set_session_data($key, $value)
3644 {
3645 // Get the current session ID.
3646 $session_id = self::get_session_id();
3647
3648 // Retrieve existing session data.
3649 $session_data = get_transient('kirki_session_' . $session_id) ?: array();
3650
3651 // Update the session data.
3652 $session_data[$key] = $value;
3653
3654 // Save the updated session data with a 24-hour expiration time.
3655 set_transient('kirki_session_' . $session_id, $session_data, DAY_IN_SECONDS);
3656 }
3657
3658 /**
3659 * Delete session data by key using WordPress transients.
3660 *
3661 * @deprecated
3662 * @see \Kirki\App\Supports\Session::forget()
3663 *
3664 * @param string $key The key of the session data to delete.
3665 * @return void
3666 */
3667 public static function delete_session_data($key)
3668 {
3669 // Get the current session ID.
3670 $session_id = self::get_session_id();
3671
3672 // Retrieve existing session data.
3673 $session_data = get_transient('kirki_session_' . $session_id);
3674
3675 if (isset($session_data[$key])) {
3676 unset($session_data[$key]);
3677
3678 // Save the updated session data or delete the transient if empty.
3679 if (!empty($session_data)) {
3680 set_transient('kirki_session_' . $session_id, $session_data, DAY_IN_SECONDS);
3681 } else {
3682 delete_transient('kirki_session_' . $session_id);
3683 }
3684 }
3685 }
3686
3687 /**
3688 * Escape a post meta value for safe output.
3689 *
3690 * The front-end content pipeline (TheFrontend::replace_content) applies a
3691 * single html_entity_decode() pass after core has processed shortcodes,
3692 * which would undo a plain esc_html(). Re-encoding the ampersands (with
3693 * double_encode enabled) yields a single-escaped value in the final output
3694 * while still neutralizing markup authored by low-privileged users.
3695 *
3696 * @param mixed $value The raw post meta value.
3697 * @return string Escaped value.
3698 */
3699 public static function escape_post_meta_value($value)
3700 {
3701 if (!is_scalar($value)) {
3702 return '';
3703 }
3704
3705 return htmlspecialchars(esc_html((string) $value), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', true);
3706 }
3707
3708 /**
3709 * Is Pro user checking function.
3710 *
3711 * @return bool
3712 */
3713 public static function is_pro_user()
3714 {
3715 $common_data = WpAdmin::get_common_data(true);
3716
3717 $bool = isset($common_data['license_key']['valid']) && boolval($common_data['license_key']['valid']) === true;
3718
3719 return $bool;
3720 }
3721
3722
3723 /**
3724 * Get all view port lists
3725 *
3726 * @return string viewports list variable in script markup.
3727 */
3728 public static function get_view_port_lists()
3729 {
3730 $s = '';
3731 $list = UserData::get_view_port_list();
3732 if ($list) {
3733 $s .= "<script id='kirki-viewport-lists'>";
3734 $s .= 'var ' . 'kirkiViewports = ' . wp_json_encode($list) . ';';
3735 $s .= '</script>';
3736 }
3737 return $s;
3738 }
3739
3740 /**
3741 * Get all css variables
3742 *
3743 * @return string variables in script markup.
3744 */
3745 public static function get_kirki_css_variables_data()
3746 {
3747 $s = '';
3748 $variableData = UserData::get_kirki_variable_data();
3749 if ($variableData) {
3750 $s .= "<script id='kirki-variable-lists'>";
3751 $s .= 'var ' . 'kirkiCSSVariable = ' . wp_json_encode($variableData) . ';';
3752 $s .= '</script>';
3753 }
3754 return $s;
3755 }
3756
3757 /**
3758 * Get smooth scroll script
3759 *
3760 * @return string script markup.
3761 */
3762 public static function get_smooth_scroll_script()
3763 {
3764 $common_data = WpAdmin::get_common_data(true);
3765 $smooth_scroll_enabled = isset($common_data['smooth_scroll'], $common_data['smooth_scroll']['enabled']) ? $common_data['smooth_scroll']['enabled'] : false;
3766
3767 $smooth_scroll_value = isset($common_data['smooth_scroll'], $common_data['smooth_scroll']['value']) ? $common_data['smooth_scroll']['value'] : 1;
3768
3769 /**
3770 * User value 1 to 200
3771 *
3772 * Min duration 1s
3773 * Max duration 12s
3774 *
3775 */
3776 $duration = ceil(($smooth_scroll_value / 200) * 12);
3777
3778 $s = '';
3779
3780 if ($smooth_scroll_enabled) {
3781 $s .= "<script id='kirki-smooth-scroll'>";
3782 $s .= "
3783 window.document.addEventListener('DOMContentLoaded', function () {
3784 if (typeof KirkiSmoothScroll !== 'undefined') {
3785 const params = {
3786 autoRaf: true,
3787 anchors: true,
3788 allowNestedScroll: true,
3789 duration: $duration,
3790 };
3791
3792 const kirkiSmoothScroll = new KirkiSmoothScroll(params);
3793
3794 kirkiSmoothScroll.on('scroll');
3795 }
3796 });
3797 ";
3798 $s .= '</script>';
3799 }
3800
3801 return $s;
3802 }
3803
3804 /**
3805 * Format the date with date format
3806 *
3807 * @return string
3808 */
3809 public static function format_date($date, $format)
3810 {
3811 if ($date && $format) {
3812 $date_formats_arr = [
3813 'DD/MM/YYYY' => 'd/m/Y',
3814 'DD-MM-YYYY' => 'd-m-Y',
3815 'DD.MM.YYYY' => 'd.m.Y',
3816 'MM/DD/YYYY' => 'm/d/Y',
3817 'MM-DD-YYYY' => 'm-d-Y',
3818 'MM.DD.YYYY' => 'm.d.Y',
3819 'MMMM DD, YYYY' => 'F j, Y',
3820 'MMM DD, YYYY' => 'M j, Y',
3821 'YYYY-MM-DD' => 'Y-m-d',
3822 'YYYY/MM/DD' => 'Y/m/d',
3823 'YY.MM.DD' => 'y.m.d',
3824 'YY/MM/DD' => 'y/m/d',
3825 'YY-MM-DD' => 'y-m-d',
3826 ];
3827
3828 $timestamp = strtotime($date);
3829 if ($timestamp === false) {
3830 return $date; // fallback (avoid fatal)
3831 }
3832
3833 $datetime = (new \DateTime())->setTimestamp($timestamp);
3834 return $datetime->format($date_formats_arr[$format] ?? $format);
3835 }
3836
3837 return $date;
3838 }
3839
3840 /**
3841 * Format the time with time format
3842 *
3843 * @return string
3844 */
3845 public static function convert_time_format($timeString, $format = 'h:i a')
3846 {
3847 $dateTime = null;
3848
3849 try {
3850 $dateTime = new \DateTime($timeString);
3851 if ($dateTime && $timeString) {
3852 return $dateTime->format($format);
3853 }
3854 } catch (\Exception $e) {
3855 // if timeString is an invalid time format
3856 $parts = explode(' ', $timeString);
3857 if (count($parts) > 1) {
3858 // Remove the last part (am/pm)
3859 $time_value = $parts[0];
3860 try {
3861 $dateTime = new \DateTime($time_value);
3862 if ($dateTime && $timeString) {
3863 return $dateTime->format($format);
3864 }
3865 } catch (\Exception $e2) {
3866 return $timeString;
3867 }
3868 }
3869 return $timeString;
3870 }
3871
3872 return $timeString;
3873 }
3874
3875 /**
3876 * Get single post if has a kirki type post
3877 *
3878 * @return object|bool
3879 */
3880 public static function get_last_edited_kirki_editor_type_page()
3881 {
3882 $args = array(
3883 'post_type' => 'page', // Change to 'post' if you want to search for posts
3884 'post_status' => ['publish', 'draft'],
3885 'numberposts' => 1, // Number of results to retrieve (change as needed)
3886 'meta_key' => 'kirki_editor_mode',
3887 'meta_value' => 'kirki',
3888 'orderby' => 'modified', // Order by post date
3889 'order' => 'DESC', // Sort in descending order
3890 );
3891
3892 $pages = get_posts($args);
3893 if (count($pages) > 0) {
3894 return $pages[0];
3895 }
3896 return false;
3897 }
3898
3899 public static function get_kirki_version_from_db()
3900 {
3901 $version = wp_cache_get('kirki_version', 'kirki');
3902
3903 if (false === $version) {
3904 $version = get_option('kirki_version', '');
3905
3906 if (!empty($version)) {
3907 wp_cache_set('kirki_version', $version, 'kirki');
3908 }
3909 }
3910
3911 return $version;
3912 }
3913
3914 public static function set_kirki_version_in_db()
3915 {
3916 $version = self::get_kirki_version_from_db();
3917
3918 if ($version && version_compare($version, KIRKI_VERSION, '==')) {
3919 // No need to update the version if it's already equal to the current version.
3920 return;
3921 }
3922
3923 update_option('kirki_version', KIRKI_VERSION, false);
3924 wp_cache_set('kirki_version', KIRKI_VERSION, 'kirki');
3925 }
3926
3927 public static function accepted_file_types_by_plugin($accepted_media_types = KIRKI_SUPPORTED_MEDIA_TYPES)
3928 {
3929 $result = array();
3930
3931 foreach ($accepted_media_types as $value) {
3932 if (is_array($value)) {
3933 $result = array_merge($result, self::accepted_file_types_by_plugin($value));
3934 } else {
3935 $result[] = $value;
3936 }
3937 }
3938
3939 return $result;
3940 }
3941
3942 public static function content_manager_link_filter($dynamic_content = array(), $href = "#")
3943 {
3944 $current_post = get_post(self::get_post_id_if_possible_from_url());
3945
3946 if ($current_post->post_type === KIRKI_CONTENT_MANAGER_PREFIX) {
3947 $fields = ContentManagerHelper::get_post_type_custom_field_keys($current_post->post_parent);
3948
3949 if (isset($fields[$dynamic_content['value']]) && is_array($fields[$dynamic_content['value']])) {
3950 if ('email' === $fields[$dynamic_content['value']]['type']) {
3951 $href = "mailto:$href";
3952 } else if ('phone' === $fields[$dynamic_content['value']]['type']) {
3953 $href = "tel:$href";
3954 }
3955 }
3956 }
3957
3958 return $href;
3959 }
3960
3961 public static function check_string_has_this_tags($string, $tag)
3962 {
3963 // Check if the string contains either a <p> tag or an <h1> tag
3964 return preg_match("/<" . $tag . "[^>]*>/i", $string) === 1;
3965 }
3966
3967 /**
3968 * @deprecated
3969 * @see Kirki\App\Managers\GlobalDataManager::get_post_id()
3970 */
3971 private static function get_global_data_post_id()
3972 {
3973 $post_id = get_option('KIRKI_GLOBAL_DATA_POST_TYPE_ID', get_option('DROIP_GLOBAL_DATA_POST_TYPE_ID', false));
3974 if ($post_id) {
3975 return $post_id;
3976 } else {
3977 //this block will run only once
3978 $posts = get_posts(array(
3979 'post_type' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3980 'numberposts' => 1,
3981 ));
3982 if ($posts) {
3983 $post_id = $posts[0]->ID;
3984 } else {
3985 //create new post
3986 $post = array(
3987 'post_title' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3988 'post_type' => KIRKI_GLOBAL_DATA_POST_TYPE_NAME,
3989 'post_status' => 'draft'
3990 );
3991 $post_id = wp_insert_post($post);
3992 }
3993 update_option('KIRKI_GLOBAL_DATA_POST_TYPE_ID', $post_id, true);
3994 }
3995
3996 return $post_id;
3997 }
3998
3999 /**
4000 * Get global data using key
4001 *
4002 * @deprecated
4003 * @see Kirki\App\Managers\GlobalDataManager::get()
4004 */
4005 public static function get_global_data_using_key($key)
4006 {
4007 //first get post using KIRKI_GLOBAL_DATA_POST_TYPE_NAME post_type name. if not found then create new one.
4008 $post_id = self::get_global_data_post_id();
4009 if (metadata_exists('post', $post_id, $key)) {
4010 return get_post_meta($post_id, $key, true);
4011 }
4012
4013 // this block will run only once for a legacy option key.
4014 $value = get_option($key, null);
4015 if (null !== $value) {
4016 update_post_meta($post_id, $key, $value);
4017 delete_option($key);
4018 }
4019
4020 return $value;
4021 }
4022
4023 /**
4024 * Get global data using key
4025 *
4026 * @deprecated
4027 * @see Kirki\App\Managers\GlobalDataManager::update()
4028 */
4029 public static function update_global_data_using_key($key, $value)
4030 {
4031 $post_id = self::get_global_data_post_id();
4032 update_post_meta($post_id, $key, $value);
4033
4034 }
4035
4036 public static function get_template_data_if_current_page_is_kirki_template()
4037 {
4038 $custom_data = get_query_var('kirki_custom_data');
4039 $data = false;
4040 $builder_div = '';
4041 if ($custom_data && isset($custom_data['kirki_template_content'])) {
4042 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : null);
4043 $load_for = HelperFunctions::sanitize_text(isset($_GET['load_for']) ? $_GET['load_for'] : '');
4044
4045 if ($action === KIRKI_EDITOR_ACTION && $load_for === 'kirki-iframe' && !str_contains($custom_data['kirki_template_content'], 'kirki-builder')) {
4046 $template_edit_url = HelperFunctions::get_post_url_arr_from_post_id($custom_data['kirki_template_id'], ['editor_url' => true])['editor_url'];
4047 $builder_div = '<div id="' . 'kirki-builder' . '" template-error="' . $template_edit_url . '"></div>';
4048 }
4049
4050 $data = array(
4051 'content' => $custom_data['kirki_template_content'] . $builder_div,
4052 'template_id' => $custom_data['kirki_template_id']
4053 );
4054 }
4055 return $data;
4056 }
4057
4058 public static function get_custom_data_if_current_page_is_kirki_custom_post()
4059 {
4060 $custom_data = get_query_var('kirki_custom_data');
4061 $data = false;
4062 $builder_div = '';
4063 if ($custom_data && isset($custom_data['kirki_custom_post_content'])) {
4064 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : null);
4065 $load_for = HelperFunctions::sanitize_text(isset($_GET['load_for']) ? $_GET['load_for'] : '');
4066
4067 if ($action === KIRKI_EDITOR_ACTION && $load_for === 'kirki-iframe' && !str_contains($custom_data['kirki_custom_post_content'], 'kirki-builder')) {
4068 $template_edit_url = HelperFunctions::get_post_url_arr_from_post_id($custom_data['kirki_custom_post_id'], ['editor_url' => true])['editor_url'];
4069 $builder_div = '<div id="' . 'kirki-builder' . '" template-error="' . $template_edit_url . '"></div>';
4070 }
4071
4072 $data = array(
4073 'content' => $custom_data['kirki_custom_post_content'] . $builder_div,
4074 'post_id' => $custom_data['kirki_custom_post_id']
4075 );
4076 }
4077 return $data;
4078 }
4079
4080 /**
4081 * Validate slug for a post.
4082 *
4083 * @param int|null $post_id
4084 * @param string $post_type
4085 * @param string $post_name
4086 * @return bool
4087 *
4088 * @deprecated Use Kirki\App\Supports\ContentManager::validate_slug() instead.
4089 * @see Kirki\App\Supports\ContentManager
4090 */
4091 public static function validate_slug($post_id, $post_type, $post_name)
4092 {
4093 global $wpdb;
4094 // Execute the query
4095 $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));
4096
4097 // If a post with the same slug exists, return false
4098 if ($result) {
4099 return false;
4100 }
4101
4102 // If no post with the same slug exists, return true
4103 return true;
4104 }
4105
4106 /**
4107 * Summary of find_utility_page_for_this_context
4108 * @param mixed $type
4109 * @param mixed $get_by id | type.
4110 * @return mixed
4111 */
4112 public static function find_utility_page_for_this_context($value = '404', $get_by = 'type')
4113 {
4114 $utility_pages = Page::fetch_list('kirki_utility', true, array('publish'));
4115 if (count($utility_pages) > 0) {
4116 foreach ($utility_pages as $key => $page) {
4117 if ($get_by === 'type') {
4118 if ($page['utility_page_type'] === $value) {
4119 return $page;
4120 }
4121 } else if ($get_by === 'id') {
4122 if ($page['id'] === (int) $value) {
4123 return $page;
4124 }
4125 }
4126 }
4127 }
4128 return false;
4129 }
4130 public static function get_current_page_context()
4131 {
4132 $context = array(); // {id, type}
4133
4134 $obj = get_queried_object();
4135
4136 if (is_404()) {
4137 $context['type'] = '404';
4138 } else if ($obj instanceof WP_Post) {
4139 $context['id'] = $obj->ID;
4140 $context['type'] = 'post';
4141 } else if ($obj instanceof WP_User) {
4142 $context['id'] = $obj->ID;
4143 $context['type'] = 'user';
4144 } elseif ($obj instanceof WP_Term) {
4145 $context['id'] = $obj->term_id;
4146 $context['type'] = 'term';
4147 }
4148 // elseif ($obj instanceof WP_Post_Type) {
4149 // echo 'This is a WP_Post_Type object';
4150 // } elseif (is_null($obj)) {
4151 // echo 'No queried object (null)';
4152 // }
4153 else {
4154 $kirki_utility_page_type = get_query_var('kirki_utility_page_type');
4155 $kirki_utility_page_id = get_query_var('kirki_utility_page_id');
4156 if (!empty($kirki_utility_page_type)) {
4157 if (!self::check_utility_page_visibility_condition($kirki_utility_page_type)) {
4158 $context['type'] = '404';
4159 } else {
4160 $context['type'] = 'kirki_utility';
4161 $context['kirki_utility_page_type'] = $kirki_utility_page_type;
4162 $context['kirki_utility_page_id'] = $kirki_utility_page_id;
4163 }
4164 }
4165 }
4166 return $context;
4167 }
4168
4169
4170 /**
4171 * Get utility page slug using type.
4172 * { type: 'login', title: 'Login' },
4173 * { type: 'sign_up', title: 'Registration' },
4174 * { type: 'forgot_password', title: 'Forgot Password' },
4175 * { type: 'reset_password', title: 'Reset Password' },
4176 * { type: 'retrive_username', title: 'Retrive Username' },
4177 * { type: '404', title: '404' },
4178 *
4179 * @param string $type //utility page type.
4180 * @return string||bool //string or false.
4181 */
4182 public static function get_utility_page_url($type)
4183 {
4184 $utility_pages = Page::fetch_list('kirki_utility', true, array('publish'));
4185 foreach ($utility_pages as $key => $page) {
4186 $utility_page_type = $page['utility_page_type'];
4187
4188 $slug = $page['slug'];
4189 if ($utility_page_type === $type) {
4190 return home_url('/' . $slug);
4191 }
4192 }
4193 return false;
4194 }
4195 public static function check_utility_page_visibility_condition($type)
4196 {
4197 if ($type === 'login' || $type === 'sign_up' || $type === 'forgot_password' || $type === 'reset_password' || $type === 'retrive_username') {
4198 // Check if the user is already logged in
4199 if (is_user_logged_in()) {
4200 return false; // User is logged in, so the page should not be visible
4201 }
4202 // Add other conditions based on the type if needed
4203 if ($type === 'signup') {
4204 // Example: Check if registrations are enabled in WordPress
4205 if (!get_option('users_can_register')) {
4206 return false; // Registration is disabled
4207 }
4208 }
4209 // If the user is not logged in and other conditions pass, allow the page to be visible
4210 return true;
4211 }
4212 // If the type is not 'login' or 'signup', return false by default
4213 return true;
4214 }
4215
4216 /**
4217 * @deprecated
4218 * @see \Kirki\Framework\Supports\Facades\File::delete()
4219 */
4220 public static function delete_directory($dirname)
4221 {
4222 global $wp_filesystem;
4223 if (empty($wp_filesystem)) {
4224 require_once ABSPATH . 'wp-admin/includes/file.php';
4225 WP_Filesystem();
4226 }
4227
4228 if ($wp_filesystem->exists($dirname)) {
4229 return $wp_filesystem->delete($dirname, true);
4230 }
4231
4232 return false;
4233 }
4234
4235 /**
4236 * @deprecated
4237 * @see \Kirki\App\Supports\FileHandler::get_temp_folder_path()
4238 */
4239 public static function get_temp_folder_path()
4240 {
4241 $upload_dir = wp_upload_dir();
4242 $temp_folder = 'kirki_temp';
4243 $temp_folder_path = $upload_dir['basedir'] . '/' . $temp_folder;
4244
4245 return $temp_folder_path;
4246 }
4247
4248 /**
4249 * @deprecated
4250 * @see \Kirki\App\Managers\GlobalDataManager::get_initial_viewports()
4251 */
4252 public static function get_initial_view_ports()
4253 {
4254 return json_decode('{
4255 "active":"md",
4256 "scale":1,
4257 "zoom":1,
4258 "width":1200,
4259 "mdWidth":"",
4260 "defaults":[
4261 "md",
4262 "tablet",
4263 "mobileLandscape",
4264 "mobile"
4265 ],
4266 "list":{
4267 "md":{
4268 "value":1200,
4269 "scale":1,
4270 "minWidth":1200,
4271 "maxWidth":1200,
4272 "title":"Desktop",
4273 "icon":"desktop",
4274 "activeIcon":"desktop-hover"
4275 },
4276 "tablet":{
4277 "value":991,
4278 "scale":1,
4279 "minWidth":991,
4280 "maxWidth":991,
4281 "title":"Tablet",
4282 "icon":"tablet-default",
4283 "activeIcon":"tablet-hover"
4284 },
4285 "mobileLandscape":{
4286 "value":767,
4287 "scale":1,
4288 "minWidth":767,
4289 "maxWidth":767,
4290 "title":"Landscape",
4291 "icon":"phone-hr-default",
4292 "activeIcon":"phone-hr-hover"
4293 },
4294 "mobile":{
4295 "value":575,
4296 "scale":1,
4297 "minWidth":575,
4298 "maxWidth":575,
4299 "title":"Mobile",
4300 "icon":"phone-vr-default",
4301 "activeIcon":"phone-vr-hover"
4302 }
4303 }
4304 }', true);
4305 }
4306
4307 /**
4308 * @deprecated
4309 * @see \Kirki\App\Supports\FileHandler::download_zip_from_remote()
4310 */
4311 public static function download_zip_from_remote($remote_file, $new_name)
4312 {
4313 return FileHandler::download_zip_from_remote($remote_file, $new_name);
4314 // $file_ext = explode('.', $remote_file); // ['file', 'ext']
4315 // $file_ext = strtolower(end($file_ext)); // 'ext'
4316 // $allowed = ['zip'];
4317 // if (!in_array($file_ext, $allowed)) {
4318 // return false;
4319 // }
4320
4321 // try {
4322 // // error_reporting(E_ALL);
4323 // // ini_set('display_errors', 1);
4324 // // Download the file from the remote server.
4325 // // Create a stream context to disable SSL verification
4326 // $options = [
4327 // "http" => [
4328 // "method" => "GET",
4329 // "header" => "User-Agent: WordPress\r\n"
4330 // ],
4331 // "ssl" => [
4332 // "verify_peer" => false, // Disable verification of the peer's certificate
4333 // "verify_peer_name" => false // Disable verification of the peer's name
4334 // ]
4335 // ];
4336 // $context = stream_context_create($options);
4337 // $file_contents = file_get_contents($remote_file, false, $context);
4338
4339 // // Save the file locally.
4340 // if ($file_contents !== false) {
4341 // // Local path to save the downloaded file.
4342 // $local_file = wp_upload_dir()['basedir'] . '/' . $new_name;
4343 // file_put_contents($local_file, $file_contents);
4344 // return $local_file;
4345 // }
4346 // } catch (\Throwable $th) {
4347 // // throw $th;
4348 // }
4349 // return false;
4350 }
4351
4352 public static function filterZipFile($zip, $zip_file_path)
4353 {
4354 // Temporary filtered ZIP path
4355 $filtered_zip_path = sys_get_temp_dir() . '/filtered.zip';
4356 $filtered_zip = new \ZipArchive;
4357
4358 if ($filtered_zip->open($filtered_zip_path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === TRUE) {
4359 // Loop through all files in the archive
4360 for ($i = 0; $i < $zip->numFiles; $i++) {
4361 $filename = $zip->getNameIndex($i);
4362 $file_path = 'zip://' . $zip_file_path . '#' . $filename;
4363
4364 // Get MIME type based on file extensioncm_f
4365 $file_mime = self::getMimeTypeByExtension($filename);
4366
4367 // Additional JSON validation
4368 if ($file_mime === 'application/json' && !self::isJsonFile($file_path)) {
4369 $file_mime = 'text/plain'; // Fallback if not valid JSON
4370 }
4371
4372 // Check if the file MIME type matches supported types
4373 $is_supported = false;
4374 foreach (KIRKI_SUPPORTED_MEDIA_TYPES as $types) {
4375 if (in_array($file_mime, $types)) {
4376 $is_supported = true;
4377 break;
4378 }
4379 }
4380
4381 // Add the file to the filtered archive if supported
4382 if ($is_supported) {
4383 $file_contents = $zip->getFromIndex($i);
4384 $filtered_zip->addFromString($filename, $file_contents);
4385 }
4386 }
4387
4388 $filtered_zip->close();
4389 return $filtered_zip_path;
4390 } else {
4391 return false;
4392 }
4393 }
4394
4395 // Helper function to get MIME type by file extension
4396 private static function getMimeTypeByExtension($filename)
4397 {
4398 $extension_to_mime = [
4399 'json' => 'application/json',
4400 'jpg' => 'image/jpeg',
4401 'jpeg' => 'image/jpeg',
4402 'png' => 'image/png',
4403 'gif' => 'image/gif',
4404 'webp' => 'image/webp',
4405 'svg' => 'image/svg+xml',
4406 'pdf' => 'application/pdf',
4407 'mp4' => 'video/mp4',
4408 'ogg' => 'audio/ogg',
4409 'lottie' => 'text/plain',
4410 'mov' => 'video/quicktime',
4411 'mp3' => 'audio/mpeg',
4412 'wav' => 'audio/wav',
4413
4414 // Add more extensions as needed
4415 ];
4416
4417 $ext = pathinfo($filename, PATHINFO_EXTENSION);
4418 return $extension_to_mime[strtolower($ext)] ?? 'application/octet-stream';
4419 }
4420
4421 // Helper function to validate JSON file content
4422 private static function isJsonFile($file_path)
4423 {
4424 $file_contents = @file_get_contents($file_path);
4425 $trimmed = trim($file_contents);
4426 return $trimmed[0] === '{' || $trimmed[0] === '[';
4427 }
4428
4429 public static function is_remote_url($url)
4430 {
4431 // Parse the URL to get components
4432 $parsed_url = wp_parse_url($url);
4433 return isset($parsed_url['scheme']);
4434 }
4435
4436 public static function is_element_accessible($access)
4437 {
4438 switch ($access) {
4439 case 'all':
4440 return true; // Accessible to everyone
4441
4442 case 'guest':
4443 return !is_user_logged_in();
4444
4445 case 'logged-in':
4446 return is_user_logged_in(); // Accessible to any logged-in user
4447
4448 case 'admin':
4449 // Administrators and Super Admins
4450 return current_user_can('manage_options');
4451
4452 case 'editor':
4453 // Editors, Admins, and Super Admins can see this
4454 return current_user_can('edit_pages');
4455
4456 case 'author':
4457 // Authors, Editors, Admins, etc.
4458 return current_user_can('publish_posts');
4459
4460 case 'subscriber':
4461 // Subscribers and EVERY logged-in user above them
4462 return current_user_can('read');
4463
4464 default:
4465 return false; // Safely hide if the access rule is unrecognized
4466 }
4467 }
4468
4469 /**
4470 * Find symbol for post id using condition
4471 * it will find and return selected symbols html and css;
4472 *
4473 * @param string $type : symbol type.
4474 * @param string $post : post object.
4475 * @return symbol || bool(false)
4476 */
4477 public static function find_symbol_for_this_page($type)
4478 {
4479 $all_symbols = Symbol::fetch_list(true, false);
4480 foreach ($all_symbols as $key => $symbol) {
4481 if (isset($symbol['setAs']) && $symbol['setAs'] === $type) {
4482 return $symbol;
4483 }
4484 }
4485 return false;
4486 }
4487 /**
4488 * Get Custom Header
4489 * it will find and return selected symbols html and css;
4490 *
4491 * @param string $type stymbol type header|footer.
4492 * @param string $html if true the function will return html otherwise return symbol object.
4493 * @return string|object custom section html or stymbol object.
4494 */
4495 public static function get_page_custom_section($type, $html = true)
4496 {
4497 $show = apply_filters('kirki_show_custom_section_' . $type, true);
4498 if (!$show) {
4499 return '';
4500 }
4501
4502
4503 $symbol = self::find_symbol_for_this_page($type);
4504 if (!$html) {
4505 return $symbol;
4506 }
4507
4508 if (isset(self::$custom_sections[$type])) {
4509 return self::$custom_sections[$type];
4510 }
4511
4512 $s = self::isShowWPThemeHeaderFooter() ? '' : ' '; // this is for disableing theme header footer forcefully if is_show_wp_theme_header_footer is false
4513
4514 if ($symbol) {
4515 $action = HelperFunctions::sanitize_text(isset($_GET['action']) ? $_GET['action'] : '');
4516 $symbol_data = $symbol['symbolData'];
4517 $set_as = isset($symbol['setAs']) ? $symbol['setAs'] : '';
4518
4519 $post_id = self::get_post_id_if_possible_from_url();
4520 $template_data = self::get_template_data_if_current_page_is_kirki_template();
4521 if ($template_data)
4522 $post_id = $template_data['template_id'];
4523
4524
4525 $custom_page_data = self::get_custom_data_if_current_page_is_kirki_custom_post();
4526 if ($custom_page_data)
4527 $post_id = $custom_page_data['post_id'];
4528
4529 $is_page_symbol_disabled = get_post_meta($post_id, KIRKI_META_NAME_FOR_PAGE_HF_SYMBOL_DISABLE_STATUS, true);
4530 if (isset($is_page_symbol_disabled) && is_array($is_page_symbol_disabled) && isset($is_page_symbol_disabled[$type])) {
4531 $is_page_symbol_disabled = $is_page_symbol_disabled[$type];
4532 } else {
4533 $is_page_symbol_disabled = false;
4534 }
4535
4536 $params = array(
4537 'blocks' => $symbol_data['data'],
4538 'style_blocks' => $symbol_data['styleBlocks'],
4539 'root' => $symbol_data['root'],
4540 'post_id' => $symbol['id'],
4541 'options' => [],
4542 'get_style' => true,
4543 'get_variable' => false,
4544 'should_take_app_script' => false,
4545 'prefix' => 'kirki-s' . $symbol['id']
4546 );
4547 if ($action === KIRKI_EDITOR_ACTION) {
4548 $extra_attr_for_hf_symbol = '';
4549 if ($is_page_symbol_disabled)
4550 $extra_attr_for_hf_symbol = ' style="display:none;"';
4551 $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
4552 } else if (!$is_page_symbol_disabled) {
4553 $params['should_take_app_script'] = true;
4554 $params['get_variable'] = true;
4555 $s = self::get_html_using_preview_script($params);
4556 } else if ($is_page_symbol_disabled) {
4557 $s = '<!-- ' . $type . ' is disabled -->';
4558 }
4559 }
4560
4561 $s = do_shortcode($s);
4562
4563 self::$custom_sections[$type] = $s;
4564
4565 return $s;
4566 }
4567
4568 public static function isShowWPThemeHeaderFooter()
4569 {
4570 $common_data = WpAdmin::get_common_data(true);
4571 return $common_data['is_show_wp_theme_header_footer'];
4572 }
4573
4574 /**
4575 * Check if a value is considered true or false.
4576 *
4577 * @param mixed $value The value to check.
4578 * @return bool Returns true if the value is considered "truthy", otherwise false.
4579 *
4580 * @deprecated
4581 * @see function \Kirki\App\is_truthy()
4582 */
4583 public static function isTruthy($value): bool
4584 {
4585 return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
4586 }
4587
4588 /**
4589 * Get the upload directory path has upload or write permission.
4590 *
4591 * @deprecated
4592 * @see Kirki\Framework\Supports\Facades\File::is_writable()
4593 * @see function Kirki\App\get_upload_directory()
4594 */
4595 public static function get_upload_dir_has_write_permission()
4596 {
4597 if (!function_exists('request_filesystem_credentials')) {
4598 require_once ABSPATH . 'wp-admin/includes/file.php';
4599 }
4600
4601 if (WP_Filesystem()) {
4602 global $wp_filesystem;
4603 $upload_dir = wp_upload_dir();
4604 return $wp_filesystem->is_writable($upload_dir['basedir']);
4605 }
4606
4607 return false;
4608 }
4609
4610 /**
4611 * $name = [] or string
4612 * @deprecated
4613 * @see Kirki\App\Supports\Template::add_prefix_to_class_name()
4614 */
4615 public static function add_prefix_to_class_name($prefix, $name)
4616 {
4617 if (is_array($name)) {
4618 foreach ($name as $key => $c) {
4619 $c = strtolower($c);
4620 if (in_array($c, KIRKI_PRESERVED_CLASS_LIST)) {
4621 $name[$key] = $c;
4622 } else {
4623 $name[$key] = $prefix ? strtolower($prefix) . '-' . $c : $c;
4624 }
4625 }
4626 } else {
4627 $name = strtolower($name);
4628 if (!in_array($name, KIRKI_PRESERVED_CLASS_LIST)) {
4629 $name = $prefix ? strtolower($prefix) . '-' . $name : $name;
4630 }
4631 }
4632 return $name;
4633 }
4634
4635 public static function checkVisibilityConditions($element, $options)
4636 {
4637 $conditions = $element['properties']['visibilityConditions'] ?? [];
4638 if (!count($conditions))
4639 return true;
4640 foreach ($conditions as $and_group) {
4641 $and_result = true;
4642 foreach ($and_group as $condition) {
4643 $source = (string) ($condition['source'] ?? 'kirki');
4644 $condition_result = apply_filters('kirki_visibility_condition_check_' . $source, false, $condition, $options);
4645 if (!$condition_result) {
4646 $and_result = false;
4647 break; // If any condition fails in AND group
4648 }
4649 }
4650 if ($and_result) {
4651 return true; // If any OR group passes
4652 }
4653 }
4654 return false; // No group passed
4655 }
4656
4657 private static function update_slider_style_blocks($blocks, $styles)
4658 {
4659
4660 $slider_mask_styleIds = [];
4661 $slider_item_styleIds = [];
4662
4663
4664 // slider_item
4665 foreach ($blocks as $id => $block) {
4666 // Check if the block has a 'name' key
4667 if (isset($block['name'])) {
4668 if (isset($block['name']) && $block['name'] === 'slider_mask') {
4669 // Loop through each item in $block['styleIds']
4670 foreach ($block['styleIds'] as $styleId) {
4671 // Check if the styleId is NOT already in the $slider_mask_styleIds array
4672 if (!in_array($styleId, $slider_mask_styleIds)) {
4673 // If not present, push it into the array
4674 $slider_mask_styleIds[] = $styleId;
4675 }
4676 }
4677 }
4678
4679 if (isset($block['name']) && $block['name'] === 'slider_item') {
4680 // Loop through each item in $block['styleIds']
4681 foreach ($block['styleIds'] as $styleId) {
4682 // Check if the styleId is NOT already in the $slider_item_styleIds array
4683 if (!in_array($styleId, $slider_item_styleIds)) {
4684 // If not present, push it into the array
4685 $slider_item_styleIds[] = $styleId;
4686 }
4687 }
4688 }
4689 }
4690 }
4691
4692 if (count($slider_mask_styleIds) > 0) {
4693 foreach ($slider_mask_styleIds as $styleId) {
4694 if ($styles[$styleId]) {
4695 $style = $styles[$styleId];
4696 $style_variants = $style['variant'];
4697
4698 $styleVariants = [];
4699
4700 if ($style_variants && count($style_variants) > 0) {
4701 foreach ($style_variants as $key => $css) {
4702 $css = preg_replace('/overflow\s*:\s*hidden;?/', '', $css);
4703 $css = preg_replace('/pointer-events\s*:\s*none;?/', '', $css);
4704
4705 $css = trim($css);
4706 $styleVariants[$key] = $css;
4707 }
4708 }
4709 $styles[$styleId]['variant'] = $styleVariants;
4710 }
4711 }
4712 }
4713
4714
4715 if (count($slider_item_styleIds) > 0) {
4716 foreach ($slider_item_styleIds as $styleId) {
4717 $style = $styles[$styleId];
4718 $style_variants = $style['variant'];
4719
4720 $styleVariants = [];
4721
4722 if ($style_variants && count($style_variants) > 0) {
4723 foreach ($style_variants as $key => $css) {
4724 $css = preg_replace('/position\s*:\s*absolute;?/', '', $css);
4725 $css = preg_replace('/display\s*:\s*none;?/', '', $css);
4726
4727 $css = trim($css);
4728 $styleVariants[$key] = $css;
4729 }
4730
4731 $styles[$styleId]['variant'] = $styleVariants;
4732 }
4733 }
4734 }
4735
4736 return $styles;
4737 }
4738
4739 public static function handle_legacy_slider_class()
4740 {
4741 $data = Page::get_all_data_by_kirki_meta_key();
4742
4743 foreach ($data as $key => $value) {
4744 $post_id = $value['post_id']; // ID of the post
4745 $meta_key = $value['meta_key']; // Meta key name
4746 $meta_value = $value['meta_value']; // Serialized meta value
4747
4748 $meta_value = unserialize($meta_value, ['allowed_classes' => false]); // Convert serialized data to array
4749
4750 // If the meta value has a 'blocks' key, handle it as a full page data
4751 if (isset($meta_value['blocks'])) {
4752 $blocks = $meta_value['blocks']; // Get the blocks array
4753 $styles = self::get_page_styleblocks($post_id);
4754 $updated_styles = self::update_slider_style_blocks($blocks, $styles); // Update block names
4755
4756 self::update_page_styleblocks($post_id, $updated_styles);
4757 } else {
4758 // If no 'blocks' key, treat entire value as blocks array
4759 $blocks = $meta_value;
4760 $post = get_post($post_id);
4761
4762
4763 if (isset($post->post_type) && $post->post_type === 'kirki_symbol') {
4764 $styles = $blocks['styleBlocks'];
4765 $updated_styles = self::update_slider_style_blocks($blocks['data'], $styles);
4766 $blocks['styleBlocks'] = $updated_styles;
4767
4768 update_post_meta($post_id, $meta_key, $blocks);
4769 }
4770 }
4771 }
4772 }
4773
4774 public static function handle_legacy_slider_default_class()
4775 {
4776 $styles = self::get_global_data_using_key(KIRKI_GLOBAL_STYLE_BLOCK_META_KEY);
4777 if (!$styles) {
4778 $styles = array();
4779 }
4780 if (count($styles) > 0) {
4781 if (isset($styles['kirki_slider_slide'])) {
4782 // Handle kirki_slider_mask
4783 if (isset($styles['kirki_slider_mask'])) {
4784 $variants = $styles['kirki_slider_mask']['variant'];
4785 if (count($variants) > 0) {
4786 $styleVariants = [];
4787 foreach ($variants as $key => $css) {
4788 $css = preg_replace('/overflow\s*:\s*hidden;?/', '', $css);
4789 $css = preg_replace('/pointer-events\s*:\s*none;?/', '', $css);
4790 $css = trim($css);
4791 $styleVariants[$key] = $css;
4792 }
4793 $styles['kirki_slider_mask']['variant'] = $styleVariants;
4794 }
4795 }
4796
4797 // Handle kirki_slider_slide
4798 if (isset($styles['kirki_slider_slide'])) {
4799 $variants = $styles['kirki_slider_slide']['variant'];
4800 if (count($variants) > 0) {
4801 $styleVariants = [];
4802 foreach ($variants as $key => $css) {
4803 $css = preg_replace('/position\s*:\s*absolute;?/', '', $css);
4804 $css = preg_replace('/display\s*:\s*none;?/', '', $css);
4805 $css = trim($css);
4806 $styleVariants[$key] = $css;
4807 }
4808 $styles['kirki_slider_slide']['variant'] = $styleVariants;
4809 }
4810 }
4811 }
4812 }
4813
4814 }
4815
4816 public static function get_current_item_index($index, $options)
4817 {
4818 $pagination = isset($options['pagination']) ? $options['pagination'] : false;
4819 $items_per_page = isset($options['items_per_page']) ? $options['items_per_page'] : 3;
4820 $page_no = isset($options['page_no']) ? $options['page_no'] : 1;
4821
4822 if ($pagination && $items_per_page > 0 && $page_no > 0) {
4823 // Calculate the start index based on the current page and items per page
4824 $index = (($page_no - 1) * $items_per_page) + $index;
4825 }
4826
4827 return $index;
4828 }
4829
4830 public static function convertToBytes($val)
4831 {
4832 $val = trim($val);
4833 $last = strtolower($val[strlen($val) - 1]);
4834 $val = (int) $val;
4835 switch ($last) {
4836 case 'g':
4837 $val *= 1024;
4838 case 'm':
4839 $val *= 1024;
4840 case 'k':
4841 $val *= 1024;
4842 }
4843 return $val;
4844 }
4845
4846 /**
4847 * @deprecated
4848 * @see Kirki\App\Supports\Canvas::get_full_canvas_template_path()
4849 */
4850 public static function get_kirki_full_canvas_template_path()
4851 {
4852 return self::normalize_kirki_full_canvas_template_path(KIRKI_FULL_CANVAS_TEMPLATE_PATH);
4853 }
4854
4855 /**
4856 * @deprecated
4857 * @see Kirki\App\Supports\Canvas::normalize_full_canvas_template_path()
4858 */
4859 public static function normalize_kirki_full_canvas_template_path($path)
4860 {
4861 // replace if has kirki-pro => kirki
4862 if (strpos($path, 'kirki-pro') !== false) {
4863 $path = str_replace('kirki-pro', 'kirki', $path);
4864 }
4865 return $path;
4866 }
4867 public static function normalize_variable_mode($mode)
4868 {
4869 if (!$mode) {
4870 return ['color' => 'inherit', 'size' => 'inherit', 'text-style' => 'inherit', 'font-family' => 'inherit'];
4871 }
4872 // if v is string
4873 if (is_string($mode)) {
4874 return ['color' => $mode, 'size' => $mode, 'text-style' => $mode, 'font-family' => $mode];
4875 }
4876 if (is_array($mode)) {
4877 return $mode;
4878 }
4879 return $mode;
4880 }
4881
4882 /**
4883 * Whether the target url is a safe, externally reachable http(s) URL.
4884 *
4885 * Rejects loopback, private, link-local and cloud-metadata addresses so a
4886 * planted form config cannot be used to probe the server's own network.
4887 *
4888 * @param string $url The URL.
4889 * @return bool
4890 */
4891 public static function is_safe_url($url) {
4892 $scheme = wp_parse_url($url, PHP_URL_SCHEME);
4893 $host = wp_parse_url($url, PHP_URL_HOST);
4894
4895 if (!is_string($scheme) || !in_array(strtolower($scheme), array('http', 'https'), true)) {
4896 return false;
4897 }
4898
4899 if (!is_string($host) || '' === $host) {
4900 return false;
4901 }
4902
4903 if (filter_var($host, FILTER_VALIDATE_IP)) {
4904 return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
4905 }
4906
4907 $ip = gethostbyname($host);
4908
4909 if (!filter_var($ip, FILTER_VALIDATE_IP) || $ip === $host) {
4910 return false;
4911 }
4912
4913 return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
4914 }
4915 }
4916