From acd728ed52f1e4130554ab6fb60324c74fea7cd2 Mon Sep 17 00:00:00 2001 From: David Parker Date: Thu, 9 Feb 2023 13:26:57 -0500 Subject: [PATCH 01/44] Marking the plugin as MMPU-incompatible --- pmpro-import-users-from-csv.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 6127975..7e238c1 100755 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -407,6 +407,15 @@ function pmproiufcsv_report_sub_error ( $errors, $user_ids ){ } +/** + * Mark the plugin as MMPU-incompatible. + */ +function pmproiufcsv_mmpu_incompatible_add_ons( $incompatible ) { + $incompatible[] = 'PMPro Import Users from CSV Add On'; + return $incompatible; +} +add_filter( 'pmpro_mmpu_incompatible_add_ons', 'pmproiufcsv_mmpu_incompatible_add_ons' ); + /* Function to add links to the plugin row meta */ From 61c1e0bd09e0cf25a37e447d1e190905118d0628 Mon Sep 17 00:00:00 2001 From: Jarryd Long Date: Thu, 20 Apr 2023 12:51:33 +0200 Subject: [PATCH 02/44] Github Templates Added --- .github/CONTRIBUTING.md | 49 +++++++++ .github/ISSUE_TEMPLATE.MD | 45 ++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 36 +++++++ .github/ISSUE_TEMPLATE/enhancement.md | 21 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 21 ++++ .github/ISSUE_TEMPLATE/support.md | 20 ++++ .github/PULL_REQUEST_TEMPLATE.MD | 32 ++++++ .github/workflows/tests-php-dispatch.yml | 112 ++++++++++++++++++++ .github/workflows/tests-php.yml | 120 ++++++++++++++++++++++ 9 files changed, 456 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.MD create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/support.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.MD create mode 100644 .github/workflows/tests-php-dispatch.yml create mode 100644 .github/workflows/tests-php.yml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..54bdee2 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contribute to Paid Memberships Pro + +Paid Memberships Pro is the "community solution" for membership sites on WordPress, and so contributions of all kinds are appreciated. + +When contributing, please follow these guidelines to ensure things work as smoothly as possible. + +__Please Note:__ GitHub is for bug reports and contributions only. If you have a support or customization question, go to our [Member Support Page](https://www.paidmembershipspro.com/support/) instead. + +## Getting Started + +* __Do not report potential security vulnerabilities here. Email them privately to [info@paidmembershipspro.com](mailto:info@paidmembershipspro.com) with the words "Security Vulnerability" in the subject.__ +* Submit a ticket for your issue, assuming one does not already exist. + * Raise it on our [Issue Tracker](https://github.com/strangerstudios/pmpro-import-users-from-csv/issues/new/choose) + * Clearly describe the issue including steps to reproduce the bug. + * Make sure you fill in the earliest version that you know has the issue as well as the version of WordPress you're using. + +## Making Changes + +* Fork the repository on GitHub +* For bug fixes, checkout the DEV branch of the PMPro repository. +* For new features and enhancements, checkout the branch for the version the feature is milestoned for. +* Make sure to pull in any "upstream" changes first. + * Use `git remote add upstream https://github.com/strangerstudios/pmpro-import-users-from-csv.git` to set the upstream repo + * Use `git checkout dev` to get on the development branch. + * Use `git pull upstream dev` to get the latest updates. + * Use `git push` to push those updates to your fork. +* Create a new local branch for each separate bug fix or feature. This will ensure that each pull request is for one issue only and easier to process. + * Use `git checkout -b nameofmybugfixorfeature` to create the new branch +* Make the changes to your local repository. +* Ensure you stick to the [WordPress Coding Standards](https://codex.wordpress.org/WordPress_Coding_Standards) (even though much of the PMPro code does not currently) +* If you have an automatic beautifier in your IDE or dev environment, turn it off. Unrelated style changes in your pull requests will make them harder to process. Feel free to message the core development team to ask them to clean up a file you are working on if the inconsitent coding styles is bothering you. +* You can update the readme.txt to include a comment about your fix or feature in the changelog, but if you do not the core team will do it for you. +* When committing, reference your issue (if present) and include a note about the fix in the commit message. +* Push the changes to your fork. +* For bug fixes, submit a pull request to the DEV branch of the PMPro repository. +* For new features and enhancements, submit a pull request to the version the feature is milestoned for. This will usually be the version number following the current release unless the core dev team has milestoned the feature for a later release. +* We will process all pull requests and make suggestions or changes as soon as possible. Feel free to ping us politely via email or social networks to take a look at your pulls. + +## Code Documentation + +* We would like for every function, filter, class, and class method to be documented using phpDoc standards. +* An example of [how PMPro uses phpDoc blocks can be found here](https://gist.github.com/sunnyratilal/5308969). +* Please make sure that every function is documented so that when we update our API Documentation things don't go awry! + * If you're adding/editing a function in a class, make sure to add `@access {private|public|protected}` +* Finally, please use tabs and not spaces. The tab indent size should be 4 for all Paid Memberships Pro code. + +# Additional Resources +* [General GitHub Documentation](https://help.github.com/) +* [GitHub Pull Request documentation](https://help.github.com/send-pull-requests/) diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD new file mode 100644 index 0000000..b933c17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.MD @@ -0,0 +1,45 @@ + + + + + + + + +## Prerequisites + + + +- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate. +- [ ] The issue still exists against the latest `dev` branch of Paid Memberships Pro on Github (this is **not** the same version as on WordPress.org!) +- [ ] I have attempted to find the simplest possible steps to reproduce the issue + +## Steps to reproduce the issue + + + +1. +2. +3. + +## Expected/actual behavior + +When I follow those steps, I see... + +I was expecting to see... + +## Isolating the problem + + + +- [ ] This bug happens with only Paid Memberships Pro plugin active +- [ ] This bug happens with a default WordPress theme active, or [Memberlite](https://wordpress.org/themes/memberlite/) +- [ ] I can reproduce this bug consistently using the steps above + +## WordPress Environment + +
+``` +Please share non-sensitive information about your hosting environment such as WordPress version, PHP version, Paid Memberships Pro and any related plugins versions. +``` +
\ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..35cbd3c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: "🐛 Bug Report" +about: Report a bug if something isn't working as expected in Paid Memberships Pro. +title: '' +labels: 'bug' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Please be as descriptive as possible; issues lacking detail, or for any other reason than to report a bug, may be closed or left unattended. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Screenshots** +If applicable, please attach a screenshot to make your issue clearer. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Isolating the problem (mark completed items with an [x]):** +- [ ] I have deactivated other plugins and confirmed this bug occurs when only Paid Memberships Pro plugin is active. +- [ ] This bug happens with a default WordPress theme active, or [Memberlite](https://wordpress.org/themes/memberlite/). +- [ ] I can reproduce this bug consistently using the steps above. + +**WordPress Environment** +
+``` +Please share non-sensitive information about your hosting environment such as WordPress version, PHP version, Paid Memberships Pro and any related plugins versions. +``` +
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..65c7905 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,21 @@ +--- +name: "⭐️ Enhancement" +about: If you have an idea to improve an existing feature or need something + for development (such as a new hook) please let us know or submit a Pull Request. +title: '' +labels: 'enhancement' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +If applicable, add any other context or screenshots about the enhancement here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..c2c5816 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: "➕ Feature Request" +about: "Suggest a new feature. We'll consider building it if it receives + sufficient interest!" +title: '' +labels: 'feature request' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +If applicable, add any other context or screenshots about your feature request here. diff --git a/.github/ISSUE_TEMPLATE/support.md b/.github/ISSUE_TEMPLATE/support.md new file mode 100644 index 0000000..6ed0a63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support.md @@ -0,0 +1,20 @@ +--- +name: "💬 Support Question" +about: "If you have a question, please see our docs or use our helpdesk." +title: '' +labels: 'Type: support' +assignees: '' + +--- + +We don't offer technical support on GitHub so we recommend using the following: + +**Reading our documentation** +Usage docs can be found here: https://www.paidmembershipspro.com/documentation/ + +**Technical support for premium extensions or if you're an active paid member** +Submit a ticket on our helpdesk by visiting https://www.paidmembershipspro.com/new-topic/ (Please note that an [active paid membership](https://www.paidmembershipspro.com/pricing) is required for paid support.) + +**General usage and development questions** +- WordPress.org Forums: https://wordpress.org/support/plugin/paid-memberships-pro +- Website: https://www.paidmembershipspro.com/contact/ diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD new file mode 100644 index 0000000..5b7dcbd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -0,0 +1,32 @@ +### All Submissions: + +* [ ] Have you followed the [Contributing guideline](https://github.com/strangerstudios/pmpro-import-users-from-csv/blob/master/.github/CONTRIBUTING.md)? +* [ ] Does your code follow the [WordPress' coding standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/)? +* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/strangerstudios/pmpro-import-users-from-csv/pulls) for the same update/change? + + + + + +### Changes proposed in this Pull Request: + + + +Resolves XXX. + +### How to test the changes in this Pull Request: + +1. +2. +3. + +### Other information: + +* [ ] Have you added an explanation of what your changes do and why you'd like us to include them? +* [ ] Have you successfully run tests with your changes locally? + + + +### Changelog entry + +> Enter a summary of all changes on this Pull Request. This will appear in the changelog if accepted. diff --git a/.github/workflows/tests-php-dispatch.yml b/.github/workflows/tests-php-dispatch.yml new file mode 100644 index 0000000..a4ed1ce --- /dev/null +++ b/.github/workflows/tests-php-dispatch.yml @@ -0,0 +1,112 @@ +name: 'Codeception Tests' +on: workflow_dispatch +jobs: + test: + strategy: + fail-fast: false + matrix: + suite: + - acceptance + - wpunit + runs-on: ubuntu-latest + steps: + # ------------------------------------------------------------------------------ + # Checkout the repo and tric + # ------------------------------------------------------------------------------ + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 1 + token: ${{ secrets.GITHUB_TOKEN }} + submodules: recursive + - name: Checkout tric + uses: actions/checkout@v2 + with: + repository: the-events-calendar/tric + ref: main + path: tric + fetch-depth: 1 + # ------------------------------------------------------------------------------ + # Prepare our composer cache directory + # ------------------------------------------------------------------------------ + - name: Get Composer Cache Directory + id: get-composer-cache-dir + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + id: composer-cache + with: + path: ${{ steps.get-composer-cache-dir.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + # ------------------------------------------------------------------------------ + # Initialize tric + # ------------------------------------------------------------------------------ + - name: Set up tric env vars + run: | + echo "TRIC_BIN=${GITHUB_WORKSPACE}/tric/tric" >> $GITHUB_ENV + echo "TRIC_WP_DIR=${GITHUB_WORKSPACE}/tric/_wordpress" >> $GITHUB_ENV + echo "TRIC_WORDPRESS_DOCKERFILE=Dockerfile.base" >> $GITHUB_ENV + - name: Set run context for tric + run: echo "TRIC=1" >> $GITHUB_ENV && echo "CI=1" >> $GITHUB_ENV + - name: Start ssh-agent + run: | + mkdir -p "${HOME}/.ssh"; + ssh-agent -a /tmp/ssh_agent.sock; + - name: Export SSH_AUTH_SOCK env var + run: echo "SSH_AUTH_SOCK=/tmp/ssh_agent.sock" >> $GITHUB_ENV + - name: Set up tric for CI + run: | + cd ${GITHUB_WORKSPACE}/.. + ${TRIC_BIN} here + ${TRIC_BIN} interactive off + ${TRIC_BIN} build-prompt off + ${TRIC_BIN} build-subdir off + ${TRIC_BIN} xdebug off + ${TRIC_BIN} composer-cache set /home/runner/.cache/composer + ${TRIC_BIN} debug on + ${TRIC_BIN} info + ${TRIC_BIN} config + # ------------------------------------------------------------------------------ + # Start the Chrome container + # ------------------------------------------------------------------------------ + - name: Start the Chrome container + if: ${{ matrix.suite == 'acceptance' }} + run: ${TRIC_BIN} up chrome + # ------------------------------------------------------------------------------ + # Set up plugin + # ------------------------------------------------------------------------------ + - name: Set up plugin + run: | + docker network prune -f + ${TRIC_BIN} use paid-memberships-pro + ${TRIC_BIN} composer install + # ------------------------------------------------------------------------------ + # Init WordPress container + # ------------------------------------------------------------------------------ + - name: Init the WordPress container + run: | + ${TRIC_BIN} up wordpress + ${TRIC_BIN} site-cli core version + # ------------------------------------------------------------------------------ + # Install and activate TwentyTwenty + # ------------------------------------------------------------------------------ + - name: Install and activate TwentyTwenty + if: ${{ matrix.suite == 'acceptance' }} + run: ${TRIC_BIN} site-cli theme install twentytwenty --activate + # ------------------------------------------------------------------------------ + # Run tests + # ------------------------------------------------------------------------------ + - name: Run suite tests + run: ${TRIC_BIN} run ${{ matrix.suite }} --ext DotReporter + # ------------------------------------------------------------------------------ + # Upload artifacts (On failure) + # ------------------------------------------------------------------------------ + - name: Upload artifacts + uses: actions/upload-artifact@v2 + if: failure() + with: + name: output ${{ matrix.suite }} + path: tests/_output/ + retention-days: 7 diff --git a/.github/workflows/tests-php.yml b/.github/workflows/tests-php.yml new file mode 100644 index 0000000..0cd7f1c --- /dev/null +++ b/.github/workflows/tests-php.yml @@ -0,0 +1,120 @@ +name: 'Codeception Tests' +on: + pull_request: + paths: + - '*/**.php' + - 'tests/**' + - '*.php' + - 'composer.json' + - 'codeception.*.yml' + - '.github/workflows/tests-php.yml' +jobs: + test: + strategy: + fail-fast: false + matrix: + suite: + - acceptance + - wpunit + runs-on: ubuntu-latest + steps: + # ------------------------------------------------------------------------------ + # Checkout the repo and tric + # ------------------------------------------------------------------------------ + - name: Checkout the repository + uses: actions/checkout@v2 + with: + fetch-depth: 1 + token: ${{ secrets.GITHUB_TOKEN }} + submodules: recursive + - name: Checkout tric + uses: actions/checkout@v2 + with: + repository: the-events-calendar/tric + ref: main + path: tric + fetch-depth: 1 + # ------------------------------------------------------------------------------ + # Prepare our composer cache directory + # ------------------------------------------------------------------------------ + - name: Get Composer Cache Directory + id: get-composer-cache-dir + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + id: composer-cache + with: + path: ${{ steps.get-composer-cache-dir.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + # ------------------------------------------------------------------------------ + # Initialize tric + # ------------------------------------------------------------------------------ + - name: Set up tric env vars + run: | + echo "TRIC_BIN=${GITHUB_WORKSPACE}/tric/tric" >> $GITHUB_ENV + echo "TRIC_WP_DIR=${GITHUB_WORKSPACE}/tric/_wordpress" >> $GITHUB_ENV + echo "TRIC_WORDPRESS_DOCKERFILE=Dockerfile.base" >> $GITHUB_ENV + - name: Set run context for tric + run: echo "TRIC=1" >> $GITHUB_ENV && echo "CI=1" >> $GITHUB_ENV + - name: Start ssh-agent + run: | + mkdir -p "${HOME}/.ssh"; + ssh-agent -a /tmp/ssh_agent.sock; + - name: Export SSH_AUTH_SOCK env var + run: echo "SSH_AUTH_SOCK=/tmp/ssh_agent.sock" >> $GITHUB_ENV + - name: Set up tric for CI + run: | + cd ${GITHUB_WORKSPACE}/.. + ${TRIC_BIN} here + ${TRIC_BIN} interactive off + ${TRIC_BIN} build-prompt off + ${TRIC_BIN} build-subdir off + ${TRIC_BIN} xdebug off + ${TRIC_BIN} composer-cache set /home/runner/.cache/composer + ${TRIC_BIN} debug on + ${TRIC_BIN} info + ${TRIC_BIN} config + # ------------------------------------------------------------------------------ + # Start the Chrome container + # ------------------------------------------------------------------------------ + - name: Start the Chrome container + if: ${{ matrix.suite == 'acceptance' }} + run: ${TRIC_BIN} up chrome + # ------------------------------------------------------------------------------ + # Set up plugin + # ------------------------------------------------------------------------------ + - name: Set up plugin + run: | + docker network prune -f + ${TRIC_BIN} use paid-memberships-pro + ${TRIC_BIN} composer install + # ------------------------------------------------------------------------------ + # Init WordPress container + # ------------------------------------------------------------------------------ + - name: Init the WordPress container + run: | + ${TRIC_BIN} up wordpress + ${TRIC_BIN} site-cli core version + # ------------------------------------------------------------------------------ + # Install and activate TwentyTwenty + # ------------------------------------------------------------------------------ + - name: Install and activate TwentyTwenty + if: ${{ matrix.suite == 'acceptance' }} + run: ${TRIC_BIN} site-cli theme install twentytwenty --activate + # ------------------------------------------------------------------------------ + # Run tests + # ------------------------------------------------------------------------------ + - name: Run suite tests + run: ${TRIC_BIN} run ${{ matrix.suite }} --ext DotReporter + # ------------------------------------------------------------------------------ + # Upload artifacts (On failure) + # ------------------------------------------------------------------------------ + - name: Upload artifacts + uses: actions/upload-artifact@v2 + if: failure() + with: + name: output ${{ matrix.suite }} + path: tests/_output/ + retention-days: 7 From c26b126c4f3194235748fbdef5e130750e5ff2d9 Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Thu, 2 Nov 2023 19:24:43 -0400 Subject: [PATCH 03/44] Update readme.txt --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 14809ea..7d83510 100755 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 5.0 Tested up to: 5.9 Stable tag: 0.4 -Add-on for the Import Users From CSV plugin to import PMPro and membership-related fields. +Automatically assign memberships and update subscriptions with the Import Users from CSV plugin. == Description == From bf366ed23e23b24d22c05f889f5574114d1557fb Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Mon, 6 Nov 2023 16:24:42 +0200 Subject: [PATCH 04/44] V1.0 initial commit * Merge IUCSV plugin * Added AJAX updating functionality --- class-readcsv.php | 160 ++++ examples/import.csv | 3 + includes/ajaximport.js | 75 ++ includes/import-users-from-csv.wpcli.php | 35 + includes/pmpro-membership-data.php | 349 ++++++++ pmpro-import-users-from-csv.php | 1025 ++++++++++++---------- 6 files changed, 1192 insertions(+), 455 deletions(-) create mode 100644 class-readcsv.php create mode 100644 examples/import.csv create mode 100644 includes/ajaximport.js create mode 100644 includes/import-users-from-csv.wpcli.php create mode 100644 includes/pmpro-membership-data.php mode change 100755 => 100644 pmpro-import-users-from-csv.php diff --git a/class-readcsv.php b/class-readcsv.php new file mode 100644 index 0000000..0c0b1e3 --- /dev/null +++ b/class-readcsv.php @@ -0,0 +1,160 @@ +file = $file_handle; + $this->sep = $sep; + $this->nc = fgetc($this->file); + // skip junk at start + for ($i=0; $i < strlen($skip); $i++) { + if ($this->nc !== $skip[$i]) + break; + $this->nc = fgetc($this->file); + } + $this->eof = ($this->nc === FALSE); + } + + private function next_char() { + $c = $this->nc; + $this->nc = fgetc($this->file); + $this->eof = ($this->nc === FALSE); + return $c; + } + + public function seek($position) { + fseek($this->file, $position); + $this->nc = fgetc($this->file); + $this->eof = ($this->nc === FALSE); + } + + public function get_position() { + return ftell($this->file); + } + + /** + * Get next record from CSV file. + * + * @return + * array of strings from the next record in the CSV file, or NULL if + * there are no more records. + */ + public function get_row() { + if ($this->eof) + return NULL; + + $row=array(); + $field=""; + $state=self::field_start; + + while (1) { + $char = $this->next_char(); + + if ($state == self::quoted_field) { + if ($char === FALSE) { + // EOF. (TODO: error case - no closing quote) + $row[]=$field; + return $row; + } + // Fall through to accumulate quoted chars in switch() {...} + } elseif ($char === FALSE || $char == "\n") { + // End of record. + // (TODO: error case if $state==self::field_start here - trailing comma) + $row[] = $field; + return $row; + } elseif ($char == "\r") { + // Possible start of \r\n line end, but might be just part of foo\rbar + $state = ($state == self::found_quote)? self::found_cr_q: self::found_cr; + continue; + } elseif ($char == $this->sep && + ($state == self::field_start || + $state == self::found_quote || + $state == self::unquoted_field)) { + // End of current field, start of next field + $row[]=$field; + $field=""; + $state=self::field_start; + continue; + } + + switch ($state) { + + case self::field_start: + if ($char == '"') + $state = self::quoted_field; + else { + $state = self::unquoted_field; + $field .= $char; + } + break; + + case self::quoted_field: + if ($char == '"') + $state = self::found_quote; + else + $field .= $char; + break; + + case self::unquoted_field: + $field .= $char; + // (TODO: error case if '"' in middle of unquoted field) + break; + + case self::found_quote: + // Found '"' escape sequence + $field .= $char; + $state = self::quoted_field; + // (TODO: error case if $char!='"' - non-separator char after single quote) + break; + + case self::found_cr: + // Lone \rX instead of \r\n. Treat as literal \rX. (TODO: error case?) + $field .= "\r".$char; + $state = self::unquoted_field; + break; + + case self::found_cr_q: + // (TODO: error case: "foo"\rX instead of "foo"\r\n or "foo"\n) + $field .= "\r".$char; + $state = self::quoted_field; + break; + } + } + } +} \ No newline at end of file diff --git a/examples/import.csv b/examples/import.csv new file mode 100644 index 0000000..3632719 --- /dev/null +++ b/examples/import.csv @@ -0,0 +1,3 @@ +"user_login","user_email","user_pass","first_name","last_name","display_name","role","custom_usermeta_1","custom_usermeta_2" +"johndoe","john.doe@localhost.localdomain",,"John","Doe","John Doe","administrator","Test","Test" +"janedoe","jane.doe@localhost.localdomain.com","test","Jane","Doe","Jane Doe","contributor","0", diff --git a/includes/ajaximport.js b/includes/ajaximport.js new file mode 100644 index 0000000..95c9f60 --- /dev/null +++ b/includes/ajaximport.js @@ -0,0 +1,75 @@ +jQuery(document).ready(function () { + //find status + var $status = jQuery('#importstatus'); + var $count = 0; + var $title = document.title; + var $cycles = ['|', '/', '-', '\\']; + var $pausebutton = jQuery('#pauseimport'); + var $resumebutton = jQuery('#resumeimport'); + var $pauseimport = false; + + //enable pause button + $pausebutton.click(function () { + $pauseimport = true; + $import_timer = false; + $pausebutton.hide(); + $resumebutton.show(); + + $status.html($status.html() + 'Pausing. You may see one more partial import update under here.\n'); + }); + + //enable resume button + $resumebutton.click(function () { + $pauseimport = false; + $resumebutton.hide(); + $pausebutton.show(); + + $status.html($status.html() + 'Resuming...\n'); + + ai_importPartial(); + }); + + //start importing and update status + if ($status.length > 0) { + $status.html($status.html() + '\n' + 'JavaScript Loaded.\n'); + + function ai_importPartial() { + jQuery.ajax({ + url: ajaxurl, type: 'GET', timeout: 30000, + dataType: 'html', + data: 'action=pmpro_import_users_from_csv&filename=' + ai_filename + '&users_update=' + ai_users_update + '&new_user_notification=' + ai_new_user_notification, + error: function (xml) { + alert('Error with import. Try refreshing.'); + }, + success: function (responseHTML) { + if (responseHTML == 'error') { + alert('Error with import. Try refreshing.'); + document.title = $title; + } + else if (responseHTML == 'nofile') { + $status.html($status.html() + '\nCould not find the file ' + ai_filename + '. Maybe it has already been imported.'); + document.title = $title; + } + else if (responseHTML == 'done') { + $status.html($status.html() + '\nDone!'); + document.title = '! ' + $title; + } + else { + $status.html($status.html() + responseHTML); + document.title = $cycles[$count % 4] + ' ' + $title; + + if (!$pauseimport) + var $import_timer = setTimeout(function () { ai_importPartial(); }, 2000); + } + + //scroll the text area unless the mouse is over it + if (jQuery('#importstatus:hover').length != 0) { + $status.scrollTop($status[0].scrollHeight - $status.height()); + } + } + }); + } + + var $import_timer = setTimeout(function () { ai_importPartial(); }, 2000); + } +}); \ No newline at end of file diff --git a/includes/import-users-from-csv.wpcli.php b/includes/import-users-from-csv.wpcli.php new file mode 100644 index 0000000..bc5fa44 --- /dev/null +++ b/includes/import-users-from-csv.wpcli.php @@ -0,0 +1,35 @@ + rest_sanitize_boolean( $params['users_update'] ?? false ), + 'new_user_notificationd_nag' => rest_sanitize_boolean( $params['new_user_notificationd_nag'] ?? false ), + ); + + $result = PMPro_Import_Users_From_CSV::import_csv( $filepath, [ 'users_update' => true ] ); + + echo sprintf( esc_html( 'Updated %d users', 'import-users-from-csv' ), count( $result['user_ids'] ) ); + + if ( ! empty( $result['errors'] ) ) { + echo ' '. esc_html( 'with errors:', 'import-users-from-csv' ); + echo implode( PHP_EOL, $result['errors'] ); + } + + echo PHP_EOL; + break; + + default: + echo esc_html( 'usage: wp iucsv import /path/to/file.csv', 'import-users-from-csv' ) . PHP_EOL; + exit; + } +} ); diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php new file mode 100644 index 0000000..0267e7e --- /dev/null +++ b/includes/pmpro-membership-data.php @@ -0,0 +1,349 @@ + Import From CSV. Browse to CSV file and import. + - pmpro_stripe_customerid (for Stripe users, will be same as membership_subscription_transaction_id above) + 5. (Optional) Send a welcome email by setting the global $pmproiufcsv_email. See example below. + 6. Go to Users --> Import From CSV. Browse to CSV file and import. + + Copy these lines to your active theme's functions.php or custom plugin and modify as desired to send a welcome email to members after import: + + global $pmproiufcsv_email; + $pmproiufcsv_email = array( + 'subject' => sprintf('Welcome to %s', get_bloginfo('sitename')), //email subject, "Welcome to Sitename" + 'body' => 'Your welcome email body text will go here.' //email body + ); +*/ + +/* + Get list of PMPro-related fields +*/ +/// Done. +function pmproiufcsv_getFields() { + $pmpro_fields = array( + "membership_id", + "membership_code_id", + "membership_discount_code", + "membership_initial_payment", + "membership_billing_amount", + "membership_cycle_number", + "membership_cycle_period", + "membership_billing_limit", + "membership_trial_amount", + "membership_trial_limit", + "membership_status", + "membership_startdate", + "membership_enddate", + "membership_subscription_transaction_id", + "membership_payment_transaction_id", + "membership_order_status", + "membership_gateway", + "membership_affiliate_id", + "membership_timestamp" + ); + + return $pmpro_fields; +} + +/* + Delete all import_ meta fields before an import in case the user has been imported in the past. +*/ +/// Done. +function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { + + if(!empty($user)) { + $pmpro_fields = pmproiufcsv_getFields(); + + foreach($pmpro_fields as $field) { + delete_user_meta($user->ID, "import_" . $field); + } + } + + /** + * Filter to allow cancellation of subscriptions during import. + * + * @since TBD + * @param boolean $allow_sub_cancellations Set this option to true if you want to let subscriptions cancel on the gateway level during import. + */ + if ( ! apply_filters( 'pmproiufcsv_cancel_prev_sub_on_import', false ) ) { + add_filter( 'pmpro_cancel_previous_subscriptions', '__return_false' ); + } + +} +add_action( 'pmproiucsv_pre_user_import', 'pmproiufcsv_is_iu_pre_user_import', 10, 3 ); + +/* + Change some of the imported columns to add "imported_" to the front so we don't confuse the data later. +*/ +function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) +{ + $pmpro_fields = pmproiufcsv_getFields(); + + $newusermeta = array(); + foreach($usermeta as $key => $value) + { + if(in_array($key, $pmpro_fields)) + $key = "import_" . $key; + + $newusermeta[$key] = $value; + } + + return $newusermeta; +} +add_filter("is_iu_import_usermeta", "pmproiufcsv_is_iu_import_usermeta", 10, 2); + +//after users are added, let's use the meta data imported to update the user +function pmproiufcsv_is_iu_post_user_import($user_id) +{ + global $pmproiufcsv_email, $wpdb; + + wp_cache_delete($user_id, 'users'); + $user = get_userdata($user_id); + + //look for a membership level and other information + $membership_id = $user->import_membership_id; + $membership_code_id = $user->import_membership_code_id; + $membership_discount_code = $user->import_membership_discount_code; + $membership_initial_payment = $user->import_membership_initial_payment; + $membership_billing_amount = $user->import_membership_billing_amount; + $membership_cycle_number = $user->import_membership_cycle_number; + $membership_cycle_period = $user->import_membership_cycle_period; + $membership_billing_limit = $user->import_membership_billing_limit; + $membership_trial_amount = $user->import_membership_trial_amount; + $membership_trial_limit = $user->import_membership_trial_limit; + $membership_status = $user->import_membership_status; + $membership_startdate = $user->import_membership_startdate; + $membership_enddate = $user->import_membership_enddate; + $membership_timestamp = $user->import_membership_timestamp; + + //fix date formats + if ( ! empty( $membership_startdate ) ) { + $membership_startdate = date( 'Y-m-d', strtotime( $membership_startdate, current_time( 'timestamp' ) ) ); + } else { + $membership_startdate = current_time( 'mysql' ); + } + + if ( ! empty( $membership_enddate ) ) { + $membership_enddate = date( 'Y-m-d', strtotime( $membership_enddate, current_time( 'timestamp' ) ) ); + } else { + $membership_enddate = 'NULL'; + } + + if ( ! empty( $membership_timestamp ) ) { + $membership_timestamp = date( 'Y-m-d', strtotime($membership_timestamp, current_time( 'timestamp' ) ) ); + } + + if ( ! empty( $membership_discount_code ) && empty( $membership_code_id ) ) { + $membership_code_id = $wpdb->get_var( + $wpdb->prepare( " + SELECT id + FROM $wpdb->pmpro_discount_codes + WHERE `code` = %s + LIMIT 1 + ", $membership_discount_code ) + ); + } + + // Check whether the member may already have been imported. + if( pmpro_hasMembershipLevel( $membership_id, $user_id ) && ! empty( $_REQUEST['skip_existing_members_same_level'] ) ){ + return; + } + + //look up discount code + if ( ! empty( $membership_discount_code ) && empty( $membership_code_id ) ) { + $membership_code_id = $wpdb->get_var( "SELECT id FROM $wpdb->pmpro_discount_codes WHERE `code` = '" . esc_sql( $membership_discount_code ) . "' LIMIT 1" ); + } + + //look for a subscription transaction id and gateway + $membership_subscription_transaction_id = $user->import_membership_subscription_transaction_id; + $membership_payment_transaction_id = $user->import_membership_payment_transaction_id; + $membership_order_status = $user->import_membership_order_status; + $membership_affiliate_id = $user->import_membership_affiliate_id; + $membership_gateway = $user->import_membership_gateway; + + if( !empty( $membership_subscription_transaction_id ) && ( $membership_status == 'active' || empty( $membership_status ) ) && !empty( $membership_enddate ) ){ + /** + * If there is a membership_subscription_transaction_id column with a value AND membership_status column with value active (or assume active if missing) AND the membership_enddate column is not empty, then (1) throw an warning but continue to import + */ + + add_filter( 'pmproiucsv_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); + } + + + //change membership level + if(!empty($membership_id)) + { + $custom_level = array( + 'user_id' => $user_id, + 'membership_id' => $membership_id, + 'code_id' => $membership_code_id, + 'initial_payment' => $membership_initial_payment, + 'billing_amount' => $membership_billing_amount, + 'cycle_number' => $membership_cycle_number, + 'cycle_period' => $membership_cycle_period, + 'billing_limit' => $membership_billing_limit, + 'trial_amount' => $membership_trial_amount, + 'trial_limit' => $membership_trial_limit, + 'status' => $membership_status, + 'startdate' => $membership_startdate, + 'enddate' => $membership_enddate + ); + + pmpro_changeMembershipLevel($custom_level, $user_id); + + //if membership was in the past make it inactive + if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) + { + $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; + $wpdb->query($sqlQuery); + $membership_in_the_past = true; + } + + if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) + { + $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); + $wpdb->query($sqlQuery); + } + } + + //add order so integration with gateway works + if( +// !empty($membership_subscription_transaction_id) && + !empty($membership_gateway) || + !empty($membership_timestamp) || !empty($membership_code_id) + ) + { + $order = new MemberOrder(); + $order->user_id = $user_id; + $order->membership_id = $membership_id; + $order->InitialPayment = $membership_initial_payment; + $order->payment_transaction_id = $membership_payment_transaction_id; + $order->subscription_transaction_id = $membership_subscription_transaction_id; + $order->affiliate_id = $membership_affiliate_id; + $order->gateway = $membership_gateway; + + if ( ! empty( $membership_order_status ) ) { + $order->status = $membership_order_status; + } elseif ( ! empty( $membership_in_the_past ) ) { + $order->status = 'cancelled'; + } else { + $order->status = 'success'; + } + + $order->saveOrder(); + + //update timestamp of order? + if(!empty($membership_timestamp)) + { + $timestamp = strtotime($membership_timestamp, current_time('timestamp')); + $order->updateTimeStamp(date("Y", $timestamp), date("m", $timestamp), date("d", $timestamp), date("H:i:s", $timestamp)); + } + } + + //add code use + if(!empty($membership_code_id) && !empty($order) && !empty($order->id)) + $wpdb->query("INSERT INTO $wpdb->pmpro_discount_codes_uses (code_id, user_id, order_id, timestamp) VALUES('" . esc_sql($membership_code_id) . "', '" . esc_sql($user_id) . "', '" . intval($order->id) . "', now())"); + + //email user if global is set + if(!empty($pmproiufcsv_email)) + { + $email = new PMProEmail(); + $email->email = $user->user_email; + $email->subject = $pmproiufcsv_email['subject']; + $email->body = $pmproiufcsv_email['body']; + $email->template = 'pmproiufcsv'; + $email->sendEmail(); + } +} +add_action("is_iu_post_user_import", "pmproiufcsv_is_iu_post_user_import"); + +/** + * Add error/warning message if user's were imported with both a subscription and expiration date. + * + * @since TBD + * + * @param array $errors An array of various error messages. + * @param [type] $user_ids + * @return array $error The error message that is set when an import fails. + */ +function pmproiufcsv_report_sub_error ( $errors, $user_ids ){ + + $error_message = sprintf( __( 'User imported with both an active subscription and a membership enddate. This configuration is not recommended with PMPro ($1$s). This user has been imported with no enddate.', 'pmpro-import-users-from-csv' ), 'https://www.paidmembershipspro.com/important-notes-on-recurring-billing-and-expiration-dates-for-membership-levels/' ); + + $errors[] = new WP_Error( 'subscriptions_expiration', $error_message ); + + return $errors; + +} + +/* +Function to add links to the plugin row meta +*/ +function pmproiufcsv_plugin_row_meta($links, $file) { + if(strpos($file, 'pmpro-import-users-from-csv.php') !== false) + { + $new_links = array( + '' . __( 'Docs', 'pmpro-import-users-from-csv' ) . '', + '' . __( 'Support', 'pmpro-import-users-from-csv' ) . '', + ); + $links = array_merge($links, $new_links); + } + return $links; +} +add_filter('plugin_row_meta', 'pmproiufcsv_plugin_row_meta', 10, 2); + +/** + * Render the additional options for the Import Users from CSV plugin settings page. + * + * @since 0.4 + */ +function pmproiufcsv_add_import_options() { + + ?> + + + + + +
+ + + + +
+ + + + Import From CSV. Browse to CSV file and import. - - pmpro_stripe_customerid (for Stripe users, will be same as membership_subscription_transaction_id above) - 5. (Optional) Send a welcome email by setting the global $pmproiufcsv_email. See example below. - 6. Go to Users --> Import From CSV. Browse to CSV file and import. - - Copy these lines to your active theme's functions.php or custom plugin and modify as desired to send a welcome email to members after import: - - global $pmproiufcsv_email; - $pmproiufcsv_email = array( - 'subject' => sprintf('Welcome to %s', get_bloginfo('sitename')), //email subject, "Welcome to Sitename" - 'body' => 'Your welcome email body text will go here.' //email body - ); -*/ - -/* - Load plugin textdomain. -*/ -function pmproiufcsv_load_textdomain() { - load_plugin_textdomain( 'pmpro-import-users-from-csv', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' ); -} -add_action( 'init', 'pmproiufcsv_load_textdomain' ); - -/* -* Check if import users from CSV exists -*/ - function pmproiufcsv_check(){ - if( !defined( 'IS_IU_CSV_DELIMITER' ) ){ - add_action( 'admin_notices', 'pmproiufcsv_admin_notice' ); - } - } - -add_action( 'admin_init', 'pmproiufcsv_check' ); - -function pmproiufcsv_admin_notice(){ - // Don't want to show this on the plugin install page. - $screen = get_current_screen(); - if ( ! empty( $screen ) && $screen->base == 'plugin-install' ) { - return; - } - - $action_url = wp_nonce_url( - add_query_arg( - [ - 'action' => 'install-plugin', - 'plugin' => 'import-users-from-csv', - ], - admin_url( 'update.php' ) - ), - 'install-plugin_import-users-from-csv' - ); - - // Maybe use the activate link if the plugin is installed but not activated. - if ( pmproiufcsv_is_plugin_installed( 'import-users-from-csv/import-users-from-csv.php' ) ) { - $action_url = wp_nonce_url( - add_query_arg( - [ - 'action' => 'activate', - 'plugin' => 'import-users-from-csv/import-users-from-csv.php', - ], - admin_url( 'plugins.php' ) - ), - 'activate-plugin_import-users-from-csv/import-users-from-csv.php' - ); - } - - ?> -
-

- Paid Memberships Pro - Import Users from CSV to function correctly, you must also install the Import Users from CSV plugin.', 'pmpro-import-users-from-csv' ), - esc_url( $action_url ) - ); - ?> -

-
- ID, "import_" . $field); - } - } - - /** - * Filter to allow cancellation of subscriptions during import. - * - * @since TBD - * @param boolean $allow_sub_cancellations Set this option to true if you want to let subscriptions cancel on the gateway level during import. - */ - if ( ! apply_filters( 'pmproiufcsv_cancel_prev_sub_on_import', false ) ) { - add_filter( 'pmpro_cancel_previous_subscriptions', '__return_false' ); - } - -} -add_action('is_iu_pre_user_import', 'pmproiufcsv_is_iu_pre_user_import', 10, 2); - -/* - Change some of the imported columns to add "imported_" to the front so we don't confuse the data later. -*/ -function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) -{ - $pmpro_fields = pmproiufcsv_getFields(); - - $newusermeta = array(); - foreach($usermeta as $key => $value) - { - if(in_array($key, $pmpro_fields)) - $key = "import_" . $key; - - $newusermeta[$key] = $value; - } - - return $newusermeta; -} -add_filter("is_iu_import_usermeta", "pmproiufcsv_is_iu_import_usermeta", 10, 2); - -//after users are added, let's use the meta data imported to update the user -function pmproiufcsv_is_iu_post_user_import($user_id) -{ - global $pmproiufcsv_email, $wpdb; - - wp_cache_delete($user_id, 'users'); - $user = get_userdata($user_id); - - //look for a membership level and other information - $membership_id = $user->import_membership_id; - $membership_code_id = $user->import_membership_code_id; - $membership_discount_code = $user->import_membership_discount_code; - $membership_initial_payment = $user->import_membership_initial_payment; - $membership_billing_amount = $user->import_membership_billing_amount; - $membership_cycle_number = $user->import_membership_cycle_number; - $membership_cycle_period = $user->import_membership_cycle_period; - $membership_billing_limit = $user->import_membership_billing_limit; - $membership_trial_amount = $user->import_membership_trial_amount; - $membership_trial_limit = $user->import_membership_trial_limit; - $membership_status = $user->import_membership_status; - $membership_startdate = $user->import_membership_startdate; - $membership_enddate = $user->import_membership_enddate; - $membership_timestamp = $user->import_membership_timestamp; - - //fix date formats - if ( ! empty( $membership_startdate ) ) { - $membership_startdate = date( 'Y-m-d', strtotime( $membership_startdate, current_time( 'timestamp' ) ) ); - } else { - $membership_startdate = current_time( 'mysql' ); - } - - if ( ! empty( $membership_enddate ) ) { - $membership_enddate = date( 'Y-m-d', strtotime( $membership_enddate, current_time( 'timestamp' ) ) ); - } else { - $membership_enddate = 'NULL'; - } - - if ( ! empty( $membership_timestamp ) ) { - $membership_timestamp = date( 'Y-m-d', strtotime($membership_timestamp, current_time( 'timestamp' ) ) ); - } - - if ( ! empty( $membership_discount_code ) && empty( $membership_code_id ) ) { - $membership_code_id = $wpdb->get_var( - $wpdb->prepare( " - SELECT id - FROM $wpdb->pmpro_discount_codes - WHERE `code` = %s - LIMIT 1 - ", $membership_discount_code ) - ); - } - - // Check whether the member may already have been imported. - if( pmpro_hasMembershipLevel( $membership_id, $user_id ) && ! empty( $_REQUEST['skip_existing_members_same_level'] ) ){ - return; - } - - //look up discount code - if ( ! empty( $membership_discount_code ) && empty( $membership_code_id ) ) { - $membership_code_id = $wpdb->get_var( "SELECT id FROM $wpdb->pmpro_discount_codes WHERE `code` = '" . esc_sql( $membership_discount_code ) . "' LIMIT 1" ); - } - - //look for a subscription transaction id and gateway - $membership_subscription_transaction_id = $user->import_membership_subscription_transaction_id; - $membership_payment_transaction_id = $user->import_membership_payment_transaction_id; - $membership_order_status = $user->import_membership_order_status; - $membership_affiliate_id = $user->import_membership_affiliate_id; - $membership_gateway = $user->import_membership_gateway; - - if( !empty( $membership_subscription_transaction_id ) && ( $membership_status == 'active' || empty( $membership_status ) ) && !empty( $membership_enddate ) ){ - /** - * If there is a membership_subscription_transaction_id column with a value AND membership_status column with value active (or assume active if missing) AND the membership_enddate column is not empty, then (1) throw an warning but continue to import - */ - - add_filter( 'is_iu_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); - } - - - //change membership level - if(!empty($membership_id)) - { - $custom_level = array( - 'user_id' => $user_id, - 'membership_id' => $membership_id, - 'code_id' => $membership_code_id, - 'initial_payment' => $membership_initial_payment, - 'billing_amount' => $membership_billing_amount, - 'cycle_number' => $membership_cycle_number, - 'cycle_period' => $membership_cycle_period, - 'billing_limit' => $membership_billing_limit, - 'trial_amount' => $membership_trial_amount, - 'trial_limit' => $membership_trial_limit, - 'status' => $membership_status, - 'startdate' => $membership_startdate, - 'enddate' => $membership_enddate - ); - - pmpro_changeMembershipLevel($custom_level, $user_id); - - //if membership was in the past make it inactive - if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) - { - $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; - $wpdb->query($sqlQuery); - $membership_in_the_past = true; - } - - if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) - { - $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); - $wpdb->query($sqlQuery); - } - } - - //add order so integration with gateway works - if( -// !empty($membership_subscription_transaction_id) && - !empty($membership_gateway) || - !empty($membership_timestamp) || !empty($membership_code_id) - ) - { - $order = new MemberOrder(); - $order->user_id = $user_id; - $order->membership_id = $membership_id; - $order->InitialPayment = $membership_initial_payment; - $order->payment_transaction_id = $membership_payment_transaction_id; - $order->subscription_transaction_id = $membership_subscription_transaction_id; - $order->affiliate_id = $membership_affiliate_id; - $order->gateway = $membership_gateway; - - if ( ! empty( $membership_order_status ) ) { - $order->status = $membership_order_status; - } elseif ( ! empty( $membership_in_the_past ) ) { - $order->status = 'cancelled'; - } else { - $order->status = 'success'; - } - - $order->saveOrder(); - - //update timestamp of order? - if(!empty($membership_timestamp)) - { - $timestamp = strtotime($membership_timestamp, current_time('timestamp')); - $order->updateTimeStamp(date("Y", $timestamp), date("m", $timestamp), date("d", $timestamp), date("H:i:s", $timestamp)); - } - } - - //add code use - if(!empty($membership_code_id) && !empty($order) && !empty($order->id)) - $wpdb->query("INSERT INTO $wpdb->pmpro_discount_codes_uses (code_id, user_id, order_id, timestamp) VALUES('" . esc_sql($membership_code_id) . "', '" . esc_sql($user_id) . "', '" . intval($order->id) . "', now())"); - - //email user if global is set - if(!empty($pmproiufcsv_email)) - { - $email = new PMProEmail(); - $email->email = $user->user_email; - $email->subject = $pmproiufcsv_email['subject']; - $email->body = $pmproiufcsv_email['body']; - $email->template = 'pmproiufcsv'; - $email->sendEmail(); - } -} -add_action("is_iu_post_user_import", "pmproiufcsv_is_iu_post_user_import"); - -/** - * Add error/warning message if user's were imported with both a subscription and expiration date. - * - * @since TBD - * - * @param array $errors An array of various error messages. - * @param [type] $user_ids - * @return array $error The error message that is set when an import fails. - */ -function pmproiufcsv_report_sub_error ( $errors, $user_ids ){ - - $error_message = sprintf( __( 'User imported with both an active subscription and a membership enddate. This configuration is not recommended with PMPro ($1$s). This user has been imported with no enddate.', 'pmpro-import-users-from-csv' ), 'https://www.paidmembershipspro.com/important-notes-on-recurring-billing-and-expiration-dates-for-membership-levels/' ); - - $errors[] = new WP_Error( 'subscriptions_expiration', $error_message ); - - return $errors; - -} - -/* -Function to add links to the plugin row meta -*/ -function pmproiufcsv_plugin_row_meta($links, $file) { - if(strpos($file, 'pmpro-import-users-from-csv.php') !== false) - { - $new_links = array( - '' . __( 'Docs', 'pmpro-import-users-from-csv' ) . '', - '' . __( 'Support', 'pmpro-import-users-from-csv' ) . '', - ); - $links = array_merge($links, $new_links); - } - return $links; -} -add_filter('plugin_row_meta', 'pmproiufcsv_plugin_row_meta', 10, 2); - -/** - * Render the additional options for the Import Users from CSV plugin settings page. - * - * @since 0.4 - */ -function pmproiufcsv_add_import_options() { - - ?> - - - - - -
- - - - -
- - - - 50 ) { + die( 'Error uploading file. Too many files with the same name. Clean out the ' . $import_dir . ' directory on your server.' ); + } + } + + // save file + if ( strpos( $_FILES['users_csv']['tmp_name'], $upload_dir['basedir'] ) !== false ) { + // was uploaded and saved to $_SESSION + rename( $_FILES['users_csv']['tmp_name'], $import_dir . $filename ); + } else { + // it was just uploaded + move_uploaded_file( $_FILES['users_csv']['tmp_name'], $import_dir . $filename ); + } + + // redurect to the page to run AJAX + $url = admin_url( 'users.php?page=pmpro-import-users-from-csv&import=resume&filename=' . $filename ); + $url = add_query_arg( + array( + 'new_user_notification' => $new_user_notification, + 'users_update' => $users_update, + ), + $url + ); + + wp_redirect( $url ); + exit; + } else { + $results = self::import_csv( + $filename, + array( + 'new_user_notification' => $new_user_notification, + 'users_update' => $users_update, + ) + ); + + // No users imported? + if ( ! $results['user_ids'] ) { + wp_redirect( add_query_arg( 'import', 'fail', wp_get_referer() ) ); + } + + // Some users imported? + elseif ( $results['errors'] ) { + wp_redirect( add_query_arg( 'import', 'errors', wp_get_referer() ) ); + } + + // All users imported? :D + else { + wp_redirect( add_query_arg( 'import', 'success', wp_get_referer() ) ); + } + + exit; + } + } + + wp_redirect( add_query_arg( 'import', 'file', wp_get_referer() ) ); + exit; + } + } + + + /** + * Content of the settings page + * + * @since 0.1 + **/ + public static function users_page() { + if ( ! current_user_can( 'create_users' ) ) { + wp_die( __( 'You do not have sufficient permissions to access this page.', 'pmpro-import-users-from-csv' ) ); + } + ?> +
+

+

' . sprintf( __( 'Notice: please make the directory %s writable so that you can see the error log.', 'pmpro-import-users-from-csv' ), self::$log_dir_path ) . '

'; + } + } + + // Adds a query arg in the + if ( isset( $_GET['import'] ) ) { + $error_log_msg = ''; + if ( file_exists( $error_log_file ) ) { + $error_log_msg = sprintf( __( ', please check the error log', 'pmpro-import-users-from-csv' ), $error_log_url ); + } + + switch ( $_GET['import'] ) { + case 'file': + echo '

' . __( 'Error during file upload.', 'pmpro-import-users-from-csv' ) . '

'; + break; + case 'data': + echo '

' . __( 'Cannot extract data from uploaded file or no file was uploaded.', 'pmpro-import-users-from-csv' ) . '

'; + break; + case 'fail': + echo '

' . sprintf( __( 'No user was successfully imported%s.', 'pmpro-import-users-from-csv' ), $error_log_msg ) . '

'; + break; + case 'errors': + echo '

' . sprintf( __( 'Some users were successfully imported but some were not%s.', 'pmpro-import-users-from-csv' ), $error_log_msg ) . '

'; + break; + case 'success': + echo '

' . __( 'Users import was successful.', 'pmpro-import-users-from-csv' ) . '

'; + break; + default: + break; + } + + if ( $_GET['import'] == 'resume' && ! empty( $_GET['filename'] ) ) { + $filename = sanitize_file_name( $_GET['filename'] ); + $users_update = isset( $_GET['users_update'] ) ? $_GET['users_update'] : false; + $new_user_notification = isset( $_GET['new_user_notification'] ) ? $_GET['new_user_notification'] : false; + + // resetting position transients? + if ( ! empty( $_GET['reset'] ) ) { + delete_transient( 'pmproiucsv_' . $filename ); + } + ?> +

Importing file over AJAX

+

IMPORTANT: Your import is not finished. Closing this page will stop it. If the import stops or you have to close your browser, you can navigate to this URL to resume the import later.

+ +

+ Click here to pause. + +

+ + + + +
+ + + + + + + + + + + + + + + + + + +
+
+ the example of the CSV file.', 'pmpro-import-users-from-csv' ), plugin_dir_url( __FILE__ ) . 'examples/import.csv' ); ?> +
+ + +
+ + +
+ + +
+

+ +

+
+ + + true, + 'users_update' => $users_update, + 'new_user_notification' => $new_user_notification, + ); + + $results = self::import_csv( $import_dir . $filename, $args ); + + // No users imported? + if ( ! $results['user_ids'] ) { + echo 'done'; + + // delete file + unlink( $import_dir . $filename ); + + // delete position transient + delete_transient( 'pmproiucsv_' . $filename ); + } + + // Some users imported? + elseif ( $results['errors'] ) { + echo 'X '; + + } else { + echo 'Importing ' . str_pad( '', count( $results['user_ids'] ), '.' ) . "\n"; + } + + exit; + } + + /** + * Import a csv file + * + * @since 0.5 + */ + public static function import_csv( $filename, $args ) { + $errors = $user_ids = array(); + + $defaults = array( + 'password_nag' => false, + 'new_user_notification' => false, + 'users_update' => false, + 'partial' => false, + 'per_partial' => 20 + ); + extract( wp_parse_args( $args, $defaults ) ); + + // User data fields list used to differentiate with user meta + $userdata_fields = array( + 'ID', 'user_login', 'user_pass', + 'user_email', 'user_url', 'user_nicename', + 'display_name', 'user_registered', 'first_name', + 'last_name', 'nickname', 'description', + 'rich_editing', 'comment_shortcuts', 'admin_color', + 'use_ssl', 'show_admin_bar_front', 'show_admin_bar_admin', + 'role' + ); + + include( plugin_dir_path( __FILE__ ) . 'class-readcsv.php' ); + + // Loop through the file lines + $file_handle = fopen( $filename, 'r' ); + $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. + + $first = true; + $rkey = 0; + while ( ( $line = $csv_reader->get_row() ) !== NULL ) { + + // If the first line is empty, abort + // If another line is empty, just skip it + if ( empty( $line ) ) { + if ( $first ) + break; + else + continue; + } + + // If we are on the first line, the columns are the headers + if ( $first ) { + $headers = $line; + $first = false; + + //skip ahead for partial imports + if(!empty($partial)) + { + $position = get_transient('iufcsv_' . basename($filename)); + if(!empty($position)) + { + $csv_reader->seek($position); + } + } + + continue; + } + + // if($position == 1122) + // var_dump($line); + + // Separate user data from meta + $userdata = $usermeta = array(); + foreach ( $line as $ckey => $column ) { + $column_name = $headers[$ckey]; + $column = trim( $column ); + + if ( in_array( $column_name, $userdata_fields ) ) { + $userdata[$column_name] = $column; + } else { + $usermeta[$column_name] = $column; + } + } + + // A plugin may need to filter the data and meta + $userdata = apply_filters( 'is_iu_import_userdata', $userdata, $usermeta ); + $usermeta = apply_filters( 'is_iu_import_usermeta', $usermeta, $userdata ); + + // If no user data, bailout! + if ( empty( $userdata ) ) + continue; + + // Something to be done before importing one user? + do_action( 'is_iu_pre_user_import', $userdata, $usermeta ); + + $user = $user_id = false; + + if ( isset( $userdata['ID'] ) ) + $user = get_user_by( 'ID', $userdata['ID'] ); + + if ( ! $user && $users_update ) { + if ( isset( $userdata['user_login'] ) ) + $user = get_user_by( 'login', $userdata['user_login'] ); + + if ( ! $user && isset( $userdata['user_email'] ) ) + $user = get_user_by( 'email', $userdata['user_email'] ); + } + + $update = false; + if ( $user ) { + $userdata['ID'] = $user->ID; + $update = true; + } + + // If creating a new user and no password was set, let auto-generate one! + if ( ! $update && empty( $userdata['user_pass'] ) ) + $userdata['user_pass'] = wp_generate_password( 12, false ); + + if ( $update ) + $user_id = wp_update_user( $userdata ); + else + $user_id = wp_insert_user( $userdata ); + + // Is there an error o_O? + if ( is_wp_error( $user_id ) ) { + $errors[$rkey] = $user_id; + } else { + // If no error, let's update the user meta too! + if ( $usermeta ) { + foreach ( $usermeta as $metakey => $metavalue ) { + $metavalue = maybe_unserialize( $metavalue ); + update_user_meta( $user_id, $metakey, $metavalue ); + } + } + + // If we created a new user, maybe set password nag and send new user notification? + if ( ! $update ) { + if ( $password_nag ) + update_user_option( $user_id, 'default_password_nag', true, true ); + + if ( $new_user_notification ) + wp_new_user_notification( $user_id, $userdata['user_pass'] ); + } + + // Some plugins may need to do things after one user has been imported. Who know? + do_action( 'is_iu_post_user_import', $user_id ); + + $user_ids[] = $user_id; + } + + $rkey++; + + //if doing a partial import, save our spot and break + if(!empty($partial) && $rkey) + { + $position = $csv_reader->get_position(); + set_transient('iufcsv_' . basename($filename), $position, 60*60*24*2); + + if($rkey > $per_partial-1) + break; + } + } + fclose( $file_handle ); + + // One more thing to do after all imports? + do_action( 'is_iu_post_users_import', $user_ids, $errors ); + + // Let's log the errors + self::log_errors( $errors ); + + return array( + 'user_ids' => $user_ids, + 'errors' => $errors + ); + } + + /** + * Log errors to a file + * + * @since 0.2 + **/ + private static function log_errors( $errors ) { + if ( empty( $errors ) ) { + return; + } + + $log = @fopen( self::$log_dir_path . 'pmproiucsv_error.log', 'a' ); + @fwrite( $log, sprintf( __( 'BEGIN %s', 'pmpro-import-users-from-csv' ), date_i18n( 'Y-m-d H:i:s', time() ) ) . "\n" ); + + foreach ( $errors as $key => $error ) { + $line = $key + 1; + $message = $error->get_error_message(); + @fwrite( $log, sprintf( __( '[Line %1$s] %2$s', 'pmpro-import-users-from-csv' ), $line, $message ) . "\n" ); + } + + @fclose( $log ); + } +} + +PMPro_Import_Users_From_CSV::init(); \ No newline at end of file From f74b3e44c2bd9988535d6052a80a84ac9584323f Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Tue, 7 Nov 2023 14:54:49 +0200 Subject: [PATCH 05/44] Updated CSV further. * Started working on validating CSV header logic. --- pmpro-import-users-from-csv.php | 174 ++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 63 deletions(-) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 31dd802..254d111 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -48,6 +48,11 @@ public static function init() { require_once __DIR__ . '/includes/pmpro-membership-data.php'; } + // Include the class here. + if ( ! class_exists( 'ReadCSV' ) ) { + include plugin_dir_path( __FILE__ ) . 'class-readcsv.php'; + } + do_action( 'pmproiucsv_after_init' ); } @@ -95,10 +100,13 @@ public static function process_csv() { if ( isset( $_FILES['users_csv']['tmp_name'] ) ) { // Setup settings variables $filename = $_FILES['users_csv']['tmp_name']; - $password_nag = isset( $_POST['password_nag'] ) ? $_POST['password_nag'] : false; $users_update = isset( $_POST['users_update'] ) ? $_POST['users_update'] : false; $new_user_notification = isset( $_POST['new_user_notification'] ) ? $_POST['new_user_notification'] : false; + /// Check this at a later stage. + // Before continuing, let's make sure the CSV file is valid. + // $is_csv_valid = self::is_csv_valid( $filename ); + // use AJAX? if ( ! empty( $_POST['ajaximport'] ) ) { // check for a imports directory in wp-content @@ -206,7 +214,7 @@ public static function users_page() { } } - // Adds a query arg in the + // Adds a query arg in the if ( isset( $_GET['import'] ) ) { $error_log_msg = ''; if ( file_exists( $error_log_file ) ) { @@ -380,120 +388,131 @@ public static function import_csv( $filename, $args ) { $errors = $user_ids = array(); $defaults = array( - 'password_nag' => false, 'new_user_notification' => false, - 'users_update' => false, - 'partial' => false, - 'per_partial' => 20 + 'users_update' => false, + 'partial' => false, + 'per_partial' => apply_filters( 'pmprocsv_ajax_import_batch', 20 ), ); extract( wp_parse_args( $args, $defaults ) ); // User data fields list used to differentiate with user meta - $userdata_fields = array( - 'ID', 'user_login', 'user_pass', - 'user_email', 'user_url', 'user_nicename', - 'display_name', 'user_registered', 'first_name', - 'last_name', 'nickname', 'description', - 'rich_editing', 'comment_shortcuts', 'admin_color', - 'use_ssl', 'show_admin_bar_front', 'show_admin_bar_admin', - 'role' + $userdata_fields = array( + 'ID', + 'user_login', + 'user_pass', + 'user_email', + 'user_url', + 'user_nicename', + 'display_name', + 'user_registered', + 'first_name', + 'last_name', + 'nickname', + 'description', + 'rich_editing', + 'comment_shortcuts', + 'admin_color', + 'use_ssl', + 'show_admin_bar_front', + 'show_admin_bar_admin', + 'role', ); - include( plugin_dir_path( __FILE__ ) . 'class-readcsv.php' ); - // Loop through the file lines $file_handle = fopen( $filename, 'r' ); - $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. + $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. $first = true; - $rkey = 0; - while ( ( $line = $csv_reader->get_row() ) !== NULL ) { + $rkey = 0; + while ( ( $line = $csv_reader->get_row() ) !== null ) { // If the first line is empty, abort // If another line is empty, just skip it if ( empty( $line ) ) { - if ( $first ) + if ( $first ) { break; - else + } else { continue; + } } // If we are on the first line, the columns are the headers if ( $first ) { $headers = $line; - $first = false; - - //skip ahead for partial imports - if(!empty($partial)) - { - $position = get_transient('iufcsv_' . basename($filename)); - if(!empty($position)) - { - $csv_reader->seek($position); + $first = false; + + // skip ahead for partial imports + if ( ! empty( $partial ) ) { + $position = get_transient( 'pmproiucsv_' . basename( $filename ) ); + if ( ! empty( $position ) ) { + $csv_reader->seek( $position ); } } continue; } - // if($position == 1122) - // var_dump($line); - // Separate user data from meta $userdata = $usermeta = array(); foreach ( $line as $ckey => $column ) { - $column_name = $headers[$ckey]; - $column = trim( $column ); + $column_name = $headers[ $ckey ]; + $column = trim( $column ); if ( in_array( $column_name, $userdata_fields ) ) { - $userdata[$column_name] = $column; + $userdata[ $column_name ] = $column; } else { - $usermeta[$column_name] = $column; + $usermeta[ $column_name ] = $column; } } // A plugin may need to filter the data and meta - $userdata = apply_filters( 'is_iu_import_userdata', $userdata, $usermeta ); - $usermeta = apply_filters( 'is_iu_import_usermeta', $usermeta, $userdata ); + $userdata = apply_filters( 'pmproiucsv_import_userdata', $userdata, $usermeta ); + $usermeta = apply_filters( 'pmproiucsv_import_usermeta', $usermeta, $userdata ); // If no user data, bailout! - if ( empty( $userdata ) ) + if ( empty( $userdata ) ) { continue; + } // Something to be done before importing one user? - do_action( 'is_iu_pre_user_import', $userdata, $usermeta ); + do_action( 'pmproiucsv_pre_user_import', $userdata, $usermeta ); $user = $user_id = false; - if ( isset( $userdata['ID'] ) ) + if ( isset( $userdata['ID'] ) ) { $user = get_user_by( 'ID', $userdata['ID'] ); + } if ( ! $user && $users_update ) { - if ( isset( $userdata['user_login'] ) ) + if ( isset( $userdata['user_login'] ) ) { $user = get_user_by( 'login', $userdata['user_login'] ); + } - if ( ! $user && isset( $userdata['user_email'] ) ) + if ( ! $user && isset( $userdata['user_email'] ) ) { $user = get_user_by( 'email', $userdata['user_email'] ); + } } $update = false; if ( $user ) { $userdata['ID'] = $user->ID; - $update = true; + $update = true; } // If creating a new user and no password was set, let auto-generate one! - if ( ! $update && empty( $userdata['user_pass'] ) ) + if ( ! $update && empty( $userdata['user_pass'] ) ) { $userdata['user_pass'] = wp_generate_password( 12, false ); + } - if ( $update ) + if ( $update ) { $user_id = wp_update_user( $userdata ); - else + } else { $user_id = wp_insert_user( $userdata ); + } // Is there an error o_O? if ( is_wp_error( $user_id ) ) { - $errors[$rkey] = $user_id; + $errors[ $rkey ] = $user_id; } else { // If no error, let's update the user meta too! if ( $usermeta ) { @@ -505,45 +524,74 @@ public static function import_csv( $filename, $args ) { // If we created a new user, maybe set password nag and send new user notification? if ( ! $update ) { - if ( $password_nag ) - update_user_option( $user_id, 'default_password_nag', true, true ); - - if ( $new_user_notification ) + if ( $new_user_notification ) { wp_new_user_notification( $user_id, $userdata['user_pass'] ); + } } // Some plugins may need to do things after one user has been imported. Who know? - do_action( 'is_iu_post_user_import', $user_id ); + do_action( 'pmproiucsv_post_user_import', $user_id ); $user_ids[] = $user_id; - } + } $rkey++; - //if doing a partial import, save our spot and break - if(!empty($partial) && $rkey) - { + // if doing a partial import, save our spot and break + if ( ! empty( $partial ) && $rkey ) { $position = $csv_reader->get_position(); - set_transient('iufcsv_' . basename($filename), $position, 60*60*24*2); + set_transient( 'pmproiucsv_' . basename( $filename ), $position, 60 * 60 * 24 * 2 ); - if($rkey > $per_partial-1) + if ( $rkey > $per_partial - 1 ) { break; + } } } fclose( $file_handle ); // One more thing to do after all imports? - do_action( 'is_iu_post_users_import', $user_ids, $errors ); + do_action( 'pmproiucsv_post_users_import', $user_ids, $errors ); // Let's log the errors self::log_errors( $errors ); return array( 'user_ids' => $user_ids, - 'errors' => $errors + 'errors' => $errors, ); } + /** + * Check the first line of the CSV is valid and contains the required headers. + * + * @param file $file The CSV file to check for specific headers. + * @return boolean|array $is_csv_valid True if everything is okay, otherwise it will return an array of a list of headers that are invalid or missing? + */ + public static function is_csv_valid( $filename ) { + $is_csv_valid = true; + + // Return an empty array if empty. + if ( empty( $filename ) ) { + return array(); + } + // Let's get the first line of the CSV file and make sure the headers are listed here. + $file_handle = fopen( $filename, 'r' ); + $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. + $headers = $csv_reader->get_row(); // Gets the first row. + + // Valid headers we need for import. + $valid_headers = apply_filters( 'pmproiucsv_required_csv_headers_array', array( 'user_login', 'user_email' ) ); + $missing_required_headers = array_diff( $valid_headers, $headers ); + + // If there are missing required headers, lets show a warning. + if ( ! empty( $missing_required_headers ) ) { + $is_csv_valid = $missing_required_headers; + } + + // Close the file now that we're done with it. + fclose($file_handle); + return $is_csv_valid; + } /** * Log errors to a file * @@ -567,4 +615,4 @@ private static function log_errors( $errors ) { } } -PMPro_Import_Users_From_CSV::init(); \ No newline at end of file +PMPro_Import_Users_From_CSV::init(); From 7a60e2115158b4c9ddd7c2c2d60a093d9bf2086e Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Thu, 9 Nov 2023 13:34:04 +0200 Subject: [PATCH 06/44] IUCSV deactivate plugin --- pmpro-import-users-from-csv.php | 59 +++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 254d111..444ca3f 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -32,6 +32,7 @@ public static function init() { add_action( 'admin_menu', array( get_called_class(), 'add_admin_pages' ) ); add_action( 'init', array( get_called_class(), 'process_csv' ) ); add_action( 'init', array( get_called_class(), 'pmproiufcsv_load_textdomain' ) ); + add_action( 'admin_init', array( get_called_class(), 'deactivate_old_plugin' ) ); add_action( 'admin_enqueue_scripts', array( get_called_class(), 'admin_enqueue_scripts' ) ); add_action( 'wp_ajax_pmpro_import_users_from_csv', array( get_called_class(), 'wp_ajax_pmpro_import_users_from_csv' ) ); @@ -71,14 +72,13 @@ public static function pmproiufcsv_load_textdomain() { * @since 0.1 **/ public static function add_admin_pages() { - $location = defined( 'PMPRO_VERSION' ) ? 'options-general.php' : 'users.php'; - add_submenu_page( $location, 'Import From CSV', 'Import From CSV', 'manage_options', 'pmpro-import-users-from-csv', array( get_called_class(), 'users_page' ) ); + add_submenu_page( 'users.php', 'Import From CSV', 'Import From CSV', 'manage_options', 'pmpro-import-users-from-csv', array( get_called_class(), 'users_page' ) ); } /** * Add admin JS * - * @since ? + * @since TBD **/ public static function admin_enqueue_scripts( $hook ) { if ( empty( $_GET['page'] ) || $_GET['page'] != 'pmpro-import-users-from-csv' ) { @@ -103,10 +103,6 @@ public static function process_csv() { $users_update = isset( $_POST['users_update'] ) ? $_POST['users_update'] : false; $new_user_notification = isset( $_POST['new_user_notification'] ) ? $_POST['new_user_notification'] : false; - /// Check this at a later stage. - // Before continuing, let's make sure the CSV file is valid. - // $is_csv_valid = self::is_csv_valid( $filename ); - // use AJAX? if ( ! empty( $_POST['ajaximport'] ) ) { // check for a imports directory in wp-content @@ -311,7 +307,11 @@ public static function users_page() { + + + +

@@ -418,6 +418,11 @@ public static function import_csv( $filename, $args ) { 'role', ); + // We need this for AJAX calls. + if ( ! class_exists( 'ReadCSV' ) ) { + include plugin_dir_path( __FILE__ ) . 'class-readcsv.php'; + } + // Loop through the file lines $file_handle = fopen( $filename, 'r' ); $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. @@ -474,15 +479,15 @@ public static function import_csv( $filename, $args ) { continue; } - // Something to be done before importing one user? - do_action( 'pmproiucsv_pre_user_import', $userdata, $usermeta ); - $user = $user_id = false; if ( isset( $userdata['ID'] ) ) { $user = get_user_by( 'ID', $userdata['ID'] ); } + // Something to be done before importing one user? + do_action( 'pmproiucsv_pre_user_import', $userdata, $usermeta, $user ); + if ( ! $user && $users_update ) { if ( isset( $userdata['user_login'] ) ) { $user = get_user_by( 'login', $userdata['user_login'] ); @@ -561,6 +566,7 @@ public static function import_csv( $filename, $args ) { ); } + /** * Check the first line of the CSV is valid and contains the required headers. * @@ -577,21 +583,46 @@ public static function is_csv_valid( $filename ) { // Let's get the first line of the CSV file and make sure the headers are listed here. $file_handle = fopen( $filename, 'r' ); $csv_reader = new ReadCSV( $file_handle, PMPROIUCSV_CSV_DELIMITER, "\xEF\xBB\xBF" ); // Skip any UTF-8 byte order mark. - $headers = $csv_reader->get_row(); // Gets the first row. + $headers = $csv_reader->get_row(); // Gets the first row. // Valid headers we need for import. - $valid_headers = apply_filters( 'pmproiucsv_required_csv_headers_array', array( 'user_login', 'user_email' ) ); + $valid_headers = apply_filters( 'pmproiucsv_required_csv_headers_array', array( 'user_login', 'user_email' ) ); $missing_required_headers = array_diff( $valid_headers, $headers ); - + // If there are missing required headers, lets show a warning. if ( ! empty( $missing_required_headers ) ) { $is_csv_valid = $missing_required_headers; } // Close the file now that we're done with it. - fclose($file_handle); + fclose( $file_handle ); return $is_csv_valid; } + + /** + * Deactivate Import Users From CSV if our plugin is activated. + */ + public static function deactivate_old_plugin() { + // See if the old Import Users From CSV is activated, if so deactivate it. + if ( is_plugin_active( 'import-users-from-csv/import-users-from-csv.php' ) ) { + deactivate_plugins( 'import-users-from-csv/import-users-from-csv.php' ); + // Show an admin notice that it was deactivated and not needed for the PMPro Import? + add_action( 'admin_notices', array( get_called_class(), 'admin_notice_deactivate_old_plugin' ) ); + } + + } + + /** + * Show an admin notice that the old plugin was deactivated. + */ + public static function admin_notice_deactivate_old_plugin() { + // Show admin notice that the old plugin was deactivated. + ?> +
+

+
+ Date: Fri, 10 Nov 2023 10:57:46 +0200 Subject: [PATCH 07/44] Added support for multiple roles back. * Sanitized and added functionality for multiple roles. --- .DS_Store | Bin 0 -> 6148 bytes includes/ajaximport.js | 6 +++- includes/pmpro-membership-data.php | 3 +- pmpro-import-users-from-csv.php | 56 +++++++++++++++++++++-------- 4 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0c91ed19c01f38a5670c4e17b7e7f54aa338f3c6 GIT binary patch literal 6148 zcmeHKF;2rU6#Xt8TES3>g$*Bp-XK(|#Kh7IG;L6el#ocM5(`oez`zDaVC4)I8wcPP zjI6xxZyJ||!h{g|U$UR|^K8GoSg}n+X0S;6L_H$vpfI+&s2YNwbJ>WN&+Gw>8>6I* z&ge3m^+y|QP5ea%_}g{x&8{e+oPzH+E%H(Xo2ZGC7jZnviU~hk-rpVC>!;&pUiu?m zIq#3^Ie9%o*EwC#6l-ch>BqXNIm5RmPN?hTCD8S0th}goZR4%$8ndo06eUnLgRTU% zU-7lF{l{ijsO|Rj`R3tu$(}Wg7pmLNT-SHR`ZUx5bwC~X&H?P%9GzW3tymfSPhgWH8tq!OIfdf0z?(zOV{QCYM^w6d{pbq>u2TVI2#RI&R z-&;4{kM~-OGDhKGUM0Bdf`;3Q!R4*^0L2CJmK(sxV literal 0 HcmV?d00001 diff --git a/includes/ajaximport.js b/includes/ajaximport.js index 95c9f60..03a8fa2 100644 --- a/includes/ajaximport.js +++ b/includes/ajaximport.js @@ -40,24 +40,28 @@ jQuery(document).ready(function () { data: 'action=pmpro_import_users_from_csv&filename=' + ai_filename + '&users_update=' + ai_users_update + '&new_user_notification=' + ai_new_user_notification, error: function (xml) { alert('Error with import. Try refreshing.'); + jQuery('#pmproiucsv_return_home').show(); }, success: function (responseHTML) { if (responseHTML == 'error') { alert('Error with import. Try refreshing.'); document.title = $title; + jQuery('#pmproiucsv_return_home').show(); } else if (responseHTML == 'nofile') { $status.html($status.html() + '\nCould not find the file ' + ai_filename + '. Maybe it has already been imported.'); document.title = $title; + jQuery('#pmproiucsv_return_home').show(); } else if (responseHTML == 'done') { $status.html($status.html() + '\nDone!'); document.title = '! ' + $title; + jQuery('#pmproiucsv_return_home').show(); } else { $status.html($status.html() + responseHTML); document.title = $cycles[$count % 4] + ' ' + $title; - + jQuery('#pmproiucsv_return_home').show(); if (!$pauseimport) var $import_timer = setTimeout(function () { ai_importPartial(); }, 2000); } diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index 0267e7e..6a7d749 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -327,8 +327,7 @@ function pmproiufcsv_add_import_options() { ?> - - +
diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 444ca3f..de172d2 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -247,15 +247,16 @@ public static function users_page() { delete_transient( 'pmproiucsv_' . $filename ); } ?> -

Importing file over AJAX

+

IMPORTANT: Your import is not finished. Closing this page will stop it. If the import stops or you have to close your browser, you can navigate to this URL to resume the import later.

- Click here to pause. - + +

- + + +
+
+

+

+ IMPORTANT: Your import is not finished. Closing this page will stop it. If the import stops or you have to close your browser, you can navigate to this URL to resume the import later.', admin_url( 'users.php' . '?' . $_SERVER['QUERY_STRING'] ) ) ); // Separated the '?' for readability. + ?> +

+

+ + +

+ + + +
+
-
- - - - - - - - - - - - - - - - - - - - -
-
- the example of the CSV file.', 'pmpro-import-users-from-csv' ), plugin_dir_url( __FILE__ ) . 'examples/import.csv' ); ?> -
- - -
- - -
- - -
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

example CSV file for help formatting your data for import.', 'pmpro-import-users-from-csv' ), esc_url( plugin_dir_url( __FILE__ ) . 'examples/import.csv' ) ); ?>

+
+ + +
+ + +
+ + +
-

- -

- - - + +

+ +
+
+ + + Import From CSV. Browse to CSV file and import. - - pmpro_stripe_customerid (for Stripe users, will be same as membership_subscription_transaction_id above) -5. (Optional) Send a welcome email by setting the global $pmproiufcsv_email. See example below. -6. Go to Users --> Import From CSV. Browse to CSV file and import. - -Copy these lines to your active theme's functions.php or custom plugin and modify as desired to send a welcome email to members after import: - -global $pmproiufcsv_email; -$pmproiufcsv_email = array( - 'subject' => sprintf('Welcome to %s', get_bloginfo('sitename')), //email subject, "Welcome to Sitename" - 'body' => 'Your welcome email body text will go here.' //email body -); +Import Members From CSV is a Paid Memberships Pro Add On that allows you to create new users and update existing users by importing a simple CSV file. + +This Add On creates users, assigns membership levels to new or existing users, and updates subscription information. + +If you are migrating from another platform, you can adding gateway subscription information to your import and continue existing subscriptions. After import, a single placeholder order (used for gateway syncing) will be created. + +For more help using this Add On, refer to the documentation here: https://www.paidmembershipspro.com/add-ons/pmpro-import-users-csv/. == Frequently Asked Questions == From 5b74eef92e69e89168047c35b99748ab6924748d Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Fri, 5 Jan 2024 08:09:13 -0500 Subject: [PATCH 31/44] Fixing links --- pmpro-import-users-from-csv.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index d926863..c77faf0 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -1,7 +1,7 @@ ' . __( 'Docs', 'pmpro-import-users-from-csv' ) . '', - '' . __( 'Support', 'pmpro-import-users-from-csv' ) . '', + '' . __( 'Docs', 'pmpro-import-users-from-csv' ) . '', + '' . __( 'Support', 'pmpro-import-users-from-csv' ) . '', ); $links = array_merge( $links, $new_links ); } From e07439f1619f94ea5dfbc800d75982a0071d0718 Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Fri, 5 Jan 2024 15:42:34 +0200 Subject: [PATCH 32/44] Stop passwords from sending on updating users * BUG FIX: Fixed an issue where emails would always send when the user would be updated via CSV. --- pmpro-import-users-from-csv.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index dfc743a..226bd2c 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -573,10 +573,8 @@ public static function import_csv( $filename, $args ) { } if ( $update && $users_update ) { - /** - * This sends a password changed email to the user when user_pass is added to the CSV. - * To stop this from sending emails, see https://developer.wordpress.org/reference/hooks/send_password_change_email/ - */ + // If we're updating the user, don't send any password emails. + add_filter( 'send_password_change_email', '__return_false' ); $user_id = wp_update_user( $userdata ); } else { $user_id = wp_insert_user( $userdata ); @@ -603,7 +601,7 @@ public static function import_csv( $filename, $args ) { } // If we need to show the new password notification, let's send it. - if ( $new_user_notification ) { + if ( $new_user_notification && ! $update ) { wp_new_user_notification( $user_id, $userdata['user_pass'], 'user' ); } From 4d5d3cb449e817bca1e9931212763d74d91b363b Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Sun, 7 Jan 2024 13:33:12 -0500 Subject: [PATCH 33/44] Removing unnecessary code comment; removed option to send a welcome email to members after import (create a gist instead) --- includes/pmpro-membership-data.php | 102 +++++++---------------------- 1 file changed, 25 insertions(+), 77 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index 146b538..f454b05 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -1,49 +1,7 @@ Import From CSV. Browse to CSV file and import. - - pmpro_stripe_customerid (for Stripe users, will be same as membership_subscription_transaction_id above) - 5. (Optional) Send a welcome email by setting the global $pmproiufcsv_email. See example below. - 6. Go to Users --> Import From CSV. Browse to CSV file and import. - - Copy these lines to your active theme's functions.php or custom plugin and modify as desired to send a welcome email to members after import: - - global $pmproiufcsv_email; - $pmproiufcsv_email = array( - 'subject' => sprintf('Welcome to %s', get_bloginfo('sitename')), //email subject, "Welcome to Sitename" - 'body' => 'Your welcome email body text will go here.' //email body - ); -*/ - -/* - Get list of PMPro-related fields -*/ -/// Done. +/** + * Get list of PMPro-related fields. + */ function pmproiufcsv_getFields() { $pmpro_fields = array( "membership_id", @@ -70,10 +28,9 @@ function pmproiufcsv_getFields() { return $pmpro_fields; } -/* - Delete all import_ meta fields before an import in case the user has been imported in the past. -*/ -/// Done. +/** + * Delete all import_ meta fields before an import in case the user has been imported in the past. + */ function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { if(!empty($user)) { @@ -87,7 +44,7 @@ function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { /** * Filter to allow cancellation of subscriptions during import. * - * @since TBD + * @since 0.4 * @param boolean $allow_sub_cancellations Set this option to true if you want to let subscriptions cancel on the gateway level during import. */ if ( ! apply_filters( 'pmproiufcsv_cancel_prev_sub_on_import', false ) ) { @@ -97,9 +54,9 @@ function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { } add_action( 'pmproiucsv_pre_user_import', 'pmproiufcsv_is_iu_pre_user_import', 10, 3 ); -/* - Change some of the imported columns to add "imported_" to the front so we don't confuse the data later. -*/ +/** + * Change some of the imported columns to add "imported_" to the front so we don't confuse the data later. + */ function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) { $pmpro_fields = pmproiufcsv_getFields(); @@ -117,15 +74,17 @@ function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) } add_filter("pmproiucsv_import_usermeta", "pmproiufcsv_is_iu_import_usermeta", 10, 2); -//after users are added, let's use the meta data imported to update the user +/** + * After users are added, let's use the meta data imported to update the user. + */ function pmproiufcsv_is_iu_post_user_import($user_id) { - global $pmproiufcsv_email, $wpdb; + global $wpdb; wp_cache_delete($user_id, 'users'); $user = get_userdata($user_id); - //look for a membership level and other information + // Look for a membership level and other information. $membership_id = $user->import_membership_id; $membership_code_id = $user->import_membership_code_id; $membership_discount_code = $user->import_membership_discount_code; @@ -141,7 +100,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) $membership_enddate = $user->import_membership_enddate; $membership_timestamp = $user->import_membership_timestamp; - //fix date formats + // Fix date formats. if ( ! empty( $membership_startdate ) ) { $membership_startdate = date( 'Y-m-d', strtotime( $membership_startdate, current_time( 'timestamp' ) ) ); } else { @@ -174,12 +133,12 @@ function pmproiufcsv_is_iu_post_user_import($user_id) return; } - //look up discount code + // Look up discount code. if ( ! empty( $membership_discount_code ) && empty( $membership_code_id ) ) { $membership_code_id = $wpdb->get_var( "SELECT id FROM $wpdb->pmpro_discount_codes WHERE `code` = '" . esc_sql( $membership_discount_code ) . "' LIMIT 1" ); } - //look for a subscription transaction id and gateway + // Look for a subscription transaction id and gateway. $membership_subscription_transaction_id = $user->import_membership_subscription_transaction_id; $membership_payment_transaction_id = $user->import_membership_payment_transaction_id; $membership_order_status = $user->import_membership_order_status; @@ -195,7 +154,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) } - //change membership level + // Change membership level. if(!empty($membership_id)) { $custom_level = array( @@ -216,7 +175,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) pmpro_changeMembershipLevel($custom_level, $user_id); - //if membership was in the past make it inactive + // If membership was in the past make it inactive. if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) { $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; @@ -231,9 +190,8 @@ function pmproiufcsv_is_iu_post_user_import($user_id) } } - //add order so integration with gateway works + // Add order so integration with gateway works. if( -// !empty($membership_subscription_transaction_id) && !empty($membership_gateway) || !empty($membership_timestamp) || !empty($membership_code_id) ) @@ -257,7 +215,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) $order->saveOrder(); - //update timestamp of order? + // Maybe update timestamp of order. if(!empty($membership_timestamp)) { $timestamp = strtotime($membership_timestamp, current_time('timestamp')); @@ -265,27 +223,17 @@ function pmproiufcsv_is_iu_post_user_import($user_id) } } - //add code use + // Add code use. if(!empty($membership_code_id) && !empty($order) && !empty($order->id)) $wpdb->query("INSERT INTO $wpdb->pmpro_discount_codes_uses (code_id, user_id, order_id, timestamp) VALUES('" . esc_sql($membership_code_id) . "', '" . esc_sql($user_id) . "', '" . intval($order->id) . "', now())"); - //email user if global is set - if(!empty($pmproiufcsv_email)) - { - $email = new PMProEmail(); - $email->email = $user->user_email; - $email->subject = $pmproiufcsv_email['subject']; - $email->body = $pmproiufcsv_email['body']; - $email->template = 'pmproiufcsv'; - $email->sendEmail(); - } } add_action("pmproiucsv_post_user_import", "pmproiufcsv_is_iu_post_user_import"); /** * Add error/warning message if user's were imported with both a subscription and expiration date. * - * @since TBD + * @since 0.4 * * @param array $errors An array of various error messages. * @param [type] $user_ids @@ -320,12 +268,12 @@ function pmproiufcsv_add_import_options() { ?> Date: Sun, 7 Jan 2024 14:05:27 -0500 Subject: [PATCH 34/44] Logic to cancel all memberships if the import level ID is exactly 0 --- includes/pmpro-membership-data.php | 71 ++++++++++++++++-------------- pmpro-import-users-from-csv.php | 2 +- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index f454b05..c64fa58 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -153,40 +153,45 @@ function pmproiufcsv_is_iu_post_user_import($user_id) add_filter( 'pmproiucsv_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); } + // Process level changes if membership_id is set. + if ( isset( $membership_id ) && ( $membership_id === '0' || ! empty( $membership_id ) ) ) { - // Change membership level. - if(!empty($membership_id)) - { - $custom_level = array( - 'user_id' => $user_id, - 'membership_id' => $membership_id, - 'code_id' => $membership_code_id, - 'initial_payment' => $membership_initial_payment, - 'billing_amount' => $membership_billing_amount, - 'cycle_number' => $membership_cycle_number, - 'cycle_period' => $membership_cycle_period, - 'billing_limit' => $membership_billing_limit, - 'trial_amount' => $membership_trial_amount, - 'trial_limit' => $membership_trial_limit, - 'status' => $membership_status, - 'startdate' => $membership_startdate, - 'enddate' => $membership_enddate - ); - - pmpro_changeMembershipLevel($custom_level, $user_id); - - // If membership was in the past make it inactive. - if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) - { - $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; - $wpdb->query($sqlQuery); - $membership_in_the_past = true; - } - - if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) - { - $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); - $wpdb->query($sqlQuery); + // Cancel all memberships if membership_id is set to 0. + if ( $membership_id === '0' ) { + pmpro_changeMembershipLevel( 0, $user_id ); + } else { + // Give the user the membership level. + $custom_level = array( + 'user_id' => $user_id, + 'membership_id' => $membership_id, + 'code_id' => $membership_code_id, + 'initial_payment' => $membership_initial_payment, + 'billing_amount' => $membership_billing_amount, + 'cycle_number' => $membership_cycle_number, + 'cycle_period' => $membership_cycle_period, + 'billing_limit' => $membership_billing_limit, + 'trial_amount' => $membership_trial_amount, + 'trial_limit' => $membership_trial_limit, + 'status' => $membership_status, + 'startdate' => $membership_startdate, + 'enddate' => $membership_enddate + ); + + pmpro_changeMembershipLevel($custom_level, $user_id); + + // If membership was in the past make it inactive. + if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) + { + $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; + $wpdb->query($sqlQuery); + $membership_in_the_past = true; + } + + if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) + { + $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); + $wpdb->query($sqlQuery); + } } } diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 9db3f65..5517344 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -245,7 +245,7 @@ public static function users_page() { } } - // Adds a query arg in the + // Show error log link if there are errors. if ( isset( $_REQUEST['import'] ) ) { $error_log_msg = ''; if ( file_exists( $error_log_file ) ) { From db3307eb1240b792b6ac2e10e2432bc10d21338a Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Sun, 7 Jan 2024 14:24:28 -0500 Subject: [PATCH 35/44] Revert "Logic to cancel all memberships if the import level ID is exactly 0" This reverts commit 466b588beb981f82efbfb5902341152d896ff493. --- includes/pmpro-membership-data.php | 71 ++++++++++++++---------------- pmpro-import-users-from-csv.php | 2 +- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index c64fa58..f454b05 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -153,45 +153,40 @@ function pmproiufcsv_is_iu_post_user_import($user_id) add_filter( 'pmproiucsv_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); } - // Process level changes if membership_id is set. - if ( isset( $membership_id ) && ( $membership_id === '0' || ! empty( $membership_id ) ) ) { - // Cancel all memberships if membership_id is set to 0. - if ( $membership_id === '0' ) { - pmpro_changeMembershipLevel( 0, $user_id ); - } else { - // Give the user the membership level. - $custom_level = array( - 'user_id' => $user_id, - 'membership_id' => $membership_id, - 'code_id' => $membership_code_id, - 'initial_payment' => $membership_initial_payment, - 'billing_amount' => $membership_billing_amount, - 'cycle_number' => $membership_cycle_number, - 'cycle_period' => $membership_cycle_period, - 'billing_limit' => $membership_billing_limit, - 'trial_amount' => $membership_trial_amount, - 'trial_limit' => $membership_trial_limit, - 'status' => $membership_status, - 'startdate' => $membership_startdate, - 'enddate' => $membership_enddate - ); - - pmpro_changeMembershipLevel($custom_level, $user_id); - - // If membership was in the past make it inactive. - if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) - { - $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; - $wpdb->query($sqlQuery); - $membership_in_the_past = true; - } - - if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) - { - $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); - $wpdb->query($sqlQuery); - } + // Change membership level. + if(!empty($membership_id)) + { + $custom_level = array( + 'user_id' => $user_id, + 'membership_id' => $membership_id, + 'code_id' => $membership_code_id, + 'initial_payment' => $membership_initial_payment, + 'billing_amount' => $membership_billing_amount, + 'cycle_number' => $membership_cycle_number, + 'cycle_period' => $membership_cycle_period, + 'billing_limit' => $membership_billing_limit, + 'trial_amount' => $membership_trial_amount, + 'trial_limit' => $membership_trial_limit, + 'status' => $membership_status, + 'startdate' => $membership_startdate, + 'enddate' => $membership_enddate + ); + + pmpro_changeMembershipLevel($custom_level, $user_id); + + // If membership was in the past make it inactive. + if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) + { + $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; + $wpdb->query($sqlQuery); + $membership_in_the_past = true; + } + + if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) + { + $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); + $wpdb->query($sqlQuery); } } diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 5517344..9db3f65 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -245,7 +245,7 @@ public static function users_page() { } } - // Show error log link if there are errors. + // Adds a query arg in the if ( isset( $_REQUEST['import'] ) ) { $error_log_msg = ''; if ( file_exists( $error_log_file ) ) { From 8e3e7a0e63d55632d926418b7fdf3655f4bacd78 Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Sun, 7 Jan 2024 14:27:45 -0500 Subject: [PATCH 36/44] Reverting removing uncommented line; fixing code comment --- includes/pmpro-membership-data.php | 1 + pmpro-import-users-from-csv.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index f454b05..af8f693 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -192,6 +192,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) // Add order so integration with gateway works. if( +// !empty($membership_subscription_transaction_id) && !empty($membership_gateway) || !empty($membership_timestamp) || !empty($membership_code_id) ) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 9db3f65..5517344 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -245,7 +245,7 @@ public static function users_page() { } } - // Adds a query arg in the + // Show error log link if there are errors. if ( isset( $_REQUEST['import'] ) ) { $error_log_msg = ''; if ( file_exists( $error_log_file ) ) { From ff714bdf1a2756f9f63b25df29f6ffb64c093c93 Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Sun, 7 Jan 2024 14:42:12 -0500 Subject: [PATCH 37/44] Logic to cancel level if import level ID is exactly 0 --- includes/pmpro-membership-data.php | 72 ++++++++++++++++-------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index af8f693..07673ae 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -153,40 +153,44 @@ function pmproiufcsv_is_iu_post_user_import($user_id) add_filter( 'pmproiucsv_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); } - - // Change membership level. - if(!empty($membership_id)) - { - $custom_level = array( - 'user_id' => $user_id, - 'membership_id' => $membership_id, - 'code_id' => $membership_code_id, - 'initial_payment' => $membership_initial_payment, - 'billing_amount' => $membership_billing_amount, - 'cycle_number' => $membership_cycle_number, - 'cycle_period' => $membership_cycle_period, - 'billing_limit' => $membership_billing_limit, - 'trial_amount' => $membership_trial_amount, - 'trial_limit' => $membership_trial_limit, - 'status' => $membership_status, - 'startdate' => $membership_startdate, - 'enddate' => $membership_enddate - ); - - pmpro_changeMembershipLevel($custom_level, $user_id); - - // If membership was in the past make it inactive. - if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) - { - $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; - $wpdb->query($sqlQuery); - $membership_in_the_past = true; - } - - if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) - { - $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); - $wpdb->query($sqlQuery); + // Process level changes if membership_id is set. + if ( isset( $membership_id ) && ( $membership_id === '0' || ! empty( $membership_id ) ) ) { + // Cancel all memberships if membership_id is set to 0. + if ( $membership_id === '0' ) { + pmpro_changeMembershipLevel( 0, $user_id ); + } else { + // Give the user the membership level. + $custom_level = array( + 'user_id' => $user_id, + 'membership_id' => $membership_id, + 'code_id' => $membership_code_id, + 'initial_payment' => $membership_initial_payment, + 'billing_amount' => $membership_billing_amount, + 'cycle_number' => $membership_cycle_number, + 'cycle_period' => $membership_cycle_period, + 'billing_limit' => $membership_billing_limit, + 'trial_amount' => $membership_trial_amount, + 'trial_limit' => $membership_trial_limit, + 'status' => $membership_status, + 'startdate' => $membership_startdate, + 'enddate' => $membership_enddate + ); + + pmpro_changeMembershipLevel($custom_level, $user_id); + + // If membership was in the past make it inactive. + if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) + { + $sqlQuery = "UPDATE $wpdb->pmpro_memberships_users SET status = 'inactive' WHERE user_id = '" . $user_id . "' AND membership_id = '" . $membership_id . "'"; + $wpdb->query($sqlQuery); + $membership_in_the_past = true; + } + + if($membership_status === "active" && (empty($membership_enddate) || $membership_enddate === "NULL" || strtotime($membership_enddate, current_time('timestamp')) >= current_time('timestamp'))) + { + $sqlQuery = $wpdb->prepare("UPDATE {$wpdb->pmpro_memberships_users} SET status = 'active' WHERE user_id = %d AND membership_id = %d ORDER BY id DESC LIMIT 1", $user_id, $membership_id); + $wpdb->query($sqlQuery); + } } } From 71acf91efe0901bc601cf0ade09d06ac2b2626e4 Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Mon, 8 Jan 2024 11:39:25 +0200 Subject: [PATCH 38/44] Added error handling for levels that don't exist. * ENHANCEMENT: Throw an error when the import membership_id value does not exist. --- includes/pmpro-membership-data.php | 27 ++++++++++++++++++++++----- pmpro-import-users-from-csv.php | 2 ++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index af8f693..c281e7b 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -150,7 +150,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) * If there is a membership_subscription_transaction_id column with a value AND membership_status column with value active (or assume active if missing) AND the membership_enddate column is not empty, then (1) throw an warning but continue to import */ - add_filter( 'pmproiucsv_errors_filter', 'pmproiufcsv_report_sub_error', 10, 2 ); + add_filter( 'pmproiucsv_errors_filter', 'pmproiucsv_report_sub_error', 10, 2 ); } @@ -173,7 +173,9 @@ function pmproiufcsv_is_iu_post_user_import($user_id) 'enddate' => $membership_enddate ); - pmpro_changeMembershipLevel($custom_level, $user_id); + if ( ! pmpro_changeMembershipLevel( $custom_level, $user_id ) ) { + add_filter( 'pmproiucsv_errors_filter', 'pmproiucsv_report_non_existent_level', 10, 2 ); + } // If membership was in the past make it inactive. if($membership_status === "inactive" || (!empty($membership_enddate) && $membership_enddate !== "NULL" && strtotime($membership_enddate, current_time('timestamp')) < current_time('timestamp'))) @@ -240,7 +242,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) * @param [type] $user_ids * @return array $error The error message that is set when an import fails. */ -function pmproiufcsv_report_sub_error ( $errors, $user_ids ){ +function pmproiucsv_report_sub_error ( $errors, $user_ids ){ $error_message = sprintf( __( 'User imported with both an active subscription and a membership enddate. This configuration is not recommended with PMPro ($1$s). This user has been imported with no enddate.', 'pmpro-import-users-from-csv' ), 'https://www.paidmembershipspro.com/important-notes-on-recurring-billing-and-expiration-dates-for-membership-levels/' ); @@ -250,12 +252,27 @@ function pmproiufcsv_report_sub_error ( $errors, $user_ids ){ } +/** + * Throw an error/warning when importing a membership ID but the level does not exist in the database. + * + * @since TBD + */ +function pmproiucsv_report_non_existent_level( $errors, $user_ids ) { + + $error_message = __( "Failed to import membership level. Level does not exist. Please confirm your membership_id value corresponds to one of your membership ID's", 'pmpro-import-users-from-csv' ); + + $errors[] = new WP_Error( 'level_not_found', $error_message ); + + return $errors; + +} + /** * Render the additional options for the Import Users from CSV plugin settings page. * * @since 0.4 */ -function pmproiufcsv_add_import_options() { ?> +function pmproiucsv_add_import_options() { ?> @@ -270,7 +287,7 @@ function pmproiufcsv_add_import_options() { ?> Date: Mon, 8 Jan 2024 11:44:25 +0200 Subject: [PATCH 39/44] Update prefix of membership functions * REFACTOR: Moved from pmproiufcsv_ to pmproiucsv_ across the plugin. --- includes/pmpro-membership-data.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index c281e7b..872de8b 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -2,7 +2,7 @@ /** * Get list of PMPro-related fields. */ -function pmproiufcsv_getFields() { +function pmproiucsv_getFields() { $pmpro_fields = array( "membership_id", "membership_code_id", @@ -31,10 +31,10 @@ function pmproiufcsv_getFields() { /** * Delete all import_ meta fields before an import in case the user has been imported in the past. */ -function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { +function pmproiucsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { if(!empty($user)) { - $pmpro_fields = pmproiufcsv_getFields(); + $pmpro_fields = pmproiucsv_getFields(); foreach($pmpro_fields as $field) { delete_user_meta($user->ID, "import_" . $field); @@ -47,19 +47,19 @@ function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { * @since 0.4 * @param boolean $allow_sub_cancellations Set this option to true if you want to let subscriptions cancel on the gateway level during import. */ - if ( ! apply_filters( 'pmproiufcsv_cancel_prev_sub_on_import', false ) ) { + if ( ! apply_filters( 'pmproiufcsv_cancel_prev_sub_on_import', false ) || ! apply_filters( 'pmproiucsv_cancel_prev_sub_on_import', false ) ) { add_filter( 'pmpro_cancel_previous_subscriptions', '__return_false' ); } } -add_action( 'pmproiucsv_pre_user_import', 'pmproiufcsv_is_iu_pre_user_import', 10, 3 ); +add_action( 'pmproiucsv_pre_user_import', 'pmproiucsv_is_iu_pre_user_import', 10, 3 ); /** * Change some of the imported columns to add "imported_" to the front so we don't confuse the data later. */ -function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) +function pmproiucsv_is_iu_import_usermeta($usermeta, $userdata) { - $pmpro_fields = pmproiufcsv_getFields(); + $pmpro_fields = pmproiucsv_getFields(); $newusermeta = array(); foreach($usermeta as $key => $value) @@ -72,12 +72,12 @@ function pmproiufcsv_is_iu_import_usermeta($usermeta, $userdata) return $newusermeta; } -add_filter("pmproiucsv_import_usermeta", "pmproiufcsv_is_iu_import_usermeta", 10, 2); +add_filter("pmproiucsv_import_usermeta", "pmproiucsv_is_iu_import_usermeta", 10, 2); /** * After users are added, let's use the meta data imported to update the user. */ -function pmproiufcsv_is_iu_post_user_import($user_id) +function pmproiucsv_is_iu_post_user_import($user_id) { global $wpdb; @@ -231,7 +231,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) $wpdb->query("INSERT INTO $wpdb->pmpro_discount_codes_uses (code_id, user_id, order_id, timestamp) VALUES('" . esc_sql($membership_code_id) . "', '" . esc_sql($user_id) . "', '" . intval($order->id) . "', now())"); } -add_action("pmproiucsv_post_user_import", "pmproiufcsv_is_iu_post_user_import"); +add_action("pmproiucsv_post_user_import", "pmproiucsv_is_iu_post_user_import"); /** * Add error/warning message if user's were imported with both a subscription and expiration date. From 04ca48a06eba4348ccf277419bba72e083fcbec4 Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Wed, 10 Jan 2024 11:15:22 +0200 Subject: [PATCH 40/44] Remove MMPU incompatible flag Remove incompatible notice for multiple memberships per user. --- pmpro-import-users-from-csv.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 5517344..06c027c 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -36,8 +36,6 @@ public static function init() { add_action( 'admin_enqueue_scripts', array( get_called_class(), 'admin_enqueue_scripts' ) ); add_action( 'wp_ajax_pmpro_import_users_from_csv', array( get_called_class(), 'wp_ajax_pmpro_import_users_from_csv' ) ); - add_filter( 'pmpro_mmpu_incompatible_add_ons', array( get_called_class(), 'pmproiucsv_mmpu_incompatible_add_ons' ) ); - add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( get_called_class(), 'add_action_links' ), 10, 2); add_filter( 'plugin_row_meta', array( get_called_class(), 'plugin_row_meta' ), 10, 2); @@ -72,14 +70,6 @@ public static function pmproiucsv_load_textdomain() { load_plugin_textdomain( 'pmpro-import-users-from-csv', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' ); } - /** - * Mark the plugin as MMPU-incompatible. - */ - public static function pmproiucsv_mmpu_incompatible_add_ons( $incompatible ) { - $incompatible[] = 'PMPro Import Users from CSV Add On'; - return $incompatible; - } - /** * Add admin menus and load the location based on PMPro installed status or not. * From 623b998e426bf8b70f3b8e9d9ba14295a965d6f2 Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Wed, 10 Jan 2024 15:40:05 +0200 Subject: [PATCH 41/44] Skip empty user meta data. --- pmpro-import-users-from-csv.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pmpro-import-users-from-csv.php b/pmpro-import-users-from-csv.php index 5517344..1f52d2b 100644 --- a/pmpro-import-users-from-csv.php +++ b/pmpro-import-users-from-csv.php @@ -625,6 +625,11 @@ public static function import_csv( $filename, $args ) { // If no error, let's update the user meta too! if ( $usermeta ) { foreach ( $usermeta as $metakey => $metavalue ) { + // If the value of the meta key is empty, lets not do anything but skip it. + if ( empty( $metavalue ) ) { + continue; + } + $metavalue = maybe_unserialize( $metavalue ); update_user_meta( $user_id, $metakey, $metavalue ); } From a804bc8460af21b2ea326f02650f8f0a7a779024 Mon Sep 17 00:00:00 2001 From: Kim Coleman Date: Wed, 10 Jan 2024 13:22:50 -0500 Subject: [PATCH 42/44] Update cases and logic around when the import should create an order --- includes/pmpro-membership-data.php | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index 07673ae..19d0db4 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -194,13 +194,27 @@ function pmproiufcsv_is_iu_post_user_import($user_id) } } - // Add order so integration with gateway works. - if( -// !empty($membership_subscription_transaction_id) && - !empty($membership_gateway) || - !empty($membership_timestamp) || !empty($membership_code_id) - ) - { + /** + * This logic adds an order for two specific cases: + * - So the gateway can locate the correct user when new subscription payments are received (importing active subscriptions), or + * - So the discount code use (if imported) can be tracked. + */ + + // Are we creating an order? Assume no. + $create_order = false; + + // Create an order if we have both a membership_subscription_transaction_id and membership_gateway. + if ( ! empty( $membership_subscription_transaction_id ) && ! empty( $membership_gateway ) ) { + $create_order = true; + } + + // Create an order to track the discount code use if we have a membership_code_id. + if ( ! empty( $membership_code_id ) ) { + $create_order = true; + } + + // Create the order. + if ( $create_order ) { $order = new MemberOrder(); $order->user_id = $user_id; $order->membership_id = $membership_id; @@ -220,7 +234,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) $order->saveOrder(); - // Maybe update timestamp of order. + // Maybe update timestamp of order if the import includes the membership_timestamp. if(!empty($membership_timestamp)) { $timestamp = strtotime($membership_timestamp, current_time('timestamp')); @@ -228,7 +242,7 @@ function pmproiufcsv_is_iu_post_user_import($user_id) } } - // Add code use. + // Add code use if we have the membership_code_id and there is an order to attach to. if(!empty($membership_code_id) && !empty($order) && !empty($order->id)) $wpdb->query("INSERT INTO $wpdb->pmpro_discount_codes_uses (code_id, user_id, order_id, timestamp) VALUES('" . esc_sql($membership_code_id) . "', '" . esc_sql($user_id) . "', '" . intval($order->id) . "', now())"); From c51a85b5044ada5f0454fb3c07f0088cc87adb1f Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Thu, 11 Jan 2024 15:32:49 +0200 Subject: [PATCH 43/44] * Ability to update user with minimal identifier * ENHANCEMENT: Ability to update membership information, for existing WP accounts, with either ID, user_email or user_login only. --- includes/pmpro-membership-data.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/includes/pmpro-membership-data.php b/includes/pmpro-membership-data.php index 07673ae..f99c271 100644 --- a/includes/pmpro-membership-data.php +++ b/includes/pmpro-membership-data.php @@ -32,7 +32,21 @@ function pmproiufcsv_getFields() { * Delete all import_ meta fields before an import in case the user has been imported in the past. */ function pmproiufcsv_is_iu_pre_user_import( $userdata, $usermeta, $user ) { - + //try to get user by ID + $user = $user_id = false; + if ( isset( $userdata['ID'] ) ) { + $user = get_user_by( 'ID', $userdata['ID'] ); + } + + //try to find user by login or email if still not found. + if ( ! $user ) { + if ( isset( $userdata['user_login'] ) ) + $user = get_user_by( 'login', $userdata['user_login'] ); + + if ( ! $user && isset( $userdata['user_email'] ) ) + $user = get_user_by( 'email', $userdata['user_email'] ); + } + if(!empty($user)) { $pmpro_fields = pmproiufcsv_getFields(); From f8e2deedf1e75b842e384fcf5d7d1bb52e3d4473 Mon Sep 17 00:00:00 2001 From: Andrew Lima Date: Tue, 16 Jan 2024 10:42:19 +0200 Subject: [PATCH 44/44] Version bump to 0.1 --- readme.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 6d533bf..6fa987b 100755 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: strangerstudios Tags: paid memberships pro, import users from csv, import, csv, members Requires at least: 5.4 Tested up to: 6.4.2 -Stable tag: 0.4 +Stable tag: 1.0 Import your users or members list to WordPress and automatically assign membership levels in PMPro. @@ -29,6 +29,17 @@ Please visit our premium support site at http://www.paidmembershipspro.com for m == Changelog == += 1.0 - 2024-01-16 = +* ENHANCEMENT: Refactored entire plugin to be a standalone plugin. This plugin no longer requires the Import Users From CSV plugin to work. +* ENHANCEMENT: Supports importing multiple memberships per user. +* ENHANCEMENT: Importing membership_id 0 will cancel any existing memberships for the user. +* ENHANCEMENT: The password reset email will only send out if the option is selected during import and the user is new. This will never send when updating users or members. +* ENHANCEMENT: Added batch importing for large CSV files. This defaults to 50 imports per iteration, adjust the amount by using the filter `pmprocsv_ajax_import_batch`. +* ENHANCEMENT: General improvements and warnings when importing data that is required but missing from the CSV or importing a level that doesn't exist. +* ENHANCEMENT: Updated the CSV sample file to have newer values. +* REFACTOR: Improved logic around creating orders during import. See documentation for full details. +* BUG FIX: Fixed an issue where empty meta keys were being imported as "". This now skips and does not update the meta key if the value is blank. + = 0.4 - 2022-03-14 = * ENHANCEMENT: Added in an option to skip over members if trying to import members with their current membership level. This is helpful for large CSV files that may have duplicate records. * ENHANCEMENT: Prevent subscriptions from being cancelled by incorrect imports. Keep these subscriptions in tact, but can disable this logic by using the `pmproiufcsv_cancel_prev_sub_on_import` filter.