PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
← All changes | includes/widgets/Media_Grid/Media_Grid.php +641 -48 51.1.37 → 51.1.87 View file →
@@ -18,11 +18,9 @@
18 18 }
19 19
20 20 class Media_Grid extends Widget_Base
21 21 {
22 -
23 22
24 -
25 23 public function get_name()
26 24 {
27 25 return 'king-addons-media-grid';
28 26 }
@@ -28,9 +26,9 @@
28 26 }
29 27
30 28 public function get_title()
31 29 {
32 - return esc_html__('Image Grid & Slider/Carousel/Gallery', 'king-addons');
30 + return esc_html__('Advanced Image Gallery', 'king-addons');
33 31 }
34 32
35 33 public function get_icon()
36 34 {
@@ -51,8 +49,9 @@
51 49
52 50 public function get_script_depends()
53 51 {
54 52 return [
53 + 'jquery',
55 54 KING_ADDONS_ASSETS_UNIQUE_KEY . '-grid-media',
56 55 KING_ADDONS_ASSETS_UNIQUE_KEY . '-isotope-kng',
57 56 KING_ADDONS_ASSETS_UNIQUE_KEY . '-slick-slick',
58 57 KING_ADDONS_ASSETS_UNIQUE_KEY . '-lightgallery-lightgallery'
@@ -801,10 +800,11 @@
801 800
802 801 $this->add_control(
803 802 'query_manual_attachment',
804 803 [
805 - 'label' => esc_html__('Add Images', 'king-addons'),
804 + 'label' => esc_html__('Add Images (Legacy)', 'king-addons'),
806 805 'type' => Controls_Manager::GALLERY,
806 + 'description' => esc_html__('Used only when "Add Images & Videos" is empty. Prefer the repeater below for images and videos together.', 'king-addons'),
807 807 'separator' => 'before',
808 808 'dynamic' => [
809 809 'active' => true,
810 810 ],
@@ -813,9 +813,79 @@
813 813 ],
814 814 ]
815 815 );
816 816
817 + $media_repeater = new Repeater();
818 +
819 + $media_repeater->add_control(
820 + 'item_type',
821 + [
822 + 'label' => esc_html__('Media Type', 'king-addons'),
823 + 'type' => Controls_Manager::SELECT,
824 + 'default' => 'image',
825 + 'options' => [
826 + 'image' => esc_html__('Image', 'king-addons'),
827 + 'video' => esc_html__('Video', 'king-addons'),
828 + ],
829 + ]
830 + );
831 +
832 + $media_repeater->add_control(
833 + 'item_image',
834 + [
835 + 'label' => esc_html__('Image', 'king-addons'),
836 + 'type' => Controls_Manager::MEDIA,
837 + 'dynamic' => [
838 + 'active' => true,
839 + ],
840 + 'description' => esc_html__('For videos, this image is used as the cover/poster in the gallery.', 'king-addons'),
841 + ]
842 + );
843 +
844 + $media_repeater->add_control(
845 + 'item_video',
846 + [
847 + 'label' => esc_html__('Video File', 'king-addons'),
848 + 'type' => Controls_Manager::MEDIA,
849 + 'media_types' => [
850 + 'video',
851 + ],
852 + 'condition' => [
853 + 'item_type' => 'video',
854 + ],
855 + ]
856 + );
857 +
817 858 $this->add_control(
859 + 'query_manual_media',
860 + [
861 + 'label' => esc_html__('Add Images & Videos', 'king-addons'),
862 + 'type' => Controls_Manager::REPEATER,
863 + 'fields' => $media_repeater->get_controls(),
864 + 'default' => [],
865 + 'title_field' => '{{{ item_type.charAt(0).toUpperCase() + item_type.slice(1) }}}',
866 + 'separator' => 'before',
867 + 'condition' => [
868 + 'query_selection' => 'manual',
869 + ],
870 + ]
871 + );
872 +
873 + $this->add_control(
874 + 'query_include_videos',
875 + [
876 + 'label' => esc_html__('Include Videos', 'king-addons'),
877 + 'type' => Controls_Manager::SWITCHER,
878 + 'default' => '',
879 + 'description' => esc_html__('Include video files from the Media Library in the Auto selection.', 'king-addons'),
880 + 'condition' => [
881 + 'query_selection' => 'dynamic',
882 + ],
883 + 'separator' => 'before',
884 + ]
885 + );
886 +
887 + $this->add_control(
818 888 'query_posts_per_page',
819 889 [
820 890 'label' => esc_html__('Items Per Page', 'king-addons'),
821 891 'type' => Controls_Manager::NUMBER,
@@ -881,8 +951,79 @@
881 951 'label_block' => true
882 952 ]
883 953 );
884 954
955 + $this->add_responsive_control(
956 + 'layout_fitrows_media_height',
957 + [
958 + 'label' => esc_html__('Media Height', 'king-addons'),
959 + 'type' => Controls_Manager::SLIDER,
960 + 'size_units' => ['px', 'vh'],
961 + 'range' => [
962 + 'px' => [
963 + 'min' => 50,
964 + 'max' => 800,
965 + ],
966 + 'vh' => [
967 + 'min' => 10,
968 + 'max' => 100,
969 + ],
970 + ],
971 + 'default' => [
972 + 'unit' => 'px',
973 + 'size' => 280,
974 + ],
975 + 'selectors' => [
976 + '{{WRAPPER}} .king-addons-grid[data-settings*="fitRows"] .king-addons-grid-media-wrap' => 'height: {{SIZE}}{{UNIT}};',
977 + ],
978 + 'condition' => [
979 + 'layout_select' => 'fitRows',
980 + ],
981 + 'separator' => 'before',
982 + ]
983 + );
984 +
985 + $this->add_responsive_control(
986 + 'layout_fitrows_image_fit',
987 + [
988 + 'label' => esc_html__('Image Fit', 'king-addons'),
989 + 'type' => Controls_Manager::SELECT,
990 + 'options' => [
991 + 'cover' => esc_html__('Cover', 'king-addons'),
992 + 'contain' => esc_html__('Contain', 'king-addons'),
993 + ],
994 + 'default' => 'cover',
995 + 'selectors' => [
996 + '{{WRAPPER}} .king-addons-grid[data-settings*="fitRows"] .king-addons-grid-image-wrap img' => 'object-fit: {{VALUE}};',
997 + ],
998 + 'condition' => [
999 + 'layout_select' => 'fitRows',
1000 + ],
1001 + ]
1002 + );
1003 +
1004 + $this->add_responsive_control(
1005 + 'layout_fitrows_image_fit_position',
1006 + [
1007 + 'label' => esc_html__('Image Fit Position', 'king-addons'),
1008 + 'type' => Controls_Manager::SELECT,
1009 + 'options' => [
1010 + 'center center' => esc_html__('Center', 'king-addons'),
1011 + 'top center' => esc_html__('Top', 'king-addons'),
1012 + 'bottom center' => esc_html__('Bottom', 'king-addons'),
1013 + 'center left' => esc_html__('Left', 'king-addons'),
1014 + 'center right' => esc_html__('Right', 'king-addons'),
1015 + ],
1016 + 'default' => 'center center',
1017 + 'selectors' => [
1018 + '{{WRAPPER}} .king-addons-grid[data-settings*="fitRows"] .king-addons-grid-image-wrap img' => 'object-position: {{VALUE}};',
1019 + ],
1020 + 'condition' => [
1021 + 'layout_select' => 'fitRows',
1022 + ],
1023 + ]
1024 + );
1025 +
885 1026 $this->add_group_control(
886 1027 Group_Control_Image_Size::get_type(),
887 1028 [
888 1029 'name' => 'layout_image_crop',
@@ -1833,9 +1974,9 @@
1833 1974 'element_animation_size' => 'large',
1834 1975 'element_sharing_trigger_icon' => 'fas fa-share',
1835 1976 ],
1836 1977 ],
1837 - 'title_field' => '{{{ element_select.charAt(0).toUpperCase() + element_select.slice(1) }}}',
1978 + 'title_field' => '{{ element_select.charAt(0).toUpperCase() + element_select.slice(1) }}',
1838 1979 ]
1839 1980 );
1840 1981
1841 1982 $this->end_controls_section();
@@ -2192,8 +2333,23 @@
2192 2333 'return_value' => 'true',
2193 2334 ]
2194 2335 );
2195 2336
2337 + $this->add_control(
2338 + 'video_play_icon',
2339 + [
2340 + 'label' => esc_html__('Video Play Icon', 'king-addons'),
2341 + 'type' => Controls_Manager::ICONS,
2342 + 'skin' => 'inline',
2343 + 'label_block' => false,
2344 + 'default' => [
2345 + 'value' => 'fas fa-play',
2346 + 'library' => 'fa-solid',
2347 + ],
2348 + 'separator' => 'before',
2349 + ]
2350 + );
2351 +
2196 2352 $this->add_control_lightbox_popup_thumbnails();
2197 2353
2198 2354 $this->add_control_lightbox_popup_thumbnails_default();
2199 2355
@@ -4207,13 +4363,13 @@
4207 4363
4208 4364 $this->add_control(
4209 4365 'lightbox_color',
4210 4366 [
4211 - 'label' => esc_html__('Color', 'king-addons'),
4367 + 'label' => esc_html__('Text Color', 'king-addons'),
4212 4368 'type' => Controls_Manager::COLOR,
4213 4369 'default' => '#ffffff',
4214 4370 'selectors' => [
4215 - '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span' => 'color: {{VALUE}}',
4371 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span, {{WRAPPER}} .king-addons-grid-item-title .inner-block a' => 'color: {{VALUE}}',
4216 4372 ],
4217 4373 ]
4218 4374 );
4219 4375
@@ -4259,13 +4415,13 @@
4259 4415
4260 4416 $this->add_control(
4261 4417 'lightbox_color_hr',
4262 4418 [
4263 - 'label' => esc_html__('Color', 'king-addons'),
4419 + 'label' => esc_html__('Text Color', 'king-addons'),
4264 4420 'type' => Controls_Manager::COLOR,
4265 4421 'default' => '#ffffff',
4266 4422 'selectors' => [
4267 - '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span:hover' => 'color: {{VALUE}}',
4423 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span:hover, {{WRAPPER}} .king-addons-grid-item-title .inner-block a:hover' => 'color: {{VALUE}} !important;',
4268 4424 ],
4269 4425 ]
4270 4426 );
4271 4427
@@ -4320,13 +4476,80 @@
4320 4476 'separator' => 'after',
4321 4477 ]
4322 4478 );
4323 4479
4480 + // Icon Size Control - separate from typography
4481 + $this->add_responsive_control(
4482 + 'lightbox_icon_size',
4483 + [
4484 + 'label' => esc_html__('Icon Size', 'king-addons'),
4485 + 'type' => Controls_Manager::SLIDER,
4486 + 'size_units' => ['px', 'em', 'rem'],
4487 + 'range' => [
4488 + 'px' => [
4489 + 'min' => 5,
4490 + 'max' => 100,
4491 + 'step' => 1,
4492 + ],
4493 + 'em' => [
4494 + 'min' => 0.5,
4495 + 'max' => 5,
4496 + 'step' => 0.1,
4497 + ],
4498 + 'rem' => [
4499 + 'min' => 0.5,
4500 + 'max' => 5,
4501 + 'step' => 0.1,
4502 + ],
4503 + ],
4504 + 'default' => [
4505 + 'unit' => 'px',
4506 + 'size' => 16,
4507 + ],
4508 + 'selectors' => [
4509 + '{{WRAPPER}} .king-addons-grid-item-lightbox i' => 'font-size: {{SIZE}}{{UNIT}};',
4510 + '{{WRAPPER}} .king-addons-grid-item-lightbox svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
4511 + ],
4512 + 'separator' => 'before',
4513 + ]
4514 + );
4515 +
4516 + // Icon Color Control - separate from text color
4517 + $this->add_responsive_control(
4518 + 'lightbox_icon_color',
4519 + [
4520 + 'label' => esc_html__('Icon Color', 'king-addons'),
4521 + 'type' => Controls_Manager::COLOR,
4522 + 'default' => '#ffffff',
4523 + 'selectors' => [
4524 + '{{WRAPPER}} .king-addons-grid-item-lightbox i' => 'color: {{VALUE}};',
4525 + '{{WRAPPER}} .king-addons-grid-item-lightbox svg' => 'fill: {{VALUE}};',
4526 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span i' => 'color: {{VALUE}};',
4527 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span svg' => 'fill: {{VALUE}};',
4528 + ],
4529 + ]
4530 + );
4531 +
4532 + // Icon Hover Color Control
4533 + $this->add_responsive_control(
4534 + 'lightbox_icon_color_hover',
4535 + [
4536 + 'label' => esc_html__('Icon Color (Hover)', 'king-addons'),
4537 + 'type' => Controls_Manager::COLOR,
4538 + 'selectors' => [
4539 + '{{WRAPPER}} .king-addons-grid-item-lightbox span:hover i' => 'color: {{VALUE}} !important;',
4540 + '{{WRAPPER}} .king-addons-grid-item-lightbox span:hover svg' => 'fill: {{VALUE}} !important;',
4541 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span:hover i' => 'color: {{VALUE}} !important;',
4542 + '{{WRAPPER}} .king-addons-grid-item-lightbox .inner-block > span:hover svg' => 'fill: {{VALUE}} !important;',
4543 + ],
4544 + ]
4545 + );
4546 +
4324 4547 $this->add_group_control(
4325 4548 Group_Control_Typography::get_type(),
4326 4549 [
4327 4550 'name' => 'lightbox_typography',
4328 - 'selector' => '{{WRAPPER}} .king-addons-grid-item-lightbox'
4551 + 'selector' => '{{WRAPPER}} .king-addons-grid-item-lightbox, {{WRAPPER}} .king-addons-grid-item-title .inner-block a',
4329 4552 ]
4330 4553 );
4331 4554
4332 4555 $this->add_control(
@@ -6640,20 +6863,314 @@
6640 6863 }
6641 6864
6642 6865 public function get_max_num_pages()
6643 6866 {
6867 + $settings = $this->get_settings_for_display();
6868 +
6869 + if ('manual' === ($settings['query_selection'] ?? '')) {
6870 + return $this->get_manual_media_max_num_pages($settings);
6871 + }
6872 +
6644 6873 $query = new WP_Query($this->get_main_query_args());
6645 - $pages = (int)ceil($query->max_num_pages);
6874 + $pages = (int) ceil($query->max_num_pages);
6646 6875 wp_reset_postdata();
6876 +
6647 6877 return $pages;
6648 6878 }
6649 6879
6880 + /**
6881 + * Returns the total page count for manual media items.
6882 + *
6883 + * @param array|null $settings Widget settings.
6884 + * @return int Total number of pages.
6885 + */
6886 + public function get_manual_media_max_num_pages($settings = null)
6887 + {
6888 + $settings = $settings ?? $this->get_settings_for_display();
6889 + $items = $this->get_manual_media_items($settings);
6890 + $total = count($items);
6891 + $per_page = (int) ($settings['query_posts_per_page'] ?? 10);
6892 +
6893 + if ($per_page <= 0 || 0 === $total) {
6894 + return 1;
6895 + }
6896 +
6897 + $offset = empty($settings['query_offset']) ? 0 : (int) $settings['query_offset'];
6898 + $remaining = max(0, $total - $offset);
6899 +
6900 + return max(1, (int) ceil($remaining / $per_page));
6901 + }
6902 +
6903 + /**
6904 + * Returns manual media items for the current pagination page.
6905 + *
6906 + * @param array $settings Widget settings.
6907 + * @return array<int, array<string, mixed>> Paginated manual media items.
6908 + */
6909 + public function get_paginated_manual_media_items($settings)
6910 + {
6911 + $items = $this->get_manual_media_items($settings);
6912 + $per_page = (int) ($settings['query_posts_per_page'] ?? 10);
6913 +
6914 + if ($per_page <= 0) {
6915 + return $items;
6916 + }
6917 +
6918 + $paged = get_query_var('paged') ? (int) get_query_var('paged') : ((int) get_query_var('page') ?: 1);
6919 + $offset = empty($settings['query_offset']) ? 0 : (int) $settings['query_offset'];
6920 + $slice_offset = $offset + ($paged - 1) * $per_page;
6921 +
6922 + return array_slice($items, $slice_offset, $per_page);
6923 + }
6924 +
6925 + /**
6926 + * Checks whether an attachment is a video file.
6927 + *
6928 + * @param int $attachment_id Attachment post ID.
6929 + * @return bool True when the attachment mime type is video.
6930 + */
6931 + public function is_video_attachment($attachment_id)
6932 + {
6933 + $mime_type = get_post_mime_type($attachment_id);
6934 +
6935 + return is_string($mime_type) && 0 === strpos($mime_type, 'video/');
6936 + }
6937 +
6938 + /**
6939 + * Returns a video mime type based on the file extension.
6940 + *
6941 + * @param string $url Video file URL.
6942 + * @return string Video mime type.
6943 + */
6944 + public function get_video_mime_from_url($url)
6945 + {
6946 + $extension = strtolower((string) pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
6947 + $map = [
6948 + 'mp4' => 'video/mp4',
6949 + 'webm' => 'video/webm',
6950 + 'ogg' => 'video/ogg',
6951 + 'ogv' => 'video/ogg',
6952 + ];
6953 +
6954 + return $map[$extension] ?? 'video/mp4';
6955 + }
6956 +
6957 + /**
6958 + * Returns a poster image URL for a video attachment when available.
6959 + *
6960 + * @param int $attachment_id Video attachment ID.
6961 + * @return string Poster image URL or an empty string.
6962 + */
6963 + public function get_attachment_video_poster_url($attachment_id)
6964 + {
6965 + $image_src = wp_get_attachment_image_src($attachment_id, 'large');
6966 + if (!empty($image_src[0])) {
6967 + return $image_src[0];
6968 + }
6969 +
6970 + $metadata = wp_get_attachment_metadata($attachment_id);
6971 + if (!empty($metadata['image']['file']) && !empty($metadata['file'])) {
6972 + $upload_dir = wp_upload_dir();
6973 +
6974 + return trailingslashit($upload_dir['baseurl']) . trailingslashit(dirname($metadata['file'])) . $metadata['image']['file'];
6975 + }
6976 +
6977 + return '';
6978 + }
6979 +
6980 + /**
6981 + * Builds manual media items from the repeater or legacy gallery control.
6982 + *
6983 + * @param array $settings Widget settings.
6984 + * @return array<int, array<string, mixed>> Manual media items.
6985 + */
6986 + public function get_manual_media_items($settings)
6987 + {
6988 + $items = [];
6989 +
6990 + if (!empty($settings['query_manual_media'])) {
6991 + foreach ($settings['query_manual_media'] as $index => $row) {
6992 + $item_type = $row['item_type'] ?? 'image';
6993 + $item_image = $row['item_image'] ?? [];
6994 + $item_video = $row['item_video'] ?? [];
6995 +
6996 + if ('video' === $item_type) {
6997 + if (empty($item_video['url'])) {
6998 + continue;
6999 + }
7000 + if (empty($item_image['id']) && empty($item_image['url'])) {
7001 + continue;
7002 + }
7003 + } elseif (empty($item_image['id']) && empty($item_image['url'])) {
7004 + continue;
7005 + }
7006 +
7007 + $items[] = [
7008 + 'key' => 'manual-' . $index,
7009 + 'type' => $item_type,
7010 + 'image' => $item_image,
7011 + 'video' => $item_video,
7012 + ];
7013 + }
7014 +
7015 + return $items;
7016 + }
7017 +
7018 + if (empty($settings['query_manual_attachment'])) {
7019 + return $items;
7020 + }
7021 +
7022 + foreach ($settings['query_manual_attachment'] as $index => $attachment) {
7023 + if (empty($attachment['id'])) {
7024 + continue;
7025 + }
7026 +
7027 + $items[] = [
7028 + 'key' => 'legacy-' . $index,
7029 + 'type' => 'image',
7030 + 'image' => $attachment,
7031 + 'video' => [],
7032 + ];
7033 + }
7034 +
7035 + return $items;
7036 + }
7037 +
7038 + /**
7039 + * Renders the play icon overlay for video gallery items.
7040 + *
7041 + * @param array $settings Widget settings.
7042 + * @return void
7043 + */
7044 + public function render_video_play_icon($settings)
7045 + {
7046 + echo '<span class="king-addons-grid-video-play-icon" aria-hidden="true">';
7047 + Icons_Manager::render_icon($settings['video_play_icon'], ['aria-hidden' => 'true']);
7048 + echo '</span>';
7049 + }
7050 +
7051 + /**
7052 + * Renders a hidden HTML5 video element referenced by the lightbox.
7053 + *
7054 + * @param string $video_url Video file URL.
7055 + * @param string $element_id Hidden container element ID.
7056 + * @return void
7057 + */
7058 + public function render_video_html_element($video_url, $element_id)
7059 + {
7060 + if ('' === $video_url) {
7061 + return;
7062 + }
7063 +
7064 + echo '<div class="king-addons-grid-video-html" id="' . esc_attr($element_id) . '" style="display:none;">';
7065 + echo '<video class="lg-video-object lg-html5" controls preload="none" playsinline>';
7066 + echo '<source src="' . esc_url($video_url) . '" type="' . esc_attr($this->get_video_mime_from_url($video_url)) . '">';
7067 + echo '</video>';
7068 + echo '</div>';
7069 + }
7070 +
7071 + /**
7072 + * Renders a gallery media thumbnail for image or video items.
7073 + *
7074 + * @param array $settings Widget settings.
7075 + * @param string $item_key Unique item key for lightbox video references.
7076 + * @param string $media_type Media type: image or video.
7077 + * @param array $image_data Image or cover attachment data.
7078 + * @param string $video_url Video file URL for video items.
7079 + * @return void
7080 + */
7081 + public function render_media_thumbnail($settings, $item_key, $media_type, $image_data, $video_url = '')
7082 + {
7083 + $image_id = !empty($image_data['id']) ? (int) $image_data['id'] : 0;
7084 + $is_video = ('video' === $media_type && '' !== $video_url);
7085 + $video_html_id = 'king-addons-grid-video-' . $this->get_id() . '-' . $item_key;
7086 +
7087 + if ($image_id) {
7088 + $src = Group_Control_Image_Size::get_attachment_image_src($image_id, 'layout_image_crop', $settings);
7089 + $alt = ('' === wp_get_attachment_caption($image_id)) ? get_the_title($image_id) : wp_get_attachment_caption($image_id);
7090 + $lightbox_src = $is_video ? $src : wp_get_attachment_url($image_id);
7091 + } else {
7092 + $src = $image_data['url'] ?? '';
7093 + $alt = '';
7094 + $lightbox_src = $src;
7095 + }
7096 +
7097 + if ($is_video && '' === $src && $image_id) {
7098 + $src = $this->get_attachment_video_poster_url($image_id);
7099 + }
7100 +
7101 + if ($is_video) {
7102 + $lightbox_src = '' !== $src ? $src : $video_url;
7103 + }
7104 +
7105 + $wrap_classes = 'king-addons-grid-image-wrap';
7106 + if ($is_video) {
7107 + $wrap_classes .= ' king-addons-grid-video-lightbox-item';
7108 + }
7109 +
7110 + echo '<div class="' . esc_attr($wrap_classes) . '" data-src="' . esc_url($lightbox_src) . '"';
7111 + if ($is_video) {
7112 + echo ' data-poster="' . esc_url($lightbox_src) . '"';
7113 + echo ' data-html="#' . esc_attr($video_html_id) . '"';
7114 + echo ' data-kng-video-url="' . esc_url($video_url) . '"';
7115 + }
7116 + echo '>';
7117 +
7118 + if ('' !== $src) {
7119 + echo '<img src="' . esc_url($src) . '" alt="' . esc_attr($alt) . '" class="king-addons-animation-timing-' . esc_attr($settings['image_effects_animation_timing']) . '">';
7120 + } elseif ($is_video) {
7121 + echo '<span class="king-addons-grid-video-cover-placeholder" aria-hidden="true"></span>';
7122 + }
7123 +
7124 + if ($is_video) {
7125 + $this->render_video_play_icon($settings);
7126 + }
7127 +
7128 + echo '</div>';
7129 +
7130 + if ($is_video) {
7131 + $this->render_video_html_element($video_url, $video_html_id);
7132 + }
7133 + }
7134 +
7135 + /**
7136 + * Renders one gallery article for manual media items.
7137 + *
7138 + * @param array $settings Widget settings.
7139 + * @param array $item Manual media item data.
7140 + * @return void
7141 + */
7142 + public function render_manual_grid_item($settings, $item)
7143 + {
7144 + $item_type = $item['type'] ?? 'image';
7145 + $video_url = ('video' === $item_type && !empty($item['video']['url'])) ? $item['video']['url'] : '';
7146 + $post_id = !empty($item['image']['id']) ? (int) $item['image']['id'] : 0;
7147 + $item_classes = ['king-addons-grid-item', 'elementor-clearfix'];
7148 +
7149 + if ('video' === $item_type) {
7150 + $item_classes[] = 'king-addons-grid-item-video';
7151 + }
7152 +
7153 + echo '<article class="' . esc_attr(implode(' ', $item_classes)) . '">';
7154 + echo '<div class="king-addons-grid-item-inner">';
7155 + $this->get_elements_by_location('above', $settings, $post_id);
7156 + echo '<div class="king-addons-grid-media-wrap' . esc_attr($this->get_image_effect_class($settings)) . '">';
7157 + $this->render_media_thumbnail($settings, $item['key'], $item_type, $item['image'] ?? [], $video_url);
7158 + echo '<div class="king-addons-grid-media-hover king-addons-animation-wrap">';
7159 + $this->render_media_overlay($settings, $post_id);
7160 + $this->get_elements_by_location('over', $settings, $post_id);
7161 + echo '</div></div>';
7162 + $this->get_elements_by_location('below', $settings, $post_id);
7163 + echo '</div></article>';
7164 + }
7165 +
6650 7166 public function get_main_query_args()
6651 7167 {
6652 7168 $s = $this->get_settings_for_display();
6653 7169 $author = !empty($s['query_author']) ? implode(',', $s['query_author']) : '';
6654 7170 $paged = get_query_var('paged') ? get_query_var('paged') : (get_query_var('page') ? get_query_var('page') : 1);
6655 - $offset = ($paged - 1) * $s['query_posts_per_page'] + (empty($s['query_offset']) ? 0 : $s['query_offset']);
7171 + $offset = ($paged - 1) * Core::jsNumber($s, 'query_posts_per_page', 10)
7172 + + (empty($s['query_offset']) ? 0 : Core::jsNumber($s, 'query_offset', 0));
6656 7173
6657 7174 // Remove premium-only randomize if not available
6658 7175 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
6659 7176 $s['query_randomize'] = '';
@@ -6659,13 +7176,18 @@
6659 7176 $s['query_randomize'] = '';
6660 7177 $s['order_posts'] = 'date';
6661 7178 }
6662 7179 $order_by = $s['query_randomize'] !== '' ? $s['query_randomize'] : $s['order_posts'];
6663 - if ('manual' === $s['query_selection']) $order_by = 'post__in';
7180 + if ('manual' === $s['query_selection']) {
7181 + $order_by = 'post__in';
7182 + }
6664 7183
7184 + $include_videos = !empty($s['query_include_videos']);
7185 + $post_mime_type = $include_videos ? ['image', 'video'] : 'image';
7186 +
6665 7187 $args = [
6666 7188 'post_type' => 'attachment',
6667 - 'post_mime_type' => 'image', // <-- Only images
7189 + 'post_mime_type' => $post_mime_type,
6668 7190 'post_status' => 'inherit',
6669 7191 'tax_query' => $this->get_tax_query_args(),
6670 7192 'post__not_in' => $s['query_exclude_attachment'],
6671 7193 'posts_per_page' => $s['query_posts_per_page'],
@@ -6676,17 +7198,29 @@
6676 7198 ];
6677 7199
6678 7200 if ('manual' === $s['query_selection']) {
6679 7201 $post_ids = [];
6680 - if (!empty($s['query_manual_attachment'])) {
7202 +
7203 + if (!empty($s['query_manual_media'])) {
7204 + foreach ($this->get_manual_media_items($s) as $item) {
7205 + if (!empty($item['image']['id'])) {
7206 + $post_ids[] = (int) $item['image']['id'];
7207 + }
7208 + }
7209 + } elseif (!empty($s['query_manual_attachment'])) {
6681 7210 foreach ($s['query_manual_attachment'] as $attachment) {
6682 7211 $post_ids[] = $attachment['id'];
6683 7212 }
6684 7213 }
7214 +
7215 + if (empty($post_ids)) {
7216 + $post_ids = [0];
7217 + }
7218 +
6685 7219 $orderby = ('' === $s['query_randomize']) ? 'post__in' : 'rand';
6686 7220 $args = [
6687 7221 'post_type' => 'attachment',
6688 - 'post_mime_type' => 'image', // <-- Only images
7222 + 'post_mime_type' => $post_mime_type,
6689 7223 'post_status' => 'inherit',
6690 7224 'post__in' => $post_ids,
6691 7225 'orderby' => $orderby,
6692 7226 'posts_per_page' => $s['query_posts_per_page'],
@@ -6716,33 +7250,35 @@
6716 7250 }
6717 7251
6718 7252 public function get_animation_class($data, $object)
6719 7253 {
6720 - $class = '';
6721 - if ('none' !== $data[$object . '_animation']) {
6722 - $class .= ' king-addons-' . $object . '-' . $data[$object . '_animation'];
6723 - $class .= ' king-addons-anim-size-' . $data[$object . '_animation_size'];
6724 - $class .= ' king-addons-animation-timing-' . $data[$object . '_animation_timing'];
6725 - if ('yes' === $data[$object . '_animation_tr']) $class .= ' king-addons-anim-transparency';
7254 + $animation = Grid_Ajax_Security::sanitize_animation($data[$object . '_animation'] ?? 'none');
7255 + if ('none' === $animation) {
7256 + return '';
6726 7257 }
7258 +
7259 + $class = ' king-addons-' . sanitize_html_class((string) $object) . '-' . $animation;
7260 + $class .= ' king-addons-anim-size-' . Grid_Ajax_Security::sanitize_animation_size($data[$object . '_animation_size'] ?? 'large');
7261 + $class .= ' king-addons-animation-timing-' . Grid_Ajax_Security::sanitize_animation_timing($data[$object . '_animation_timing'] ?? 'ease-default');
7262 +
7263 + if ('yes' === Grid_Ajax_Security::sanitize_yes_no_switcher($data[$object . '_animation_tr'] ?? '')) {
7264 + $class .= ' king-addons-anim-transparency';
7265 + }
7266 +
6727 7267 return $class;
6728 7268 }
6729 7269
6730 7270 public function get_image_effect_class($s)
6731 7271 {
7272 + $effect = Grid_Ajax_Security::sanitize_image_effect($s['image_effects'] ?? 'none');
6732 7273 $class = '';
6733 - if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
6734 - if (in_array($s['image_effects'], ['pro-zi', 'pro-zo', 'pro-go', 'pro-bo'])) {
6735 - $s['image_effects'] = 'none';
6736 - }
7274 + if ('none' !== $effect) {
7275 + $class .= ' king-addons-' . $effect;
6737 7276 }
6738 - if ('none' !== $s['image_effects']) {
6739 - $class .= ' king-addons-' . $s['image_effects'];
6740 - }
6741 - if ('slide' !== $s['image_effects']) {
6742 - $class .= ' king-addons-effect-size-' . $s['image_effects_size'];
7277 + if ('slide' !== $effect) {
7278 + $class .= ' king-addons-effect-size-' . Grid_Ajax_Security::sanitize_image_effect_size($s['image_effects_size'] ?? 'medium');
6743 7279 } else {
6744 - $class .= ' king-addons-effect-dir-' . $s['image_effects_direction'];
7280 + $class .= ' king-addons-effect-dir-' . Grid_Ajax_Security::sanitize_image_effect_direction($s['image_effects_direction'] ?? 'bottom');
6745 7281 }
6746 7282 return $class;
6747 7283 }
6748 7284
@@ -6748,18 +7284,28 @@
6748 7284
6749 7285 public function render_post_thumbnail($settings)
6750 7286 {
6751 7287 $id = get_the_ID();
6752 - $src = Group_Control_Image_Size::get_attachment_image_src($id, 'layout_image_crop', $settings);
6753 - $alt = ('' === wp_get_attachment_caption($id)) ? get_the_title() : wp_get_attachment_caption($id);
6754 - echo '<div class="king-addons-grid-image-wrap" data-src="' . esc_url(wp_get_attachment_url($id)) . '">';
6755 - echo '<img src="' . esc_url($src) . '" alt="' . wp_kses_post($alt) . '" class="king-addons-animation-timing-' . esc_html($settings['image_effects_animation_timing']) . '">';
6756 - echo '</div>';
7288 + $is_video = $this->is_video_attachment($id);
7289 + $video_url = $is_video ? (string) wp_get_attachment_url($id) : '';
7290 +
7291 + $this->render_media_thumbnail(
7292 + $settings,
7293 + 'attachment-' . $id,
7294 + $is_video ? 'video' : 'image',
7295 + ['id' => $id],
7296 + $video_url
7297 + );
6757 7298 }
6758 7299
6759 - public function render_media_overlay($s)
7300 + public function render_media_overlay($s, $post_id = 0)
6760 7301 {
6761 - echo '<div class="king-addons-grid-media-hover-bg ' . $this->get_animation_class($s, 'overlay') . '" data-url="' . esc_url(get_the_permalink(get_the_ID())) . '">';
7302 + if (!$post_id) {
7303 + $post_id = get_the_ID();
7304 + }
7305 +
7306 + $overlay_url = $post_id ? get_the_permalink($post_id) : '#';
7307 + echo '<div class="king-addons-grid-media-hover-bg ' . esc_attr($this->get_animation_class($s, 'overlay')) . '" data-url="' . esc_url($overlay_url) . '">';
6762 7308 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
6763 7309 if ('' !== $s['overlay_image']['url']) {
6764 7310 echo '<img src="' . esc_url($s['overlay_image']['url']) . '">';
6765 7311 }
@@ -6894,9 +7440,12 @@
6894 7440 public function render_post_lightbox($s, $class, $post_id)
6895 7441 {
6896 7442 echo '<div class="' . esc_attr($class) . '"><div class="inner-block">';
6897 7443 $lightbox_source = get_the_post_thumbnail_url($post_id);
6898 - if ('audio' === get_post_format()) {
7444 +
7445 + if ($this->is_video_attachment($post_id)) {
7446 + $lightbox_source = $this->get_attachment_video_poster_url($post_id);
7447 + } elseif ('audio' === get_post_format()) {
6899 7448 if ('meta' === $s['element_lightbox_pfa_select']) {
6900 7449 $meta_value = get_post_meta($post_id, $s['element_lightbox_pfa_meta'], true);
6901 7450 if (false === strpos($meta_value, '<iframe ')) {
6902 7451 add_filter('oembed_result', $media_grid_filter = function ($html) {
@@ -6925,9 +7474,11 @@
6925 7474 }
6926 7475 if (isset($video_url)) $lightbox_source = $video_url;
6927 7476 }
6928 7477 }
6929 - if (!$lightbox_source) $lightbox_source = wp_get_attachment_url($post_id);
7478 + if (!$lightbox_source && !$this->is_video_attachment($post_id)) {
7479 + $lightbox_source = wp_get_attachment_url($post_id);
7480 + }
6930 7481 echo '<span data-src="' . esc_url($lightbox_source) . '">';
6931 7482 if ('before' === $s['element_extra_text_pos']) {
6932 7483 echo '<span class="king-addons-grid-extra-text-left">' . esc_html($s['element_extra_text']) . '</span>';
6933 7484 }
@@ -7341,13 +7892,13 @@
7341 7892 'selector' => '.king-addons-grid-image-wrap',
7342 7893 'iframeMaxWidth' => '60%',
7343 7894 'hash' => false,
7344 7895 'autoplay' => $s['lightbox_popup_autoplay'],
7345 - 'pause' => $s['lightbox_popup_pause'] * 1000,
7896 + 'pause' => Core::jsNumber($s, 'lightbox_popup_pause', 5) * 1000,
7346 7897 'progressBar' => $s['lightbox_popup_progressbar'],
7347 7898 'counter' => $s['lightbox_popup_counter'],
7348 7899 'controls' => $s['lightbox_popup_arrows'],
7349 - 'getCaptionFromTitleOrAlt' => $s['lightbox_popup_captions'],
7900 + 'getCaptionFromTitleOrAlt' => false,
7350 7901 'thumbnail' => $s['lightbox_popup_thumbnails'],
7351 7902 'showThumbByDefault' => $s['lightbox_popup_thumbnails_default'],
7352 7903 'share' => $s['lightbox_popup_sharing'],
7353 7904 'zoom' => $s['lightbox_popup_zoom'],
@@ -7391,9 +7942,9 @@
7391 7942 'pause' => $s['lightbox_popup_pause'] * 1000,
7392 7943 'progressBar' => $s['lightbox_popup_progressbar'],
7393 7944 'counter' => $s['lightbox_popup_counter'],
7394 7945 'controls' => $s['lightbox_popup_arrows'],
7395 - 'getCaptionFromTitleOrAlt' => $s['lightbox_popup_captions'],
7946 + 'getCaptionFromTitleOrAlt' => false,
7396 7947 'thumbnail' => $s['lightbox_popup_thumbnails'],
7397 7948 'showThumbByDefault' => $s['lightbox_popup_thumbnails_default'],
7398 7949 'share' => $s['lightbox_popup_sharing'],
7399 7950 'zoom' => $s['lightbox_popup_zoom'],
@@ -7412,8 +7963,43 @@
7412 7963
7413 7964 protected function render()
7414 7965 {
7415 7966 $s = $this->get_settings();
7967 +
7968 + if ('manual' === $s['query_selection']) {
7969 + $manual_items = $this->get_paginated_manual_media_items($s);
7970 +
7971 + if (empty($manual_items)) {
7972 + echo '<h2>' . esc_html($s['query_not_found_text']) . '</h2>';
7973 + return;
7974 + }
7975 +
7976 + if ('slider' !== $s['layout_select']) {
7977 + $this->render_grid_filters($s);
7978 + $this->add_grid_settings($s);
7979 + $render_attribute = $this->get_render_attribute_string('grid-settings');
7980 + } else {
7981 + $this->add_slider_settings($s);
7982 + $render_attribute = $this->get_render_attribute_string('slider-settings');
7983 + }
7984 +
7985 + echo '<section class="king-addons-grid king-addons-media-grid elementor-clearfix" ' . $render_attribute . '>';
7986 + foreach ($manual_items as $item) {
7987 + $this->render_manual_grid_item($s, $item);
7988 + }
7989 + echo '</section>';
7990 +
7991 + if ('slider' === $s['layout_select']) {
7992 + echo '<div class="king-addons-grid-slider-arrow-container">';
7993 + echo '<div class="king-addons-grid-slider-prev-arrow king-addons-grid-slider-arrow" id="king-addons-grid-slider-prev-' . esc_attr($this->get_id()) . '">' . Core::getIcon($s['layout_slider_nav_icon'], '') . '</div>';
7994 + echo '<div class="king-addons-grid-slider-next-arrow king-addons-grid-slider-arrow" id="king-addons-grid-slider-next-' . esc_attr($this->get_id()) . '">' . Core::getIcon($s['layout_slider_nav_icon'], '') . '</div>';
7995 + echo '</div><div class="king-addons-grid-slider-dots"></div>';
7996 + }
7997 +
7998 + $this->render_grid_pagination($s);
7999 + return;
8000 + }
8001 +
7416 8002 $posts = new WP_Query($this->get_main_query_args());
7417 8003 if ($posts->have_posts()) {
7418 8004 if ('slider' !== $s['layout_select']) {
7419 8005 $this->render_grid_filters($s);
@@ -7425,16 +8011,23 @@
7425 8011 }
7426 8012 echo '<section class="king-addons-grid king-addons-media-grid elementor-clearfix" ' . $render_attribute . '>';
7427 8013 while ($posts->have_posts()) {
7428 8014 $posts->the_post();
7429 - // Skip non-image attachments (in case)
7430 - if (!wp_attachment_is_image(get_the_ID())) continue;
8015 + $attachment_id = get_the_ID();
7431 8016
7432 - $post_class = implode(' ', get_post_class('king-addons-grid-item elementor-clearfix', get_the_ID()));
8017 + if (!wp_attachment_is_image($attachment_id) && !$this->is_video_attachment($attachment_id)) {
8018 + continue;
8019 + }
8020 +
8021 + $post_class = implode(' ', get_post_class('king-addons-grid-item elementor-clearfix', $attachment_id));
8022 + if ($this->is_video_attachment($attachment_id)) {
8023 + $post_class .= ' king-addons-grid-item-video';
8024 + }
8025 +
7433 8026 echo '<article class="' . esc_attr($post_class) . '">';
7434 8027 echo '<div class="king-addons-grid-item-inner">';
7435 8028 $this->get_elements_by_location('above', $s, get_the_ID());
7436 - echo '<div class="king-addons-grid-media-wrap' . $this->get_image_effect_class($s) . ' ">';
8029 + echo '<div class="king-addons-grid-media-wrap' . esc_attr($this->get_image_effect_class($s)) . ' ">';
7437 8030 $this->render_post_thumbnail($s);
7438 8031 echo '<div class="king-addons-grid-media-hover king-addons-animation-wrap">';
7439 8032 $this->render_media_overlay($s);
7440 8033 $this->get_elements_by_location('over', $s, get_the_ID());