]/g.exec(item)) {
+ var tbody = document.createElement('tbody');
+ tbody.innerHTML = item;
+ return tbody.firstChild;
+ } else if (item.indexOf("<") !== -1) {
+ var div = document.createElement('div');
+ div.innerHTML = item;
+ return div.firstChild;
+ } else {
+ var source = document.getElementById(list.item);
+ if (source) {
+ return source;
+ }
+ }
+ return undefined;
+ };
+
+ this.get = function(item, valueNames) {
+ templater.create(item);
+ var values = {};
+ for(var i = 0, il = valueNames.length; i < il; i++) {
+ var elm;
+ if (valueNames[i].data) {
+ for (var j = 0, jl = valueNames[i].data.length; j < jl; j++) {
+ values[valueNames[i].data[j]] = list.utils.getAttribute(item.elm, 'data-'+valueNames[i].data[j]);
+ }
+ } else if (valueNames[i].attr && valueNames[i].name) {
+ elm = list.utils.getByClass(item.elm, valueNames[i].name, true);
+ values[valueNames[i].name] = elm ? list.utils.getAttribute(elm, valueNames[i].attr) : "";
+ } else {
+ elm = list.utils.getByClass(item.elm, valueNames[i], true);
+ values[valueNames[i]] = elm ? elm.innerHTML : "";
+ }
+ elm = undefined;
+ }
+ return values;
+ };
+
+ this.set = function(item, values) {
+ var getValueName = function(name) {
+ for (var i = 0, il = list.valueNames.length; i < il; i++) {
+ if (list.valueNames[i].data) {
+ var data = list.valueNames[i].data;
+ for (var j = 0, jl = data.length; j < jl; j++) {
+ if (data[j] === name) {
+ return { data: name };
+ }
+ }
+ } else if (list.valueNames[i].attr && list.valueNames[i].name && list.valueNames[i].name == name) {
+ return list.valueNames[i];
+ } else if (list.valueNames[i] === name) {
+ return name;
+ }
+ }
+ };
+ var setValue = function(name, value) {
+ var elm;
+ var valueName = getValueName(name);
+ if (!valueName)
+ return;
+ if (valueName.data) {
+ item.elm.setAttribute('data-'+valueName.data, value);
+ } else if (valueName.attr && valueName.name) {
+ elm = list.utils.getByClass(item.elm, valueName.name, true);
+ if (elm) {
+ elm.setAttribute(valueName.attr, value);
+ }
+ } else {
+ elm = list.utils.getByClass(item.elm, valueName, true);
+ if (elm) {
+ elm.innerHTML = value;
+ }
+ }
+ elm = undefined;
+ };
+ if (!templater.create(item)) {
+ for(var v in values) {
+ if (values.hasOwnProperty(v)) {
+ setValue(v, values[v]);
+ }
+ }
+ }
+ };
+
+ this.create = function(item) {
+ if (item.elm !== undefined) {
+ return false;
+ }
+ if (itemSource === undefined) {
+ throw new Error("The list need to have at list one item on init otherwise you'll have to add a template.");
+ }
+ /* If item source does not exists, use the first item in list as
+ source for new items */
+ var newItem = itemSource.cloneNode(true);
+ newItem.removeAttribute('id');
+ item.elm = newItem;
+ templater.set(item, item.values());
+ return true;
+ };
+ this.remove = function(item) {
+ if (item.elm.parentNode === list.list) {
+ list.list.removeChild(item.elm);
+ }
+ };
+ this.show = function(item) {
+ templater.create(item);
+ list.list.appendChild(item.elm);
+ };
+ this.hide = function(item) {
+ if (item.elm !== undefined && item.elm.parentNode === list.list) {
+ list.list.removeChild(item.elm);
+ }
+ };
+ this.clear = function() {
+ /* .innerHTML = ''; fucks up IE */
+ if (list.list.hasChildNodes()) {
+ while (list.list.childNodes.length >= 1)
+ {
+ list.list.removeChild(list.list.firstChild);
+ }
+ }
+ };
+
+ init();
+};
+
+module.exports = function(list) {
+ return new Templater(list);
+};
+
+
+/***/ }),
+/* 17 */
+/***/ (function(module, exports) {
+
+/**
+ * A cross-browser implementation of getAttribute.
+ * Source found here: http://stackoverflow.com/a/3755343/361337 written by Vivin Paliath
+ *
+ * Return the value for `attr` at `element`.
+ *
+ * @param {Element} el
+ * @param {String} attr
+ * @api public
+ */
+
+module.exports = function(el, attr) {
+ var result = (el.getAttribute && el.getAttribute(attr)) || null;
+ if( !result ) {
+ var attrs = el.attributes;
+ var length = attrs.length;
+ for(var i = 0; i < length; i++) {
+ if (attr[i] !== undefined) {
+ if(attr[i].nodeName === attr) {
+ result = attr[i].nodeValue;
+ }
+ }
+ }
+ }
+ return result;
+};
+
+
+/***/ }),
+/* 18 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var alphabet;
+var alphabetIndexMap;
+var alphabetIndexMapLength = 0;
+
+function isNumberCode(code) {
+ return code >= 48 && code <= 57;
+}
+
+function naturalCompare(a, b) {
+ var lengthA = (a += '').length;
+ var lengthB = (b += '').length;
+ var aIndex = 0;
+ var bIndex = 0;
+
+ while (aIndex < lengthA && bIndex < lengthB) {
+ var charCodeA = a.charCodeAt(aIndex);
+ var charCodeB = b.charCodeAt(bIndex);
+
+ if (isNumberCode(charCodeA)) {
+ if (!isNumberCode(charCodeB)) {
+ return charCodeA - charCodeB;
+ }
+
+ var numStartA = aIndex;
+ var numStartB = bIndex;
+
+ while (charCodeA === 48 && ++numStartA < lengthA) {
+ charCodeA = a.charCodeAt(numStartA);
+ }
+ while (charCodeB === 48 && ++numStartB < lengthB) {
+ charCodeB = b.charCodeAt(numStartB);
+ }
+
+ var numEndA = numStartA;
+ var numEndB = numStartB;
+
+ while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) {
+ ++numEndA;
+ }
+ while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) {
+ ++numEndB;
+ }
+
+ var difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length
+ if (difference) {
+ return difference;
+ }
+
+ while (numStartA < numEndA) {
+ difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++);
+ if (difference) {
+ return difference;
+ }
+ }
+
+ aIndex = numEndA;
+ bIndex = numEndB;
+ continue;
+ }
+
+ if (charCodeA !== charCodeB) {
+ if (
+ charCodeA < alphabetIndexMapLength &&
+ charCodeB < alphabetIndexMapLength &&
+ alphabetIndexMap[charCodeA] !== -1 &&
+ alphabetIndexMap[charCodeB] !== -1
+ ) {
+ return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB];
+ }
+
+ return charCodeA - charCodeB;
+ }
+
+ ++aIndex;
+ ++bIndex;
+ }
+
+ return lengthA - lengthB;
+}
+
+naturalCompare.caseInsensitive = naturalCompare.i = function(a, b) {
+ return naturalCompare(('' + a).toLowerCase(), ('' + b).toLowerCase());
+};
+
+Object.defineProperties(naturalCompare, {
+ alphabet: {
+ get: function() {
+ return alphabet;
+ },
+ set: function(value) {
+ alphabet = value;
+ alphabetIndexMap = [];
+ var i = 0;
+ if (alphabet) {
+ for (; i < alphabet.length; i++) {
+ alphabetIndexMap[alphabet.charCodeAt(i)] = i;
+ }
+ }
+ alphabetIndexMapLength = alphabetIndexMap.length;
+ for (i = 0; i < alphabetIndexMapLength; i++) {
+ if (alphabetIndexMap[i] === undefined) {
+ alphabetIndexMap[i] = -1;
+ }
+ }
+ },
+ },
+});
+
+module.exports = naturalCompare;
+
+
+/***/ }),
+/* 19 */
+/***/ (function(module, exports) {
+
+module.exports = function(text, pattern, options) {
+ // Aproximately where in the text is the pattern expected to be found?
+ var Match_Location = options.location || 0;
+
+ //Determines how close the match must be to the fuzzy location (specified above). An exact letter match which is 'distance' characters away from the fuzzy location would score as a complete mismatch. A distance of '0' requires the match be at the exact location specified, a threshold of '1000' would require a perfect match to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
+ var Match_Distance = options.distance || 100;
+
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match (of both letters and location), a threshold of '1.0' would match anything.
+ var Match_Threshold = options.threshold || 0.4;
+
+ if (pattern === text) return true; // Exact match
+ if (pattern.length > 32) return false; // This algorithm cannot be used
+
+ // Set starting location at beginning text and initialise the alphabet.
+ var loc = Match_Location,
+ s = (function() {
+ var q = {},
+ i;
+
+ for (i = 0; i < pattern.length; i++) {
+ q[pattern.charAt(i)] = 0;
+ }
+
+ for (i = 0; i < pattern.length; i++) {
+ q[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
+ }
+
+ return q;
+ }());
+
+ // Compute and return the score for a match with e errors and x location.
+ // Accesses loc and pattern through being a closure.
+
+ function match_bitapScore_(e, x) {
+ var accuracy = e / pattern.length,
+ proximity = Math.abs(loc - x);
+
+ if (!Match_Distance) {
+ // Dodge divide by zero error.
+ return proximity ? 1.0 : accuracy;
+ }
+ return accuracy + (proximity / Match_Distance);
+ }
+
+ var score_threshold = Match_Threshold, // Highest score beyond which we give up.
+ best_loc = text.indexOf(pattern, loc); // Is there a nearby exact match? (speedup)
+
+ if (best_loc != -1) {
+ score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
+ // What about in the other direction? (speedup)
+ best_loc = text.lastIndexOf(pattern, loc + pattern.length);
+
+ if (best_loc != -1) {
+ score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
+ }
+ }
+
+ // Initialise the bit arrays.
+ var matchmask = 1 << (pattern.length - 1);
+ best_loc = -1;
+
+ var bin_min, bin_mid;
+ var bin_max = pattern.length + text.length;
+ var last_rd;
+ for (var d = 0; d < pattern.length; d++) {
+ // Scan for the best match; each iteration allows for one more error.
+ // Run a binary search to determine how far from 'loc' we can stray at this
+ // error level.
+ bin_min = 0;
+ bin_mid = bin_max;
+ while (bin_min < bin_mid) {
+ if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) {
+ bin_min = bin_mid;
+ } else {
+ bin_max = bin_mid;
+ }
+ bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);
+ }
+ // Use the result from this iteration as the maximum for the next.
+ bin_max = bin_mid;
+ var start = Math.max(1, loc - bin_mid + 1);
+ var finish = Math.min(loc + bin_mid, text.length) + pattern.length;
+
+ var rd = Array(finish + 2);
+ rd[finish + 1] = (1 << d) - 1;
+ for (var j = finish; j >= start; j--) {
+ // The alphabet (s) is a sparse hash, so the following line generates
+ // warnings.
+ var charMatch = s[text.charAt(j - 1)];
+ if (d === 0) { // First pass: exact match.
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
+ } else { // Subsequent passes: fuzzy match.
+ rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) |
+ (((last_rd[j + 1] | last_rd[j]) << 1) | 1) |
+ last_rd[j + 1];
+ }
+ if (rd[j] & matchmask) {
+ var score = match_bitapScore_(d, j - 1);
+ // This match will almost certainly be better than any existing match.
+ // But check anyway.
+ if (score <= score_threshold) {
+ // Told you so.
+ score_threshold = score;
+ best_loc = j - 1;
+ if (best_loc > loc) {
+ // When passing loc, don't exceed our current distance from loc.
+ start = Math.max(1, 2 * loc - best_loc);
+ } else {
+ // Already passed loc, downhill from here on in.
+ break;
+ }
+ }
+ }
+ }
+ // No hope for a (better) match at greater error levels.
+ if (match_bitapScore_(d + 1, loc) > score_threshold) {
+ break;
+ }
+ last_rd = rd;
+ }
+
+ return (best_loc < 0) ? false : true;
+};
+
+
+/***/ })
+/******/ ]);
\ No newline at end of file
diff --git a/common/php/class-module.php b/common/php/class-module.php
index dff2dd17c..f6d9ec19b 100755
--- a/common/php/class-module.php
+++ b/common/php/class-module.php
@@ -483,6 +483,44 @@ function users_select_form( $selected = null, $args = null ) {
'authors',
+ 'fields' => array(
+ 'ID',
+ ),
+ );
+
+ $usersQuery = new WP_User_Query( array('who' => 'authors', 'fields' => 'ID') );
+ $users_count = $usersQuery->get_total();
+
+ ?>
+
+
+
+
+
+
+
+
+
+ Total users
+
+
+
+
+
+
+
+
+ There was an error. Please reload the page.');
+jQuery( document ).ready( function( $ ) {
+ // $('#ef-post_following_users_box ul').listFilterizer();
+
+ var post_id = $( '#post_ID' ).val();
+
+ // check post_id, only run the following JS if it is in post page
+ if ( post_id !== undefined ) {
+ var params = {
+ action: 'save_notifications',
+ post_id: post_id,
+ };
+
+ $( document ).on( 'click', '.ef-post_following_list li input:checkbox, .ef-following_usergroups li input:checkbox', function() {
+ var user_group_ids = [];
+ var parent_this = $( this );
+ params.ef_notifications_name = $( this ).attr( 'name' );
+ params._nonce = $( '#ef_notifications_nonce' ).val();
+
+ $( this )
+ .parent()
+ .parent()
+ .parent()
+ .find( 'input:checked' )
+ .map( function() {
+ user_group_ids.push( $( this ).val() );
+ } );
+
+ params.user_group_ids = user_group_ids;
+
+ $.ajax( {
+ type: 'POST',
+ url: ( ajaxurl ) ? ajaxurl : wpListL10n.url,
+ data: params,
+ success: function( x ) {
+ // This event is used to show an updated list of who will be notified of editorial comments and status updates.
+ $( '#ef-post_following_box' ).trigger( 'following_list_updated' );
+
+ var backgroundColor = parent_this.css( 'background-color' );
+ $( parent_this.parent().parent() )
+ .animate( { backgroundColor: '#CCEEBB' }, 200 )
+ .animate( { backgroundColor: backgroundColor }, 200 );
+ },
+ error: function( r ) {
+ $( '#ef-post_following_users_box' ).prev().append( '
There was an error. Please reload the page.
' );
+ },
+ } );
+ } );
+
+ // Options for the list
+ var options = {
+ // values used for filters
+ valueNames: [ 'user-item-name', 'user-item-email', {
+ name: 'user_checked',
+ attr: '',
+ }, { data: [ 'user-item-id' ] } ],
+
+ // searchClass is used for filtering values in the list
+ searchClass: 'filter-users',
+
+ // item used for user list template
+ item: '
',
+ };
+
+ // Initialize the list.js, 'users' is the html class name to fill in the users list
+ var userList = new List( 'users', options );
+ var usersPerPage = 10;
+ var totalUsers = 0;
+ var totalUsersCount = $( '#total-users-count' ).val(); //embedded total users in the hidden html
+
+ /**
+ * The function will show paginated users list. Each users page will show a number of users defined by the parameter.
+ * Total users pages will be calculated by dividing totalUsers with usersPerPage. Each users page retrieved using ajax.
+ *
+ * @param {number} totalUsers Total users related to the search keyword
+ * @param {number} usersPerPage Total user shown in a users page
+ * @param {string} searchKeyword The keyword for users to be shown in the page
+ */
+ function fillPaginatedUsersList( totalUsers, usersPerPage, searchKeyword ) {
+ // remove pagination if it existed
+ if ( $( '#users-pagination' ).data( 'twbs-pagination' ) ) {
+ $( '#users-pagination' ).twbsPagination( 'destroy' );
}
- });
- });
-});
\ No newline at end of file
+
+ $( '#users-pagination' ).twbsPagination( {
+ totalPages: Math.ceil( totalUsers / usersPerPage ), // The total number of user pages
+ visiblePages: usersPerPage, // Number of users displayed in a page
+ next: 'Next',
+ prev: 'Prev',
+ onPageClick: function( event, page ) {
+ // clear the users list when the page created
+ userList.clear();
+
+ // Data sent to WP through ajax for paginated users list
+ var data = {
+ action: 'retrieve_users',
+ post_id: $( '#post_ID' ).val(),
+ page: page,
+ users_per_page: usersPerPage,
+ nonce: $( '#ef_notifications_nonce' ).val(),
+ search_keyword: searchKeyword,
+ };
+
+ jQuery.post( ajax_object.ajax_url, data, function( response ) {
+ // Add the users retrieved from wordpress db to list
+ for ( var user of response.users ) {
+ userList.add( user );
+ if ( user.user_checked ) {
+ $( 'li[data-user-item-id=' + user[ 'user-item-id' ] + '] input:checkbox' ).prop( 'checked', true );
+ }
+ }
+
+ // Fill in users count info
+ $( '.users-total-info-value' ).text( totalUsers );
+ if ( searchKeyword !== '' ) {
+ $( '.users-total-info-text' ).text( 'Totals users found' );
+ }
+ } );
+ },
+ } );
+ }
+
+ /**
+ * This will populate users based on a keyword. First it will retrieve the count of users based on the keyword.
+ * The count then will be used as base to calculate pagination related variables in fillPaginatedUsersList
+ *
+ * @param {string} searchKeyword Text based on users for to be shown in the users list. Can contain wildcard.
+ */
+ function fillUsersListByKeyword( searchKeyword ) {
+ // Data sent to WP through ajax for user counts
+ var data_user_count = {
+ action: 'retrieve_users_count_by_keyword',
+ nonce: $( '#ef_notifications_nonce' ).val(),
+ // count_users: true,
+ search_keyword: searchKeyword,
+ };
+
+ jQuery.post( ajax_object.ajax_url, data_user_count, function( response ) {
+ totalUsers = parseInt( response );
+
+ if ( totalUsers > 0 ) {
+ fillPaginatedUsersList( totalUsers, usersPerPage, searchKeyword );
+ } else {
+ $( '#users-pagination' ).twbsPagination( 'destroy' );
+ $( '.users-total-info-text' ).text( 'Totals users found' );
+ $( '.users-total-info-value' ).text( totalUsers );
+ }
+ } );
+ }
+
+ // jQuery bind to search users when pressing Enter key
+ $( '.search-users' ).bind( 'keypress', function( e ) {
+ if ( e.keyCode == 13 ) {
+ clearTimeout( $.data( this, 'timer' ) );
+
+ e.preventDefault();
+ var searchKeyword = $( '.search-users' ).val();
+ userList.clear();
+
+ var wait = setTimeout( fillUsersListByKeyword( searchKeyword ), 10000 );
+
+ $( this ).data( 'timer', wait );
+ }
+ } );
+
+ // jQuery binding search button click
+ $( '.btn-search-users' ).click( function( e ) {
+ clearTimeout( $.data( this, 'timer' ) );
+
+ e.preventDefault();
+ var searchKeyword = $( '.search-users' ).val();
+ userList.clear();
+
+ var wait = setTimeout( fillUsersListByKeyword( searchKeyword ), 10000 );
+
+ $( this ).data( 'timer', wait );
+ } );
+
+ // Ajax for saving checked/unchecked user
+ $( document ).on( 'click', '.user-item', function( e ) {
+ var item_element = $( this );
+ var checkbox = item_element.find( "input[type='checkbox']" );
+
+ // check the checkbox when .user-item element is clicked
+ if ( ! $( e.target ).is( ':checkbox' ) && ! checkbox.is( ':checked' ) ) {
+ checkbox.attr( 'checked', true );
+ } else if ( ( ! $( e.target ).is( ':checkbox' ) && checkbox.is( ':checked' ) ) ) {
+ checkbox.attr( 'checked', false );
+ }
+
+ var data = {
+ action: 'save_user_in_notification',
+ post_id: post_id,
+ nonce: $( '#ef_notifications_nonce' ).val(),
+ user_id: $( this ).data( 'user-item-id' ),
+ };
+
+ // add the user to notification if the checkbox checked or remove if unchecked
+ if ( checkbox.is( ':checked' ) ) {
+ data.follow = true;
+ } else {
+ data.follow = false;
+ }
+
+ jQuery.post( ajaxurl, data )
+ .done( function( response ) {
+ // This event is used to show an updated list of who will be notified of editorial comments and status updates.
+ $( '#ef-post_following_box' ).trigger( 'following_list_updated' );
+
+ // Trigger visual effect when ajax successful
+ var backgroundColor = item_element.parent().css( 'background-color' );
+ item_element
+ .animate( { backgroundColor: '#CCEEBB' }, 200 )
+ .animate( { backgroundColor: backgroundColor }, 200 );
+ } )
+ .fail( function( xhr, status, error ) {
+ $( '#ef-post_following_users_box' ).prev().append( '
There was an error. Please reload the page.
' );
+ } );
+ } );
+
+ // Fill the initial users list on document load
+ fillPaginatedUsersList( totalUsersCount, usersPerPage, '' );
+ }// checks post_id
+} );
diff --git a/modules/notifications/notifications.php b/modules/notifications/notifications.php
index 8bc794590..550f7a868 100644
--- a/modules/notifications/notifications.php
+++ b/modules/notifications/notifications.php
@@ -100,8 +100,14 @@ function init() {
//Ajax for saving notifiction updates
add_action( 'wp_ajax_save_notifications', array( $this, 'ajax_save_post_subscriptions' ) );
add_action( 'wp_ajax_ef_notifications_user_post_subscription', array( $this, 'handle_user_post_subscription' ) );
-
- }
+
+ //Ajax for retrieving users
+ add_action('wp_ajax_retrieve_users', array($this, 'ajax_retrieve_users'));
+ //Ajax to save user notification
+ add_action('wp_ajax_save_user_in_notification', array( $this, 'ajax_save_user_in_notification' ));
+ //Ajax for retrieving total users count by search keyword
+ add_action('wp_ajax_retrieve_users_count_by_keyword', array( $this, 'ajax_retrieve_users_count_by_keyword' ));
+ }
/**
* Load the capabilities onto users the first time the module is run
@@ -190,7 +196,16 @@ function enqueue_admin_scripts() {
if ( $this->is_whitelisted_functional_view() ) {
wp_enqueue_script( 'jquery-listfilterizer' );
wp_enqueue_script( 'jquery-quicksearch' );
- wp_enqueue_script( 'edit-flow-notifications-js', $this->module_url . 'lib/notifications.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true );
+
+ wp_enqueue_script( 'list');
+ wp_enqueue_script( 'jquery-twbsPagination');
+
+ wp_enqueue_script( 'edit-flow-notifications-js', $this->module_url . 'lib/notifications.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch', 'list', 'jquery-twbsPagination' ), EDIT_FLOW_VERSION, true );
+
+
+ wp_localize_script( 'edit-flow-notifications-js', 'ajax_object',
+ array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'ajax_nonce' => wp_create_nonce( "edit-flow-users-list-notifications-ajax" ) )
+ );
}
}
@@ -336,7 +351,8 @@ function notifications_meta_box() {
$select_form_args = array(
'list_class' => 'ef-post_following_list',
);
- $this->users_select_form( $followers, $select_form_args ); ?>
+// $this->users_select_form( $followers, $select_form_args );
+ $this->users_list(); ?>
module_enabled( 'user_groups' ) && in_array( $this->get_current_post_type(), $this->get_post_types_for_module( $edit_flow->user_groups->module ) ) ): ?>
@@ -354,7 +370,130 @@ function notifications_meta_box() {
'authors',
+ 'fields' => array(
+ 'ID',
+ ),
+ 'search' => '*' . $search_keyword .'*',
+ 'search_columns' => array('display_name', 'user_email'),
+ 'count_total' => true
+ );
+
+ $usersQuery = new WP_User_Query( $args );
+ $users_count = $usersQuery->get_total();
+ wp_send_json($users_count);
+
+ }
+
+ /**
+ * Ajax endpoint for retrieving users in notification
+ */
+ function ajax_retrieve_users(){
+
+ check_ajax_referer("save_user_usergroups" , "nonce");
+
+ $post_id = isset( $_POST['post_id'] ) ? intval($_POST['post_id']) : 0;
+ $selected = $this->get_following_users( $post_id, 'id' );
+
+ $search_keyword = isset( $_POST['search_keyword']) ? sanitize_text_field($_POST['search_keyword']) : '';
+
+ $users_per_page = isset( $_POST['users_per_page']) ? intval($_POST['users_per_page']) : 0;
+ $page = isset( $_POST['page']) ? intval($_POST['page']) : 0;
+ $offset = $users_per_page * ($page - 1);
+
+ $args = array(
+ 'number' => $users_per_page,
+ 'offset' => $offset,
+ 'who' => 'authors',
+ 'fields' => array(
+ 'ID',
+ 'display_name',
+ 'user_email'
+ ),
+ 'orderby' => 'display_name',
+ 'search' => '*' . $search_keyword .'*',
+ 'search_columns' => array('display_name', 'user_email'),
+ );
+
+ $users = get_users($args);
+
+ if ( ! is_array($selected)){
+ $selected = array();
+ }
+
+ // Compile users with selected users on top of the list
+ $users_with_selection = array();
+
+ foreach ($users as $user){
+
+ $user_arr['user-item-id'] = $user->ID;
+ $user_arr['user-item-name'] = $user->display_name;
+ $user_arr['user-item-email'] = $user->user_email;
+
+ if ( in_array($user->ID, $selected) ){
+ $user_arr['user_checked'] = true;
+ } else {
+ $user_arr['user_checked'] = false;
+ }
+
+ array_push($users_with_selection, $user_arr);
+ }
+
+ wp_send_json(['users' => $users_with_selection]);
+
+ }
+
+ function ajax_save_user_in_notification(){
+
+ check_ajax_referer('save_user_usergroups', 'nonce');
+
+ $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
+ $post = get_post( $post_id );
+ $user_id = isset( $_POST['user_id']) ? intval( $_POST['user_id'] ) : 0;
+ $follow = isset( $_POST['follow']) ? filter_var($_POST['follow'], FILTER_VALIDATE_BOOLEAN) : false;
+
+ // Add selected user, current user, and author to notification if they are set to receive notifications
+ $users = array();
+ $users[] = $user_id;
+
+ // Add current user to notified users
+ $current_user = wp_get_current_user();
+ if ( $current_user && apply_filters( 'ef_notification_auto_subscribe_current_user', true, 'subscription_action' ) )
+ $users[] = $current_user->ID;
+
+ // Add post author to notified users
+ if ( apply_filters( 'ef_notification_auto_subscribe_post_author', true, 'subscription_action' ) )
+ $users[] = $post->post_author;
+
+ $users = array_unique( array_map( 'intval', $users ) );
+
+ // check if the post used for notification is valid
+ $valid_post = ! is_null( $post ) && ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id );
+ if ( ! $valid_post || ! current_user_can( $this->edit_post_subscriptions_cap ) ) {
+ die();
+ }
+
+ // do follow or unfollow based on checkbox in the frontend
+ if ( $follow ) {
+ $this->follow_post_user( $post, $users, true );
+ } else {
+ $this->unfollow_post_user($post, $user_id);
+ }
+
+ wp_send_json_success('change to notification list is saved');
+ }
+
/**
* Called when a notification editorial metadata checkbox is checked. Handles saving of a user/usergroup to a post.
*/
@@ -475,8 +614,8 @@ function save_post_following_usergroups( $post, $usergroups = null ) {
$usergroups = array_map( 'intval', $usergroups );
$follow = $this->follow_post_usergroups($post, $usergroups, false);
- }
-
+ }
+
/**
* Set up and send post status change notification email
*/
diff --git a/modules/user-groups/lib/user-groups.js b/modules/user-groups/lib/user-groups.js
index 04403ad6a..48e671121 100644
--- a/modules/user-groups/lib/user-groups.js
+++ b/modules/user-groups/lib/user-groups.js
@@ -1,9 +1,182 @@
-jQuery(document).ready(function () {
- jQuery('ul#ef-post_following_users li').quicksearch({
- position: 'before',
- attached: 'ul#ef-post_following_users',
- loaderText: '',
- delay: 100
- })
- jQuery('#ef-usergroup-users ul').listFilterizer();
-});
\ No newline at end of file
+jQuery( document ).ready( function( $ ) {
+ // jQuery('ul#ef-post_following_users li').quicksearch({
+ // position: 'before',
+ // attached: 'ul#ef-post_following_users',
+ // loaderText: '',
+ // delay: 100
+ // })
+ // jQuery('#ef-usergroup-users ul').listFilterizer();
+
+ var usergroup_id = $( '#usergroup_id' ).val();
+
+ if ( usergroup_id !== undefined ) {
+ // Options for the list
+ var options = {
+ // values used for filters
+ valueNames: [ 'user-item-name', 'user-item-email', {
+ name: 'user_checked',
+ attr: '',
+ }, { data: [ 'user-item-id' ] } ],
+
+ // searchClass is used for filtering values in the list
+ searchClass: 'filter-users',
+
+ // item used for user list template
+ item: '
',
+ };
+
+ // Initialize the list.js, 'users' is the html class name to fill in the users list
+ var userList = new List( 'users', options );
+ var usersPerPage = 10;
+ var totalUsers = 0;
+ var totalUsersCount = $( '#total-users-count' ).val(); //embedded total users in the hidden html
+
+ /**
+ * The function will show paginated users list. Each users page will show a number of users defined by the parameter.
+ * Total users pages will be calculated by dividing totalUsers with usersPerPage. Each users page retrieved using ajax.
+ *
+ * @param {number} totalUsers Total users related to the search keyword
+ * @param {number} usersPerPage Total user shown in a users page
+ * @param {string} searchKeyword The keyword for users to be shown in the page
+ */
+ function fillPaginatedUsersList( totalUsers, usersPerPage, searchKeyword ) {
+ // remove pagination if it existed
+ if ( $( '#users-pagination' ).data( 'twbs-pagination' ) ) {
+ $( '#users-pagination' ).twbsPagination( 'destroy' );
+ }
+
+ $( '#users-pagination' ).twbsPagination( {
+ totalPages: Math.ceil( totalUsers / usersPerPage ), // The total number of user pages
+ visiblePages: usersPerPage, // Number of users displayed in a page
+ next: 'Next',
+ prev: 'Prev',
+ onPageClick: function( event, page ) {
+ // clear the users list when the page created
+ userList.clear();
+
+ // Data sent to WP through ajax for paginated users list
+ var data = {
+ action: 'retrieve_users_in_usergroup',
+ usergroup_id: usergroup_id,
+ page: page,
+ users_per_page: usersPerPage,
+ nonce: ajax_object.ajax_nonce,
+ search_keyword: searchKeyword,
+ };
+
+ jQuery.post( ajax_object.ajax_url, data, function( response ) {
+ // Add the users retrieved from wordpress db to list
+ for ( var user of response.users ) {
+ userList.add( user );
+ if ( user.user_checked ) {
+ $( 'li[data-user-item-id=' + user[ 'user-item-id' ] + '] input:checkbox' ).prop( 'checked', true );
+ }
+ }
+
+ // Fill in users count info
+ $( '.users-total-info-value' ).text( totalUsers );
+ if ( searchKeyword !== '' ) {
+ $( '.users-total-info-text' ).text( 'Totals users found' );
+ }
+ } );
+ },
+ } );
+ }
+
+ /**
+ * This will populate users based on a keyword. First it will retrieve the count of users based on the keyword.
+ * The count then will be used as base to calculate pagination related variables in fillPaginatedUsersList
+ *
+ * @param {string} searchKeyword Text based on users for to be shown in the users list. Can contain wildcard.
+ */
+ function fillUsersListByKeyword( searchKeyword ) {
+ // Data sent to WP through ajax for user counts
+ var data_user_count = {
+ action: 'retrieve_users_count_in_usergroup_by_keyword',
+ nonce: ajax_object.ajax_nonce,
+ search_keyword: searchKeyword,
+ };
+
+ jQuery.post( ajax_object.ajax_url, data_user_count, function( response ) {
+ totalUsers = parseInt( response );
+
+ if ( totalUsers > 0 ) {
+ fillPaginatedUsersList( totalUsers, usersPerPage, searchKeyword );
+ } else {
+ $( '#users-pagination' ).twbsPagination( 'destroy' );
+ $( '.users-total-info-text' ).text( 'Totals users found' );
+ $( '.users-total-info-value' ).text( totalUsers );
+ }
+ } );
+ }
+
+ // jQuery bind to search users when pressing Enter key
+ $( '.search-users' ).bind( 'keypress', function( e ) {
+ if ( e.keyCode == 13 ) {
+ clearTimeout( $.data( this, 'timer' ) );
+
+ e.preventDefault();
+ var searchKeyword = $( '.search-users' ).val();
+ userList.clear();
+
+ var wait = setTimeout( fillUsersListByKeyword( searchKeyword ), 10000 );
+
+ $( this ).data( 'timer', wait );
+ }
+ } );
+
+ // jQuery binding search button click
+ $( '.btn-search-users' ).click( function( e ) {
+ clearTimeout( $.data( this, 'timer' ) );
+
+ e.preventDefault();
+ var searchKeyword = $( '.search-users' ).val();
+ userList.clear();
+
+ var wait = setTimeout( fillUsersListByKeyword( searchKeyword ), 10000 );
+
+ $( this ).data( 'timer', wait );
+ } );
+
+ // Ajax for saving checked/unchecked user
+ $( document ).on( 'click', '.user-item', function( e ) {
+ var item_element = $( this );
+ var checkbox = item_element.find( "input[type='checkbox']" );
+
+ // check the checkbox when .user-item element is clicked
+ if ( ! $( e.target ).is( ':checkbox' ) && ! checkbox.is( ':checked' ) ) {
+ checkbox.attr( 'checked', true );
+ } else if ( ( ! $( e.target ).is( ':checkbox' ) && checkbox.is( ':checked' ) ) ) {
+ checkbox.attr( 'checked', false );
+ }
+
+ var params = {
+ action: 'save_user_to_usergroup',
+ usergroup_id: usergroup_id,
+ nonce: ajax_object.ajax_nonce,
+ user_id: ( $( this ).data( 'user-item-id' ) ),
+ };
+
+ if ( checkbox.is( ':checked' ) ) {
+ params.add = true;
+ } else {
+ params.remove = true;
+ }
+
+ jQuery.post( ajaxurl, params )
+ .done( function( response ) {
+ // Trigger visual effect when ajax successful
+ var backgroundColor = item_element.parent().css( 'background-color' );
+ item_element
+ .animate( { backgroundColor: '#CCEEBB' }, 200 )
+ .animate( { backgroundColor: backgroundColor }, 200 );
+ } )
+ .fail( function( xhr, status, error ) {
+ $( '#ef-post_following_users_box' ).prev().append( '
There was an error. Please reload the page.
' );
+ } );
+ } );
+
+ // Fill the initial users list on document load
+ fillPaginatedUsersList( totalUsersCount, usersPerPage, '' );
+ } // check on usergroup
+} );
diff --git a/modules/user-groups/user-groups.php b/modules/user-groups/user-groups.php
index e85fa9ff6..45438e7ac 100644
--- a/modules/user-groups/user-groups.php
+++ b/modules/user-groups/user-groups.php
@@ -101,8 +101,16 @@ function init() {
// Javascript and CSS if we need it
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
-
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
+
+ // Ajax for retrieving users
+ add_action( 'wp_ajax_retrieve_users_in_usergroup', array( $this, 'ajax_retrieve_users_in_usergroups'));
+
+ // Ajax for saving user to usergroup
+ add_action( 'wp_ajax_save_user_to_usergroup', array( $this, 'ajax_save_user_to_usergroup' ) );
+ //Ajax for retrieving total users count by search keyword
+ add_action( 'wp_ajax_retrieve_users_count_in_usergroup_by_keyword', array( $this, 'ajax_retrieve_users_count_in_usergroup_by_keyword' ));
+
}
/**
@@ -231,7 +239,18 @@ function enqueue_admin_scripts() {
if ( $this->is_whitelisted_functional_view() || $this->is_whitelisted_settings_view( $this->module->name ) ) {
wp_enqueue_script( 'jquery-listfilterizer' );
wp_enqueue_script( 'jquery-quicksearch' );
- wp_enqueue_script( 'edit-flow-user-groups-js', $this->module_url . 'lib/user-groups.js', array( 'jquery', 'jquery-listfilterizer', 'jquery-quicksearch' ), EDIT_FLOW_VERSION, true );
+
+ wp_enqueue_script( 'list');
+ wp_enqueue_script( 'jquery-twbsPagination');
+
+
+ wp_enqueue_script( 'edit-flow-user-groups-js', $this->module_url . 'lib/user-groups.js', array( 'jquery', 'jquery-color', 'jquery-listfilterizer', 'jquery-quicksearch', 'list', 'jquery-twbsPagination' ), EDIT_FLOW_VERSION, true );
+
+ wp_localize_script( 'edit-flow-user-groups-js', 'ajax_object',
+ array( 'ajax_url' => admin_url( 'admin-ajax.php' ),
+ 'ajax_nonce' => wp_create_nonce( "edit-flow-users-list-usergroups-ajax" )
+ )
+ );
}
if ( $this->is_whitelisted_settings_view( $this->module->name ) )
@@ -384,9 +403,11 @@ function handle_edit_usergroup() {
'description' => $description,
);
// Gracefully handle the case where all users have been unsubscribed from the user group
- $users = isset( $_POST['usergroup_users'] ) ? (array)$_POST['usergroup_users'] : array();
- $users = array_map( 'intval', $users );
- $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args, $users );
+// $users = isset( $_POST['usergroup_users'] ) ? (array)$_POST['usergroup_users'] : array();
+// $users = array_map( 'intval', $users );
+// $users = null;
+// $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args, $users );
+ $usergroup = $this->update_usergroup( $existing_usergroup->term_id, $args );
if ( is_wp_error( $usergroup ) )
wp_die( __( 'Error updating user group.', 'edit-flow' ) );
@@ -559,11 +580,12 @@ function print_configure_view() {
'input_id' => 'usergroup_users'
);
?>
- users_select_form( $usergroup->user_ids , $select_form_args ); ?>
+ users_list(); ?>
+ users_select_form( $usergroup->user_ids , $select_form_args ); ?>