Changeset 2700272
- Timestamp:
- 03/27/2022 05:11:35 PM (4 years ago)
- Location:
- e20r-members-list
- Files:
-
- 4 added
- 2 deleted
- 32 edited
- 1 copied
-
tags/8.6 (copied) (copied from e20r-members-list/trunk)
-
tags/8.6/.am_on_github (deleted)
-
tags/8.6/.codecov.yml (added)
-
tags/8.6/CHANGELOG.md (modified) (1 diff)
-
tags/8.6/README.md (modified) (2 diffs)
-
tags/8.6/README.txt (modified) (2 diffs)
-
tags/8.6/class-e20r-members-list.php (modified) (11 diffs)
-
tags/8.6/docs/FILTERS.md (modified) (1 diff)
-
tags/8.6/inc/autoload.php (modified) (1 diff)
-
tags/8.6/inc/composer/autoload_real.php (modified) (5 diffs)
-
tags/8.6/inc/composer/autoload_static.php (modified) (2 diffs)
-
tags/8.6/languages/e20r-members-list.pot (modified) (6 diffs)
-
tags/8.6/src/E20R/members-list/Members_List.php (modified) (32 diffs)
-
tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Cancel.php (modified) (7 diffs)
-
tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Operations.php (modified) (6 diffs)
-
tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Update.php (modified) (4 diffs)
-
tags/8.6/src/E20R/members-list/admin/exceptions/InvalidMemberList.php (added)
-
tags/8.6/src/E20R/members-list/admin/export/Export_Members.php (modified) (2 diffs)
-
tags/8.6/src/E20R/members-list/js/e20r-memberslist-page.js (modified) (3 diffs)
-
tags/8.6/src/E20R/members-list/modules/Multiple_Memberships.php (modified) (4 diffs)
-
trunk/.am_on_github (deleted)
-
trunk/.codecov.yml (added)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/README.md (modified) (2 diffs)
-
trunk/README.txt (modified) (2 diffs)
-
trunk/class-e20r-members-list.php (modified) (11 diffs)
-
trunk/docs/FILTERS.md (modified) (1 diff)
-
trunk/inc/autoload.php (modified) (1 diff)
-
trunk/inc/composer/autoload_real.php (modified) (5 diffs)
-
trunk/inc/composer/autoload_static.php (modified) (2 diffs)
-
trunk/languages/e20r-members-list.pot (modified) (6 diffs)
-
trunk/src/E20R/members-list/Members_List.php (modified) (32 diffs)
-
trunk/src/E20R/members-list/admin/bulk/Bulk_Cancel.php (modified) (7 diffs)
-
trunk/src/E20R/members-list/admin/bulk/Bulk_Operations.php (modified) (6 diffs)
-
trunk/src/E20R/members-list/admin/bulk/Bulk_Update.php (modified) (4 diffs)
-
trunk/src/E20R/members-list/admin/exceptions/InvalidMemberList.php (added)
-
trunk/src/E20R/members-list/admin/export/Export_Members.php (modified) (2 diffs)
-
trunk/src/E20R/members-list/js/e20r-memberslist-page.js (modified) (3 diffs)
-
trunk/src/E20R/members-list/modules/Multiple_Memberships.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
e20r-members-list/tags/8.6/CHANGELOG.md
r2672951 r2700272 6 6 7 7 ## [Unreleased] 8 9 ## v8.6 - 2022-03-27 10 - BUG FIX: Revert codecov upload step (Eighty/20Results Bot on Github) 11 - BUG FIX: Set permissions for coverage output directory (Eighty/20Results Bot on Github) 12 - BUG FIX: More debug output (Eighty/20Results Bot on Github) 13 - BUG FIX: Typo in Makefile (Eighty/20Results Bot on Github) 14 - BUG FIX: Adding more debug text to makefile (Eighty/20Results Bot on Github) 15 - BUG FIX: Adding debug text to makefile (Eighty/20Results Bot on Github) 16 - BUG FIX: Adding PHP 8.1 to test matrix (Eighty/20Results Bot on Github) 17 - BUG FIX: Integration tests weren't doing much (Eighty/20Results Bot on Github) 18 - BUG FIX: Add PHP 8.1 to test matrix and revert code coverage upload (Eighty/20Results Bot on Github) 19 - BUG FIX: Reverted codecov upload step and adding php 8.1 to test matrix (Eighty/20Results Bot on Github) 20 - BUG FIX: Be explicit about the file to load in action (Eighty/20Results Bot on Github) 21 - BUG FIX: Different config for codecov-action (Eighty/20Results Bot on Github) 22 - BUG FIX: Try to fix setup for codecov/codecov-action (Eighty/20Results Bot on Github) 23 - BUG FIX: Update codecov configuration and rename the config file (Eighty/20Results Bot on Github) 24 - BUG FIX: Revert removal of PMPro dependency for PHPStan tests (Eighty/20Results Bot on Github) 25 - BUG FIX: Attempting to enable code coverage uploads (Eighty/20Results Bot on Github) 26 - BUG FIX: Adding the codecov.yml config file (Eighty/20Results Bot on Github) 27 - BUG FIX: PHPDoc entry for failed class variable was wrong (Eighty/20Results Bot on Github) 28 - BUG FIX: More unit test coverage for the bulk operations including fix for members_to_process check (Eighty/20Results Bot on Github) 29 - BUG FIX: Checksum for composer wasn't a make variable (Eighty/20Results Bot on Github) 30 - BUG FIX: Exception for when the array of users/levels to perform bulk operations is incorrect (Eighty/20Results Bot on Github) 31 - BUG FIX: Make coverage a local only thing (for now?) (Eighty/20Results Bot on Github) 32 - BUG FIX: Don't create unneeded coverage subdirectories (Eighty/20Results Bot on Github) 33 - BUG FIX: Fix workflow config (Eighty/20Results Bot on Github) 34 - BUG FIX: Use remote coverage service (Eighty/20Results Bot on Github) 35 - BUG FIX: Problem with coverage artifacts (Eighty/20Results Bot on Github) 36 - BUG FIX: Bumped version number to 8.6 (Eighty/20Results Bot on Github) 37 - BUG FIX: Clean up message in get_members() -> InvalidSQL exception (Eighty/20Results Bot on Github) 38 - BUG FIX: Not re-running workflow when PR is updated/edited (Eighty/20Results Bot on Github) 39 - BUG FIX: Add support for Brain\faker() mocking for WP and the ext-mysqli extension for integration testing purposes (Eighty/20Results Bot on Github) 40 - BUG FIX: Transition to mocking PMPro functions for Integration tests (probably moving actual PMPro integration to acceptance testing) (Eighty/20Results Bot on Github) 41 - BUG FIX: Make sure Integration/Acceptance test container uses PHP v7.4 (Eighty/20Results Bot on Github) 42 - BUG FIX: Added PHP8/Paid Memberships Pro incompatibility as a 'Known Issue' (Eighty/20Results Bot on Github) 43 - BUG FIX: Updated tags for Wodby docker4wordpress (Eighty/20Results Bot on Github) 44 - BUG FIX: Wrong number of placeholders and data in sprintf() (Eighty/20Results Bot on Github) 45 - BUG FIX: Clean up Known Issues text (Eighty/20Results Bot on Github) 46 - BUG FIX: Refactored option HTML and changed to using gmdate() (Eighty/20Results Bot on Github) 47 - BUG FIX: Adding support for listing multiple memberships (still have TODOs, not releasing yet) (Eighty/20Results Bot on Github) 48 - BUG FIX: Added a known issue (Eighty/20Results Bot on Github) 49 - BUG FIX: Cleaned up namespace path (Eighty/20Results Bot on Github) 50 - BUG FIX: No longer using singleton pattern in E20R_Members_List() class and fixed PHPCS warnings (Eighty/20Results Bot on Github) 51 - BUG FIX: Null value disallowed for str_replace() (Eighty/20Results Bot on Github) 52 - BUG FIX: Bulk-exported all users, not just the selected ones and fixed PHP Deprecated messages for bulk action checks (Eighty/20Results Bot on Github) 53 - BUG FIX: Adding tests with data for column_last() method (Eighty/20Results Bot on Github) 54 - BUG FIX: Fix column_last() to avoid PHP Warning messages and refactor, make the definition of an 'empty' enddate filtered and add helper methods. Added exceptions and handlers (Eighty/20Results Bot on Github) 55 - BUG FIX: Add e20r_members_list_empty_date_values filter documentation (Eighty/20Results Bot on Github) 56 - BUG FIX: Cleanup patchwork.json (Eighty/20Results Bot on Github) 57 - BUG FIX: Variables rely on tests/_env/.env.testing (Eighty/20Results Bot on Github) 58 - BUG FIX: Run install for WP and set only DEBUG variable (Eighty/20Results Bot on Github) 59 - BUG FIX: Make sure we have the needed database tables for testing (Eighty/20Results Bot on Github) 60 - BUG FIX: Actual path to source files for e20r utilities module is src/E20R/... (Eighty/20Results Bot on Github) 61 - BUG FIX: PMPro doesn't check if constants are defined before use triggering test failures for us (Eighty/20Results Bot on Github) 62 - BUG FIX: Load fixtures to load/clear the DB (Eighty/20Results Bot on Github) 63 - BUG FIX: Lacked DB records for PMPro and User in integration tests (Eighty/20Results Bot on Github) 64 - BUG FIX: Wrong path to test specific _bootstrap.php file (Eighty/20Results Bot on Github) 65 - BUG FIX: Didn't load test specific _bootstrap.php files (Eighty/20Results Bot on Github) 66 - BUG FIX: Initial commit for column_last integration test (Eighty/20Results Bot on Github) 67 - BUG FIX: Add WP_DEBUG logging for test container(s) (Eighty/20Results Bot on Github) 68 - BUG FIX: Use E20R_PLUGIN_NAME env variable to set PROJECT_NAME variable (with default value set to plugin slug) (Eighty/20Results Bot on Github) 69 - BUG FIX: Upgraded wodby container environments (Eighty/20Results Bot on Github) 70 - BUG FIX: Fixed install-hooks for pre-commit git hook (Eighty/20Results Bot on Github) 71 - BUG FIX: Path to documentation (Eighty/20Results Bot on Github) 72 - BUG FIX: Make sure we strip away the test file (Eighty/20Results Bot on Github) 8 73 9 74 ## v8.5 - 2022-02-04 -
e20r-members-list/tags/8.6/README.md
r2672951 r2700272 3 3 `Tags: paid memberships pro, members, memberships, pmpro enhancements, better members list, members list, addon` <br /> 4 4 `Requires at least: 4.9` <br /> 5 `Tested up to: 5.9 ` <br />5 `Tested up to: 5.9.2` <br /> 6 6 `Requires PHP: 7.1` <br /> 7 `Stable tag: 8. 5` <br />7 `Stable tag: 8.6` <br /> 8 8 `License: GPLv2` <br /> 9 9 `License URI: http://www.gnu.org/licenses/gpl` <br /> … … 36 36 See [ACTIONS.md](https://github.com/eighty20results.com/e20r-members-list/blob/main/docs/ACTIONS.md) 37 37 38 38 39 ### Known Issues 39 No known issues at this time 40 PHP 8.0 and later introduces warning messages for certain behaviors that were ignored prior to v8.0. Because of this, and the fact that this plugin relies on functionality from Paid Memberships Pro, the "end date" column may print messages indicating problems with the `trim()` function. Until Paid Memberships Pro updates their plugin to support PHP8.x, these messages will need to be disabled in your web server configuration (suppressed). 41 42 Setting the "Members per page" in the "Options" drop-down on the Members List page to a number greater than 50 can result in unexpected errors/warnings. The default value is 20. One symptom is seeing the PHP warning: "Warning: Unknown: Input variables exceeded 2000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0" 40 43 41 44 ### Changelog -
e20r-members-list/tags/8.6/README.txt
r2672951 r2700272 3 3 Tags: paid memberships pro, members, memberships, pmpro enhancements, better members list, members list, addon 4 4 Requires at least: 4.9 5 Tested up to: 5.9 5 Tested up to: 5.9.2 6 6 Requires PHP: 7.1 7 Stable tag: 8. 57 Stable tag: 8.6 8 8 License: GPLv2 9 9 License URI: http://www.gnu.org/licenses/gpl … … 38 38 39 39 == Known Issues == 40 No known issues at this time 40 PHP 8.0 and later introduces warning messages for certain behaviors that were ignored prior to v8.0. Because of this, and the fact that this plugin relies on functionality from Paid Memberships Pro, the "end date" column may print messages indicating problems with the `trim()` function. Until Paid Memberships Pro updates their plugin to support PHP8.x, these messages will need to be disabled in your web server configuration (suppressed). 41 42 Setting the "Members per page" in the "Options" drop-down on the Members List page to a number greater than 50 can result in unexpected errors/warnings. The default value is 20. One symptom is seeing the PHP warning: "Warning: Unknown: Input variables exceeded 2000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0" 41 43 42 44 == Changelog == 43 45 See the official [CHANGELOG.md](https://github.com/eighty20results.com/e20r-members-list/blob/main/CHANGELOG.md) file 44 -
e20r-members-list/tags/8.6/class-e20r-members-list.php
r2672951 r2700272 4 4 Plugin URI: https://wordpress.org/plugins/e20r-members-list 5 5 Description: Extensible, sortable & bulk action capable members listing + export to CSV tool for Paid Memberships Pro. 6 Version: 8. 56 Version: 8.6 7 7 Author: Thomas Sjolshagen @ Eighty / 20 Results by Wicked Strong Chicks, LLC <[email protected]> 8 8 Author URI: https://eighty20results.com/thomas-sjolshagen/ … … 39 39 use E20R\Metrics\Exceptions\MissingDependencies; 40 40 use E20R\Metrics\MixpanelConnector; 41 use E20R\Utilities\ActivateUtilitiesPlugin; 41 42 use E20R\Utilities\Cache; 42 43 use E20R\Utilities\Utilities; … … 52 53 53 54 if ( ! defined( 'E20R_MEMBERSLIST_VER' ) ) { 54 define( 'E20R_MEMBERSLIST_VER', '8. 5' );55 define( 'E20R_MEMBERSLIST_VER', '8.6' ); 55 56 } 56 57 … … 62 63 63 64 /** 64 * Instance of the Member List controller65 *66 * @var null|E20R_Members_List67 */68 private static $instance = null;69 70 /**71 65 * The E20R Utilities Module class instance 72 66 * … … 98 92 */ 99 93 public function __construct( $ml_page = null, $utils = null, $mixpanel = null ) { 100 self::$instance = $this;101 94 102 95 if ( empty( $utils ) ) { … … 139 132 * @throws InvalidSettingsKey Raised when an invalid class property is specified for the get() method 140 133 */ 141 public function get( $property = ' instance' ) {134 public function get( $property = 'utils' ) { 142 135 143 136 if ( ! property_exists( $this, $property ) ) { … … 152 145 return $this->{$property}; 153 146 } 154 /** 155 * Get or instantiate and get the current class 156 * 157 * @return E20R_Members_List|null 158 * 159 * @test E20R_Members_ListTest::test_get_instance() 160 */ 161 public static function get_instance() { 162 163 if ( is_null( self::$instance ) ) { 164 self::$instance = new self(); 165 } 166 167 return self::$instance; 147 148 /** 149 * Fetch the Page object 150 * 151 * @return Members_List_Page|null 152 */ 153 public function get_page() { 154 return $this->page; 168 155 } 169 156 … … 181 168 public function load_hooks( $utils = null ) { 182 169 183 if ( ! method_exists( '\ \E20R\\Utilities\\Utilities', 'get_instance' ) ) {170 if ( ! method_exists( '\E20R\Utilities\Utilities', 'get_instance' ) ) { 184 171 $msg = esc_attr__( 'The E20R Utilities Module is missing/inactive!', 'e20r-members-list' ); 185 172 throw new MissingUtilitiesModule( $msg ); … … 354 341 'filters' => sprintf( 355 342 '<a href="%1$s" title="%2$s">%3$s</a>', 356 esc_url_raw( plugin_dir_url( __FILE__ ) . ' ../docs/FILTERS.md' ),343 esc_url_raw( plugin_dir_url( __FILE__ ) . '/docs/FILTERS.md' ), 357 344 esc_attr__( 'View the Filter documentation', 'e20r-members-list' ), 358 345 esc_attr__( 'Filters', 'e20r-members-list' ) … … 360 347 'actions' => sprintf( 361 348 '<a href="%1$s" title="%2$s">%3$s</a>', 362 esc_url_raw( plugin_dir_url( __FILE__ ) . ' ../docs/ACTIONS.md' ),349 esc_url_raw( plugin_dir_url( __FILE__ ) . '/docs/ACTIONS.md' ), 363 350 esc_attr__( 'View the Actions documentation', 'e20r-members-list' ), 364 351 esc_attr__( 'Actions', 'e20r-members-list' ) … … 395 382 $required_plugin = 'Better Members List for Paid Memberships Pro'; 396 383 397 if ( false === \E20R\Utilities\ActivateUtilitiesPlugin::attempt_activation() ) {384 if ( false === ActivateUtilitiesPlugin::attempt_activation() ) { 398 385 add_action( 399 386 'admin_notices', 400 387 function () use ( $required_plugin ) { 401 \E20R\Utilities\ActivateUtilitiesPlugin::plugin_not_installed( $required_plugin );388 ActivateUtilitiesPlugin::plugin_not_installed( $required_plugin ); 402 389 } 403 390 ); -
e20r-members-list/tags/8.6/docs/FILTERS.md
r2672951 r2700272 250 250 ); 251 251 ``` 252 253 ### e20r_members_list_empty_date_values 254 255 Modifies: Array of the date values that we (and PMPro) consider to be the equivalent of an 'empty' (not configured) date value 256 257 Purpose: To modify or update the list of date values we'll think of as 'empty'. Anything goes here. Including integer values representing seconds since epoch start. 258 259 Default: 260 ```php 261 array( 262 '', 263 null, 264 0, 265 '0', 266 '0000-00-00 00:00:00', 267 '0000-00-00', 268 '00:00:00', 269 ); 270 ``` 271 272 Dependencies: N/A 273 274 Example: 275 ```php 276 add_filter( 277 'e20r_members_list_empty_date_values', 278 function( $values ) { 279 // Also include DD-MM-YYYY at midnight as a valid 'empty' value 280 return $values + array( 281 '00-00-0000 00:00:00', 282 '00-00-00 00:00:00', 283 ); 284 }, 285 10, 286 1 287 ); 288 ``` -
e20r-members-list/tags/8.6/inc/autoload.php
r2672951 r2700272 5 5 require_once __DIR__ . '/composer/autoload_real.php'; 6 6 7 return ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d::getLoader();7 return ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829::getLoader(); -
e20r-members-list/tags/8.6/inc/composer/autoload_real.php
r2672951 r2700272 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d5 class ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); 29 spl_autoload_unregister(array('ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829', 'loadClassLoader')); 30 30 31 31 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); … … 33 33 require __DIR__ . '/autoload_static.php'; 34 34 35 call_user_func(\Composer\Autoload\ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::getInitializer($loader));35 call_user_func(\Composer\Autoload\ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::getInitializer($loader)); 36 36 } else { 37 37 $map = require __DIR__ . '/autoload_namespaces.php'; … … 54 54 55 55 if ($useStaticLoader) { 56 $includeFiles = Composer\Autoload\ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$files;56 $includeFiles = Composer\Autoload\ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$files; 57 57 } else { 58 58 $includeFiles = require __DIR__ . '/autoload_files.php'; 59 59 } 60 60 foreach ($includeFiles as $fileIdentifier => $file) { 61 composerRequire 40868d3a6d59e2d6945ded47ea627a6d($fileIdentifier, $file);61 composerRequire8968a46d1d5402e6dbb4601beede8829($fileIdentifier, $file); 62 62 } 63 63 … … 71 71 * @return void 72 72 */ 73 function composerRequire 40868d3a6d59e2d6945ded47ea627a6d($fileIdentifier, $file)73 function composerRequire8968a46d1d5402e6dbb4601beede8829($fileIdentifier, $file) 74 74 { 75 75 if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { -
e20r-members-list/tags/8.6/inc/composer/autoload_static.php
r2672951 r2700272 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d7 class ComposerStaticInit8968a46d1d5402e6dbb4601beede8829 8 8 { 9 9 public static $files = array ( … … 91 91 { 92 92 return \Closure::bind(function () use ($loader) { 93 $loader->prefixLengthsPsr4 = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$prefixLengthsPsr4;94 $loader->prefixDirsPsr4 = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$prefixDirsPsr4;95 $loader->classMap = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$classMap;93 $loader->prefixLengthsPsr4 = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$prefixLengthsPsr4; 94 $loader->prefixDirsPsr4 = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$prefixDirsPsr4; 95 $loader->classMap = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$classMap; 96 96 97 97 }, null, ClassLoader::class); -
e20r-members-list/tags/8.6/languages/e20r-members-list.pot
r2672951 r2700272 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Better Members List for Paid Memberships Pro 8. 5\n"5 "Project-Id-Version: Better Members List for Paid Memberships Pro 8.6\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/e20r-members-list\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2022-0 2-04T13:27:08+00:00\n"12 "POT-Creation-Date: 2022-03-27T16:56:55+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.6.0\n" … … 35 35 msgstr "" 36 36 37 #: class-e20r-members-list.php:1 4537 #: class-e20r-members-list.php:138 38 38 msgid "The specified E20R_Members_List() class property does not exist!" 39 39 msgstr "" 40 40 41 #: class-e20r-members-list.php:1 8441 #: class-e20r-members-list.php:171 42 42 msgid "The E20R Utilities Module is missing/inactive!" 43 43 msgstr "" 44 44 45 #: class-e20r-members-list.php:2 1545 #: class-e20r-members-list.php:202 46 46 msgid "Could not clear all of the cached members list data. Check error logs for more information." 47 47 msgstr "" 48 48 49 #: class-e20r-members-list.php:3 4249 #: class-e20r-members-list.php:329 50 50 msgid "Donate to support updates, maintenance and tech support for this plugin" 51 51 msgstr "" 52 52 53 #: class-e20r-members-list.php:3 4653 #: class-e20r-members-list.php:333 54 54 msgid "Donate" 55 55 msgstr "" 56 56 57 #: class-e20r-members-list.php:338 58 msgid "View the documentation" 59 msgstr "" 60 61 #: class-e20r-members-list.php:339 62 msgid "Docs" 63 msgstr "" 64 65 #: class-e20r-members-list.php:344 66 msgid "View the Filter documentation" 67 msgstr "" 68 69 #: class-e20r-members-list.php:345 70 msgid "Filters" 71 msgstr "" 72 73 #: class-e20r-members-list.php:350 74 msgid "View the Actions documentation" 75 msgstr "" 76 57 77 #: class-e20r-members-list.php:351 58 msgid " View the documentation"59 msgstr "" 60 61 #: class-e20r-members-list.php:35 262 msgid " Docs"78 msgid "Actions" 79 msgstr "" 80 81 #: class-e20r-members-list.php:356 82 msgid "Visit the support forum" 63 83 msgstr "" 64 84 65 85 #: class-e20r-members-list.php:357 66 msgid " View the Filter documentation"67 msgstr "" 68 69 #: class-e20r-members-list.php:3 5870 msgid " Filters"86 msgid "Support" 87 msgstr "" 88 89 #: class-e20r-members-list.php:362 90 msgid "Report issues with this plugin" 71 91 msgstr "" 72 92 73 93 #: class-e20r-members-list.php:363 74 msgid "View the Actions documentation"75 msgstr ""76 77 #: class-e20r-members-list.php:36478 msgid "Actions"79 msgstr ""80 81 #: class-e20r-members-list.php:36982 msgid "Visit the support forum"83 msgstr ""84 85 #: class-e20r-members-list.php:37086 msgid "Support"87 msgstr ""88 89 #: class-e20r-members-list.php:37590 msgid "Report issues with this plugin"91 msgstr ""92 93 #: class-e20r-members-list.php:37694 94 msgid "Report Issues" 95 95 msgstr "" … … 101 101 102 102 #: src/E20R/members-list/admin/bulk/Bulk_Cancel.php:145 103 msgid "Cannot find the pmpro_cancelMembershipLevel() function!" 103 msgid "The pmpro_cancelMembershipLevel() function is not defined. Is Paid Memberships Pro activated on this site?" 104 msgstr "" 105 106 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:106 107 msgid "The specified variable is not an array of user IDs" 104 108 msgstr "" 105 109 106 110 #. translators: %1$s - The parameter supplied, %2$s - The class name where we expect the supplied parameter to exist, 107 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php: 98111 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:120 108 112 msgid "Invalid parameter \"%1$s\" supplied for %2$s" 109 113 msgstr "" 110 114 111 115 #. translators: %1$s - The class name where we expect the supplied parameter to exist. 112 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:1 23116 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:145 113 117 msgid "Invalid parameter supplied for %1$s" 114 118 msgstr "" … … 154 158 155 159 #: src/E20R/members-list/admin/bulk/Bulk_Update.php:408 160 #: src/E20R/members-list/Members_List.php:2163 161 #: src/E20R/members-list/Members_List.php:2177 156 162 msgid "Unknown" 157 163 msgstr "" 158 164 159 #: src/E20R/members-list/admin/export/Export_Members.php:100 5165 #: src/E20R/members-list/admin/export/Export_Members.php:1006 160 166 msgid "Error - Undefined HTTP headers. Exiting!" 161 167 msgstr "" 162 168 163 #: src/E20R/members-list/admin/export/Export_Members.php:101 4169 #: src/E20R/members-list/admin/export/Export_Members.php:1015 164 170 msgid "Cannot transmit export file. Review web server error logs for notices/warnings/errors. Exiting!" 165 171 msgstr "" 166 172 167 #: src/E20R/members-list/admin/export/Export_Members.php:102 6173 #: src/E20R/members-list/admin/export/Export_Members.php:1027 168 174 msgid "Error: No export data found to transmit..." 169 175 msgstr "" … … 251 257 msgstr "" 252 258 253 #: src/E20R/members-list/Members_List.php:3 00259 #: src/E20R/members-list/Members_List.php:317 254 260 msgid "member" 255 261 msgstr "" 256 262 257 #: src/E20R/members-list/Members_List.php:3 01263 #: src/E20R/members-list/Members_List.php:318 258 264 msgid "members" 259 265 msgstr "" 260 266 261 #: src/E20R/members-list/Members_List.php:4 07267 #: src/E20R/members-list/Members_List.php:446 262 268 msgid "Error: Invalid list of tables & joins for member list!" 263 269 msgstr "" 264 270 265 #: src/E20R/members-list/Members_List.php:4 15271 #: src/E20R/members-list/Members_List.php:454 266 272 msgid "Error: No FROM table specified for member list!" 267 273 msgstr "" 268 274 269 275 #. translators: %1$s is the per-page value supplied by the e20r_memberslist_per_page filter. 270 #: src/E20R/members-list/Members_List.php: 568276 #: src/E20R/members-list/Members_List.php:607 271 277 msgid "Error: Invalid 'per_page' value (need an integer): %1$s" 272 278 msgstr "" 273 279 274 280 #. translators: %1$s - The error message returned by the exception thrown 275 #: src/E20R/members-list/Members_List.php:7 10281 #: src/E20R/members-list/Members_List.php:749 276 282 msgid "Cannot create a valid Database Query. Error message: %1$s" 277 283 msgstr "" 278 284 279 285 #. translators: %1$s - The error message returned by the exception thrown 280 #: src/E20R/members-list/Members_List.php:7 35286 #: src/E20R/members-list/Members_List.php:774 281 287 msgid "Cannot execute database query. Error message: %1$s" 282 288 msgstr "" 283 289 284 #: src/E20R/members-list/Members_List.php:10 09290 #: src/E20R/members-list/Members_List.php:1051 285 291 msgid "Insecure action denied." 286 292 msgstr "" 287 293 288 #. translators: Error message from the execption thrown289 #: src/E20R/members-list/Members_List.php:10 48290 msgid " Cannot export data!. Error message: %1$s"291 msgstr "" 292 293 #: src/E20R/members-list/Members_List.php:11 02294 #: src/E20R/members-list/Members_List.php:11 11294 #. translators: %1$s - Error message from exception 295 #: src/E20R/members-list/Members_List.php:1085 296 msgid "Bulk Cancel: %1$s" 297 msgstr "" 298 299 #: src/E20R/members-list/Members_List.php:1146 300 #: src/E20R/members-list/Members_List.php:1155 295 301 msgid "Error cancelling membership(s)" 296 302 msgstr "" 297 303 298 304 #. translators: %1$s - Error message from thrown exception(s) 299 #: src/E20R/members-list/Members_List.php:1 163300 #: src/E20R/members-list/Members_List.php:1 178305 #: src/E20R/members-list/Members_List.php:1207 306 #: src/E20R/members-list/Members_List.php:1222 301 307 msgid "Cannot export member data! Error message: %1$s" 302 308 msgstr "" 303 309 304 310 #. translators: %1$s query string. 305 #: src/E20R/members-list/Members_List.php:1 264311 #: src/E20R/members-list/Members_List.php:1320 306 312 msgid "Error processing Members List database query: %1$s" 307 313 msgstr "" 308 314 309 315 #. translators: %1$s - The class parameter, %2$s - The class name where we expect said parameter to exist. 310 #: src/E20R/members-list/Members_List.php:1 298316 #: src/E20R/members-list/Members_List.php:1354 311 317 msgid "%1$s is not a member variable in %2$s" 312 318 msgstr "" 313 319 314 #: src/E20R/members-list/Members_List.php:1 380320 #: src/E20R/members-list/Members_List.php:1436 315 321 msgid "Error: Invalid database configuration!!!" 316 322 msgstr "" 317 323 318 #: src/E20R/members-list/Members_List.php:14 06324 #: src/E20R/members-list/Members_List.php:1462 319 325 msgid "Missing the \"FROM\" statement" 320 326 msgstr "" 321 327 322 #: src/E20R/members-list/Members_List.php:14 17328 #: src/E20R/members-list/Members_List.php:1473 323 329 msgid "Missing the minimum expected number of \"JOIN\" statements" 324 330 msgstr "" 325 331 326 332 #. translators: %1$d the number of JOIN entries in the Meembers_List::table_list definition 327 #: src/E20R/members-list/Members_List.php:14 35333 #: src/E20R/members-list/Members_List.php:1491 328 334 msgid "Have %1$d JOIN entries, but need at least 3!" 329 335 msgstr "" 330 336 331 #: src/E20R/members-list/Members_List.php:1 456337 #: src/E20R/members-list/Members_List.php:1512 332 338 msgid "Does not include the \"JOIN\" to support membership level search, but specified a level" 333 339 msgstr "" 334 340 335 #: src/E20R/members-list/Members_List.php:1 468341 #: src/E20R/members-list/Members_List.php:1524 336 342 msgid "Unexpected condition value for the 4th \"JOIN\" element" 337 343 msgstr "" 338 344 339 345 #. translators: %1$s table name defined by PMPro, %2$s Unexpected table name from user/developer 340 #: src/E20R/members-list/Members_List.php:1 482346 #: src/E20R/members-list/Members_List.php:1538 341 347 msgid "Unexpected table name value for the 4th \"JOIN\" element. Want: \"%1$s\", got: \"%2$s\"" 342 348 msgstr "" 343 349 344 #: src/E20R/members-list/Members_List.php:1 645345 #: src/E20R/members-list/Members_List.php:20 57346 #: src/E20R/members-list/Members_List.php:2 154350 #: src/E20R/members-list/Members_List.php:1701 351 #: src/E20R/members-list/Members_List.php:2097 352 #: src/E20R/members-list/Members_List.php:2233 347 353 msgid "Cancel" 348 354 msgstr "" 349 355 350 #: src/E20R/members-list/Members_List.php:1 646351 #: src/E20R/members-list/Members_List.php:17 20356 #: src/E20R/members-list/Members_List.php:1702 357 #: src/E20R/members-list/Members_List.php:1776 352 358 msgid "Update" 353 359 msgstr "" 354 360 355 #: src/E20R/members-list/Members_List.php:1 647361 #: src/E20R/members-list/Members_List.php:1703 356 362 msgid "Export" 357 363 msgstr "" 358 364 359 #: src/E20R/members-list/Members_List.php:17 19365 #: src/E20R/members-list/Members_List.php:1775 360 366 msgid "Update member info" 361 367 msgstr "" 362 368 363 #: src/E20R/members-list/Members_List.php:18 18364 #: src/E20R/members-list/Members_List.php:18 35369 #: src/E20R/members-list/Members_List.php:1874 370 #: src/E20R/members-list/Members_List.php:1891 365 371 msgid "Not found" 366 372 msgstr "" 367 373 368 #: src/E20R/members-list/Members_List.php:1838 374 #: src/E20R/members-list/Members_List.php:1894 375 #: src/E20R/members-list/Members_List.php:2168 376 #: src/E20R/members-list/Members_List.php:2181 369 377 msgid "N/A" 370 378 msgstr "" 371 379 372 #: src/E20R/members-list/Members_List.php:1877 373 #: src/E20R/members-list/modules/Multiple_Memberships.php:93 380 #: src/E20R/members-list/Members_List.php:1940 381 #: src/E20R/members-list/Members_List.php:2306 382 #: src/E20R/members-list/modules/Multiple_Memberships.php:124 383 msgid "Reset" 384 msgstr "" 385 386 #: src/E20R/members-list/Members_List.php:2010 387 msgid "Free" 388 msgstr "" 389 390 #: src/E20R/members-list/Members_List.php:2062 391 msgid "Invalid" 392 msgstr "" 393 394 #: src/E20R/members-list/Members_List.php:2128 374 395 msgid "No levels found. Paid Memberships Pro is inactive!" 375 396 msgstr "" 376 397 377 #: src/E20R/members-list/Members_List.php:1901 378 #: src/E20R/members-list/Members_List.php:2229 379 #: src/E20R/members-list/modules/Multiple_Memberships.php:117 380 msgid "Reset" 381 msgstr "" 382 383 #: src/E20R/members-list/Members_List.php:1970 384 msgid "Free" 385 msgstr "" 386 387 #: src/E20R/members-list/Members_List.php:2022 388 msgid "Invalid" 389 msgstr "" 390 391 #: src/E20R/members-list/Members_List.php:2086 392 msgid "Never" 393 msgstr "" 394 395 #. translators: %1$s HTML %2$s formatted payment amount, %3$s HTML. 396 #: src/E20R/members-list/Members_List.php:2103 398 #. translators: %1$s - HTML %2$s - next payment date, %3$s - HTML. 399 #: src/E20R/members-list/Members_List.php:2184 397 400 msgid "N/A (%1$sNext Payment: %2$s%3$s)" 398 401 msgstr "" 399 402 400 #: src/E20R/members-list/Members_List.php:2 159401 msgid " Bulk update membership end/expiration date"402 msgstr "" 403 404 #: src/E20R/members-list/Members_List.php:2 234403 #: src/E20R/members-list/Members_List.php:2238 404 msgid "Update membership end/expiration date" 405 msgstr "" 406 407 #: src/E20R/members-list/Members_List.php:2311 405 408 msgid "Update the member's membership status" 406 409 msgstr "" 407 410 408 #: src/E20R/members-list/Members_List.php:2 362411 #: src/E20R/members-list/Members_List.php:2439 409 412 msgid "No members found" 410 413 msgstr "" 411 414 412 #: src/E20R/members-list/Members_List.php:2 366415 #: src/E20R/members-list/Members_List.php:2443 413 416 msgid "It's possible the information you're looking for can be found in one of the following categories:" 414 417 msgstr "" 415 418 416 419 #. translators: %1$s HTML, %2%s HTML. 417 #: src/E20R/members-list/Members_List.php:2 373420 #: src/E20R/members-list/Members_List.php:2450 418 421 msgid "Repeat search: %1$sActive Members list%2$s" 419 422 msgstr "" 420 423 421 424 #. translators: %1$s HTML, %2%s HTML. 422 #: src/E20R/members-list/Members_List.php:2 386425 #: src/E20R/members-list/Members_List.php:2463 423 426 msgid "Repeat search: %1$sAll Members list%2$s" 424 427 msgstr "" 425 428 426 429 #. translators: %1$s HTML, %2$s HTML. 427 #: src/E20R/members-list/Members_List.php:2 398430 #: src/E20R/members-list/Members_List.php:2475 428 431 msgid "Repeat search: %1$sCancelled Members list%2$s" 429 432 msgstr "" 430 433 431 434 #. translators: %1$s HTML, %2$s HTML. 432 #: src/E20R/members-list/Members_List.php:24 13435 #: src/E20R/members-list/Members_List.php:2490 433 436 msgid "Repeat search: %1$sExpired Members list%2$s" 434 437 msgstr "" 435 438 436 439 #. translators: %1$s HTML, %2$s HTML. 437 #: src/E20R/members-list/Members_List.php:2 425440 #: src/E20R/members-list/Members_List.php:2502 438 441 msgid "Repeat search: %1$sOld Members list%2$s" 439 442 msgstr "" 440 443 441 444 #. translators: %1$s HTML, %2$s HTML. 442 #: src/E20R/members-list/Members_List.php:2 436445 #: src/E20R/members-list/Members_List.php:2513 443 446 msgid "Repeat search: %1$sAll Users list%2$s" 444 447 msgstr "" 448 449 #: src/E20R/members-list/modules/Multiple_Memberships.php:84 450 msgid "primary" 451 msgstr "" 452 453 #: src/E20R/members-list/modules/Multiple_Memberships.php:133 454 msgid "Click to edit primary membership level" 455 msgstr "" -
e20r-members-list/tags/8.6/src/E20R/members-list/Members_List.php
r2672951 r2700272 36 36 use E20R\Utilities\Message; 37 37 use E20R\Utilities\Utilities; 38 use stdClass; 38 39 use WP_List_Table; 40 use WP_User; 39 41 40 42 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { … … 46 48 } 47 49 48 if ( ! class_exists( ' \\E20R\\Members_List\\Members_List' ) ) {50 if ( ! class_exists( 'E20R\Members_List\Members_List' ) ) { 49 51 50 52 /** … … 272 274 273 275 /** 276 * List of date values we consider 'empty' (not confiured/set/defined) 277 * 278 * @var mixed|void|null $empty_date_values 279 */ 280 private $empty_date_values = array(); 281 282 /** 274 283 * Members_List constructor. 275 284 * … … 288 297 $this->action = $this->utils->get_variable( 'action', '' ); 289 298 $this->date_format = get_option( 'date_format' ); 299 300 // Default settings for what we consider 'empty' (not set) date values 301 add_filter( 302 'e20r_members_list_empty_date_values', 303 array( $this, 'set_empty_date_values' ), 304 -1, 305 1 306 ); 290 307 291 308 if ( empty( $page ) ) { … … 326 343 ); 327 344 345 // Configure the defaults for the empty/not set date values 346 $this->empty_date_values = apply_filters( 'e20r_members_list_empty_date_values', array() ); 347 328 348 if ( $this->is_module_enabled( 'pmpro-multiple-memberships-per-user/pmpro-multiple-memberships-per-user.php' ) ) { 329 349 // TODO: Add actions to process data for the PMPro Multiple Memberships Per User add-on 330 350 $this->utils->log( 'Enable the MMPU extensions' ); 331 351 } 352 332 353 333 354 /** … … 341 362 } 342 363 364 /** 365 * Define the list of 'empty' values for the date check(s) 366 * 367 * @param array $values Received list of values 368 * 369 * @return array 370 */ 371 public function set_empty_date_values( $values = array() ) { 372 return $values + array( 373 '', 374 null, 375 0, 376 '0', 377 '0000-00-00 00:00:00', 378 '0000-00-00', 379 '00:00:00', 380 ); 381 } 343 382 /** 344 383 * Generate the SQL and set the sql_query member variable to use the query … … 961 1000 // Some of the default columns are sortable. 962 1001 switch ( $col ) { 1002 case 'display_name': 963 1003 case 'user_login': 964 1004 case 'user_email': … … 967 1007 case 'status': 968 1008 case 'last': 969 case 'last_name':970 1009 $sortable_columns[ $col ] = array( $col, false ); 971 1010 break; … … 997 1036 998 1037 // Are we processing a bulk action? 999 if ( 1 === preg_match( '/bulk-/', $a ) || 1 === preg_match( '/bulk-/', $a2 ) ) { 1038 if ( 1039 ( null !== $a && 1 === preg_match( '/bulk-/', $a ) ) || 1040 ( null !== $a2 && 1 === preg_match( '/bulk-/', $a2 ) ) 1041 ) { 1000 1042 1001 1043 $this->utils->log( 'Processing a bulk action' ); … … 1018 1060 $action = $this->current_action(); 1019 1061 $data = array(); 1020 $selected_members = $this->utils->get_variable( 'member_user_id ', array() );1062 $selected_members = $this->utils->get_variable( 'member_user_ids', array() ); 1021 1063 1022 1064 foreach ( $selected_members as $key => $user_id ) { … … 1034 1076 1035 1077 if ( in_array( 'bulk-cancel', $bulk_actions, true ) ) { 1036 $this->cancel = new Bulk_Cancel( $data, $this->utils ); 1037 $this->cancel->execute(); 1078 try { 1079 $this->cancel = new Bulk_Cancel( $data, $this->utils ); 1080 $this->cancel->execute(); 1081 } catch ( InvalidProperty $e ) { 1082 $this->utils->add_message( 1083 sprintf( 1084 // translators: %1$s - Error message from exception 1085 esc_attr__( 'Bulk Cancel: %1$s', 'e20r-members-list' ), 1086 $e->getMessage() 1087 ), 1088 'error', 1089 'backend' 1090 ); 1091 } 1092 1038 1093 return; 1039 1094 … … 1041 1096 1042 1097 $this->utils->log( 'Requested Export of members!' ); 1043 try { 1044 $this->export_members(); 1045 } catch ( DBQueryError | InvalidSQL $e ) { 1046 $msg = sprintf( 1047 // translators: Error message from the execption thrown 1048 esc_attr__( 'Cannot export data!. Error message: %1$s', 'e20r-members-list' ), 1049 $e->getMessage() 1050 ); 1051 $this->utils->add_message( $msg, 'error', 'backend' ); 1052 return; 1053 } 1098 $this->export_members(); 1054 1099 1055 1100 // To push the export file to the browser, we have to terminate execution of this process. … … 1078 1123 $this->utils->log( 'Single action for the Members List...' ); 1079 1124 1080 $user_id = $this->utils->get_variable( 'member_user_id ', array() );1125 $user_id = $this->utils->get_variable( 'member_user_ids', array() ); 1081 1126 $level_id = $this->utils->get_variable( 'membership_id', array() ); 1082 1127 $action = $this->current_action(); … … 1097 1142 $this->cancel = new Bulk_Cancel( $user_ids, $this->utils ); 1098 1143 } 1099 1100 1144 1101 1145 if ( false === $this->cancel->execute() ) { … … 1205 1249 * @throws InvalidSQL Raised when there's a problem with the SQL we generated. 1206 1250 * @throws DBQueryError Raised if the DB Query reports a problem 1207 * @throws BadOperation|InvalidSettingsKey Raised when the Cache() class tries something unexpected1208 1251 */ 1209 1252 public function get_members( $per_page = null, $page_number = null, $return_count = false ) { … … 1213 1256 } 1214 1257 1215 $result = null;1216 1258 $result_cache_group = $this->page->get( 'result_cache_group' ); 1217 $cache_timeout = ( (int) $this->page->get( 'cache_timeout' ) * self::MINUTE_IN_SECONDS ); 1259 try { 1260 $cache_timeout = ( (int) $this->page->get( 'cache_timeout' ) * self::MINUTE_IN_SECONDS ); 1261 } catch ( InvalidSettingsKey $e ) { 1262 $this->utils->log( 'Error: cache_timeout is not a valid settings key!' ); 1263 $cache_timeout = 10 * self::MINUTE_IN_SECONDS; 1264 } 1218 1265 1219 1266 global $wpdb; … … 1221 1268 if ( empty( $this->sql_query ) || 1 !== preg_match_all( '/SELECT\s+.*\s+FROM(\s+(.*)){1,}/im', $this->sql_query ) ) { 1222 1269 $this->utils->log( 'Error in/Missing SQL statement! "' . $this->sql_query . '"' ); 1223 throw new InvalidSQL( ' Error:Attempting to fetch data without a valid SQL query!' );1270 throw new InvalidSQL( 'Attempting to fetch data without a valid SQL query!' ); 1224 1271 } 1225 1272 1226 1273 // Generate the Cache key based on 1227 1274 $lookup_cache_key = md5( $this->sql_query ); 1228 $result = Cache::get( $lookup_cache_key, $result_cache_group ); 1275 try { 1276 $result = Cache::get( $lookup_cache_key, $result_cache_group ); 1277 } catch ( BadOperation $e ) { 1278 $this->utils->log( 'Error returning cached results for ' . $lookup_cache_key ); 1279 $result = null; 1280 } 1229 1281 1230 1282 if ( null === $result ) { … … 1251 1303 // Save the result to the cache based on the cache specific key 1252 1304 $this->utils->log( "Attempting to cache the results for {$lookup_cache_key}" ); 1253 if ( false === Cache::set( $lookup_cache_key, $result, $cache_timeout, $result_cache_group ) ) { 1254 $this->utils->log( 'Error saving the result to cache!' ); 1305 try { 1306 if ( false === Cache::set( $lookup_cache_key, $result, $cache_timeout, $result_cache_group ) ) { 1307 $this->utils->log( 'Error saving the result to cache!' ); 1308 } 1309 } catch ( BadOperation $e ) { 1310 $this->utils->log( 'Error: Unable to save to cache for ' . $lookup_cache_key ); 1255 1311 } 1256 1312 } else { … … 1567 1623 $this->utils->log( 'Requesting (active/old/etc) member export' ); 1568 1624 1569 $member_ids = $this->utils->get_variable( 'member_user_id ', array() );1625 $member_ids = $this->utils->get_variable( 'member_user_ids', array() ); 1570 1626 $added_where = null; 1571 1627 … … 1677 1733 */ 1678 1734 public function column_user_login( $item ) { 1679 $user = new \WP_User( $item['ID'] );1735 $user = new WP_User( $item['ID'] ); 1680 1736 1681 1737 $edit_url = add_query_arg( … … 1832 1888 1833 1889 if ( empty( $address ) ) { 1834 1890 $this->utils->log( "User {$item['ID']} has no billing address" ); 1835 1891 $address = esc_attr__( 'Not found', 'e20r-members-list' ); 1836 1892 … … 1858 1914 <input type="hidden" value="%3$s" class="e20r-members-list-membership_id-label" name="e20r-members-list-membership_label_%2$s" /> 1859 1915 <input type="hidden" value="%1$d" class="e20r-members-list-db-membership_id" name="e20r-members-list-db_membership_id_%2$s" /> 1916 <input type="hidden" value="" class="e20r-members-list-db-membership_level_ids" name="e20r-members-list-db_membership_level_ids_%2$s" /> 1860 1917 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 1861 1918 <input type="hidden" value="%4$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', … … 1867 1924 ); 1868 1925 1869 $options = ''; 1870 if ( function_exists( 'pmpro_getAllLevels' ) ) { 1871 $levels = pmpro_getAllLevels( true, true ); 1872 } else { 1873 1874 // Default info if PMPro is disabled. 1875 $null_level = new \stdClass(); 1876 $null_level->level_id = 0; 1877 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 1878 $levels = array( $null_level ); 1879 } 1880 1881 foreach ( $levels as $level ) { 1882 $options .= sprintf( 1883 '<option value="%1$s" %2$s>%3$s</option>', 1884 $level->id, 1885 selected( $level->id, $item['membership_id'], false ), 1886 $level->name 1887 ) . "\n"; 1888 } 1926 $options = self::build_option_string( $item['membership_id'] ); 1927 1889 1928 $new_membershiplevel_input = sprintf( 1890 1929 '<div class="ml-row-settings clearfix"> … … 1931 1970 if ( true === $this->is_module_enabled( 'pmpro-multiple-memberships-per-user/pmpro-multiple-memberships-per-user.php' ) ) { 1932 1971 $mmpu = new Multiple_Memberships(); 1933 return $mmpu->column_name( $item ); 1972 $this->utils->log( 'MMPU support is deactivated pending support for adding/removing MMPU memberships' ); 1973 // return $mmpu->multiple_membership_column( $item ); FIXME: Actually enable MMPU support 1934 1974 } 1935 1975 … … 1979 2019 * @param array $item The record to process. 1980 2020 * 1981 * @return \stdClass2021 * @return stdClass 1982 2022 */ 1983 2023 public function column_code( $item ) { … … 2018 2058 public function column_startdate( $item ) { 2019 2059 2020 if ( '0000-00-00 00:00:00' === $item['startdate'] || empty( $item['startdate'] ) ) {2060 if ( $this->has_empty_date( $item['startdate'] ) ) { 2021 2061 $date_value = null; 2022 2062 $start_label = esc_attr__( 'Invalid', 'e20r-members-list' ); 2023 2063 } else { 2024 $date_value = ! empty( $item['startdate'] ) ? date_i18n( 'Y-m-d', strtotime( $item['startdate'], time() ) ) : null;2025 $start_label = date_i18n( $this->date_format, strtotime( $item['startdate'], time() ) );2026 } 2027 2028 $min_val = empty( $item['startdate'] ) ? sprintf( 'min="%s"', date_i18n( 'Y-m-d', time() ) ) : null;2064 $date_value = ! empty( $item['startdate'] ) ? gmdate( 'Y-m-d', strtotime( $item['startdate'], time() ) ) : null; 2065 $start_label = gmdate( $this->date_format, strtotime( $item['startdate'], time() ) ); 2066 } 2067 2068 $min_val = empty( $item['startdate'] ) ? sprintf( 'min="%s"', gmdate( 'Y-m-d', time() ) ) : null; 2029 2069 2030 2070 $startdate_input = sprintf( … … 2069 2109 2070 2110 /** 2111 * Generate the HTML for the membership level options 2112 * 2113 * @param int $level_id The membership level ID to highlight 2114 * 2115 * @return string 2116 */ 2117 public static function build_option_string( $level_id ) { 2118 2119 $options = ''; 2120 2121 if ( function_exists( 'pmpro_getAllLevels' ) ) { 2122 $levels = pmpro_getAllLevels( true, true ); 2123 } else { 2124 2125 // Default info if PMPro is disabled. 2126 $null_level = new stdClass(); 2127 $null_level->level_id = 0; 2128 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 2129 $levels = array( $null_level ); 2130 } 2131 2132 foreach ( $levels as $level ) { 2133 $options .= sprintf( 2134 '<option value="%1$s" %2$s>%3$s</option>', 2135 $level->id, 2136 selected( $level->id, $level_id, false ), 2137 $level->name 2138 ) . "\n"; 2139 } 2140 2141 return $options; 2142 } 2143 /** 2144 * Does the received value represent an 'empty' date value 2145 * 2146 * @param string|int|null $date The value to check 2147 * 2148 * @return bool 2149 */ 2150 public function has_empty_date( $date ) { 2151 return in_array( $date, $this->empty_date_values, true ); 2152 } 2153 /** 2071 2154 * Create the last column for the default Members_List table (Expiration date) 2072 2155 * … … 2077 2160 public function column_last( $item ) { 2078 2161 2079 $enddate = null; 2080 2081 // No billing amount (not recurring) and no end date (i.e. a free, limit less membership) 2082 if ( 2083 empty( $item['billing_amount'] ) && 2084 ( empty( $item['enddate'] ) || '0000-00-00 00:00:00' === $item['enddate'] ) 2085 ) { 2086 $enddate_label = esc_attr__( 'Never', 'e20r-members-list' ); 2087 $enddate_label = null; 2088 } else { 2089 $enddate = date_i18n( 2090 'Y-m-d', 2162 $no_enddate = false; 2163 $html_label = esc_attr__( 'Unknown', 'e20r-members-list' ); 2164 $enddate_label = ''; 2165 2166 // The end-date field is empty/not configured (never ending membership) 2167 if ( ! isset( $item['enddate'] ) || in_array( $item['enddate'], $this->empty_date_values, true ) ) { 2168 $enddate_label = esc_attr__( 'N/A', 'e20r-members-list' ); 2169 $html_label = $enddate_label; 2170 $no_enddate = true; 2171 } 2172 2173 // We have a user with a recurring billing membership. 2174 if ( true === $no_enddate && ! empty( $item['billing_amount'] ) && ! empty( $item['cycle_number'] ) ) { 2175 $next_payment = pmpro_next_payment( $item['ID'], 'success', 'timestamp' ); 2176 if ( false === $next_payment ) { 2177 $next_payment_date = esc_attr__( 'Unknown', 'e20r-members-list' ); 2178 } else { 2179 $next_payment_date = gmdate( $this->date_format, $next_payment ); 2180 } 2181 $enddate_label = esc_attr__( 'N/A', 'e20r-members-list' ); 2182 $html_label = sprintf( 2183 // translators: %1$s - HTML %2$s - next payment date, %3$s - HTML. 2184 esc_attr__( 'N/A (%1$sNext Payment: %2$s%3$s)', 'e20r-members-list' ), 2185 '<span class="e20r-members-list-small" style="font-size: 10px; font-style: italic;">', 2186 $next_payment_date, 2187 '</span>' 2188 ); 2189 } 2190 2191 // Setting the date label only if the value is non-empty _AND_ the value is _NOT_ '' 2192 if ( false === $no_enddate ) { 2193 $enddate_label = gmdate( 2194 $this->date_format, 2091 2195 strtotime( $item['enddate'], time() ) 2092 2196 ); 2093 $enddate_label = date_i18n( $this->date_format, strtotime( $item['enddate'], time() ) ); 2094 } 2095 2096 // The membership level has recurring payment. 2097 if ( 2098 ( empty( $item['enddate'] ) || '0000-00-00 00:00:00' === $item['enddate'] ) && 2099 ! empty( $item['billing_amount'] ) && ! empty( $item['cycle_number'] ) 2100 ) { 2101 $enddate_label = sprintf( 2102 // translators: %1$s HTML %2$s formatted payment amount, %3$s HTML. 2103 esc_attr__( 'N/A (%1$sNext Payment: %2$s%3$s)', 'e20r-members-list' ), 2104 '<span class="e20r-members-list-small" style="font-size: 10px; font-style: italic;">', 2105 date_i18n( 2106 $this->date_format, 2107 pmpro_next_payment( 2108 $item['ID'], 2109 'success', 2110 'timestamp' 2111 ) 2112 ), 2113 '</span>' 2114 ); 2115 $enddate = null; 2116 } 2117 2118 $enddate = apply_filters( 'e20r_members_list_enddate_col_result', $enddate, $item ); 2197 $html_label = $enddate_label; 2198 } 2119 2199 2120 2200 // BUG FIX: Didn't set the date format to match the WP setting as documented. 2121 $date_value = ! (2122 empty( $item['enddate'] ) ||2123 '0000-00-00 00:00:00' === $item['enddate'] ) ?2124 date_i18n( $this->date_format, strtotime( $item['enddate'], time() ) ) : 2125 null;2201 $date_value = ( false === $no_enddate ) ? 2202 strtotime( $item['enddate'], time() ) : 2203 null; 2204 2205 $date_value = apply_filters( 'e20r_members_list_enddate_col_result', $date_value, $item ); 2126 2206 2127 2207 // These are used to configure the enddate with JavaScript. … … 2133 2213 <input type="hidden" value="%4$s" class="e20r-members-list-db-enddate" name="e20r-members-list-db_enddate_%2$s" /> 2134 2214 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 2135 <input type="hidden" value=" %6$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />',2215 <input type="hidden" value="enddate" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', 2136 2216 $item['membership_id'], 2137 2217 $item['ID'], … … 2139 2219 $date_value, 2140 2220 $item['record_id'], 2141 'enddate'2142 2221 ); 2143 2222 … … 2146 2225 %1$s 2147 2226 <input type="date" placeholder="YYYY-MM-DD" pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))" title="Enter a date in this format YYYY-MM-DD" name="e20r-members-list-new_enddate_%2$s" class="e20r-members-list-input-enddate" value="%3$s"/> 2148 <br >2227 <br /> 2149 2228 <a href="#" class="e20r-members-list-cancel e20r-members-list-list-link">%4$s</a> 2150 2229 </div>', 2151 2230 $enddate_input, 2152 2231 $item['ID'], 2153 $date_value ,2232 $date_value ? gmdate( 'Y-m-d', $date_value ) : $date_value, 2154 2233 esc_attr__( 'Cancel', 'e20r-members-list' ) 2155 2234 ); 2156 2235 2157 $value =sprintf(2158 '<a href="#" class="e20r-members-list_enddate e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></ a></span>%3$s',2159 esc_attr__( ' Bulk update membership end/expiration date', 'e20r-members-list' ),2160 $ enddate_label,2236 return sprintf( 2237 '<a href="#" class="e20r-members-list_enddate e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></span></a>%3$s', 2238 esc_attr__( 'Update membership end/expiration date', 'e20r-members-list' ), 2239 $html_label, 2161 2240 $new_date_input 2162 2241 ); 2163 2164 return $value;2165 2242 } 2166 2243 … … 2221 2298 %3$s 2222 2299 </select> 2223 <br >2300 <br /> 2224 2301 <a href="#" class="e20r-members-list-cancel e20r-members-list-link">%4$s</a> 2225 2302 </div>', -
e20r-members-list/tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Cancel.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 25 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 26 use E20R\Members_List\Admin\Exceptions\PMProNotActive; 26 27 use E20R\Utilities\Message; 27 28 use E20R\Utilities\Utilities; 29 use function pmpro_cancelMembershipLevel; 28 30 29 31 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { … … 31 33 } 32 34 33 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Bulk\\Bulk_Cancel' ) ) {35 if ( ! class_exists( 'E20R\Members_List\Admin\Bulk\Bulk_Cancel' ) ) { 34 36 35 37 /** … … 41 43 * Bulk_Cancel constructor (singleton) 42 44 * 43 * @param array[]| int[]|null$members_to_update The array of member IDs to perform the bulk cancel operation against44 * @param Utilities|null $utils Instance of the E20R Utilities Module class45 * @param array[]|null $members_to_update The array of member IDs to perform the bulk cancel operation against 46 * @param Utilities|null $utils Instance of the E20R Utilities Module class 45 47 * 46 48 * @access public 47 49 * @throws InvalidProperty Thrown when the class or base class lacks the specified set() property 50 * @throws InvalidMemberList Thrown when the supplied array isn't null or has the wrong format 48 51 */ 49 public function __construct( $members_to_update = array(), $utils = null ) {52 public function __construct( $members_to_update = null, $utils = null ) { 50 53 51 54 if ( empty( $utils ) ) { … … 54 57 } 55 58 56 parent::__construct( $ utils );59 parent::__construct( $members_to_update, $utils ); 57 60 58 61 $this->set( 'operation', 'cancel' ); 59 $this->set( 'members_to_update', $members_to_update );60 62 } 61 63 … … 68 70 public function execute() { 69 71 72 if ( ! $this->pmpro_is_active() ) { 73 return false; 74 } 75 70 76 // Process all User & level ID for the single action. 71 77 $this->failed = array(); … … 74 80 // Process all selected records/members 75 81 foreach ( $this->members_to_update as $key => $cancel_info ) { 76 try { 77 if ( false === $this->cancel_member( $cancel_info['user_id'], $cancel_info['level_id'] ) ) { 78 if ( ! isset( $this->failed[ $cancel_info['user_id'] ] ) ) { 79 $this->failed[ $cancel_info['user_id'] ] = array(); 80 } 81 $this->failed[ $cancel_info['user_id'] ][] = $cancel_info['level_id']; // FIXME: Add level info for multiple membership levels 82 if ( false === $this->cancel_member( $cancel_info['user_id'], $cancel_info['level_id'] ) ) { 83 if ( ! isset( $this->failed[ $cancel_info['user_id'] ] ) ) { 84 $this->failed[ $cancel_info['user_id'] ] = array(); 82 85 } 83 } catch ( PMProNotActive $e ) { 84 $this->utils->add_message( $e->getMessage(), 'error', 'backend' ); 85 $this->failed[] = $cancel_info['user_id']; 86 return false; 86 $this->failed[ $cancel_info['user_id'] ][] = $cancel_info['level_id']; // FIXME: Add level info for multiple membership levels 87 87 } 88 88 } … … 144 144 throw new PMProNotActive( 145 145 esc_attr__( 146 ' Cannot find the pmpro_cancelMembershipLevel() function!',146 'The pmpro_cancelMembershipLevel() function is not defined. Is Paid Memberships Pro activated on this site?', 147 147 'e20r-members-list' 148 148 ) 149 149 ); 150 150 } 151 $this->utils->log( "Cancelling membership level {$id} for user {$id}" ); 152 return \pmpro_cancelMembershipLevel( $level_id, $id, 'admin_cancelled' ); 151 $this->utils->log( "Cancelling membership level {$level_id} for user {$id}" ); 152 return pmpro_cancelMembershipLevel( $level_id, $id, 'admin_cancelled' ); 153 } 154 155 /** 156 * Check if PMPro is active and throw exception if it isn't 157 * 158 * @return bool 159 */ 160 private function pmpro_is_active() { 161 return function_exists( 'pmpro_cancelMembershipLevel' ); 153 162 } 154 163 } -
e20r-members-list/tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Operations.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 25 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 26 use E20R\Utilities\Message; … … 47 48 * Array of WP_User IDs where the cancel operation failed 48 49 * 49 * @var null|int[] $failed50 * @var array[][] $failed 50 51 */ 51 protected $failed = null;52 protected $failed = array(); 52 53 53 54 /** 54 55 * Array of members to update where the memebr date is represented as an array per member 55 56 * 56 * @var array[] |int[]|null57 * @var array[] 57 58 */ 58 59 protected $members_to_update = array(); … … 68 69 * Bulk_Cancel constructor (singleton) 69 70 * 71 * @param array $members_to_update The list of members and level IDs to process 70 72 * @param Utilities|null $utils Instance of the E20R Utilities Module class 71 73 * 72 74 * @access public 73 75 */ 74 public function __construct( $ utils = null ) {76 public function __construct( $members_to_update = null, $utils = null ) { 75 77 76 78 if ( empty( $utils ) ) { … … 79 81 } 80 82 83 if ( null !== $members_to_update ) { 84 $this->set( 'members_to_update', $members_to_update ); 85 } 81 86 $this->utils = $utils; 82 87 } … … 89 94 * 90 95 * @throws InvalidProperty Raised if the user supplies an invalid class parameter 96 * @throws InvalidMemberList Raised if we're attempting to set the 'members_to_update' property and its value isn't appropriate 91 97 */ 92 98 public function set( string $param, $value ) { 99 100 if ( 101 'members_to_update' === $param && 102 null !== $value && 103 false === $this->valid_member_array( $value ) 104 ) { 105 throw new InvalidMemberList( 106 esc_attr__( 107 'The specified variable is not an array of user IDs', 108 'e20r-members-list' 109 ) 110 ); 111 } elseif ( 'members_to_update' === $param && null === $value ) { 112 $value = array(); 113 } 114 93 115 // Make sure we let the caller know there's a problem if the variable doesn't exist. 94 116 if ( ! property_exists( $this, $param ) ) { … … 131 153 132 154 /** 155 * Make sure the array is a valid user/membership level array 156 * 157 * @param mixed $members Variable (array) to test 158 * 159 * @return bool 160 */ 161 protected function valid_member_array( $members ) { 162 163 if ( ! is_array( $members ) ) { 164 return false; 165 } 166 167 if ( empty( $members ) ) { 168 return true; 169 } 170 171 return array_reduce( 172 $members, 173 function ( $result, $item ) { 174 return $result && 175 ( isset( $item['user_id'] ) && is_int( $item['user_id'] ) ) && 176 ( isset( $item['level_id'] ) && is_int( $item['level_id'] ) ); 177 }, 178 true 179 ); 180 } 181 182 /** 133 183 * Execute the Bulk Operation 134 184 * -
e20r-members-list/tags/8.6/src/E20R/members-list/admin/bulk/Bulk_Update.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use DateTime; 25 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 26 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 27 use E20R\Members_List\Admin\Exceptions\PMProNotActive; … … 41 43 * Bulk_Update constructor 42 44 * 43 * @param array$members Array of member information to update45 * @param null|array[] $members Array of member information to update 44 46 * @param null|Utilities $utils Instance of the E20R Utilities Module class 45 47 * 46 * @throws Invalid Property Raised if the specified class parameter for some reason is missing48 * @throws InvalidMemberList Raised if the supplied $members variable isn't a list of integers 47 49 */ 48 public function __construct( $members, $utils = null ) { 49 50 public function __construct( $members = null, $utils = null ) { 50 51 if ( empty( $utils ) ) { 51 52 $message = new Message(); 52 53 $utils = new Utilities( $message ); 53 54 } 54 55 parent::__construct( $utils ); 56 $this->set( 'members_to_update', $members ); 57 $this->set( 'operation', 'cancel' ); 55 $this->utils = $utils; 56 parent::__construct( $members, $this->utils ); 57 $this->set( 'operation', 'update' ); 58 58 } 59 59 … … 495 495 496 496 /** 497 * Return the list of members being updated498 *499 * @return array[]500 */501 public function get_members() {502 return $this->members_to_update;503 }504 505 /**506 497 * Test the date supplied for MySQL compliance 507 498 * … … 515 506 private function validate_date_format( $date, $format = 'Y-m-d' ) { 516 507 517 $check_date = \DateTime::createFromFormat( $format, $date );508 $check_date = DateTime::createFromFormat( $format, $date ); 518 509 519 510 return $check_date && $check_date->format( $format ) === $date; -
e20r-members-list/tags/8.6/src/E20R/members-list/admin/export/Export_Members.php
r2672951 r2700272 30 30 } 31 31 32 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Export\\Export_Members' ) ) {32 if ( ! class_exists( 'E20R\Members_List\Admin\Export\Export_Members' ) ) { 33 33 34 34 /** … … 662 662 */ 663 663 private function enclose( $text ) { 664 $text = empty( $text ) ? '' : $text; 664 665 return '"' . str_replace( '"', '\\"', $text ) . '"'; 665 666 } -
e20r-members-list/tags/8.6/src/E20R/members-list/js/e20r-memberslist-page.js
r2672951 r2700272 2 2 * License: 3 3 4 Copyright 2016-202 1- Eighty / 20 Results by Wicked Strong Chicks, LLC ([email protected])4 Copyright 2016-2022 - Eighty / 20 Results by Wicked Strong Chicks, LLC ([email protected]) 5 5 6 6 This program is free software; you can redistribute it and/or modify … … 23 23 24 24 let e20rMembersList_Page = { 25 init: function () { 26 27 // this.levels_dropdown = $('select#e20r-pmpro-memberslist-levels'); 28 // this.enddate_lnk = $('a.e20r-members-list_enddate'); 29 // this.cancelMemberLnk = $('a.e20r-cancel-member'); 30 // this.updateBtn = $('a.e20r-members-list-save'); 31 this.memberslist_form = $('.e20r-pmpro-memberslist-page form#posts-filter'); 32 this.edit_lnk = $('a.e20r-members-list-editable'); 33 this.resetBtn = $('a.e20r-members-list-cancel'); 34 this.updateMemberLnk = $('a.e20r-update-member'); 35 this.exportBtn = $('a.e20r-memberslist-export'); 36 this.changed_input = $('input[class^="e20r-members-list-input-"]'); 37 this.changed_select = $( 'select[class^="e20r-members-list-select-"]'); 38 this.levels_dropdown = $('#e20r-pmpro-memberslist-levels'); 39 this.bulkUpdate = $('#doaction, #doaction2'); 40 this.updateListBtn = $('#e20r-update-list'); 41 this.search_field = $('#post-search-input'); 42 this.dateFields = $('.e20r-members-list-input-enddate, .e20r-members-list-input-startdate'); 43 this.dataSearchBtn = $('#e20r-memberslist-search-data'); 44 this.bulkActionSelectTop = $('select#bulk-action-selector-top'); 45 this.bulkActionSelectBottom = $('select#bulk-action-selector-bottom'); 46 47 this.dateOpts = { 48 year: '2-digit', month: 'short', 49 day: 'numeric' 50 }; 51 52 let self = this; 53 54 self.dateFields.datepicker({ 55 dateFormat: "yy-mm-dd" 25 init: function () { 26 // this.levels_dropdown = $('select#e20r-pmpro-memberslist-levels'); 27 // this.enddate_lnk = $('a.e20r-members-list_enddate'); 28 // this.cancelMemberLnk = $('a.e20r-cancel-member'); 29 // this.updateBtn = $('a.e20r-members-list-save'); 30 this.memberslist_form = $('.e20r-pmpro-memberslist-page form#posts-filter'); 31 this.edit_lnk = $('a.e20r-members-list-editable'); 32 this.resetBtn = $('a.e20r-members-list-cancel'); 33 this.updateMemberLnk = $('a.e20r-update-member'); 34 this.exportBtn = $('a.e20r-memberslist-export'); 35 this.changed_input = $('input[class^="e20r-members-list-input-"]'); 36 this.changed_select = $( 'select[class^="e20r-members-list-select-"]'); 37 this.levels_dropdown = $('#e20r-pmpro-memberslist-levels'); 38 this.bulkUpdate = $('#doaction, #doaction2'); 39 this.updateListBtn = $('#e20r-update-list'); 40 this.search_field = $('#post-search-input'); 41 this.dateFields = $('.e20r-members-list-input-enddate, .e20r-members-list-input-startdate'); 42 this.dataSearchBtn = $('#e20r-memberslist-search-data'); 43 this.bulkActionSelectTop = $('select#bulk-action-selector-top'); 44 this.bulkActionSelectBottom = $('select#bulk-action-selector-bottom'); 45 46 this.dateOpts = { 47 year: '2-digit', month: 'short', 48 day: 'numeric' 49 }; 50 51 let self = this; 52 53 self.dateFields.datepicker({ 54 dateFormat: "yy-mm-dd" 55 }); 56 57 self.search_field.unbind('keypress').on('keypress', function(event){ 58 let keycode = (event.key ? event.key : event.keyCode); 59 if ('Enter' !== keycode) { 60 return; 61 } 62 self.dataSearchBtn.click(); 63 }); 64 65 // self.changed_input.unbind('blur').on('blur', function(ev) { 66 self.changed_input.unbind('blur').on('blur', function() { 67 self.set_update( this ); 68 }); 69 70 // self.updateListBtn.unbind('click').on('click', function(ev) { 71 self.updateListBtn.unbind('click').on('click', function(ev) { 72 $('#post-search-input').val(null); 73 74 if ( 'Clear Search' === self.updateListBtn.val() ) { 75 ev.preventDefault(); 76 window.console.log("We're clearing the search..."); 77 window.console.log(e20rml.url); //jshint ignore:line 78 window.location = e20rml.url; //jshint ignore:line 79 } 80 }); 81 82 // Trigger search whenever the levels drop-down is changed 83 self.levels_dropdown.unbind('change').on('change', function() { 84 self.dataSearchBtn.click(); 85 }); 86 87 /** 88 * Search operation initiated (a few other things simulate clicking the button) 89 */ 90 self.dataSearchBtn.unbind('click').on('click', function(event) { 91 let $search_string = $( '#post-search-input' ).val(); 92 let $level_string = $( '#e20r-pmpro-memberslist-levels' ).val(); 93 let $uri = window.location.toString(); 94 95 // If we have a string in the search box we'll append it to the URL 96 if ($search_string) { 97 event.preventDefault(); 98 99 // URL Encode the search string and add or replace it for the URI 100 $uri = self.add_to_uri( $uri, 'find', encodeURIComponent( $search_string ) ); 101 $uri = self.add_to_uri( $uri, 'level', encodeURIComponent( $level_string ) ); 102 window.console.log( 'New URI should be: ' + $uri ); 103 104 // Now we trigger the search 105 window.location = $uri; 106 107 // Clear the search field - Possible FIXME if user's do not want clearing the search field to happen 108 // $( '#post-search-input' ).val( null ); 109 } 110 }); 111 112 self.changed_select.unbind('change').on('change', function() { 113 let current_select = $(this); 114 let current_select_info = current_select.val(); 115 let $field_name = current_select.closest('div.ml-row-settings').find('.e20r-members-list-field-name').val(); 116 let previous_select_info = current_select.closest('div.ml-row-settings').find('input.e20r-members-list-db_' + $field_name ).val(); 117 let enddate = $('#the-list').find('.last .e20r-members-list-db-enddate').val(); 118 119 window.console.log("Previous value: " + previous_select_info ); 120 window.console.log("New value: " + current_select_info ); 121 122 if ( previous_select_info !== current_select_info && '' !== enddate ) { 123 if ( ! window.confirm(e20rml.lang.clearing_enddate) ) { //jshint ignore:line 124 return false; 125 } 126 } 127 128 self.set_update( this ); 129 }); 130 131 self.bulkUpdate.unbind('click').on('click', function (ev) { 132 133 ev.preventDefault(); 134 135 if ('bulk-export' === self.bulkActionSelectTop.val() || 'bulk-export' === self.bulkActionSelectBottom.val()) { 136 self.export_data(self); 137 return true; 138 } 139 140 self.memberslist_form.submit(); 141 }); 142 143 self.updateMemberLnk.unbind('click').on('click', function (ev) { 144 145 ev.preventDefault(); 146 let btn = $(this); 147 let row = btn.closest('tr'); 148 149 let membership_col = row.find('td.column-membership'); 150 let membership_settings = membership_col.find('div.ml-row-settings'); 151 let membership_label = membership_col.find('a.e20r-members-list-editable'); 152 153 let startdate_col = row.find('td.column-startdate'); 154 let startdate_settings = startdate_col.find('div.ml-row-settings'); 155 let startdate_label = startdate_col.find('a.e20r-members-list-editable'); 156 157 let enddate_col = row.find('td.column-last'); 158 let enddate_settings = enddate_col.find('div.ml-row-settings'); 159 let enddate_label = enddate_col.find('a.e20r-members-list-editable'); 160 161 let status_col = row.find( 'td.column-status'); 162 let status_settings = status_col.find('div.ml-row-settings'); 163 let status_label = status_col.find('a.e20r-members-list-editable'); 164 165 membership_label.toggle(); 166 membership_settings.toggle(); 167 168 status_label.toggle(); 169 status_settings.toggle(); 170 171 $('.column-joindate').each(function () { 172 $(this).toggle(); 56 173 }); 57 174 58 self.search_field.unbind('keypress').on('keypress', function(event){ 59 let keycode = (event.key ? event.key : event.keyCode); 60 if ('Enter' !== keycode) { 61 return; 62 } 63 64 self.dataSearchBtn.click(); 65 }); 66 67 // self.changed_input.unbind('blur').on('blur', function(ev) { 68 self.changed_input.unbind('blur').on('blur', function() { 69 self.set_update( this ); 70 }); 71 72 // self.updateListBtn.unbind('click').on('click', function(ev) { 73 self.updateListBtn.unbind('click').on('click', function(ev) { 74 $('#post-search-input').val(null); 75 76 if ( 'Clear Search' === self.updateListBtn.val() ) { 77 ev.preventDefault(); 78 window.console.log("We're clearing the search..."); 79 window.console.log(e20rml.url); //jshint ignore:line 80 window.location = e20rml.url; //jshint ignore:line 81 } 82 }); 83 84 // Trigger search whenever the levels drop-down is changed 85 self.levels_dropdown.unbind('change').on('change', function() { 86 self.dataSearchBtn.click(); 87 }); 88 89 /** 90 * Search operation initiated (a few other things simulate clicking the button) 91 */ 92 93 self.dataSearchBtn.unbind('click').on('click', function(event) { 94 95 let $search_string = $( '#post-search-input' ).val(); 96 let $level_string = $( '#e20r-pmpro-memberslist-levels' ).val(); 97 let $uri = window.location.toString(); 98 99 // If we have a string in the search box we'll append it to the URL 100 if ($search_string) { 101 event.preventDefault(); 102 103 // URL Encode the search string and add or replace it for the URI 104 $uri = self.add_to_uri( $uri, 'find', encodeURIComponent( $search_string ) ); 105 $uri = self.add_to_uri( $uri, 'level', encodeURIComponent( $level_string ) ); 106 window.console.log( 'New URI should be: ' + $uri ); 107 108 // Now we trigger the search 109 window.location = $uri; 110 111 // Clear the search field - Possible FIXME if user's do not want clearing the search field to happen 112 // $( '#post-search-input' ).val( null ); 113 } 114 }); 115 116 self.changed_select.unbind('change').on('change', function() { 117 118 let current_select = $(this); 119 let current_select_info = current_select.val(); 120 let $field_name = current_select.closest('div.ml-row-settings').find('.e20r-members-list-field-name').val(); 121 let previous_select_info = current_select.closest('div.ml-row-settings').find('input.e20r-members-list-db_' + $field_name ).val(); 122 let enddate = $('#the-list').find('.last .e20r-members-list-db-enddate').val(); 123 124 window.console.log("Previous value: " + previous_select_info ); 125 window.console.log("New value: " + current_select_info ); 126 127 if ( previous_select_info !== current_select_info && '' !== enddate ) { 128 if ( ! window.confirm(e20rml.lang.clearing_enddate) ) { //jshint ignore:line 129 return false; 130 } 131 } 132 133 self.set_update( this ); 134 }); 135 136 self.bulkUpdate.unbind('click').on('click', function (ev) { 137 138 ev.preventDefault(); 139 140 if ('bulk-export' === self.bulkActionSelectTop.val() || 'bulk-export' === self.bulkActionSelectBottom.val()) { 141 self.export_data(self); 142 return true; 143 } 144 145 self.memberslist_form.submit(); 146 }); 147 148 self.updateMemberLnk.unbind('click').on('click', function (ev) { 149 150 ev.preventDefault(); 151 let btn = $(this); 152 let row = btn.closest('tr'); 153 154 let membership_col = row.find('td.column-membership'); 155 let membership_settings = membership_col.find('div.ml-row-settings'); 156 let membership_label = membership_col.find('a.e20r-members-list-editable'); 157 158 let startdate_col = row.find('td.column-startdate'); 159 let startdate_settings = startdate_col.find('div.ml-row-settings'); 160 let startdate_label = startdate_col.find('a.e20r-members-list-editable'); 161 162 let enddate_col = row.find('td.column-last'); 163 let enddate_settings = enddate_col.find('div.ml-row-settings'); 164 let enddate_label = enddate_col.find('a.e20r-members-list-editable'); 165 166 let status_col = row.find( 'td.column-status'); 167 let status_settings = status_col.find('div.ml-row-settings'); 168 let status_label = status_col.find('a.e20r-members-list-editable'); 169 170 membership_label.toggle(); 171 membership_settings.toggle(); 172 173 status_label.toggle(); 174 status_settings.toggle(); 175 176 $('.column-joindate').each(function () { 177 $(this).toggle(); 178 }); 179 180 startdate_label.toggle(); 181 startdate_settings.toggle(); 182 183 enddate_label.toggle(); 184 enddate_settings.toggle(); 185 }); 186 187 // Process edit link 188 self.edit_lnk.unbind('click').on('click', function ($event) { 189 190 $event.preventDefault(); 191 192 let $edit = $(this); 193 let $input = $edit.next('div.ml-row-settings'); 194 195 $input.toggle(); 196 $edit.toggle(); 197 198 }); 199 200 /** 201 * Cancel membership link handler 202 */ 203 self.resetBtn.unbind('click').on('click', function (ev) { 204 205 ev.preventDefault(); 206 207 let $btn = $(this); 208 let $settings = $btn.closest('div.ml-row-settings'); 209 let $label = $settings.prev('a.e20r-members-list-editable'); 210 let $field_name = $settings.find('.e20r-members-list-field-name').val(); 211 212 // Return the select value (or input value) to its original value... 213 let $original = $settings.find('.e20r-members-list-db-' + $field_name ).val(); 214 let $field_input_key = 'e20r-members-list-new_' + $field_name; 215 216 $settings.find('select[name^="' + $field_input_key + '"], input[name^="'+ $field_input_key + '"]').val( $original ); 217 218 $settings.toggle(); 219 $label.toggle(); 220 }); 221 222 self.exportBtn.unbind('click').on('click', function (ev) { 223 ev.preventDefault(); 224 window.console.log("Export button clicked!"); 225 226 self.export_data( self ); 227 }); 228 }, 229 add_to_uri: function(url, paramName, paramValue) { 230 231 let pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)'); 232 233 if (paramValue === null) { 234 paramValue = ''; 235 } 236 237 if (url.search(pattern)>=0) { 238 return url.replace(pattern,'$1' + paramValue + '$2'); 239 } 240 241 url = url.replace(/[?#]$/,''); 242 return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue; 243 }, 244 export_data: function( self ) { 245 246 $("#overlay").fadeIn(300); 247 let inputs = $('.e20r-search-arguments input, .e20r-search-arguments textarea, .e20r-search-arguments select') 248 .not(':input[type=button], :input[type=submit], input[type=reset]'); 249 let export_args = self.prepare_export(self, inputs); 250 251 // Open a new tab to trigger the AJAX request for the download 252 let file_name = window.prompt( 'Filename for data to save/export', 'members_list' ); 253 let extension = '.csv'; 254 if ( file_name.toLowerCase().endsWith( '.csv' ) ) { 255 extension = ''; 256 } 257 self.download_csv(file_name + extension, export_args, ); //jshint ignore:line 258 }, 259 /** 260 * Set the request variables based on the members list page 261 * 262 * @param self Instance of this object 263 * @param inputs The HTML inputs 264 * @returns Object 265 */ 266 prepare_export: function( self, inputs ) { 267 let request_args = { 268 '_wpnonce': $('#_wpnonce').val(), 269 'showDebugTrace': true, 270 'action': 'e20rml_export_records', 271 }; 272 request_args.find = $( '#post-search-input' ).val(); 273 inputs.each(function () { 274 let input = $(this); 275 let name = input.attr('name'); 276 let value = input.val(); 277 if (false === self.is_empty(value)) { 278 window.console.log(name + " contains " + value); 279 request_args[name] = value; 280 } 281 }); 282 283 let is_checked = false; 284 let selected_ids = []; 285 $('input[name^="member_user_id"]').each(function () { 286 if ($(this).is(':checked')) { 287 is_checked = true; 288 selected_ids.push($(this).val()); 289 } 290 }); 291 if (selected_ids.length > 0) { 292 request_args.member_id = selected_ids; 293 } 294 // return self.build_export_url( export_args ); 295 return request_args; 296 }, 297 /** 298 * Download the exported .CSV data 299 * @param filename The file name to use for download 300 * @param download_args The POST argument(s) 301 */ 302 download_export: function(filename, download_args) { 303 fetch(e20rml.ajax_url, download_args) // jshint ignore:line 304 .then(response => { 305 if (!response.ok) { 306 throw new Error('Request failed.' + response); 307 } 308 }) 309 .then(response => response.blob()) 310 .then(blob => { 311 const link = window.document.createElement("a"); 312 link.href = URL.createObjectURL(blob); 313 link.download = filename; 314 link.click(); 315 }) 316 .catch(window.console.error); 317 }, 318 download_csv: function(file_name, attributes) { 319 let URL = e20rml.ajax_url; // jshint ignore:line 320 window.console.log( 'AJAX URL: ' + URL); 321 window.console.log( attributes ); 322 $.ajax({ 323 url: URL, 324 cache: false, 325 method: 'POST', 326 data: attributes, 327 accepts: 'text/csv', 328 xhr: function () { 329 var xhr = new XMLHttpRequest(); 330 xhr.onreadystatechange = function () { 331 if (xhr.readyState === 2) { 332 if (xhr.status === 200) { 333 xhr.responseType = "blob"; 334 } else { 335 xhr.responseType = "text"; 336 } 337 } 338 }; 339 return xhr; 340 }, 341 success: function (data) { 342 //Convert the Byte Data to BLOB object. 343 let blob = new Blob([data], { type: "text/csv" }); 344 345 //Check the Browser type and download the File. 346 let isIE = !!document.documentMode; 347 if (isIE) { 348 window.navigator.msSaveBlob(blob, file_name); 349 } else { 350 let url = window.URL || window.webkitURL; 351 let link = url.createObjectURL(blob); 352 let body = $('body'); 353 let a = $("<a />"); 354 a.attr("download", file_name); 355 a.attr("href", link); 356 body.append(a); 357 a[0].click(); 358 body.remove(a); 359 } 360 }, 361 error: function (jqXHR, textStatus, errorThrown) { 362 window.console.log( 'Status: ' + textStatus + ', Error thrown: ' + errorThrown ); 363 } 364 }).done(function() { 365 setTimeout(function(){ 366 $("#overlay").fadeOut(300); 367 },500); 368 }); 369 }, 370 set_update: function( $element ) { 371 let self = this; 372 373 let element = $($element); 374 let $settings = element.closest('div.ml-row-settings'); 375 // let users_id = $settings.find('input.e20r-members-list-user-id').val(); 376 let field_name = $settings.find('input.e20r-members-list-field-name').val(); 377 let $label = $settings.prev('a.e20r-members-list-' + field_name + '-label'); 378 // let $new_value_field = $settings.find('input.e20r-members-list-db-' + field_name); 379 let select = $settings.find('.e20r-members-list-select-' + field_name); 380 381 let $checkbox = element.closest('tr').find('th.check-column input[type="checkbox"]'); 382 383 let $date = $settings.find('.e20r-members-list-input-' + field_name).val(); 384 let select_val = select.val(); 385 let test_checked = false; 386 387 window.console.log("Value: ", $date, select_val); 388 389 if ('undefined' !== select_val) { 390 391 $label.text(select.find('option:selected').text()); 392 test_checked = true; 393 } 394 395 if (null !== $date) { 396 397 let date = new Date($date); 398 399 window.console.log("Date info? ", date); 400 401 if (Object.prototype.toString.call(date) === "[object Date]") { 402 $label.text(date.toLocaleDateString(e20rml.locale, self.dateOpts)); //jshint ignore:line 403 } 404 405 test_checked = true; 406 } 407 408 if (true === test_checked && false === $checkbox.is('checked')) { 409 window.console.log("Checkbox not checked. Fixing!"); 410 $checkbox.prop('checked', true); 411 412 self.bulkActionSelectBottom.val('bulk-update'); 413 self.bulkActionSelectTop.val('bulk-update'); 414 $('#doaction').val(e20rml.lang.save_btn_text); //jshint ignore:line 415 $('#doaction2').val(e20rml.lang.save_btn_text); //jshint ignore:line 416 } 417 /* 175 startdate_label.toggle(); 176 startdate_settings.toggle(); 177 178 enddate_label.toggle(); 179 enddate_settings.toggle(); 180 }); 181 182 // Process edit link 183 self.edit_lnk.unbind('click').on('click', function ($event) { 184 185 $event.preventDefault(); 186 187 let $edit = $(this); 188 let $input = $edit.next('div.ml-row-settings'); 189 190 $input.toggle(); 191 $edit.toggle(); 192 193 }); 194 195 /** 196 * Cancel membership link handler 197 */ 198 self.resetBtn.unbind('click').on('click', function (ev) { 199 200 ev.preventDefault(); 201 202 let $btn = $(this); 203 let $settings = $btn.closest('div.ml-row-settings'); 204 let $label = $settings.prev('a.e20r-members-list-editable'); 205 let $field_name = $settings.find('.e20r-members-list-field-name').val(); 206 207 // Return the select value (or input value) to its original value... 208 let $original = $settings.find('.e20r-members-list-db-' + $field_name ).val(); 209 let $field_input_key = 'e20r-members-list-new_' + $field_name; 210 211 $settings.find('select[name^="' + $field_input_key + '"], input[name^="'+ $field_input_key + '"]').val( $original ); 212 418 213 $settings.toggle(); 419 214 $label.toggle(); 420 */ 421 }, 422 /** 423 * Build the URL used to export the data 424 * 425 * @param export_attrs The attributes to use for the AJAX URL (export) 426 */ 427 build_export_url: function (export_attrs) { 428 window.console.log("About to transmit: ", export_attrs); 429 let export_arguments = ''; 430 Object.keys(export_attrs).forEach(function(key){ 431 let value = export_attrs[key]; 432 if ( value instanceof Array) { 433 value = value.join(','); 434 } 435 export_arguments = export_arguments + key + '=' + encodeURI( value ) + '&'; 436 }); 437 438 let location = e20rml.ajax_url + '?' + export_arguments; // jshint ignore:line 439 window.console.log( location ); 440 return location; 441 }, 442 is_empty: function (data) { 443 444 if (typeof(data) === 'number' || typeof(data) === 'boolean') { 445 return false; 215 }); 216 217 self.exportBtn.unbind('click').on('click', function (ev) { 218 ev.preventDefault(); 219 window.console.log("Export button clicked!"); 220 221 self.export_data( self ); 222 }); 223 }, 224 add_to_uri: function(url, paramName, paramValue) { 225 226 let pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)'); 227 228 if (paramValue === null) { 229 paramValue = ''; 230 } 231 232 if (url.search(pattern)>=0) { 233 return url.replace(pattern,'$1' + paramValue + '$2'); 234 } 235 236 url = url.replace(/[?#]$/,''); 237 return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue; 238 }, 239 export_data: function( self ) { 240 241 $("#overlay").fadeIn(300); 242 let inputs = $('.e20r-search-arguments input, .e20r-search-arguments textarea, .e20r-search-arguments select') 243 .not(':input[type=button], :input[type=submit], input[type=reset]'); 244 let export_args = self.prepare_export(self, inputs); 245 246 // Open a new tab to trigger the AJAX request for the download 247 let file_name = window.prompt( 'Filename for data to save/export', 'members_list' ); 248 let extension = '.csv'; 249 if ( file_name.toLowerCase().endsWith( '.csv' ) ) { 250 extension = ''; 251 } 252 self.download_csv(file_name + extension, export_args, ); //jshint ignore:line 253 }, 254 /** 255 * Set the request variables based on the members list page 256 * 257 * @param self Instance of this object 258 * @param inputs The HTML inputs 259 * @returns Object 260 */ 261 prepare_export: function( self, inputs ) { 262 let request_args = { 263 '_wpnonce': $('#_wpnonce').val(), 264 'showDebugTrace': true, 265 'action': 'e20rml_export_records', 266 }; 267 request_args.find = $( '#post-search-input' ).val(); 268 inputs.each(function () { 269 let input = $(this); 270 let name = input.attr('name'); 271 let value = input.val(); 272 if (false === self.is_empty(value)) { 273 window.console.log(name + " contains " + value); 274 request_args[name] = value; 275 } 276 }); 277 278 let is_checked = false; 279 let selected_ids = []; 280 $('input[name^="member_user_id"]').each(function () { 281 if ($(this).is(':checked')) { 282 is_checked = true; 283 selected_ids.push($(this).val()); 284 } 285 }); 286 if (selected_ids.length > 0) { 287 request_args.member_user_ids = selected_ids; 288 } 289 // return self.build_export_url( export_args ); 290 return request_args; 291 }, 292 /** 293 * Download the exported .CSV data 294 * 295 * @param file_name The file name to use for download 296 * @param attributes The POST argument(s) 297 */ 298 download_csv: function(file_name, attributes) { 299 let URL = e20rml.ajax_url; // jshint ignore:line 300 $.ajax({ 301 url: URL, 302 cache: false, 303 method: 'POST', 304 data: attributes, 305 accepts: 'text/csv', 306 xhr: function () { 307 var xhr = new XMLHttpRequest(); 308 xhr.onreadystatechange = function () { 309 if (xhr.readyState === 2) { 310 if (xhr.status === 200) { 311 xhr.responseType = "blob"; 312 } else { 313 xhr.responseType = "text"; 314 } 315 } 316 }; 317 return xhr; 318 }, 319 success: function (data) { 320 //Convert the Byte Data to BLOB object. 321 let blob = new Blob([data], { type: "text/csv" }); 322 323 //Check the Browser type and download the File. 324 let isIE = !!document.documentMode; 325 if (isIE) { 326 window.navigator.msSaveBlob(blob, file_name); 327 } else { 328 let url = window.URL || window.webkitURL; 329 let link = url.createObjectURL(blob); 330 let body = $('body'); 331 let a = $("<a />"); 332 a.attr("download", file_name); 333 a.attr("href", link); 334 body.append(a); 335 a[0].click(); 336 body.remove(a); 446 337 } 447 448 if (typeof(data) === 'undefined' || data === null) { 449 return true; 450 } 451 452 if (typeof(data.length) !== 'undefined') { 453 return data.length === 0; 454 } 455 456 let count = 0; 457 458 for (let i in data) { 459 460 if (data.hasOwnProperty(i)) { 461 count++; 462 } 463 } 464 465 return count === 0; 466 } 338 }, 339 error: function (jqXHR, textStatus, errorThrown) { 340 window.console.log( 'Status: ' + textStatus + ', Error thrown: ' + errorThrown ); 341 } 342 }).done(function() { 343 setTimeout(function(){ 344 $("#overlay").fadeOut(300); 345 },500); 346 }); 347 }, 348 set_update: function( $element ) { 349 let self = this; 350 let element = $($element); 351 let $settings = element.closest('div.ml-row-settings'); 352 // let users_id = $settings.find('input.e20r-members-list-user-id').val(); 353 let field_name = $settings.find('input.e20r-members-list-field-name').val(); 354 let $label = $settings.prev('a.e20r-members-list-' + field_name + '-label'); 355 // let $new_value_field = $settings.find('input.e20r-members-list-db-' + field_name); 356 let select = $settings.find('.e20r-members-list-select-' + field_name); 357 let $checkbox = element.closest('tr').find('th.check-column input[type="checkbox"]'); 358 let $date = $settings.find('.e20r-members-list-input-' + field_name).val(); 359 let select_val = select.val(); 360 let test_checked = false; 361 362 window.console.log("Value: ", $date, select_val); 363 364 if ('undefined' !== select_val) { 365 $label.text(select.find('option:selected').text()); 366 test_checked = true; 367 } 368 369 if (null !== $date) { 370 let date = new Date($date); 371 window.console.log("Date info? ", date); 372 if (Object.prototype.toString.call(date) === "[object Date]") { 373 $label.text(date.toLocaleDateString(e20rml.locale, self.dateOpts)); //jshint ignore:line 374 } 375 test_checked = true; 376 } 377 378 if (true === test_checked && false === $checkbox.is('checked')) { 379 window.console.log("Checkbox not checked. Fixing!"); 380 $checkbox.prop('checked', true); 381 382 self.bulkActionSelectBottom.val('bulk-update'); 383 self.bulkActionSelectTop.val('bulk-update'); 384 $('#doaction').val(e20rml.lang.save_btn_text); //jshint ignore:line 385 $('#doaction2').val(e20rml.lang.save_btn_text); //jshint ignore:line 386 } 387 /* 388 $settings.toggle(); 389 $label.toggle(); 390 */ 391 }, 392 /** 393 * Build the URL used to export the data 394 * 395 * @param export_attrs The attributes to use for the AJAX URL (export) 396 */ 397 build_export_url: function (export_attrs) { 398 window.console.log("About to transmit: ", export_attrs); 399 let export_arguments = ''; 400 Object.keys(export_attrs).forEach(function(key){ 401 let value = export_attrs[key]; 402 if ( value instanceof Array) { 403 value = value.join(','); 404 } 405 export_arguments = export_arguments + key + '=' + encodeURI( value ) + '&'; 406 }); 407 408 let location = e20rml.ajax_url + '?' + export_arguments; // jshint ignore:line 409 window.console.log( location ); 410 return location; 411 }, 412 is_empty: function (data) { 413 414 if (typeof(data) === 'number' || typeof(data) === 'boolean') { 415 return false; 416 } 417 418 if (typeof(data) === 'undefined' || data === null) { 419 return true; 420 } 421 422 if (typeof(data.length) !== 'undefined') { 423 return data.length === 0; 424 } 425 426 let count = 0; 427 428 for (let i in data) { 429 430 if (data.hasOwnProperty(i)) { 431 count++; 432 } 433 } 434 435 return count === 0; 436 } 467 437 }; 468 438 … … 470 440 e20rMembersList_Page.init(); 471 441 }); 472 473 442 })(jQuery); -
e20r-members-list/tags/8.6/src/E20R/members-list/modules/Multiple_Memberships.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Modules; 23 23 24 use E20R\Members_List\Members_List; 25 24 26 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { 25 27 die( 'WordPress not loaded. Naughty, naughty!' ); 26 28 } 27 29 28 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Modules\\Multiple_Memberships' ) ) {30 if ( ! class_exists( 'E20R\Members_List\Admin\Modules\Multiple_Memberships' ) ) { 29 31 /** 30 32 * Support for PHP's Multiple Memberships plugin … … 60 62 * @return string 61 63 */ 62 public function column_name( $item ) {64 public function multiple_membership_column( $item ) { 63 65 64 // FIXME: Update this to support MMPU65 66 // FIXME: Allow adding more levels and changing the "primary" level. 67 if ( ! function_exists( 'pmpro_getMembershipLevelsForUser' ) ) { 68 return ''; 69 } 70 71 $level_list = pmpro_getMembershipLevelsForUser( $item['ID'] ); 72 $current_level = isset( $item['membership_id'] ) ? (int) $item['membership_id'] : null; 73 $level_ids = array(); 74 $level_names = array(); 75 76 foreach ( $level_list as $level ) { 77 $level_ids[] = $level->id; 78 79 // Generate HTML for the membership levels the user is assigned to 80 $level_names[] = ( (int) $level->id === $current_level ? 81 sprintf( 82 '<span class="e20r-members-list-level-name e20r-members-list-primary-level">%1$s (%2$s)</span>', 83 esc_attr( $level->name ), 84 esc_attr__( 'primary', 'e20r-members-list' ) 85 ) : 86 sprintf( '<span class="e20r-members-list-level-name">%1$s</span>', $level->name ) 87 ); 88 } 66 89 67 90 // These are used to configure the membership level with JavaScript. … … 75 98 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 76 99 <input type="hidden" value="%4$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', 77 $item['membership_id'],78 $item['ID'],79 $item['name'],100 (int) $item['membership_id'], 101 (int) $item['ID'], 102 esc_attr( $item['name'] ), 80 103 'membership_id', 81 $item['record_id'], 82 $item['membership_level_ids'] 104 (int) $item['record_id'], 105 empty( $item['membership_level_ids'] ) ? 106 implode( ',', $level_ids ) : 107 esc_attr( $item['membership_level_ids'] ) 83 108 ); 84 109 85 $options = ''; 86 if ( function_exists( 'pmpro_getAllLevels' ) ) { 87 $levels = pmpro_getAllLevels( true, true ); 88 } else { 110 $options = Members_List::build_option_string( $item['membership_id'] ); 89 111 90 // Default info if PMPro is disabled. 91 $null_level = new \stdClass(); 92 $null_level->level_id = 0; 93 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 94 $levels = array( $null_level ); 95 } 96 97 foreach ( $levels as $level ) { 98 $options .= sprintf( 99 '<option value="%1$s" %2$s>%3$s</option>', 100 $level->id, 101 selected( $level->id, $item['membership_id'], false ), 102 $level->name 103 ) . "\n"; 104 } 105 $new_membershiplevel_input = sprintf( 112 $new_level_input = sprintf( 106 113 '<div class="ml-row-settings clearfix"> 107 114 %1$s … … 113 120 </div>', 114 121 $membership_input, 115 $item['ID'],122 (int) $item['ID'], 116 123 $options, 117 124 esc_attr__( 'Reset', 'e20r-members-list' ) 118 125 ); 119 126 120 $value = sprintf( 127 $level_info = sprintf( 128 '<div class="e20r-members-list-level-names">%1$s</div>', 129 implode( '<br class="e20r-members-list-level-name-spacer" />', $level_names ) 130 ); 131 return sprintf( 121 132 '<a href="#" class="e20r-members-list_membership_id e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></a>%3$s', 122 esc_attr__( 'Click to edit membership level', 'e20rapp' ),123 $ item['name'],124 $ new_membershiplevel_input133 esc_attr__( 'Click to edit primary membership level', 'e20r-members-list' ), 134 $new_level_input, 135 $level_info 125 136 ); 126 127 return '';128 137 } 129 138 } -
e20r-members-list/trunk/CHANGELOG.md
r2672951 r2700272 6 6 7 7 ## [Unreleased] 8 9 ## v8.6 - 2022-03-27 10 - BUG FIX: Revert codecov upload step (Eighty/20Results Bot on Github) 11 - BUG FIX: Set permissions for coverage output directory (Eighty/20Results Bot on Github) 12 - BUG FIX: More debug output (Eighty/20Results Bot on Github) 13 - BUG FIX: Typo in Makefile (Eighty/20Results Bot on Github) 14 - BUG FIX: Adding more debug text to makefile (Eighty/20Results Bot on Github) 15 - BUG FIX: Adding debug text to makefile (Eighty/20Results Bot on Github) 16 - BUG FIX: Adding PHP 8.1 to test matrix (Eighty/20Results Bot on Github) 17 - BUG FIX: Integration tests weren't doing much (Eighty/20Results Bot on Github) 18 - BUG FIX: Add PHP 8.1 to test matrix and revert code coverage upload (Eighty/20Results Bot on Github) 19 - BUG FIX: Reverted codecov upload step and adding php 8.1 to test matrix (Eighty/20Results Bot on Github) 20 - BUG FIX: Be explicit about the file to load in action (Eighty/20Results Bot on Github) 21 - BUG FIX: Different config for codecov-action (Eighty/20Results Bot on Github) 22 - BUG FIX: Try to fix setup for codecov/codecov-action (Eighty/20Results Bot on Github) 23 - BUG FIX: Update codecov configuration and rename the config file (Eighty/20Results Bot on Github) 24 - BUG FIX: Revert removal of PMPro dependency for PHPStan tests (Eighty/20Results Bot on Github) 25 - BUG FIX: Attempting to enable code coverage uploads (Eighty/20Results Bot on Github) 26 - BUG FIX: Adding the codecov.yml config file (Eighty/20Results Bot on Github) 27 - BUG FIX: PHPDoc entry for failed class variable was wrong (Eighty/20Results Bot on Github) 28 - BUG FIX: More unit test coverage for the bulk operations including fix for members_to_process check (Eighty/20Results Bot on Github) 29 - BUG FIX: Checksum for composer wasn't a make variable (Eighty/20Results Bot on Github) 30 - BUG FIX: Exception for when the array of users/levels to perform bulk operations is incorrect (Eighty/20Results Bot on Github) 31 - BUG FIX: Make coverage a local only thing (for now?) (Eighty/20Results Bot on Github) 32 - BUG FIX: Don't create unneeded coverage subdirectories (Eighty/20Results Bot on Github) 33 - BUG FIX: Fix workflow config (Eighty/20Results Bot on Github) 34 - BUG FIX: Use remote coverage service (Eighty/20Results Bot on Github) 35 - BUG FIX: Problem with coverage artifacts (Eighty/20Results Bot on Github) 36 - BUG FIX: Bumped version number to 8.6 (Eighty/20Results Bot on Github) 37 - BUG FIX: Clean up message in get_members() -> InvalidSQL exception (Eighty/20Results Bot on Github) 38 - BUG FIX: Not re-running workflow when PR is updated/edited (Eighty/20Results Bot on Github) 39 - BUG FIX: Add support for Brain\faker() mocking for WP and the ext-mysqli extension for integration testing purposes (Eighty/20Results Bot on Github) 40 - BUG FIX: Transition to mocking PMPro functions for Integration tests (probably moving actual PMPro integration to acceptance testing) (Eighty/20Results Bot on Github) 41 - BUG FIX: Make sure Integration/Acceptance test container uses PHP v7.4 (Eighty/20Results Bot on Github) 42 - BUG FIX: Added PHP8/Paid Memberships Pro incompatibility as a 'Known Issue' (Eighty/20Results Bot on Github) 43 - BUG FIX: Updated tags for Wodby docker4wordpress (Eighty/20Results Bot on Github) 44 - BUG FIX: Wrong number of placeholders and data in sprintf() (Eighty/20Results Bot on Github) 45 - BUG FIX: Clean up Known Issues text (Eighty/20Results Bot on Github) 46 - BUG FIX: Refactored option HTML and changed to using gmdate() (Eighty/20Results Bot on Github) 47 - BUG FIX: Adding support for listing multiple memberships (still have TODOs, not releasing yet) (Eighty/20Results Bot on Github) 48 - BUG FIX: Added a known issue (Eighty/20Results Bot on Github) 49 - BUG FIX: Cleaned up namespace path (Eighty/20Results Bot on Github) 50 - BUG FIX: No longer using singleton pattern in E20R_Members_List() class and fixed PHPCS warnings (Eighty/20Results Bot on Github) 51 - BUG FIX: Null value disallowed for str_replace() (Eighty/20Results Bot on Github) 52 - BUG FIX: Bulk-exported all users, not just the selected ones and fixed PHP Deprecated messages for bulk action checks (Eighty/20Results Bot on Github) 53 - BUG FIX: Adding tests with data for column_last() method (Eighty/20Results Bot on Github) 54 - BUG FIX: Fix column_last() to avoid PHP Warning messages and refactor, make the definition of an 'empty' enddate filtered and add helper methods. Added exceptions and handlers (Eighty/20Results Bot on Github) 55 - BUG FIX: Add e20r_members_list_empty_date_values filter documentation (Eighty/20Results Bot on Github) 56 - BUG FIX: Cleanup patchwork.json (Eighty/20Results Bot on Github) 57 - BUG FIX: Variables rely on tests/_env/.env.testing (Eighty/20Results Bot on Github) 58 - BUG FIX: Run install for WP and set only DEBUG variable (Eighty/20Results Bot on Github) 59 - BUG FIX: Make sure we have the needed database tables for testing (Eighty/20Results Bot on Github) 60 - BUG FIX: Actual path to source files for e20r utilities module is src/E20R/... (Eighty/20Results Bot on Github) 61 - BUG FIX: PMPro doesn't check if constants are defined before use triggering test failures for us (Eighty/20Results Bot on Github) 62 - BUG FIX: Load fixtures to load/clear the DB (Eighty/20Results Bot on Github) 63 - BUG FIX: Lacked DB records for PMPro and User in integration tests (Eighty/20Results Bot on Github) 64 - BUG FIX: Wrong path to test specific _bootstrap.php file (Eighty/20Results Bot on Github) 65 - BUG FIX: Didn't load test specific _bootstrap.php files (Eighty/20Results Bot on Github) 66 - BUG FIX: Initial commit for column_last integration test (Eighty/20Results Bot on Github) 67 - BUG FIX: Add WP_DEBUG logging for test container(s) (Eighty/20Results Bot on Github) 68 - BUG FIX: Use E20R_PLUGIN_NAME env variable to set PROJECT_NAME variable (with default value set to plugin slug) (Eighty/20Results Bot on Github) 69 - BUG FIX: Upgraded wodby container environments (Eighty/20Results Bot on Github) 70 - BUG FIX: Fixed install-hooks for pre-commit git hook (Eighty/20Results Bot on Github) 71 - BUG FIX: Path to documentation (Eighty/20Results Bot on Github) 72 - BUG FIX: Make sure we strip away the test file (Eighty/20Results Bot on Github) 8 73 9 74 ## v8.5 - 2022-02-04 -
e20r-members-list/trunk/README.md
r2672951 r2700272 3 3 `Tags: paid memberships pro, members, memberships, pmpro enhancements, better members list, members list, addon` <br /> 4 4 `Requires at least: 4.9` <br /> 5 `Tested up to: 5.9 ` <br />5 `Tested up to: 5.9.2` <br /> 6 6 `Requires PHP: 7.1` <br /> 7 `Stable tag: 8. 5` <br />7 `Stable tag: 8.6` <br /> 8 8 `License: GPLv2` <br /> 9 9 `License URI: http://www.gnu.org/licenses/gpl` <br /> … … 36 36 See [ACTIONS.md](https://github.com/eighty20results.com/e20r-members-list/blob/main/docs/ACTIONS.md) 37 37 38 38 39 ### Known Issues 39 No known issues at this time 40 PHP 8.0 and later introduces warning messages for certain behaviors that were ignored prior to v8.0. Because of this, and the fact that this plugin relies on functionality from Paid Memberships Pro, the "end date" column may print messages indicating problems with the `trim()` function. Until Paid Memberships Pro updates their plugin to support PHP8.x, these messages will need to be disabled in your web server configuration (suppressed). 41 42 Setting the "Members per page" in the "Options" drop-down on the Members List page to a number greater than 50 can result in unexpected errors/warnings. The default value is 20. One symptom is seeing the PHP warning: "Warning: Unknown: Input variables exceeded 2000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0" 40 43 41 44 ### Changelog -
e20r-members-list/trunk/README.txt
r2672951 r2700272 3 3 Tags: paid memberships pro, members, memberships, pmpro enhancements, better members list, members list, addon 4 4 Requires at least: 4.9 5 Tested up to: 5.9 5 Tested up to: 5.9.2 6 6 Requires PHP: 7.1 7 Stable tag: 8. 57 Stable tag: 8.6 8 8 License: GPLv2 9 9 License URI: http://www.gnu.org/licenses/gpl … … 38 38 39 39 == Known Issues == 40 No known issues at this time 40 PHP 8.0 and later introduces warning messages for certain behaviors that were ignored prior to v8.0. Because of this, and the fact that this plugin relies on functionality from Paid Memberships Pro, the "end date" column may print messages indicating problems with the `trim()` function. Until Paid Memberships Pro updates their plugin to support PHP8.x, these messages will need to be disabled in your web server configuration (suppressed). 41 42 Setting the "Members per page" in the "Options" drop-down on the Members List page to a number greater than 50 can result in unexpected errors/warnings. The default value is 20. One symptom is seeing the PHP warning: "Warning: Unknown: Input variables exceeded 2000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0" 41 43 42 44 == Changelog == 43 45 See the official [CHANGELOG.md](https://github.com/eighty20results.com/e20r-members-list/blob/main/CHANGELOG.md) file 44 -
e20r-members-list/trunk/class-e20r-members-list.php
r2672951 r2700272 4 4 Plugin URI: https://wordpress.org/plugins/e20r-members-list 5 5 Description: Extensible, sortable & bulk action capable members listing + export to CSV tool for Paid Memberships Pro. 6 Version: 8. 56 Version: 8.6 7 7 Author: Thomas Sjolshagen @ Eighty / 20 Results by Wicked Strong Chicks, LLC <[email protected]> 8 8 Author URI: https://eighty20results.com/thomas-sjolshagen/ … … 39 39 use E20R\Metrics\Exceptions\MissingDependencies; 40 40 use E20R\Metrics\MixpanelConnector; 41 use E20R\Utilities\ActivateUtilitiesPlugin; 41 42 use E20R\Utilities\Cache; 42 43 use E20R\Utilities\Utilities; … … 52 53 53 54 if ( ! defined( 'E20R_MEMBERSLIST_VER' ) ) { 54 define( 'E20R_MEMBERSLIST_VER', '8. 5' );55 define( 'E20R_MEMBERSLIST_VER', '8.6' ); 55 56 } 56 57 … … 62 63 63 64 /** 64 * Instance of the Member List controller65 *66 * @var null|E20R_Members_List67 */68 private static $instance = null;69 70 /**71 65 * The E20R Utilities Module class instance 72 66 * … … 98 92 */ 99 93 public function __construct( $ml_page = null, $utils = null, $mixpanel = null ) { 100 self::$instance = $this;101 94 102 95 if ( empty( $utils ) ) { … … 139 132 * @throws InvalidSettingsKey Raised when an invalid class property is specified for the get() method 140 133 */ 141 public function get( $property = ' instance' ) {134 public function get( $property = 'utils' ) { 142 135 143 136 if ( ! property_exists( $this, $property ) ) { … … 152 145 return $this->{$property}; 153 146 } 154 /** 155 * Get or instantiate and get the current class 156 * 157 * @return E20R_Members_List|null 158 * 159 * @test E20R_Members_ListTest::test_get_instance() 160 */ 161 public static function get_instance() { 162 163 if ( is_null( self::$instance ) ) { 164 self::$instance = new self(); 165 } 166 167 return self::$instance; 147 148 /** 149 * Fetch the Page object 150 * 151 * @return Members_List_Page|null 152 */ 153 public function get_page() { 154 return $this->page; 168 155 } 169 156 … … 181 168 public function load_hooks( $utils = null ) { 182 169 183 if ( ! method_exists( '\ \E20R\\Utilities\\Utilities', 'get_instance' ) ) {170 if ( ! method_exists( '\E20R\Utilities\Utilities', 'get_instance' ) ) { 184 171 $msg = esc_attr__( 'The E20R Utilities Module is missing/inactive!', 'e20r-members-list' ); 185 172 throw new MissingUtilitiesModule( $msg ); … … 354 341 'filters' => sprintf( 355 342 '<a href="%1$s" title="%2$s">%3$s</a>', 356 esc_url_raw( plugin_dir_url( __FILE__ ) . ' ../docs/FILTERS.md' ),343 esc_url_raw( plugin_dir_url( __FILE__ ) . '/docs/FILTERS.md' ), 357 344 esc_attr__( 'View the Filter documentation', 'e20r-members-list' ), 358 345 esc_attr__( 'Filters', 'e20r-members-list' ) … … 360 347 'actions' => sprintf( 361 348 '<a href="%1$s" title="%2$s">%3$s</a>', 362 esc_url_raw( plugin_dir_url( __FILE__ ) . ' ../docs/ACTIONS.md' ),349 esc_url_raw( plugin_dir_url( __FILE__ ) . '/docs/ACTIONS.md' ), 363 350 esc_attr__( 'View the Actions documentation', 'e20r-members-list' ), 364 351 esc_attr__( 'Actions', 'e20r-members-list' ) … … 395 382 $required_plugin = 'Better Members List for Paid Memberships Pro'; 396 383 397 if ( false === \E20R\Utilities\ActivateUtilitiesPlugin::attempt_activation() ) {384 if ( false === ActivateUtilitiesPlugin::attempt_activation() ) { 398 385 add_action( 399 386 'admin_notices', 400 387 function () use ( $required_plugin ) { 401 \E20R\Utilities\ActivateUtilitiesPlugin::plugin_not_installed( $required_plugin );388 ActivateUtilitiesPlugin::plugin_not_installed( $required_plugin ); 402 389 } 403 390 ); -
e20r-members-list/trunk/docs/FILTERS.md
r2672951 r2700272 250 250 ); 251 251 ``` 252 253 ### e20r_members_list_empty_date_values 254 255 Modifies: Array of the date values that we (and PMPro) consider to be the equivalent of an 'empty' (not configured) date value 256 257 Purpose: To modify or update the list of date values we'll think of as 'empty'. Anything goes here. Including integer values representing seconds since epoch start. 258 259 Default: 260 ```php 261 array( 262 '', 263 null, 264 0, 265 '0', 266 '0000-00-00 00:00:00', 267 '0000-00-00', 268 '00:00:00', 269 ); 270 ``` 271 272 Dependencies: N/A 273 274 Example: 275 ```php 276 add_filter( 277 'e20r_members_list_empty_date_values', 278 function( $values ) { 279 // Also include DD-MM-YYYY at midnight as a valid 'empty' value 280 return $values + array( 281 '00-00-0000 00:00:00', 282 '00-00-00 00:00:00', 283 ); 284 }, 285 10, 286 1 287 ); 288 ``` -
e20r-members-list/trunk/inc/autoload.php
r2672951 r2700272 5 5 require_once __DIR__ . '/composer/autoload_real.php'; 6 6 7 return ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d::getLoader();7 return ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829::getLoader(); -
e20r-members-list/trunk/inc/composer/autoload_real.php
r2672951 r2700272 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d5 class ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829 6 6 { 7 7 private static $loader; … … 25 25 require __DIR__ . '/platform_check.php'; 26 26 27 spl_autoload_register(array('ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d', 'loadClassLoader'), true, true);27 spl_autoload_register(array('ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829', 'loadClassLoader'), true, true); 28 28 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); 29 spl_autoload_unregister(array('ComposerAutoloaderInit 40868d3a6d59e2d6945ded47ea627a6d', 'loadClassLoader'));29 spl_autoload_unregister(array('ComposerAutoloaderInit8968a46d1d5402e6dbb4601beede8829', 'loadClassLoader')); 30 30 31 31 $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); … … 33 33 require __DIR__ . '/autoload_static.php'; 34 34 35 call_user_func(\Composer\Autoload\ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::getInitializer($loader));35 call_user_func(\Composer\Autoload\ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::getInitializer($loader)); 36 36 } else { 37 37 $map = require __DIR__ . '/autoload_namespaces.php'; … … 54 54 55 55 if ($useStaticLoader) { 56 $includeFiles = Composer\Autoload\ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$files;56 $includeFiles = Composer\Autoload\ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$files; 57 57 } else { 58 58 $includeFiles = require __DIR__ . '/autoload_files.php'; 59 59 } 60 60 foreach ($includeFiles as $fileIdentifier => $file) { 61 composerRequire 40868d3a6d59e2d6945ded47ea627a6d($fileIdentifier, $file);61 composerRequire8968a46d1d5402e6dbb4601beede8829($fileIdentifier, $file); 62 62 } 63 63 … … 71 71 * @return void 72 72 */ 73 function composerRequire 40868d3a6d59e2d6945ded47ea627a6d($fileIdentifier, $file)73 function composerRequire8968a46d1d5402e6dbb4601beede8829($fileIdentifier, $file) 74 74 { 75 75 if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { -
e20r-members-list/trunk/inc/composer/autoload_static.php
r2672951 r2700272 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d7 class ComposerStaticInit8968a46d1d5402e6dbb4601beede8829 8 8 { 9 9 public static $files = array ( … … 91 91 { 92 92 return \Closure::bind(function () use ($loader) { 93 $loader->prefixLengthsPsr4 = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$prefixLengthsPsr4;94 $loader->prefixDirsPsr4 = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$prefixDirsPsr4;95 $loader->classMap = ComposerStaticInit 40868d3a6d59e2d6945ded47ea627a6d::$classMap;93 $loader->prefixLengthsPsr4 = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$prefixLengthsPsr4; 94 $loader->prefixDirsPsr4 = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$prefixDirsPsr4; 95 $loader->classMap = ComposerStaticInit8968a46d1d5402e6dbb4601beede8829::$classMap; 96 96 97 97 }, null, ClassLoader::class); -
e20r-members-list/trunk/languages/e20r-members-list.pot
r2672951 r2700272 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Better Members List for Paid Memberships Pro 8. 5\n"5 "Project-Id-Version: Better Members List for Paid Memberships Pro 8.6\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/e20r-members-list\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2022-0 2-04T13:27:08+00:00\n"12 "POT-Creation-Date: 2022-03-27T16:56:55+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.6.0\n" … … 35 35 msgstr "" 36 36 37 #: class-e20r-members-list.php:1 4537 #: class-e20r-members-list.php:138 38 38 msgid "The specified E20R_Members_List() class property does not exist!" 39 39 msgstr "" 40 40 41 #: class-e20r-members-list.php:1 8441 #: class-e20r-members-list.php:171 42 42 msgid "The E20R Utilities Module is missing/inactive!" 43 43 msgstr "" 44 44 45 #: class-e20r-members-list.php:2 1545 #: class-e20r-members-list.php:202 46 46 msgid "Could not clear all of the cached members list data. Check error logs for more information." 47 47 msgstr "" 48 48 49 #: class-e20r-members-list.php:3 4249 #: class-e20r-members-list.php:329 50 50 msgid "Donate to support updates, maintenance and tech support for this plugin" 51 51 msgstr "" 52 52 53 #: class-e20r-members-list.php:3 4653 #: class-e20r-members-list.php:333 54 54 msgid "Donate" 55 55 msgstr "" 56 56 57 #: class-e20r-members-list.php:338 58 msgid "View the documentation" 59 msgstr "" 60 61 #: class-e20r-members-list.php:339 62 msgid "Docs" 63 msgstr "" 64 65 #: class-e20r-members-list.php:344 66 msgid "View the Filter documentation" 67 msgstr "" 68 69 #: class-e20r-members-list.php:345 70 msgid "Filters" 71 msgstr "" 72 73 #: class-e20r-members-list.php:350 74 msgid "View the Actions documentation" 75 msgstr "" 76 57 77 #: class-e20r-members-list.php:351 58 msgid " View the documentation"59 msgstr "" 60 61 #: class-e20r-members-list.php:35 262 msgid " Docs"78 msgid "Actions" 79 msgstr "" 80 81 #: class-e20r-members-list.php:356 82 msgid "Visit the support forum" 63 83 msgstr "" 64 84 65 85 #: class-e20r-members-list.php:357 66 msgid " View the Filter documentation"67 msgstr "" 68 69 #: class-e20r-members-list.php:3 5870 msgid " Filters"86 msgid "Support" 87 msgstr "" 88 89 #: class-e20r-members-list.php:362 90 msgid "Report issues with this plugin" 71 91 msgstr "" 72 92 73 93 #: class-e20r-members-list.php:363 74 msgid "View the Actions documentation"75 msgstr ""76 77 #: class-e20r-members-list.php:36478 msgid "Actions"79 msgstr ""80 81 #: class-e20r-members-list.php:36982 msgid "Visit the support forum"83 msgstr ""84 85 #: class-e20r-members-list.php:37086 msgid "Support"87 msgstr ""88 89 #: class-e20r-members-list.php:37590 msgid "Report issues with this plugin"91 msgstr ""92 93 #: class-e20r-members-list.php:37694 94 msgid "Report Issues" 95 95 msgstr "" … … 101 101 102 102 #: src/E20R/members-list/admin/bulk/Bulk_Cancel.php:145 103 msgid "Cannot find the pmpro_cancelMembershipLevel() function!" 103 msgid "The pmpro_cancelMembershipLevel() function is not defined. Is Paid Memberships Pro activated on this site?" 104 msgstr "" 105 106 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:106 107 msgid "The specified variable is not an array of user IDs" 104 108 msgstr "" 105 109 106 110 #. translators: %1$s - The parameter supplied, %2$s - The class name where we expect the supplied parameter to exist, 107 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php: 98111 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:120 108 112 msgid "Invalid parameter \"%1$s\" supplied for %2$s" 109 113 msgstr "" 110 114 111 115 #. translators: %1$s - The class name where we expect the supplied parameter to exist. 112 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:1 23116 #: src/E20R/members-list/admin/bulk/Bulk_Operations.php:145 113 117 msgid "Invalid parameter supplied for %1$s" 114 118 msgstr "" … … 154 158 155 159 #: src/E20R/members-list/admin/bulk/Bulk_Update.php:408 160 #: src/E20R/members-list/Members_List.php:2163 161 #: src/E20R/members-list/Members_List.php:2177 156 162 msgid "Unknown" 157 163 msgstr "" 158 164 159 #: src/E20R/members-list/admin/export/Export_Members.php:100 5165 #: src/E20R/members-list/admin/export/Export_Members.php:1006 160 166 msgid "Error - Undefined HTTP headers. Exiting!" 161 167 msgstr "" 162 168 163 #: src/E20R/members-list/admin/export/Export_Members.php:101 4169 #: src/E20R/members-list/admin/export/Export_Members.php:1015 164 170 msgid "Cannot transmit export file. Review web server error logs for notices/warnings/errors. Exiting!" 165 171 msgstr "" 166 172 167 #: src/E20R/members-list/admin/export/Export_Members.php:102 6173 #: src/E20R/members-list/admin/export/Export_Members.php:1027 168 174 msgid "Error: No export data found to transmit..." 169 175 msgstr "" … … 251 257 msgstr "" 252 258 253 #: src/E20R/members-list/Members_List.php:3 00259 #: src/E20R/members-list/Members_List.php:317 254 260 msgid "member" 255 261 msgstr "" 256 262 257 #: src/E20R/members-list/Members_List.php:3 01263 #: src/E20R/members-list/Members_List.php:318 258 264 msgid "members" 259 265 msgstr "" 260 266 261 #: src/E20R/members-list/Members_List.php:4 07267 #: src/E20R/members-list/Members_List.php:446 262 268 msgid "Error: Invalid list of tables & joins for member list!" 263 269 msgstr "" 264 270 265 #: src/E20R/members-list/Members_List.php:4 15271 #: src/E20R/members-list/Members_List.php:454 266 272 msgid "Error: No FROM table specified for member list!" 267 273 msgstr "" 268 274 269 275 #. translators: %1$s is the per-page value supplied by the e20r_memberslist_per_page filter. 270 #: src/E20R/members-list/Members_List.php: 568276 #: src/E20R/members-list/Members_List.php:607 271 277 msgid "Error: Invalid 'per_page' value (need an integer): %1$s" 272 278 msgstr "" 273 279 274 280 #. translators: %1$s - The error message returned by the exception thrown 275 #: src/E20R/members-list/Members_List.php:7 10281 #: src/E20R/members-list/Members_List.php:749 276 282 msgid "Cannot create a valid Database Query. Error message: %1$s" 277 283 msgstr "" 278 284 279 285 #. translators: %1$s - The error message returned by the exception thrown 280 #: src/E20R/members-list/Members_List.php:7 35286 #: src/E20R/members-list/Members_List.php:774 281 287 msgid "Cannot execute database query. Error message: %1$s" 282 288 msgstr "" 283 289 284 #: src/E20R/members-list/Members_List.php:10 09290 #: src/E20R/members-list/Members_List.php:1051 285 291 msgid "Insecure action denied." 286 292 msgstr "" 287 293 288 #. translators: Error message from the execption thrown289 #: src/E20R/members-list/Members_List.php:10 48290 msgid " Cannot export data!. Error message: %1$s"291 msgstr "" 292 293 #: src/E20R/members-list/Members_List.php:11 02294 #: src/E20R/members-list/Members_List.php:11 11294 #. translators: %1$s - Error message from exception 295 #: src/E20R/members-list/Members_List.php:1085 296 msgid "Bulk Cancel: %1$s" 297 msgstr "" 298 299 #: src/E20R/members-list/Members_List.php:1146 300 #: src/E20R/members-list/Members_List.php:1155 295 301 msgid "Error cancelling membership(s)" 296 302 msgstr "" 297 303 298 304 #. translators: %1$s - Error message from thrown exception(s) 299 #: src/E20R/members-list/Members_List.php:1 163300 #: src/E20R/members-list/Members_List.php:1 178305 #: src/E20R/members-list/Members_List.php:1207 306 #: src/E20R/members-list/Members_List.php:1222 301 307 msgid "Cannot export member data! Error message: %1$s" 302 308 msgstr "" 303 309 304 310 #. translators: %1$s query string. 305 #: src/E20R/members-list/Members_List.php:1 264311 #: src/E20R/members-list/Members_List.php:1320 306 312 msgid "Error processing Members List database query: %1$s" 307 313 msgstr "" 308 314 309 315 #. translators: %1$s - The class parameter, %2$s - The class name where we expect said parameter to exist. 310 #: src/E20R/members-list/Members_List.php:1 298316 #: src/E20R/members-list/Members_List.php:1354 311 317 msgid "%1$s is not a member variable in %2$s" 312 318 msgstr "" 313 319 314 #: src/E20R/members-list/Members_List.php:1 380320 #: src/E20R/members-list/Members_List.php:1436 315 321 msgid "Error: Invalid database configuration!!!" 316 322 msgstr "" 317 323 318 #: src/E20R/members-list/Members_List.php:14 06324 #: src/E20R/members-list/Members_List.php:1462 319 325 msgid "Missing the \"FROM\" statement" 320 326 msgstr "" 321 327 322 #: src/E20R/members-list/Members_List.php:14 17328 #: src/E20R/members-list/Members_List.php:1473 323 329 msgid "Missing the minimum expected number of \"JOIN\" statements" 324 330 msgstr "" 325 331 326 332 #. translators: %1$d the number of JOIN entries in the Meembers_List::table_list definition 327 #: src/E20R/members-list/Members_List.php:14 35333 #: src/E20R/members-list/Members_List.php:1491 328 334 msgid "Have %1$d JOIN entries, but need at least 3!" 329 335 msgstr "" 330 336 331 #: src/E20R/members-list/Members_List.php:1 456337 #: src/E20R/members-list/Members_List.php:1512 332 338 msgid "Does not include the \"JOIN\" to support membership level search, but specified a level" 333 339 msgstr "" 334 340 335 #: src/E20R/members-list/Members_List.php:1 468341 #: src/E20R/members-list/Members_List.php:1524 336 342 msgid "Unexpected condition value for the 4th \"JOIN\" element" 337 343 msgstr "" 338 344 339 345 #. translators: %1$s table name defined by PMPro, %2$s Unexpected table name from user/developer 340 #: src/E20R/members-list/Members_List.php:1 482346 #: src/E20R/members-list/Members_List.php:1538 341 347 msgid "Unexpected table name value for the 4th \"JOIN\" element. Want: \"%1$s\", got: \"%2$s\"" 342 348 msgstr "" 343 349 344 #: src/E20R/members-list/Members_List.php:1 645345 #: src/E20R/members-list/Members_List.php:20 57346 #: src/E20R/members-list/Members_List.php:2 154350 #: src/E20R/members-list/Members_List.php:1701 351 #: src/E20R/members-list/Members_List.php:2097 352 #: src/E20R/members-list/Members_List.php:2233 347 353 msgid "Cancel" 348 354 msgstr "" 349 355 350 #: src/E20R/members-list/Members_List.php:1 646351 #: src/E20R/members-list/Members_List.php:17 20356 #: src/E20R/members-list/Members_List.php:1702 357 #: src/E20R/members-list/Members_List.php:1776 352 358 msgid "Update" 353 359 msgstr "" 354 360 355 #: src/E20R/members-list/Members_List.php:1 647361 #: src/E20R/members-list/Members_List.php:1703 356 362 msgid "Export" 357 363 msgstr "" 358 364 359 #: src/E20R/members-list/Members_List.php:17 19365 #: src/E20R/members-list/Members_List.php:1775 360 366 msgid "Update member info" 361 367 msgstr "" 362 368 363 #: src/E20R/members-list/Members_List.php:18 18364 #: src/E20R/members-list/Members_List.php:18 35369 #: src/E20R/members-list/Members_List.php:1874 370 #: src/E20R/members-list/Members_List.php:1891 365 371 msgid "Not found" 366 372 msgstr "" 367 373 368 #: src/E20R/members-list/Members_List.php:1838 374 #: src/E20R/members-list/Members_List.php:1894 375 #: src/E20R/members-list/Members_List.php:2168 376 #: src/E20R/members-list/Members_List.php:2181 369 377 msgid "N/A" 370 378 msgstr "" 371 379 372 #: src/E20R/members-list/Members_List.php:1877 373 #: src/E20R/members-list/modules/Multiple_Memberships.php:93 380 #: src/E20R/members-list/Members_List.php:1940 381 #: src/E20R/members-list/Members_List.php:2306 382 #: src/E20R/members-list/modules/Multiple_Memberships.php:124 383 msgid "Reset" 384 msgstr "" 385 386 #: src/E20R/members-list/Members_List.php:2010 387 msgid "Free" 388 msgstr "" 389 390 #: src/E20R/members-list/Members_List.php:2062 391 msgid "Invalid" 392 msgstr "" 393 394 #: src/E20R/members-list/Members_List.php:2128 374 395 msgid "No levels found. Paid Memberships Pro is inactive!" 375 396 msgstr "" 376 397 377 #: src/E20R/members-list/Members_List.php:1901 378 #: src/E20R/members-list/Members_List.php:2229 379 #: src/E20R/members-list/modules/Multiple_Memberships.php:117 380 msgid "Reset" 381 msgstr "" 382 383 #: src/E20R/members-list/Members_List.php:1970 384 msgid "Free" 385 msgstr "" 386 387 #: src/E20R/members-list/Members_List.php:2022 388 msgid "Invalid" 389 msgstr "" 390 391 #: src/E20R/members-list/Members_List.php:2086 392 msgid "Never" 393 msgstr "" 394 395 #. translators: %1$s HTML %2$s formatted payment amount, %3$s HTML. 396 #: src/E20R/members-list/Members_List.php:2103 398 #. translators: %1$s - HTML %2$s - next payment date, %3$s - HTML. 399 #: src/E20R/members-list/Members_List.php:2184 397 400 msgid "N/A (%1$sNext Payment: %2$s%3$s)" 398 401 msgstr "" 399 402 400 #: src/E20R/members-list/Members_List.php:2 159401 msgid " Bulk update membership end/expiration date"402 msgstr "" 403 404 #: src/E20R/members-list/Members_List.php:2 234403 #: src/E20R/members-list/Members_List.php:2238 404 msgid "Update membership end/expiration date" 405 msgstr "" 406 407 #: src/E20R/members-list/Members_List.php:2311 405 408 msgid "Update the member's membership status" 406 409 msgstr "" 407 410 408 #: src/E20R/members-list/Members_List.php:2 362411 #: src/E20R/members-list/Members_List.php:2439 409 412 msgid "No members found" 410 413 msgstr "" 411 414 412 #: src/E20R/members-list/Members_List.php:2 366415 #: src/E20R/members-list/Members_List.php:2443 413 416 msgid "It's possible the information you're looking for can be found in one of the following categories:" 414 417 msgstr "" 415 418 416 419 #. translators: %1$s HTML, %2%s HTML. 417 #: src/E20R/members-list/Members_List.php:2 373420 #: src/E20R/members-list/Members_List.php:2450 418 421 msgid "Repeat search: %1$sActive Members list%2$s" 419 422 msgstr "" 420 423 421 424 #. translators: %1$s HTML, %2%s HTML. 422 #: src/E20R/members-list/Members_List.php:2 386425 #: src/E20R/members-list/Members_List.php:2463 423 426 msgid "Repeat search: %1$sAll Members list%2$s" 424 427 msgstr "" 425 428 426 429 #. translators: %1$s HTML, %2$s HTML. 427 #: src/E20R/members-list/Members_List.php:2 398430 #: src/E20R/members-list/Members_List.php:2475 428 431 msgid "Repeat search: %1$sCancelled Members list%2$s" 429 432 msgstr "" 430 433 431 434 #. translators: %1$s HTML, %2$s HTML. 432 #: src/E20R/members-list/Members_List.php:24 13435 #: src/E20R/members-list/Members_List.php:2490 433 436 msgid "Repeat search: %1$sExpired Members list%2$s" 434 437 msgstr "" 435 438 436 439 #. translators: %1$s HTML, %2$s HTML. 437 #: src/E20R/members-list/Members_List.php:2 425440 #: src/E20R/members-list/Members_List.php:2502 438 441 msgid "Repeat search: %1$sOld Members list%2$s" 439 442 msgstr "" 440 443 441 444 #. translators: %1$s HTML, %2$s HTML. 442 #: src/E20R/members-list/Members_List.php:2 436445 #: src/E20R/members-list/Members_List.php:2513 443 446 msgid "Repeat search: %1$sAll Users list%2$s" 444 447 msgstr "" 448 449 #: src/E20R/members-list/modules/Multiple_Memberships.php:84 450 msgid "primary" 451 msgstr "" 452 453 #: src/E20R/members-list/modules/Multiple_Memberships.php:133 454 msgid "Click to edit primary membership level" 455 msgstr "" -
e20r-members-list/trunk/src/E20R/members-list/Members_List.php
r2672951 r2700272 36 36 use E20R\Utilities\Message; 37 37 use E20R\Utilities\Utilities; 38 use stdClass; 38 39 use WP_List_Table; 40 use WP_User; 39 41 40 42 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { … … 46 48 } 47 49 48 if ( ! class_exists( ' \\E20R\\Members_List\\Members_List' ) ) {50 if ( ! class_exists( 'E20R\Members_List\Members_List' ) ) { 49 51 50 52 /** … … 272 274 273 275 /** 276 * List of date values we consider 'empty' (not confiured/set/defined) 277 * 278 * @var mixed|void|null $empty_date_values 279 */ 280 private $empty_date_values = array(); 281 282 /** 274 283 * Members_List constructor. 275 284 * … … 288 297 $this->action = $this->utils->get_variable( 'action', '' ); 289 298 $this->date_format = get_option( 'date_format' ); 299 300 // Default settings for what we consider 'empty' (not set) date values 301 add_filter( 302 'e20r_members_list_empty_date_values', 303 array( $this, 'set_empty_date_values' ), 304 -1, 305 1 306 ); 290 307 291 308 if ( empty( $page ) ) { … … 326 343 ); 327 344 345 // Configure the defaults for the empty/not set date values 346 $this->empty_date_values = apply_filters( 'e20r_members_list_empty_date_values', array() ); 347 328 348 if ( $this->is_module_enabled( 'pmpro-multiple-memberships-per-user/pmpro-multiple-memberships-per-user.php' ) ) { 329 349 // TODO: Add actions to process data for the PMPro Multiple Memberships Per User add-on 330 350 $this->utils->log( 'Enable the MMPU extensions' ); 331 351 } 352 332 353 333 354 /** … … 341 362 } 342 363 364 /** 365 * Define the list of 'empty' values for the date check(s) 366 * 367 * @param array $values Received list of values 368 * 369 * @return array 370 */ 371 public function set_empty_date_values( $values = array() ) { 372 return $values + array( 373 '', 374 null, 375 0, 376 '0', 377 '0000-00-00 00:00:00', 378 '0000-00-00', 379 '00:00:00', 380 ); 381 } 343 382 /** 344 383 * Generate the SQL and set the sql_query member variable to use the query … … 961 1000 // Some of the default columns are sortable. 962 1001 switch ( $col ) { 1002 case 'display_name': 963 1003 case 'user_login': 964 1004 case 'user_email': … … 967 1007 case 'status': 968 1008 case 'last': 969 case 'last_name':970 1009 $sortable_columns[ $col ] = array( $col, false ); 971 1010 break; … … 997 1036 998 1037 // Are we processing a bulk action? 999 if ( 1 === preg_match( '/bulk-/', $a ) || 1 === preg_match( '/bulk-/', $a2 ) ) { 1038 if ( 1039 ( null !== $a && 1 === preg_match( '/bulk-/', $a ) ) || 1040 ( null !== $a2 && 1 === preg_match( '/bulk-/', $a2 ) ) 1041 ) { 1000 1042 1001 1043 $this->utils->log( 'Processing a bulk action' ); … … 1018 1060 $action = $this->current_action(); 1019 1061 $data = array(); 1020 $selected_members = $this->utils->get_variable( 'member_user_id ', array() );1062 $selected_members = $this->utils->get_variable( 'member_user_ids', array() ); 1021 1063 1022 1064 foreach ( $selected_members as $key => $user_id ) { … … 1034 1076 1035 1077 if ( in_array( 'bulk-cancel', $bulk_actions, true ) ) { 1036 $this->cancel = new Bulk_Cancel( $data, $this->utils ); 1037 $this->cancel->execute(); 1078 try { 1079 $this->cancel = new Bulk_Cancel( $data, $this->utils ); 1080 $this->cancel->execute(); 1081 } catch ( InvalidProperty $e ) { 1082 $this->utils->add_message( 1083 sprintf( 1084 // translators: %1$s - Error message from exception 1085 esc_attr__( 'Bulk Cancel: %1$s', 'e20r-members-list' ), 1086 $e->getMessage() 1087 ), 1088 'error', 1089 'backend' 1090 ); 1091 } 1092 1038 1093 return; 1039 1094 … … 1041 1096 1042 1097 $this->utils->log( 'Requested Export of members!' ); 1043 try { 1044 $this->export_members(); 1045 } catch ( DBQueryError | InvalidSQL $e ) { 1046 $msg = sprintf( 1047 // translators: Error message from the execption thrown 1048 esc_attr__( 'Cannot export data!. Error message: %1$s', 'e20r-members-list' ), 1049 $e->getMessage() 1050 ); 1051 $this->utils->add_message( $msg, 'error', 'backend' ); 1052 return; 1053 } 1098 $this->export_members(); 1054 1099 1055 1100 // To push the export file to the browser, we have to terminate execution of this process. … … 1078 1123 $this->utils->log( 'Single action for the Members List...' ); 1079 1124 1080 $user_id = $this->utils->get_variable( 'member_user_id ', array() );1125 $user_id = $this->utils->get_variable( 'member_user_ids', array() ); 1081 1126 $level_id = $this->utils->get_variable( 'membership_id', array() ); 1082 1127 $action = $this->current_action(); … … 1097 1142 $this->cancel = new Bulk_Cancel( $user_ids, $this->utils ); 1098 1143 } 1099 1100 1144 1101 1145 if ( false === $this->cancel->execute() ) { … … 1205 1249 * @throws InvalidSQL Raised when there's a problem with the SQL we generated. 1206 1250 * @throws DBQueryError Raised if the DB Query reports a problem 1207 * @throws BadOperation|InvalidSettingsKey Raised when the Cache() class tries something unexpected1208 1251 */ 1209 1252 public function get_members( $per_page = null, $page_number = null, $return_count = false ) { … … 1213 1256 } 1214 1257 1215 $result = null;1216 1258 $result_cache_group = $this->page->get( 'result_cache_group' ); 1217 $cache_timeout = ( (int) $this->page->get( 'cache_timeout' ) * self::MINUTE_IN_SECONDS ); 1259 try { 1260 $cache_timeout = ( (int) $this->page->get( 'cache_timeout' ) * self::MINUTE_IN_SECONDS ); 1261 } catch ( InvalidSettingsKey $e ) { 1262 $this->utils->log( 'Error: cache_timeout is not a valid settings key!' ); 1263 $cache_timeout = 10 * self::MINUTE_IN_SECONDS; 1264 } 1218 1265 1219 1266 global $wpdb; … … 1221 1268 if ( empty( $this->sql_query ) || 1 !== preg_match_all( '/SELECT\s+.*\s+FROM(\s+(.*)){1,}/im', $this->sql_query ) ) { 1222 1269 $this->utils->log( 'Error in/Missing SQL statement! "' . $this->sql_query . '"' ); 1223 throw new InvalidSQL( ' Error:Attempting to fetch data without a valid SQL query!' );1270 throw new InvalidSQL( 'Attempting to fetch data without a valid SQL query!' ); 1224 1271 } 1225 1272 1226 1273 // Generate the Cache key based on 1227 1274 $lookup_cache_key = md5( $this->sql_query ); 1228 $result = Cache::get( $lookup_cache_key, $result_cache_group ); 1275 try { 1276 $result = Cache::get( $lookup_cache_key, $result_cache_group ); 1277 } catch ( BadOperation $e ) { 1278 $this->utils->log( 'Error returning cached results for ' . $lookup_cache_key ); 1279 $result = null; 1280 } 1229 1281 1230 1282 if ( null === $result ) { … … 1251 1303 // Save the result to the cache based on the cache specific key 1252 1304 $this->utils->log( "Attempting to cache the results for {$lookup_cache_key}" ); 1253 if ( false === Cache::set( $lookup_cache_key, $result, $cache_timeout, $result_cache_group ) ) { 1254 $this->utils->log( 'Error saving the result to cache!' ); 1305 try { 1306 if ( false === Cache::set( $lookup_cache_key, $result, $cache_timeout, $result_cache_group ) ) { 1307 $this->utils->log( 'Error saving the result to cache!' ); 1308 } 1309 } catch ( BadOperation $e ) { 1310 $this->utils->log( 'Error: Unable to save to cache for ' . $lookup_cache_key ); 1255 1311 } 1256 1312 } else { … … 1567 1623 $this->utils->log( 'Requesting (active/old/etc) member export' ); 1568 1624 1569 $member_ids = $this->utils->get_variable( 'member_user_id ', array() );1625 $member_ids = $this->utils->get_variable( 'member_user_ids', array() ); 1570 1626 $added_where = null; 1571 1627 … … 1677 1733 */ 1678 1734 public function column_user_login( $item ) { 1679 $user = new \WP_User( $item['ID'] );1735 $user = new WP_User( $item['ID'] ); 1680 1736 1681 1737 $edit_url = add_query_arg( … … 1832 1888 1833 1889 if ( empty( $address ) ) { 1834 1890 $this->utils->log( "User {$item['ID']} has no billing address" ); 1835 1891 $address = esc_attr__( 'Not found', 'e20r-members-list' ); 1836 1892 … … 1858 1914 <input type="hidden" value="%3$s" class="e20r-members-list-membership_id-label" name="e20r-members-list-membership_label_%2$s" /> 1859 1915 <input type="hidden" value="%1$d" class="e20r-members-list-db-membership_id" name="e20r-members-list-db_membership_id_%2$s" /> 1916 <input type="hidden" value="" class="e20r-members-list-db-membership_level_ids" name="e20r-members-list-db_membership_level_ids_%2$s" /> 1860 1917 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 1861 1918 <input type="hidden" value="%4$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', … … 1867 1924 ); 1868 1925 1869 $options = ''; 1870 if ( function_exists( 'pmpro_getAllLevels' ) ) { 1871 $levels = pmpro_getAllLevels( true, true ); 1872 } else { 1873 1874 // Default info if PMPro is disabled. 1875 $null_level = new \stdClass(); 1876 $null_level->level_id = 0; 1877 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 1878 $levels = array( $null_level ); 1879 } 1880 1881 foreach ( $levels as $level ) { 1882 $options .= sprintf( 1883 '<option value="%1$s" %2$s>%3$s</option>', 1884 $level->id, 1885 selected( $level->id, $item['membership_id'], false ), 1886 $level->name 1887 ) . "\n"; 1888 } 1926 $options = self::build_option_string( $item['membership_id'] ); 1927 1889 1928 $new_membershiplevel_input = sprintf( 1890 1929 '<div class="ml-row-settings clearfix"> … … 1931 1970 if ( true === $this->is_module_enabled( 'pmpro-multiple-memberships-per-user/pmpro-multiple-memberships-per-user.php' ) ) { 1932 1971 $mmpu = new Multiple_Memberships(); 1933 return $mmpu->column_name( $item ); 1972 $this->utils->log( 'MMPU support is deactivated pending support for adding/removing MMPU memberships' ); 1973 // return $mmpu->multiple_membership_column( $item ); FIXME: Actually enable MMPU support 1934 1974 } 1935 1975 … … 1979 2019 * @param array $item The record to process. 1980 2020 * 1981 * @return \stdClass2021 * @return stdClass 1982 2022 */ 1983 2023 public function column_code( $item ) { … … 2018 2058 public function column_startdate( $item ) { 2019 2059 2020 if ( '0000-00-00 00:00:00' === $item['startdate'] || empty( $item['startdate'] ) ) {2060 if ( $this->has_empty_date( $item['startdate'] ) ) { 2021 2061 $date_value = null; 2022 2062 $start_label = esc_attr__( 'Invalid', 'e20r-members-list' ); 2023 2063 } else { 2024 $date_value = ! empty( $item['startdate'] ) ? date_i18n( 'Y-m-d', strtotime( $item['startdate'], time() ) ) : null;2025 $start_label = date_i18n( $this->date_format, strtotime( $item['startdate'], time() ) );2026 } 2027 2028 $min_val = empty( $item['startdate'] ) ? sprintf( 'min="%s"', date_i18n( 'Y-m-d', time() ) ) : null;2064 $date_value = ! empty( $item['startdate'] ) ? gmdate( 'Y-m-d', strtotime( $item['startdate'], time() ) ) : null; 2065 $start_label = gmdate( $this->date_format, strtotime( $item['startdate'], time() ) ); 2066 } 2067 2068 $min_val = empty( $item['startdate'] ) ? sprintf( 'min="%s"', gmdate( 'Y-m-d', time() ) ) : null; 2029 2069 2030 2070 $startdate_input = sprintf( … … 2069 2109 2070 2110 /** 2111 * Generate the HTML for the membership level options 2112 * 2113 * @param int $level_id The membership level ID to highlight 2114 * 2115 * @return string 2116 */ 2117 public static function build_option_string( $level_id ) { 2118 2119 $options = ''; 2120 2121 if ( function_exists( 'pmpro_getAllLevels' ) ) { 2122 $levels = pmpro_getAllLevels( true, true ); 2123 } else { 2124 2125 // Default info if PMPro is disabled. 2126 $null_level = new stdClass(); 2127 $null_level->level_id = 0; 2128 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 2129 $levels = array( $null_level ); 2130 } 2131 2132 foreach ( $levels as $level ) { 2133 $options .= sprintf( 2134 '<option value="%1$s" %2$s>%3$s</option>', 2135 $level->id, 2136 selected( $level->id, $level_id, false ), 2137 $level->name 2138 ) . "\n"; 2139 } 2140 2141 return $options; 2142 } 2143 /** 2144 * Does the received value represent an 'empty' date value 2145 * 2146 * @param string|int|null $date The value to check 2147 * 2148 * @return bool 2149 */ 2150 public function has_empty_date( $date ) { 2151 return in_array( $date, $this->empty_date_values, true ); 2152 } 2153 /** 2071 2154 * Create the last column for the default Members_List table (Expiration date) 2072 2155 * … … 2077 2160 public function column_last( $item ) { 2078 2161 2079 $enddate = null; 2080 2081 // No billing amount (not recurring) and no end date (i.e. a free, limit less membership) 2082 if ( 2083 empty( $item['billing_amount'] ) && 2084 ( empty( $item['enddate'] ) || '0000-00-00 00:00:00' === $item['enddate'] ) 2085 ) { 2086 $enddate_label = esc_attr__( 'Never', 'e20r-members-list' ); 2087 $enddate_label = null; 2088 } else { 2089 $enddate = date_i18n( 2090 'Y-m-d', 2162 $no_enddate = false; 2163 $html_label = esc_attr__( 'Unknown', 'e20r-members-list' ); 2164 $enddate_label = ''; 2165 2166 // The end-date field is empty/not configured (never ending membership) 2167 if ( ! isset( $item['enddate'] ) || in_array( $item['enddate'], $this->empty_date_values, true ) ) { 2168 $enddate_label = esc_attr__( 'N/A', 'e20r-members-list' ); 2169 $html_label = $enddate_label; 2170 $no_enddate = true; 2171 } 2172 2173 // We have a user with a recurring billing membership. 2174 if ( true === $no_enddate && ! empty( $item['billing_amount'] ) && ! empty( $item['cycle_number'] ) ) { 2175 $next_payment = pmpro_next_payment( $item['ID'], 'success', 'timestamp' ); 2176 if ( false === $next_payment ) { 2177 $next_payment_date = esc_attr__( 'Unknown', 'e20r-members-list' ); 2178 } else { 2179 $next_payment_date = gmdate( $this->date_format, $next_payment ); 2180 } 2181 $enddate_label = esc_attr__( 'N/A', 'e20r-members-list' ); 2182 $html_label = sprintf( 2183 // translators: %1$s - HTML %2$s - next payment date, %3$s - HTML. 2184 esc_attr__( 'N/A (%1$sNext Payment: %2$s%3$s)', 'e20r-members-list' ), 2185 '<span class="e20r-members-list-small" style="font-size: 10px; font-style: italic;">', 2186 $next_payment_date, 2187 '</span>' 2188 ); 2189 } 2190 2191 // Setting the date label only if the value is non-empty _AND_ the value is _NOT_ '' 2192 if ( false === $no_enddate ) { 2193 $enddate_label = gmdate( 2194 $this->date_format, 2091 2195 strtotime( $item['enddate'], time() ) 2092 2196 ); 2093 $enddate_label = date_i18n( $this->date_format, strtotime( $item['enddate'], time() ) ); 2094 } 2095 2096 // The membership level has recurring payment. 2097 if ( 2098 ( empty( $item['enddate'] ) || '0000-00-00 00:00:00' === $item['enddate'] ) && 2099 ! empty( $item['billing_amount'] ) && ! empty( $item['cycle_number'] ) 2100 ) { 2101 $enddate_label = sprintf( 2102 // translators: %1$s HTML %2$s formatted payment amount, %3$s HTML. 2103 esc_attr__( 'N/A (%1$sNext Payment: %2$s%3$s)', 'e20r-members-list' ), 2104 '<span class="e20r-members-list-small" style="font-size: 10px; font-style: italic;">', 2105 date_i18n( 2106 $this->date_format, 2107 pmpro_next_payment( 2108 $item['ID'], 2109 'success', 2110 'timestamp' 2111 ) 2112 ), 2113 '</span>' 2114 ); 2115 $enddate = null; 2116 } 2117 2118 $enddate = apply_filters( 'e20r_members_list_enddate_col_result', $enddate, $item ); 2197 $html_label = $enddate_label; 2198 } 2119 2199 2120 2200 // BUG FIX: Didn't set the date format to match the WP setting as documented. 2121 $date_value = ! (2122 empty( $item['enddate'] ) ||2123 '0000-00-00 00:00:00' === $item['enddate'] ) ?2124 date_i18n( $this->date_format, strtotime( $item['enddate'], time() ) ) : 2125 null;2201 $date_value = ( false === $no_enddate ) ? 2202 strtotime( $item['enddate'], time() ) : 2203 null; 2204 2205 $date_value = apply_filters( 'e20r_members_list_enddate_col_result', $date_value, $item ); 2126 2206 2127 2207 // These are used to configure the enddate with JavaScript. … … 2133 2213 <input type="hidden" value="%4$s" class="e20r-members-list-db-enddate" name="e20r-members-list-db_enddate_%2$s" /> 2134 2214 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 2135 <input type="hidden" value=" %6$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />',2215 <input type="hidden" value="enddate" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', 2136 2216 $item['membership_id'], 2137 2217 $item['ID'], … … 2139 2219 $date_value, 2140 2220 $item['record_id'], 2141 'enddate'2142 2221 ); 2143 2222 … … 2146 2225 %1$s 2147 2226 <input type="date" placeholder="YYYY-MM-DD" pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))" title="Enter a date in this format YYYY-MM-DD" name="e20r-members-list-new_enddate_%2$s" class="e20r-members-list-input-enddate" value="%3$s"/> 2148 <br >2227 <br /> 2149 2228 <a href="#" class="e20r-members-list-cancel e20r-members-list-list-link">%4$s</a> 2150 2229 </div>', 2151 2230 $enddate_input, 2152 2231 $item['ID'], 2153 $date_value ,2232 $date_value ? gmdate( 'Y-m-d', $date_value ) : $date_value, 2154 2233 esc_attr__( 'Cancel', 'e20r-members-list' ) 2155 2234 ); 2156 2235 2157 $value =sprintf(2158 '<a href="#" class="e20r-members-list_enddate e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></ a></span>%3$s',2159 esc_attr__( ' Bulk update membership end/expiration date', 'e20r-members-list' ),2160 $ enddate_label,2236 return sprintf( 2237 '<a href="#" class="e20r-members-list_enddate e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></span></a>%3$s', 2238 esc_attr__( 'Update membership end/expiration date', 'e20r-members-list' ), 2239 $html_label, 2161 2240 $new_date_input 2162 2241 ); 2163 2164 return $value;2165 2242 } 2166 2243 … … 2221 2298 %3$s 2222 2299 </select> 2223 <br >2300 <br /> 2224 2301 <a href="#" class="e20r-members-list-cancel e20r-members-list-link">%4$s</a> 2225 2302 </div>', -
e20r-members-list/trunk/src/E20R/members-list/admin/bulk/Bulk_Cancel.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 25 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 26 use E20R\Members_List\Admin\Exceptions\PMProNotActive; 26 27 use E20R\Utilities\Message; 27 28 use E20R\Utilities\Utilities; 29 use function pmpro_cancelMembershipLevel; 28 30 29 31 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { … … 31 33 } 32 34 33 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Bulk\\Bulk_Cancel' ) ) {35 if ( ! class_exists( 'E20R\Members_List\Admin\Bulk\Bulk_Cancel' ) ) { 34 36 35 37 /** … … 41 43 * Bulk_Cancel constructor (singleton) 42 44 * 43 * @param array[]| int[]|null$members_to_update The array of member IDs to perform the bulk cancel operation against44 * @param Utilities|null $utils Instance of the E20R Utilities Module class45 * @param array[]|null $members_to_update The array of member IDs to perform the bulk cancel operation against 46 * @param Utilities|null $utils Instance of the E20R Utilities Module class 45 47 * 46 48 * @access public 47 49 * @throws InvalidProperty Thrown when the class or base class lacks the specified set() property 50 * @throws InvalidMemberList Thrown when the supplied array isn't null or has the wrong format 48 51 */ 49 public function __construct( $members_to_update = array(), $utils = null ) {52 public function __construct( $members_to_update = null, $utils = null ) { 50 53 51 54 if ( empty( $utils ) ) { … … 54 57 } 55 58 56 parent::__construct( $ utils );59 parent::__construct( $members_to_update, $utils ); 57 60 58 61 $this->set( 'operation', 'cancel' ); 59 $this->set( 'members_to_update', $members_to_update );60 62 } 61 63 … … 68 70 public function execute() { 69 71 72 if ( ! $this->pmpro_is_active() ) { 73 return false; 74 } 75 70 76 // Process all User & level ID for the single action. 71 77 $this->failed = array(); … … 74 80 // Process all selected records/members 75 81 foreach ( $this->members_to_update as $key => $cancel_info ) { 76 try { 77 if ( false === $this->cancel_member( $cancel_info['user_id'], $cancel_info['level_id'] ) ) { 78 if ( ! isset( $this->failed[ $cancel_info['user_id'] ] ) ) { 79 $this->failed[ $cancel_info['user_id'] ] = array(); 80 } 81 $this->failed[ $cancel_info['user_id'] ][] = $cancel_info['level_id']; // FIXME: Add level info for multiple membership levels 82 if ( false === $this->cancel_member( $cancel_info['user_id'], $cancel_info['level_id'] ) ) { 83 if ( ! isset( $this->failed[ $cancel_info['user_id'] ] ) ) { 84 $this->failed[ $cancel_info['user_id'] ] = array(); 82 85 } 83 } catch ( PMProNotActive $e ) { 84 $this->utils->add_message( $e->getMessage(), 'error', 'backend' ); 85 $this->failed[] = $cancel_info['user_id']; 86 return false; 86 $this->failed[ $cancel_info['user_id'] ][] = $cancel_info['level_id']; // FIXME: Add level info for multiple membership levels 87 87 } 88 88 } … … 144 144 throw new PMProNotActive( 145 145 esc_attr__( 146 ' Cannot find the pmpro_cancelMembershipLevel() function!',146 'The pmpro_cancelMembershipLevel() function is not defined. Is Paid Memberships Pro activated on this site?', 147 147 'e20r-members-list' 148 148 ) 149 149 ); 150 150 } 151 $this->utils->log( "Cancelling membership level {$id} for user {$id}" ); 152 return \pmpro_cancelMembershipLevel( $level_id, $id, 'admin_cancelled' ); 151 $this->utils->log( "Cancelling membership level {$level_id} for user {$id}" ); 152 return pmpro_cancelMembershipLevel( $level_id, $id, 'admin_cancelled' ); 153 } 154 155 /** 156 * Check if PMPro is active and throw exception if it isn't 157 * 158 * @return bool 159 */ 160 private function pmpro_is_active() { 161 return function_exists( 'pmpro_cancelMembershipLevel' ); 153 162 } 154 163 } -
e20r-members-list/trunk/src/E20R/members-list/admin/bulk/Bulk_Operations.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 25 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 26 use E20R\Utilities\Message; … … 47 48 * Array of WP_User IDs where the cancel operation failed 48 49 * 49 * @var null|int[] $failed50 * @var array[][] $failed 50 51 */ 51 protected $failed = null;52 protected $failed = array(); 52 53 53 54 /** 54 55 * Array of members to update where the memebr date is represented as an array per member 55 56 * 56 * @var array[] |int[]|null57 * @var array[] 57 58 */ 58 59 protected $members_to_update = array(); … … 68 69 * Bulk_Cancel constructor (singleton) 69 70 * 71 * @param array $members_to_update The list of members and level IDs to process 70 72 * @param Utilities|null $utils Instance of the E20R Utilities Module class 71 73 * 72 74 * @access public 73 75 */ 74 public function __construct( $ utils = null ) {76 public function __construct( $members_to_update = null, $utils = null ) { 75 77 76 78 if ( empty( $utils ) ) { … … 79 81 } 80 82 83 if ( null !== $members_to_update ) { 84 $this->set( 'members_to_update', $members_to_update ); 85 } 81 86 $this->utils = $utils; 82 87 } … … 89 94 * 90 95 * @throws InvalidProperty Raised if the user supplies an invalid class parameter 96 * @throws InvalidMemberList Raised if we're attempting to set the 'members_to_update' property and its value isn't appropriate 91 97 */ 92 98 public function set( string $param, $value ) { 99 100 if ( 101 'members_to_update' === $param && 102 null !== $value && 103 false === $this->valid_member_array( $value ) 104 ) { 105 throw new InvalidMemberList( 106 esc_attr__( 107 'The specified variable is not an array of user IDs', 108 'e20r-members-list' 109 ) 110 ); 111 } elseif ( 'members_to_update' === $param && null === $value ) { 112 $value = array(); 113 } 114 93 115 // Make sure we let the caller know there's a problem if the variable doesn't exist. 94 116 if ( ! property_exists( $this, $param ) ) { … … 131 153 132 154 /** 155 * Make sure the array is a valid user/membership level array 156 * 157 * @param mixed $members Variable (array) to test 158 * 159 * @return bool 160 */ 161 protected function valid_member_array( $members ) { 162 163 if ( ! is_array( $members ) ) { 164 return false; 165 } 166 167 if ( empty( $members ) ) { 168 return true; 169 } 170 171 return array_reduce( 172 $members, 173 function ( $result, $item ) { 174 return $result && 175 ( isset( $item['user_id'] ) && is_int( $item['user_id'] ) ) && 176 ( isset( $item['level_id'] ) && is_int( $item['level_id'] ) ); 177 }, 178 true 179 ); 180 } 181 182 /** 133 183 * Execute the Bulk Operation 134 184 * -
e20r-members-list/trunk/src/E20R/members-list/admin/bulk/Bulk_Update.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Bulk; 23 23 24 use DateTime; 25 use E20R\Members_List\Admin\Exceptions\InvalidMemberList; 24 26 use E20R\Members_List\Admin\Exceptions\InvalidProperty; 25 27 use E20R\Members_List\Admin\Exceptions\PMProNotActive; … … 41 43 * Bulk_Update constructor 42 44 * 43 * @param array$members Array of member information to update45 * @param null|array[] $members Array of member information to update 44 46 * @param null|Utilities $utils Instance of the E20R Utilities Module class 45 47 * 46 * @throws Invalid Property Raised if the specified class parameter for some reason is missing48 * @throws InvalidMemberList Raised if the supplied $members variable isn't a list of integers 47 49 */ 48 public function __construct( $members, $utils = null ) { 49 50 public function __construct( $members = null, $utils = null ) { 50 51 if ( empty( $utils ) ) { 51 52 $message = new Message(); 52 53 $utils = new Utilities( $message ); 53 54 } 54 55 parent::__construct( $utils ); 56 $this->set( 'members_to_update', $members ); 57 $this->set( 'operation', 'cancel' ); 55 $this->utils = $utils; 56 parent::__construct( $members, $this->utils ); 57 $this->set( 'operation', 'update' ); 58 58 } 59 59 … … 495 495 496 496 /** 497 * Return the list of members being updated498 *499 * @return array[]500 */501 public function get_members() {502 return $this->members_to_update;503 }504 505 /**506 497 * Test the date supplied for MySQL compliance 507 498 * … … 515 506 private function validate_date_format( $date, $format = 'Y-m-d' ) { 516 507 517 $check_date = \DateTime::createFromFormat( $format, $date );508 $check_date = DateTime::createFromFormat( $format, $date ); 518 509 519 510 return $check_date && $check_date->format( $format ) === $date; -
e20r-members-list/trunk/src/E20R/members-list/admin/export/Export_Members.php
r2672951 r2700272 30 30 } 31 31 32 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Export\\Export_Members' ) ) {32 if ( ! class_exists( 'E20R\Members_List\Admin\Export\Export_Members' ) ) { 33 33 34 34 /** … … 662 662 */ 663 663 private function enclose( $text ) { 664 $text = empty( $text ) ? '' : $text; 664 665 return '"' . str_replace( '"', '\\"', $text ) . '"'; 665 666 } -
e20r-members-list/trunk/src/E20R/members-list/js/e20r-memberslist-page.js
r2672951 r2700272 2 2 * License: 3 3 4 Copyright 2016-202 1- Eighty / 20 Results by Wicked Strong Chicks, LLC ([email protected])4 Copyright 2016-2022 - Eighty / 20 Results by Wicked Strong Chicks, LLC ([email protected]) 5 5 6 6 This program is free software; you can redistribute it and/or modify … … 23 23 24 24 let e20rMembersList_Page = { 25 init: function () { 26 27 // this.levels_dropdown = $('select#e20r-pmpro-memberslist-levels'); 28 // this.enddate_lnk = $('a.e20r-members-list_enddate'); 29 // this.cancelMemberLnk = $('a.e20r-cancel-member'); 30 // this.updateBtn = $('a.e20r-members-list-save'); 31 this.memberslist_form = $('.e20r-pmpro-memberslist-page form#posts-filter'); 32 this.edit_lnk = $('a.e20r-members-list-editable'); 33 this.resetBtn = $('a.e20r-members-list-cancel'); 34 this.updateMemberLnk = $('a.e20r-update-member'); 35 this.exportBtn = $('a.e20r-memberslist-export'); 36 this.changed_input = $('input[class^="e20r-members-list-input-"]'); 37 this.changed_select = $( 'select[class^="e20r-members-list-select-"]'); 38 this.levels_dropdown = $('#e20r-pmpro-memberslist-levels'); 39 this.bulkUpdate = $('#doaction, #doaction2'); 40 this.updateListBtn = $('#e20r-update-list'); 41 this.search_field = $('#post-search-input'); 42 this.dateFields = $('.e20r-members-list-input-enddate, .e20r-members-list-input-startdate'); 43 this.dataSearchBtn = $('#e20r-memberslist-search-data'); 44 this.bulkActionSelectTop = $('select#bulk-action-selector-top'); 45 this.bulkActionSelectBottom = $('select#bulk-action-selector-bottom'); 46 47 this.dateOpts = { 48 year: '2-digit', month: 'short', 49 day: 'numeric' 50 }; 51 52 let self = this; 53 54 self.dateFields.datepicker({ 55 dateFormat: "yy-mm-dd" 25 init: function () { 26 // this.levels_dropdown = $('select#e20r-pmpro-memberslist-levels'); 27 // this.enddate_lnk = $('a.e20r-members-list_enddate'); 28 // this.cancelMemberLnk = $('a.e20r-cancel-member'); 29 // this.updateBtn = $('a.e20r-members-list-save'); 30 this.memberslist_form = $('.e20r-pmpro-memberslist-page form#posts-filter'); 31 this.edit_lnk = $('a.e20r-members-list-editable'); 32 this.resetBtn = $('a.e20r-members-list-cancel'); 33 this.updateMemberLnk = $('a.e20r-update-member'); 34 this.exportBtn = $('a.e20r-memberslist-export'); 35 this.changed_input = $('input[class^="e20r-members-list-input-"]'); 36 this.changed_select = $( 'select[class^="e20r-members-list-select-"]'); 37 this.levels_dropdown = $('#e20r-pmpro-memberslist-levels'); 38 this.bulkUpdate = $('#doaction, #doaction2'); 39 this.updateListBtn = $('#e20r-update-list'); 40 this.search_field = $('#post-search-input'); 41 this.dateFields = $('.e20r-members-list-input-enddate, .e20r-members-list-input-startdate'); 42 this.dataSearchBtn = $('#e20r-memberslist-search-data'); 43 this.bulkActionSelectTop = $('select#bulk-action-selector-top'); 44 this.bulkActionSelectBottom = $('select#bulk-action-selector-bottom'); 45 46 this.dateOpts = { 47 year: '2-digit', month: 'short', 48 day: 'numeric' 49 }; 50 51 let self = this; 52 53 self.dateFields.datepicker({ 54 dateFormat: "yy-mm-dd" 55 }); 56 57 self.search_field.unbind('keypress').on('keypress', function(event){ 58 let keycode = (event.key ? event.key : event.keyCode); 59 if ('Enter' !== keycode) { 60 return; 61 } 62 self.dataSearchBtn.click(); 63 }); 64 65 // self.changed_input.unbind('blur').on('blur', function(ev) { 66 self.changed_input.unbind('blur').on('blur', function() { 67 self.set_update( this ); 68 }); 69 70 // self.updateListBtn.unbind('click').on('click', function(ev) { 71 self.updateListBtn.unbind('click').on('click', function(ev) { 72 $('#post-search-input').val(null); 73 74 if ( 'Clear Search' === self.updateListBtn.val() ) { 75 ev.preventDefault(); 76 window.console.log("We're clearing the search..."); 77 window.console.log(e20rml.url); //jshint ignore:line 78 window.location = e20rml.url; //jshint ignore:line 79 } 80 }); 81 82 // Trigger search whenever the levels drop-down is changed 83 self.levels_dropdown.unbind('change').on('change', function() { 84 self.dataSearchBtn.click(); 85 }); 86 87 /** 88 * Search operation initiated (a few other things simulate clicking the button) 89 */ 90 self.dataSearchBtn.unbind('click').on('click', function(event) { 91 let $search_string = $( '#post-search-input' ).val(); 92 let $level_string = $( '#e20r-pmpro-memberslist-levels' ).val(); 93 let $uri = window.location.toString(); 94 95 // If we have a string in the search box we'll append it to the URL 96 if ($search_string) { 97 event.preventDefault(); 98 99 // URL Encode the search string and add or replace it for the URI 100 $uri = self.add_to_uri( $uri, 'find', encodeURIComponent( $search_string ) ); 101 $uri = self.add_to_uri( $uri, 'level', encodeURIComponent( $level_string ) ); 102 window.console.log( 'New URI should be: ' + $uri ); 103 104 // Now we trigger the search 105 window.location = $uri; 106 107 // Clear the search field - Possible FIXME if user's do not want clearing the search field to happen 108 // $( '#post-search-input' ).val( null ); 109 } 110 }); 111 112 self.changed_select.unbind('change').on('change', function() { 113 let current_select = $(this); 114 let current_select_info = current_select.val(); 115 let $field_name = current_select.closest('div.ml-row-settings').find('.e20r-members-list-field-name').val(); 116 let previous_select_info = current_select.closest('div.ml-row-settings').find('input.e20r-members-list-db_' + $field_name ).val(); 117 let enddate = $('#the-list').find('.last .e20r-members-list-db-enddate').val(); 118 119 window.console.log("Previous value: " + previous_select_info ); 120 window.console.log("New value: " + current_select_info ); 121 122 if ( previous_select_info !== current_select_info && '' !== enddate ) { 123 if ( ! window.confirm(e20rml.lang.clearing_enddate) ) { //jshint ignore:line 124 return false; 125 } 126 } 127 128 self.set_update( this ); 129 }); 130 131 self.bulkUpdate.unbind('click').on('click', function (ev) { 132 133 ev.preventDefault(); 134 135 if ('bulk-export' === self.bulkActionSelectTop.val() || 'bulk-export' === self.bulkActionSelectBottom.val()) { 136 self.export_data(self); 137 return true; 138 } 139 140 self.memberslist_form.submit(); 141 }); 142 143 self.updateMemberLnk.unbind('click').on('click', function (ev) { 144 145 ev.preventDefault(); 146 let btn = $(this); 147 let row = btn.closest('tr'); 148 149 let membership_col = row.find('td.column-membership'); 150 let membership_settings = membership_col.find('div.ml-row-settings'); 151 let membership_label = membership_col.find('a.e20r-members-list-editable'); 152 153 let startdate_col = row.find('td.column-startdate'); 154 let startdate_settings = startdate_col.find('div.ml-row-settings'); 155 let startdate_label = startdate_col.find('a.e20r-members-list-editable'); 156 157 let enddate_col = row.find('td.column-last'); 158 let enddate_settings = enddate_col.find('div.ml-row-settings'); 159 let enddate_label = enddate_col.find('a.e20r-members-list-editable'); 160 161 let status_col = row.find( 'td.column-status'); 162 let status_settings = status_col.find('div.ml-row-settings'); 163 let status_label = status_col.find('a.e20r-members-list-editable'); 164 165 membership_label.toggle(); 166 membership_settings.toggle(); 167 168 status_label.toggle(); 169 status_settings.toggle(); 170 171 $('.column-joindate').each(function () { 172 $(this).toggle(); 56 173 }); 57 174 58 self.search_field.unbind('keypress').on('keypress', function(event){ 59 let keycode = (event.key ? event.key : event.keyCode); 60 if ('Enter' !== keycode) { 61 return; 62 } 63 64 self.dataSearchBtn.click(); 65 }); 66 67 // self.changed_input.unbind('blur').on('blur', function(ev) { 68 self.changed_input.unbind('blur').on('blur', function() { 69 self.set_update( this ); 70 }); 71 72 // self.updateListBtn.unbind('click').on('click', function(ev) { 73 self.updateListBtn.unbind('click').on('click', function(ev) { 74 $('#post-search-input').val(null); 75 76 if ( 'Clear Search' === self.updateListBtn.val() ) { 77 ev.preventDefault(); 78 window.console.log("We're clearing the search..."); 79 window.console.log(e20rml.url); //jshint ignore:line 80 window.location = e20rml.url; //jshint ignore:line 81 } 82 }); 83 84 // Trigger search whenever the levels drop-down is changed 85 self.levels_dropdown.unbind('change').on('change', function() { 86 self.dataSearchBtn.click(); 87 }); 88 89 /** 90 * Search operation initiated (a few other things simulate clicking the button) 91 */ 92 93 self.dataSearchBtn.unbind('click').on('click', function(event) { 94 95 let $search_string = $( '#post-search-input' ).val(); 96 let $level_string = $( '#e20r-pmpro-memberslist-levels' ).val(); 97 let $uri = window.location.toString(); 98 99 // If we have a string in the search box we'll append it to the URL 100 if ($search_string) { 101 event.preventDefault(); 102 103 // URL Encode the search string and add or replace it for the URI 104 $uri = self.add_to_uri( $uri, 'find', encodeURIComponent( $search_string ) ); 105 $uri = self.add_to_uri( $uri, 'level', encodeURIComponent( $level_string ) ); 106 window.console.log( 'New URI should be: ' + $uri ); 107 108 // Now we trigger the search 109 window.location = $uri; 110 111 // Clear the search field - Possible FIXME if user's do not want clearing the search field to happen 112 // $( '#post-search-input' ).val( null ); 113 } 114 }); 115 116 self.changed_select.unbind('change').on('change', function() { 117 118 let current_select = $(this); 119 let current_select_info = current_select.val(); 120 let $field_name = current_select.closest('div.ml-row-settings').find('.e20r-members-list-field-name').val(); 121 let previous_select_info = current_select.closest('div.ml-row-settings').find('input.e20r-members-list-db_' + $field_name ).val(); 122 let enddate = $('#the-list').find('.last .e20r-members-list-db-enddate').val(); 123 124 window.console.log("Previous value: " + previous_select_info ); 125 window.console.log("New value: " + current_select_info ); 126 127 if ( previous_select_info !== current_select_info && '' !== enddate ) { 128 if ( ! window.confirm(e20rml.lang.clearing_enddate) ) { //jshint ignore:line 129 return false; 130 } 131 } 132 133 self.set_update( this ); 134 }); 135 136 self.bulkUpdate.unbind('click').on('click', function (ev) { 137 138 ev.preventDefault(); 139 140 if ('bulk-export' === self.bulkActionSelectTop.val() || 'bulk-export' === self.bulkActionSelectBottom.val()) { 141 self.export_data(self); 142 return true; 143 } 144 145 self.memberslist_form.submit(); 146 }); 147 148 self.updateMemberLnk.unbind('click').on('click', function (ev) { 149 150 ev.preventDefault(); 151 let btn = $(this); 152 let row = btn.closest('tr'); 153 154 let membership_col = row.find('td.column-membership'); 155 let membership_settings = membership_col.find('div.ml-row-settings'); 156 let membership_label = membership_col.find('a.e20r-members-list-editable'); 157 158 let startdate_col = row.find('td.column-startdate'); 159 let startdate_settings = startdate_col.find('div.ml-row-settings'); 160 let startdate_label = startdate_col.find('a.e20r-members-list-editable'); 161 162 let enddate_col = row.find('td.column-last'); 163 let enddate_settings = enddate_col.find('div.ml-row-settings'); 164 let enddate_label = enddate_col.find('a.e20r-members-list-editable'); 165 166 let status_col = row.find( 'td.column-status'); 167 let status_settings = status_col.find('div.ml-row-settings'); 168 let status_label = status_col.find('a.e20r-members-list-editable'); 169 170 membership_label.toggle(); 171 membership_settings.toggle(); 172 173 status_label.toggle(); 174 status_settings.toggle(); 175 176 $('.column-joindate').each(function () { 177 $(this).toggle(); 178 }); 179 180 startdate_label.toggle(); 181 startdate_settings.toggle(); 182 183 enddate_label.toggle(); 184 enddate_settings.toggle(); 185 }); 186 187 // Process edit link 188 self.edit_lnk.unbind('click').on('click', function ($event) { 189 190 $event.preventDefault(); 191 192 let $edit = $(this); 193 let $input = $edit.next('div.ml-row-settings'); 194 195 $input.toggle(); 196 $edit.toggle(); 197 198 }); 199 200 /** 201 * Cancel membership link handler 202 */ 203 self.resetBtn.unbind('click').on('click', function (ev) { 204 205 ev.preventDefault(); 206 207 let $btn = $(this); 208 let $settings = $btn.closest('div.ml-row-settings'); 209 let $label = $settings.prev('a.e20r-members-list-editable'); 210 let $field_name = $settings.find('.e20r-members-list-field-name').val(); 211 212 // Return the select value (or input value) to its original value... 213 let $original = $settings.find('.e20r-members-list-db-' + $field_name ).val(); 214 let $field_input_key = 'e20r-members-list-new_' + $field_name; 215 216 $settings.find('select[name^="' + $field_input_key + '"], input[name^="'+ $field_input_key + '"]').val( $original ); 217 218 $settings.toggle(); 219 $label.toggle(); 220 }); 221 222 self.exportBtn.unbind('click').on('click', function (ev) { 223 ev.preventDefault(); 224 window.console.log("Export button clicked!"); 225 226 self.export_data( self ); 227 }); 228 }, 229 add_to_uri: function(url, paramName, paramValue) { 230 231 let pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)'); 232 233 if (paramValue === null) { 234 paramValue = ''; 235 } 236 237 if (url.search(pattern)>=0) { 238 return url.replace(pattern,'$1' + paramValue + '$2'); 239 } 240 241 url = url.replace(/[?#]$/,''); 242 return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue; 243 }, 244 export_data: function( self ) { 245 246 $("#overlay").fadeIn(300); 247 let inputs = $('.e20r-search-arguments input, .e20r-search-arguments textarea, .e20r-search-arguments select') 248 .not(':input[type=button], :input[type=submit], input[type=reset]'); 249 let export_args = self.prepare_export(self, inputs); 250 251 // Open a new tab to trigger the AJAX request for the download 252 let file_name = window.prompt( 'Filename for data to save/export', 'members_list' ); 253 let extension = '.csv'; 254 if ( file_name.toLowerCase().endsWith( '.csv' ) ) { 255 extension = ''; 256 } 257 self.download_csv(file_name + extension, export_args, ); //jshint ignore:line 258 }, 259 /** 260 * Set the request variables based on the members list page 261 * 262 * @param self Instance of this object 263 * @param inputs The HTML inputs 264 * @returns Object 265 */ 266 prepare_export: function( self, inputs ) { 267 let request_args = { 268 '_wpnonce': $('#_wpnonce').val(), 269 'showDebugTrace': true, 270 'action': 'e20rml_export_records', 271 }; 272 request_args.find = $( '#post-search-input' ).val(); 273 inputs.each(function () { 274 let input = $(this); 275 let name = input.attr('name'); 276 let value = input.val(); 277 if (false === self.is_empty(value)) { 278 window.console.log(name + " contains " + value); 279 request_args[name] = value; 280 } 281 }); 282 283 let is_checked = false; 284 let selected_ids = []; 285 $('input[name^="member_user_id"]').each(function () { 286 if ($(this).is(':checked')) { 287 is_checked = true; 288 selected_ids.push($(this).val()); 289 } 290 }); 291 if (selected_ids.length > 0) { 292 request_args.member_id = selected_ids; 293 } 294 // return self.build_export_url( export_args ); 295 return request_args; 296 }, 297 /** 298 * Download the exported .CSV data 299 * @param filename The file name to use for download 300 * @param download_args The POST argument(s) 301 */ 302 download_export: function(filename, download_args) { 303 fetch(e20rml.ajax_url, download_args) // jshint ignore:line 304 .then(response => { 305 if (!response.ok) { 306 throw new Error('Request failed.' + response); 307 } 308 }) 309 .then(response => response.blob()) 310 .then(blob => { 311 const link = window.document.createElement("a"); 312 link.href = URL.createObjectURL(blob); 313 link.download = filename; 314 link.click(); 315 }) 316 .catch(window.console.error); 317 }, 318 download_csv: function(file_name, attributes) { 319 let URL = e20rml.ajax_url; // jshint ignore:line 320 window.console.log( 'AJAX URL: ' + URL); 321 window.console.log( attributes ); 322 $.ajax({ 323 url: URL, 324 cache: false, 325 method: 'POST', 326 data: attributes, 327 accepts: 'text/csv', 328 xhr: function () { 329 var xhr = new XMLHttpRequest(); 330 xhr.onreadystatechange = function () { 331 if (xhr.readyState === 2) { 332 if (xhr.status === 200) { 333 xhr.responseType = "blob"; 334 } else { 335 xhr.responseType = "text"; 336 } 337 } 338 }; 339 return xhr; 340 }, 341 success: function (data) { 342 //Convert the Byte Data to BLOB object. 343 let blob = new Blob([data], { type: "text/csv" }); 344 345 //Check the Browser type and download the File. 346 let isIE = !!document.documentMode; 347 if (isIE) { 348 window.navigator.msSaveBlob(blob, file_name); 349 } else { 350 let url = window.URL || window.webkitURL; 351 let link = url.createObjectURL(blob); 352 let body = $('body'); 353 let a = $("<a />"); 354 a.attr("download", file_name); 355 a.attr("href", link); 356 body.append(a); 357 a[0].click(); 358 body.remove(a); 359 } 360 }, 361 error: function (jqXHR, textStatus, errorThrown) { 362 window.console.log( 'Status: ' + textStatus + ', Error thrown: ' + errorThrown ); 363 } 364 }).done(function() { 365 setTimeout(function(){ 366 $("#overlay").fadeOut(300); 367 },500); 368 }); 369 }, 370 set_update: function( $element ) { 371 let self = this; 372 373 let element = $($element); 374 let $settings = element.closest('div.ml-row-settings'); 375 // let users_id = $settings.find('input.e20r-members-list-user-id').val(); 376 let field_name = $settings.find('input.e20r-members-list-field-name').val(); 377 let $label = $settings.prev('a.e20r-members-list-' + field_name + '-label'); 378 // let $new_value_field = $settings.find('input.e20r-members-list-db-' + field_name); 379 let select = $settings.find('.e20r-members-list-select-' + field_name); 380 381 let $checkbox = element.closest('tr').find('th.check-column input[type="checkbox"]'); 382 383 let $date = $settings.find('.e20r-members-list-input-' + field_name).val(); 384 let select_val = select.val(); 385 let test_checked = false; 386 387 window.console.log("Value: ", $date, select_val); 388 389 if ('undefined' !== select_val) { 390 391 $label.text(select.find('option:selected').text()); 392 test_checked = true; 393 } 394 395 if (null !== $date) { 396 397 let date = new Date($date); 398 399 window.console.log("Date info? ", date); 400 401 if (Object.prototype.toString.call(date) === "[object Date]") { 402 $label.text(date.toLocaleDateString(e20rml.locale, self.dateOpts)); //jshint ignore:line 403 } 404 405 test_checked = true; 406 } 407 408 if (true === test_checked && false === $checkbox.is('checked')) { 409 window.console.log("Checkbox not checked. Fixing!"); 410 $checkbox.prop('checked', true); 411 412 self.bulkActionSelectBottom.val('bulk-update'); 413 self.bulkActionSelectTop.val('bulk-update'); 414 $('#doaction').val(e20rml.lang.save_btn_text); //jshint ignore:line 415 $('#doaction2').val(e20rml.lang.save_btn_text); //jshint ignore:line 416 } 417 /* 175 startdate_label.toggle(); 176 startdate_settings.toggle(); 177 178 enddate_label.toggle(); 179 enddate_settings.toggle(); 180 }); 181 182 // Process edit link 183 self.edit_lnk.unbind('click').on('click', function ($event) { 184 185 $event.preventDefault(); 186 187 let $edit = $(this); 188 let $input = $edit.next('div.ml-row-settings'); 189 190 $input.toggle(); 191 $edit.toggle(); 192 193 }); 194 195 /** 196 * Cancel membership link handler 197 */ 198 self.resetBtn.unbind('click').on('click', function (ev) { 199 200 ev.preventDefault(); 201 202 let $btn = $(this); 203 let $settings = $btn.closest('div.ml-row-settings'); 204 let $label = $settings.prev('a.e20r-members-list-editable'); 205 let $field_name = $settings.find('.e20r-members-list-field-name').val(); 206 207 // Return the select value (or input value) to its original value... 208 let $original = $settings.find('.e20r-members-list-db-' + $field_name ).val(); 209 let $field_input_key = 'e20r-members-list-new_' + $field_name; 210 211 $settings.find('select[name^="' + $field_input_key + '"], input[name^="'+ $field_input_key + '"]').val( $original ); 212 418 213 $settings.toggle(); 419 214 $label.toggle(); 420 */ 421 }, 422 /** 423 * Build the URL used to export the data 424 * 425 * @param export_attrs The attributes to use for the AJAX URL (export) 426 */ 427 build_export_url: function (export_attrs) { 428 window.console.log("About to transmit: ", export_attrs); 429 let export_arguments = ''; 430 Object.keys(export_attrs).forEach(function(key){ 431 let value = export_attrs[key]; 432 if ( value instanceof Array) { 433 value = value.join(','); 434 } 435 export_arguments = export_arguments + key + '=' + encodeURI( value ) + '&'; 436 }); 437 438 let location = e20rml.ajax_url + '?' + export_arguments; // jshint ignore:line 439 window.console.log( location ); 440 return location; 441 }, 442 is_empty: function (data) { 443 444 if (typeof(data) === 'number' || typeof(data) === 'boolean') { 445 return false; 215 }); 216 217 self.exportBtn.unbind('click').on('click', function (ev) { 218 ev.preventDefault(); 219 window.console.log("Export button clicked!"); 220 221 self.export_data( self ); 222 }); 223 }, 224 add_to_uri: function(url, paramName, paramValue) { 225 226 let pattern = new RegExp('\\b('+paramName+'=).*?(&|#|$)'); 227 228 if (paramValue === null) { 229 paramValue = ''; 230 } 231 232 if (url.search(pattern)>=0) { 233 return url.replace(pattern,'$1' + paramValue + '$2'); 234 } 235 236 url = url.replace(/[?#]$/,''); 237 return url + (url.indexOf('?')>0 ? '&' : '?') + paramName + '=' + paramValue; 238 }, 239 export_data: function( self ) { 240 241 $("#overlay").fadeIn(300); 242 let inputs = $('.e20r-search-arguments input, .e20r-search-arguments textarea, .e20r-search-arguments select') 243 .not(':input[type=button], :input[type=submit], input[type=reset]'); 244 let export_args = self.prepare_export(self, inputs); 245 246 // Open a new tab to trigger the AJAX request for the download 247 let file_name = window.prompt( 'Filename for data to save/export', 'members_list' ); 248 let extension = '.csv'; 249 if ( file_name.toLowerCase().endsWith( '.csv' ) ) { 250 extension = ''; 251 } 252 self.download_csv(file_name + extension, export_args, ); //jshint ignore:line 253 }, 254 /** 255 * Set the request variables based on the members list page 256 * 257 * @param self Instance of this object 258 * @param inputs The HTML inputs 259 * @returns Object 260 */ 261 prepare_export: function( self, inputs ) { 262 let request_args = { 263 '_wpnonce': $('#_wpnonce').val(), 264 'showDebugTrace': true, 265 'action': 'e20rml_export_records', 266 }; 267 request_args.find = $( '#post-search-input' ).val(); 268 inputs.each(function () { 269 let input = $(this); 270 let name = input.attr('name'); 271 let value = input.val(); 272 if (false === self.is_empty(value)) { 273 window.console.log(name + " contains " + value); 274 request_args[name] = value; 275 } 276 }); 277 278 let is_checked = false; 279 let selected_ids = []; 280 $('input[name^="member_user_id"]').each(function () { 281 if ($(this).is(':checked')) { 282 is_checked = true; 283 selected_ids.push($(this).val()); 284 } 285 }); 286 if (selected_ids.length > 0) { 287 request_args.member_user_ids = selected_ids; 288 } 289 // return self.build_export_url( export_args ); 290 return request_args; 291 }, 292 /** 293 * Download the exported .CSV data 294 * 295 * @param file_name The file name to use for download 296 * @param attributes The POST argument(s) 297 */ 298 download_csv: function(file_name, attributes) { 299 let URL = e20rml.ajax_url; // jshint ignore:line 300 $.ajax({ 301 url: URL, 302 cache: false, 303 method: 'POST', 304 data: attributes, 305 accepts: 'text/csv', 306 xhr: function () { 307 var xhr = new XMLHttpRequest(); 308 xhr.onreadystatechange = function () { 309 if (xhr.readyState === 2) { 310 if (xhr.status === 200) { 311 xhr.responseType = "blob"; 312 } else { 313 xhr.responseType = "text"; 314 } 315 } 316 }; 317 return xhr; 318 }, 319 success: function (data) { 320 //Convert the Byte Data to BLOB object. 321 let blob = new Blob([data], { type: "text/csv" }); 322 323 //Check the Browser type and download the File. 324 let isIE = !!document.documentMode; 325 if (isIE) { 326 window.navigator.msSaveBlob(blob, file_name); 327 } else { 328 let url = window.URL || window.webkitURL; 329 let link = url.createObjectURL(blob); 330 let body = $('body'); 331 let a = $("<a />"); 332 a.attr("download", file_name); 333 a.attr("href", link); 334 body.append(a); 335 a[0].click(); 336 body.remove(a); 446 337 } 447 448 if (typeof(data) === 'undefined' || data === null) { 449 return true; 450 } 451 452 if (typeof(data.length) !== 'undefined') { 453 return data.length === 0; 454 } 455 456 let count = 0; 457 458 for (let i in data) { 459 460 if (data.hasOwnProperty(i)) { 461 count++; 462 } 463 } 464 465 return count === 0; 466 } 338 }, 339 error: function (jqXHR, textStatus, errorThrown) { 340 window.console.log( 'Status: ' + textStatus + ', Error thrown: ' + errorThrown ); 341 } 342 }).done(function() { 343 setTimeout(function(){ 344 $("#overlay").fadeOut(300); 345 },500); 346 }); 347 }, 348 set_update: function( $element ) { 349 let self = this; 350 let element = $($element); 351 let $settings = element.closest('div.ml-row-settings'); 352 // let users_id = $settings.find('input.e20r-members-list-user-id').val(); 353 let field_name = $settings.find('input.e20r-members-list-field-name').val(); 354 let $label = $settings.prev('a.e20r-members-list-' + field_name + '-label'); 355 // let $new_value_field = $settings.find('input.e20r-members-list-db-' + field_name); 356 let select = $settings.find('.e20r-members-list-select-' + field_name); 357 let $checkbox = element.closest('tr').find('th.check-column input[type="checkbox"]'); 358 let $date = $settings.find('.e20r-members-list-input-' + field_name).val(); 359 let select_val = select.val(); 360 let test_checked = false; 361 362 window.console.log("Value: ", $date, select_val); 363 364 if ('undefined' !== select_val) { 365 $label.text(select.find('option:selected').text()); 366 test_checked = true; 367 } 368 369 if (null !== $date) { 370 let date = new Date($date); 371 window.console.log("Date info? ", date); 372 if (Object.prototype.toString.call(date) === "[object Date]") { 373 $label.text(date.toLocaleDateString(e20rml.locale, self.dateOpts)); //jshint ignore:line 374 } 375 test_checked = true; 376 } 377 378 if (true === test_checked && false === $checkbox.is('checked')) { 379 window.console.log("Checkbox not checked. Fixing!"); 380 $checkbox.prop('checked', true); 381 382 self.bulkActionSelectBottom.val('bulk-update'); 383 self.bulkActionSelectTop.val('bulk-update'); 384 $('#doaction').val(e20rml.lang.save_btn_text); //jshint ignore:line 385 $('#doaction2').val(e20rml.lang.save_btn_text); //jshint ignore:line 386 } 387 /* 388 $settings.toggle(); 389 $label.toggle(); 390 */ 391 }, 392 /** 393 * Build the URL used to export the data 394 * 395 * @param export_attrs The attributes to use for the AJAX URL (export) 396 */ 397 build_export_url: function (export_attrs) { 398 window.console.log("About to transmit: ", export_attrs); 399 let export_arguments = ''; 400 Object.keys(export_attrs).forEach(function(key){ 401 let value = export_attrs[key]; 402 if ( value instanceof Array) { 403 value = value.join(','); 404 } 405 export_arguments = export_arguments + key + '=' + encodeURI( value ) + '&'; 406 }); 407 408 let location = e20rml.ajax_url + '?' + export_arguments; // jshint ignore:line 409 window.console.log( location ); 410 return location; 411 }, 412 is_empty: function (data) { 413 414 if (typeof(data) === 'number' || typeof(data) === 'boolean') { 415 return false; 416 } 417 418 if (typeof(data) === 'undefined' || data === null) { 419 return true; 420 } 421 422 if (typeof(data.length) !== 'undefined') { 423 return data.length === 0; 424 } 425 426 let count = 0; 427 428 for (let i in data) { 429 430 if (data.hasOwnProperty(i)) { 431 count++; 432 } 433 } 434 435 return count === 0; 436 } 467 437 }; 468 438 … … 470 440 e20rMembersList_Page.init(); 471 441 }); 472 473 442 })(jQuery); -
e20r-members-list/trunk/src/E20R/members-list/modules/Multiple_Memberships.php
r2672951 r2700272 22 22 namespace E20R\Members_List\Admin\Modules; 23 23 24 use E20R\Members_List\Members_List; 25 24 26 if ( ! defined( 'ABSPATH' ) && ! defined( 'PLUGIN_PHPUNIT' ) ) { 25 27 die( 'WordPress not loaded. Naughty, naughty!' ); 26 28 } 27 29 28 if ( ! class_exists( ' \\E20R\\Members_List\\Admin\\Modules\\Multiple_Memberships' ) ) {30 if ( ! class_exists( 'E20R\Members_List\Admin\Modules\Multiple_Memberships' ) ) { 29 31 /** 30 32 * Support for PHP's Multiple Memberships plugin … … 60 62 * @return string 61 63 */ 62 public function column_name( $item ) {64 public function multiple_membership_column( $item ) { 63 65 64 // FIXME: Update this to support MMPU65 66 // FIXME: Allow adding more levels and changing the "primary" level. 67 if ( ! function_exists( 'pmpro_getMembershipLevelsForUser' ) ) { 68 return ''; 69 } 70 71 $level_list = pmpro_getMembershipLevelsForUser( $item['ID'] ); 72 $current_level = isset( $item['membership_id'] ) ? (int) $item['membership_id'] : null; 73 $level_ids = array(); 74 $level_names = array(); 75 76 foreach ( $level_list as $level ) { 77 $level_ids[] = $level->id; 78 79 // Generate HTML for the membership levels the user is assigned to 80 $level_names[] = ( (int) $level->id === $current_level ? 81 sprintf( 82 '<span class="e20r-members-list-level-name e20r-members-list-primary-level">%1$s (%2$s)</span>', 83 esc_attr( $level->name ), 84 esc_attr__( 'primary', 'e20r-members-list' ) 85 ) : 86 sprintf( '<span class="e20r-members-list-level-name">%1$s</span>', $level->name ) 87 ); 88 } 66 89 67 90 // These are used to configure the membership level with JavaScript. … … 75 98 <input type="hidden" value="%5$d" class="e20r-members-list-db_record_id" name="e20r-members-list-db_record_id_%2$s" /> 76 99 <input type="hidden" value="%4$s" class="e20r-members-list-field-name" name="e20r-members-list-field_name_%2$s" />', 77 $item['membership_id'],78 $item['ID'],79 $item['name'],100 (int) $item['membership_id'], 101 (int) $item['ID'], 102 esc_attr( $item['name'] ), 80 103 'membership_id', 81 $item['record_id'], 82 $item['membership_level_ids'] 104 (int) $item['record_id'], 105 empty( $item['membership_level_ids'] ) ? 106 implode( ',', $level_ids ) : 107 esc_attr( $item['membership_level_ids'] ) 83 108 ); 84 109 85 $options = ''; 86 if ( function_exists( 'pmpro_getAllLevels' ) ) { 87 $levels = pmpro_getAllLevels( true, true ); 88 } else { 110 $options = Members_List::build_option_string( $item['membership_id'] ); 89 111 90 // Default info if PMPro is disabled. 91 $null_level = new \stdClass(); 92 $null_level->level_id = 0; 93 $null_level->name = esc_attr__( 'No levels found. Paid Memberships Pro is inactive!', 'e20r-members-list' ); 94 $levels = array( $null_level ); 95 } 96 97 foreach ( $levels as $level ) { 98 $options .= sprintf( 99 '<option value="%1$s" %2$s>%3$s</option>', 100 $level->id, 101 selected( $level->id, $item['membership_id'], false ), 102 $level->name 103 ) . "\n"; 104 } 105 $new_membershiplevel_input = sprintf( 112 $new_level_input = sprintf( 106 113 '<div class="ml-row-settings clearfix"> 107 114 %1$s … … 113 120 </div>', 114 121 $membership_input, 115 $item['ID'],122 (int) $item['ID'], 116 123 $options, 117 124 esc_attr__( 'Reset', 'e20r-members-list' ) 118 125 ); 119 126 120 $value = sprintf( 127 $level_info = sprintf( 128 '<div class="e20r-members-list-level-names">%1$s</div>', 129 implode( '<br class="e20r-members-list-level-name-spacer" />', $level_names ) 130 ); 131 return sprintf( 121 132 '<a href="#" class="e20r-members-list_membership_id e20r-members-list-editable" title="%1$s">%2$s<span class="dashicons dashicons-edit"></a>%3$s', 122 esc_attr__( 'Click to edit membership level', 'e20rapp' ),123 $ item['name'],124 $ new_membershiplevel_input133 esc_attr__( 'Click to edit primary membership level', 'e20r-members-list' ), 134 $new_level_input, 135 $level_info 125 136 ); 126 127 return '';128 137 } 129 138 }
Note: See TracChangeset
for help on using the changeset viewer.