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

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