PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.21
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.21
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.21, at classes/models/FrmPluginSearch.php

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