PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.11.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.11.0
2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.7 2.6.8 2.6.5 2.6.4 2.6.3 2.6.2 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2.0 2.0 2.1.0 2.1.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1
reviews-feed / class / Common / Services / ShortcodeService.php
reviews-feed / class / Common / Services Last commit date
Upgrade 1 week ago CLIService.php 1 week ago FeedCacheUpdateService.php 1 week ago MigrationReactivationNotice.php 1 week ago SBR_Upgrader.php 1 week ago SettingsManagerService.php 1 week ago ShortcodeService.php 1 week ago SiteUrlWatcher.php 1 week ago
ShortcodeService.php
216 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Common\Services;
4
5 use SmashBalloon\Reviews\Common\Feed;
6 use SmashBalloon\Reviews\Common\FeedCache;
7 use SmashBalloon\Reviews\Common\FeedDisplay;
8 use SmashBalloon\Reviews\Common\Feed_Locator;
9 use SmashBalloon\Reviews\Common\Parser;
10 use SmashBalloon\Reviews\Common\SBR_Settings;
11 use SmashBalloon\Reviews\Common\Util;
12 use Smashballoon\Stubs\Services\ServiceProvider;
13
14 class ShortcodeService extends ServiceProvider
15 {
16 public function register()
17 {
18 add_shortcode('reviews-feed', [$this, 'render']);
19 }
20
21 public function render($atts = array())
22 {
23 $feed_id = ! empty($atts['feed']) ? $atts['feed'] : 0;
24 $is_single_manual_review = isset($atts['name']) && ! empty($atts['name']) ? true : false;
25
26 $settings = SBR_Settings::get_settings_by_feed_id($feed_id, false, $is_single_manual_review);
27
28 if ($is_single_manual_review) {
29 $settings = array_merge(
30 $settings,
31 $this->get_single_manual_review_content($atts)
32 );
33 }
34
35 do_action('sbr_before_shortcode_render', $settings);
36
37 // Track feed location for usage statistics.
38 $this->track_feed_location($feed_id, $atts);
39
40 $feed = new Feed($settings, $feed_id, new FeedCache($feed_id, 2 * DAY_IN_SECONDS));
41
42 $feed->init();
43 if (! empty($feed->get_errors())) {
44 $feed_display = new FeedDisplay($feed, new Parser());
45 return $feed_display->error_html();
46 }
47 $feed->get_set_cache();
48
49 $feed_display = new FeedDisplay($feed, new Parser());
50
51 return $feed_display->with_wrap();
52 }
53
54 /**
55 * Track the feed location for usage statistics.
56 *
57 * @param int|string $feed_id The feed ID.
58 * @param array $atts Shortcode attributes.
59 */
60 private function track_feed_location($feed_id, $atts)
61 {
62 // Only track if we have a valid feed ID and post ID.
63 if (empty($feed_id)) {
64 return;
65 }
66
67 // Get the current post ID.
68 $post_id = get_the_ID();
69 if (! $post_id) {
70 // Try to get from global post.
71 global $post;
72 $post_id = ! empty($post->ID) ? $post->ID : 0;
73 }
74
75 if (! $post_id) {
76 return;
77 }
78
79 // Determine HTML location based on context.
80 $html_location = $this->determine_html_location();
81
82 // Check if we should track this request.
83 // Track more frequently initially to collect data, then reduce to save DB load.
84 $should_track = $this->should_track_location($feed_id, $post_id);
85 if (! $should_track) {
86 return;
87 }
88
89 $feed_details = array(
90 'feed_id' => $feed_id,
91 'atts' => is_array($atts) ? $atts : array( 'feed' => $feed_id ),
92 'location' => array(
93 'post_id' => $post_id,
94 'html' => $html_location,
95 ),
96 );
97
98 // Ensure feed attribute is set.
99 if (! isset($feed_details['atts']['feed'])) {
100 $feed_details['atts']['feed'] = $feed_id;
101 }
102
103 $locator = new Feed_Locator($feed_details);
104 $locator->add_or_update_entry();
105 }
106
107 /**
108 * Determine if we should track this feed location.
109 *
110 * Always tracks if this feed/post combination hasn't been tracked yet.
111 * For already tracked combinations, uses random sampling to reduce DB load.
112 *
113 * @param int|string $feed_id The feed ID.
114 * @param int $post_id The post ID.
115 * @return bool Whether to track this location.
116 */
117 private function should_track_location($feed_id, $post_id)
118 {
119 global $wpdb;
120
121 $feed_locator_table = $wpdb->prefix . SBR_FEED_LOCATOR;
122
123 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
124 $existing = $wpdb->get_var(
125 $wpdb->prepare(
126 "SELECT COUNT(*) FROM $feed_locator_table WHERE feed_id = %s AND post_id = %d",
127 (string) $feed_id,
128 $post_id
129 )
130 );
131 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
132
133 // If no existing entry, always track (first visit).
134 if (empty($existing) || (int) $existing === 0) {
135 return true;
136 }
137
138 // For existing entries, use random sampling to update periodically (reduce DB load).
139 // This helps keep the last_update timestamp fresh.
140 return Feed_Locator::should_do_locating();
141 }
142
143 /**
144 * Determine the HTML location where the feed is being rendered.
145 *
146 * @return string The HTML location (content, header, footer, sidebar, or unknown).
147 */
148 private function determine_html_location()
149 {
150 // Check if we're in a sidebar/widget.
151 if (is_active_widget(false, false, 'text', true) || doing_action('dynamic_sidebar')) {
152 return 'sidebar';
153 }
154
155 // Check if we're in the header.
156 if (doing_action('wp_head') || did_action('wp_head') && ! did_action('wp_footer') && ! in_the_loop()) {
157 return 'header';
158 }
159
160 // Check if we're in the footer.
161 if (doing_action('wp_footer')) {
162 return 'footer';
163 }
164
165 // Check if we're in the main content loop.
166 if (in_the_loop() || is_singular()) {
167 return 'content';
168 }
169
170 return 'unknown';
171 }
172
173 /**
174 * Get single manual review content settings.
175 *
176 * @param array $atts Shortcode attributes.
177 * @return array Settings array.
178 */
179 public function get_single_manual_review_content($atts)
180 {
181 $settings = array();
182 $settings['singleManualReview'] = true;
183 // These attributes are Contributor-authorable and never reach
184 // SBR_Feed_Saver_Manager::cache_single_review(), so this path carries its own
185 // sanitisers (SMASH-1795). Each is picked for the attribute's real value space
186 // — the obvious general-purpose choice breaks a supported input in four of the
187 // six cases: sanitize_key('wordpress.org') is 'wordpressorg', absint('positive')
188 // is 0, esc_url_raw('wp-content/a.jpg') gains an http:// host, and
189 // sanitize_textarea_field('<strong>hi</strong>') is 'hi'.
190 $settings['singleManualReviewContent'] = array(
191 'name' => ! empty($atts['name']) ? sanitize_text_field(Util::payload_string($atts['name'])) : false,
192 'content' => ! empty($atts['content']) ? sbr_kses_review_text(Util::payload_string($atts['content'])) : false,
193 'rating' => ! empty($atts['rating']) ? Util::sanitize_rating($atts['rating']) : false,
194 'avatar' => ! empty($atts['avatar']) ? Util::sanitize_avatar_url($atts['avatar']) : false,
195 'time' => ! empty($atts['time']) ? Util::sanitize_time($atts['time']) : false,
196 'provider' => ! empty($atts['provider']) ? Util::sanitize_provider_slug($atts['provider']) : false,
197 );
198 $settings['showHeader'] = false;
199 $settings['showLoadButton'] = false;
200
201 // This to remove the multiple columns since we are only showing one Review.
202 $settings['gridDesktopColumns'] = 1;
203 $settings['gridTabletColumns'] = 1;
204 $settings['gridMobileColumns'] = 1;
205 $settings['masonryDesktopColumns'] = 1;
206 $settings['masonryTabletColumns'] = 1;
207 $settings['masonryMobileColumns'] = 1;
208 $settings['carouselDesktopColumns'] = 1;
209 $settings['carouselTabletColumns'] = 1;
210 $settings['carouselMobileColumns'] = 1;
211
212 return $settings;
213 }
214
215 }
216