Completed
Push — add/plugin-search-hints ( 80ac63...7bb320 )
by
unknown
06:40
created

Jetpack_Plugin_Search::start()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Module Name: Plugin Search Hints
4
 * Module Description: Make suggestions when people search the plugin directory for things that Jetpack already does for them.
5
 * Sort Order: 50
6
 * Recommendation Order: 1
7
 * First Introduced: 7.1
8
 * Requires Connection: Yes
9
 * Auto Activate: Yes
10
 * Module Tags: Recommended
11
 * Feature: Jumpstart
12
 */
13
14
/**
15
 * @todo Convert into a Jetpack module. Autoload/enable.
16
 *
17
 * @todo Handle different scenarios:
18
 * - Jetpack installed, active, not connected; prompt to connect to get feature
19
 * - Done: Installed, active, feature not enabled; prompt to enable
20
 * - Done: Installed, active, feature enabled; link to settings
21
 * - Activate module via AJAX, then prompt to configure/settings
22
 */
23
24
if (
25
	is_admin() &&
26
	Jetpack::is_active() &&
27
	/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
28
	apply_filters( 'jetpack_show_promotions', true )
29
) {
30
	add_action( 'jetpack_modules_loaded', array( 'Jetpack_Plugin_Search', 'init' ) );
31
}
32
33
/**
34
 * Class that includes cards in the plugin search results when users enter terms that match some Jetpack feature.
35
 * Card can be dismissed and includes a title, description, button to enable the feature and a link for more information.
36
 *
37
 * @since 7.1.0
38
 */
39
class Jetpack_Plugin_Search {
40
41
	static $slug = 'jetpack-plugin-search';
42
43
	public static function init() {
44
		static $instance = null;
45
46
		if ( ! $instance ) {
47
			jetpack_require_lib( 'tracks/client' );
48
			$instance = new Jetpack_Plugin_Search();
49
		}
50
51
		return $instance;
52
	}
53
54
	public function __construct() {
55
		add_action( 'current_screen', array( $this, 'start' ) );
56
	}
57
58
	/**
59
	 * Add actions and filters only if this is the plugin installation screen and it's the first page.
60
	 *
61
	 * @param object $screen
62
	 */
63
	public function start( $screen ) {
64
		if ( 'plugin-install' === $screen->base && ( ! isset( $_GET['paged'] ) || 1 == $_GET['paged'] ) ) {
65
			add_action( 'admin_enqueue_scripts', array( $this, 'load_plugins_search_script' ) );
66
			add_filter( 'plugins_api_result', array( $this, 'inject_jetpack_module_suggestion' ), 10, 3 );
67
			add_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
68
			add_filter( 'plugin_install_action_links', array( $this, 'insert_module_related_links' ), 10, 2 );
69
		}
70
	}
71
72
	/**
73
	 * Modify URL used to fetch to plugin information so it pulls Jetpack plugin page.
74
	 *
75
	 * @param string $url URL to load in dialog pulling the plugin page from wporg.
76
	 *
77
	 * @return string The URL with 'jetpack' instead of 'jetpack-plugin-search'.
78
	 */
79
	public function plugin_details( $url ) {
80
		if ( false !== stripos( $url, 'tab=plugin-information&amp;plugin=' . self::$slug ) ) {
81
			return 'plugin-install.php?tab=plugin-information&amp;plugin=jetpack&amp;TB_iframe=true&amp;width=600&amp;height=550';
82
		}
83
		return $url;
84
	}
85
86
	public function load_plugins_search_script( $hook ) {
87
		wp_enqueue_script( self::$slug, plugins_url( 'modules/plugin-search/plugin-search.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION, true );
88
		wp_localize_script(
89
			self::$slug,
90
			'jetpackPluginSearch',
91
			array(
92
				'nonce'                => wp_create_nonce( 'wp_rest' ),
93
				'rest_url'             => rest_url( '/jetpack/v4/settings/' ),
94
				'manageSettingsString' => esc_html__( 'Module Settings', 'jetpack' ),
95
				'activateModuleString' => esc_html__( 'Activate Module', 'jetpack' ),
96
				'activatedString'      => esc_html__( 'Activated', 'jetpack' ),
97
				'activatingString'     => esc_html__( 'Activating', 'jetpack' ),
98
			)
99
		);
100
101
		wp_enqueue_style( self::$slug, plugins_url( 'modules/plugin-search/plugin-search.css', JETPACK__PLUGIN_FILE ) );
102
	}
103
104
	/**
105
	 * Get the plugin repo's data for Jetpack to populate the fields with.
106
	 *
107
	 * @return array|mixed|object|WP_Error
108
	 */
109
	public static function get_jetpack_plugin_data() {
110
		$data = get_transient( 'jetpack_plugin_data' );
111
112
		if ( false === $data || is_wp_error( $data ) ) {
113
			include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
114
			$data = plugins_api( 'plugin_information', array(
115
				'slug' => 'jetpack',
116
				'is_ssl' => is_ssl(),
117
				'fields' => array(
118
					'banners' => true,
119
					'reviews' => true,
120
					'active_installs' => true,
121
					'versions' => false,
122
					'sections' => false,
123
				),
124
			) );
125
			set_transient( 'jetpack_plugin_data', $data, DAY_IN_SECONDS );
126
		}
127
128
		return $data;
129
	}
130
131
	/**
132
	 * Intercept the plugins API response and add in an appropriate card for Jetpack
133
	 */
134
	public function inject_jetpack_module_suggestion( $result, $action, $args ) {
135
		require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php';
136
		$jetpack_modules_list = Jetpack_Admin::init()->get_modules();
137
138
		// Never suggest this module.
139
		unset( $jetpack_modules_list['plugin-search'] );
140
141
		// Looks like a search query; it's matching time
142
		if ( ! empty( $args->search ) ) {
143
			$matching_module = null;
144
145
			// Lowercase, trim, remove punctuation/special chars, decode url, remove 'jetpack'
146
			$this->track_search_term( $args->search );
147
			$normalized_term = $this->sanitize_search_term( $args->search );
148
149
			usort( $jetpack_modules_list, array( $this, 'by_sorting_option' ) );
150
151
			// Try to match a passed search term with module's search terms
152
			foreach ( $jetpack_modules_list as $module_slug => $module_opts ) {
153
				$search_terms = strtolower( $module_opts['search_terms'] . ', ' . $module_opts['name'] );
154
				$terms_array  = explode( ', ', $search_terms );
155
				if ( in_array( $normalized_term, $terms_array ) ) {
156
					$matching_module = $module_slug;
157
					break;
158
				}
159
			}
160
161
			if ( isset( $matching_module ) ) {
162
				$inject = (array) self::get_jetpack_plugin_data();
163
164
				$overrides = array(
165
					'plugin-search' => true, // Helps to determine if that an injected card.
166
					'name' => sprintf(       // Supplement name/description so that they clearly indicate this was added.
167
						_x( 'Jetpack: %s', 'Jetpack: Module Name', 'jetpack' ),
168
						$jetpack_modules_list[ $matching_module ]['name']
169
					),
170
					'short_description' => sprintf(
171
						_x( 'You already have Jetpack installed, and it provides this functionality. %s', 'You already have Jetpack installed... Module description.', 'jetpack' ),
172
						$jetpack_modules_list[ $matching_module ]['short_description']
173
					),
174
					'requires_connection' => (bool) $jetpack_modules_list[ $matching_module ]['requires_connection'],
175
					'slug'    => self::$slug,
176
					'version' => JETPACK__VERSION,
177
					'icons' => array(
178
						'1x'  => 'https://ps.w.org/jetpack/assets/icon.svg?rev=1791404',
179
						'2x'  => 'https://ps.w.org/jetpack/assets/icon-256x256.png?rev=1791404',
180
						'svg' => 'https://ps.w.org/jetpack/assets/icon.svg?rev=1791404',
181
					),
182
				);
183
184
				// Splice in the base module data
185
				$inject = array_merge( $inject, $jetpack_modules_list[ $matching_module ], $overrides );
186
187
				// Add it to the top of the list
188
				array_unshift( $result->plugins, $inject );
189
			}
190
		}
191
		return $result;
192
	}
193
194
	/**
195
	 * Take a raw search query and return something a bit more standardized and
196
	 * easy to work with.
197
	 *
198
	 * @param  String $term The raw search term
199
	 * @return String A simplified/sanitized version.
200
	 */
201
	private function sanitize_search_term( $term ) {
202
		$term = strtolower( urldecode( $term ) );
203
204
		// remove non-alpha/space chars.
205
		$term = preg_replace( '/[^a-z ]/', '', $term );
206
207
		// remove strings that don't help matches.
208
		$term = trim( str_replace( array( 'jetpack', 'jp', 'free', 'wordpress' ), '', $term ) );
209
210
		return $term;
211
	}
212
213
	/**
214
	 * Tracks every search term used in plugins search as 'jetpack_wpa_plugin_search_term'
215
	 *
216
	 * @param String $term The raw search term.
217
	 * @return true|WP_Error true for success, WP_Error if error occurred.
218
	 */
219
	private function track_search_term( $term ) {
220
		return JetpackTracking::record_user_event( 'wpa_plugin_search_term', array( 'search_term' => $term ) );
221
	}
222
223
	/**
224
	 * Callback function to sort the array of modules by the sort option.
225
	 */
226
	private function by_sorting_option( $m1, $m2 ) {
227
		return $m1['sort'] - $m2['sort'];
228
	}
229
230
	/**
231
	 * Put some more appropriate links on our custom result cards.
232
	 */
233
	public function insert_module_related_links( $links, $plugin ) {
234
		if ( self::$slug !== $plugin['slug'] ) {
235
			return $links;
236
		}
237
238
		// By the time this filter is added, self_admin_url was already applied and we don't need it anymore.
239
		remove_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
240
241
		$links = array();
242
243
		// Jetpack installed, active, feature not enabled; prompt to enable.
244
		if (
245
			(
246
				Jetpack::is_active() ||
247
				(
248
					Jetpack::is_development_mode() &&
249
					! $plugin[ 'requires_connection' ]
250
				)
251
			) &&
252
			current_user_can( 'jetpack_activate_modules' ) &&
253
			! Jetpack::is_module_active( $plugin['module'] )
254
		) {
255
			$links = array(
256
				'<button id="plugin-select-activate" class="button activate-module-now" data-module="' . esc_attr( $plugin['module'] ) . '" data-configure-url="' . esc_url( Jetpack::module_configuration_url( $plugin['module'] ) ) . '"> ' . esc_html__( 'Enable', 'jetpack' ) . '</button>',
257
			);
258
			// Jetpack installed, active, feature enabled; link to settings.
259
		} elseif (
260
			! empty( $plugin['configure_url'] ) &&
261
			current_user_can( 'jetpack_configure_modules' ) &&
262
			Jetpack::is_module_active( $plugin['module'] ) &&
263
			/** This filter is documented in class.jetpack-admin.php */
264
			apply_filters( 'jetpack_module_configurable_' . $plugin['module'], false )
265
		) {
266
			$links = array(
267
				'<a id="plugin-select-settings" href="' . esc_url( $plugin['configure_url'] ) . '">' . esc_html__( 'Module Settings', 'jetpack' ) . '</a>',
268
			);
269
		}
270
271
		// Adds link pointing to a relevant doc page in jetpack.com
272
		if ( ! empty( $plugin['learn_more_button'] ) ) {
273
			$links[] = '<a href="' . esc_url( $plugin['learn_more_button'] ) . '" target="_blank">' . esc_html__( 'Learn more', 'jetpack' ) . '</a>';
274
		}
275
276
		return $links;
277
	}
278
}
279