Нет описания

class-jetpack-stats-upgrade-nudges.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. <?php
  2. /**
  3. * Adds a section with Upgrade nudges to the Status Report page
  4. *
  5. * @package jetpack
  6. */
  7. use Automattic\Jetpack\Connection\Manager;
  8. use Automattic\Jetpack\Jetpack_CRM_Data;
  9. use Automattic\Jetpack\Redirect;
  10. use Automattic\Jetpack\Tracking;
  11. jetpack_require_lib( 'plugins' );
  12. /**
  13. * Class that adds a new section to the Stats Report page
  14. */
  15. class Jetpack_Stats_Upgrade_Nudges {
  16. /**
  17. * Indicates whether the class initialized or not
  18. *
  19. * @var bool
  20. */
  21. private static $initialized = false;
  22. /**
  23. * Initialize the class by registering the action
  24. *
  25. * @return void
  26. */
  27. public static function init() {
  28. if ( ! self::$initialized ) {
  29. self::$initialized = true;
  30. add_action( 'jetpack_admin_pages_wrap_ui_after_callback', array( __CLASS__, 'render' ) );
  31. }
  32. }
  33. /**
  34. * Unsets the collapse nudges setting.
  35. */
  36. public static function unset_nudges_setting() {
  37. $stats_options = get_option( 'stats_options' );
  38. if ( $stats_options ) {
  39. unset( $stats_options['collapse_nudges'] );
  40. update_option( 'stats_options', $stats_options );
  41. }
  42. }
  43. /**
  44. * Determines whether Backup is active
  45. *
  46. * @return boolean
  47. */
  48. private static function is_backup_active() {
  49. $rewind_data = Jetpack_Core_Json_Api_Endpoints::rewind_data();
  50. return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state;
  51. }
  52. /**
  53. * Checks if a plugin is installed.
  54. *
  55. * @param string $plugin_file The plugin filename.
  56. * @return boolean
  57. */
  58. private static function is_plugin_installed( $plugin_file ) {
  59. $plugins = Jetpack_Plugins::get_plugins();
  60. return isset( $plugins[ $plugin_file ] );
  61. }
  62. /**
  63. * Checks if a plugin is active.
  64. *
  65. * @param string $plugin_file The plugin filename.
  66. * @return boolean
  67. */
  68. private static function is_plugin_active( $plugin_file ) {
  69. $plugins = Jetpack_Plugins::get_plugins();
  70. return isset( $plugins[ $plugin_file ] ) && isset( $plugins[ $plugin_file ]['active'] ) && $plugins[ $plugin_file ]['active'];
  71. }
  72. /**
  73. * Determines whether Scan is active
  74. *
  75. * @return boolean
  76. */
  77. private static function is_scan_active() {
  78. $scan_data = Jetpack_Core_Json_Api_Endpoints::scan_state();
  79. return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
  80. }
  81. /**
  82. * Determines whether Search module is active
  83. *
  84. * @return boolean
  85. */
  86. private static function is_search_active() {
  87. return Jetpack::is_module_active( 'search' );
  88. }
  89. /**
  90. * Determines whether the Site is on a Security Plan. It will also return true if site has backup, scan and akismet.
  91. *
  92. * @return boolean
  93. */
  94. private static function has_security_plan() {
  95. $plan_data = Jetpack_Plan::get();
  96. if ( is_array( $plan_data ) && isset( $plan_data['product_slug'] ) ) {
  97. $has_plan = wp_startswith( $plan_data['product_slug'], 'jetpack_security' );
  98. return (
  99. $has_plan || (
  100. self::is_backup_active() &&
  101. self::is_scan_active() &&
  102. self::is_akismet_active()
  103. )
  104. );
  105. }
  106. return false;
  107. }
  108. /**
  109. * Determines whether the Site is on the Complete Plan.
  110. *
  111. * @return boolean
  112. */
  113. private static function has_complete_plan() {
  114. $plan_data = Jetpack_Plan::get();
  115. if ( is_array( $plan_data ) && isset( $plan_data['product_slug'] ) ) {
  116. return wp_startswith( $plan_data['product_slug'], 'jetpack_complete' );
  117. }
  118. return false;
  119. }
  120. /**
  121. * Determines whether Akismet is active
  122. *
  123. * @return boolean
  124. */
  125. private static function is_akismet_active() {
  126. return Jetpack::is_akismet_active();
  127. }
  128. /**
  129. * Outputs the header of the Upgrade Secion
  130. *
  131. * @return void
  132. */
  133. private static function print_header() {
  134. if ( self::has_security_plan() ) {
  135. // translators: %s is the Site Name.
  136. $title = __( 'Performance and growth tools for %s', 'jetpack' );
  137. } else {
  138. // translators: %s is the Site Name.
  139. $title = __( 'Security, performance, and growth tools for %s', 'jetpack' );
  140. }
  141. $title = sprintf( $title, get_bloginfo( 'site_name' ) );
  142. $aria_expanded = 'true';
  143. $postbox_closed = '';
  144. $stats_options = get_option( 'stats_options' );
  145. if ( isset( $stats_options['collapse_nudges'] ) && $stats_options['collapse_nudges'] ) {
  146. $aria_expanded = 'false';
  147. $postbox_closed = ' closed';
  148. }
  149. ?>
  150. <div id="jp-stats-report-upgrade-wrap">
  151. <div class="meta-box-sortables">
  152. <div class="postbox<?php echo esc_attr( $postbox_closed ); ?>">
  153. <div class="dops-card dops-section-header is-compact jp-stats-report-upgrade-header">
  154. <div class="dops-section-header__label">
  155. <span class="dops-section-header__label-text">
  156. <?php echo esc_html( $title ); ?>
  157. </span>
  158. </div>
  159. <div class="dops-section-header__actions handle-actions hide-if-no-js">
  160. <button type="button" id="stats_nudges_toggle" class="handlediv" aria-expanded="<?php echo esc_attr( $aria_expanded ); ?>">
  161. <span class="screen-reader-text">Toggle Upgrade Nudges</span>
  162. <span class="toggle-indicator" aria-hidden="true"></span>
  163. </button>
  164. </div>
  165. </div>
  166. <div class="inside">
  167. <?php
  168. }
  169. /**
  170. * Outputs the footer of the Upgrade Section
  171. *
  172. * @return void
  173. */
  174. private static function print_footer() {
  175. ?>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. <?php
  181. }
  182. /**
  183. * Outputs the custom css rules of the Upgrade Section
  184. *
  185. * @return void
  186. */
  187. private static function print_styles() {
  188. ?>
  189. <style>
  190. .dops-section-header.dops-card.jp-stats-report-upgrade-header {
  191. font-weight: bold;
  192. box-shadow: none;
  193. flex-wrap: nowrap;
  194. }
  195. #jp-stats-report-upgrade-wrap .dops-section-header__label-text {
  196. white-space: normal;
  197. }
  198. #stats_nudges_toggle {
  199. height: 100%;
  200. }
  201. .dops-banner.dops-card.is-product.jp-stats-report-upgrade-item {
  202. margin-bottom: 0px;
  203. border-left: 0px;
  204. box-shadow: none;
  205. border-top: 1px solid #c3c4c7;
  206. padding: 12px 24px;
  207. }
  208. .dops-banner.dops-card.jp-stats-report-upgrade-item.jp-stats-report-upgrade-subitem {
  209. margin-left: 72px;
  210. padding-left: 0px;
  211. }
  212. .jp-stats-report-upgrade-item .dops-banner__action {
  213. margin-right: 0px;
  214. }
  215. #jp-stats-report-upgrade-wrap .dops-card::after {
  216. content: "";
  217. }
  218. .jp-stats-report-upgrade-item .dops-banner__title p {
  219. margin: 5px 0 0 0;
  220. font-weight: normal;
  221. }
  222. #jp-stats-report-upgrade-wrap .postbox {
  223. background-color: white;
  224. border: 1px solid #c3c4c7;
  225. margin-bottom: 0;
  226. }
  227. #jp-stats-report-upgrade-wrap .postbox .inside {
  228. padding: 0;
  229. }
  230. </style>
  231. <?php
  232. }
  233. /**
  234. * Add a script which handles collapse/expansion of the nudge UI.
  235. */
  236. private static function print_script() {
  237. ?>
  238. <script>
  239. (function(window, document, undefined){
  240. window.onload = set_up_click_handler;
  241. const stats_wrap = document.getElementById( 'jp-stats-wrap' );
  242. stats_wrap.addEventListener( 'stats-loaded', function () {
  243. const stat_chart = document.getElementById( 'statchart' );
  244. if ( stat_chart === null ) {
  245. document.getElementById( 'stats_nudges_toggle' ).style.display = 'none';
  246. }
  247. });
  248. function set_up_click_handler(){
  249. document.getElementById( 'stats_nudges_toggle' ).onclick = function () {
  250. const collapseValue = 'true' === this.getAttribute( 'aria-expanded' );
  251. fetch( '/wp-json/jetpack/v4/settings', {
  252. method: 'post',
  253. body: JSON.stringify( { collapse_nudges: collapseValue } ),
  254. headers: {
  255. 'X-WP-Nonce': "<?php echo esc_js( wp_create_nonce( 'wp_rest' ) ); ?>",
  256. 'Content-type': 'application/json' }
  257. } );
  258. };
  259. }
  260. })(window, document, undefined);
  261. </script>
  262. <?php
  263. }
  264. /**
  265. * Gets the upgrade Redirect link
  266. *
  267. * @param string $source The source of the redirect link.
  268. * @return string
  269. */
  270. private static function get_upgrade_link( $source ) {
  271. $args = array();
  272. if ( ! ( new Manager( 'jetpack' ) )->has_connected_owner() ) {
  273. $args['query'] = 'unlinked=1';
  274. }
  275. return Redirect::get_url( $source, $args );
  276. }
  277. /**
  278. * Tracks an event in Tracks
  279. *
  280. * @param string $event_name The event name.
  281. * @return void
  282. */
  283. private static function track_event( $event_name ) {
  284. $connection_manager = new Manager( 'jetpack' );
  285. $tracking = new Tracking( 'jetpack', $connection_manager );
  286. $tracking->record_user_event(
  287. $event_name,
  288. array(
  289. 'has_connected_owner' => $connection_manager->has_connected_owner(),
  290. )
  291. );
  292. }
  293. /**
  294. * Outputs one Upgrade item
  295. *
  296. * @param string $title The title of the item.
  297. * @param string $text The description of the item.
  298. * @param string $icon The path of the icon, relative to Jetpack images folder.
  299. * @param string $link The link of the button.
  300. * @param string $tracks_id The id used to identify the tracks events. Automatically prefixed with "jetpack_stats_nudges_{view|click|learn_more}_".
  301. * @param string $learn_more_link The target of the "Learn more" link.
  302. * @param boolean $subitem Whether it is a subitem or not.
  303. * @param string $button_label The button label.
  304. * @return void
  305. */
  306. private static function print_item( $title, $text, $icon, $link, $tracks_id, $learn_more_link, $subitem = false, $button_label = null ) {
  307. $additional_classes = $subitem ? 'jp-stats-report-upgrade-subitem' : '';
  308. $button_class = $subitem ? 'is-secondary' : 'is-primary';
  309. $icon_url = plugins_url( '', JETPACK__PLUGIN_FILE ) . '/images/products/' . $icon;
  310. $button_label = is_null( $button_label ) ? __( 'Upgrade', 'jetpack' ) : $button_label;
  311. $view_event = "stats_nudges_view_$tracks_id";
  312. $click_event = "stats_nudges_click_$tracks_id";
  313. $learn_more_event = "stats_nudges_learn_more_$tracks_id";
  314. self::track_event( $view_event );
  315. ?>
  316. <div class="dops-card dops-banner has-call-to-action is-product jp-stats-report-upgrade-item <?php echo esc_attr( $additional_classes ); ?>">
  317. <div class="dops-banner__icon-plan">
  318. <img src="<?php echo esc_attr( $icon_url ); ?>" alt="" width="32" height="32">
  319. </div>
  320. <div class="dops-banner__content">
  321. <div class="dops-banner__info">
  322. <div class="dops-banner__title">
  323. <?php echo esc_html( $title ); ?>
  324. <p>
  325. <?php echo esc_html( $text ); ?>
  326. <a href="<?php echo esc_attr( $learn_more_link ); ?>" class="jptracks" target="_blank" rel="noopener noreferrer" data-jptracks-name="<?php echo esc_attr( $learn_more_event ); ?>">
  327. <?php esc_html_e( 'Learn more', 'jetpack' ); ?>
  328. </a>
  329. </p>
  330. </div>
  331. </div>
  332. <div class="dops-banner__action">
  333. <a href="<?php echo esc_attr( $link ); ?>" type="button" class="jptracks dops-button is-compact <?php echo esc_attr( $button_class ); ?>" data-jptracks-name="<?php echo esc_attr( $click_event ); ?>">
  334. <?php echo esc_html( $button_label ); ?>
  335. </a>
  336. </div>
  337. </div>
  338. </div>
  339. <?php
  340. }
  341. /**
  342. * Prints the Security item
  343. *
  344. * @return void
  345. */
  346. private static function print_security() {
  347. $upgrade_link = self::get_upgrade_link( 'stats-nudges-security' );
  348. $learn_link = self::get_upgrade_link( 'stats-nudges-security-learn' );
  349. $text = __( 'Comprehensive protection for your site, including Backup, Scan, and Anti-spam.', 'jetpack' );
  350. self::print_item( __( 'Security', 'jetpack' ), $text, 'product-jetpack-security-bundle.svg', $upgrade_link, 'security', $learn_link );
  351. }
  352. /**
  353. * Prints the Backup item
  354. *
  355. * @return void
  356. */
  357. private static function print_backup() {
  358. $upgrade_link = self::get_upgrade_link( 'stats-nudges-backup' );
  359. $learn_link = self::get_upgrade_link( 'stats-nudges-backup-learn' );
  360. $text = __( 'Save every change and get back online quickly with one-click restores.', 'jetpack' );
  361. self::print_item( __( 'Backup', 'jetpack' ), $text, 'product-jetpack-backup.svg', $upgrade_link, 'backup', $learn_link, true );
  362. }
  363. /**
  364. * Prints the Scan item
  365. *
  366. * @return void
  367. */
  368. private static function print_scan() {
  369. $upgrade_link = self::get_upgrade_link( 'stats-nudges-scan' );
  370. $learn_link = self::get_upgrade_link( 'stats-nudges-scan-learn' );
  371. $text = __( 'Stay ahead of security threats with automated scanning and one-click fixes.', 'jetpack' );
  372. self::print_item( __( 'Scan', 'jetpack' ), $text, 'product-jetpack-scan.svg', $upgrade_link, 'scan', $learn_link, true );
  373. }
  374. /**
  375. * Prints the Akismet item
  376. *
  377. * @return void
  378. */
  379. private static function print_akismet() {
  380. $upgrade_link = self::get_upgrade_link( 'stats-nudges-akismet' );
  381. $learn_link = self::get_upgrade_link( 'stats-nudges-akismet-learn' );
  382. $text = __( 'Automatically clear spam from comments and forms.', 'jetpack' );
  383. self::print_item( __( 'Anti-spam', 'jetpack' ), $text, 'product-jetpack-anti-spam.svg', $upgrade_link, 'akismet', $learn_link, true );
  384. }
  385. /**
  386. * Prints the Search item
  387. *
  388. * @return void
  389. */
  390. private static function print_search() {
  391. $upgrade_link = self::get_upgrade_link( 'stats-nudges-search' );
  392. $learn_link = self::get_upgrade_link( 'stats-nudges-search-learn' );
  393. $text = __( 'Help your site visitors instantly find what they\'re looking for so they read and buy more.', 'jetpack' );
  394. self::print_item( __( 'Search', 'jetpack' ), $text, 'product-jetpack-search.svg', $upgrade_link, 'search', $learn_link );
  395. }
  396. /**
  397. * Prints the Boost item
  398. *
  399. * @param bool $print Whether to print the item output or just check whether it would be printed or not.
  400. *
  401. * @return bool
  402. */
  403. private static function get_boost_output( $print = true ) {
  404. $plugin_file = 'jetpack-boost/jetpack-boost.php';
  405. $plugin_slug = 'jetpack-boost';
  406. if ( self::is_plugin_active( $plugin_file ) ) {
  407. return false;
  408. } elseif ( self::is_plugin_installed( $plugin_file ) ) {
  409. $label = __( 'Activate Boost', 'jetpack' );
  410. $link = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . rawurlencode( $plugin_file ) . '&amp;plugin_status=all&amp;paged=1', 'activate-plugin_' . $plugin_file );
  411. } else {
  412. $label = __( 'Install Boost', 'jetpack' );
  413. $link = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug ), 'install-plugin_' . $plugin_slug );
  414. }
  415. if ( $print ) {
  416. $learn_link = self::get_upgrade_link( 'stats-nudges-boost-learn' );
  417. $text = __( 'Improve your site\'s performance and SEO in a few clicks with the free Jetpack Boost plugin.', 'jetpack' );
  418. self::print_item( __( 'Boost', 'jetpack' ), $text, 'product-jetpack-boost.svg', $link, 'boost', $learn_link, false, $label );
  419. }
  420. return true;
  421. }
  422. /**
  423. * Prints the CRM item
  424. *
  425. * @param bool $print Whether to print the item output or just check whether it would be printed or not.
  426. *
  427. * @return bool
  428. */
  429. private static function get_crm_output( $print = true ) {
  430. $plugin_file = Jetpack_CRM_Data::JETPACK_CRM_PLUGIN_SLUG;
  431. $plugin_slug = substr( $plugin_file, 0, strpos( $plugin_file, '/' ) );
  432. if ( self::is_plugin_active( $plugin_file ) ) {
  433. return false;
  434. } elseif ( self::is_plugin_installed( $plugin_file ) ) {
  435. $link = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . rawurlencode( $plugin_file ) . '&amp;plugin_status=all&amp;paged=1', 'activate-plugin_' . $plugin_file );
  436. $label = __( 'Activate CRM', 'jetpack' );
  437. } else {
  438. $link = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug ), 'install-plugin_' . $plugin_slug );
  439. $label = __( 'Install CRM', 'jetpack' );
  440. }
  441. if ( $print ) {
  442. $learn_link = self::get_upgrade_link( 'stats-nudges-crm-learn' );
  443. $text = __( 'Sell more and get more leads with the Jetpack CRM plugin built specifically for WordPress.', 'jetpack' );
  444. self::print_item( __( 'CRM', 'jetpack' ), $text, 'product-jetpack-crm.svg', $link, 'crm', $learn_link, false, $label );
  445. }
  446. return true;
  447. }
  448. /**
  449. * Outputs the section to the Stats Report page
  450. *
  451. * @param string $callback The callback passed to the jetpack admin page.
  452. * @return void
  453. */
  454. public static function render( $callback ) {
  455. /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
  456. if ( 'stats_reports_page' !== $callback || ! apply_filters( 'jetpack_show_promotions', true ) || ! current_user_can( 'manage_options' ) ) {
  457. return;
  458. }
  459. if ( self::has_complete_plan() ) {
  460. return;
  461. }
  462. if (
  463. self::has_security_plan() &&
  464. self::is_backup_active() &&
  465. self::is_scan_active() &&
  466. self::is_akismet_active() &&
  467. self::is_search_active() &&
  468. ! self::get_boost_output( false ) &&
  469. ! self::get_crm_output( false )
  470. ) {
  471. return;
  472. }
  473. self::print_styles();
  474. self::print_script();
  475. self::print_header();
  476. if ( ! self::has_security_plan() ) {
  477. self::print_security();
  478. if ( ! self::is_backup_active() ) {
  479. self::print_backup();
  480. }
  481. if ( ! self::is_scan_active() ) {
  482. self::print_scan();
  483. }
  484. if ( ! self::is_akismet_active() ) {
  485. self::print_akismet();
  486. }
  487. }
  488. if ( ! self::is_search_active() ) {
  489. self::print_search();
  490. }
  491. self::get_boost_output();
  492. self::get_crm_output();
  493. self::print_footer();
  494. }
  495. }