From ac0596f1d7cfb44eb8f238035b1796409d0a33d5 Mon Sep 17 00:00:00 2001 From: Paul Ryley <134939+pryley@users.noreply.github.com> Date: Sat, 19 Oct 2024 03:14:29 -0500 Subject: [PATCH] add Ultimate Member integration --- config/integrations/ultimatemember.php | 99 +++++++++++++ languages/site-reviews-en_US.mo | Bin 144252 -> 144751 bytes languages/site-reviews-en_US.po | 51 +++++-- languages/site-reviews.pot | 47 ++++-- plugin/Database/CountManager.php | 46 ++++++ .../UltimateMember/Controller.php | 36 ----- .../UltimateMember/Controllers/Controller.php | 137 ++++++++++++++++++ .../Controllers/DirectoryController.php | 136 +++++++++++++++++ .../Controllers/ProfileController.php | 137 ++++++++++++++++++ plugin/Integrations/UltimateMember/Hooks.php | 46 +++++- plugin/Modules/Html/Tags/SummaryTag.php | 3 +- templates/ultimatemember/reviews.php | 11 ++ tests/phpstan/stubs/ultimate-member.php | 129 +++++++++++++++++ .../integrations/ultimatemember/settings.php | 9 ++ 14 files changed, 819 insertions(+), 68 deletions(-) create mode 100644 config/integrations/ultimatemember.php delete mode 100644 plugin/Integrations/UltimateMember/Controller.php create mode 100644 plugin/Integrations/UltimateMember/Controllers/Controller.php create mode 100644 plugin/Integrations/UltimateMember/Controllers/DirectoryController.php create mode 100644 plugin/Integrations/UltimateMember/Controllers/ProfileController.php create mode 100644 templates/ultimatemember/reviews.php create mode 100644 views/integrations/ultimatemember/settings.php diff --git a/config/integrations/ultimatemember.php b/config/integrations/ultimatemember.php new file mode 100644 index 000000000..434446afe --- /dev/null +++ b/config/integrations/ultimatemember.php @@ -0,0 +1,99 @@ + [ + 'default' => 'no', + 'label' => _x('Enable Integration?', 'admin-text', 'site-reviews'), + 'sanitizer' => 'text', + 'tooltip' => sprintf(_x('This will enable the Ultimate Member integration with Site Reviews.', 'admin-text', 'site-reviews'), + sprintf('%s', glsr_admin_url('tools', 'general'), _x('Import Reviews', 'admin-text', 'site-reviews')) + ), + 'type' => 'yes_no', + ], + 'settings.integrations.ultimatemember.display_directory_ratings' => [ + 'default' => 'no', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + ], + 'label' => _x('Display Directory Ratings?', 'admin-text', 'site-reviews'), + 'sanitizer' => 'text', + 'tooltip' => _x('This will display the rating of each person in the Member Directory.', 'admin-text', 'site-reviews'), + 'type' => 'yes_no', + ], + 'settings.integrations.ultimatemember.display_empty' => [ + 'default' => 'no', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + 'settings.integrations.ultimatemember.display_directory_ratings' => ['yes'], + ], + 'label' => _x('Display Empty Ratings?', 'admin-text', 'site-reviews'), + 'sanitizer' => 'text', + 'tooltip' => _x('This will display the rating stars even if the member has no reviews.', 'admin-text', 'site-reviews'), + 'type' => 'yes_no', + ], + 'settings.integrations.ultimatemember.sorting' => [ + 'class' => 'regular-text', + 'default' => '', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + 'settings.integrations.ultimatemember.display_directory_ratings' => ['yes'], + ], + 'label' => _x('Member Sorting', 'admin-text', 'site-reviews'), + 'options' => [ + '' => _x('Average Rating', 'admin-text', 'site-reviews'), + 'bayesian' => _x('Bayesian Ranking', 'admin-text', 'site-reviews'), + ], + 'sanitizer' => 'text', + 'tooltip' => _x('This is the method used when sorting members by rating on the Members Directory page.', 'admin-text', 'site-reviews'), + 'type' => 'select', + ], + 'settings.integrations.ultimatemember.display_reviews_tab' => [ + 'default' => 'no', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + ], + 'label' => _x('Display Reviews Tab?', 'admin-text', 'site-reviews'), + 'sanitizer' => 'text', + 'tooltip' => _x('This will display the reviews tab in member profiles.', 'admin-text', 'site-reviews'), + 'type' => 'yes_no', + ], + 'settings.integrations.ultimatemember.summary' => [ + 'class' => 'large-text', + 'default' => '[site_reviews_summary assigned_users="profile_id"]', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + 'settings.integrations.ultimatemember.display_reviews_tab' => ['yes'], + ], + 'label' => _x('Summary Shortcode', 'admin-text', 'site-reviews'), + 'placeholder' => '[site_reviews_summary assigned_users="profile_id"]', + 'sanitizer' => 'text', + 'tooltip' => _x('Enter the rating summary shortcode used on the profile page', 'admin-text', 'site-reviews'), + 'type' => 'text', + ], + 'settings.integrations.ultimatemember.reviews' => [ + 'class' => 'large-text', + 'default' => '[site_reviews assigned_users="profile_id" hide="assigned_links" pagination="loadmore"]', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + 'settings.integrations.ultimatemember.display_reviews_tab' => ['yes'], + ], + 'label' => _x('Reviews Shortcode', 'admin-text', 'site-reviews'), + 'placeholder' => '[site_reviews assigned_users="profile_id" hide="assigned_links" pagination="loadmore"]', + 'sanitizer' => 'text', + 'tooltip' => _x('Enter the latest reviews shortcode used on the profile page', 'admin-text', 'site-reviews'), + 'type' => 'text', + ], + 'settings.integrations.ultimatemember.form' => [ + 'class' => 'large-text', + 'default' => '[site_reviews_form assigned_users="profile_id"]', + 'depends_on' => [ + 'settings.integrations.ultimatemember.enabled' => ['yes'], + 'settings.integrations.ultimatemember.display_reviews_tab' => ['yes'], + ], + 'label' => _x('Form Shortcode', 'admin-text', 'site-reviews'), + 'placeholder' => '[site_reviews_form assigned_users="profile_id"]', + 'sanitizer' => 'text', + 'tooltip' => _x('Enter the form shortcode used on the profile page', 'admin-text', 'site-reviews'), + 'type' => 'text', + ], +]; diff --git a/languages/site-reviews-en_US.mo b/languages/site-reviews-en_US.mo index d7a1c0acf28f19f57844d96925c1a44d71362cfc..c89eeda787653756fd434510d5aa91b045674901 100644 GIT binary patch delta 27072 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 delta 26751 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 T+0MhSJY7O}ev{hoYo`ALakfYC diff --git a/languages/site-reviews-en_US.po b/languages/site-reviews-en_US.po index 50ccd5a49..07c469812 100644 --- a/languages/site-reviews-en_US.po +++ b/languages/site-reviews-en_US.po @@ -2085,11 +2085,21 @@ msgctxt "admin-text" msgid "Terms Accepted" msgstr "Terms Accepted" -#: config/integrations/woocommerce.php:6 +#: config/integrations/ultimatemember.php:6, config/integrations/woocommerce.php:6 msgctxt "admin-text" msgid "Enable Integration?" msgstr "Enable Integration?" +#: config/integrations/ultimatemember.php:8 +msgctxt "admin-text" +msgid "This will enable the Ultimate Member integration with Site Reviews." +msgstr "This will enable the Ultimate Member integration with Site Reviews." + +#: config/integrations/ultimatemember.php:9, config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113 +msgctxt "admin-text" +msgid "Import Reviews" +msgstr "Import Reviews" + #: config/integrations/woocommerce.php:8 msgctxt "admin-text" msgid "" @@ -2103,11 +2113,6 @@ msgstr "" "need to first export them to a CSV file, and then import them using the %s " "tool." -#: config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113 -msgctxt "admin-text" -msgid "Import Reviews" -msgstr "Import Reviews" - #: config/integrations/woocommerce.php:19 msgctxt "admin-text" msgid "Rating Style" @@ -2982,7 +2987,7 @@ msgctxt "admin-text" msgid "Tools" msgstr "Tools" -#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:139 +#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:140 msgctxt "admin-text" msgid "Addons" msgstr "Addons" @@ -3017,7 +3022,7 @@ msgctxt "admin-text" msgid "API" msgstr "API" -#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:160 +#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:161 msgctxt "admin-text" msgid "General" msgstr "General" @@ -3037,27 +3042,32 @@ msgctxt "admin-text" msgid "Schema" msgstr "Schema" -#: plugin/Controllers/MenuController.php:140 +#: plugin/Controllers/MenuController.php:139 +msgctxt "admin-text" +msgid "Integrations" +msgstr "Integrations" + +#: plugin/Controllers/MenuController.php:141 msgctxt "admin-text" msgid "Licenses" msgstr "Licenses" -#: plugin/Controllers/MenuController.php:161 +#: plugin/Controllers/MenuController.php:162 msgctxt "admin-text" msgid "Scheduled Actions" msgstr "Scheduled Actions" -#: plugin/Controllers/MenuController.php:162, views/pages/tools/sync.php:58 +#: plugin/Controllers/MenuController.php:163, views/pages/tools/sync.php:58 msgctxt "admin-text" msgid "Sync Reviews" msgstr "Sync Reviews" -#: plugin/Controllers/MenuController.php:163 +#: plugin/Controllers/MenuController.php:164 msgctxt "admin-text" msgid "Console" msgstr "Console" -#: plugin/Controllers/MenuController.php:164 +#: plugin/Controllers/MenuController.php:165 msgctxt "admin-text" msgid "System Info" msgstr "System Info" @@ -4643,6 +4653,21 @@ msgctxt "admin-text" msgid "Unapproved Review (Site Reviews)" msgstr "Unapproved Review (Site Reviews)" +#: plugin/Integrations/UltimateMember/DirectoryController.php:38, plugin/Integrations/UltimateMember/ProfileController.php:38 +msgctxt "admin-text" +msgid "Site Reviews: Display User Rating" +msgstr "Site Reviews: Display User Rating" + +#: plugin/Integrations/UltimateMember/DirectoryController.php:49, plugin/Integrations/UltimateMember/ProfileController.php:49 +msgctxt "admin-text" +msgid "Site Reviews: Highest rated first" +msgstr "Site Reviews: Highest rated first" + +#: plugin/Integrations/UltimateMember/DirectoryController.php:50, plugin/Integrations/UltimateMember/ProfileController.php:50 +msgctxt "admin-text" +msgid "Site Reviews: Lowest rated first" +msgstr "Site Reviews: Lowest rated first" + #: plugin/Modules/Html/TemplateTags.php:144 msgctxt "admin-text" msgid "View the review in WordPress →" diff --git a/languages/site-reviews.pot b/languages/site-reviews.pot index 79ead1507..f6387dcc8 100644 --- a/languages/site-reviews.pot +++ b/languages/site-reviews.pot @@ -1567,21 +1567,26 @@ msgstr "" msgid "This review is based on my own experience and is my genuine opinion." msgstr "" -#: config/integrations/woocommerce.php:6 +#: config/integrations/ultimatemember.php:6, config/integrations/woocommerce.php:6 msgctxt "admin-text" msgid "Enable Integration?" msgstr "" -#: config/integrations/woocommerce.php:8 +#: config/integrations/ultimatemember.php:8 msgctxt "admin-text" -msgid "This will completely replace the default WooCommerce review system with Site Reviews. If you have existing WooCommerce comment reviews, you may need to first export them to a CSV file, and then import them using the %s tool." +msgid "This will enable the Ultimate Member integration with Site Reviews." msgstr "" -#: config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113 +#: config/integrations/ultimatemember.php:9, config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113 msgctxt "admin-text" msgid "Import Reviews" msgstr "" +#: config/integrations/woocommerce.php:8 +msgctxt "admin-text" +msgid "This will completely replace the default WooCommerce review system with Site Reviews. If you have existing WooCommerce comment reviews, you may need to first export them to a CSV file, and then import them using the %s tool." +msgstr "" + #: config/integrations/woocommerce.php:19 msgctxt "admin-text" msgid "Rating Style" @@ -2428,7 +2433,7 @@ msgctxt "admin-text" msgid "Tools" msgstr "" -#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:139 +#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:140 msgctxt "admin-text" msgid "Addons" msgstr "" @@ -2463,7 +2468,7 @@ msgctxt "admin-text" msgid "API" msgstr "" -#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:160 +#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:161 msgctxt "admin-text" msgid "General" msgstr "" @@ -2483,27 +2488,32 @@ msgctxt "admin-text" msgid "Schema" msgstr "" -#: plugin/Controllers/MenuController.php:140 +#: plugin/Controllers/MenuController.php:139 +msgctxt "admin-text" +msgid "Integrations" +msgstr "" + +#: plugin/Controllers/MenuController.php:141 msgctxt "admin-text" msgid "Licenses" msgstr "" -#: plugin/Controllers/MenuController.php:161 +#: plugin/Controllers/MenuController.php:162 msgctxt "admin-text" msgid "Scheduled Actions" msgstr "" -#: plugin/Controllers/MenuController.php:162, views/pages/tools/sync.php:58 +#: plugin/Controllers/MenuController.php:163, views/pages/tools/sync.php:58 msgctxt "admin-text" msgid "Sync Reviews" msgstr "" -#: plugin/Controllers/MenuController.php:163 +#: plugin/Controllers/MenuController.php:164 msgctxt "admin-text" msgid "Console" msgstr "" -#: plugin/Controllers/MenuController.php:164 +#: plugin/Controllers/MenuController.php:165 msgctxt "admin-text" msgid "System Info" msgstr "" @@ -4738,6 +4748,21 @@ msgctxt "admin-text" msgid "Unapproved Review (Site Reviews)" msgstr "" +#: plugin/Integrations/UltimateMember/DirectoryController.php:38, plugin/Integrations/UltimateMember/ProfileController.php:38 +msgctxt "admin-text" +msgid "Site Reviews: Display User Rating" +msgstr "" + +#: plugin/Integrations/UltimateMember/DirectoryController.php:49, plugin/Integrations/UltimateMember/ProfileController.php:49 +msgctxt "admin-text" +msgid "Site Reviews: Highest rated first" +msgstr "" + +#: plugin/Integrations/UltimateMember/DirectoryController.php:50, plugin/Integrations/UltimateMember/ProfileController.php:50 +msgctxt "admin-text" +msgid "Site Reviews: Lowest rated first" +msgstr "" + #: plugin/Modules/Html/Form.php:24 msgid "Submit Form" msgstr "" diff --git a/plugin/Database/CountManager.php b/plugin/Database/CountManager.php index b514d269d..ef5745d6c 100644 --- a/plugin/Database/CountManager.php +++ b/plugin/Database/CountManager.php @@ -3,6 +3,7 @@ namespace GeminiLabs\SiteReviews\Database; use GeminiLabs\SiteReviews\Database; +use GeminiLabs\SiteReviews\Helpers\Cast; use GeminiLabs\SiteReviews\Helpers\Str; use GeminiLabs\SiteReviews\Modules\Rating; @@ -21,6 +22,21 @@ public function posts(int $postId): void glsr()->action('ratings/count/post', $postId, $counts); } + public function postsAverage(int $postId): float + { + return Cast::toFloat(get_post_meta($postId, static::META_AVERAGE, true)); + } + + public function postsRanking(int $postId): float + { + return Cast::toFloat(get_post_meta($postId, static::META_RANKING, true)); + } + + public function postsReviews(int $postId): int + { + return Cast::toInt(get_post_meta($postId, static::META_REVIEWS, true)); + } + public function recalculate(): void { $this->recalculateFor('post'); @@ -53,6 +69,21 @@ public function terms(int $termId): void glsr()->action('ratings/count/term', $termId, $counts); } + public function termsAverage(int $termId): float + { + return Cast::toFloat(get_term_meta($termId, static::META_AVERAGE, true)); + } + + public function termsRanking(int $termId): float + { + return Cast::toFloat(get_term_meta($termId, static::META_RANKING, true)); + } + + public function termsReviews(int $termId): int + { + return Cast::toInt(get_term_meta($termId, static::META_REVIEWS, true)); + } + public function users(int $userId): void { $counts = glsr_get_ratings(['assigned_users' => $userId]); @@ -62,6 +93,21 @@ public function users(int $userId): void glsr()->action('ratings/count/user', $userId, $counts); } + public function usersAverage(int $userId): float + { + return Cast::toFloat(get_user_meta($userId, static::META_AVERAGE, true)); + } + + public function usersRanking(int $userId): float + { + return Cast::toFloat(get_user_meta($userId, static::META_RANKING, true)); + } + + public function usersReviews(int $userId): int + { + return Cast::toInt(get_user_meta($userId, static::META_REVIEWS, true)); + } + protected function metaId(string $metaGroup): string { return "{$metaGroup}_id"; diff --git a/plugin/Integrations/UltimateMember/Controller.php b/plugin/Integrations/UltimateMember/Controller.php deleted file mode 100644 index 97c7f40a1..000000000 --- a/plugin/Integrations/UltimateMember/Controller.php +++ /dev/null @@ -1,36 +0,0 @@ -author_id) { - return $defaultUrl; - } - if ('none' !== $type && $url === $defaultUrl) { - return ''; - } - return $url; - } - - /** - * @filter site-reviews/assigned_users/profile_id - */ - public function filterProfileId(int $profileId): int - { - if (empty($profileId)) { - return (int) um_get_requested_user(); - } - return $profileId; - } -} diff --git a/plugin/Integrations/UltimateMember/Controllers/Controller.php b/plugin/Integrations/UltimateMember/Controllers/Controller.php new file mode 100644 index 000000000..68d2f769e --- /dev/null +++ b/plugin/Integrations/UltimateMember/Controllers/Controller.php @@ -0,0 +1,137 @@ +author_id) { + return $defaultUrl; + } + if ('none' !== $type && $url === $defaultUrl) { + return ''; + } + return $url; + } + + /** + * @filter site-reviews/enqueue/public/inline-styles + */ + public function filterInlineStyles(string $css): string + { + if (glsr_get_option('integrations.ultimatemember.display_directory_ratings', false, 'bool')) { + $css .= '.um-directory .um-members-grid .um-member-rating:has(.glsr-star-rating) {display:flex; justify-content:center;}'; + $css .= '.um-directory .um-members-list .um-member-rating:has(.glsr-star-rating) {display:flex;}'; + $css .= '.um-directory .glsr-star-rating {margin:3px 0;}'; + } + if (glsr_get_option('integrations.ultimatemember.display_reviews_tab', false, 'bool')) { + $css .= '.um-reviews-summary .glsr-summary-text {--glsr-summary-text: var(--glsr-text-base);}'; + $css .= '.um-reviews-summary {padding-top:var(--glsr-gap-lg); padding-bottom:var(--glsr-gap-xl);}'; + $css .= '.um-reviews-form:has(div) {border-top: 3px solid #eee; padding:var(--glsr-gap-xl) 0;}'; + $css .= '.um-reviews-reviews:has(.glsr-reviews) {border-top: 3px solid #eee; padding-top:var(--glsr-gap-xl);}'; + } + return $css; + } + + /** + * @filter site-reviews/assigned_users/profile_id + */ + public function filterProfileId(int $profileId): int + { + if (empty($profileId)) { + return (int) um_get_requested_user(); + } + return $profileId; + } + + /** + * @filter site-reviews/settings + */ + public function filterSettings(array $settings): array + { + return array_merge(glsr()->config('integrations/ultimatemember'), $settings); + } + + /** + * @filter site-reviews/settings/sanitize + */ + public function filterSettingsCallback(array $settings, array $input): array + { + $enabled = Arr::get($input, 'settings.integrations.ultimatemember.enabled'); + if ('yes' === $enabled && !$this->gatekeeper()->allows()) { // this renders any error notices + $settings = Arr::set($settings, 'settings.integrations.ultimatemember.enabled', 'no'); + } + $shortcodes = [ + 'form' => 'site_reviews_form', + 'reviews' => 'site_reviews', + 'summary' => 'site_reviews_summary', + ]; + foreach ($shortcodes as $key => $shortcode) { + $path = "settings.integrations.ultimatemember.{$key}"; + $value = Arr::get($input, $path); + if (1 !== preg_match("/^\[{$shortcode}(\s[^\]]*\]|\])$/", $value)) { + continue; + } + if (!str_contains($value, 'assigned_users')) { + $value = str_replace($shortcode, sprintf('%s assigned_users="profile_id"', $shortcode), $value); + $settings = Arr::set($settings, $path, $value); + } + } + return $settings; + } + + /** + * @filter site-reviews/integration/subsubsub + */ + public function filterSubsubsub(array $subsubsub): array + { + $subsubsub['ultimatemember'] = 'Ultimate Member'; + return $subsubsub; + } + + /** + * @action admin_init + */ + public function renderNotice(): void + { + if (glsr_get_option('integrations.ultimatemember.enabled', false, 'bool')) { + $this->gatekeeper()->allows(); // this renders any error notices + } + } + + /** + * @action site-reviews/settings/ultimatemember + */ + public function renderSettings(string $rows): void + { + glsr(Template::class)->render('integrations/ultimatemember/settings', [ + 'context' => [ + 'rows' => $rows, + ], + ]); + } + + protected function gatekeeper(): Gatekeeper + { + return new Gatekeeper([ + 'ultimate-member/ultimate-member.php' => [ + 'minimum_version' => '2.5', + 'name' => 'Ultimate Member', + 'plugin_uri' => 'https://wordpress.org/plugins/ultimate-member/', + 'untested_version' => '3.0', + ], + ]); + } +} diff --git a/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php b/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php new file mode 100644 index 000000000..1db401f9b --- /dev/null +++ b/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php @@ -0,0 +1,136 @@ +usersReviews($userId); + if (0 < $total || glsr_get_option('integrations.ultimatemember.display_empty', false, 'bool')) { + $rating = glsr(CountManager::class)->usersAverage($userId); + $html = glsr(Builder::class)->div([ + 'class' => 'um-member-rating', + 'text' => glsr_star_rating($rating, $total), + ]); + } + $data['glsr_rating_html'] = $html; + return $data; + } + + /** + * @filter um_admin_extend_directory_options_profile + */ + public function filterDirectoryProfileOptions(array $fields): array + { + $fields[] = [ + 'id' => '_um_glsr_display_user_rating', + 'type' => 'checkbox', + 'label' => _x('Site Reviews: Display User Rating', 'admin-text', 'site-reviews'), + 'value' => UM()->query()->get_meta_value('_um_glsr_display_user_rating', null, 'na'), + ]; + return $fields; + } + + /** + * @filter um_members_directory_sort_fields + */ + public function filterDirectoryProfileSortOptions(array $options): array + { + $options['glsr_highest_rated'] = _x('Site Reviews: Highest rated first', 'admin-text', 'site-reviews'); + $options['glsr_lowest_rated'] = _x('Site Reviews: Lowest rated first', 'admin-text', 'site-reviews'); + return $options; + } + + /** + * Used when the UM "Custom usermeta table" setting is disabled. + * + * @param string $sortby + * + * @filter um_modify_sortby_parameter + */ + public function filterDirectorySortBy(array $queryArgs, $sortby): array + { + if (!in_array($sortby, ['glsr_highest_rated', 'glsr_lowest_rated'])) { + return $queryArgs; + } + $order = 'glsr_highest_rated' === $sortby ? 'DESC' : 'ASC'; + $sortKey = 'bayesian' === glsr_get_option('integrations.ultimatemember.sorting') + ? CountManager::META_RANKING + : CountManager::META_AVERAGE; + $queryArgs['meta_query'] ??= []; + $queryArgs['meta_query'][] = [ + 'relation' => 'OR', + [ + 'compare' => 'NOT EXISTS', + 'key' => $sortKey, + 'type' => 'NUMERIC', + ], + '_rating_key' => [ + 'compare' => 'EXISTS', + 'key' => $sortKey, + 'type' => 'NUMERIC', + ], + ]; + $queryArgs['orderby'] = [ + '_rating_key' => $order, + 'user_registered' => 'DESC', + ]; + unset($queryArgs['order']); + return $queryArgs; + } + + /** + * Used when the UM "Custom usermeta table" setting is enabled. + * + * @param \um\core\Member_Directory_Meta $query + * @param array $directoryData + * @param string $sortby + * + * @action um_pre_users_query + */ + public function modifyQuerySortby($query, $directoryData, $sortby): void + { + if (!in_array($sortby, ['glsr_highest_rated', 'glsr_lowest_rated'])) { + return; + } + $order = esc_sql('glsr_highest_rated' === $sortby ? 'DESC' : 'ASC'); + $sortKey = 'bayesian' === glsr_get_option('integrations.ultimatemember.sorting') + ? CountManager::META_RANKING + : CountManager::META_AVERAGE; + $query->joins[] = glsr(Query::class)->sql( + "LEFT JOIN table|usermeta AS glsr_user_meta ON (glsr_user_meta.user_id = u.ID AND glsr_user_meta.meta_key = %s)", + $sortKey + ); + $query->sql_order = " ORDER BY CAST(glsr_user_meta.meta_value AS SIGNED) {$order}, u.user_registered DESC"; + } + + /** + * @param array $args + * + * @action um_members_just_after_name_tmpl + * @action um_members_list_after_user_name_tmpl + */ + public function modifyTmpl($args): void + { + $displayRating = Arr::get($args, 'glsr_display_user_rating'); + if (empty($displayRating)) { + return; + } + echo "<# if ('undefined' !== typeof user.glsr_rating_html) { #>{{{user.glsr_rating_html}}}<# } #>"; + } +} diff --git a/plugin/Integrations/UltimateMember/Controllers/ProfileController.php b/plugin/Integrations/UltimateMember/Controllers/ProfileController.php new file mode 100644 index 000000000..f14c3e9cd --- /dev/null +++ b/plugin/Integrations/UltimateMember/Controllers/ProfileController.php @@ -0,0 +1,137 @@ + 'um-faicon-star', + 'name' => __('Reviews', 'site-reviews'), + ]; + return $tabs; + } + + /** + * @filter site-reviews/reviews/fallback + */ + public function filterReviewsFallback(string $fallback, array $args): string + { + if ($args['fallback'] !== __('There are no reviews yet. Be the first one to write one.', 'site-reviews')) { + return $fallback; + } + $value = get_current_user_id() === um_get_requested_user() + ? __('You have not received any reviews.', 'site-reviews') + : __('This person has not received any reviews.', 'site-reviews'); + return glsr(Builder::class)->p([ + 'class' => 'glsr-no-margins', + 'text' => $value, + ]); + } + + /** + * @filter site-reviews/summary/value/percentages + */ + public function filterSummaryPercentagesValue(string $value, TagContract $tag): string + { + if (!empty(array_sum($tag->ratings))) { + return $value; + } + return ''; + } + + /** + * @filter site-reviews/summary/value/text + */ + public function filterSummaryRatingValue(string $value, TagContract $tag): string + { + $value = get_current_user_id() === um_get_requested_user() + ? __('Your rating', 'site-reviews') + : __('User rating', 'site-reviews'); + return glsr(Builder::class)->h4($value); + } + + /** + * @filter site-reviews/summary/value/rating + */ + public function filterSummaryTextValue(string $value, TagContract $tag): string + { + if (!empty(array_sum($tag->ratings))) { + return $value; + } + if (get_current_user_id() === um_get_requested_user()) { + return __('No one has reviewed you yet.', 'site-reviews'); + } + return __('No one has reviewed this person yet.', 'site-reviews'); + } + + /** + * @action um_profile_content_user_reviews + */ + public function renderReviewsTab(): void + { + if (!glsr_get_option('integrations.ultimatemember.display_reviews_tab', false, 'bool')) { + return; + } + glsr(Template::class)->render('templates/ultimatemember/reviews', [ + 'context' => [ + 'form' => $this->shortcodeForm(), + 'reviews' => $this->shortcodeReviews(), + 'summary' => $this->shortcodeSummary(), + ], + ]); + } + + protected function shortcodeForm(): string + { + if (!is_user_logged_in()) { + $text = sprintf(__('You must be %s to review this person.', 'site-reviews'), glsr(SiteReviewsFormShortcode::class)->loginLink()); + return glsr(Template::class)->build('templates/login-register', [ + 'context' => compact('text'), + ]); + } else { + $ratings = glsr_get_ratings([ + 'assigned_users' => um_get_requested_user(), + 'status' => 'all', + 'user__in' => get_current_user_id(), + ]); + if (0 < $ratings->reviews) { + return ''; + } + } + return do_shortcode(glsr_get_option('integrations.ultimatemember.form')); + } + + protected function shortcodeReviews(): string + { + add_filter('site-reviews/reviews/fallback', [$this, 'filterReviewsFallback'], 20, 2); + $shortcode = do_shortcode(glsr_get_option('integrations.ultimatemember.reviews')); + remove_filter('site-reviews/reviews/fallback', [$this, 'filterReviewsFallback'], 20); + return $shortcode; + } + + protected function shortcodeSummary(): string + { + add_filter('site-reviews/summary/value/percentages', [$this, 'filterSummaryPercentagesValue'], 20, 2); + add_filter('site-reviews/summary/value/rating', [$this, 'filterSummaryRatingValue'], 20, 2); + add_filter('site-reviews/summary/value/text', [$this, 'filterSummaryTextValue'], 20, 2); + $shortcode = do_shortcode(glsr_get_option('integrations.ultimatemember.summary')); + remove_filter('site-reviews/summary/value/percentages', [$this, 'filterSummaryPercentagesValue'], 20); + remove_filter('site-reviews/summary/value/rating', [$this, 'filterSummaryRatingValue'], 20); + remove_filter('site-reviews/summary/value/text', [$this, 'filterSummaryTextValue'], 20); + return $shortcode; + } +} diff --git a/plugin/Integrations/UltimateMember/Hooks.php b/plugin/Integrations/UltimateMember/Hooks.php index 56577ee74..cb067e22b 100644 --- a/plugin/Integrations/UltimateMember/Hooks.php +++ b/plugin/Integrations/UltimateMember/Hooks.php @@ -3,23 +3,57 @@ namespace GeminiLabs\SiteReviews\Integrations\UltimateMember; use GeminiLabs\SiteReviews\Hooks\AbstractHooks; +use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\Controller; +use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\DirectoryController; +use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\ProfileController; class Hooks extends AbstractHooks { public function run(): void { - if (!$this->isInstalled()) { - return; - } $this->hook(Controller::class, [ - ['filterAvatarUrl', 'site-reviews/avatar/generate', 10, 2], - ['filterProfileId', 'site-reviews/assigned_users/profile_id', 5], + ['filterSettings', 'site-reviews/settings'], + ['filterSettingsCallback', 'site-reviews/settings/sanitize', 10, 2], + ['filterSubsubsub', 'site-reviews/integration/subsubsub'], + ['renderNotice', 'admin_init'], + ['renderSettings', 'site-reviews/settings/ultimatemember'], ]); + if ($this->isInstalled()) { + $this->hook(Controller::class, [ + ['filterAvatarUrl', 'site-reviews/avatar/generate', 10, 2], + ['filterProfileId', 'site-reviews/assigned_users/profile_id', 5], + ]); + } + if ($this->isEnabled()) { + $this->hook(Controller::class, [ + ['filterInlineStyles', 'site-reviews/enqueue/public/inline-styles'], + ]); + $this->hook(DirectoryController::class, [ + ['filterAjaxMembersData', 'um_ajax_get_members_data', 50, 2], + ['filterDirectoryProfileOptions', 'um_admin_extend_directory_options_profile', 15], + ['filterDirectoryProfileSortOptions', 'um_members_directory_sort_fields'], + ['filterDirectorySortBy', 'um_modify_sortby_parameter', 100, 2], + ['modifyQuerySortby', 'um_pre_users_query', 10, 3], + ['modifyTmpl', 'um_members_just_after_name_tmpl', 1], + ['modifyTmpl', 'um_members_list_after_user_name_tmpl', 1], + ]); + $this->hook(ProfileController::class, [ + ['filterProfileTabs', 'um_user_profile_tabs', 100], + ['renderReviewsTab', 'um_profile_content_user_reviews'], + ]); + } + } + + protected function isEnabled(): bool + { + return 'yes' === $this->option('integrations.ultimatemember.enabled') + && $this->isInstalled(); } protected function isInstalled(): bool { - return function_exists('um_get_default_avatar_uri') + return function_exists('UM') + && function_exists('um_get_default_avatar_uri') && function_exists('um_get_requested_user'); } } diff --git a/plugin/Modules/Html/Tags/SummaryTag.php b/plugin/Modules/Html/Tags/SummaryTag.php index 88cb6bb30..1b0af3b31 100644 --- a/plugin/Modules/Html/Tags/SummaryTag.php +++ b/plugin/Modules/Html/Tags/SummaryTag.php @@ -7,8 +7,7 @@ class SummaryTag extends Tag { - /** @var array */ - protected $ratings; + public array $ratings = []; protected function hideOption(): string { diff --git a/templates/ultimatemember/reviews.php b/templates/ultimatemember/reviews.php new file mode 100644 index 000000000..7e8515b8c --- /dev/null +++ b/templates/ultimatemember/reviews.php @@ -0,0 +1,11 @@ + + +
+ {{ summary }} +
+
+ {{ form }} +
+
+ {{ reviews }} +
diff --git a/tests/phpstan/stubs/ultimate-member.php b/tests/phpstan/stubs/ultimate-member.php index 1fb6c0270..fab7551dc 100644 --- a/tests/phpstan/stubs/ultimate-member.php +++ b/tests/phpstan/stubs/ultimate-member.php @@ -1,6 +1,135 @@ + +

Ultimate Member

+ + + + {{ rows }} + +