Нет описания

Alerts.php 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. /**
  9. * Class PUM_Utils_Alerts
  10. */
  11. class PUM_Utils_Alerts {
  12. /**
  13. *
  14. */
  15. public static function init() {
  16. add_action( 'admin_init', array( __CLASS__, 'hooks' ) );
  17. add_action( 'admin_init', array( __CLASS__, 'php_handler' ) );
  18. add_action( 'wp_ajax_pum_alerts_action', array( __CLASS__, 'ajax_handler' ) );
  19. add_filter( 'pum_alert_list', array( __CLASS__, 'whats_new_alerts' ), 0 );
  20. add_filter( 'pum_alert_list', array( __CLASS__, 'integration_alerts' ), 5 );
  21. add_filter( 'pum_alert_list', array( __CLASS__, 'translation_request' ), 10 );
  22. add_action( 'admin_menu', array( __CLASS__, 'append_alert_count' ), 999 );
  23. }
  24. /**
  25. * Gets a count of current alerts.
  26. *
  27. * @return int
  28. */
  29. public static function alert_count() {
  30. return count( self::get_alerts() );
  31. }
  32. /**
  33. * Append alert count to Popup Maker menu item.
  34. */
  35. public static function append_alert_count() {
  36. global $menu;
  37. $count = self::alert_count();
  38. foreach ( $menu as $key => $item ) {
  39. if ( $item[2] == 'edit.php?post_type=popup' ) {
  40. $menu[ $key ][0] .= $count ? ' <span class="update-plugins count-' . $count . '"><span class="plugin-count pum-alert-count" aria-hidden="true">' . $count . '</span></span>' : '';
  41. }
  42. }
  43. }
  44. /**
  45. * @param array $alerts
  46. *
  47. * @return array
  48. */
  49. public static function translation_request( $alerts = array() ) {
  50. $version = explode( '.', Popup_Maker::$VER );
  51. // Get only the major.minor version exclude the point releases.
  52. $version = $version[0] . '.' . $version[1];
  53. $code = 'translation_request_' . $version;
  54. // Bail Early if they have already dismissed.
  55. if ( self::has_dismissed_alert( $code ) ) {
  56. return $alerts;
  57. }
  58. // Get locales based on the HTTP accept language header.
  59. $locales_from_header = PUM_Utils_I10n::get_http_locales();
  60. // Abort early if no locales in header.
  61. if ( empty( $locales_from_header ) ) {
  62. return $alerts;
  63. }
  64. // Get acceptable non EN WordPress locales based on the HTTP accept language header.
  65. // Used when the current locale is EN only I believe.
  66. $non_en_locales_from_header = PUM_Utils_I10n::get_non_en_accepted_wp_locales_from_header();
  67. // If no additional languages are supported abort
  68. if ( empty( $non_en_locales_from_header ) ) {
  69. return $alerts;
  70. }
  71. /**
  72. * Assume all at this point are possible polyglots.
  73. *
  74. * Viewing in English!
  75. * -- Translation available in one additional language!
  76. * ---- Show notice that there other language is available and we need help translating.
  77. * -- Translation available in more than one language!
  78. * ---- Show notice that their other languages are available and need help translating.
  79. * -- Translation not available!
  80. * ---- Show notice that plugin is not translated and we need help.
  81. * Else If translation for their language(s) exists, but isn't up to date!
  82. * -- Show notice that their language is available, but out of date and need help translating.
  83. * Else If translations for their language doesn't exist!
  84. * -- Show notice that plugin is not translated and we need help.
  85. */
  86. $current_locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
  87. // Get the active language packs of the plugin.
  88. $translation_status = PUM_Utils_I10n::translation_status();
  89. // Retrieve all the WordPress locales in which the plugin is translated.
  90. $locales_with_translations = wp_list_pluck( $translation_status, 'language' );
  91. $locale_translation_versions = wp_list_pluck( $translation_status, 'version' );
  92. // Suggests existing langpacks
  93. $suggested_locales_with_langpack = array_values( array_intersect( $non_en_locales_from_header, $locales_with_translations ) );
  94. $current_locale_is_suggested = in_array( $current_locale, $suggested_locales_with_langpack );
  95. $current_locale_is_translated = in_array( $current_locale, $locales_with_translations );
  96. // Last chance to abort early before querying all available languages.
  97. // We abort here if the user is already using a translated language that is up to date!
  98. if ( $current_locale_is_suggested && $current_locale_is_translated && version_compare( $locale_translation_versions[ $current_locale ], Popup_Maker::$VER, '>=' ) ) {
  99. return $alerts;
  100. }
  101. // Retrieve all the WordPress locales.
  102. $locales_supported_by_wordpress = PUM_Utils_I10n::available_locales();
  103. // Get the native language names of the locales.
  104. $suggest_translated_locale_names = array();
  105. foreach ( $suggested_locales_with_langpack as $locale ) {
  106. $suggest_translated_locale_names[ $locale ] = $locales_supported_by_wordpress[ $locale ]['native_name'];
  107. }
  108. $suggest_string = '';
  109. // If we get this far, they clearly have multiple language available
  110. // If current locale is english but they have others available, they are likely polyglots.
  111. $currently_in_english = strpos( $current_locale, 'en' ) === 0;
  112. // Currently in English.
  113. if ( $currently_in_english ) {
  114. // Only one locale suggestion.
  115. if ( 1 === count( $suggest_translated_locale_names ) ) {
  116. $language = current( $suggest_translated_locale_names );
  117. $suggest_string = sprintf( /* translators: %s: native language name. */
  118. __( 'This plugin is also available in %1$s. <a href="%2$s" target="_blank">Help improve the translation!</a>', 'popup-maker' ), $language, esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  119. // Multiple locale suggestions.
  120. } elseif ( ! empty( $suggest_translated_locale_names ) ) {
  121. $primary_language = current( $suggest_translated_locale_names );
  122. array_shift( $suggest_translated_locale_names );
  123. $other_suggest = '';
  124. foreach ( $suggest_translated_locale_names as $language ) {
  125. $other_suggest .= $language . ', ';
  126. }
  127. $suggest_string = sprintf( /* translators: 1: native language name, 2: other native language names, comma separated */
  128. __( 'This plugin is also available in %1$s (also: %2$s). <a href="%3$s" target="_blank">Help improve the translation!</a>', 'popup-maker' ), $primary_language, trim( $other_suggest, ' ,' ), esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  129. // Non-English locale in header, no translations.
  130. } elseif ( ! empty( $non_en_locales_from_header ) ) {
  131. if ( 1 === count( $non_en_locales_from_header ) ) {
  132. $locale = reset( $non_en_locales_from_header );
  133. $suggest_string = sprintf( /* translators: 1: native language name, 2: URL to translate.wordpress.org */
  134. __( 'This plugin is not translated into %1$s yet. <a href="%2$s" target="_blank">Help translate it!</a>', 'popup-maker' ), $locales_supported_by_wordpress[ $locale ]['native_name'], esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  135. } else {
  136. $primary_locale = reset( $non_en_locales_from_header );
  137. $primary_language = $locales_supported_by_wordpress[ $primary_locale ]['native_name'];
  138. array_shift( $non_en_locales_from_header );
  139. $other_suggest = '';
  140. foreach ( $non_en_locales_from_header as $locale ) {
  141. $other_suggest .= $locales_supported_by_wordpress[ $locale ]['native_name'] . ', ';
  142. }
  143. $suggest_string = sprintf( /* translators: 1: native language name, 2: other native language names, comma separated */
  144. __( 'This plugin is also available in %1$s (also: %2$s). <a href="%3$s" target="_blank">Help improve the translation!</a>', 'popup-maker' ), $primary_language, trim( $other_suggest, ' ,' ), esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  145. }
  146. }
  147. // The plugin has no translation for the current locale.
  148. } elseif ( ! $current_locale_is_suggested && ! $current_locale_is_translated ) {
  149. $suggest_string = sprintf( __( 'This plugin is not translated into %1$s yet. <a href="%2$s" target="_blank">Help translate it!</a>', 'popup-maker' ), $locales_supported_by_wordpress[ $current_locale ]['native_name'], esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  150. // The plugin has translations for current locale, but they are out of date.
  151. } elseif ( $current_locale_is_suggested && $current_locale_is_translated && version_compare( $locale_translation_versions[ $current_locale ], Popup_Maker::$VER, '<' ) ) {
  152. $suggest_string = sprintf( /* translators: %s: native language name. */
  153. __( 'This plugin\'s translation for %1$s is out of date. <a href="%2$s" target="_blank">Help improve the translation!</a>', 'popup-maker' ), $locales_supported_by_wordpress[ $current_locale ]['native_name'], esc_url( 'https://translate.wordpress.org/projects/wp-plugins/popup-maker' ) );
  154. }
  155. if ( ! empty( $suggest_string ) ) {
  156. $alerts[] = array(
  157. 'code' => $code,
  158. 'message' => $suggest_string,
  159. 'type' => 'info',
  160. );
  161. }
  162. return $alerts;
  163. }
  164. /**
  165. * @param array $alerts
  166. *
  167. * @return array
  168. */
  169. public static function whats_new_alerts( $alerts = array() ) {
  170. $upgraded_from = PUM_Utils_Upgrades::$upgraded_from;
  171. if ( version_compare( $upgraded_from, '0.0.0', '>' ) ) {
  172. if ( version_compare( $upgraded_from, '1.8.0', '<' ) ) {
  173. $alerts[] = array(
  174. 'code' => 'whats_new_1_8_0',
  175. 'type' => 'success',
  176. 'message' => sprintf( '<strong>' . __( 'See whats new in v%s - (%sview all changes%s)', 'popup-maker' ) . '</strong>', '1.8.0', '<a href="' . add_query_arg( array(
  177. 'tab' => 'plugin-information',
  178. 'plugin' => 'popup-maker',
  179. 'section' => 'changelog',
  180. 'TB_iframe' => true,
  181. 'width' => 722,
  182. 'height' => 949,
  183. ), admin_url( 'plugin-install.php' ) ) . '" target="_blank">', '</a>' ),
  184. 'html' => "<ul class='ul-disc'>" . "<li>" . 'New UX for the Popup Theme editor.' . "</li>" . "<li>" . 'New close button positions: top center, bottom center, middle left & middle right.' . "</li>" . "<li>" . 'New option to position close button outside of popup.' . "</li>" . "</ul>",
  185. 'priority' => 100,
  186. );
  187. }
  188. }
  189. return $alerts;
  190. }
  191. /**
  192. * @param array $alerts
  193. *
  194. * @return array
  195. */
  196. public static function integration_alerts( $alerts = array() ) {
  197. $integrations = array(
  198. 'buddypress' => array(
  199. 'label' => __( 'BuddyPress', 'buddypress' ),
  200. 'learn_more_url' => 'https://wppopupmaker.com/works-with/buddypress/',
  201. 'conditions' => ! class_exists( 'PUM_BuddyPress' ) && ( function_exists( 'buddypress' ) || class_exists( 'BuddyPress' ) ),
  202. 'slug' => 'popup-maker-buddypress-integration',
  203. 'name' => 'Popup Maker - BuddyPress Integration',
  204. 'free' => true,
  205. ),
  206. );
  207. foreach ( $integrations as $key => $integration ) {
  208. if ( $integration['conditions'] ) {
  209. $path = "{$integration['slug']}/{$integration['slug']}.php";
  210. $plugin_data = file_exists( WP_PLUGIN_DIR . '/' . $path ) ? get_plugin_data( WP_PLUGIN_DIR . '/' . $path, false, false ) : false;
  211. $installed = $plugin_data && ! empty( $plugin_data['Name'] ) && $plugin_data['Name'] === $integration['name'];
  212. $text = $installed ? __( 'activate it now', 'popup-maker' ) : __( 'install it now', 'popup-maker' );
  213. $url = $installed ? esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=' . $path ), 'activate-plugin_' . $path ) ) : esc_url( wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=popup-maker-buddypress-integration' ), 'install-plugin_popup-maker-buddypress-integration' ) );
  214. $alerts[] = array(
  215. 'code' => $key . '_integration_available',
  216. 'message' => sprintf( __( '%sDid you know:%s Popup Maker has custom integrations with %s, %slearn more%s or %s%s%s!', 'popup-maker' ), '<strong>', '</strong>', $integration['label'], '<a href="' . $integration['learn_more_url'] . '" target="_blank">', '</a>', '<a href="' . $url . '">', $text, '</a>' ),
  217. 'dismissible' => true,
  218. 'global' => false,
  219. 'type' => $installed ? 'warning' : 'info',
  220. );
  221. }
  222. }
  223. return $alerts;
  224. }
  225. /**
  226. * Hook into relevant WP actions.
  227. */
  228. public static function hooks() {
  229. if ( is_admin() && current_user_can( 'edit_posts' ) ) {
  230. add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
  231. add_action( 'network_admin_notices', array( __CLASS__, 'admin_notices' ) );
  232. add_action( 'user_admin_notices', array( __CLASS__, 'admin_notices' ) );
  233. }
  234. }
  235. /**
  236. * @return bool
  237. */
  238. public static function should_show_alerts() {
  239. return in_array( true, array(
  240. pum_is_admin_page(),
  241. count( self::get_global_alerts() ) > 0,
  242. ) );
  243. }
  244. /**
  245. * Render admin alerts if available.
  246. */
  247. public static function admin_notices() {
  248. if ( ! self::should_show_alerts() ) {
  249. return;
  250. }
  251. $global_only = ! pum_is_admin_page();
  252. $alerts = $global_only ? self::get_global_alerts() : self::get_alerts();
  253. $count = count( $alerts );
  254. if ( ! $count ) {
  255. return;
  256. }
  257. wp_enqueue_script( 'pum-admin-general' );
  258. wp_enqueue_style( 'pum-admin-general' );
  259. $nonce = wp_create_nonce( 'pum_alerts_action' );
  260. ?>
  261. <script type="text/javascript">
  262. window.pum_alerts_nonce = '<?php echo $nonce ?>';
  263. </script>
  264. <div class="pum-alerts">
  265. <h3>
  266. <img alt="" class="logo" width="30" src="<?php echo Popup_Maker::$URL; ?>assets/images/logo.png" /> <?php printf( '%s%s (%s)', ( $global_only ? __( 'Popup Maker', 'popup-maker' ) . ' ' : '' ), __( 'Notifications', 'popup-maker' ), '<span class="pum-alert-count">' . $count . '</span>' ); ?>
  267. </h3>
  268. <p><?php __( 'Check out the following notifications from Popup Maker.', 'popup-maker' ); ?></p>
  269. <?php foreach ( $alerts as $alert ) {
  270. $expires = 1 == $alert['dismissible'] ? '' : $alert['dismissible'];
  271. $dismiss_url = add_query_arg( array(
  272. 'nonce' => $nonce,
  273. 'code' => $alert['code'],
  274. 'pum_dismiss_alert' => 'dismiss',
  275. 'expires' => $expires,
  276. ));
  277. ?>
  278. <div class="pum-alert-holder" data-code="<?php echo $alert['code']; ?>" class="<?php echo $alert['dismissible'] ? 'is-dismissible' : ''; ?>" data-dismissible="<?php echo esc_attr( $alert['dismissible'] ); ?>">
  279. <div class="pum-alert <?php echo '' !== $alert['type'] ? 'pum-alert__' . esc_attr( $alert['type'] ) : ''; ?>">
  280. <?php if ( ! empty( $alert['message'] ) ) : ?>
  281. <p><?php echo $alert['message']; ?></p>
  282. <?php endif; ?>
  283. <?php if ( ! empty( $alert['html'] ) ) : ?>
  284. <?php echo function_exists( 'wp_encode_emoji' ) ? wp_encode_emoji( $alert['html'] ) : $alert['html']; ?>
  285. <?php endif; ?>
  286. <?php if ( ! empty( $alert['actions'] ) && is_array( $alert['actions'] ) ) : ?>
  287. <ul>
  288. <?php
  289. foreach ( $alert['actions'] as $action ) {
  290. $link_text = ! empty( $action['primary'] ) && true === $action['primary'] ? '<strong>' . esc_html( $action['text'] ) . '</strong>' : esc_html( $action['text'] );
  291. if ( 'link' === $action['type'] ) {
  292. $url = $action['href'];
  293. $attributes = 'target="_blank" rel="noreferrer noopener"';
  294. } else {
  295. $url = add_query_arg( array(
  296. 'nonce' => $nonce,
  297. 'code' => $alert['code'],
  298. 'pum_dismiss_alert' => $action['action'],
  299. 'expires' => $expires,
  300. ));
  301. $attributes = 'class="pum-dismiss"';
  302. }
  303. ?>
  304. <li><a data-action="<?php echo esc_attr( $action['action'] ); ?>" href="<?php echo esc_url( $url ); ?>" <?php echo $attributes; ?> ><?php echo $link_text; ?></a></li>
  305. <?php } ?>
  306. </ul>
  307. <?php endif; ?>
  308. </div>
  309. <?php if ( $alert['dismissible'] ) : ?>
  310. <a href="<?php echo esc_url( $dismiss_url ); ?>" data-action="dismiss" class="button dismiss pum-dismiss">
  311. <span class="screen-reader-text"><?php _e( 'Dismiss this item.', 'popup-maker' ); ?></span> <span class="dashicons dashicons-no-alt"></span>
  312. </a>
  313. <?php endif; ?>
  314. </div>
  315. <?php } ?>
  316. </div>
  317. <?php
  318. }
  319. /**
  320. * @return array
  321. */
  322. public static function get_global_alerts() {
  323. $alerts = self::get_alerts();
  324. $global_alerts = array();
  325. foreach ( $alerts as $alert ) {
  326. if ( $alert['global'] ) {
  327. $global_alerts[] = $alert;
  328. }
  329. }
  330. return $global_alerts;
  331. }
  332. /**
  333. * @return array
  334. */
  335. public static function get_alerts() {
  336. static $alert_list;
  337. if ( ! isset( $alert_list ) ) {
  338. $alert_list = apply_filters( 'pum_alert_list', array() );
  339. }
  340. $alerts = array();
  341. foreach ( $alert_list as $alert ) {
  342. // Ignore dismissed alerts.
  343. if ( self::has_dismissed_alert( $alert['code'] ) ) {
  344. continue;
  345. }
  346. $alerts[] = wp_parse_args( $alert, array(
  347. 'code' => 'default',
  348. 'priority' => 10,
  349. 'message' => '',
  350. 'type' => 'info',
  351. 'html' => '',
  352. 'dismissible' => true,
  353. 'global' => false,
  354. ) );
  355. }
  356. // Sort alerts by priority, highest to lowest.
  357. $alerts = PUM_Utils_Array::sort( $alerts, 'priority', true );
  358. return $alerts;
  359. }
  360. /**
  361. * Handles if alert was dismissed AJAX
  362. */
  363. public static function ajax_handler() {
  364. $args = wp_parse_args( $_REQUEST, array(
  365. 'code' => '',
  366. 'expires' => '',
  367. 'pum_dismiss_alert' => '',
  368. ));
  369. if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'pum_alerts_action' ) ) {
  370. wp_send_json_error();
  371. }
  372. $results = self::action_handler( $args['code'], $args['pum_dismiss_alert'], $args['expires'] );
  373. if ( true === $results ) {
  374. wp_send_json_success();
  375. } else {
  376. wp_send_json_error();
  377. }
  378. }
  379. /**
  380. * Handles if alert was dismissed by page reload instead of AJAX
  381. *
  382. * @since 1.11.0
  383. */
  384. public static function php_handler() {
  385. if ( ! isset( $_REQUEST['pum_dismiss_alert'] ) ) {
  386. return;
  387. }
  388. if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'pum_alerts_action' ) ) {
  389. return;
  390. }
  391. $args = wp_parse_args( $_REQUEST, array(
  392. 'code' => '',
  393. 'expires' => '',
  394. 'pum_dismiss_alert' => '',
  395. ));
  396. self::action_handler( $args['code'], $args['pum_dismiss_alert'], $args['expires'] );
  397. }
  398. /**
  399. * Handles the action taken on the alert.
  400. *
  401. * @param string $code The specific alert.
  402. * @param string $action Which action was taken
  403. * @param string $expires When the dismissal expires, if any.
  404. *
  405. * @return bool
  406. * @uses PUM_Utils_Logging::instance
  407. * @uses PUM_Utils_Logging::log
  408. * @since 1.11.0
  409. */
  410. public static function action_handler( $code, $action, $expires ) {
  411. if ( empty( $action ) || 'dismiss' === $action ) {
  412. try {
  413. $dismissed_alerts = self::dismissed_alerts();
  414. $dismissed_alerts[ $code ] = ! empty( $expires ) ? strtotime( '+' . $expires ) : true;
  415. $user_id = get_current_user_id();
  416. update_user_meta( $user_id, '_pum_dismissed_alerts', $dismissed_alerts );
  417. return true;
  418. } catch ( Exception $e ) {
  419. PUM_Utils_Logging::instance()->log( 'Error dismissing alert. Exception: ' . $e->getMessage() );
  420. return false;
  421. }
  422. }
  423. do_action( 'pum_alert_dismissed', $code, $action );
  424. }
  425. /**
  426. * @param string $code
  427. *
  428. * @return bool
  429. */
  430. public static function has_dismissed_alert( $code = '' ) {
  431. $dimissed_alerts = self::dismissed_alerts();
  432. $alert_dismissed = array_key_exists( $code, $dimissed_alerts );
  433. // If the alert was dismissed and has a non true type value, it is an expiry time.
  434. if ( $alert_dismissed && true !== $dimissed_alerts[ $code ] ) {
  435. return strtotime( 'now' ) < $dimissed_alerts[ $code ];
  436. }
  437. return $alert_dismissed;
  438. }
  439. /**
  440. * Returns an array of dismissed alert groups.
  441. *
  442. * @return array
  443. */
  444. public static function dismissed_alerts() {
  445. $user_id = get_current_user_id();
  446. $dismissed_alerts = get_user_meta( $user_id, '_pum_dismissed_alerts', true );
  447. if ( ! is_array( $dismissed_alerts ) ) {
  448. $dismissed_alerts = array();
  449. update_user_meta( $user_id, '_pum_dismissed_alerts', $dismissed_alerts );
  450. }
  451. return $dismissed_alerts;
  452. }
  453. }