PageRenderTime 60ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/class-supportflow-admin.php

https://github.com/SupportFlow/supportflow
PHP | 1295 lines | 1092 code | 115 blank | 88 comment | 110 complexity | 7cc90a1e7d68dedc798ba47b34237dfb MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. */
  5. defined( 'ABSPATH' ) or die( "Cheatin' uh?" );
  6. class SupportFlow_Admin extends SupportFlow {
  7. function __construct() {
  8. add_action( 'wp_ajax_sf_forward_conversation', array( $this, 'action_wp_ajax_sf_email_conversation' ) );
  9. add_filter( 'heartbeat_received', array( $this, 'filter_heartbeat_received' ), 10, 2 );
  10. add_action( 'wp_ajax_ticket_attachment_upload', array( $this, 'action_wp_ajax_ticket_attachment_upload' ) );
  11. add_action( 'supportflow_after_setup_actions', array( $this, 'setup_actions' ) );
  12. add_action( 'add_attachment', array( $this, 'action_add_attachment' ) );
  13. }
  14. public function setup_actions() {
  15. // Creating or updating a ticket
  16. add_action( 'add_meta_boxes', array( $this, 'action_add_meta_boxes' ) );
  17. add_action( 'save_post', array( $this, 'action_save_post' ) );
  18. if ( ! $this->is_edit_screen() ) {
  19. return;
  20. }
  21. // Everything
  22. add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) );
  23. add_filter( 'post_updated_messages', array( $this, 'filter_post_updated_messages' ) );
  24. add_action( 'admin_init', array( $this, 'action_admin_init' ) );
  25. // Manage tickets view
  26. add_filter( 'manage_' . SupportFlow()->post_type . '_posts_columns', array( $this, 'filter_manage_post_columns' ) );
  27. add_filter( 'manage_edit-' . SupportFlow()->post_type . '_sortable_columns', array( $this, 'manage_sortable_columns' ) );
  28. add_action( 'manage_posts_custom_column', array( $this, 'action_manage_posts_custom_column' ), 10, 2 );
  29. add_filter( 'views_edit-' . SupportFlow()->post_type, array( $this, 'filter_views' ) );
  30. add_filter( 'post_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
  31. add_filter( 'bulk_actions-edit-' . SupportFlow()->post_type, array( $this, 'filter_bulk_actions' ) );
  32. add_action( 'pre_get_posts', array( $this, 'action_pre_get_posts' ) );
  33. add_action( 'admin_action_change_status', array( $this, 'handle_action_change_status' ) );
  34. add_action( 'restrict_manage_posts', array( $this, 'action_restrict_manage_posts' ) );
  35. }
  36. /**
  37. * Re-sort the custom statuses so trash appears last
  38. */
  39. function action_admin_init() {
  40. global $wp_post_statuses, $pagenow;
  41. $trash_status = $wp_post_statuses['trash'];
  42. unset( $wp_post_statuses['trash'] );
  43. $wp_post_statuses['trash'] = $trash_status;
  44. if ( 'edit.php' == $pagenow ) {
  45. add_filter( 'get_the_excerpt', array( $this, 'filter_get_the_excerpt' ) );
  46. }
  47. }
  48. /**
  49. * Add any CSS or JS we need for the admin
  50. */
  51. public function action_admin_enqueue_scripts() {
  52. global $pagenow;
  53. $handle = SupportFlow()->enqueue_style( 'supportflow-admin', 'admin.css' );
  54. if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) ) {
  55. wp_enqueue_media();
  56. $customers_autocomplete_handle = SupportFlow()->enqueue_script( 'supportflow-customers-autocomplete', 'customers-autocomplete.js', array( 'jquery', 'jquery-ui-autocomplete' ) );
  57. $ticket_attachment_handle = SupportFlow()->enqueue_script( 'supportflow-ticket-attachments', 'ticket_attachments.js' );
  58. $supportflow_tickets_handle = SupportFlow()->enqueue_script( 'supportflow-tickets', 'tickets.js' );
  59. $auto_save_handle = SupportFlow()->enqueue_script( 'supportflow-auto-save', 'auto_save.js', array( 'jquery', 'heartbeat' ) );
  60. wp_localize_script( $customers_autocomplete_handle, 'SFCustomersAc', array(
  61. 'ajax_url' => add_query_arg( 'action', SupportFlow()->extend->jsonapi->action, admin_url( 'admin-ajax.php' ) )
  62. ) );
  63. wp_localize_script( $ticket_attachment_handle, 'SFTicketAttachments', array(
  64. 'frame_title' => __( 'Attach files', 'supportflow' ),
  65. 'button_title' => __( 'Insert as attachment', 'supportflow' ),
  66. 'remove_attachment' => __( 'Remove', 'supportflow' ),
  67. 'sure_remove' => __( 'Are you sure want to remove this attachment?', 'supportflow' ),
  68. ) );
  69. wp_localize_script( $supportflow_tickets_handle, 'SFTickets', array(
  70. 'no_title_msg' => __( 'You must need to specify the subject of the ticket', 'supportpress' ),
  71. 'no_customer_msg' => __( 'You must need to add atleast one customer', 'supportpress' ),
  72. 'pagenow' => $pagenow,
  73. 'send_msg' => __( 'Send Message', 'supportflow' ),
  74. 'add_private_note' => __( 'Add Private Note', 'supportflow' ),
  75. ) );
  76. wp_localize_script( $auto_save_handle, 'SFAutoSave', array(
  77. 'ticket_id' => get_the_ID(),
  78. ) );
  79. }
  80. if ( 'post.php' == $pagenow ) {
  81. $email_conversation_handle = SupportFlow()->enqueue_script( 'supportflow-email-conversation', 'email_conversation.js' );
  82. wp_localize_script( $email_conversation_handle, 'SFEmailConversation', array(
  83. 'post_id' => get_the_ID(),
  84. 'sending_emails' => __( 'Please wait while sending E-Mail(s)', 'supportflow' ),
  85. 'failed_sending' => __( 'Failed sending E-Mails', 'supportflow' ),
  86. '_email_conversation_nonce' => wp_create_nonce( 'sf_email_conversation' ),
  87. ) );
  88. }
  89. }
  90. /**
  91. *
  92. */
  93. public function action_wp_ajax_sf_email_conversation() {
  94. if ( false === check_ajax_referer( 'sf_email_conversation', '_email_conversation_nonce', false ) ) {
  95. _e( 'Invalid request. Please try refreshing the page.', 'supportflow' );
  96. die;
  97. }
  98. if ( ! isset( $_REQUEST['email_ids'] ) || ! isset( $_REQUEST['post_id'] ) ) {
  99. _e( 'Invalid request. Please try refreshing the page.', 'supportflow' );
  100. die;
  101. }
  102. $email_ids = SupportFlow()->extract_email_ids( $_REQUEST['email_ids'] );
  103. $ticket_id = (int) $_REQUEST['post_id'];
  104. if ( ! current_user_can( 'edit_post', $ticket_id ) ) {
  105. _e( 'You are not allowed to edit this item.' );
  106. die;
  107. }
  108. if ( empty( $email_ids ) ) {
  109. _e( 'No valid E-Mail ID found', 'supportflow' );
  110. die;
  111. }
  112. SupportFlow()->extend->emails->email_conversation( $ticket_id, $email_ids );
  113. _e( 'Successfully sented E-Mails', 'supportflow' );
  114. exit;
  115. }
  116. /**
  117. * Add random characters to attachment uploaded through SupportFlow web UI
  118. *
  119. * @todo Conversion to a better way to determine if attachment if uploaded through SF web UI rather than HTTP referer
  120. */
  121. function action_add_attachment( $attachment_id ) {
  122. if ( empty( $_SERVER['HTTP_REFERER'] ) ) {
  123. return;
  124. }
  125. $post_type = SupportFlow()->post_type;
  126. $referer = $_SERVER['HTTP_REFERER'];
  127. $url = parse_url( $referer );
  128. $path = $url['scheme'] . '://' . $url['host'] . $url['path'];
  129. parse_str( $url['query'], $query );
  130. // Check if referred by SupportFlow ticket page
  131. if ( admin_url( 'post-new.php' ) == $path ) {
  132. if ( empty( $query['post_type'] ) || $query['post_type'] != $post_type ) {
  133. return;
  134. }
  135. } elseif ( admin_url( 'post.php' ) == $path ) {
  136. if ( empty( $query['post'] ) || get_post_type( (int) $query['post'] ) != $post_type ) {
  137. return;
  138. }
  139. } else {
  140. return;
  141. }
  142. SupportFlow()->extend->attachments->secure_attachment_file( $attachment_id );
  143. }
  144. /**
  145. * Filter the messages that appear to the user after they perform an action on a ticket
  146. */
  147. public function filter_post_updated_messages( $messages ) {
  148. global $post;
  149. $messages[SupportFlow()->post_type] = array(
  150. 0 => '', // Unused. Messages start at index 1.
  151. 1 => __( 'Ticket updated.', 'supportflow' ),
  152. 2 => __( 'Custom field updated.', 'supportflow' ),
  153. 3 => __( 'Custom field deleted.', 'supportflow' ),
  154. 4 => __( 'Ticket updated.', 'supportflow' ),
  155. /* translators: %s: date and time of the revision */
  156. 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Ticket restored to revision from %s', 'supportflow' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
  157. 6 => __( 'Ticket updated.', 'supportflow' ),
  158. 7 => __( 'Ticket updated.', 'supportflow' ),
  159. 8 => __( 'Ticket updated.', 'supportflow' ),
  160. 9 => __( 'Ticket updated.', 'supportflow' ),
  161. 10 => __( 'Ticket updated.', 'supportflow' ),
  162. );
  163. return $messages;
  164. }
  165. public function filter_heartbeat_received( $response, $data ) {
  166. if (
  167. isset( $data['supportflow-autosave'] ) &&
  168. is_array( $data['supportflow-autosave'] ) &&
  169. isset( $data['supportflow-autosave']['ticket_id'] ) &&
  170. current_user_can( 'edit_post', (int) $data['supportflow-autosave']['ticket_id'] )
  171. ) {
  172. // Save data received from client to the database as post meta
  173. $ticket_id = (int) $data['supportflow-autosave']['ticket_id'];
  174. unset( $data['supportflow-autosave']['ticket_id'] );
  175. if ( 'auto-draft' == get_post_status( $ticket_id ) ) {
  176. wp_update_post( array( 'ID' => $ticket_id, 'post_status' => 'draft' ) );
  177. }
  178. foreach ( $data['supportflow-autosave'] as $element_id => $element_value ) {
  179. update_post_meta( $ticket_id, "_sf_autosave_$element_id", $element_value );
  180. }
  181. echo $data['supportflow-autosave']['post_title'];
  182. if ( ! empty( $data['supportflow-autosave']['post_title'] ) ) {
  183. wp_update_post( array( 'ID' => $ticket_id, 'post_title' => $data['supportflow-autosave']['post_title'] ) );
  184. }
  185. }
  186. return $response;
  187. }
  188. /**
  189. *
  190. */
  191. public function filter_views( $views ) {
  192. $post_type = SupportFlow()->post_type;
  193. $statuses = SupportFlow()->post_statuses;
  194. $status_slugs = array();
  195. foreach ( $statuses as $status => $status_data ) {
  196. if ( true == $status_data['show_tickets'] ) {
  197. $status_slugs[] = $status;
  198. }
  199. }
  200. $wp_query = new WP_Query( array(
  201. 'post_type' => $post_type,
  202. 'post_parent' => 0,
  203. 'posts_per_page' => 1,
  204. 'post_status' => $status_slugs,
  205. ) );
  206. $total_posts = $wp_query->found_posts;
  207. $class = empty( $class ) && empty( $_REQUEST['post_status'] ) && empty( $_REQUEST['show_sticky'] ) ? ' class="current"' : '';
  208. $view_all = "<a href='edit.php?post_type=$post_type'$class>" . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts' ), number_format_i18n( $total_posts ) ) . '</a>';
  209. $post_statuses = SupportFlow()->post_statuses;
  210. array_pop( $post_statuses );
  211. $post_statuses = "'" . implode( "','", array_map( 'sanitize_key', array_keys( $post_statuses ) ) ) . "'";
  212. // @todo Only show "Mine" if the user is an agent
  213. $mine_args = array(
  214. 'post_type' => SupportFlow()->post_type,
  215. 'author' => get_current_user_id(),
  216. );
  217. $wp_query = new WP_Query( array(
  218. 'post_type' => SupportFlow()->post_type,
  219. 'author' => get_current_user_id(),
  220. 'post_status' => $post_statuses,
  221. 'posts_per_page' => 1,
  222. ) );
  223. $my_posts = $wp_query->found_posts;
  224. $view_mine = '<a href="' . add_query_arg( $mine_args, admin_url( 'edit.php' ) ) . '">' . sprintf( _nx( 'Mine <span class="count">(%s)</span>', 'Mine <span class="count">(%s)</span>', $my_posts, 'posts' ), number_format_i18n( $my_posts ) ) . '</a>';
  225. $unassigned_args = array(
  226. 'post_type' => SupportFlow()->post_type,
  227. 'author' => 0,
  228. );
  229. $wp_query = new WP_Query( array(
  230. 'post_type' => SupportFlow()->post_type,
  231. 'author' => 0,
  232. 'post_status' => $post_statuses,
  233. 'posts_per_page' => 1,
  234. ) );
  235. $unassigned_posts = $wp_query->found_posts;
  236. $view_unassigned = '<a href="' . add_query_arg( $unassigned_args, admin_url( 'edit.php' ) ) . '">' . sprintf( _nx( 'Unassigned <span class="count">(%s)</span>', 'Unassigned <span class="count">(%s)</span>', $unassigned_posts, 'posts' ), number_format_i18n( $unassigned_posts ) ) . '</a>';
  237. // Put 'All' and 'Mine' at the beginning of the array
  238. array_shift( $views );
  239. $views = array_reverse( $views );
  240. $views['unassigned'] = $view_unassigned;
  241. $views['mine'] = $view_mine;
  242. $views['all'] = $view_all;
  243. $views = array_reverse( $views );
  244. // Remove private option from filter links as they are just private replies to ticket
  245. unset( $views['private'] );
  246. return $views;
  247. }
  248. /**
  249. * Add custom filters for the Manage Tickets view
  250. */
  251. public function action_restrict_manage_posts() {
  252. // Filter to specific agents
  253. $agent_dropdown_args = array(
  254. 'show_option_all' => __( 'Show all agents', 'supportflow' ),
  255. 'name' => 'author',
  256. 'selected' => ( ! empty( $_REQUEST['author'] ) ) ? (int) $_REQUEST['author'] : false,
  257. 'who' => 'authors',
  258. );
  259. $agent_dropdown_args = apply_filters( 'supportflow_admin_agent_dropdown_args', $agent_dropdown_args );
  260. wp_dropdown_users( $agent_dropdown_args );
  261. // Filter to specify tag
  262. $tax_slug = SupportFlow()->tags_tax;
  263. $terms = get_terms( 'sf_tags', array( 'hide_empty' => false ) );
  264. echo "<select name='" . esc_attr( $tax_slug ) . "' id='" . esc_attr( $tax_slug ) . "' class='postform'>";
  265. echo "<option value=''>" . __( 'Show All tags', 'supportflow' ) . "</option>";
  266. foreach ( $terms as $term ) {
  267. $selected = selected( isset( $_REQUEST[$tax_slug] ) && ( $_REQUEST[$tax_slug] == $term->slug ), true, false );
  268. echo "<option value='" . esc_attr( $term->slug ) . "' $selected>" . esc_html( $term->name ) . '</option>';
  269. }
  270. echo "</select>";
  271. // Filter to specify E-Mail account
  272. $email_accounts = SupportFlow()->extend->email_accounts->get_email_accounts( true );
  273. echo "<select name='email_account' id='email_account' class='postform'>";
  274. echo "<option value=''>" . __( 'Show All Accounts', 'supportflow' ) . "</option>";
  275. foreach ( $email_accounts as $id => $email_account ) {
  276. $selected = selected( isset( $_REQUEST['email_account'] ) && ( $_REQUEST['email_account'] == $id ), true, false );
  277. echo "<option value='" . esc_attr( $id ) . "'$selected>" . esc_html( $email_account['username'] ) . '</option>';
  278. }
  279. echo "</select>";
  280. }
  281. /**
  282. * Filter the actions available to the agent on the post type
  283. */
  284. function filter_post_row_actions( $row_actions, $post ) {
  285. // Rename these actions
  286. if ( isset( $row_actions['edit'] ) ) {
  287. $row_actions['edit'] = str_replace( __( 'Edit' ), __( 'View', 'supportflow' ), str_replace( __( 'Edit this item' ), __( 'View Ticket', 'supportflow' ), $row_actions['edit'] ) );
  288. }
  289. // Save the trash action for the end
  290. if ( isset( $row_actions['trash'] ) ) {
  291. $trash_action = $row_actions['trash'];
  292. unset( $row_actions['trash'] );
  293. } else {
  294. $trash_action = false;
  295. }
  296. // Allow an agent to easily close a ticket
  297. $statuses = SupportFlow()->post_statuses;
  298. $status_slugs = array_keys( $statuses );
  299. $last_status = array_pop( $status_slugs );
  300. if ( ! in_array( get_query_var( 'post_status' ), array( 'trash' ) ) ) {
  301. if ( $last_status == get_post_status( $post->ID ) ) {
  302. $change_to = $status_slugs[2];
  303. } else {
  304. $change_to = $last_status;
  305. }
  306. $args = array(
  307. 'action' => 'change_status',
  308. 'sf_nonce' => wp_create_nonce( 'sf-change-status' ),
  309. 'post_status' => $change_to,
  310. 'ticket_id' => $post->ID,
  311. 'post_type' => SupportFlow()->post_type,
  312. );
  313. $action_link = add_query_arg( $args, admin_url( 'edit.php' ) );
  314. if ( $last_status == $change_to ) {
  315. $title_attr = esc_attr__( 'Close Ticket', 'supportflow' );
  316. $action_text = esc_html__( 'Close', 'supportflow' );
  317. } else {
  318. $title_attr = esc_attr__( 'Reopen Ticket', 'supportflow' );
  319. $action_text = esc_html__( 'Reopen', 'supportflow' );
  320. }
  321. if ( current_user_can( 'edit_post', $post->ID ) ) {
  322. $row_actions['change_status'] = '<a href="' . esc_url( $action_link ) . '" title="' . $title_attr . '">' . $action_text . '</a>';
  323. }
  324. }
  325. // Actions we don't want
  326. unset( $row_actions['inline hide-if-no-js'] );
  327. unset( $row_actions['view'] );
  328. if ( $trash_action ) {
  329. $row_actions['trash'] = $trash_action;
  330. }
  331. return $row_actions;
  332. }
  333. /**
  334. * Remove the 'edit' bulk action. Doesn't do much for us
  335. */
  336. public function filter_bulk_actions( $actions ) {
  337. unset( $actions['edit'] );
  338. return $actions;
  339. }
  340. /**
  341. * Handle which tickets are show on the Manage Tickets view when
  342. */
  343. function action_pre_get_posts( $query ) {
  344. global $pagenow;
  345. if ( 'edit.php' != $pagenow || ! $query->is_main_query() ) {
  346. return;
  347. }
  348. $statuses = SupportFlow()->post_statuses;
  349. $status_slugs = array();
  350. foreach ( $statuses as $status => $status_data ) {
  351. if ( true == $status_data['show_tickets'] ) {
  352. $status_slugs[] = $status;
  353. }
  354. }
  355. // Order posts by post_modified if there's no orderby set
  356. if ( ! $query->get( 'orderby' ) ) {
  357. $query->set( 'orderby', 'modified' );
  358. $query->set( 'order', 'DESC' );
  359. }
  360. // Do our own custom search handling so we can search against reply text
  361. if ( $search = $query->get( 's' ) ) {
  362. // Get all replies that match our results
  363. $args = array(
  364. 'search' => $search,
  365. 'status' => 'any',
  366. );
  367. $matching_replies = SupportFlow()->get_replies( $args );
  368. $post_ids = wp_list_pluck( $matching_replies, 'post_parent' );
  369. $args = array(
  370. 's' => $search,
  371. 'post_type' => SupportFlow()->post_type,
  372. 'no_found_rows' => true,
  373. 'update_post_meta_cache' => false,
  374. 'update_post_term_cache' => false,
  375. 'fields' => 'ids',
  376. );
  377. $post_query = new WP_Query( $args );
  378. if ( ! is_wp_error( $post_query ) ) {
  379. $post_ids = array_merge( $post_ids, $post_query->posts );
  380. }
  381. $query->set( 'post__in', $post_ids );
  382. // Ignore the original search query
  383. add_filter( 'posts_search', array( $this, 'filter_posts_search' ) );
  384. }
  385. // Only show tickets with the last status if the last status is set
  386. $post_status = $query->get( 'post_status' );
  387. if ( ! $query->get( 's' ) && empty( $post_status ) ) {
  388. $query->set( 'post_status', $status_slugs );
  389. }
  390. add_action( 'posts_clauses', array( $this, 'filter_author_clause' ), 10, 2 );
  391. if ( isset( $_GET['email_account'] ) && ! empty( $_GET['email_account'] ) ) {
  392. $query->set( 'meta_key', 'email_account' );
  393. $query->set( 'meta_value', (int) $_GET['email_account'] );
  394. }
  395. }
  396. /*
  397. * Show unassigned tickets when query author is 0
  398. */
  399. public function filter_author_clause( $clauses, $query ) {
  400. if ( isset( $query->query['author'] ) && 0 == $query->query['author'] ) {
  401. $clauses['where'] .= ' AND post_author = 0 ';
  402. }
  403. return $clauses;
  404. }
  405. /**
  406. * Sometimes we want to ignore the original search query because we do our own
  407. */
  408. public function filter_posts_search( $posts_search ) {
  409. return '';
  410. }
  411. /**
  412. * Handle $_GET actions in the admin
  413. */
  414. function handle_action_change_status() {
  415. if ( ! isset( $_GET['action'], $_GET['sf_nonce'], $_GET['post_status'], $_GET['ticket_id'] ) ) {
  416. return;
  417. }
  418. if ( ! wp_verify_nonce( $_GET['sf_nonce'], 'sf-change-status' ) ) {
  419. wp_die( __( "Doin' something phishy, huh?", 'supportflow' ) );
  420. }
  421. $ticket_id = (int) $_GET['ticket_id'];
  422. if ( ! current_user_can( 'edit_post', $ticket_id ) ) {
  423. wp_die( __( 'You are not allowed to edit this item.' ) );
  424. }
  425. $post_status = sanitize_key( $_GET['post_status'] );
  426. $new_ticket = array(
  427. 'ID' => $ticket_id,
  428. 'post_status' => $post_status,
  429. );
  430. wp_update_post( $new_ticket );
  431. wp_safe_redirect( wp_get_referer() );
  432. exit;
  433. }
  434. /**
  435. * Manipulate the meta boxes appearing on the edit post view
  436. *
  437. * When creating a new ticket, you should be able to:
  438. *
  439. * When updating an existing ticket, you should be able to:
  440. *
  441. */
  442. public function action_add_meta_boxes() {
  443. global $pagenow;
  444. if ( ! $this->is_edit_screen() ) {
  445. return;
  446. }
  447. $customers_box = 'tagsdiv-' . SupportFlow()->customers_tax;
  448. remove_meta_box( 'submitdiv', SupportFlow()->post_type, 'side' );
  449. remove_meta_box( $customers_box, SupportFlow()->post_type, 'side' );
  450. remove_meta_box( 'slugdiv', SupportFlow()->post_type, 'normal' );
  451. add_meta_box( 'supportflow-details', __( 'Details', 'supportflow' ), array( $this, 'meta_box_details' ), SupportFlow()->post_type, 'side' );
  452. add_meta_box( 'supportflow-subject', __( 'Subject', 'supportflow' ), array( $this, 'meta_box_subject' ), SupportFlow()->post_type, 'normal' );
  453. add_meta_box( 'supportflow-customers', __( 'Customers', 'supportflow' ), array( $this, 'meta_box_customers' ), SupportFlow()->post_type, 'normal' );
  454. add_meta_box( 'supportflow-cc-bcc', __( 'CC and BCC', 'supportflow' ), array( $this, 'meta_box_cc_bcc' ), SupportFlow()->post_type, 'normal' );
  455. add_meta_box( 'supportflow-replies', __( 'Replies', 'supportflow' ), array( $this, 'meta_box_replies' ), SupportFlow()->post_type, 'normal' );
  456. if ( 'post.php' == $pagenow ) {
  457. add_meta_box( 'supportflow-other-customers-tickets', __( 'Customer(s) recent Tickets', 'supportflow' ), array( $this, 'meta_box_other_customers_tickets' ), SupportFlow()->post_type, 'side' );
  458. add_meta_box( 'supportflow-forward_conversation', __( 'Forward this conversation', 'supportflow' ), array( $this, 'meta_box_email_conversation' ), SupportFlow()->post_type, 'side' );
  459. }
  460. }
  461. public function meta_box_other_customers_tickets() {
  462. $ticket_customers = SupportFlow()->get_ticket_customers( get_the_ID(), array( 'fields' => 'slugs' ) );
  463. $statuses = SupportFlow()->post_statuses;
  464. $status_slugs = array_keys($statuses);
  465. $table = new SupportFlow_Table( '', false, false );
  466. if ( empty( $ticket_customers ) ) {
  467. $tickets = array();
  468. } else {
  469. $args = array(
  470. 'post_type' => SupportFlow()->post_type,
  471. 'post_parent' => 0,
  472. 'post_status' => $status_slugs,
  473. 'numberposts' => 10,
  474. 'post__not_in' => array( get_the_id() ),
  475. 'tax_query' => array(
  476. array(
  477. 'taxonomy' => SupportFlow()->customers_tax,
  478. 'field' => 'slug',
  479. 'terms' => $ticket_customers,
  480. ),
  481. ),
  482. );
  483. $wp_query = new WP_Query( $args );
  484. $tickets = $wp_query->posts;
  485. }
  486. $no_items = __( 'No recent tickets found.', 'supportflow' );
  487. $table->set_no_items( $no_items );
  488. $table->set_columns( array(
  489. 'title' => __( 'Subject', 'supportflow' ),
  490. 'status' => __( 'Status', 'supportflow' ),
  491. ) );
  492. $data = array();
  493. foreach ( $tickets as $ticket ) {
  494. $post_date = strtotime( $ticket->post_date );
  495. $post_modified = strtotime( $ticket->post_modified );
  496. $title = '<b>' . esc_html( $ticket->post_title ) . '</b>';
  497. $title = "<a href='post.php?post=" . $ticket->ID . "&action=edit'>" . $title . "</a>";
  498. $data[] = array(
  499. 'title' => $title,
  500. 'status' => $statuses[$ticket->post_status]['label'],
  501. );
  502. }
  503. $table->set_data( $data );
  504. $table->display();
  505. }
  506. public function meta_box_email_conversation() {
  507. ?>
  508. <p class="description"><?php _e( "Please enter E-Mail address seperated by comma to whom you want to send this conversation.", 'supportflow' ) ?></p>
  509. <br />
  510. <input type="text" id="email_conversation_to" />
  511. <?php submit_button( __( 'Send', 'supportflow' ), '', 'email_conversation_submit', false ); ?>
  512. <p id="email_conversation_status"></p>
  513. <?php
  514. }
  515. /**
  516. * Show details about the ticket, and allow the post status and agent to be changed
  517. */
  518. public function meta_box_details() {
  519. global $pagenow;
  520. // Get post creation and last update time
  521. if ( 'post.php' == $pagenow ) {
  522. $opened = get_the_date() . ' ' . get_the_time();
  523. $modified_gmt = get_post_modified_time( 'U', true, get_the_ID() );
  524. $last_activity = sprintf( __( '%s ago', 'supportflow' ), human_time_diff( $modified_gmt ) );
  525. }
  526. // Get post status
  527. $post_statuses = SupportFlow()->post_statuses;
  528. $current_status_id = get_post_status( get_the_ID() );
  529. if ( ! isset( $post_statuses[$current_status_id] ) ) {
  530. $post_statuses_key = array_keys( $post_statuses );
  531. $current_status_id = $post_statuses_key[0];
  532. }
  533. $current_status_label = $post_statuses[$current_status_id]['label'];
  534. // Get post authors
  535. $post_author_id = get_post( get_the_ID() )->post_author;
  536. // WP change owner to current user if $post_author_id is 0 (returned when ticket is unassigned)
  537. if ( 0 == $post_author_id ) {
  538. $post_author_id = - 1;
  539. }
  540. if ( 0 < $post_author_id ) {
  541. $post_author_label = get_userdata( $post_author_id )->data->user_nicename;
  542. } else {
  543. $post_author_label = __( '-- Unassigned --', 'supportflow' );
  544. }
  545. $args = array(
  546. 'show_option_none' => __( '-- Unassigned --', 'supportflow' ),
  547. 'selected' => $post_author_id,
  548. 'id' => '',
  549. 'name' => '',
  550. 'who' => 'author',
  551. 'class' => 'meta-item-dropdown',
  552. 'echo' => false
  553. );
  554. $post_authors_dropdown = wp_dropdown_users( $args );
  555. // Get post E-Mail account
  556. $email_accounts = SupportFlow()->extend->email_accounts->get_email_accounts( true );
  557. $user_permissions = SupportFlow()->extend->permissions->get_user_permissions_data( get_current_user_id() );
  558. $user_permissions = $user_permissions['email_accounts'];
  559. $email_account_id = get_post_meta( get_the_id(), 'email_account', true );
  560. if ( '' == $email_account_id ) {
  561. $email_account_dropdown = '<select class="meta-item-dropdown">';
  562. foreach ( $email_accounts as $id => $email_account ) {
  563. if ( empty( $email_account ) || ( ! current_user_can( 'manage_options' ) && ! in_array( $id, $user_permissions ) ) ) {
  564. continue;
  565. }
  566. $email_account_dropdown .= '<option value="' . esc_attr( $id ) . '" ' . '>' . esc_html( $email_account['username'] ) . '</option>';
  567. }
  568. $email_account_dropdown .= '</select>';
  569. $email_account_keys = array_keys( $email_accounts );
  570. $email_account_first = $email_account_keys[0];
  571. $email_account_label = $email_accounts[$email_account_first]['username'];
  572. }
  573. // Get E-Mail notification settings
  574. $notification_id = 0;
  575. $notification_label = 'Default';
  576. $notification_label_title = 'Choose default if you want to receive E-Mail notifications based on what you set in `E-Mail notification` page. Choose Enable/Disable if you want to override those settings';
  577. $notification_dropdown = '';
  578. $notification_dropdown .= '<select class="meta-item-dropdown">';
  579. if ( 'post-new.php' == $pagenow ) {
  580. $notification_dropdown .= '<option value="default">' . __( 'Default', 'supportflow' ) . '</option>';
  581. $notification_dropdown .= '<option value="enable">' . __( 'Subscribed', 'supportflow' ) . '</option>';
  582. $notification_dropdown .= '<option value="disable">' . __( 'Unsubscribed', 'supportflow' ) . '</option>';
  583. } elseif ( 'post.php' == $pagenow ) {
  584. $email_notifications_override = get_post_meta( get_the_ID(), 'email_notifications_override', true );
  585. $current_user_id = get_current_user_id();
  586. if ( isset( $email_notifications_override[$current_user_id] ) ) {
  587. $override_status = $email_notifications_override[$current_user_id];
  588. if ( 'enable' == $override_status ) {
  589. $notification_label = 'Subscribed';
  590. $notification_id = 1;
  591. } elseif ( 'disable' == $override_status ) {
  592. $notification_label = 'Unsubscribed';
  593. $notification_id = 2;
  594. }
  595. }
  596. $notification_dropdown .= '<option value="default"' . selected( $notification_id, 0, false ) . '>' . __( 'Default', 'supportflow' ) . '</option>';
  597. $notification_dropdown .= '<option value="enable"' . selected( $notification_id, 1, false ) . '>' . __( 'Subscribed', 'supportflow' ) . '</option>';
  598. $notification_dropdown .= '<option value="disable"' . selected( $notification_id, 2, false ) . '>' . __( 'Unsubscribed', 'supportflow' ) . '</option>';
  599. }
  600. $notification_dropdown .= '</select>';
  601. $close_ticket_label = __( 'Close ticket', 'supportflow' );
  602. // Get submit button label
  603. if ( 'post-new.php' == $pagenow ) {
  604. $submit_text = __( 'Start Ticket', 'supportflow' );
  605. } else {
  606. $submit_text = __( 'Update Ticket', 'supportflow' );
  607. }
  608. ?>
  609. <div id="minor-publishing">
  610. <div id="misc-publishing-actions">
  611. <?php if ( '' == $email_account_id ) : ?>
  612. <div class="misc-pub-section meta-item">
  613. <label class="meta-item-toggle-button"><?php _e( 'Account', 'supportflow' ) ?>:</label>
  614. <span class="meta-item-label"><?php _e( $email_account_label, 'supportflow' ) ?></span>
  615. <a href="#" class="meta-item-toggle-button meta-item-toggle-content hide-if-no-js">
  616. <span aria-hidden="true"><?php _e( 'Edit' ) ?></span>
  617. </a>
  618. <input name="post_email_account" class="meta-item-name" value="<?php echo $email_account_first ?>" type="hidden" />
  619. <div class="meta-item-toggle-content hide-if-js">
  620. <?php echo $email_account_dropdown ?>
  621. <a href="#" class="hide-if-no-js button meta-item-ok-button meta-item-toggle-button"><?php _e( 'OK' ) ?></a>
  622. <a href="#" class="hide-if-no-js button-cancel meta-item-cancel-button meta-item-toggle-button"><?php _e( 'Cancel' ) ?></a>
  623. </div>
  624. </div>
  625. <?php endif; ?>
  626. <!--Ticket opening date/time-->
  627. <?php if ( 'post.php' == $pagenow ) : ?>
  628. <div class="misc-pub-section meta-item">
  629. <label><?php _e( 'Opened', 'supportflow' ) ?>:</label>
  630. <span class="meta-item-label"><?php esc_html_e( $opened ) ?></span>
  631. </div>
  632. <!--Last ticket update time-->
  633. <div class="misc-pub-section meta-item">
  634. <label><?php _e( 'Last Activity', 'supportflow' ) ?>:</label>
  635. <span class="meta-item-label"><?php esc_html_e( $last_activity ) ?></span>
  636. </div>
  637. <?php endif; ?>
  638. <!--Ticket status box-->
  639. <div class="misc-pub-section meta-item">
  640. <label class="meta-item-toggle-button"><?php _e( 'Status', 'supportflow' ) ?>:</label>
  641. <span class="meta-item-label"><?php esc_html_e( $current_status_label, 'supportflow' ) ?></span>
  642. <a href="#" class="meta-item-toggle-button meta-item-toggle-content hide-if-no-js">
  643. <span aria-hidden="true"><?php _e( 'Edit' ) ?></span>
  644. </a>
  645. <input name="post_status" class="meta-item-name" value="<?php esc_attr_e( $current_status_id ) ?>" type="hidden" />
  646. <div class="meta-item-toggle-content hide-if-js">
  647. <select class="meta-item-dropdown">
  648. <?php foreach ( $post_statuses as $slug => $post_status ) : ?>
  649. <option value="<?php esc_attr_e( $slug ) ?>"<?php selected( $current_status_id, $slug ) ?>><?php esc_html_e( $post_status['label'] ) ?></option>;
  650. <?php endforeach; ?>
  651. </select>
  652. <a href="#" class="hide-if-no-js button meta-item-ok-button meta-item-toggle-button"><?php _e( 'OK' ) ?></a>
  653. <a href="#" class="hide-if-no-js button-cancel meta-item-cancel-button meta-item-toggle-button"><?php _e( 'Cancel' ) ?></a>
  654. </div>
  655. </div>
  656. <div class="misc-pub-section meta-item">
  657. <label class="meta-item-toggle-button"><?php _e( 'Owner', 'supportflow' ) ?>:</label>
  658. <span class="meta-item-label"><?php _e( $post_author_label, 'supportflow' ) ?></span>
  659. <a href="#" class="meta-item-toggle-button meta-item-toggle-content hide-if-no-js">
  660. <span aria-hidden="true"><?php _e( 'Edit' ) ?></span>
  661. </a>
  662. <input name="post_author" class="meta-item-name" value="<?php esc_attr_e( $post_author_id ) ?>" type="hidden" />
  663. <div class="meta-item-toggle-content hide-if-js">
  664. <?php echo $post_authors_dropdown ?>
  665. <a href="#" class="hide-if-no-js button meta-item-ok-button meta-item-toggle-button"><?php _e( 'OK' ) ?></a>
  666. <a href="#" class="hide-if-no-js button-cancel meta-item-cancel-button meta-item-toggle-button"><?php _e( 'Cancel' ) ?></a>
  667. </div>
  668. </div>
  669. <div class="misc-pub-section meta-item">
  670. <label class="meta-item-toggle-button" title="<?php _e( $notification_label_title, 'supportflow' ) ?>"><?php _e( 'E-Mail Notifications', 'supportflow' ) ?>:</label>
  671. <span class="meta-item-label"><?php esc_html_e( $notification_label, 'supportflow' ) ?></span>
  672. <a href="#" class="meta-item-toggle-button meta-item-toggle-content hide-if-no-js">
  673. <span aria-hidden="true"><?php _e( 'Edit' ) ?></span>
  674. </a>
  675. <input name="post_email_notifications_override" class="meta-item-name" value="<?php echo $notification_id ?>" type="hidden" />
  676. <div class="meta-item-toggle-content hide-if-js">
  677. <?php echo $notification_dropdown ?>
  678. <a href="#" class="hide-if-no-js button meta-item-ok-button meta-item-toggle-button"><?php _e( 'OK' ) ?></a>
  679. <a href="#" class="hide-if-no-js button-cancel meta-item-cancel-button meta-item-toggle-button"><?php _e( 'Cancel' ) ?></a>
  680. </div>
  681. </div>
  682. </div>
  683. <div class="clear"></div>
  684. </div>
  685. <div id="major-publishing-actions">
  686. <?php if ( 'post.php' == $pagenow && $current_status_id != 'sf_closed' ) : ?>
  687. <div id="delete-action">
  688. <?php submit_button( $close_ticket_label, '', 'close-ticket-submit', false, array( 'id' => 'close-ticket-submit' ) ); ?>
  689. </div>
  690. <?php endif; ?>
  691. <div id="publishing-action">
  692. <?php submit_button( $submit_text, 'save-button primary', 'update-ticket', false ); ?>
  693. </div>
  694. <div class="clear"></div>
  695. </div>
  696. <?php
  697. }
  698. /**
  699. * A box that appears at the top
  700. */
  701. public function meta_box_subject() {
  702. $placeholder = __( 'What is your conversation about?', 'supportflow' );
  703. echo '<h4>' . __( 'Subject', 'supportflow' ) . '</h4>';
  704. echo '<input type="text" id="subject" name="post_title" class="sf_autosave" placeholder="' . $placeholder . '" value="' . get_the_title() . '" autocomplete="off" />';
  705. echo '<p class="description">' . __( 'Please describe what this ticket is about in several words', 'supportflow' ) . '</p>';
  706. }
  707. /**
  708. * Add a form element where the user can change the customers
  709. */
  710. public function meta_box_customers() {
  711. $placeholder = __( 'Who are you starting a conversation with?', 'supportflow' );
  712. if ( 'draft' == get_post_status( get_the_ID() ) ) {
  713. $customers_string = get_post_meta( get_the_ID(), '_sf_autosave_customers', true );
  714. } else {
  715. $customers = SupportFlow()->get_ticket_customers( get_the_ID(), array( 'fields' => 'emails' ) );
  716. $customers_string = implode( ', ', $customers );
  717. $customers_string .= empty( $customers_string ) ? '' : ', ';
  718. }
  719. echo '<h4>' . __( 'Customer(s)', 'supportflow' ) . '</h4>';
  720. echo '<input type="text" id="customers" name="customers" class="sf_autosave" placeholder="' . $placeholder . '" value="' . esc_attr( $customers_string ) . '" autocomplete="off" />';
  721. echo '<p class="description">' . __( 'Enter each customer email address, separated with a comma', 'supportflow' ) . '</p>';
  722. }
  723. /**
  724. * Add a form element where you can choose cc and bcc receiver of reply
  725. */
  726. public function meta_box_cc_bcc() {
  727. $cc_value = esc_attr( get_post_meta( get_the_ID(), '_sf_autosave_cc', true ) );
  728. $bcc = esc_attr( get_post_meta( get_the_ID(), '_sf_autosave_bcc', true ) );
  729. ?>
  730. <p class="description"> <?php _e( "Please add all the E-Mail ID's seperated by comma.", 'supportflow' ) ?></p>
  731. <h4 class="inline"><?php _e( "CC: ", 'supportflow' ) ?></h4>
  732. <input type="text" class="sf_autosave" id="cc" name="cc" value="<?php echo $cc_value ?>" />
  733. <h4 class="inline"> <?php _e( "BCC: ", 'supportflow' ) ?></h4>
  734. <input type="text" class="sf_autosave" id="bcc" name="bcc" value="<?php echo $bcc ?>" />
  735. <?php
  736. }
  737. /**
  738. * Standard listing of replies includes a form at the top
  739. * and any existing replies listed in reverse chronological order
  740. */
  741. public function meta_box_replies() {
  742. global $pagenow;
  743. $predefined_replies = get_posts( array( 'post_type' => 'sf_predefs' ) );
  744. $pre_defs = array( array( 'title' => __( 'Pre-defined Replies', 'supportflow' ), 'content' => '' ) );
  745. foreach ( $predefined_replies as $predefined_reply ) {
  746. $content = $predefined_reply->post_content;
  747. if ( ! empty( $predefined_reply->post_title ) ) {
  748. $title = $predefined_reply->post_title;
  749. } else {
  750. $title = $predefined_reply->post_content;
  751. }
  752. // Limit size to 75 characters
  753. if ( strlen( $title ) > 75 ) {
  754. $title = substr( $title, 0, 75 - 3 ) . '...';
  755. }
  756. if ( 0 != strlen( $content ) ) {
  757. $pre_defs[] = array( 'title' => $title, 'content' => $content );
  758. }
  759. }
  760. $email_account_id = get_post_meta( get_the_ID(), 'email_account', true );
  761. $email_account = SupportFlow()->extend->email_accounts->get_email_account( $email_account_id );
  762. $ticket_lock = ( null == $email_account && '' != $email_account_id );
  763. $disabled_attr = $ticket_lock ? 'disabled' : '';
  764. $submit_attr_array = $ticket_lock ? array( 'disabled' => 'true' ) : array();
  765. if ( $ticket_lock ) {
  766. $placeholder = __( "Ticket is locked permanently because E-Mail account associated with it is deleted. Please create a new ticket now. You can't now reply to it.", 'supportflow' );
  767. } else {
  768. $placeholders = array(
  769. __( "What's burning?", 'supportflow' ),
  770. __( 'What do you need to get off your chest?', 'supportflow' ),
  771. );
  772. $rand = array_rand( $placeholders );
  773. $placeholder = $placeholders[$rand];
  774. }
  775. echo '<div class="alignleft"><h4>' . __( 'Conversation', 'supportflow' ) . '</h4></div>';
  776. echo '<div class="alignright">';
  777. echo '<select id="predefs" ' . $disabled_attr . ' class="predefined_replies_dropdown">';
  778. foreach ( $pre_defs as $pre_def ) {
  779. echo '<option class="predef" data-content="' . esc_attr( $pre_def['content'] ) . '">' . esc_html( $pre_def['title'] ) . "</option>\n";
  780. }
  781. echo '</select></div>';
  782. echo '<div id="ticket-reply-box">';
  783. echo "<textarea id='reply' name='reply' $disabled_attr class='ticket-reply sf_autosave' rows='4' placeholder='" . esc_attr( $placeholder ) . "'>";
  784. echo esc_html( get_post_meta( get_the_ID(), '_sf_autosave_reply', true ) );
  785. echo "</textarea>";
  786. echo '<div id="message-tools">';
  787. echo '<div id="replies-attachments-wrap">';
  788. echo '<div class="drag-drop-buttons">';
  789. echo '<input id="reply-attachment-browse-button" ' . $disabled_attr . ' type="button" value="' . esc_attr( __( 'Attach files', 'supportflow' ) ) . '" class="button" />';
  790. echo '</div>';
  791. echo '<ul id="replies-attachments-list">';
  792. echo '</ul>';
  793. echo '<input type="hidden" id="reply-attachments" name="reply-attachments" value="," />';
  794. echo '</div>';
  795. echo '<div id="submit-action">';
  796. $signature_label_title = __( 'Append your signature at the bottom of the reply. Signature can be removed or changed in preferences page', 'supportflow' );
  797. echo '<input type="checkbox" ' . $disabled_attr . ' checked="checked" id="insert-signature" name="insert-signature" />';
  798. echo "<label for='insert-signature' title='$signature_label_title'>" . __( 'Insert signature', 'supportflow' ) . '</label>';
  799. echo '<input type="checkbox" ' . $disabled_attr . ' id="mark-private" name="mark-private" />';
  800. echo '<label for="mark-private">' . __( 'Mark private', 'supportflow' ) . '</label>';
  801. if ( 'post-new.php' == $pagenow ) {
  802. $submit_text = __( 'Start Ticket', 'supportflow' );
  803. } else {
  804. $submit_text = __( 'Send Message', 'supportflow' );
  805. }
  806. submit_button( $submit_text, 'primary save-button', 'insert-reply', false, $submit_attr_array );
  807. echo '</div>';
  808. echo '</div>';
  809. echo '</div>';
  810. echo '<div class="clear"></div>';
  811. $this->display_ticket_replies();
  812. }
  813. public function display_ticket_replies() {
  814. $private_replies = SupportFlow()->get_ticket_replies( get_the_ID(), array( 'status' => 'private' ) );
  815. if ( ! empty( $private_replies ) ) {
  816. echo '<ul class="private-replies">';
  817. foreach ( $private_replies as $reply ) {
  818. echo '<li>';
  819. echo '<div class="ticket-reply">';
  820. $post_content = wpautop( stripslashes( $reply->post_content ) );
  821. // Make link clickable
  822. $post_content = make_clickable( $post_content );
  823. $post_content = $this->hide_quoted_text( $post_content );
  824. echo $post_content;
  825. if ( $attachment_ids = get_post_meta( $reply->ID, 'sf_attachments' ) ) {
  826. echo '<ul class="ticket-reply-attachments">';
  827. foreach ( $attachment_ids as $attachment_id ) {
  828. $attachment_link = SupportFlow()->extend->attachments->get_attachment_url( $attachment_id );
  829. echo '<li><a target="_blank" href="' . esc_url( $attachment_link ) . '">' . esc_html( get_the_title( $attachment_id ) ) . '</a></li>';
  830. }
  831. echo '</ul>';
  832. }
  833. echo '</div>';
  834. $reply_author = get_post_meta( $reply->ID, 'reply_author', true );
  835. $reply_timestamp = sprintf( __( 'Noted by %1$s on %2$s at %3$s', 'supportflow' ), $reply_author, get_the_date(), get_the_time() );
  836. $modified_gmt = get_post_modified_time( 'U', true, get_the_ID() );
  837. $last_activity = sprintf( __( '%s ago', 'supportflow' ), human_time_diff( $modified_gmt ) );
  838. echo '<div class="ticket-meta"><span class="reply-timestamp">' . esc_html( $reply_timestamp ) . ' (' . $last_activity . ')' . '</span></div>';
  839. echo '</li>';
  840. }
  841. echo '</ul>';
  842. }
  843. $replies = SupportFlow()->get_ticket_replies( get_the_ID(), array( 'status' => 'public' ) );
  844. if ( ! empty( $replies ) ) {
  845. echo '<ul class="ticket-replies">';
  846. foreach ( $replies as $reply ) {
  847. $reply_author = get_post_meta( $reply->ID, 'reply_author', true );
  848. $reply_author_email = get_post_meta( $reply->ID, 'reply_author_email', true );
  849. echo '<li>';
  850. echo '<div class="reply-avatar">' . get_avatar( $reply_author_email, 72 );
  851. echo '<p class="reply-author">' . esc_html( $reply_author ) . '</p>';
  852. echo '</div>';
  853. echo '<div class="ticket-reply">';
  854. $post_content = wpautop( stripslashes( $reply->post_content ) );
  855. // Make link clickable
  856. $post_content = make_clickable( $post_content );
  857. $post_content = $this->hide_quoted_text( $post_content );
  858. echo $post_content;
  859. if ( $attachment_ids = get_post_meta( $reply->ID, 'sf_attachments' ) ) {
  860. echo '<ul class="ticket-reply-attachments">';
  861. foreach ( $attachment_ids as $attachment_id ) {
  862. $attachment_link = SupportFlow()->extend->attachments->get_attachment_url( $attachment_id );
  863. echo '<li><a target="_blank" href="' . esc_url( $attachment_link ) . '">' . esc_html( get_the_title( $attachment_id ) ) . '</a></li>';
  864. }
  865. echo '</ul>';
  866. }
  867. echo '</div>';
  868. $reply_timestamp = sprintf( __( '%s at %s', 'supportflow' ), get_the_date(), get_the_time() );
  869. $modified_gmt = get_post_modified_time( 'U', true, get_the_ID() );
  870. $last_activity = sprintf( __( '%s ago', 'supportflow' ), human_time_diff( $modified_gmt ) );
  871. echo '<div class="ticket-meta"><span class="reply-timestamp">' . esc_html( $reply_timestamp ) . ' (' . $last_activity . ')' . '</span></div>';
  872. echo '</li>';
  873. }
  874. echo '</ul>';
  875. }
  876. echo '<div class="clear"></div>';
  877. }
  878. /**
  879. * Modifications to the columns appearing in the All Tickets view
  880. */
  881. public function filter_manage_post_columns( $columns ) {
  882. $new_columns = array(
  883. 'cb' => $columns['cb'],
  884. 'updated' => __( 'Updated', 'supportflow' ),
  885. 'title' => __( 'Subject', 'supportflow' ),
  886. 'sf_excerpt' => __( 'Excerpt', 'supportflow' ),
  887. 'customers' => __( 'Customers', 'supportflow' ),
  888. 'status' => __( 'Status', 'supportflow' ),
  889. 'author' => __( 'Agent', 'supportflow' ),
  890. 'sf_replies' => '<span title="' . __( 'Reply count', 'supportflow' ) . '" class="comment-grey-bubble"></span>',
  891. 'email' => __( 'E-Mail account', 'supportflow' ),
  892. 'created' => __( 'Created', 'support' ),
  893. );
  894. return $new_columns;
  895. }
  896. /**
  897. * Make some other columns sortable too
  898. */
  899. public function manage_sortable_columns( $columns ) {
  900. $columns['updated'] = 'modified';
  901. $columns['created'] = 'date';
  902. return $columns;
  903. }
  904. /**
  905. * Use the most recent public reply as the post excerpt
  906. * on the Manage Tickets view so mode=excerpt works well
  907. */
  908. public function filter_get_the_excerpt( $orig ) {
  909. if ( $reply = array_pop( SupportFlow()->get_ticket_replies( get_the_ID() ) ) ) {
  910. $reply_author = get_post_meta( $reply->ID, 'reply_author' );
  911. return $reply_author . ': "' . wp_trim_excerpt( $reply->post_content ) . '"';
  912. } else {
  913. return $orig;
  914. }
  915. }
  916. /**
  917. * Produce the column values for the custom columns we created
  918. */
  919. function action_manage_posts_custom_column( $column_name, $ticket_id ) {
  920. switch ( $column_name ) {
  921. case 'updated':
  922. $modified_gmt = get_post_modified_time( 'U', true, $ticket_id );
  923. echo sprintf( __( '%s ago', 'supportflow' ), human_time_diff( $modified_gmt ) );
  924. break;
  925. case 'sf_excerpt':
  926. $replies = SupportFlow()->get_ticket_replies( $ticket_id, array( 'numberposts' => 1, 'order' => 'ASC' ) );
  927. if ( ! isset( $replies[0] ) ) {
  928. echo '—';
  929. break;
  930. }
  931. $first_reply = $replies[0]->post_content;
  932. if ( strlen( $first_reply ) > 50 ) {
  933. $first_reply = substr( $first_reply, 0, 50 );
  934. }
  935. echo $first_reply;
  936. break;
  937. case 'customers':
  938. $customers = SupportFlow()->get_ticket_customers( $ticket_id, array( 'fields' => 'emails' ) );
  939. if ( empty( $customers ) ) {
  940. echo '—';
  941. break;
  942. }
  943. foreach ( $customers as $key => $customer_email ) {
  944. $args = array(
  945. SupportFlow()->customers_tax => SupportFlow()->get_email_hash( $customer_email ),
  946. 'post_type' => SupportFlow()->post_type,
  947. );
  948. $customer_photo = get_avatar( $customer_email, 16 );
  949. $customer_link = '<a class="customer_link" href="' . esc_url( add_query_arg( $args, admin_url( 'edit.php' ) ) ) . '">' . $customer_email . '</a>';
  950. $customers[$key] = $customer_photo . '&nbsp;' . $customer_link;
  951. }
  952. echo implode( '<br />', $customers );
  953. break;
  954. case 'status':
  955. $post_status = get_post_status( $ticket_id );
  956. $args = array(
  957. 'post_type' => SupportFlow()->post_type,
  958. 'post_status' => $post_status,
  959. );
  960. $status_name = get_post_status_object( $post_status )->label;
  961. $filter_link = add_query_arg( $args, admin_url( 'edit.php' ) );
  962. echo '<a href="' . esc_url( $filter_link ) . '">' . esc_html( $status_name ) . '</a>';
  963. break;
  964. case 'email':
  965. $email_account_id = get_post_meta( $ticket_id, 'email_account', true );
  966. $email_accounts = SupportFlow()->extend->email_accounts->get_email_accounts();
  967. $args = array(
  968. 'post_type' => SupportFlow()->post_type,
  969. 'email_account' => $email_account_id,
  970. );
  971. if ( ! isset( $email_accounts[$email_account_id] ) ) {
  972. echo '—';
  973. break;
  974. }
  975. $email_account_username = $email_accounts[$email_account_id]['username'];
  976. $filter_link = add_query_arg( $args, admin_url( 'edit.php' ) );
  977. echo '<a href="' . esc_url( $filter_link ) . '">' . esc_html( $email_account_username ) . '</a>';
  978. break;
  979. case 'sf_replies':
  980. $replies = SupportFlow()->get_ticket_replies_count( $ticket_id );
  981. echo '<div class="post-com-count-wrapper">';
  982. echo "<span class='replies-count'>{$replies}</span>";
  983. echo '</div>';
  984. break;
  985. case 'created':
  986. $created_time = get_the_time( get_option( 'time_format' ) . ' T', $ticket_id );
  987. $created_date = get_the_time( get_option( 'date_format' ), $ticket_id );
  988. echo sprintf( __( '%s<br />%s', 'supportflow' ), $created_time, $created_date );
  989. break;
  990. }
  991. }
  992. /**
  993. * Whether or not we're on a view for creating or updating a ticket
  994. *
  995. * @return string $pagenow Return the context for the screen we're in
  996. */
  997. public function is_edit_screen() {
  998. global $pagenow;
  999. if ( in_array( $pagenow, array( 'edit.php', 'post-new.php' ) ) && ! empty( $_GET['post_type'] ) && $_GET['post_type'] == SupportFlow()->post_type ) {
  1000. return $pagenow;
  1001. } elseif ( 'post.php' == $pagenow && ! empty( $_GET['action'] ) && 'edit' == $_GET['action'] && ! empty( $_GET['post'] ) ) {
  1002. $the_post = get_post( absint( $_GET['post'] ) );
  1003. return ( is_a( $the_post, 'WP_Post' ) && $the_post->post_type == SupportFlow()->post_type ) ? $pagenow : false;
  1004. } else {
  1005. return false;
  1006. }
  1007. }
  1008. /**
  1009. * When a ticket is saved or updated, make sure we save the customer
  1010. * and new reply data
  1011. */
  1012. public function action_save_post( $ticket_id ) {
  1013. $email_account_id = get_post_meta( $ticket_id, 'email_account', true );
  1014. $email_account = SupportFlow()->extend->email_accounts->get_email_account( $email_account_id );
  1015. $ticket_lock = ( null == $email_account && '' != $email_account );
  1016. if ( SupportFlow()->post_type != get_post_type( $ticket_id ) ) {
  1017. return;
  1018. }
  1019. if ( isset( $_POST['customers'] ) ) {
  1020. $customers = array_map( 'sanitize_email', explode( ',', $_POST['customers'] ) );
  1021. SupportFlow()->update_ticket_customers( $ticket_id, $customers );
  1022. }
  1023. if ( isset( $_POST['post_email_account'] ) && is_numeric( $_POST['post_email_account'] ) && '' == $email_account_id ) {
  1024. $email_account = (int) $_POST['post_email_account'];
  1025. update_post_meta( $ticket_id, 'email_account', $email_account );
  1026. }
  1027. if ( isset( $_POST['post_email_notifications_override'] ) && in_array( $_POST['post_email_notifications_override'], array( 'default', 'enable', 'disable' ) ) ) {
  1028. $email_notifications_override = get_post_meta( $ticket_id, 'email_notifications_override', true );
  1029. $email_notifications_override[get_current_user_id()] = $_POST['post_email_notifications_override'];
  1030. update_post_meta( $ticket_id, 'email_notifications_override', $email_notifications_override );
  1031. }
  1032. if ( isset( $_POST['reply'] ) && ! empty( $_POST['reply'] ) && ! $ticket_lock ) {
  1033. $reply = $_POST['reply'];
  1034. if ( isset( $_POST['insert-signature'] ) && 'on' == $_POST['insert-signature'] ) {
  1035. $agent_signature = get_user_meta( get_current_user_id(), 'sf_user_signature', true );
  1036. if ( ! empty( $agent_signature ) ) {
  1037. $reply .= "\n\n-----\n$agent_signature";
  1038. }
  1039. }
  1040. $reply = SupportFlow()->sanitize_ticket_reply( $reply );
  1041. $visibility = ( ! empty( $_POST['mark-private'] ) ) ? 'private' : 'public';
  1042. if ( ! empty( $_POST['reply-attachments'] ) ) {
  1043. $attachements = explode( ',', trim( $_POST['reply-attachments'], ',' ) );
  1044. // Remove same attachment added more than once
  1045. $attachements = array_unique($attachements);
  1046. // Remove non-int attachment ID's from array
  1047. $attachements = array_filter( $attachements, function ( $val ) {
  1048. return (string) (int) $val === (string) $val;
  1049. } );
  1050. $attachment_ids = array_map( 'intval', $attachements );
  1051. } else {
  1052. $attachment_ids = '';
  1053. }
  1054. $cc = ( ! empty( $_POST['cc'] ) ) ? SupportFlow()->extract_email_ids( $_POST['cc'] ) : '';
  1055. $bcc = ( ! empty( $_POST['bcc'] ) ) ? SupportFlow()->extract_email_ids( $_POST['bcc'] ) : '';
  1056. $reply_args = array(
  1057. 'post_status' => $visibility,
  1058. 'attachment_ids' => $attachment_ids,
  1059. 'cc' => $cc,
  1060. 'bcc' => $bcc,
  1061. );
  1062. SupportFlow()->add_ticket_reply( $ticket_id, $reply, $reply_args );
  1063. }
  1064. }
  1065. /**
  1066. * Hide quoted content in a message and display a link to show it.
  1067. * Line startings with ">" sign are considered quoted content
  1068. */
  1069. public function hide_quoted_text( $text ) {
  1070. $ws = '\s'; // Whitespace ( matches \r\n\t\f )
  1071. $gt = '&gt;'; // Greater than sign
  1072. $br = "<{$ws}*br[^>]*>"; // BR tag
  1073. $non_br = "<(\s|/)*(?:(?!br( |>)).)*[^>]>"; // Any tag other than BR
  1074. $regex = "(^|$br)($non_br|$ws)*$gt(.*?)($br(?!($non_br|$ws)*$gt)|$)";
  1075. $res = preg_replace_callback( "~$regex~is", array( $this, 'hide_quoted_text_regex_callback' ), $text );
  1076. return $res;
  1077. }
  1078. /**
  1079. * Just a function used by hide_quoted_text() for its regex callback
  1080. * Anonymous function are not used as they unavailable in PHP 5.2.x
  1081. * create_function() is not used as it it not readable
  1082. */
  1083. protected function hide_quoted_text_regex_callback( $matches ) {
  1084. $match = esc_attr( $matches[0] );
  1085. $show_msg = __( 'Show quoted content', 'supportflow' );
  1086. return "<span><a href='' class='sf_toggle_quoted_text' data-quoted_text='$match'><br />$show_msg</a><br /></span>";
  1087. }
  1088. }
  1089. SupportFlow()->extend->admin = new SupportFlow_Admin();