/lib/com/notice.php

  1. <?php 
  2. /** 
  3. * License: GPLv3 
  4. * License URI: https://www.gnu.org/licenses/gpl.txt 
  5. * Copyright 2012-2017 Jean-Sebastien Morisset (https://surniaulula.com/) 
  6. */ 
  7.  
  8. if ( ! defined( 'ABSPATH' ) ) { 
  9. die( 'These aren\'t the droids you\'re looking for...' ); 
  10.  
  11. if ( ! class_exists( 'SucomNotice' ) ) { 
  12.  
  13. class SucomNotice { 
  14.  
  15. private $p; 
  16. private $lca = 'sucom'; 
  17. private $text_dom = 'sucom'; 
  18. private $opt_name = 'sucom_notices'; 
  19. private $dis_name = 'sucom_dismissed'; 
  20. private $hide_err = false; 
  21. private $hide_warn = false; 
  22. private $all_types = array( 'nag', 'err', 'warn', 'upd', 'inf' ); 
  23. private $notice_cache = array(); 
  24. private $reference_url = null; 
  25. private $has_shown = false; 
  26.  
  27. public $enabled = true; 
  28.  
  29. public function __construct( &$plugin ) { 
  30. $this->p =& $plugin; 
  31.  
  32. if ( ! empty( $this->p->debug->enabled ) ) { 
  33. $this->p->debug->mark(); 
  34.  
  35. if ( ! empty( $this->p->cf['lca'] ) ) { 
  36. $this->lca = $this->p->cf['lca']; 
  37. if ( ! empty( $this->p->cf['plugin'][$this->lca]['text_domain'] ) ) 
  38. $this->text_dom = $this->p->cf['plugin'][$this->lca]['text_domain']; 
  39.  
  40. $uca = strtoupper( $this->lca ); 
  41.  
  42. $this->opt_name = defined( $uca.'_NOTICE_NAME' ) ? 
  43. constant( $uca.'_NOTICE_NAME' ) : $this->lca.'_notices'; 
  44.  
  45. $this->dis_name = defined( $uca.'_DISMISS_NAME' ) ? 
  46. constant( $uca.'_DISMISS_NAME' ) : $this->lca.'_dismissed'; 
  47.  
  48. $this->hide_err = defined( $uca.'_HIDE_ALL_ERRORS' ) ? 
  49. constant( $uca.'_HIDE_ALL_ERRORS' ) : false; 
  50.  
  51. $this->hide_warn = defined( $uca.'_HIDE_ALL_WARNINGS' ) ? 
  52. constant( $uca.'_HIDE_ALL_WARNINGS' ) : false; 
  53.  
  54. if ( is_admin() ) { 
  55. add_action( 'wp_ajax_'.$this->lca.'_dismiss_notice', array( &$this, 'ajax_dismiss_notice' ) ); 
  56. add_action( 'admin_footer', array( &$this, 'admin_footer_script' ) ); 
  57. add_action( 'in_admin_header', array( &$this, 'hook_admin_notices' ), 300000 ); 
  58.  
  59. add_action( 'shutdown', array( &$this, 'shutdown_save_notices' ) ); 
  60.  
  61. public function hook_admin_notices() { 
  62. add_action( 'all_admin_notices', array( &$this, 'show_admin_notices' ), -10 ); 
  63.  
  64. public function nag( $msg_txt, $user_id = true, $dis_key = false ) { 
  65. $this->log( 'nag', $msg_txt, $user_id, $dis_key, false ); // $dis_time = false 
  66.  
  67. public function err( $msg_txt, $user_id = true, $dis_key = false, $dis_time = false ) { 
  68. $this->log( 'err', $msg_txt, $user_id, $dis_key, $dis_time ); 
  69.  
  70. public function warn( $msg_txt, $user_id = true, $dis_key = false, $dis_time = false, $silent = false ) { 
  71. $payload = array( 'silent' => $silent ? true : false ); 
  72. $this->log( 'warn', $msg_txt, $user_id, $dis_key, $dis_time, $payload ); 
  73.  
  74. public function upd( $msg_txt, $user_id = true, $dis_key = false, $dis_time = false ) { 
  75. $this->log( 'upd', $msg_txt, $user_id, $dis_key, $dis_time ); 
  76.  
  77. public function inf( $msg_txt, $user_id = true, $dis_key = false, $dis_time = false ) { 
  78. $this->log( 'inf', $msg_txt, $user_id, $dis_key, $dis_time ); 
  79.  
  80. public function log( $msg_type, $msg_txt, $user_id = true, $dis_key = false, $dis_time = false, $payload = array() ) { 
  81.  
  82. if ( empty( $msg_type ) || empty( $msg_txt ) ) { 
  83. return; 
  84.  
  85. // dis_key is independant of the msg_type, so all can be hidden with one dis_key 
  86. $payload['dis_key'] = empty( $dis_key ) ? false : $dis_key; 
  87.  
  88. // dis_key and dis_time (true or seconds) are required to dismiss a notice 
  89. if ( ! empty( $dis_key ) && ! empty( $dis_time ) && $this->can_dismiss() ) { 
  90. $payload['dis_time'] = $dis_time; 
  91. if ( is_numeric( $payload['dis_time'] ) ) { 
  92. $msg_txt .= ' '.sprintf( __( 'This notice can be dismissed for %s.', $this->text_dom ),  
  93. human_time_diff( 0, $payload['dis_time'] ) ); 
  94. } else { 
  95. $payload['dis_time'] = false; 
  96.  
  97. if ( $this->reference_url ) { 
  98. $msg_txt .= '<br/><small>'.sprintf( __( 'Reference URL: %s', $this->text_dom ),  
  99. '<a href="'.$this->reference_url.'">'.$this->reference_url.'</a>' ).'</small>'; 
  100.  
  101. if ( $user_id === true ) { 
  102. $user_id = (int) get_current_user_id(); 
  103. } else { 
  104. $user_id = (int) $user_id; // false = 0 
  105.  
  106. // returns reference to cache array 
  107. $user_notices =& $this->get_user_notices( $user_id ); 
  108.  
  109. // user notices are saved on shutdown 
  110. if ( ! isset( $user_notices[$msg_type][$msg_txt] ) ) { 
  111. $user_notices[$msg_type][$msg_txt] = $payload; 
  112.  
  113. public function trunc_key( $dis_key, $user_id = true ) { 
  114. $this->trunc( '', '', $dis_key, $user_id ); 
  115.  
  116. public function trunc_all() { 
  117. $this->trunc( '', '', false, 'all' ); 
  118.  
  119. public function trunc( $msg_type = '', $msg_txt = '', $dis_key = false, $user_id = true ) { 
  120.  
  121. if ( $user_id === true ) { 
  122. $user_ids = array( get_current_user_id() ); 
  123. } elseif ( $user_id === 'all' ) { 
  124. $user_ids =& $this->get_user_ids(); // returns reference 
  125. } elseif ( is_array( $user_id ) ) { 
  126. $user_ids = $user_id; 
  127. } else { 
  128. $user_ids = array( $user_id ); 
  129.  
  130. $trunc_types = empty( $msg_type ) ? 
  131. $this->all_types : array( (string) $msg_type ); 
  132.  
  133. foreach ( $user_ids as $user_id ) { 
  134.  
  135. // returns reference to cache array 
  136. $user_notices =& $this->get_user_notices( $user_id ); 
  137.  
  138. foreach ( $trunc_types as $msg_type ) { 
  139. if ( isset( $user_notices[$msg_type] ) ) { 
  140.  
  141. // clear notice for a specific dis_key 
  142. if ( ! empty( $dis_key ) && is_array( $user_notices[$msg_type] ) ) { 
  143.  
  144. // use a reference for the payload 
  145. foreach ( $user_notices[$msg_type] as $msg_txt => &$payload ) { 
  146. if ( ! empty( $payload['dis_key'] ) && $payload['dis_key'] === $dis_key ) { 
  147. unset( $payload ); // unset by reference 
  148.  
  149. // clear all notices for that type 
  150. } elseif ( empty( $msg_txt ) ) { 
  151. $user_notices[$msg_type] = array(); 
  152.  
  153. // clear a specific message string 
  154. } elseif ( isset( $user_notices[$msg_type][$msg_txt] ) ) { 
  155. unset( $user_notices[$msg_type][$msg_txt] ); 
  156.  
  157. // returns the previous URL 
  158. public function set_reference_url( $url = null ) { 
  159. $previous_url = $this->reference_url; 
  160. $this->reference_url = $url; 
  161. return $previous_url; 
  162.  
  163. public function get_reference_url() { 
  164. return $this->reference_url; 
  165.  
  166. public function is_admin_pre_notices( $dis_key = false, $user_id = true ) { 
  167. if ( is_admin() ) { 
  168. if ( ! empty( $dis_key ) ) { 
  169. // if notice is dismissed, say we've already shown the notices 
  170. if ( $this->is_dismissed( $dis_key, $user_id ) ) { 
  171. return false; 
  172. if ( ! $this->has_shown ) { 
  173. return true; 
  174. return false; 
  175.  
  176. public function show_admin_notices() { 
  177.  
  178. $hidden = array(); 
  179. $msg_html = ''; 
  180. $nag_msgs = ''; 
  181. $seen_msgs = array(); // duplicate check 
  182. $dismissed_updated = false; 
  183. $user_id = (int) get_current_user_id(); 
  184. $user_notices =& $this->get_user_notices( $user_id ); // returns reference 
  185. $user_dismissed = empty( $user_id ) ? false : // just in case 
  186. get_user_option( $this->dis_name, $user_id ); // get dismissed message ids 
  187. $this->has_shown = true; 
  188.  
  189. if ( isset( $this->p->cf['plugin'] ) && class_exists( 'SucomUpdate' ) ) { 
  190. foreach ( array_keys( $this->p->cf['plugin'] ) as $lca ) { 
  191. if ( ! empty( $this->p->options['plugin_'.$lca.'_tid'] ) ) { 
  192. $uerr = SucomUpdate::get_umsg( $lca ); 
  193. if ( ! empty( $uerr ) ) { 
  194. $user_notices['err'][$uerr] = array(); 
  195.  
  196. // loop through all the msg types and show them all 
  197. foreach ( $this->all_types as $msg_type ) { 
  198.  
  199. if ( ! isset( $user_notices[$msg_type] ) ) { // just in case 
  200. continue; 
  201.  
  202. foreach ( $user_notices[$msg_type] as $msg_txt => $payload ) { 
  203.  
  204. if ( empty( $msg_txt ) || isset( $seen_msgs[$msg_txt] ) ) { // skip duplicates 
  205. continue; 
  206.  
  207. $seen_msgs[$msg_txt] = true; // avoid duplicates 
  208.  
  209. switch ( $msg_type ) { 
  210. case 'nag': 
  211. $nag_msgs .= $msg_txt; // append to echo a single msg block 
  212. continue; 
  213. default: 
  214. // dis_time will be false if there's no dis_key 
  215. if ( ! empty( $payload['dis_time'] ) ) { 
  216.  
  217. // initialize the count 
  218. if ( ! isset( $hidden[$msg_type] ) ) { 
  219. $hidden[$msg_type] = 0; 
  220.  
  221. // check for automatically hidden errors and/or warnings 
  222. if ( ( $msg_type === 'err' && $this->hide_err ) || 
  223. ( $msg_type === 'warn' && $this->hide_warn ) ) { 
  224.  
  225. $payload['hidden'] = true; 
  226. if ( empty( $payload['silent'] ) ) { 
  227. $hidden[$msg_type]++; 
  228.  
  229. // dis_key has been dismissed 
  230. } elseif ( ! empty( $payload['dis_key'] ) && 
  231. isset( $user_dismissed[$payload['dis_key']] ) ) { 
  232.  
  233. $now_time = time(); 
  234. $dis_time = $user_dismissed[$payload['dis_key']]; 
  235.  
  236. if ( empty( $dis_time ) || $dis_time > $now_time ) { 
  237. $payload['hidden'] = true; 
  238. if ( empty( $payload['silent'] ) ) { 
  239. $hidden[$msg_type]++; 
  240. } else { // dismiss has expired 
  241. $dismissed_updated = true; // update the array when done 
  242. unset( $user_dismissed[$payload['dis_key']] ); 
  243. $msg_html .= $this->get_notice_html( $msg_type, $msg_txt, $payload ); 
  244. break; 
  245.  
  246. // delete all notices for the current user id 
  247. $this->trunc(); 
  248.  
  249. // don't save unless we've changed something 
  250. if ( $dismissed_updated === true && ! empty( $user_id ) ) { 
  251. if ( empty( $user_dismissed ) ) { 
  252. delete_user_option( $user_id, $this->dis_name ); 
  253. } else { 
  254. update_user_option( $user_id, $this->dis_name, $user_dismissed ); 
  255.  
  256. echo "\n"; 
  257. echo '<!-- '.$this->lca.' admin notices begin -->'."\n"; 
  258.  
  259. if ( ! empty( $nag_msgs ) ) { 
  260. echo $this->get_nag_style(); 
  261. echo $this->get_notice_html( 'nag', $nag_msgs ); 
  262.  
  263. // remind the user that there are hidden error messages 
  264. foreach ( array( 
  265. 'err' => _x( 'error', 'notification type', $this->text_dom ),  
  266. 'warn' => _x( 'warning', 'notification type', $this->text_dom ),  
  267. ) as $msg_type => $log_name ) { 
  268. if ( empty( $hidden[$msg_type] ) ) { 
  269. continue; 
  270. } elseif ( $hidden[$msg_type] > 1 ) { 
  271. $msg_text = __( '%1$d important %2$s notices have been hidden and/or dismissed — <a id="%3$s">unhide and view the %2$s messages</a>.', $this->text_dom ); 
  272. } else { 
  273. $msg_text = __( '%1$d important %2$s notice has been hidden and/or dismissed — <a id="%3$s">unhide and view the %2$s message</a>.', $this->text_dom ); 
  274. echo $this->get_notice_html( $msg_type, sprintf( $msg_text, $hidden[$msg_type], $log_name, $this->lca.'-unhide-notices' ) ); 
  275.  
  276. echo $msg_html; 
  277. echo '<!-- '.$this->lca.' admin notices end -->'."\n"; 
  278.  
  279. public function ajax_dismiss_notice() { 
  280. $dis_info = array(); 
  281. $user_id = get_current_user_id(); 
  282.  
  283. if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) ) { 
  284. die( '-1' ); 
  285.  
  286. check_ajax_referer( __FILE__, '_ajax_nonce', true ); 
  287.  
  288. // read arguments 
  289. foreach ( array( 'key', 'time' ) as $key ) { 
  290. $dis_info[$key] = sanitize_text_field( filter_input( INPUT_POST, $key ) ); 
  291.  
  292. if ( empty( $dis_info['key'] ) ) { 
  293. die( '-1' ); 
  294.  
  295. // site specific user options 
  296. $user_dismissed = get_user_option( $this->dis_name, $user_id ); 
  297.  
  298. if ( ! is_array( $user_dismissed ) ) { 
  299. $user_dismissed = array(); 
  300.  
  301. // save the message id and expiration time (0 = never) 
  302. $user_dismissed[$dis_info['key']] = empty( $dis_info['time'] ) || 
  303. ! is_numeric( $dis_info['time'] ) ? 0 : time() + $dis_info['time']; 
  304.  
  305. update_user_option( $user_id, $this->dis_name, $user_dismissed ); 
  306.  
  307. die( '1' ); 
  308.  
  309. private function can_dismiss() { 
  310. global $wp_version; 
  311. if ( version_compare( $wp_version, 4.2, '>=' ) ) { 
  312. return true; 
  313. } else { 
  314. return false; 
  315.  
  316. public function is_dismissed( $dis_key = false, $user_id = true ) { 
  317.  
  318. if ( empty( $dis_key ) || ! $this->can_dismiss() ) { // just in case 
  319. return false; 
  320.  
  321. if ( $user_id === true ) { 
  322. $user_id = (int) get_current_user_id(); 
  323. } else { 
  324. $user_id = (int) $user_id; // false = 0 
  325.  
  326. if ( empty( $user_id ) ) { 
  327. return false; 
  328.  
  329. $user_dismissed = get_user_option( $this->dis_name, $user_id ); 
  330.  
  331. if ( ! is_array( $user_dismissed ) ) { 
  332. return false; 
  333.  
  334. // notice has been dismissed 
  335. if ( isset( $user_dismissed[$dis_key] ) ) { 
  336.  
  337. $now_time = time(); 
  338. $dis_time = $user_dismissed[$dis_key]; 
  339.  
  340. if ( empty( $dis_time ) || $dis_time > $now_time ) { 
  341. return true; 
  342.  
  343. // dismiss time has expired 
  344. } else { 
  345. unset( $user_dismissed[$dis_key] ); 
  346. if ( empty( $user_dismissed ) ) { 
  347. delete_user_option( $user_id, $this->dis_name ); 
  348. } else { 
  349. update_user_option( $user_id, $this->dis_name, $user_dismissed ); 
  350.  
  351. return false; 
  352.  
  353. public function admin_footer_script() { 
  354. echo ' 
  355. <script type="text/javascript"> 
  356. jQuery( document ).ready( function() { 
  357. jQuery("#'.$this->lca.'-unhide-notices").click( function() { 
  358. var notice = jQuery( this ).parents(".'.$this->lca.'-notice"); 
  359. jQuery(".'.$this->lca.'-dismissible").show(); 
  360. notice.hide(); 
  361. }); 
  362. jQuery(".'.$this->lca.'-dismissible > .notice-dismiss").click( function() { 
  363. var notice = jQuery( this ).parent(".'.$this->lca.'-dismissible"); 
  364. var dismiss_nonce = notice.data( "dismiss-nonce" ); 
  365. var dismiss_key = notice.data( "dismiss-key" ); 
  366. var dismiss_time = notice.data( "dismiss-time" ); 
  367. jQuery.post( 
  368. ajaxurl, { 
  369. action: "'.$this->lca.'_dismiss_notice",  
  370. _ajax_nonce: dismiss_nonce,  
  371. key: dismiss_key,  
  372. time: dismiss_time 
  373. ); 
  374. notice.hide(); 
  375. }); 
  376. }); 
  377. </script> 
  378. '; 
  379.  
  380. private function get_notice_html( $msg_type, $msg_txt, $payload = array() ) { 
  381.  
  382. $charset = get_bloginfo( 'charset' ); 
  383.  
  384. if ( ! isset( $payload['label'] ) ) { 
  385. $payload['label'] = sprintf( __( '%s Note', $this->text_dom ), strtoupper( $this->lca ) ); 
  386.  
  387. switch ( $msg_type ) { 
  388. case 'nag': 
  389. $payload['label'] = ''; 
  390. $msg_class = 'update-nag'; 
  391. break; 
  392. case 'warn': 
  393. $msg_class = 'notice notice-warning'; 
  394. break; 
  395. case 'err': 
  396. $msg_class = 'notice notice-error error'; 
  397. break; 
  398. case 'upd': 
  399. $msg_class = 'notice notice-success updated'; 
  400. break; 
  401. case 'inf': 
  402. default: 
  403. $msg_type = 'inf'; // just in case 
  404. $msg_class = 'notice notice-info'; 
  405. break; 
  406.  
  407. // dis_key and dis_time must have values to create a dismissible notice 
  408. $is_dismissible = empty( $payload['dis_key'] ) || empty( $payload['dis_time'] ) ? false : true; 
  409.  
  410. $css_id_attr = empty( $payload['dis_key'] ) ? '' : ' id="'.$msg_type.'_'.$payload['dis_key'].'"'; 
  411.  
  412. $data_attr = ! $is_dismissible ? 
  413. '' : ' data-dismiss-nonce="'.wp_create_nonce( __FILE__ ).'"'. 
  414. ' data-dismiss-key="'.$payload['dis_key'].'"'. 
  415. ' data-dismiss-time="'.( is_numeric( $payload['dis_time'] ) ? 
  416. $payload['dis_time'] : 0 ).'"'; 
  417.  
  418. // optionally hide notices if required 
  419. $style_attr = ' style="'. 
  420. ( empty( $payload['style'] ) ? '' : $payload['style'] ). 
  421. ( empty( $payload['hidden'] ) ? 'display:block !important; visibility:visible !important;' : 'display:none;' ).'"'; 
  422.  
  423. $msg_html = '<div class="'.$this->lca.'-notice '. 
  424. ( ! $is_dismissible ? '' : $this->lca.'-dismissible ' ). 
  425. $msg_class.'"'.$css_id_attr.$style_attr.$data_attr.'>'; // display block or none 
  426.  
  427. if ( ! empty( $payload['dis_time'] ) ) { 
  428. $msg_html .= '<div class="notice-dismiss"><div class="notice-dismiss-text">'. 
  429. __( 'Dismiss', $this->text_dom ).'</div></div>'; 
  430.  
  431. if ( ! empty( $payload['label'] ) ) { 
  432. $msg_html .= '<div class="notice-label">'. 
  433. $payload['label'].'</div>'; 
  434.  
  435. $msg_html .= '<div class="notice-message">'.$msg_txt.'</div>'; 
  436. $msg_html .= '</div>'."\n"; 
  437.  
  438. return $msg_html; 
  439.  
  440. private function get_nag_style() { 
  441. $custom_style_css = ''; 
  442.  
  443. if ( isset( $this->p->cf['menu']['color'] ) ) { 
  444. $custom_style_css .= ' 
  445. .'.$this->lca.'-notice.update-nag { 
  446. border:1px dotted #'.$this->p->cf['menu']['color'].'; 
  447. border-top:none; 
  448. '; 
  449.  
  450. $custom_style_css .= ' 
  451. .'.$this->lca.'-notice.update-nag { 
  452. margin-top:0; 
  453. .'.$this->lca.'-notice.update-nag > div { 
  454. display:block; 
  455. margin:0 auto; 
  456. max-width:850px; 
  457. .'.$this->lca.'-notice.update-nag p,  
  458. .'.$this->lca.'-notice.update-nag ul,  
  459. .'.$this->lca.'-notice.update-nag ol { 
  460. font-size:1em; 
  461. text-align:center; 
  462. margin:15px auto 15px auto; 
  463. .'.$this->lca.'-notice.update-nag ul li { 
  464. list-style-type:square; 
  465. .'.$this->lca.'-notice.update-nag ol li { 
  466. list-style-type:decimal; 
  467. .'.$this->lca.'-notice.update-nag li { 
  468. text-align:left; 
  469. margin:5px 0 5px 60px; 
  470. '; 
  471.  
  472. if ( method_exists( 'SucomUtil', 'minify_css' ) ) { 
  473. $custom_style_css = SucomUtil::minify_css( $custom_style_css, $this->lca ); 
  474.  
  475. return '<style type="text/css">'.$custom_style_css.'</style>'; 
  476.  
  477. private function &get_user_ids() { 
  478. $user_ids = array(); 
  479. foreach ( get_users() as $user ) { 
  480. $user_ids[] = $user->ID; 
  481. return $user_ids; 
  482.  
  483. private function &get_user_notices( $user_id = true ) { 
  484.  
  485. if ( $user_id === true ) { 
  486. $user_id = (int) get_current_user_id(); 
  487. } else { 
  488. $user_id = (int) $user_id; // false = 0 
  489.  
  490. if ( isset( $this->notice_cache[$user_id] ) ) { 
  491. return $this->notice_cache[$user_id]; 
  492.  
  493. if ( $user_id > 0 ) { 
  494. $this->notice_cache[$user_id] = get_user_option( $this->opt_name, $user_id ); 
  495. if ( is_array( $this->notice_cache[$user_id] ) ) { 
  496. $this->notice_cache[$user_id]['have_notices'] = true; 
  497. } else { 
  498. $this->notice_cache[$user_id] = array( 'have_notices' => false ); 
  499.  
  500. foreach ( $this->all_types as $msg_type ) { 
  501. if ( ! isset( $this->notice_cache[$user_id][$msg_type] ) ) { 
  502. $this->notice_cache[$user_id][$msg_type] = array(); 
  503.  
  504. return $this->notice_cache[$user_id]; 
  505.  
  506. public function shutdown_save_notices() { 
  507.  
  508. $user_id = (int) get_current_user_id(); 
  509. $have_notices = false; 
  510.  
  511. if ( $user_id > 0 ) { 
  512. if ( isset( $this->notice_cache[$user_id]['have_notices'] ) ) { 
  513. $have_notices = $this->notice_cache[$user_id]['have_notices']; 
  514. unset( $this->notice_cache[$user_id]['have_notices'] ); 
  515. if ( empty( $this->notice_cache[$user_id] ) ) { 
  516. if ( $have_notices ) { 
  517. delete_user_option( $user_id, $this->opt_name ); 
  518. } else { 
  519. update_user_option( $user_id, $this->opt_name, $this->notice_cache[$user_id] ); 
  520.  
  521. ?> 
.