diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..c887932
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+.gitattributes export-ignore
+languages/gettext.sh export-ignore
+README.md export-ignore
+roles-for-membership-levels.jpg export-ignore
+.github export-ignore
\ No newline at end of file
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..a9b8b71
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,49 @@
+# Contribute to Paid Memberships Pro - Roles for Membership Levels
+
+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](http://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-roles/issues)
+ * 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-roles.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..2a43f4c
--- /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..dd160c9
--- /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 - Roles for Membership Levels.
+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..75e1915
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/support.md
@@ -0,0 +1,19 @@
+---
+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 a Paid Memberships Pro Plus member**
+Submit a ticket on our helpdesk by visiting https://www.paidmembershipspro.com/new-topic/ (Please note that an [active membership] (https://www.paidmembershipspro.com/pricing) is required for paid support.)
+
+**General usage and development questions**
+- 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..6c2e9ac
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.MD
@@ -0,0 +1,32 @@
+### All Submissions:
+
+* [ ] Have you followed the [Contributing guideline](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](../../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/generate-translations.yml b/.github/workflows/generate-translations.yml
new file mode 100644
index 0000000..426177b
--- /dev/null
+++ b/.github/workflows/generate-translations.yml
@@ -0,0 +1,18 @@
+name: Generate Translations
+on: workflow_dispatch
+jobs:
+ generate-translations:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: WordPress POT/PO/MO Generator
+ uses: strangerstudios/action-wp-pot-po-mo-generator@main
+ with:
+ generate_pot: 1
+ generate_po: 1
+ generate_mo: 1
+ generate_lang_packs: 1
+ merge_changes: 1
+ headers: '{"Report-Msgid-Bugs-To":"info@paidmembershipspro.com","Last-Translator":"Paid Memberships Pro ","Language-Team":"Paid Memberships Pro "}'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3c2d627
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+
+
+# [Roles for Membership Levels](https://www.paidmembershipspro.com/add-ons/pmpro-roles/) #
+[comment]: # (Generate badges from shields.io, only works for .org plugins to get other stats etc. We'd have to create our own endpoints for Premium plugins)
+
+
+
+### Welcome to the Roles for Membership Levels GitHub Repository
+Adds a new WordPress Role for each Membership Level. A member’s role will be set to the role for their membership level after checkout. Assign roles to members based on their membership level.
+
+For more information please visit [paidmembershipspro.com/add-ons/pmpro-roles/](https://www.paidmembershipspro.com/add-ons/pmpro-roles/)
+
+## Installation ##
+For detailed installation steps, visit the [documentation](https://www.paidmembershipspro.com/add-ons/pmpro-roles/) page.
+
+1. Download the current development ZIP file directly: `https://github.com/strangerstudios/pmpro-roles/archive/dev.zip`
+
+**Please ensure that once installing this version of the plugin to remove `-dev` from the plugin's folder name.**
+
+## Bugs ##
+If you find an issue/bug, let us know by [creating a detailed GitHub issue](https://github.com/strangerstudios/pmpro-roles/issues/new).
+
+## Support ##
+This is a developer's portal for Roles for Membership Levels. We do not offer support on this channel. **Any support related questions should be directed to [paidmembershipspro.com/add-ons/pmpro-roles/](https://www.paidmembershipspro.com/add-ons/pmpro-roles/).**
+
+## Contributing to Roles for Membership Levels ##
+We encourage and welcome any contribution to Roles for Membership Levels. Please read the [guidelines for contributing](https://github.com/strangerstudios/pmpro-roles/blob/dev/.github/CONTRIBUTING.md) to this repository.
+
+There are various **ways to the help development** of Roles for Membership Levels:
+
+1. Report [bugs/issues](https://github.com/strangerstudios/pmpro-roles/issues/new) on GitHub.
+2. Work on any issues by submitting a Pull Request.
+
+Here are some ways for **non-developers to contribute** to Roles for Membership Levels:
+
+1. Translate Roles for Membership Levels into your own [language](https://www.paidmembershipspro.com/paid-memberships-pro-in-your-language/).
+2. [Purchase a plus membership](https://paidmembershipspro.com/pricing) to help fund ongoing development and bug fixes.
\ No newline at end of file
diff --git a/admin.css b/admin.css
index 865d0e7..8f0b0b3 100644
--- a/admin.css
+++ b/admin.css
@@ -1,14 +1,15 @@
-#repair_roles_container{
+#repair_roles_container {
background: #fff;
float: left;
padding: 8px 10px;
margin-top: 25px;
}
-#repaired_roles{
+#repaired_roles {
display: none;
margin-bottom: 0;
}
-#repair_roles, #repair_roles:active{
+#repair_roles,
+#repair_roles:active {
vertical-align: middle;
margin-left: 15px;
-}
\ No newline at end of file
+}
diff --git a/languages/gettext.sh b/languages/gettext.sh
new file mode 100644
index 0000000..bbe9a7f
--- /dev/null
+++ b/languages/gettext.sh
@@ -0,0 +1,25 @@
+#---------------------------
+# This script generates a new pmpro-roles.pot file for use in translations.
+# To generate a new pmpro-roles.pot, cd to the main /pmpro-roles/ directory,
+# then execute `languages/gettext.sh` from the command line.
+# then fix the header info (helps to have the old pmpro.pot open before running script above)
+# then execute `cp languages/pmpro-roles.pot languages/pmpro-roles.po` to copy the .pot to .po
+# then execute `msgfmt languages/pmpro-roles.po --output-file languages/pmpro-roles.mo` to generate the .mo
+#---------------------------
+echo "Updating pmpro-roles.pot... "
+xgettext -j -o languages/pmpro-roles.pot \
+--default-domain=pmpro-roles \
+--language=PHP \
+--keyword=_ \
+--keyword=__ \
+--keyword=_e \
+--keyword=_ex \
+--keyword=_n \
+--keyword=_x \
+--keyword=esc_html_e \
+--keyword=esc_html__ \
+--sort-by-file \
+--package-version=1.0 \
+--msgid-bugs-address="info@paidmembershipspro.com" \
+$(find . -name "*.php")
+echo "Done!"
\ No newline at end of file
diff --git a/languages/pmpro-roles.mo b/languages/pmpro-roles.mo
new file mode 100644
index 0000000..ae3b247
Binary files /dev/null and b/languages/pmpro-roles.mo differ
diff --git a/languages/pmpro-roles.po b/languages/pmpro-roles.po
new file mode 100644
index 0000000..4036e7f
--- /dev/null
+++ b/languages/pmpro-roles.po
@@ -0,0 +1,126 @@
+# Copyright (C) 2021 Paid Memberships Pro
+# This file is distributed under the GPL2.
+msgid ""
+msgstr ""
+"Project-Id-Version: Paid Memberships Pro - Roles Add On 1.4\n"
+"Report-Msgid-Bugs-To: info@paidmembershipspro.com\n"
+"Last-Translator: Paid Memberships Pro \n"
+"Language-Team: Paid Memberships Pro \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"POT-Creation-Date: 2021-08-18T14:26:14+00:00\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"X-Generator: WP-CLI 2.5.0\n"
+"X-Domain: pmpro-roles\n"
+
+#: pmpro-roles.php:55
+msgid "Levels not matching up, or missing?"
+msgstr ""
+
+#: pmpro-roles.php:56
+msgid "Repair"
+msgstr ""
+
+#: pmpro-roles.php:57
+msgid "Working..."
+msgstr ""
+
+#: pmpro-roles.php:58
+msgid "Done!"
+msgstr ""
+
+#: pmpro-roles.php:59
+msgid "role connections were needed/repaired."
+msgstr ""
+
+#: pmpro-roles.php:60
+msgid "An error occurred while repairing roles."
+msgstr ""
+
+#: pmpro-roles.php:217
+msgid "Paid Memberships Pro - Roles"
+msgstr ""
+
+#: pmpro-roles.php:218
+msgid "Choose which roles should be applied to this level."
+msgstr ""
+
+#: pmpro-roles.php:243
+msgid "Select Roles For This Level"
+msgstr ""
+
+#: pmpro-roles.php:352
+msgid "No"
+msgstr ""
+
+#: pmpro-roles.php:399
+#: pmpro-roles.php:538
+msgid "Delete Roles and Deactivate"
+msgstr ""
+
+#: pmpro-roles.php:413
+#: pmpro-roles.php:552
+msgid "Docs"
+msgstr ""
+
+#: pmpro-roles.php:413
+#: pmpro-roles.php:552
+msgid "View Documentation"
+msgstr ""
+
+#: pmpro-roles.php:414
+#: pmpro-roles.php:553
+msgid "Support"
+msgstr ""
+
+#: pmpro-roles.php:414
+#: pmpro-roles.php:553
+msgid "Visit Customer Support Forum"
+msgstr ""
+
+#: pmpro-roles.php:462
+#: pmpro-roles.php:601
+msgid "Dismiss this notice."
+msgstr ""
+
+#: pmpro-roles.php:462
+#: pmpro-roles.php:601
+msgid "Plugin deactivated"
+msgstr ""
+
+#. Plugin Name of the plugin
+msgid "Paid Memberships Pro - Roles Add On"
+msgstr ""
+
+#. Plugin URI of the plugin
+msgid "https://www.paidmembershipspro.com/add-ons/pmpro-roles/"
+msgstr ""
+
+#. Description of the plugin
+msgid "Adds a WordPress Role for each Membership Level."
+msgstr ""
+
+#. Author of the plugin
+msgid "Paid Memberships Pro"
+msgstr ""
+
+#. Author URI of the plugin
+msgid "https://www.paidmembershipspro.com"
+msgstr ""
+
+#: pmpro-roles.php:299
+msgid "Role Settings"
+msgstr ""
+
+#: pmpro-roles.php:309
+msgid "Choose one or more roles to be assigned for members of this level. Visit the documentation page for more information."
+msgstr ""
+
+#: pmpro-roles.php:331
+msgid "Roles"
+msgstr ""
+
+#: pmpro-roles.php:341
+msgid "Create a new custom role for this membership level"
+msgstr ""
diff --git a/languages/pmpro-roles.pot b/languages/pmpro-roles.pot
new file mode 100644
index 0000000..4036e7f
--- /dev/null
+++ b/languages/pmpro-roles.pot
@@ -0,0 +1,126 @@
+# Copyright (C) 2021 Paid Memberships Pro
+# This file is distributed under the GPL2.
+msgid ""
+msgstr ""
+"Project-Id-Version: Paid Memberships Pro - Roles Add On 1.4\n"
+"Report-Msgid-Bugs-To: info@paidmembershipspro.com\n"
+"Last-Translator: Paid Memberships Pro \n"
+"Language-Team: Paid Memberships Pro \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"POT-Creation-Date: 2021-08-18T14:26:14+00:00\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"X-Generator: WP-CLI 2.5.0\n"
+"X-Domain: pmpro-roles\n"
+
+#: pmpro-roles.php:55
+msgid "Levels not matching up, or missing?"
+msgstr ""
+
+#: pmpro-roles.php:56
+msgid "Repair"
+msgstr ""
+
+#: pmpro-roles.php:57
+msgid "Working..."
+msgstr ""
+
+#: pmpro-roles.php:58
+msgid "Done!"
+msgstr ""
+
+#: pmpro-roles.php:59
+msgid "role connections were needed/repaired."
+msgstr ""
+
+#: pmpro-roles.php:60
+msgid "An error occurred while repairing roles."
+msgstr ""
+
+#: pmpro-roles.php:217
+msgid "Paid Memberships Pro - Roles"
+msgstr ""
+
+#: pmpro-roles.php:218
+msgid "Choose which roles should be applied to this level."
+msgstr ""
+
+#: pmpro-roles.php:243
+msgid "Select Roles For This Level"
+msgstr ""
+
+#: pmpro-roles.php:352
+msgid "No"
+msgstr ""
+
+#: pmpro-roles.php:399
+#: pmpro-roles.php:538
+msgid "Delete Roles and Deactivate"
+msgstr ""
+
+#: pmpro-roles.php:413
+#: pmpro-roles.php:552
+msgid "Docs"
+msgstr ""
+
+#: pmpro-roles.php:413
+#: pmpro-roles.php:552
+msgid "View Documentation"
+msgstr ""
+
+#: pmpro-roles.php:414
+#: pmpro-roles.php:553
+msgid "Support"
+msgstr ""
+
+#: pmpro-roles.php:414
+#: pmpro-roles.php:553
+msgid "Visit Customer Support Forum"
+msgstr ""
+
+#: pmpro-roles.php:462
+#: pmpro-roles.php:601
+msgid "Dismiss this notice."
+msgstr ""
+
+#: pmpro-roles.php:462
+#: pmpro-roles.php:601
+msgid "Plugin deactivated"
+msgstr ""
+
+#. Plugin Name of the plugin
+msgid "Paid Memberships Pro - Roles Add On"
+msgstr ""
+
+#. Plugin URI of the plugin
+msgid "https://www.paidmembershipspro.com/add-ons/pmpro-roles/"
+msgstr ""
+
+#. Description of the plugin
+msgid "Adds a WordPress Role for each Membership Level."
+msgstr ""
+
+#. Author of the plugin
+msgid "Paid Memberships Pro"
+msgstr ""
+
+#. Author URI of the plugin
+msgid "https://www.paidmembershipspro.com"
+msgstr ""
+
+#: pmpro-roles.php:299
+msgid "Role Settings"
+msgstr ""
+
+#: pmpro-roles.php:309
+msgid "Choose one or more roles to be assigned for members of this level. Visit the documentation page for more information."
+msgstr ""
+
+#: pmpro-roles.php:331
+msgid "Roles"
+msgstr ""
+
+#: pmpro-roles.php:341
+msgid "Create a new custom role for this membership level"
+msgstr ""
diff --git a/pmpro-roles.php b/pmpro-roles.php
index f986d17..ca09240 100755
--- a/pmpro-roles.php
+++ b/pmpro-roles.php
@@ -1,32 +1,18 @@
pmpro_hooks();
+ $this->wp_hooks();
+ }
+
+ /**
+ * Hooks that run only when PMPro is active.
+ *
+ * @since 1.4.2
+ */
+ function pmpro_hooks() {
+ add_action( 'pmpro_save_membership_level', array( $this, 'edit_level' ) );
+ add_action( 'pmpro_delete_membership_level', array( $this, 'delete_level' ) );
+ if ( defined( 'PMPRO_VERSION' ) && version_compare( '2.5.8', PMPRO_VERSION, '>' ) ) {
+ // Use legacy functionality to update roles on level change.
+ add_action( 'pmpro_after_change_membership_level', array($this, 'user_change_level' ), 10, 2 );
+ } else {
+ add_action( 'pmpro_after_all_membership_level_changes', array( $this, 'after_all_level_changes' ), 10, 1 );
+ }
+
+ add_action( 'pmpro_membership_level_after_other_settings', array( 'PMPRO_Roles', 'level_settings' ) );
+
+ add_action( 'pmpro_manage_memberslist_columns', array( $this, 'add_role_column_to_members_list' ) );
+ add_action( 'pmpro_manage_memberslist_custom_column', array( $this, 'add_role_column_content_to_members_list' ), 10, 2 );
+ }
+
+ /**
+ * Hooks that may need to run without PMPro activated.
+ *
+ * @return void
+ */
+ function wp_hooks() {
add_action( 'admin_enqueue_scripts', array($this, 'enqueue_admin_scripts' ) );
- add_action('wp_ajax_'.PMPRO_Roles::$ajaction, array( $this, 'install' ) );
+ add_action( 'init', array( $this, 'load_text_domain' ) );
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( 'PMPRO_Roles', 'add_action_links' ) );
+ add_action( 'wp_ajax_' . PMPRO_Roles::$ajaction, array( $this, 'install' ) );
+ add_filter( 'plugin_row_meta', array( 'PMPRO_Roles', 'plugin_row_meta' ), 10, 2 );
+ add_filter( 'editable_roles', array( 'PMPRO_Roles', 'remove_list_roles' ), 10, 1 );
+ add_action( 'admin_init', array( 'PMPRO_Roles', 'delete_and_deactivate' ) );
+ }
+
+ /**
+ * Load plugin text domain for translations.
+ * @since 1.3
+ */
+ function load_text_domain() {
+ load_plugin_textdomain( 'pmpro-roles', false, basename( dirname( __FILE__ ) ) . '/languages' );
}
+ /**
+ * Javascript for admin area.
+ */
function enqueue_admin_scripts($hook) {
- if( 'toplevel_page_pmpro-membershiplevels' != $hook )
- return;
- wp_enqueue_script( PMPRO_Roles::$plugin_prefix.'admin', plugin_dir_url( __FILE__ ) . '/admin.js' );
- wp_enqueue_style( PMPRO_Roles::$plugin_prefix.'admin', plugin_dir_url( __FILE__ ) . '/admin.css' );
+ if ( 'toplevel_page_pmpro-membershiplevels' != $hook ) {
+ return;
+ }
+
+ wp_enqueue_script( PMPRO_Roles::$plugin_prefix.'admin', plugin_dir_url( __FILE__ ) . '/admin.js', array( 'jquery' ), PMPRO_ROLES_VERSION );
+ wp_enqueue_style( PMPRO_Roles::$plugin_prefix.'admin', plugin_dir_url( __FILE__ ) . '/admin.css', array(), PMPRO_ROLES_VERSION );
$nonce = wp_create_nonce( PMPRO_Roles::$ajaction );
$vars = array(
- 'desc' => __('Levels not matching up, or missing?', PMPRO_Roles::$plugin_slug),
- 'repair' => __('Repair', PMPRO_Roles::$plugin_slug),
- 'working' => __('Working...', PMPRO_Roles::$plugin_slug),
- 'done' => __('Done!', PMPRO_Roles::$plugin_slug),
- 'fixed'=> __(' role connections were needed/repaired.', PMPRO_Roles::$plugin_slug),
- 'failed'=> __('An error occurred while repairing roles.', PMPRO_Roles::$plugin_slug),
- 'ajaction'=>PMPRO_Roles::$ajaction,
- 'nonce'=>$nonce,
+ 'desc' => esc_html__( 'Levels not matching up, or missing?', 'pmpro-roles' ),
+ 'repair' => esc_html__( 'Repair', 'pmpro-roles' ),
+ 'working' => esc_html__(' Working...', 'pmpro-roles' ),
+ 'done' => esc_html__( 'Done!', 'pmpro-roles' ),
+ 'fixed' => esc_html__( 'role connections were needed/repaired.', 'pmpro-roles' ),
+ 'failed' => esc_html__( 'An error occurred while repairing roles.', 'pmpro-roles' ),
+ 'ajaction' => PMPRO_Roles::$ajaction,
+ 'nonce' => $nonce,
);
$key = PMPRO_Roles::$plugin_prefix.'vars';
wp_localize_script( PMPRO_Roles::$plugin_prefix . 'admin', 'key', array( 'key'=>$key ) );
wp_localize_script( PMPRO_Roles::$plugin_prefix . 'admin', $key, $vars );
}
+ /**
+ * Get an array of roles set for a given level.
+ * @since 1.4.1
+ * @param int $level_id The ID of the level to check for.
+ */
+ public static function get_roles_for_level( $level_id ) {
+ // In case a level object is passed in.
+ if ( is_object( $level_id ) && ! empty( $level_id->id ) ) {
+ $level_id = $level_id->id;
+ }
+
+ // Fail if no level.
+ if ( empty( $level_id ) ) {
+ return array();
+ }
+
+ $roles = get_option( self::$plugin_prefix . $level_id );
+
+ // Default to the site role, if no role is found.
+ if ( empty( $roles ) ) {
+ $roles = array();
+ $default_role = get_option( 'default_role' );
+ $roles[ $default_role ] = ucfirst( $default_role );
+
+ }
+
+ return $roles;
+ }
+
+ /**
+ * Settings for the edit level admin screen. Creates and saves role selection per level.
+ * SECURITY: Nonce checks are run in paid-memberships-pro/adminpages/membershiplevels.php which once passed and OK, run the pmpro_save_membership_level hook.
+ * @since 1.3
+ */
function edit_level( $saveid ) {
+ global $wpdb;
+
//by being here, we know we already have the $_REQUEST we need, so no need to check.
- $role_key = PMPRO_Roles::$role_key . $saveid;
- //created a new level
- if( $_REQUEST['edit'] < 0 ) {
- add_role( $role_key, $_REQUEST['name'] );
- }
- //edited a level
- else {
- global $wpdb;
- //have to get all roles and find ours because get_role() doesn't yield the role's "pretty" name, only its index.
- $roles = get_option( $wpdb->get_blog_prefix() . 'user_roles' );
- //can't get the roles, die
- if(!is_array( $roles ) ) return;
- //the role doesn't exist - create it, then we are done.
- if(!isset( $roles[$role_key] ) ){
- add_role( $role_key, $_REQUEST['name'] );
- return;
+ $capabilities = self::capabilities( self::$role_key . $saveid ) ?: array( 'read' => true );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! empty( $_REQUEST['pmpro_roles_level_present'] ) ) {
+
+ if ( ! empty( $_REQUEST['pmpro_roles_level'] ) ) {
+ $level_roles = $_REQUEST['pmpro_roles_level'];
+ } else {
+ // If no role chosen, use the default.
+ $level_roles = array();
+ $default_role = get_option( 'default_role' );
+ $level_roles[ $default_role ] = ucfirst( $default_role );
}
- $role = $roles[$role_key];
- $role_name = $role['name'];
- //we only need to update if the role's name has changed.
- if( $role_name !== $_REQUEST['name'] ) {
- //delete the role (because update_role() doesn't exist...)
- remove_role( $role_key );
- //then recreate it
- add_role( $role_key, $_REQUEST['name'] );
+
+ // We detect that the draft_role has been selected, so lets try to create it. (This can now happen whenever the role doesn't exist not just on new level creation)
+ if ( ! empty( $level_roles['pmpro_draft_role'] ) ) {
+ unset( $level_roles['pmpro_draft_role'] ); // Remove it from the array.
+ $role_name = sanitize_text_field( $_REQUEST['name'] );
+ add_role( PMPRO_Roles::$role_key.$saveid, $role_name, $capabilities );
+ $level_roles[PMPRO_Roles::$role_key.$saveid] = $role_name; // Got to add the newly created role to the level_roles array.
+ remove_role( 'pmpro_draft_role' ); // Delete the role entirely in case it exists, we no longer need it at this point forward.
}
+
+ if ( isset( $_REQUEST['edit'] ) && $_REQUEST['edit'] < 0 ) {
+ foreach( $level_roles as $role_key => $role_name ){
+ if ( $role_key === 'pmpro_role_'. $saveid ) {
+ $capabilities = PMPRO_Roles::capabilities( $role_key );
+ add_role( $role_key, $role_name, $capabilities );
+ }
+ }
+ } else {
+ //have to get all roles and find ours because get_role() doesn't yield the role's "pretty" name, only its index.
+ $roles = get_option( $wpdb->get_blog_prefix() . 'user_roles' );
+
+ if(!is_array( $roles ) ) return;
+
+ foreach( $level_roles as $role_key => $role_name ){
+
+ if( !isset( $roles[$role_key] ) ){
+ $capabilities = PMPRO_Roles::capabilities( $role_key );
+ add_role( $role_key, sanitize_text_field( $_REQUEST['name'] ), $capabilities[$role_key] );
+ return;
+ }
+
+ if( ( strpos( $role_key, PMPRO_Roles::$role_key ) !== FALSE ) && sanitize_text_field( $_REQUEST['name'] ) !== $role_name ) {
+ PMPRO_Roles::update_role_name( $role_key, sanitize_text_field( $_REQUEST['name'] ) );
+ }
+
+ }
+ }
+
+ update_option( 'pmpro_roles_'.$saveid, $level_roles );
+
}
+
}
- function delete_level() {
- //if there is no action, or if there is an action but it isn't deleting, return.
- if( !isset( $_REQUEST['action'] ) || ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] !== 'delete_membership_level' ) ) { return; }
- $ml_id = $_REQUEST['deleteid'];
- if($ml_id > 0){
- $role_key = PMPRO_Roles::$role_key . $ml_id;
- //this will silently fail if the role doesn't exist, so we're fine.
+ /**
+ * Delete custom PMPro role on level delete.
+ * @since 1.0
+ */
+ function delete_level( $delete_id ) {
+ $role_key = PMPRO_Roles::$role_key . $delete_id;
+ if( !empty( $role_key ) ){
remove_role( $role_key );
}
+
+ delete_option( 'pmpro_roles_' . $delete_id );
+
+ do_action( 'pmpro_roles_delete_membership_level', $delete_id );
}
+
+ /**
+ * Update a PMPro Roles Name if level name changes.
+ * @since 1.3
+ */
+ function update_role_name( $role, $name ){
+ if( strpos( $role, PMPRO_Roles::$role_key ) !== FALSE ) {
+ $roles_array = get_option( 'wp_user_roles', true );
+ if( !empty( $roles_array[$role] ) ){
+ $roles_array[$role]['name'] = sanitize_text_field( $name );
+ $updated = update_option( 'wp_user_roles', $roles_array );
+ }
+ }
+ }
+
+ /**
+ * Update users' roles after their membership levels are changed.
+ *
+ * @param array $old_user_levels $user_id => array $old_levels
+ */
+ function after_all_level_changes( $old_user_levels ) {
+ foreach ( $old_user_levels as $user_id => $old_levels ) {
+ // Get the user who changed levels.
+ $user = new WP_User( $user_id );
+
+ // Ignore administrators.
+ if ( in_array( 'administrator', $user->roles ) ) {
+ continue;
+ }
+
+ // Get the user's current active membership levels.
+ $new_levels = pmpro_getMembershipLevelsForUser( $user_id );
+
+ $new_roles = array();
+ $old_roles = array();
+
+ // Add default role to $old_roles if it is already one of the user roles.
+ $default_role = apply_filters( 'pmpro_roles_downgraded_role', get_option( 'default_role' ) );
+ if ( in_array( $default_role, $user->roles ) ) {
+ $old_roles = array( $default_role );
+ }
+
+ // Build an array of all roles assigned to the user's old membership levels.
+ foreach ( $old_levels as $old_level ) {
+ $old_level_roles = self::get_roles_for_level( $old_level->id );
+ if ( ! empty( $old_level_roles ) && is_array( $old_level_roles ) ) {
+ $old_roles = array_merge( $old_roles, array_keys( $old_level_roles ) );
+ }
+ }
+
+ // Build an array of all roles assigned to the user's new membership levels.
+ foreach ( $new_levels as $new_level ) {
+ $new_level_roles = self::get_roles_for_level( $new_level->id );
+ if ( ! empty( $new_level_roles ) && is_array( $new_level_roles ) ) {
+ $new_roles = array_merge( $new_roles, array_keys( $new_level_roles ) );
+ }
+ }
+
+ // Remove duplicates in the array of old and new roles.
+ $old_roles = array_unique( $old_roles );
+ $new_roles = array_unique( $new_roles );
+
+ // Build a unique array of roles to add and remove from user.
+ $add_roles = $new_roles;
+ $remove_roles = array_values( array_diff( $old_roles, $new_roles ) );
+
+ // Add roles to user.
+ foreach ( $add_roles as $add_role ) {
+ $user->add_role( $add_role );
+ }
+ // Remove roles from user.
+ foreach ( $remove_roles as $remove_role ) {
+ $user->remove_role( $remove_role );
+ }
+
+ // Handle case where user has no role.
+ if ( empty( $user->roles ) ) {
+ $user->add_role( $default_role );
+ }
+
+ // Allows user's to be inside the foreach loop and hook in to do things.
+ do_action( 'pmpro_roles_after_role_change', $user, $old_user_levels, $old_roles, $new_roles );
+
+ }
+ }
+
+ /**
+ * Change user role based on level change. (Now supports MMPU)
+ * No longer being used if PMPro version >= 2.5.8.
+ *
+ * @since 1.0
+ */
function user_change_level($level_id, $user_id){
+
+ global $pmpro_checkout_levels;
//get user object
$wp_user_object = new WP_User($user_id);
//ignore admins
if( in_array( 'administrator', $wp_user_object->roles ) )
return;
- //downgrade time!
- if( $level_id == 0 ) {
- $wp_user_object->set_role('subscriber');
+
+ // Check if user is cancelling.
+ if( defined( 'PMPROMMPU_DIR' ) && !empty( $_REQUEST['levelstocancel'] ) ) { //Adds support for MMPU
+ $levels_to_cancel = explode( " ", $_REQUEST['levelstocancel'] );
+ if( !empty( $levels_to_cancel ) ){
+ foreach( $levels_to_cancel as $ltc ){
+ $wp_user_object->remove_role( PMPRO_Roles::$role_key.intval( $ltc ) );
+ }
+ }
+
+ } else if( $level_id == 0 ) {
+ $default_role = apply_filters( 'pmpro_roles_downgraded_role', get_option( 'default_role' ) );
+ $wp_user_object->set_role( $default_role );
+ } else {
+ if( !empty( $pmpro_checkout_levels ) ){
+ //Adds support for MMPU
+ foreach( $pmpro_checkout_levels as $co_level ){
+ $roles = self::get_roles_for_level( $co_level->id );
+ if( is_array( $roles ) && ! empty( $roles ) ){
+ foreach( $roles as $role_key => $role_name ){
+ $wp_user_object->add_role( $role_key );
+ }
+ } else {
+ $wp_user_object->set_role( PMPRO_Roles::$role_key . $co_level->id );
+ }
+ }
+ } else if( $level_id > 0 ){
+ $roles = self::get_roles_for_level( $level_id );
+ if( is_array( $roles ) && ! empty( $roles ) ){
+ $count = 1;
+ foreach( $roles as $role_key => $role_name ){
+ if( $count == 1 ){
+ $wp_user_object->set_role( $role_key );
+ } else {
+ $wp_user_object->add_role( $role_key );
+ }
+ $count++;
+ }
+ } else {
+ $wp_user_object->set_role( PMPRO_Roles::$role_key.intval( $level_id ) );
+ }
+ }
+ }
+ }
+
+ /**
+ * Show a list of all available roles as a checkbox inside level settings.
+ * @since 1.3
+ */
+ public static function level_settings() {
+ ?>
+
+
+
+
+ array (
+ 'href' => array(),
+ 'target' => array(),
+ 'title' => array(),
+ ),
+ );
+
+ // translators: %s is a link to the download page for the Roles Add On.
+ echo sprintf( wp_kses( __( 'Choose one or more roles to be assigned for members of this level. Visit the documentation page for more information.', 'pmpro-roles' ), $allowed_pmpro_roles_description_html ), 'https://www.paidmembershipspro.com/add-ons/pmpro-roles/?utm_source=plugin&utm_medium=pmpro-membershiplevels&utm_campaign=add-ons&utm_content=pmpro-roles' );
+ echo '
' . esc_html__( 'If you do not select a custom role for users of this membership level, the user will be assigned the "New User Default Role" as defined under Settings > General in the WordPress admin', 'pmpro-roles' ) . '
';
+ ?>
+
+
+ $level ){
+ if( $level_key !== $edit_level ){
+ if( isset( $roles[PMPRO_Roles::$role_key.$level_key] ) ){
+ unset( $roles[PMPRO_Roles::$role_key.$level_key] );
+ }
+ }
+ }
+
}
- //set the role to our key
- else {
- $wp_user_object->set_role( PMPRO_Roles::$role_key . $level_id );
+
+ return $roles;
+
+ }
+
+ /**
+ * Add a "Role" column to the Members List table.
+ * @since TBD
+ * @param array $columns The columns in the members list table.
+ * @return array The columns in the members list table, with the "Role" column added.
+ */
+ public function add_role_column_to_members_list( $columns ) {
+ $columns['role'] = __( 'Role', 'pmpro-roles' );
+ return $columns;
+ }
+
+ /**
+ * Add content to the "Role" column in the Members List table.
+ * @since 1.4
+ * @param string $column_name The name of the column.
+ * @param int $user_id The ID of the user.
+ * @return void
+ */
+ public function add_role_column_content_to_members_list( $column_name, $user_id ) {
+ global $wp_roles;
+ if ( $column_name === 'role' && ! empty( $wp_roles->roles ) ) {
+ $user = get_userdata( $user_id );
+ $roles = $user->roles;
+
+ // Get the display name for each role
+ $role_names = array_map( function ( $role ) use ( $wp_roles ) {
+ return isset( $wp_roles->roles[$role]['name'] ) ? translate_user_role( $wp_roles->roles[$role]['name'] ) : ucfirst( $role );
+ }, $roles );
+
+ echo esc_html( implode( ', ', $role_names ) );
}
}
-
- //activation function
+
+ /**
+ * Initial function to run on install. Create roles for each existing level.
+ * @since 1.0
+ */
public static function install() {
-
- global $wpdb;
- if( defined( 'DOING_AJAX' ) && DOING_AJAX ){
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ){
check_ajax_referer( PMPRO_Roles::$ajaction );
}
- $levels = $wpdb->get_results( "SELECT * FROM $wpdb->pmpro_membership_levels" );
-
+ // Only run this code if PMPro is active.
+ if ( function_exists( 'pmpro_getAllLevels' ) ) {
+ $levels = pmpro_getAllLevels( true, false );
+ } else {
+ $levels = false;
+ }
+
if( !$levels ) {
if( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
die( 'failed' );
@@ -139,25 +582,151 @@ public static function install() {
return;
}
}
+
+ $capabilities = PMPRO_Roles::capabilities();
+
$i = 0;
foreach ( $levels as $level ) {
$role_key = PMPRO_Roles::$role_key . $level->id;
//the role doesn't exist for this level
if( !get_role( $role_key ) ) {
$i++;
- add_role( $role_key, $level->name );
+ add_role( $role_key, $level->name, $capabilities[$level->id] );
}
}
if( defined( 'DOING_AJAX' ) && DOING_AJAX ){
if($i > 0){
- echo $i;
+ echo (int) $i;
}
else{
- echo __('No', PMPRO_Roles::$plugin_slug);
+ echo esc_html__('No', 'pmpro-roles');
}
die();
}
}
+
+ /**
+ * Assign capabilities to custom roles.
+ * @since 1.0
+ */
+ public static function capabilities( $role_key = null ) {
+ $all_levels = pmpro_getAllLevels( true, false );
+ $capabilities = array();
+
+ if( !empty( $role_key ) ){
+ if( strpos( $role_key, PMPRO_Roles::$role_key ) !== false){
+ $capabilities[$role_key] = array( 'read' => true );
+ } else {
+ $caps = array();
+ //Get the caps of this role
+ $role_caps = get_role( $role_key )->capabilities;
+ if( !empty( $role_caps ) ){
+ foreach( $role_caps as $cap ){
+ $caps[$cap] = true;
+ }
+ }
+ $capabilities[$role_key] = $caps;
+ }
+ } else {
+ foreach ( $all_levels as $key => $value ) {
+ $capabilities[$key] = array( 'read' => true );
+ }
+ }
+
+ $capabilities = apply_filters( 'pmpro_roles_default_caps', $capabilities );
+
+ return $capabilities;
+ }
+
+ /**
+ * Add "Delete Roles and Deactivate" link to plugins page
+ * @since 1.0
+ */
+ public static function add_action_links($links) {
+ // Only add this if plugin is active.
+ if( is_plugin_active( 'pmpro-roles/pmpro-roles.php' ) ) {
+ $new_links = array(
+ '' . esc_html__( 'Delete Roles and Deactivate', 'pmpro-roles' ) . '',
+ );
+ return array_merge($new_links, $links);
+ }
+
+ return $links;
+ }
+
+ /**
+ * Add links to the plugin row meta
+ */
+ public static function plugin_row_meta( $links, $file ) {
+ if ( strpos( $file, 'pmpro-roles' ) !== false ) {
+ $new_links = array(
+ '' . esc_html__( 'Docs', 'pmpro-roles' ) . '',
+ '' . esc_html__( 'Support', 'pmpro-roles' ) . '',
+ );
+ $links = array_merge( $links, $new_links );
+ }
+ return $links;
+ }
+
+ /**
+ * Process delete and deactivate if clicked.
+ */
+ public static function delete_and_deactivate() {
+ //see if our param was passed
+ if(empty($_REQUEST['pmpro_roles_delete_and_deactivate']))
+ return;
+
+ //check nonce
+ check_admin_referer('pmpro_roles_delete_and_deactivate');
+
+ //find roles based on levels
+ global $wpdb;
+ $roles = get_option( $wpdb->get_blog_prefix() . 'user_roles' );
+
+ foreach($roles as $key => $role) {
+ //is this a pmpro role?
+ if(strpos($key, PMPRO_Roles::$role_key) !== FALSE ) {
+ //change all users with those roles to have the default role
+ $users = get_users( array( 'role' => $key ) );
+
+ foreach($users as $user) {
+ if ( count( $user->roles ) > 1 ){
+ $user->remove_role( $key );
+ } else {
+ $default_role = apply_filters( 'pmpro_roles_downgraded_role', get_option( 'default_role' ) );
+ $user->set_role( $default_role );
+ }
+ }
+
+ //delete the roles
+ remove_role($key);
+ }
+ }
+
+ // Remove the pmpro_draft_role if it exists.
+ remove_role( 'pmpro_draft_role' );
+
+ //deactivate the plugin
+ deactivate_plugins( plugin_basename( __FILE__ ) );
+
+ //output deactivated notice:
+ ?>
+
+