PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
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 51.1.45 All 40 releases
← All changes | includes/widgets/Media_Grid/Media_Grid.php +634 -37 51.1.36 → 51.1.86 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,15 +7250,21 @@
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)
@@ -6748,18 +7288,28 @@
6748 7288
6749 7289 public function render_post_thumbnail($settings)
6750 7290 {
6751 7291 $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>';
7292 + $is_video = $this->is_video_attachment($id);
7293 + $video_url = $is_video ? (string) wp_get_attachment_url($id) : '';
7294 +
7295 + $this->render_media_thumbnail(
7296 + $settings,
7297 + 'attachment-' . $id,
7298 + $is_video ? 'video' : 'image',
7299 + ['id' => $id],
7300 + $video_url
7301 + );
6757 7302 }
6758 7303
6759 - public function render_media_overlay($s)
7304 + public function render_media_overlay($s, $post_id = 0)
6760 7305 {
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())) . '">';
7306 + if (!$post_id) {
7307 + $post_id = get_the_ID();
7308 + }
7309 +
7310 + $overlay_url = $post_id ? get_the_permalink($post_id) : '#';
7311 + echo '<div class="king-addons-grid-media-hover-bg ' . esc_attr($this->get_animation_class($s, 'overlay')) . '" data-url="' . esc_url($overlay_url) . '">';
6762 7312 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
6763 7313 if ('' !== $s['overlay_image']['url']) {
6764 7314 echo '<img src="' . esc_url($s['overlay_image']['url']) . '">';
6765 7315 }
@@ -6894,9 +7444,12 @@
6894 7444 public function render_post_lightbox($s, $class, $post_id)
6895 7445 {
6896 7446 echo '<div class="' . esc_attr($class) . '"><div class="inner-block">';
6897 7447 $lightbox_source = get_the_post_thumbnail_url($post_id);
6898 - if ('audio' === get_post_format()) {
7448 +
7449 + if ($this->is_video_attachment($post_id)) {
7450 + $lightbox_source = $this->get_attachment_video_poster_url($post_id);
7451 + } elseif ('audio' === get_post_format()) {
6899 7452 if ('meta' === $s['element_lightbox_pfa_select']) {
6900 7453 $meta_value = get_post_meta($post_id, $s['element_lightbox_pfa_meta'], true);
6901 7454 if (false === strpos($meta_value, '<iframe ')) {
6902 7455 add_filter('oembed_result', $media_grid_filter = function ($html) {
@@ -6925,9 +7478,11 @@
6925 7478 }
6926 7479 if (isset($video_url)) $lightbox_source = $video_url;
6927 7480 }
6928 7481 }
6929 - if (!$lightbox_source) $lightbox_source = wp_get_attachment_url($post_id);
7482 + if (!$lightbox_source && !$this->is_video_attachment($post_id)) {
7483 + $lightbox_source = wp_get_attachment_url($post_id);
7484 + }
6930 7485 echo '<span data-src="' . esc_url($lightbox_source) . '">';
6931 7486 if ('before' === $s['element_extra_text_pos']) {
6932 7487 echo '<span class="king-addons-grid-extra-text-left">' . esc_html($s['element_extra_text']) . '</span>';
6933 7488 }
@@ -7341,13 +7896,13 @@
7341 7896 'selector' => '.king-addons-grid-image-wrap',
7342 7897 'iframeMaxWidth' => '60%',
7343 7898 'hash' => false,
7344 7899 'autoplay' => $s['lightbox_popup_autoplay'],
7345 - 'pause' => $s['lightbox_popup_pause'] * 1000,
7900 + 'pause' => Core::jsNumber($s, 'lightbox_popup_pause', 5) * 1000,
7346 7901 'progressBar' => $s['lightbox_popup_progressbar'],
7347 7902 'counter' => $s['lightbox_popup_counter'],
7348 7903 'controls' => $s['lightbox_popup_arrows'],
7349 - 'getCaptionFromTitleOrAlt' => $s['lightbox_popup_captions'],
7904 + 'getCaptionFromTitleOrAlt' => false,
7350 7905 'thumbnail' => $s['lightbox_popup_thumbnails'],
7351 7906 'showThumbByDefault' => $s['lightbox_popup_thumbnails_default'],
7352 7907 'share' => $s['lightbox_popup_sharing'],
7353 7908 'zoom' => $s['lightbox_popup_zoom'],
@@ -7391,9 +7946,9 @@
7391 7946 'pause' => $s['lightbox_popup_pause'] * 1000,
7392 7947 'progressBar' => $s['lightbox_popup_progressbar'],
7393 7948 'counter' => $s['lightbox_popup_counter'],
7394 7949 'controls' => $s['lightbox_popup_arrows'],
7395 - 'getCaptionFromTitleOrAlt' => $s['lightbox_popup_captions'],
7950 + 'getCaptionFromTitleOrAlt' => false,
7396 7951 'thumbnail' => $s['lightbox_popup_thumbnails'],
7397 7952 'showThumbByDefault' => $s['lightbox_popup_thumbnails_default'],
7398 7953 'share' => $s['lightbox_popup_sharing'],
7399 7954 'zoom' => $s['lightbox_popup_zoom'],
@@ -7412,8 +7967,43 @@
7412 7967
7413 7968 protected function render()
7414 7969 {
7415 7970 $s = $this->get_settings();
7971 +
7972 + if ('manual' === $s['query_selection']) {
7973 + $manual_items = $this->get_paginated_manual_media_items($s);
7974 +
7975 + if (empty($manual_items)) {
7976 + echo '<h2>' . esc_html($s['query_not_found_text']) . '</h2>';
7977 + return;
7978 + }
7979 +
7980 + if ('slider' !== $s['layout_select']) {
7981 + $this->render_grid_filters($s);
7982 + $this->add_grid_settings($s);
7983 + $render_attribute = $this->get_render_attribute_string('grid-settings');
7984 + } else {
7985 + $this->add_slider_settings($s);
7986 + $render_attribute = $this->get_render_attribute_string('slider-settings');
7987 + }
7988 +
7989 + echo '<section class="king-addons-grid king-addons-media-grid elementor-clearfix" ' . $render_attribute . '>';
7990 + foreach ($manual_items as $item) {
7991 + $this->render_manual_grid_item($s, $item);
7992 + }
7993 + echo '</section>';
7994 +
7995 + if ('slider' === $s['layout_select']) {
7996 + echo '<div class="king-addons-grid-slider-arrow-container">';
7997 + 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>';
7998 + 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>';
7999 + echo '</div><div class="king-addons-grid-slider-dots"></div>';
8000 + }
8001 +
8002 + $this->render_grid_pagination($s);
8003 + return;
8004 + }
8005 +
7416 8006 $posts = new WP_Query($this->get_main_query_args());
7417 8007 if ($posts->have_posts()) {
7418 8008 if ('slider' !== $s['layout_select']) {
7419 8009 $this->render_grid_filters($s);
@@ -7425,12 +8015,19 @@
7425 8015 }
7426 8016 echo '<section class="king-addons-grid king-addons-media-grid elementor-clearfix" ' . $render_attribute . '>';
7427 8017 while ($posts->have_posts()) {
7428 8018 $posts->the_post();
7429 - // Skip non-image attachments (in case)
7430 - if (!wp_attachment_is_image(get_the_ID())) continue;
8019 + $attachment_id = get_the_ID();
7431 8020
7432 - $post_class = implode(' ', get_post_class('king-addons-grid-item elementor-clearfix', get_the_ID()));
8021 + if (!wp_attachment_is_image($attachment_id) && !$this->is_video_attachment($attachment_id)) {
8022 + continue;
8023 + }
8024 +
8025 + $post_class = implode(' ', get_post_class('king-addons-grid-item elementor-clearfix', $attachment_id));
8026 + if ($this->is_video_attachment($attachment_id)) {
8027 + $post_class .= ' king-addons-grid-item-video';
8028 + }
8029 +
7433 8030 echo '<article class="' . esc_attr($post_class) . '">';
7434 8031 echo '<div class="king-addons-grid-item-inner">';
7435 8032 $this->get_elements_by_location('above', $s, get_the_ID());
7436 8033 echo '<div class="king-addons-grid-media-wrap' . $this->get_image_effect_class($s) . ' ">';