PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / FrmPluginSearch.php

FrmPluginSearch.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.28, at classes/models/FrmPluginSearch.php

385 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmPluginSearch {
7
8 /**
9 * PSH slug name.
10 *
11 * @var string
12 */
13 public static $slug = 'frm-plugin-search';
14
15 /**
16 * Option name that holds dismissed suggestions.
17 *
18 * @var string
19 */
20 protected static $dismissed_opt = 'frm_dismissed_hints';
21
22 public function __construct() {
23 add_action( 'current_screen', array( $this, 'start' ) );
24 }
25
26 /**
27 * Add actions and filters only if this is the plugin installation screen and it's the first page.
28 *
29 * @since 4.12
30 *
31 * @param object $screen WP Screen object.
32 *
33 * @return void
34 */
35 public function start( $screen ) {
36 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
37 if ( 'plugin-install' === $screen->base && ( ! isset( $_GET['paged'] ) || 1 === intval( $_GET['paged'] ) ) ) {
38 add_filter( 'plugins_api_result', array( $this, 'inject_suggestion' ), 10, 3 );
39 add_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
40 add_filter( 'plugin_install_action_links', array( $this, 'insert_related_links' ), 10, 2 );
41 add_action( 'admin_enqueue_scripts', array( $this, 'load_plugins_search_script' ) );
42 $this->maybe_dismiss();
43 }
44 }
45
46 /**
47 * Intercept the plugins API response and add in an appropriate card.
48 *
49 * @param object $result Plugin search results.
50 * @param string $action unused.
51 * @param object $args Search args.
52 *
53 * @return object
54 */
55 public function inject_suggestion( $result, $action, $args ) {
56 // Looks like a search query; it's matching time.
57 if ( empty( $args->search ) ) {
58 return $result;
59 }
60
61 if ( is_wp_error( $result ) ) {
62 // This may happen if the request failed due to an internet connection issue.
63 return $result;
64 }
65
66 $addon_list = $this->get_addons();
67
68 // Lowercase, trim, remove punctuation/special chars, decode url, remove 'formidable'.
69 $normalized_term = $this->search_to_array( $args->search );
70
71 if ( ! $normalized_term ) {
72 // Don't add anything extra.
73 return $result;
74 }
75
76 $matching_addon = $this->matching_addon( $addon_list, $normalized_term );
77
78 if ( ! $matching_addon || ! $this->should_display_hint( $matching_addon ) ) {
79 return $result;
80 }
81
82 $inject = (array) $this->get_plugin_data();
83 $overrides = array(
84 // Helps to determine if that an injected card.
85 'plugin-search' => true,
86 'name' => sprintf(
87 /* translators: Formidable addon name */
88 esc_html_x( 'Formidable %s', 'Formidable Addon Name', 'formidable' ),
89 $addon_list[ $matching_addon ]['name']
90 ),
91 'addon' => $addon_list[ $matching_addon ]['slug'],
92 'short_description' => $addon_list[ $matching_addon ]['excerpt'],
93 'slug' => self::$slug,
94 'version' => $addon_list[ $matching_addon ]['version'],
95 );
96
97 if ( ! empty( $addon_list[ $matching_addon ]['external'] ) ) {
98 unset( $overrides['name'] );
99 }
100
101 // Splice in the base addon data.
102 $inject = array_merge( $inject, $addon_list[ $matching_addon ], $overrides );
103
104 // Add it to the top of the list.
105 array_unshift( $result->plugins, $inject );
106
107 return $result;
108 }
109
110 /**
111 * Search for any addons that match the searched terms.
112 *
113 * @since 4.12
114 *
115 * @param array $addon_list
116 * @param array $normalized_term
117 *
118 * @return int|null
119 */
120 private function matching_addon( $addon_list, $normalized_term ) {
121 $matching_addon = null;
122
123 // Try to match a passed search term with addon's search terms.
124 foreach ( $addon_list as $addon_id => $addon_opts ) {
125 if ( ! is_array( $addon_opts ) || empty( $addon_opts['excerpt'] ) ) {
126 continue;
127 }
128
129 if ( ! isset( $addon_opts['search_terms'] ) ) {
130 $addon_opts['search_terms'] = '';
131 }
132
133 $addon_terms = $this->search_to_array( $addon_opts['search_terms'] . ' ' . $addon_opts['name'] );
134 $matched_terms = array_intersect( $addon_terms, $normalized_term );
135
136 if ( count( $matched_terms ) === count( $normalized_term ) ) {
137 $matching_addon = $addon_id;
138 break;
139 }
140 }
141
142 return $matching_addon;
143 }
144
145 /**
146 * @since 4.12
147 *
148 * @return array
149 */
150 private function get_addons() {
151 $api = new FrmFormApi();
152 $addons = $api->get_api_info();
153 return apply_filters( 'frm_plugin_search', $addons );
154 }
155
156 /**
157 * Get the plugin repo's data to populate the fields with.
158 *
159 * @return array|mixed|object|WP_Error
160 */
161 private function get_plugin_data() {
162 $data = get_transient( 'formidable_plugin_data' );
163
164 if ( false !== $data && ! is_wp_error( $data ) ) {
165 return $data;
166 }
167
168 include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
169 $data = plugins_api(
170 'plugin_information',
171 array(
172 'slug' => 'formidable',
173 'is_ssl' => is_ssl(),
174 'fields' => array(
175 'banners' => true,
176 'reviews' => true,
177 'active_installs' => true,
178 'versions' => false,
179 'sections' => false,
180 ),
181 )
182 );
183 set_transient( 'formidable_plugin_data', $data, DAY_IN_SECONDS );
184
185 return $data;
186 }
187
188 /**
189 * Modify URL used to fetch to plugin information so it pulls Formidable plugin page.
190 *
191 * @since 4.12
192 *
193 * @param string $url URL to load in dialog pulling the plugin page from wporg.
194 *
195 * @return string The URL with 'formidable' instead of 'frm-plugin-search'.
196 */
197 public function plugin_details( $url ) {
198 // phpcs:disable Generic.WhiteSpace.ScopeIndent
199 return false !== stripos( $url, 'tab=plugin-information&amp;plugin=' . self::$slug )
200 ? 'plugin-install.php?tab=plugin-information&amp;plugin=formidable&amp;TB_iframe=true&amp;width=600&amp;height=550'
201 : $url;
202 // phpcs:enable Generic.WhiteSpace.ScopeIndent
203 }
204
205 /**
206 * @since 4.12
207 *
208 * @return void
209 */
210 private function maybe_dismiss() {
211 $addon = FrmAppHelper::get_param( 'frm-dismiss', '', 'get', 'absint' );
212
213 if ( $addon ) {
214 $this->add_to_dismissed_hints( $addon );
215 }
216 }
217
218 /**
219 * Returns a list of previously dismissed hints.
220 *
221 * @since 4.12
222 *
223 * @return array List of dismissed hints.
224 */
225 protected function get_dismissed_hints() {
226 $dismissed_hints = get_option( self::$dismissed_opt );
227 return $dismissed_hints && is_array( $dismissed_hints ) ? $dismissed_hints : array();
228 }
229
230 /**
231 * Save the hint in the list of dismissed hints.
232 *
233 * @since 4.12
234 *
235 * @param string $hint The hint id, which is a Formidable addon slug.
236 *
237 * @return bool Whether the card was added to the list and hence dismissed.
238 */
239 protected function add_to_dismissed_hints( $hint ) {
240 $hints = array_merge( $this->get_dismissed_hints(), array( $hint ) );
241 return update_option( self::$dismissed_opt, $hints, false );
242 }
243
244 /**
245 * Checks that the addon slug passed should be displayed.
246 *
247 * A feature hint will be displayed if it has not been dismissed before or if 2 or fewer other hints have been dismissed.
248 *
249 * @since 7.2.1
250 *
251 * @param string $hint The hint id, which is a Formidable addon slug.
252 *
253 * @return bool True if $hint should be displayed.
254 */
255 protected function should_display_hint( $hint ) {
256 $dismissed_hints = $this->get_dismissed_hints();
257
258 // If more than 3 hints have been dismissed, then show no more.
259 if ( 3 < count( $dismissed_hints ) ) {
260 return false;
261 }
262
263 return ! in_array( $hint, $dismissed_hints, true );
264 }
265
266 /**
267 * Take a raw search query and return something a bit more standardized and
268 * easy to work with.
269 *
270 * @param string $term The raw search term.
271 *
272 * @return string A simplified/sanitized version.
273 */
274 private function sanitize_search_term( $term ) {
275 $term = strtolower( urldecode( $term ) );
276
277 // Remove non-alpha/space chars.
278 $term = preg_replace( '/[^a-z ]/', '', $term );
279
280 // Remove strings that don't help matches.
281 return trim( str_replace( array( 'formidable', 'free', 'wordpress', 'wp ', 'plugin' ), '', $term ) );
282 }
283
284 /**
285 * @since 4.12
286 *
287 * @param string $terms
288 *
289 * @return array
290 */
291 private function search_to_array( $terms ) {
292 $terms = $this->sanitize_search_term( $terms );
293 return array_filter( explode( ' ', $terms ) );
294 }
295
296 /**
297 * Put some more appropriate links on our custom result cards.
298 *
299 * @param array $links Related links.
300 * @param array $plugin Plugin result information.
301 *
302 * @return array
303 */
304 public function insert_related_links( $links, $plugin ) {
305 if ( self::$slug !== $plugin['slug'] ) {
306 return $links;
307 }
308
309 // By the time this filter is applied, self_admin_url was already applied and we don't need it anymore.
310 remove_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
311
312 $links = array();
313 $is_installed = $this->is_installed( $plugin['plugin'] );
314 $is_active = is_plugin_active( $plugin['plugin'] );
315
316 // Plugin installed, active, feature not enabled; prompt to enable.
317 if ( ! $is_active && $is_installed ) {
318 if ( current_user_can( 'activate_plugins' ) ) {
319 $activate_url = add_query_arg(
320 array(
321 'action' => 'activate',
322 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin['plugin'] ),
323 'plugin' => $plugin['plugin'],
324 ),
325 admin_url( 'plugins.php' )
326 );
327 $links['frm_get_started'] = '<a href="' . esc_url( $activate_url ) . '" class="button activate-now" aria-label="Activate ' . esc_attr( $plugin['name'] ) . '">' . esc_html__( 'Activate', 'formidable' ) . '</a>'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
328 }
329 } elseif ( ! $is_active && isset( $plugin['url'] ) ) {
330 // Go to the add-ons page to install.
331 $links[] = '<a
332 class="button-secondary"
333 href="' . esc_url( admin_url( 'admin.php?page=formidable-addons' ) ) . '"
334 >' . esc_html__( 'Install Now', 'formidable' ) . '</a>';
335 } elseif ( ! empty( $plugin['link'] ) ) {
336 // Add link pointing to a relevant doc page in formidable.com.
337 $links[] = '<a
338 class="button-primary frm-plugin-search__learn-more"
339 href="' . esc_url( FrmAppHelper::admin_upgrade_link( 'plugin-learn-more', $plugin['link'] ) ) . '"
340 target="_blank"
341 data-addon="' . esc_attr( $plugin['addon'] ) . '"
342 >' . esc_html__( 'Learn more', 'formidable' ) . '</a>';
343 }//end if
344
345 // Dismiss link.
346 $dismiss = add_query_arg( array( 'frm-dismiss' => $plugin['id'] ) );
347 $links[] = '<a
348 href="' . $dismiss . '"
349 class="frm-plugin-search__dismiss"
350 data-addon="' . esc_attr( $plugin['addon'] ) . '"
351 >' . esc_html__( 'Hide this suggestion', 'formidable' ) . '</a>';
352
353 return $links;
354 }
355
356 /**
357 * @param string $plugin
358 *
359 * @return bool
360 */
361 protected function is_installed( $plugin ) {
362 $all_plugins = FrmAppHelper::get_plugins();
363 return isset( $all_plugins[ $plugin ] );
364 }
365
366 /**
367 * Load the search scripts and CSS for PSH.
368 *
369 * @return void
370 */
371 public function load_plugins_search_script() {
372 wp_enqueue_script( self::$slug, FrmAppHelper::plugin_url() . '/js/plugin-search.js', array(), FrmAppHelper::plugin_version(), true );
373 wp_localize_script(
374 self::$slug,
375 'frmPlugSearch',
376 array(
377 'legend' => esc_html__(
378 'This suggestion was made by Formidable Forms, the form builder and application plugin already installed on your site.',
379 'formidable'
380 ),
381 )
382 );
383 }
384 }
385