From d6804f780abfae7ba7b25e1a1f368f9d405b6a1d Mon Sep 17 00:00:00 2001 From: Scott Kingsley Clark Date: Tue, 13 Dec 2022 09:04:38 -0600 Subject: [PATCH 01/11] Update version --- init.php | 4 ++-- package.json | 2 +- readme.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/init.php b/init.php index 2a81e254df..ffc7c88824 100644 --- a/init.php +++ b/init.php @@ -10,7 +10,7 @@ * Plugin Name: Pods - Custom Content Types and Fields * Plugin URI: https://pods.io/ * Description: Pods is a framework for creating, managing, and deploying customized content types and fields - * Version: 2.9.10 + * Version: 2.9.11-a-1 * Author: Pods Framework Team * Author URI: https://pods.io/about/ * Text Domain: pods @@ -43,7 +43,7 @@ add_action( 'init', 'pods_deactivate_pods_ui' ); } else { // Current version. - define( 'PODS_VERSION', '2.9.10' ); + define( 'PODS_VERSION', '2.9.11-a-1' ); // Current database version, this is the last version the database changed. define( 'PODS_DB_VERSION', '2.3.5' ); diff --git a/package.json b/package.json index e50ad421d5..85fa3fe741 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pods", - "version": "2.9.10", + "version": "2.9.11-a-1", "description": "Pods is a development framework for creating, extending, managing, and deploying customized content types in WordPress.", "author": "Pods Foundation, Inc", "homepage": "https://pods.io/", diff --git a/readme.txt b/readme.txt index 72ebd24838..a94420a811 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Tags: pods, custom post types, custom taxonomies, content types, custom fields, Requires at least: 5.7 Tested up to: 6.1 Requires PHP: 5.6 -Stable tag: 2.9.10 +Stable tag: 2.9.11-a-1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From c47565016a0c49abd4732f34e0da6c99b80a2a60 Mon Sep 17 00:00:00 2001 From: Scott Kingsley Clark Date: Wed, 14 Dec 2022 10:27:13 -0600 Subject: [PATCH 02/11] Remove pods_debug() output from Repair tool --- src/Pods/Tools/Base.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Pods/Tools/Base.php b/src/Pods/Tools/Base.php index acbb84fb4c..2b7017d1a6 100644 --- a/src/Pods/Tools/Base.php +++ b/src/Pods/Tools/Base.php @@ -83,7 +83,6 @@ protected function get_message_html( $tool_heading, array $results, $mode = null } if ( empty( $result_set ) ) { - pods_debug( $heading ); $result_set[] = __( 'No actions were needed.', 'pods' ); } From 46fe4a36fc5ba80ebe0295cb23085725caf4389b Mon Sep 17 00:00:00 2001 From: Scott Kingsley Clark Date: Thu, 15 Dec 2022 10:50:39 -0600 Subject: [PATCH 03/11] Update namespace var checks to check if not empty --- classes/PodsInit.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/PodsInit.php b/classes/PodsInit.php index e40b87aab3..fa3a5bf299 100644 --- a/classes/PodsInit.php +++ b/classes/PodsInit.php @@ -1564,7 +1564,7 @@ public function setup_content_types( $force = false ) { $rest_namespace = pods_v( 'rest_namespace', $post_type ); // Get the namespace and sanitize/clean up the path. - if ( $rest_namespace ) { + if ( ! empty( $rest_namespace ) ) { $rest_namespace = str_replace( '\\', '/', $rest_namespace ); $rest_namespace = explode( '/', $rest_namespace ); $rest_namespace = array_map( 'sanitize_title', $rest_namespace ); @@ -1578,7 +1578,7 @@ public function setup_content_types( $force = false ) { $pods_post_types[ $post_type_name ]['rest_base'] = $rest_base; $pods_post_types[ $post_type_name ]['rest_controller_class'] = 'WP_REST_Posts_Controller'; - if ( $rest_namespace ) { + if ( ! empty( $rest_namespace ) ) { $pods_post_types[ $post_type_name ]['rest_namespace'] = $rest_namespace; } } @@ -1771,7 +1771,7 @@ public function setup_content_types( $force = false ) { $rest_namespace = pods_v( 'rest_namespace', $taxonomy ); // Get the namespace and sanitize/clean up the path. - if ( $rest_namespace ) { + if ( ! empty( $rest_namespace ) ) { $rest_namespace = str_replace( '\\', '/', $rest_namespace ); $rest_namespace = explode( '/', $rest_namespace ); $rest_namespace = array_map( 'sanitize_title', $rest_namespace ); @@ -1785,7 +1785,7 @@ public function setup_content_types( $force = false ) { $pods_taxonomies[ $taxonomy_name ]['rest_base'] = $rest_base; $pods_taxonomies[ $taxonomy_name ]['rest_controller_class'] = 'WP_REST_Terms_Controller'; - if ( $rest_namespace ) { + if ( ! empty( $rest_namespace ) ) { $pods_taxonomies[ $taxonomy_name ]['rest_namespace'] = $rest_namespace; } } From aece2d1119b49aeaf14d3cb4edc2d1aa799ed94e Mon Sep 17 00:00:00 2001 From: Scott Kingsley Clark Date: Thu, 15 Dec 2022 10:58:09 -0600 Subject: [PATCH 04/11] Update Tribe Common to 5.0.5 --- .../Editor/Full_Site/Template_Utils.php | 88 ++ .../src/Tribe/Admin/Activation_Page.php | 68 +- .../Conditional_Content/Black_Friday.php | 84 ++ .../Datetime_Conditional_Abstract.php | 150 +++ .../Conditional_Content/End_Of_Year_Sale.php | 85 ++ .../Conditional_Content/Service_Provider.php | 49 + tribe-common/src/Tribe/Admin/Help_Page.php | 281 +++++- tribe-common/src/Tribe/Admin/Helpers.php | 13 +- .../src/Tribe/Admin/Notice/Date_Based.php | 367 ++++++++ .../src/Tribe/Admin/Notice/Marketing.php | 16 +- .../Admin/Notice/Marketing/Black_Friday.php | 72 ++ .../Notice/Marketing/End_Of_Year_Sale.php | 66 ++ .../Admin/Notice/Marketing/Stellar_Sale.php | 77 ++ .../Tribe/Admin/Notice/Plugin_Download.php | 2 +- .../Tribe/Admin/Notice/Service_Provider.php | 63 ++ .../src/Tribe/Admin/Notice/WP_Version.php | 4 +- tribe-common/src/Tribe/Admin/Notices.php | 75 +- tribe-common/src/Tribe/Admin/Pages.php | 309 +++++++ tribe-common/src/Tribe/Admin/Settings.php | 82 ++ .../src/Tribe/Admin/Troubleshooting.php | 382 ++++++++ .../src/Tribe/Admin/Upsell_Notice/Main.php | 97 ++ .../src/Tribe/Admin/Upsell_Notice/README.md | 65 ++ tribe-common/src/Tribe/Ajax/Dropdown.php | 6 +- tribe-common/src/Tribe/App_Shop.php | 62 ++ tribe-common/src/Tribe/Assets.php | 116 ++- tribe-common/src/Tribe/Assets_Pipeline.php | 36 +- tribe-common/src/Tribe/Cache.php | 114 ++- tribe-common/src/Tribe/Cache_Listener.php | 322 ++++--- tribe-common/src/Tribe/Context.php | 18 +- tribe-common/src/Tribe/Context/locations.php | 10 +- tribe-common/src/Tribe/Cost_Utils.php | 4 +- tribe-common/src/Tribe/Credits.php | 50 +- tribe-common/src/Tribe/Customizer.php | 272 +++++- .../src/Tribe/Customizer/Controls/Heading.php | 25 +- .../src/Tribe/Customizer/Controls/Number.php | 78 ++ .../src/Tribe/Customizer/Controls/Radio.php | 73 ++ .../Customizer/Controls/Range_Slider.php | 97 ++ .../Tribe/Customizer/Controls/Separator.php | 62 ++ .../src/Tribe/Customizer/Controls/Toggle.php | 86 ++ tribe-common/src/Tribe/Customizer/Section.php | 871 ++++++++++++++++-- tribe-common/src/Tribe/Data.php | 4 - tribe-common/src/Tribe/Date_Utils.php | 92 +- tribe-common/src/Tribe/Dialog/View.php | 85 +- tribe-common/src/Tribe/Editor.php | 205 +++-- tribe-common/src/Tribe/Editor/Assets.php | 156 +--- .../src/Tribe/Editor/Blocks/Abstract.php | 16 +- .../src/Tribe/Editor/Compatibility.php | 31 + .../Editor/Compatibility/Classic_Editor.php | 386 ++++++++ .../src/Tribe/Editor/Compatibility/Divi.php | 109 +++ .../src/Tribe/Editor/Configuration.php | 2 +- tribe-common/src/Tribe/Editor/Provider.php | 4 +- tribe-common/src/Tribe/Field.php | 96 +- tribe-common/src/Tribe/JSON_LD/Abstract.php | 9 - tribe-common/src/Tribe/Log/Admin.php | 17 +- .../src/Tribe/Log/Service_Provider.php | 8 +- tribe-common/src/Tribe/Main.php | 141 ++- .../src/Tribe/Models/Post_Types/Base.php | 233 ++++- .../src/Tribe/Models/Post_Types/Nothing.php | 2 +- .../src/Tribe/Onboarding/Hints_Abstract.php | 98 ++ tribe-common/src/Tribe/Onboarding/Main.php | 223 +++++ tribe-common/src/Tribe/Onboarding/README.md | 242 +++++ .../src/Tribe/Onboarding/Tour_Abstract.php | 98 ++ tribe-common/src/Tribe/PUE/Checker.php | 155 +++- tribe-common/src/Tribe/PUE/Notices.php | 27 + tribe-common/src/Tribe/Plugin_Meta_Links.php | 4 +- tribe-common/src/Tribe/Plugins_API.php | 38 +- tribe-common/src/Tribe/Process/Queue.php | 2 +- tribe-common/src/Tribe/Promoter/Connector.php | 4 +- tribe-common/src/Tribe/REST/Main.php | 4 +- tribe-common/src/Tribe/REST/System.php | 19 + tribe-common/src/Tribe/Repository.php | 81 +- .../src/Tribe/Repository/Decorator.php | 16 + .../src/Tribe/Repository/Interface.php | 27 + .../src/Tribe/Repository/Query_Filters.php | 2 +- .../src/Tribe/Service_Providers/Dialog.php | 2 +- .../Tribe/Service_Providers/Onboarding.php | 147 +++ .../src/Tribe/Service_Providers/Tooltip.php | 15 +- tribe-common/src/Tribe/Settings.php | 445 ++++----- tribe-common/src/Tribe/Settings_Manager.php | 88 +- tribe-common/src/Tribe/Settings_Tab.php | 2 +- tribe-common/src/Tribe/Support.php | 79 +- tribe-common/src/Tribe/Template.php | 43 +- tribe-common/src/Tribe/Template_Factory.php | 219 ----- tribe-common/src/Tribe/Timezones.php | 12 +- tribe-common/src/Tribe/Tracker.php | 4 + tribe-common/src/Tribe/Utils/Array.php | 63 +- tribe-common/src/Tribe/Utils/Body_Classes.php | 2 +- .../src/Tribe/Utils/Collection_Trait.php | 10 - tribe-common/src/Tribe/Utils/Color.php | 25 +- .../src/Tribe/Utils/Compatibility_Classes.php | 352 +++++++ tribe-common/src/Tribe/Utils/Date_I18n.php | 1 + .../src/Tribe/Utils/Date_I18n_Immutable.php | 1 + .../src/Tribe/Utils/Element_Classes.php | 6 +- tribe-common/src/Tribe/Utils/Lazy_Events.php | 2 +- tribe-common/src/Tribe/Utils/Lazy_String.php | 7 +- .../src/Tribe/Utils/Post_Thumbnail.php | 4 - tribe-common/src/Tribe/Utils/Taxonomy.php | 73 ++ .../src/Tribe/Utils/Theme_Compatibility.php | 276 ++++++ tribe-common/src/Tribe/Validate.php | 14 + .../src/Tribe/Values/Abstract_Currency.php | 369 ++++++++ .../src/Tribe/Values/Abstract_Value.php | 455 +++++++++ .../src/Tribe/Values/Currency_Interface.php | 86 ++ .../src/Tribe/Values/Value_Calculation.php | 71 ++ .../src/Tribe/Values/Value_Formatting.php | 58 ++ .../src/Tribe/Values/Value_Interface.php | 145 +++ .../src/Tribe/Values/Value_Update.php | 58 ++ .../src/Tribe/Widget/Widget_Abstract.php | 17 +- tribe-common/src/admin-views/app-shop.php | 53 +- .../src/admin-views/components/icons/dot.php | 23 + .../src/admin-views/components/loader.php | 44 + .../src/admin-views/components/switch.php | 57 ++ .../conditional_content/black-friday.php | 35 + .../conditional_content/end-of-year-sale.php | 35 + .../src/admin-views/help-calendar.php | 248 +++++ .../src/admin-views/help-community.php | 241 +++++ .../src/admin-views/help-ticketing.php | 244 +++++ tribe-common/src/admin-views/help.php | 107 +++ .../admin-views/notices/end-of-year-sale.php | 30 + .../admin-views/notices/tribe-bf-general.php | 15 +- .../notices/tribe-stellar-sale.php | 29 + .../src/admin-views/notices/upsell/icon.php | 21 + .../src/admin-views/notices/upsell/main.php | 50 + .../src/admin-views/tribe-options-display.php | 62 -- .../src/admin-views/tribe-options-general.php | 80 -- .../src/admin-views/tribe-options-network.php | 35 - .../src/admin-views/troubleshooting.php | 55 ++ .../troubleshooting/common-issues.php | 40 + .../troubleshooting/detected-issues.php | 54 ++ .../admin-views/troubleshooting/ea-status.php | 52 ++ .../ea-status/current-status.php | 41 + .../ea-status/current-usage.php | 42 + .../troubleshooting/ea-status/eventbrite.php | 48 + .../troubleshooting/ea-status/license-key.php | 54 ++ .../troubleshooting/ea-status/meetup.php | 39 + .../ea-status/scheduler-status.php | 33 + .../ea-status/server-connection.php | 55 ++ .../admin-views/troubleshooting/event-log.php | 18 + .../troubleshooting/first-steps.php | 55 ++ .../troubleshooting/footer-logo.php | 14 + .../troubleshooting/introduction.php | 31 + .../admin-views/troubleshooting/notice.php | 20 + .../recent-template-changes.php | 16 + .../troubleshooting/support-cta.php | 31 + .../troubleshooting/system-information.php | 42 + .../deprecated/Tribe__Template_Factory.php | 32 + tribe-common/src/functions/conditionals.php | 78 ++ tribe-common/src/functions/editor.php | 20 + tribe-common/src/functions/files.php | 42 + .../src/functions/template-tags/date.php | 6 +- .../src/functions/template-tags/general.php | 85 +- tribe-common/src/functions/utils.php | 92 +- tribe-common/src/views/dialog/confirm.php | 4 +- tribe-common/src/views/dialog/warning.php | 32 + tribe-common/src/views/tooltip/tooltip.php | 2 +- .../views/v2/components/icons/arrow-right.php | 2 +- .../views/v2/components/icons/cal-export.php | 28 + .../views/v2/components/icons/caret-down.php | 2 +- .../views/v2/components/icons/caret-left.php | 2 +- .../views/v2/components/icons/caret-right.php | 2 +- .../views/v2/components/icons/close-alt.php | 2 +- .../src/views/v2/components/icons/close.php | 2 +- .../src/views/v2/components/icons/day.php | 2 +- .../src/views/v2/components/icons/dot.php | 2 +- .../src/views/v2/components/icons/error.php | 2 +- .../views/v2/components/icons/featured.php | 11 +- .../src/views/v2/components/icons/filter.php | 2 +- .../src/views/v2/components/icons/hybrid.php | 34 + .../src/views/v2/components/icons/list.php | 15 +- .../views/v2/components/icons/location.php | 2 +- .../src/views/v2/components/icons/mail.php | 2 +- .../src/views/v2/components/icons/map-pin.php | 2 +- .../src/views/v2/components/icons/map.php | 2 +- .../components/icons/messages-not-found.php | 2 +- .../src/views/v2/components/icons/minus.php | 2 +- .../src/views/v2/components/icons/month.php | 2 +- .../src/views/v2/components/icons/no-map.php | 2 +- .../src/views/v2/components/icons/phone.php | 2 +- .../src/views/v2/components/icons/photo.php | 2 +- .../src/views/v2/components/icons/play.php | 2 +- .../src/views/v2/components/icons/plus.php | 2 +- .../views/v2/components/icons/recurring.php | 14 +- .../src/views/v2/components/icons/reset.php | 2 +- .../src/views/v2/components/icons/search.php | 2 +- .../v2/components/icons/stellar-icon.php | 27 + .../src/views/v2/components/icons/summary.php | 27 + .../src/views/v2/components/icons/video.php | 2 +- .../src/views/v2/components/icons/virtual.php | 14 +- .../src/views/v2/components/icons/website.php | 2 +- .../src/views/v2/components/icons/week.php | 2 +- tribe-common/tribe-autoload.php | 1 + tribe-common/vendor/autoload.php | 2 +- tribe-common/vendor/autoload_52.php | 2 +- .../vendor/clipboard/clipboard.min.js | 7 - .../vendor/composer/autoload_classmap.php | 38 + .../vendor/composer/autoload_psr4.php | 1 + .../vendor/composer/autoload_real.php | 8 +- .../vendor/composer/autoload_real_52.php | 6 +- .../vendor/composer/autoload_static.php | 53 +- tribe-common/vendor/composer/installed.json | 68 +- .../php-jwt/src/BeforeValidException.php | 2 +- .../firebase/php-jwt/src/ExpiredException.php | 2 +- .../vendor/firebase/php-jwt/src/JWT.php | 526 ++++++++--- .../php-jwt/src/SignatureInvalidException.php | 2 +- .../vendor/psr/log/Psr/Log/AbstractLogger.php | 32 +- .../psr/log/Psr/Log/LoggerAwareTrait.php | 2 +- .../tribe-selectWoo/dist/js/select2.full.js | 16 +- .../vendor/tribe-selectWoo/dist/js/select2.js | 16 +- .../tribe-selectWoo/dist/js/selectWoo.full.js | 14 +- .../dist/js/selectWoo.full.min.js | 2 +- .../tribe-selectWoo/dist/js/selectWoo.js | 16 +- 210 files changed, 12450 insertions(+), 1716 deletions(-) create mode 100644 tribe-common/src/Common/Editor/Full_Site/Template_Utils.php create mode 100644 tribe-common/src/Tribe/Admin/Conditional_Content/Black_Friday.php create mode 100644 tribe-common/src/Tribe/Admin/Conditional_Content/Datetime_Conditional_Abstract.php create mode 100644 tribe-common/src/Tribe/Admin/Conditional_Content/End_Of_Year_Sale.php create mode 100644 tribe-common/src/Tribe/Admin/Conditional_Content/Service_Provider.php create mode 100644 tribe-common/src/Tribe/Admin/Notice/Date_Based.php create mode 100644 tribe-common/src/Tribe/Admin/Notice/Marketing/Black_Friday.php create mode 100644 tribe-common/src/Tribe/Admin/Notice/Marketing/End_Of_Year_Sale.php create mode 100644 tribe-common/src/Tribe/Admin/Notice/Marketing/Stellar_Sale.php create mode 100644 tribe-common/src/Tribe/Admin/Notice/Service_Provider.php create mode 100644 tribe-common/src/Tribe/Admin/Pages.php create mode 100644 tribe-common/src/Tribe/Admin/Settings.php create mode 100644 tribe-common/src/Tribe/Admin/Troubleshooting.php create mode 100644 tribe-common/src/Tribe/Admin/Upsell_Notice/Main.php create mode 100644 tribe-common/src/Tribe/Admin/Upsell_Notice/README.md create mode 100644 tribe-common/src/Tribe/Customizer/Controls/Number.php create mode 100644 tribe-common/src/Tribe/Customizer/Controls/Radio.php create mode 100644 tribe-common/src/Tribe/Customizer/Controls/Range_Slider.php create mode 100644 tribe-common/src/Tribe/Customizer/Controls/Separator.php create mode 100644 tribe-common/src/Tribe/Customizer/Controls/Toggle.php create mode 100644 tribe-common/src/Tribe/Editor/Compatibility.php create mode 100644 tribe-common/src/Tribe/Editor/Compatibility/Classic_Editor.php create mode 100644 tribe-common/src/Tribe/Editor/Compatibility/Divi.php create mode 100644 tribe-common/src/Tribe/Onboarding/Hints_Abstract.php create mode 100644 tribe-common/src/Tribe/Onboarding/Main.php create mode 100644 tribe-common/src/Tribe/Onboarding/README.md create mode 100644 tribe-common/src/Tribe/Onboarding/Tour_Abstract.php create mode 100644 tribe-common/src/Tribe/Service_Providers/Onboarding.php delete mode 100755 tribe-common/src/Tribe/Template_Factory.php create mode 100644 tribe-common/src/Tribe/Utils/Compatibility_Classes.php create mode 100644 tribe-common/src/Tribe/Utils/Theme_Compatibility.php create mode 100644 tribe-common/src/Tribe/Values/Abstract_Currency.php create mode 100644 tribe-common/src/Tribe/Values/Abstract_Value.php create mode 100644 tribe-common/src/Tribe/Values/Currency_Interface.php create mode 100644 tribe-common/src/Tribe/Values/Value_Calculation.php create mode 100644 tribe-common/src/Tribe/Values/Value_Formatting.php create mode 100644 tribe-common/src/Tribe/Values/Value_Interface.php create mode 100644 tribe-common/src/Tribe/Values/Value_Update.php create mode 100644 tribe-common/src/admin-views/components/icons/dot.php create mode 100644 tribe-common/src/admin-views/components/loader.php create mode 100644 tribe-common/src/admin-views/components/switch.php create mode 100644 tribe-common/src/admin-views/conditional_content/black-friday.php create mode 100644 tribe-common/src/admin-views/conditional_content/end-of-year-sale.php create mode 100644 tribe-common/src/admin-views/help-calendar.php create mode 100644 tribe-common/src/admin-views/help-community.php create mode 100644 tribe-common/src/admin-views/help-ticketing.php create mode 100755 tribe-common/src/admin-views/help.php create mode 100644 tribe-common/src/admin-views/notices/end-of-year-sale.php create mode 100644 tribe-common/src/admin-views/notices/tribe-stellar-sale.php create mode 100644 tribe-common/src/admin-views/notices/upsell/icon.php create mode 100644 tribe-common/src/admin-views/notices/upsell/main.php delete mode 100755 tribe-common/src/admin-views/tribe-options-display.php delete mode 100755 tribe-common/src/admin-views/tribe-options-general.php delete mode 100755 tribe-common/src/admin-views/tribe-options-network.php create mode 100755 tribe-common/src/admin-views/troubleshooting.php create mode 100644 tribe-common/src/admin-views/troubleshooting/common-issues.php create mode 100644 tribe-common/src/admin-views/troubleshooting/detected-issues.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/current-status.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/current-usage.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/eventbrite.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/license-key.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/meetup.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/scheduler-status.php create mode 100644 tribe-common/src/admin-views/troubleshooting/ea-status/server-connection.php create mode 100644 tribe-common/src/admin-views/troubleshooting/event-log.php create mode 100644 tribe-common/src/admin-views/troubleshooting/first-steps.php create mode 100644 tribe-common/src/admin-views/troubleshooting/footer-logo.php create mode 100644 tribe-common/src/admin-views/troubleshooting/introduction.php create mode 100644 tribe-common/src/admin-views/troubleshooting/notice.php create mode 100644 tribe-common/src/admin-views/troubleshooting/recent-template-changes.php create mode 100644 tribe-common/src/admin-views/troubleshooting/support-cta.php create mode 100644 tribe-common/src/admin-views/troubleshooting/system-information.php create mode 100644 tribe-common/src/deprecated/Tribe__Template_Factory.php create mode 100644 tribe-common/src/functions/conditionals.php create mode 100644 tribe-common/src/functions/editor.php create mode 100644 tribe-common/src/functions/files.php create mode 100644 tribe-common/src/views/dialog/warning.php create mode 100644 tribe-common/src/views/v2/components/icons/cal-export.php create mode 100644 tribe-common/src/views/v2/components/icons/hybrid.php create mode 100644 tribe-common/src/views/v2/components/icons/stellar-icon.php create mode 100644 tribe-common/src/views/v2/components/icons/summary.php delete mode 100644 tribe-common/vendor/clipboard/clipboard.min.js diff --git a/tribe-common/src/Common/Editor/Full_Site/Template_Utils.php b/tribe-common/src/Common/Editor/Full_Site/Template_Utils.php new file mode 100644 index 0000000000..d87f398d8e --- /dev/null +++ b/tribe-common/src/Common/Editor/Full_Site/Template_Utils.php @@ -0,0 +1,88 @@ +> $blocks Array of parsed block objects. + * + * @return array> Block references to the passed blocks and their inner blocks. + */ + public static function flatten_blocks( &$blocks ) { + $all_blocks = []; + $queue = []; + + foreach ( $blocks as &$block ) { + $queue[] = &$block; + } + + $queue_count = count( $queue ); + + while ( $queue_count > 0 ) { + $block = &$queue[0]; + array_shift( $queue ); + $all_blocks[] = &$block; + + if ( ! empty( $block['innerBlocks'] ) ) { + foreach ( $block['innerBlocks'] as &$inner_block ) { + $queue[] = &$inner_block; + } + } + + $queue_count = count( $queue ); + } + + return $all_blocks; + } + + /** + * Parses wp_template content and injects the current theme's stylesheet as a theme attribute into + * each wp_template_part. + * + * @since 4.14.18 + * + * @param string $template_content serialized wp_template content. + * + * @return string Updated wp_template content. + */ + public static function inject_theme_attribute_in_content( $template_content ) { + $has_updated_content = false; + $new_content = ''; + $template_blocks = parse_blocks( $template_content ); + + $blocks = static::flatten_blocks( $template_blocks ); + foreach ( $blocks as &$block ) { + if ( + 'core/template-part' === $block['blockName'] && + ! isset( $block['attrs']['theme'] ) + ) { + $block['attrs']['theme'] = wp_get_theme()->get_stylesheet(); + $has_updated_content = true; + } + } + + if ( $has_updated_content ) { + foreach ( $template_blocks as &$block ) { + $new_content .= serialize_block( $block ); + } + + return $new_content; + } + + return $template_content; + } +} diff --git a/tribe-common/src/Tribe/Admin/Activation_Page.php b/tribe-common/src/Tribe/Admin/Activation_Page.php index 9d83bfdb6a..dbe6d44a4a 100644 --- a/tribe-common/src/Tribe/Admin/Activation_Page.php +++ b/tribe-common/src/Tribe/Admin/Activation_Page.php @@ -27,6 +27,8 @@ class Tribe__Admin__Activation_Page { public function __construct( array $args = [] ) { $this->args = wp_parse_args( $args, [ 'slug' => '', + 'admin_page' => '', + 'admin_url' => '', 'activation_transient' => '', 'version' => '', 'plugin_path' => '', @@ -69,6 +71,11 @@ public function is_update_page() { * Listen for opportunities to show update and welcome splash pages. */ public function hooks() { + // Never show this on the front-end. + if ( ! is_admin() ) { + return; + } + if ( tribe_is_truthy( get_option( 'tribe_skip_welcome', false ) ) || tribe_is_truthy( tribe_get_option( 'skip_welcome', false ) ) @@ -77,7 +84,7 @@ public function hooks() { } add_action( 'admin_init', [ $this, 'maybe_redirect' ], 10, 0 ); - add_action( 'admin_menu', [ $this, 'register_page' ], 100, 0 ); // come in after the default page is registered + add_action( 'admin_menu', [ $this, 'register_page' ], 100, 0 ); // Come in after the default page is registered. add_action( 'update_plugin_complete_actions', [ $this, 'update_complete_actions' ], 15, 2 ); add_action( 'update_bulk_plugins_complete_actions', [ $this, 'update_complete_actions' ], 15, 2 ); @@ -123,7 +130,7 @@ public function update_complete_actions( $actions, $plugin ) { */ public function maybe_redirect() { if ( ! empty( $_POST ) ) { - return; // don't interrupt anything the user's trying to do + return; // Don't interrupt anything the user's trying to do. } if ( ! is_admin() || defined( 'DOING_AJAX' ) ) { @@ -131,18 +138,46 @@ public function maybe_redirect() { } if ( defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) { - return; // probably the plugin update/install iframe + return; // Probably the plugin update/install iframe. } if ( isset( $_GET[ $this->welcome_slug ] ) || isset( $_GET[ $this->update_slug ] ) ) { - return; // no infinite redirects + return; // No infinite redirects. } if ( isset( $_GET['tribe-skip-welcome'] ) ) { - return; // a way to skip these checks and + return; // A way to skip these checks and. } - // bail if we aren't activating a plugin + if ( ! $this->showed_update_message_for_current_version() && ! $this->is_new_install() ) { + $page = tribe_get_request_var( 'page' ); + if ( empty( $page ) ) { + return; + } + + $match_page = str_replace( 'tribe_events_page_', '', $this->args['admin_page'] ); + + if ( $page !== $match_page ) { + return; + } + + /** + * Filters whether we should disable the update page redirect. + * + * @since 5.0.0 + * + * @param $bypass bool + */ + $bypass_update_page = apply_filters( 'tec_admin_update_page_bypass', false, $this ); + + if ( $bypass_update_page ) { + return; + } + + $this->redirect_to_update_page(); + } + + // Bail if we aren't activating a plugin. if ( ! get_transient( $this->args['activation_transient'] ) ) { return; } @@ -153,10 +188,6 @@ public function maybe_redirect() { return; } - if ( $this->showed_update_message_for_current_version() ) { - return; - } - // the redirect might be intercepted by another plugin, but // we'll go ahead and mark it as viewed right now, just in case // we end up in a redirect loop @@ -171,9 +202,11 @@ public function maybe_redirect() { /** * Have we shown the welcome/update message for the current version? * + * @since 5.0.0 Turned this method public. + * * @return bool */ - protected function showed_update_message_for_current_version() { + public function showed_update_message_for_current_version() { $message_version_displayed = Tribe__Settings_Manager::get_option( 'last-update-message-' . $this->args['slug'] ); if ( empty( $message_version_displayed ) ) { @@ -240,12 +273,11 @@ protected function redirect_to_update_page() { */ protected function get_message_page_url( $slug ) { $settings = Tribe__Settings::instance(); - // get the base settings page url - $url = apply_filters( - 'tribe_settings_url', - add_query_arg( 'page', $settings->adminSlug, admin_url( 'edit.php' ) ) - ); + + $url = ! empty( $this->args['admin_url'] ) ? $this->args['admin_url'] : $settings->get_url(); + $url = esc_url_raw( add_query_arg( $slug, 1, $url ) ); + return $url; } @@ -263,7 +295,7 @@ public function register_page() { $this->disable_default_settings_page(); add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] ); - add_action( Tribe__Settings::instance()->admin_page, [ $this, 'display_page' ] ); + add_action( $this->args['admin_page'], [ $this, 'display_page' ] ); } /** @@ -283,7 +315,7 @@ public function admin_body_class( $classes ) { * in the Events > Settings slot instead, for this request only). */ protected function disable_default_settings_page() { - remove_action( Tribe__Settings::instance()->admin_page, [ Tribe__Settings::instance(), 'generatePage' ] ); + remove_action( $this->args['admin_page'], [ Tribe__Settings::instance(), 'generatePage' ] ); } /** diff --git a/tribe-common/src/Tribe/Admin/Conditional_Content/Black_Friday.php b/tribe-common/src/Tribe/Admin/Conditional_Content/Black_Friday.php new file mode 100644 index 0000000000..eb729f67d3 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Conditional_Content/Black_Friday.php @@ -0,0 +1,84 @@ +modify( '-3 days' ); + + return $date; + } + + /** + * Replace the opening markup for the general settings info box. + * + * @since 4.14.7 + * @return void + */ + public function add_conditional_content( $fields ) { + // Check if the content should currently be displayed. + if( ! $this->should_display() ) { + return $fields; + } + + // Set up template variables. + $images_dir = \Tribe__Main::instance()->plugin_url . 'src/resources/images/'; + $template_args = [ + 'branding_logo' => $images_dir . 'logo/tec-brand.svg', + 'background_image' => $images_dir . 'marketing/bf-promo.png', + 'button_link' => 'https://evnt.is/1aqi', + ]; + + // Get the Black Friday promo content. + $content = $this->get_template()->template( 'conditional_content/black-friday', $template_args, false ); + + // Replace starting info box markup. + $fields['info-start']['html'] .= $content; + + return $fields; + } +} diff --git a/tribe-common/src/Tribe/Admin/Conditional_Content/Datetime_Conditional_Abstract.php b/tribe-common/src/Tribe/Admin/Conditional_Content/Datetime_Conditional_Abstract.php new file mode 100644 index 0000000000..374698f362 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Conditional_Content/Datetime_Conditional_Abstract.php @@ -0,0 +1,150 @@ +start_date, 'UTC' ); + $date = $date->setTime( $this->start_time, 0 ); + + /** + * Allow filtering of the start date for testing. + * + * @since 4.14.7 + * @param \DateTime $date - Unix timestamp for start date + * @param object $this + */ + $date = apply_filters( "tec_admin_conditional_content_{$this->slug}_start_date", $date, $this ); + + return $date; + } + + /** + * Unix datetime for content end. + * + * @since 4.14.7 + * @return int - Unix timestamp + */ + protected function get_end_time() { + $date = Dates::build_date_object( $this->end_date, 'UTC' ); + $date = $date->setTime( $this->end_time, 0 ); + + /** + * Allow filtering of the end date for testing. + * + * @since 4.14.7 + * @param \DateTime $date - Unix timestamp for end date + * @param object $this + */ + $date = apply_filters( "tec_admin_conditional_content_{$this->slug}_end_date", $date, $this ); + + return $date; + } + + /** + * Whether the content should display. + * + * @since 4.14.7 + * @return boolean - Whether the content should display + */ + protected function should_display() { + $now = Dates::build_date_object( 'now', 'UTC' ); + $notice_start = $this->get_start_time(); + $notice_end = $this->get_end_time(); + $display = $notice_start <= $now && $now < $notice_end; + + /** + * Allow filtering whether the content should display. + * + * @since 4.14.7 + * @param bool $should_display - whether the content should display + * @param object $this - the conditional content object + */ + $should_display = apply_filters( "tec_admin_conditional_content_{$this->slug}_should_display", $display, $this ); + + return $should_display; + } + + /** + * Gets the template instance used to setup the rendering of the page. + * + * @since 4.14.7 + * + * @return \Tribe__Template + */ + public function get_template() { + if ( empty( $this->template ) ) { + $this->template = new \Tribe__Template(); + $this->template->set_template_origin( \Tribe__Main::instance() ); + $this->template->set_template_folder( 'src/admin-views' ); + $this->template->set_template_context_extract( true ); + $this->template->set_template_folder_lookup( false ); + } + + return $this->template; + } +} diff --git a/tribe-common/src/Tribe/Admin/Conditional_Content/End_Of_Year_Sale.php b/tribe-common/src/Tribe/Admin/Conditional_Content/End_Of_Year_Sale.php new file mode 100644 index 0000000000..f2a51e14d4 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Conditional_Content/End_Of_Year_Sale.php @@ -0,0 +1,85 @@ +should_display() ) { + return $fields; + } + + // Set up template variables. + $images_dir = \Tribe__Main::instance()->plugin_url . 'src/resources/images/'; + $template_args = [ + 'branding_logo' => $images_dir . 'logo/tec-brand.svg', + 'background_image' => $images_dir . 'marketing/eoy-sale-promo.png', + 'button_link' => 'https://evnt.is/1a-x', + ]; + + // Get the Black Friday promo content. + $content = $this->get_template()->template( 'conditional_content/end-of-year-sale', $template_args, false ); + + // Replace starting info box markup. + $fields['info-start']['html'] .= $content; + + return $fields; + } + + /** + * Unix time for notice end. + * + * @since 4.14.9 + * + * @return int $end_time The date & time the notice should stop displaying, as a Unix timestamp. + */ + public function get_end_time() { + $date = parent::get_end_time(); + $date = $date->setTime( 23, 59 ); + + return $date; + } +} diff --git a/tribe-common/src/Tribe/Admin/Conditional_Content/Service_Provider.php b/tribe-common/src/Tribe/Admin/Conditional_Content/Service_Provider.php new file mode 100644 index 0000000000..64506e1968 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Conditional_Content/Service_Provider.php @@ -0,0 +1,49 @@ +container->singleton( Black_Friday::class, Black_Friday::class, [ 'hook' ] ); + // EOY Sale disabled for 2022 + // $this->container->singleton( End_Of_Year_Sale::class, End_Of_Year_Sale::class, [ 'hook' ] ); + $this->hooks(); + } + + /** + * Set up hooks for classes. + * + * @since 4.14.7 + */ + protected function hooks() { + add_action( 'tribe_plugins_loaded', [ $this, 'plugins_loaded' ] ); + } + + /** + * Setup for things that require plugins loaded first. + * + * @since 4.14.7 + */ + public function plugins_loaded() { + $this->container->make( Black_Friday::class ); + // EOY Sale disabled for 2022 + // $this->container->make( End_Of_Year_Sale::class ); + } +} diff --git a/tribe-common/src/Tribe/Admin/Help_Page.php b/tribe-common/src/Tribe/Admin/Help_Page.php index b66b278c01..b0c2501709 100644 --- a/tribe-common/src/Tribe/Admin/Help_Page.php +++ b/tribe-common/src/Tribe/Admin/Help_Page.php @@ -21,6 +21,33 @@ public static function instance() { return tribe( static::class ); } + /** + * Set up hooks. + * + * @since 4.15.0 + */ + public function hook() { + add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] ); + } + + /** + * Hooked to admin_body_class to add a class for help page. + * + * @since 4.15.0 + * + * @param string $classes A space separated string of classes to be added to body. + * + * @return string $classes A space separated string of classes to be added to body. + */ + public function admin_body_class( $classes ) { + if ( ! $this->is_current_page() ) { + return $classes; + } + + $classes .= ' tribe-help tec-help'; + return $classes; + } + /** * Checks if the current page is the Help one * @@ -29,7 +56,14 @@ public static function instance() { * @return bool */ public function is_current_page() { - return Tribe__Admin__Helpers::instance()->is_screen( 'tribe_events_page_tribe-help' ) || Tribe__Admin__Helpers::instance()->is_screen( 'settings_page_tribe-common-help-network' ); + global $current_screen; + + $help_pages = [ + 'tribe_events_page_tec-events-help', + 'tickets_page_tec-tickets-help', + ]; + + return in_array( $current_screen->id, $help_pages ); } /** @@ -52,10 +86,13 @@ public function register_assets() { 'localize' => [ 'name' => 'tribe_system_info', 'data' => [ - 'sysinfo_optin_nonce' => wp_create_nonce( 'sysinfo_optin_nonce' ), - 'clipboard_btn_text' => __( 'Copy to clipboard', 'tribe-common' ), - 'clipboard_copied_text' => __( 'System info copied', 'tribe-common' ), - 'clipboard_fail_text' => __( 'Press "Cmd + C" to copy', 'tribe-common' ), + 'sysinfo_optin_nonce' => wp_create_nonce( 'sysinfo_optin_nonce' ), + 'clipboard_btn_text' => _x( 'Copy to clipboard', 'Copy to clipboard button text.', 'tribe-common' ), + 'clipboard_copied_text' => _x( 'System info copied', 'Copy to clipboard success message', 'tribe-common' ), + 'clipboard_fail_text' => _x( 'Press "Cmd + C" to copy', 'Copy to clipboard instructions', 'tribe-common' ), + 'sysinfo_error_message_text' => _x( 'Something has gone wrong!', 'Default error message for system info optin', 'tribe-common' ), + 'sysinfo_error_code_text' => _x( 'Code:', 'Error code label for system info optin', 'tribe-common'), + 'sysinfo_error_status_text' => _x( 'Status:', 'Error status label for system info optin', 'tribe-common'), ], ], ] @@ -913,4 +950,238 @@ public function print_plugin_box( $plugin ) { __( 'Can I have more than one calendar?', 'tribe-common' ), + 'answer' => __( 'No, but you can use event categories or tags to display certain events like having...', 'tribe-common' ), + 'link' => 'https://evnt.is/1arh', + ], + [ + 'question' => __( 'What do I get with Events Calendar Pro?', 'tribe-common' ), + 'answer' => __( 'Events Calendar Pro runs alongside The Events Calendar and enhances...' ), + 'link' => 'https://evnt.is/1arj', + ], + [ + 'question' => __( 'How do I sell tickets to events?', 'tribe-common' ), + 'answer' => __( 'Use our free Event Tickets plugin to get started with tickets and RSVPs.', 'tribe-common' ), + 'link' => 'https://evnt.is/1ark', + ], + [ + 'question' => __( 'Where can I find a list of available shortcodes?', 'tribe-common' ), + 'answer' => __( 'Our plugins include many shortcodes that do everything from embedding the calendar...', 'tribe-common' ), + 'link' => 'https://evnt.is/1arl', + ], + ] ); + + return $faqs; + } + + /** + * Defines calendar extensions and displays them in the UI. + * + * @since 4.14.2 + * + * @return array of extensions which are displayed on the calendar and community tab of the in-app help page. + */ + public function get_calendar_extensions() { + $extensions = apply_filters( 'tec_help_calendar_extensions', [ + [ + 'title' => __( 'Calendar widget areas', 'tribe-common' ), + 'description' => __( 'This extension creates a useful variety of WordPress widget areas (a.k.a. sidebars).', 'tribe-common' ), + 'link' => 'https://evnt.is/1arc', + 'product-slug' => 'the-events-calendar', + ], + [ + 'title' => __( 'Event block patterns', 'tribe-common' ), + 'description' => __( 'This extension adds a set of block patterns for events to the WordPress block editor.', 'tribe-common' ), + 'link' => 'https://evnt.is/1ard', + 'product-slug' => 'the-events-calendar', + ], + [ + 'title' => __( 'Alternative photo view', 'tribe-common' ), + 'description' => __( 'This extension replaces photo view with a tiled grid of cards featuring event images.', 'tribe-common' ), + 'link' => 'https://evnt.is/1are', + 'product-slug' => 'events-calendar-pro', + ], + [ + 'title' => __( 'The Events Calendar Tweaks', 'tribe-common' ), + 'description' => __( 'This extension is a collection of tweaks and snippets for The Events Calendar.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arg', + 'product-slug' => 'the-events-calendar', + ], + ] ); + + return $extensions; + } + + /** + * Defines calendar products. + * + * @since 4.14.2 + * + * @return array of products which are displayed on the calendar tab of the in-app help page. + */ + public function get_calendar_products() { + $calendar_products = apply_filters( 'tec_help_calendar_products', [ + 'events-calendar-pro', + 'tribe-filterbar', + 'event-aggregator', + 'events-virtual', + ] ); + + return $calendar_products; + } + + /** + * Defines ticketing frequently asked questions and displays them in the UI. + * + * @since 4.14.2 + * + * @return array of FAQs which are displayed on the ticketing tab of the in-app help page. + */ + public function get_ticketing_faqs() { + $faqs = apply_filters( 'tec_help_ticketing_faqs', [ + [ + 'question' => __( 'How Do I create events with Tickets or RSVP’s?', 'tribe-common' ), + 'answer' => __( 'We’ve put together a video tutorial showing how to create events with Tickets using our plugins. Click on the link in the link in the title to learn more.', 'tribe-common' ), + 'link' => 'https://evnt.is/1art', + ], + [ + 'question' => __( 'How Do I Set Up E-Commerce Plugins for Selling Tickets?', 'tribe-common' ), + 'answer' => __( 'You can sell tickets using our built-in e-commerce option, or upgrade to Event Tickets Plus to use ecommerce plugins such as WooCommerce.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arq', + ], + [ + 'question' => __( 'Can I have a seating chart associated with my tickets?', 'tribe-common' ), + 'answer' => __( 'Yes! You can easily accomplish this task using the stock options and multiple ticket types available with Event Tickets.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arr', + ], + [ + 'question' => __( 'How do I process refunds for tickets?', 'tribe-common' ), + 'answer' => __( 'When it comes to paid tickets, these orders can be refunded through the e-commerce platform in use.', 'tribe-common' ), + 'link' => 'https://evnt.is/1ars', + ], + ] ); + + return $faqs; + } + + /** + * Defines ticketing extensions and displays them in the UI. + * + * @since 4.14.2 + * + * @return array of extensions which are displayed on the ticketing tab of the in-app help page. + */ + public function get_ticketing_extensions() { + $extensions = apply_filters( 'tec_help_ticketing_extensions', [ + [ + 'title' => __( 'Ticket Email Settings', 'tribe-common' ), + 'description' => __( 'Adds a new settings panel in Events > Settings that gives more control over the ticket and rsvp emails that are sent to attendees after registration.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arx', + 'product-slug' => 'event-tickets', + ], + [ + 'title' => __( 'Per Event Check In API', 'tribe-common' ), + 'description' => __( 'This extension shows a meta box with an API key on each Event with Ticket/RSVP.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arw', + 'product-slug' => 'event-tickets', + ], + [ + 'title' => __( 'Add Event & Attendee Info to WooCommerce Order Details', 'tribe-common' ), + 'description' => __( 'Displays the information collected by “attendee meta fields” in the WooCommerce order screens as well.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arv', + 'product-slug' => 'event-tickets', + ], + [ + 'title' => __( 'Organizer Notification Email', 'tribe-common' ), + 'description' => __( 'This extension will send an email to event organizers whenever a user registers for their event.', 'tribe-common' ), + 'link' => 'https://evnt.is/1aru', + 'product-slug' => 'event-tickets', + ], + ] ); + + return $extensions; + } + + /** + * Defines ticketing products. + * + * @since 4.14.2 + * + * @return array of products which are displayed on the ticketing tab of the in-app help page. + */ + public function get_ticketing_products() { + $ticketing_products = apply_filters( 'tec_help_ticketing_products', [ + 'event-tickets', + 'event-tickets-plus', + 'tribe-eventbrite', + 'promoter', + ] ); + + return $ticketing_products; + } + + /** + * Defines community extensions and displays them in the UI. + * + * @since 4.14.2 + * + * @return array of extensions which are displayed on the community tab of the in-app help page. + */ + public function get_community_extensions() { + $extensions = apply_filters( 'tec_help_ticketing_extensions', [ + [ + 'title' => __( 'Add Cost Currency Symbol', 'tribe-common' ), + 'description' => __( 'This extension allows you to set default currency symbols for your users to choose from instead of having a plain text field.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arn', + 'product-slug' => 'community-events', + ], + [ + 'title' => __( 'Add Google Maps Display and Link Options', 'tribe-common' ), + 'description' => __( 'This extension adds the “Show Google Maps” and “Show Google Maps Link” checkboxes when creating a new Venue.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arm', + 'product-slug' => 'community-events', + ], + [ + 'title' => __( 'Hide Others’ Organizers and Venues', 'tribe-common' ), + 'description' => __( 'This extension allows you to hide the Organizers and Venues that a visitor has not created from the Community Events submission form.', 'tribe-common' ), + 'link' => 'https://evnt.is/1aro', + 'product-slug' => 'community-events', + ], + [ + 'title' => __( 'Display Custom HTML', 'tribe-common' ), + 'description' => __( 'This extension allows you to add custom HTML content to the top of the Community Events submission form.', 'tribe-common' ), + 'link' => 'https://evnt.is/1arp', + 'product-slug' => 'community-events', + ], + ] ); + + return $extensions; + } + + /** + * Defines community products. + * + * @since 4.14.2 + * + * @return array of products which are displayed on the community tab of the in-app help page. + */ + public function get_community_products() { + $community_products = apply_filters( 'tec_help_ticketing_products', [ + 'events-community', + 'events-community-tickets', + ] ); + + return $community_products; + } } diff --git a/tribe-common/src/Tribe/Admin/Helpers.php b/tribe-common/src/Tribe/Admin/Helpers.php index 21fd6817e5..7f1d832b29 100644 --- a/tribe-common/src/Tribe/Admin/Helpers.php +++ b/tribe-common/src/Tribe/Admin/Helpers.php @@ -119,27 +119,32 @@ public function is_screen( $id = null ) { return false; } - // Avoid Notices by checking the object type of WP_Screen + // Avoid Notices by checking the object type of WP_Screen. if ( ! $this->is_wp_screen() ) { return false; } - // Match any screen from Tribe + // Match any screen from Tribe. if ( is_null( $id ) && false !== strpos( $current_screen->id, 'tribe' ) ) { return true; } + // Match any screen from TEC. + if ( is_null( $id ) && false !== strpos( $current_screen->id, 'tec' ) ) { + return true; + } + // Match any of the pages set if ( ! is_scalar( $id ) && in_array( $current_screen->id, (array) $id ) ) { return true; } - // Match a specific page + // Match a specific page. if ( $current_screen->id === $id ) { return true; } - // Match any post type page in the supported post types + // Match any post type page in the supported post types. $defaults = apply_filters( 'tribe_is_post_type_screen_post_types', Tribe__Main::get_post_types() ); if ( in_array( $current_screen->post_type, $defaults ) ) { return true; diff --git a/tribe-common/src/Tribe/Admin/Notice/Date_Based.php b/tribe-common/src/Tribe/Admin/Notice/Date_Based.php new file mode 100644 index 0000000000..d5b47262bc --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Notice/Date_Based.php @@ -0,0 +1,367 @@ +tec_is_active = $tribe_dependency->is_plugin_active( 'Tribe__Events__Main' ); + $this->et_is_active = $tribe_dependency->is_plugin_active( 'Tribe__Tickets__Main' ); + + $now = Dates::build_date_object( 'now', 'UTC' ); + $notice_start = $this->get_start_time(); + $notice_end = $this->get_end_time(); + $extension_date = $this->get_extension_time(); + + // If we have an extension date defined. + if ( ! empty( $this->get_extension_time() ) ) { + // If the sale has started and + if ( + $notice_start <= $now + && $notice_end < $now + && $now < $extension_date + ) { + add_filter( "tribe_{$this->slug}_notice_end_date", function() { + return $this->get_extension_time(); + }); + } + } + + $this->hook(); + } + + /** + * Register the various Marketing notices. + * + * @since 4.14.2 + */ + public function hook() { + $this->hook_notice(); + } + + /** + * Register the notice. + * + * @since 4.14.2 + */ + public function hook_notice() { + tribe_notice( + $this->slug, + [ $this, "display_notice" ], + [ + 'type' => 'tribe-banner', + 'dismiss' => 1, + 'priority' => -1, + 'wrap' => false, + ], + [ $this, "should_display" ] + ); + } + + /** + * HTML for the notice. + * + * @since 4.14.2 + * + * @return string The HTML string to be displayed. + */ + abstract function display_notice(); + + /** + * Function to get and filter the screens the notice is displayed on. + * + * @since 4.15.4 + * + * @return array List of allowed screens. + */ + public function get_screens() { + $screens = $this->screens; + + /** + * Allows filtering of the screens for all date-based notices. + * + * @since 4.15.4 + * + * @param array $screens The current list of allowed screens. + * @param string $slug The slug for the current notice. + * + * @return array $screens The modified list of allowed screens. + */ + $screens = apply_filters( + 'tec_date_based_notice_get_screens', + $screens, + $this->slug + ); + + /** + * Allows filtering of the screens for a specific date-based notice. + * + * @since 4.15.4 + * + * @param array $screens The current list of allowed screens. + * + * @return array $screens The modified list of allowed screens. + */ + $screens = apply_filters( + "tec_date_based_notice_get_screens_{$this->slug}", + $screens + ); + + return $screens; + } + + /** + * Whether the notice should display. + * + * @since 4.14.2 + * + * @return boolean $should_display Whether the notice should display or not. + */ + public function should_display() { + // If upsells have been manually hidden, respect that. + if ( tec_should_hide_upsell() ) { + return false; + } + + $current_screen = get_current_screen(); + + $screens = $this->get_screens(); + + // If not a valid screen, don't display. + if ( empty( $current_screen->id ) || ! in_array( $current_screen->id, $screens, true ) ) { + return false; + } + + $now = Dates::build_date_object( 'now', 'UTC' ); + $notice_start = $this->get_start_time(); + $notice_end = $this->get_end_time(); + + $should_display = $notice_start <= $now && $now < $notice_end; + + + /** + * Allow filtering of whether the notice should display. + * + * @since 4.14.2 + * + * @param boolean $should_display Whether the notice should display. + * @param Tribe__Admin__Notice_Date_Based $notice The notice object. + */ + return apply_filters( "tribe_{$this->slug}_notice_should_display", $should_display, $this ); + } + + /** + * Unix time for notice start. + * + * @since 4.14.2 + * + * @return int $start_time The date & time the notice should start displaying, as a Unix timestamp. + */ + public function get_start_time() { + $date = Dates::build_date_object( $this->start_date, 'UTC' ); + $date = $date->setTime( $this->start_time, 0 ); + + /** + * Allow filtering of the start date DateTime object, + * to allow for things like "the day before" ( $date->modify( '-1 day' ) ) and such. + * + * @since 4.14.2 + * + * @param \DateTime $date Date object for the notice start. + */ + $date = apply_filters( "tribe_{$this->slug}_notice_start_date", $date, $this ); + + return $date; + } + + /** + * Unix time for notice end. + * + * @since 4.14.2 + * + * @return int $end_time The date & time the notice should stop displaying, or shift to the extension datetime as a Unix timestamp. + */ + public function get_end_time() { + $date = Dates::build_date_object( $this->end_date, 'UTC' ); + $date = $date->setTime( $this->end_time, 0 ); + + /** + * Allow filtering of the end date DateTime object, + * to allow for things like "the day after" ( $date->modify( '+1 day' ) ) and such. + * + * @since 4.14.2 + * + * @param \DateTime $date Date object for the notice end. + */ + $date = apply_filters( "tribe_{$this->slug}_notice_end_date", $date, $this ); + + return $date; + } + + + + /** + * Unix time for notice extension end. + * + * @since 4.15.4 + * + * @return int $end_time The date & time the notice should stop displaying, as a Unix timestamp. + */ + public function get_extension_time() { + $date = Dates::build_date_object( $this->extension_date, 'UTC' ); + $date = $date->setTime( $this->extension_time, 0 ); + + /** + * Allow filtering of the extension date DateTime object, + * to allow for things like "the day after" ( $date->modify( '+1 day' ) ) and such. + * + * @since 4.14.2 + * + * @param \DateTime $date Date object for the notice end. + */ + $date = apply_filters( "tribe_{$this->slug}_notice_extension_date", $date, $this ); + + return $date; + } + + /** + * Gets the template instance used to setup the rendering of the page. + * + * @since 4.14.7 + * + * @return \Tribe__Template + */ + public function get_template() { + if ( empty( $this->template ) ) { + $this->template = new \Tribe__Template(); + $this->template->set_template_origin( \Tribe__Main::instance() ); + $this->template->set_template_folder( 'src/admin-views' ); + $this->template->set_template_context_extract( true ); + $this->template->set_template_folder_lookup( false ); + } + + return $this->template; + } +} diff --git a/tribe-common/src/Tribe/Admin/Notice/Marketing.php b/tribe-common/src/Tribe/Admin/Notice/Marketing.php index c4ea865516..add054cb85 100644 --- a/tribe-common/src/Tribe/Admin/Notice/Marketing.php +++ b/tribe-common/src/Tribe/Admin/Notice/Marketing.php @@ -1,4 +1,5 @@ plugin_url . 'src/resources/images/icons/sale-burst.svg'; $cta_url = 'https://evnt.is/bf' . date( 'Y' ); + $screens = [ + 'tribe_events_page_tribe-common', + 'tribe_events_page_tec-events-settings', + 'events_page_tribe-common', + 'toplevel_page_tribe-common', + ]; // If we are on the settings page or a welcome page, change the Black Friday URL. if ( ! empty( $current_screen->id ) - && ( - 'tribe_events_page_tribe-common' === $current_screen->id - || 'events_page_tribe-common' === $current_screen->id - || 'toplevel_page_tribe-common' === $current_screen->id - ) + && in_array( $current_screen->id, $screens ) ) { if ( isset( $_GET['welcome-message-the-events-calendar'] ) || isset( $_GET['welcome-message-event-tickets' ] ) ) { $cta_url .= 'welcome'; diff --git a/tribe-common/src/Tribe/Admin/Notice/Marketing/Black_Friday.php b/tribe-common/src/Tribe/Admin/Notice/Marketing/Black_Friday.php new file mode 100644 index 0000000000..15100faef5 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Notice/Marketing/Black_Friday.php @@ -0,0 +1,72 @@ +enqueue( [ 'tribe-common-admin' ] ); + + // Set up template variables. + $template_args = [ + 'icon_url' => \Tribe__Main::instance()->plugin_url . 'src/resources/images/icons/sale-burst.svg', + 'cta_url' => 'https://evnt.is/1aqi', + 'end_date' => $this->get_end_time()->format_i18n( 'F jS' ), + ]; + + // Get the Black Friday notice content. + $content = $this->get_template()->template( 'notices/tribe-bf-general', $template_args, false ); + + return $content; + } + + /** + * Unix time for notice start. + * + * @since 4.14.2 + * + * @return int $end_time The date & time the notice should start displaying, as a Unix timestamp. + */ + public function get_start_time() { + $date = parent::get_start_time(); + $date = $date->modify( '-3 days' ); + + return $date; + } +} diff --git a/tribe-common/src/Tribe/Admin/Notice/Marketing/End_Of_Year_Sale.php b/tribe-common/src/Tribe/Admin/Notice/Marketing/End_Of_Year_Sale.php new file mode 100644 index 0000000000..d2a0ff521a --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Notice/Marketing/End_Of_Year_Sale.php @@ -0,0 +1,66 @@ + \Tribe__Main::instance()->plugin_url . 'src/resources/images/marketing/eoy-sale-2021.svg', + 'cta_url' => 'https://evnt.is/1a-x', + ]; + + // Get the sale notice content. + $content = $this->get_template()->template( 'notices/end-of-year-sale', $template_args, false ); + + return $content; + } + + /** + * Unix time for notice end. + * + * @since 4.14.9 + * + * @return int $end_time The date & time the notice should stop displaying, as a Unix timestamp. + */ + public function get_end_time() { + $date = parent::get_end_time(); + $date = $date->setTime( 23, 59 ); + + return $date; + } +} diff --git a/tribe-common/src/Tribe/Admin/Notice/Marketing/Stellar_Sale.php b/tribe-common/src/Tribe/Admin/Notice/Marketing/Stellar_Sale.php new file mode 100644 index 0000000000..484a417e2c --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Notice/Marketing/Stellar_Sale.php @@ -0,0 +1,77 @@ +enqueue( [ 'tribe-common-admin' ] ); + + // Used in the template. + $cta_url = 'https://evnt.is/1aqi'; + $icon_url = \Tribe__Main::instance()->plugin_url . 'src/resources/images/marketing/circles.svg'; + $icon_classes = [ 'tribe-common-c-svgicon--circles' ]; + $end_date = $this->get_end_time(); + + ob_start(); + + include \Tribe__Main::instance()->plugin_path . 'src/admin-views/notices/tribe-stellar-sale.php'; + + return ob_get_clean(); + } +} diff --git a/tribe-common/src/Tribe/Admin/Notice/Plugin_Download.php b/tribe-common/src/Tribe/Admin/Notice/Plugin_Download.php index c17e6c37c1..8deb0a0108 100644 --- a/tribe-common/src/Tribe/Admin/Notice/Plugin_Download.php +++ b/tribe-common/src/Tribe/Admin/Notice/Plugin_Download.php @@ -119,7 +119,7 @@ public function show_inactive_plugins_alert() { $plugin_names_clean_text = wp_kses( $this->implode_with_grammar( $plugin_name ), $allowed_html ); $req_plugin_names_clean_text = wp_kses( $this->implode_with_grammar( $req_plugins ), $allowed_html ); - $notice_html_content = '

' . esc_html__( 'To begin using %2$s, please install and activate %3$s.', 'tribe-common' ) . '

'; + $notice_html_content = '

' . esc_html__( 'To begin using %2$s, please install (or upgrade) and activate %3$s.', 'tribe-common' ) . '

'; $read_more_link = '' . esc_html__( 'Read more', 'tribe-common' ) . '.'; $pue_notice_text = esc_html__( 'There’s a new version of %1$s available, but your license is expired. You’ll need to renew your license to get access to the latest version. If you plan to continue using your current version of the plugin(s), be sure to use a compatible version of The Events Calendar. %2$s', 'tribe-common' ); diff --git a/tribe-common/src/Tribe/Admin/Notice/Service_Provider.php b/tribe-common/src/Tribe/Admin/Notice/Service_Provider.php new file mode 100644 index 0000000000..84f5d88af8 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Notice/Service_Provider.php @@ -0,0 +1,63 @@ +hooks(); + } + + /** + * Set up hooks for classes. + * + * @since 4.14.2 + */ + private function hooks() { + add_action( 'tribe_plugins_loaded', [ $this, 'plugins_loaded'] ); + } + + /** + * Setup for things that require plugins loaded first. + * + * @since 4.14.2 + */ + public function plugins_loaded() { + tribe( 'pue.notices' ); + tribe( 'admin.notice.php.version' ); + tribe( WP_Version::class ); + + if ( defined( 'TRIBE_HIDE_MARKETING_NOTICES' ) ) { + return; + } + + tribe( Marketing\Stellar_Sale::class ); + tribe( Marketing\Black_Friday::class ); + // EOY Sale disabled for 2022 + // tribe( Marketing\End_Of_Year_Sale::class ); + } +} diff --git a/tribe-common/src/Tribe/Admin/Notice/WP_Version.php b/tribe-common/src/Tribe/Admin/Notice/WP_Version.php index 5a61a18f71..8c3137957a 100644 --- a/tribe-common/src/Tribe/Admin/Notice/WP_Version.php +++ b/tribe-common/src/Tribe/Admin/Notice/WP_Version.php @@ -1,7 +1,6 @@ 'tec-admin-notices', + ] ); } @@ -137,10 +140,10 @@ public function hook() { } /** - * This will allow the user to Dimiss the Notice using JS. + * This will allow the user to Dismiss the Notice using JS. * * We will dismiss the notice without checking to see if the slug was already - * registered (via a call to exists()) for the reason that, during a dismiss + * registered (via a call to exists()) for the reason that, during dismissal * ajax request, some valid notices may not have been registered yet. * * @since 4.3 @@ -152,9 +155,9 @@ public function maybe_dismiss() { wp_send_json( false ); } - $slug = sanitize_title_with_dashes( $_GET[ self::$meta_key ] ); + $slug = sanitize_key( $_GET[ self::$meta_key ] ); - // Send a JSON answer with the status of dimissal + // Send a JSON answer with the status of dismissal wp_send_json( $this->dismiss( $slug ) ); } @@ -188,12 +191,33 @@ public function __call( $name, $arguments ) { $content = $notice->content; $wrap = isset( $notice->wrap ) ? $notice->wrap : false; + if ( is_array( $content ) && isset( $content[0] ) && $content[0] instanceof __PHP_Incomplete_Class ) { + // From a class that no longer exists (e.g. the plugin is not active), clean and bail. + $this->remove( $slug ); + $this->remove_transient( $slug ); + + return false; + } + if ( is_callable( $content ) ) { $content = call_user_func_array( $content, [ $notice ] ); } - // Return the rendered HTML - return $this->render( $slug, $content, false, $wrap ); + if ( empty( $content ) ) { + // There is nothing to render, let's avoid the empty notice frame. + return false; + } + + tribe_asset_enqueue_group( 'tec-admin-notices' ); + + // Return the rendered HTML. + $html = $this->render( $slug, $content, false, $wrap ); + + // Remove the notice and the transient (if any) since it's been rendered. + $this->remove( $slug ); + $this->remove_transient( $slug ); + + return $html; } return false; @@ -236,6 +260,10 @@ public function render( $slug, $content = null, $return = true, $wrap = false ) $classes[] = 'is-dismissible'; } + if ( $notice->inline ) { + $classes[] = 'inline'; + } + // Prevents Empty Notices if ( empty( $content ) ) { return false; @@ -246,6 +274,7 @@ public function render( $slug, $content = null, $return = true, $wrap = false ) } $html = sprintf( '
%s
', implode( ' ', $classes ), $notice->slug, $content ); + tribe_asset_enqueue_group( 'tec-admin-notices' ); if ( ! $return ) { echo $html; @@ -514,7 +543,7 @@ public function undismiss_for_all( $slug ) { */ public function register( $slug, $callback, $arguments = [], $active_callback = null ) { // Prevent weird stuff here - $slug = sanitize_title_with_dashes( $slug ); + $slug = sanitize_key( $slug ); $defaults = [ 'callback' => null, @@ -523,6 +552,7 @@ public function register( $slug, $callback, $arguments = [], $active_callback = 'priority' => 10, 'expire' => false, 'dismiss' => false, + 'inline' => false, 'recurring' => false, 'recurring_interval' => null, 'type' => 'error', @@ -546,9 +576,15 @@ public function register( $slug, $callback, $arguments = [], $active_callback = // Clean these $notice->priority = absint( $notice->priority ); $notice->expire = (bool) $notice->expire; - $notice->dismiss = (bool) $notice->dismiss; $notice->recurring = (bool) $notice->recurring; + if ( ! is_callable( $notice->dismiss ) ) { + $notice->dismiss = (bool) $notice->dismiss; + } + if ( ! is_callable( $notice->inline ) ) { + $notice->inline = (bool) $notice->inline; + } + // Set the Notice on the array of notices $this->notices[ $slug ] = $notice; @@ -616,18 +652,29 @@ public function remove( $slug ) { * * @param string $slug * - * @return array|null + * @return object|array|null */ public function get( $slug = null ) { - // Prevent weird stuff here - $slug = sanitize_title_with_dashes( $slug ); - if ( is_null( $slug ) ) { return $this->notices; } + // Prevent weird stuff here + $slug = sanitize_key( $slug ); + if ( ! empty( $this->notices[ $slug ] ) ) { - return $this->notices[ $slug ]; + // I want to avoid modifying the registered value. + $notice = $this->notices[ $slug ]; + + if ( is_callable( $notice->inline ) ) { + $notice->inline = call_user_func( $notice->inline, $notice ); + } + + if ( is_callable( $notice->dismiss ) ) { + $notice->dismiss = call_user_func( $notice->dismiss, $notice ); + } + + return $notice; } return null; diff --git a/tribe-common/src/Tribe/Admin/Pages.php b/tribe-common/src/Tribe/Admin/Pages.php new file mode 100644 index 0000000000..3a05e4f847 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Pages.php @@ -0,0 +1,309 @@ + + */ + private $pages = []; + + /** + * Get registered pages. + * + * @since 4.15.0 + * + * @return array $pages { + * Array containing the registered pages. + * + * @type array $page_id { + * @type string id Id to reference the page. + * @type array title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string path Path for this page, full path in app context; ex /analytics/report + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * @type int order Navigation item order. + * @type callable callback The function to be called to output the content for the page. + * } + * } + */ + public function get_pages() { + /** + * Filters the list of registered TEC admin pages. + * + * @since 4.15.0 + * + * @param array $pages { + * Array containing the registered pages to be filtered + * + * @type array $page_id { + * @type string id Id to reference the page. + * @type array title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string path Path for this page, full path in app context; ex /analytics/report + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * @type int order Navigation item order. + * @type callable callback The function to be called to output the content for the page. + * } + * } + */ + $pages = apply_filters( 'tec_admin_pages', $this->pages ); + + return $pages; + } + + /** + * Adds a page to `tec-admin`. + * + * @since 4.15.0 + * + * @param array $options { + * Array describing the page. + * + * @type string id Id to reference the page. + * @type string title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string path Path for this page, full path in app context; ex /analytics/report + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * @type int order Navigation item order. + * @type callable callback The function to be called to output the content for the page. + * } + * + * @return string $page The resulting page's hook_suffix. + * + */ + public function register_page( $options = [] ) { + $defaults = [ + 'id' => null, + 'parent' => null, + 'title' => '', + 'capability' => self::get_capability(), + 'path' => '', + 'icon' => '', + 'position' => null, + 'callback' => [ __CLASS__, 'render_page' ], + ]; + + $options = wp_parse_args( $options, $defaults ); + + if ( is_null( $options['parent'] ) ) { + $page = add_menu_page( + $options['title'], + $options['title'], + $options['capability'], + $options['path'], + $options['callback'], + $options['icon'], + $options['position'] + ); + } else { + $page = add_submenu_page( + $options['parent'], + $options['title'], + $options['title'], + $options['capability'], + $options['path'], + $options['callback'] + ); + } + + $this->connect_page( $options ); + + return $page; + } + + /** + * Get the current page. + * + * @since 4.15.0 + * + * @return string|boolean Current page or false if not registered with this controller. + */ + public function get_current_page() { + if ( is_null( $this->current_page ) ) { + $this->determine_current_page(); + } + + return $this->current_page; + } + + /** + * Determine the current page. + * + * @since 4.15.0 + * + * @return string|boolean Current page or false if not registered with this controller. + */ + public function determine_current_page() { + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + + if ( is_null( $current_screen ) ) { + $this->current_page = tribe_get_request_var( 'page' ); + return $this->current_page; + } + + $this->current_page = $current_screen->id; + + return $this->current_page; + } + + /** + * Connect an existing page to wp-admin. + * + * @since 4.15.0 + * + * @param array $options { + * Array describing the page. + * + * @type string id Id to reference the page. + * @type string|array title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string path Path for this page. E.g. admin.php?page=wc-settings&tab=checkout + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * } + */ + public function connect_page( $options = [] ) { + if ( ! is_array( $options['title'] ) ) { + $options['title'] = array( $options['title'] ); + } + + /** + * Filter the options when connecting or registering a page. + * + * @param array $options { + * Array describing the page. + * + * @type string id Id to reference the page. + * @type string|array title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string screen_id The screen ID that represents the connected page. (Not required for registering). + * @type string path Path for this page. E.g. admin.php?page=wc-settings&tab=checkout + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * @type boolean js_page If this is a JS-powered page. + * } + */ + $options = apply_filters( 'tec_admin_pages_connect_page_options', $options ); + + $this->pages[ $options['id'] ] = $options; + } + + /** + * Get the capability. + * + * @param string $capability The capability required for a TEC page to be displayed to the user. + * + * @since 4.15.0 + * + * @return string The capability required for a TEC page to be displayed to the user. + */ + public static function get_capability( $capability = 'manage_options' ) { + /** + * Filters the default capability for Tribe admin pages. + * + * @param string $capability The capability required for a TEC page to be displayed to the user. + * + * @todo: We'll need to deprecate this one in favor of the one below. + */ + $capability = apply_filters( 'tribe_common_event_page_capability', $capability ); + + /** + * Filters the default capability for TEC admin pages. + * + * @param string $capability The capability required for a TEC page to be displayed to the user. + * + * @since 4.15.0 + */ + $capability = apply_filters( 'tec_admin_pages_capability', $capability ); + + return $capability; + } + + /** + * Define if is a `tec` admin page (registered). + * + * @since 4.15.0 + * + * @param string $page_id The ID of the page to check if is a `tec` admin page. + * + * @return boolean True if is a `tec` admin page, false otherwise. + */ + public function is_tec_page( $page_id = '' ) { + return in_array( $page_id, array_keys( $this->pages ), true ); + } + + /** + * Get pages with tabs. + * @since 4.15.0 + * + * @param array $pages The list of pages with tabs. + * @return array $pages The list of pages with tabs, filtered. + */ + public function get_pages_with_tabs( $pages = [] ) { + /** + * Filters the pages with tabs. + * + * @param array $pages Pages with tabs. + * + * @since 4.15.0 + */ + return apply_filters( + 'tec_admin_pages_with_tabs', + $pages + ); + } + + /** + * Check if the current page has tabs. + * + * @since 4.15.0 + * + * @param string $page The page slug. + * @return boolean True if the page has tabs, false otherwise. + */ + public function has_tabs( $page = '' ) { + if ( empty( $page ) ) { + $page = $this->get_current_page(); + } + + return in_array( $page, $this->get_pages_with_tabs() ); + } + + /** + * Generic page. + * + * @since 4.15.0 + */ + public static function render_page() { + ?> +
+ should_setup_pages() ) { + return; + } + + $page_title = esc_html__( 'Troubleshooting', 'tribe-common' ); + $menu_title = esc_html__( 'Troubleshooting', 'tribe-common' ); + + $capability = $this->get_required_capability(); + + $where = Tribe__Settings::instance()->get_parent_slug(); + + $this->admin_page = add_submenu_page( + $where, + $page_title, + $menu_title, + $capability, + static::MENU_SLUG, + [ + $this, + 'do_menu_page', + ] + ); + } + + /** + * Gets the required capability for the troubleshooting page. + * + * @since 4.14.2 + * + * @return string Which capability we required for the troubleshooting page. + */ + public function get_required_capability() { + /** + * Allows third party filtering of capability required to see the Troubleshooting page. + * + * @since 4.14.2 + * + * @param string $capability Which capability we are using as the one required for the + * troubleshooting page. + * @param static $troubleshooting The current instance of the class that handles this page. + */ + $capability = apply_filters( 'tec_troubleshooting_capability', 'install_plugins', $this ); + return $capability; + } + + /** + * Hooked to admin_body_class to add a class for troubleshooting page. + * + * @since 4.15.0 + * + * @param string $classes a space separated string of classes to be added to body. + * + * @return string $classes a space separated string of classes to be added to body. + */ + public function admin_body_class( $classes ) { + if ( ! $this->is_current_page() ) { + return $classes; + } + + $classes .= ' tec-troubleshooting'; + return $classes; + } + + /** + * Adds the troubleshooting menu to the the WP admin bar under events. + * + * @since 4.14.2 + * + */ + public function add_toolbar_item() { + $capability = $this->get_required_capability(); + + if ( ! current_user_can( $capability ) ) { + return; + } + + global $wp_admin_bar; + + $wp_admin_bar->add_menu( [ + 'id' => 'tec-troubleshooting', + 'title' => esc_html__( 'Troubleshooting', 'tribe-common' ), + 'href' => Tribe__Settings::instance()->get_url( [ 'page' => static::MENU_SLUG ] ), + 'parent' => 'tribe-events-settings-group', + ] ); + } + + /** + * Checks if the current page is the troubleshooting page. + * + * @since 4.14.2 + * + * @return boolean returns true if the current page is the troubleshooting page. + */ + public function is_current_page() { + if ( ! Tribe__Settings::instance()->should_setup_pages() || ! did_action( 'admin_menu' ) ) { + return false; + } + + if ( is_null( $this->admin_page ) ) { + _doing_it_wrong( + __FUNCTION__, + 'Function was called before it is possible to accurately determine what the current page is.', + '4.5.6' + ); + return false; + } + + global $current_screen; + + $troubleshooting_pages = [ + 'tribe_events_page_tec-troubleshooting', + 'tickets_page_tec-tickets-troubleshooting', + ]; + + return in_array( $current_screen->id, $troubleshooting_pages ); + } + + /** + * Renders the Troubleshooting page. + * + * @since 4.14.2 + * + */ + public function do_menu_page() { + tribe_asset_enqueue( 'tribe-admin-help-page' ); + $main = Tribe__Main::instance(); + include_once Tribe__Main::instance()->plugin_path . 'src/admin-views/troubleshooting.php'; + } + + /** + * This method checks if there are any active issues that need to be flagged. + * + * @since 4.14.2 + * + * @return boolean returns true if there are any active issues. + */ + public function is_any_issue_active() { + $issues = $this->get_issues_found(); + $active_issues = wp_list_pluck( $issues, 'active' ); + return in_array( true, $active_issues ); + } + + /** + * Checks if any active TEC plugins require an update. + * + * @since 4.14.2 + * + * @return boolean returns true is any of the plugins requires an update. + */ + public function is_any_tec_plugin_out_of_date() { + $current = get_site_transient( 'update_plugins' ); + $plugins = []; + if ( defined( 'TRIBE_EVENTS_FILE' ) ) { + $plugins[] = TRIBE_EVENTS_FILE; + } + if ( defined( 'EVENTS_CALENDAR_PRO_FILE' ) ) { + $plugins[] = EVENTS_CALENDAR_PRO_FILE; + } + if ( defined( 'EVENT_TICKETS_PLUS_FILE' ) ) { + $plugins[] = EVENT_TICKETS_PLUS_FILE; + } + if ( defined( 'EVENTS_VIRTUAL_FILE' ) ) { + $plugins[] = EVENTS_VIRTUAL_FILE; + } + if ( defined( 'EVENT_TICKETS_MAIN_PLUGIN_FILE' ) ) { + $plugins[] = EVENT_TICKETS_MAIN_PLUGIN_FILE; + } + if ( defined( 'TRIBE_EVENTS_FILTERBAR_FILE' ) ) { + $plugins[] = TRIBE_EVENTS_FILTERBAR_FILE; + } + if ( defined( 'EVENTS_COMMUNITY_TICKETS_FILE' ) ) { + $plugins[] = EVENTS_COMMUNITY_TICKETS_FILE; + } + if ( defined( 'EVENTS_COMMUNITY_FILE' ) ) { + $plugins[] = EVENTS_COMMUNITY_FILE; + } + if ( defined( 'EVENTBRITE_PLUGIN_FILE' ) ) { + $plugins[] = EVENTBRITE_PLUGIN_FILE; + } + if ( defined( 'TRIBE_APM_FILE' ) ) { + $plugins[] = TRIBE_APM_FILE; + } + if ( defined( 'IMAGE_WIDGET_PLUS_DIR' ) ) { + $plugins[] = IMAGE_WIDGET_PLUS_DIR; + } + $plugins = array_map( static function( $file ) { + $file = \str_replace( WP_PLUGIN_DIR . '/', '', $file ); + return $file; + }, $plugins ); + + foreach ( $plugins as $file ) { + if ( ! isset( $current->response[ $file ] ) ) { + continue; + } + $response = $current->response[ $file ]; + if ( ! empty( $response->new_version ) ) { + return true; + } + } + return false; + } + + /** + * Checks if any of the issues defined are active. + * + * @since 4.14.2 + * + * @param string $slug the slug of active issue. + * + * @return boolean returns a boolean value for each individual issue depending on whether it is active or not. + */ + public function is_active_issue( $slug ) { + if ( 'timezone' === $slug ) { + return Timezones::is_utc_offset( Timezones::wp_timezone_string() ); + } + if ( 'geolocation' === $slug && class_exists( 'Tribe__Events__Google__Maps_API_Key' ) ) { + $key = \tribe_get_option( 'google_maps_js_api_key', false ); + return empty( $key ) || Tribe__Events__Google__Maps_API_Key::$default_api_key === $key ; + } + if ( 'out-of-date' === $slug ) { + return $this->is_any_tec_plugin_out_of_date(); + } + return false; + } + + /** + * Displays issues found in the UI. + * + * @since 4.14.2 + * + * @return array of issues which are displayed on the troubleshooting page. + */ + public function get_issues_found() { + $issues_found = apply_filters( 'tec_help_troubleshooting_issues_found', [ + [ + 'title' => __( 'Site time zone uses UTC', 'tribe-common' ), + 'description' => __( 'When using The Events Calendar, we highly recommend that you use a geographic timezone such as "America/Los_Angeles" and avoid using a UTC timezone offset such as “UTC+9”. Choosing a UTC timezone for your site or individual events may cause problems when importing events or with Daylight Saving Time. Go to your the General WordPress settings to adjust your site timezone.', 'tribe-common' ), + 'more_info' => 'http://evnt.is/1ad3', + 'resolve_text' => __( 'Adjust your timezone', 'tribe-common' ), + 'fix' => '/wp-admin/options-general.php', + 'active' => $this->is_active_issue( 'timezone' ), + ], + [ + 'title' => __( 'Install max has been reached', 'tribe-common' ), + 'description' => __( 'License keys can only be used on a limited number of sites, which varies depending on your license level. You\'ll need to remove the license from one or more other site\'s in order to use it on this one.', 'tribe-common' ), + 'more_info' => 'https://evnt.is/1aqz', + 'resolve_text' => __( 'Manage your licenses', 'tribe-common' ), + 'fix' => 'https://evnt.is/1aq-', + 'active' => $this->is_active_issue( 'install-max' ), + ], + [ + 'title' => __( 'Default Google Maps API key', 'tribe-common' ), + 'description' => __( 'The Events Calendar comes with an API key for basic maps functionality. If you’d like to use more advanced features like custom map pins, dynamic map loads, or Events Calendar Pro\'s Location Search and advanced Map View, you’ll need to get your own Google Maps API key and add it to Events > Settings > Integrations', 'tribe-common' ), + 'more_info' => 'https://evnt.is/1aqx', + 'resolve_text' => __( 'Enter a custom API key', 'tribe-common' ), + 'fix' => '/wp-admin/edit.php?page=tribe-common&tab=addons&post_type=tribe_events', + 'active' => $this->is_active_issue( 'geolocation' ), + ], + [ + 'title' => __( 'Plugin(s) are out of date', 'tribe-common' ), + 'description' => __( 'It\'s important to use the most recent versions of our plugins so that you have access to the latest features, bug fixes, and security updates. Plugin functionality can be comprimised if your site is running outdated or mis-matched versions.', 'tribe-common' ), + 'more_info' => 'https://evnt.is/1aqy', + 'resolve_text' => __( 'Check for updates', 'tribe-common' ), + 'fix' => '/wp-admin/update-core.php', + 'active' => $this->is_active_issue( 'out-of-date' ), + ], + ] ); + + return $issues_found; + } + + /** + * Defines common troubleshooting issues and displays them in the UI. + * + * @since 4.14.2 + * + * @return array of common issues which are displayed on the troubleshooting page. + */ + public function get_common_issues() { + $common_issues = apply_filters( 'tec_help_troubleshooting_issues', [ + [ + 'issue' => __( 'Common Error Messages', 'tribe-common' ), + 'solution' => __( 'Here’s an overview of %s and what they mean.', 'tribe-common' ), + 'link' => 'https://evnt.is/1as0', + 'link_label' => 'common error messages', + ], + [ + 'issue' => __( 'My calendar doesn’t look right.', 'tribe-common' ), + 'solution' => __( 'This can happen when other plugins try to improve performance. %s.' ), + 'link' => 'https://theeventscalendar.com/knowledgebase/k/troubleshooting-the-most-common-installation-issues/#layout-issue', + 'link_label' => 'More info', + ], + [ + 'issue' => __( 'I installed the calendar and it crashed my site.', 'tribe-common' ), + 'solution' => __( '%s and other common installation issues.', 'tribe-common' ), + 'link' => 'https://theeventscalendar.com/knowledgebase/k/troubleshooting-the-most-common-installation-issues/#fatal-errors', + 'link_label' => 'Find solutions to this', + ], + [ + 'issue' => __( 'I keep getting “Page Not Found” on events.', 'tribe-common' ), + 'solution' => __( 'There are a few %s to resolve and prevent 404 errors.', 'tribe-common' ), + 'link' => 'https://evnt.is/1as2', + 'link_label' => 'things you can do', + ], + ] ); + + return $common_issues; + } + + /** + * Fired to display notices in the admin pages where the method is called. + * + * @since 4.14.2 + * + * @param string $page the page which the action is being applied. + * + */ + public function admin_notice( $page ) { + do_action( 'tec_admin_notice_area', $page ); + } +} diff --git a/tribe-common/src/Tribe/Admin/Upsell_Notice/Main.php b/tribe-common/src/Tribe/Admin/Upsell_Notice/Main.php new file mode 100644 index 0000000000..5f220d0e58 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Upsell_Notice/Main.php @@ -0,0 +1,97 @@ +template = new \Tribe__Template(); + $this->template->set_template_origin( \Tribe__Main::instance() ); + $this->template->set_template_folder( 'src/admin-views/notices/upsell' ); + $this->template->set_template_context_extract( true ); + $this->template->set_template_folder_lookup( false ); + } + + return $this->template; + } + + /** + * Checks if upsell should be rendered. + * + * @since 4.14.17 + * + * @return boolean + */ + private function should_render() { + if ( function_exists( 'tec_should_hide_upsell' ) ) { + return ! tec_should_hide_upsell(); + } + if ( defined( 'TRIBE_HIDE_UPSELL' ) ) { + return ! tribe_is_truthy( TRIBE_HIDE_UPSELL ); + } + return true; + } + + /** + * Render upsell notice. + * + * @since 4.14.17 + * + * @param array $args Array of arguments that will ultimately be sent to the template. + * @param bool $echo Whether or not to echo the HTML. Defaults to true. + * + * @return string HTML of upsell notice. + */ + public function render( $args, $echo = true ) { + // Check if upsell should be rendered. + if ( ! $this->should_render() ) { + return; + } + + // Default args for the container. + $args = wp_parse_args( $args, [ + 'classes' => [], + 'text' => '', + 'link_target' => '_blank', + 'icon_url' => tribe_resource_url( 'images/icons/circle-bolt.svg', false, null, \Tribe__Main::instance() ), + 'link' => [], + ] ); + + // Default args for the link. + $args['link'] = wp_parse_args( $args['link'], [ + 'classes' => [], + 'text' => '', + 'url' => '', + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ] ); + + $template = $this->get_template(); + return $template->template( 'main', $args, $echo ); + } +} \ No newline at end of file diff --git a/tribe-common/src/Tribe/Admin/Upsell_Notice/README.md b/tribe-common/src/Tribe/Admin/Upsell_Notice/README.md new file mode 100644 index 0000000000..aa0b1012e1 --- /dev/null +++ b/tribe-common/src/Tribe/Admin/Upsell_Notice/README.md @@ -0,0 +1,65 @@ +# Upsell Notices + +To add an upsell notice, use the following code: + +``` +tribe( \Tribe\Admin\Upsell_Notice\Main::class )->render( [ + 'text' => 'Text to explain what you are promoting.', + 'link' => [ + 'text' => 'Text for the link.', + 'url' => 'https://url.com/to/more/info', + ], +] ); +``` + +## Customizing the notice container + +There are a couple of classes you can add that will display the upsell notice with different styles. + +- `.tec-admin__upsell--rounded-corners` - Adds a rounded-corner, light gray background around the entire notice. +- `.tec-admin__upsell--rounded-corners-text` - Adds a rounded-corner, light gray background around the notice text, only. + +Example: +``` +tribe( \Tribe\Admin\Upsell_Notice\Main::class )->render( [ + 'classes' => [ + 'tec-admin__upsell--rounded-corners' + ], + 'text' => 'Text to explain what you are promoting.', + 'link' => [ + 'text' => 'Text for the link.', + 'url' => 'https://url.com/to/more/info', + ], +] ); +``` + +## Customizing the notice link + +Likewise, you can also add these classes to the link array to change the appearance. + +- `.tec-admin__upsell-link--dark` - Changes the color to a dark color, instead of the default blue. +- `.tec-admin__upsell-link--underlined` - Adds an underline to the link text. + +You can also change the following attributes of the link: + +- `target` - Default is '_blank'. +- `rel` - Default is 'nofollow noreferrer'. + +Example: +``` +tribe( \Tribe\Admin\Upsell_Notice\Main::class )->render( [ + 'classes' => [ + 'tec-admin__upsell--rounded-corners-text' + ], + 'text' => 'Text to explain what you are promoting.', + 'link' => [ + 'classes' => [ + 'tec-admin__upsell-link--dark', + 'tec-admin__upsell-link--underlined', + ], + 'text' => 'Text for the link.', + 'url' => 'https://url.com/to/more/info', + 'target' => '_parent' + ], +] ); +``` \ No newline at end of file diff --git a/tribe-common/src/Tribe/Ajax/Dropdown.php b/tribe-common/src/Tribe/Ajax/Dropdown.php index fd490f4ef3..071d977cf0 100644 --- a/tribe-common/src/Tribe/Ajax/Dropdown.php +++ b/tribe-common/src/Tribe/Ajax/Dropdown.php @@ -88,6 +88,10 @@ public function search_terms( $search, $page, $args, $source ) { } } + foreach ( $results as $result ) { + $result->text = wp_specialchars_decode( wp_kses( $result->text, [] ) ); + } + $data['results'] = $results; $data['taxonomies'] = get_taxonomies(); @@ -271,7 +275,7 @@ public function route() { if ( has_filter( $filter ) ) { $data = apply_filters( $filter, [], $args->search, $args->page, $args->args, $args->source ); } else { - $data = call_user_func_array( [ $this, $args->source ], (array) $args ); + $data = call_user_func_array( [ $this, $args->source ], array_values( (array) $args ) ); } // If we've got a empty dataset we return an error. diff --git a/tribe-common/src/Tribe/App_Shop.php b/tribe-common/src/Tribe/App_Shop.php index 154b262a61..9eb664a736 100755 --- a/tribe-common/src/Tribe/App_Shop.php +++ b/tribe-common/src/Tribe/App_Shop.php @@ -135,6 +135,7 @@ public function do_menu_page() { $products = $this->get_all_products(); $bundles = $this->get_bundles(); $extensions = $this->get_extensions(); + $stellar_brands = $this->get_stellar_brands(); include_once Tribe__Main::instance()->plugin_path . 'src/admin-views/app-shop.php'; } @@ -299,6 +300,67 @@ private function get_extensions() { return $extensions; } + /** + * Gets Stellar brands + * + * @return array|WP_Error + */ + private function get_stellar_brands() { + $stellar_brands = [ + (object) [ + 'image' => 'images/shop/stellar-learndash-cta.jpg', + 'logo' => 'images/shop/stellar-learndash-logo.png', + 'title' => __( 'The online course platform created by e-learning experts.', 'tribe-common' ), + 'link' => 'https://evnt.is/learndash', + 'linktext' => __( 'Add Courses', 'tribe-common' ), + 'description' => __( 'Trusted to power learning programs for major universities, startups, entrepreneurs, and bloggers worldwide.', 'tribe-common' ), + ], + (object) [ + 'image' => 'images/shop/stellar-ithemes-cta.jpg', + 'logo' => 'images/shop/stellar-ithemes-logo.png', + 'title' => __( 'Foundational favorites: iThemes Security and Developer Toolkit.', 'tribe-common' ), + 'link' => 'https://evnt.is/ithemes', + 'linktext' => __( 'Add Security', 'tribe-common' ), + 'description' => __( 'iThemes Security, the WordPress security plugin that’s easy to use. Built with performance in mind.', 'tribe-common' ), + ], + (object) [ + 'image' => 'images/shop/stellar-rcp-cta.jpg', + 'logo' => 'images/shop/stellar-rcp-logo.png', + 'title' => __( 'Built with developers in mind.', 'tribe-common' ), + 'link' => 'https://evnt.is/rcp', + 'linktext' => __( 'Add Content Restriction', 'tribe-common' ), + 'description' => __( 'Restrict Content Pro is flexible, easy to extend, and chock full of action hooks and filters, making it easy to modify and tweak to your specific needs.', 'tribe-common' ), + ], + (object) [ + 'image' => 'images/shop/stellar-kadence-cta.jpg', + 'logo' => 'images/shop/stellar-kadence-logo.png', + 'title' => __( 'Build better WordPress websites with Kadence.', 'tribe-common' ), + 'link' => 'https://evnt.is/kadencewp', + 'linktext' => __( 'Add Starter Templates', 'tribe-common' ), + 'description' => __( 'Kadence lets you unlock your creativity in the WordPress Block Editor with expertly designed blocks, a robust theme, and a massive library of starter templates.', 'tribe-common' ), + ], + (object) [ + 'image' => 'images/shop/stellar-iconic-cta.jpg', + 'logo' => 'images/shop/stellar-iconic-logo.png', + 'title' => __( 'Sales-boosting WooCommerce plugins.', 'tribe-common' ), + 'link' => 'https://evnt.is/iconic', + 'linktext' => __( 'Add Commerce Tools', 'tribe-common' ), + 'description' => __( 'Easy-to-use WooCommerce plugins work perfectly together, with any theme. Create a fast and profitable eCommerce store without any technical knowledge. + ', 'tribe-common' ), + ], + (object) [ + 'image' => 'images/shop/stellar-give-cta.jpg', + 'logo' => 'images/shop/stellar-give-logo.png', + 'title' => __( 'The best WordPress donation plugin.', 'tribe-common' ), + 'link' => 'https://evnt.is/givewp', + 'linktext' => __( 'Add Donations', 'tribe-common' ), + 'description' => __( 'GiveWP makes it easy to raise money online with donation forms, donor databases, and fundraising reporting.', 'tribe-common' ), + ], + ]; + + return $stellar_brands; + } + /** * Static Singleton Factory Method * diff --git a/tribe-common/src/Tribe/Assets.php b/tribe-common/src/Tribe/Assets.php index f431e56a76..4b816f45c9 100644 --- a/tribe-common/src/Tribe/Assets.php +++ b/tribe-common/src/Tribe/Assets.php @@ -39,11 +39,55 @@ public function __construct() { // Hook the actual registering of. add_action( 'init', [ $this, 'register_in_wp' ], 1, 0 ); add_filter( 'script_loader_tag', [ $this, 'filter_tag_async_defer' ], 50, 2 ); + add_filter( 'script_loader_tag', [ $this, 'filter_modify_to_module' ], 50, 2 ); + add_filter( 'script_loader_tag', [ $this, 'filter_print_before_after_script' ], 100, 2 ); // Enqueue late. add_filter( 'script_loader_tag', [ $this, 'filter_add_localization_data' ], 500, 2 ); } + /** + * Depending on how certain scripts are loaded and how much cross-compatibility is required we need to be able to + * create noConflict backups and restore other scripts, which normally need to be printed directly on the scripts. + * + * @since 5.0.0 + * + * @param string $tag Tag we are filtering. + * @param string $handle Which is the ID/Handle of the tag we are about to print. + * + * @return string Script tag with the before and after strings attached to it. + */ + public function filter_print_before_after_script( $tag, $handle ) : string { + // Only filter for our own filters. + if ( ! $asset = $this->get( $handle ) ) { + return (string) $tag; + } + + // Bail when not dealing with JS assets. + if ( 'js' !== $asset->type ) { + return (string) $tag; + } + + // Only go forward if there is any print before or after. + if ( empty( $asset->print_before ) && empty( $asset->print_after ) ) { + return (string) $tag; + } + + $before = ''; + if ( ! empty( $asset->print_before ) ) { + $before = (string) ( is_callable( $asset->print_before ) ? call_user_func( $asset->print_before, $asset ) : $asset->print_before ); + } + + $after = ''; + if ( ! empty( $asset->print_after ) ) { + $after = (string) ( is_callable( $asset->print_after ) ? call_user_func( $asset->print_after, $asset ) : $asset->print_after ); + } + + $tag = $before . (string) $tag . $after; + + return $tag; + } + /** * Handles adding localization data, when attached to `script_loader_tag` which allows dependencies to load in their * localization data as well. @@ -56,7 +100,7 @@ public function __construct() { * @return string Script tag with the localization variable HTML attached to it. */ public function filter_add_localization_data( $tag, $handle ) { - // Only filter for own own filters. + // Only filter for own filters. if ( ! $asset = $this->get( $handle ) ) { return $tag; } @@ -122,7 +166,7 @@ public function filter_add_localization_data( $tag, $handle ) { * @return string Script tag with the defer and/or async attached. */ public function filter_tag_async_defer( $tag, $handle ) { - // Only filter for own own filters. + // Only filter for our own filters. if ( ! $asset = $this->get( $handle ) ) { return $tag; } @@ -149,16 +193,53 @@ public function filter_tag_async_defer( $tag, $handle ) { $replacement .= 'defer '; } - $replacement_src = $replacement . 'src='; - $replacement_type = $replacement . 'type='; - return str_replace( [ '\n" - . $tag - . "\n"; + . $tag + . "\n"; } + + return $tag; + } + + /** + * After select2 is loaded to the FE we add one scripts after to prevent select2 from breaking. + * + * @since 4.13.2 + * @since 4.14.18 Ensure we don't run this in the admin. + * + * @param string $tag The \n"; + return $tag; } } diff --git a/tribe-common/src/Tribe/Cache.php b/tribe-common/src/Tribe/Cache.php index b769760ce7..353466767d 100755 --- a/tribe-common/src/Tribe/Cache.php +++ b/tribe-common/src/Tribe/Cache.php @@ -118,7 +118,7 @@ public function get( $id, $expiration_trigger = '', $default = false, $expiratio if ( is_callable( $default ) ) { // A callback has been specified. - $value = call_user_func_array( $default, $args ); + $value = $default( ...$args ); } else { // Default is a value. $value = $default; @@ -258,7 +258,7 @@ public function maybe_delete_expired_transients() { public function get_id( $key, $expiration_trigger = '' ) { if ( is_array( $expiration_trigger ) ) { $triggers = $expiration_trigger; - } else { + } elseif ( 'tribe-events-non-persistent' !== $expiration_trigger && 'tribe-events' !== $expiration_trigger ) { $triggers = array_filter( explode( '|', $expiration_trigger ) ); } @@ -434,6 +434,21 @@ public function offsetUnset( $offset ) { $this->delete( $offset ); } + /** + * Removes a group of the cache, for now only `non_persistent` is supported. + * + * @since 4.14.13 + * + * @return bool + */ + public function reset( $group = 'non_persistent' ) { + if ( 'non_persistent' !== $group ) { + return false; + } + $this->non_persistent_keys = []; + return true; + } + /** * Warms up the caches for a collection of posts. * @@ -540,4 +555,99 @@ public function data_size_over_packet_size( $value ) { // If the size of the string is above 90% of the database `max_allowed_packet` setting, then it should not be written to the db. return $size > ( $feature_detection->get_mysql_max_packet_size() * .9 ); } + + /** + * Returns a transient that might have been stored, due ot its size, in chunks. + * + * @since 4.13.3 + * + * @param string $id The name of the transients to return. + * @param string|array $expiration_trigger The transient expiration trigger(s). + * + * @return false|mixed Either the transient value, joined back into one, or `false` to indicate + * the transient was not found or was malformed. + */ + public function get_chunkable_transient( $id, $expiration_trigger = '' ) { + $transient = $this->get_id( $id, $expiration_trigger ); + + if ( wp_using_ext_object_cache() ) { + return get_transient( $transient ); + } + + $chunks = []; + $i = 0; + do { + $chunk_transient = $transient . '_' . $i++; + $chunk = get_transient( $chunk_transient ); + $chunks[ $chunk_transient ] = (string) $chunk; + } while ( ! empty( $chunk ) ); + + // Remove any piece of data that was added but is not relevant. + $chunks = array_filter( $chunks ); + + if ( empty( $chunks ) ) { + return false; + } + + try { + $data = implode( '', $chunks ); + $is_serialized = preg_match( '/^[aO]:\\d+:/', $data ); + $unserialized = maybe_unserialize( implode( '', $chunks ) ); + + if ( is_string( $unserialized ) && $unserialized === $data && $is_serialized ) { + // Something was messed up. + return false; + } + + return $unserialized; + } catch ( Exception $e ) { + return false; + } + } + + /** + * Sets a transient in the database with the knowledge that, if too large to be stored in one + * DB row, it will be chunked. + * + * The method will redirect to the `set_transient` function if the site is using object caching. + * + * + * @since 4.13.3 + * + * @param string $id The transient ID. + * @param mixed $value The value to store, that could be chunked. + * @param int $expiration The transient expiration, in seconds. + * @param string|array $expiration_trigger The transient expiration trigger(s). + * + * @return bool Whether the transient, or the transient chunks, have been stored correctly or not. + */ + public function set_chunkable_transient( $id, $value, $expiration = 0, $expiration_trigger = '' ) { + $transient = $this->get_id( $id, $expiration_trigger ); + + if ( wp_using_ext_object_cache() ) { + return $this->set_transient( $transient, $value, $expiration ); + } + + $inserted = []; + $serialized_value = maybe_serialize( $value ); + $chunk_size = tribe( 'feature-detection' )->get_mysql_max_packet_size() * 0.9; + $chunks = str_split( $serialized_value, $chunk_size ); + foreach ( $chunks as $i => $chunk ) { + $chunk_transient = $transient . '_' . $i; + + $set = set_transient( $chunk_transient, $chunk, $expiration ); + + if ( ! $set ) { + foreach ( $inserted as $transient_to_delete ) { + delete_transient( $transient_to_delete ); + } + + return false; + } + + $inserted[] = $chunk_transient; + } + + return true; + } } diff --git a/tribe-common/src/Tribe/Cache_Listener.php b/tribe-common/src/Tribe/Cache_Listener.php index bfa3fce112..bc428398af 100755 --- a/tribe-common/src/Tribe/Cache_Listener.php +++ b/tribe-common/src/Tribe/Cache_Listener.php @@ -1,171 +1,215 @@ cache = new Tribe__Cache(); + } - /** - * Class constructor. - * - * @return void - */ - public function __construct() { - $this->cache = new Tribe__Cache(); - } + /** + * Run the init functionality (like add_hooks). + * + * @return void + */ + public function init() { + $this->add_hooks(); + } - /** - * Run the init functionality (like add_hooks). - * - * @return void - */ - public function init() { - $this->add_hooks(); - } + /** + * Add the hooks necessary. + * + * @return void + */ + private function add_hooks() { + add_action( 'save_post', [ $this, 'save_post' ], 0, 2 ); + add_action( 'updated_option', [ $this, 'update_last_updated_option' ], 10, 3 ); + add_action( 'updated_option', [ $this, 'update_last_save_post' ], 10, 3 ); + add_action( 'generate_rewrite_rules', [ $this, 'generate_rewrite_rules' ] ); + add_action( 'clean_post_cache', [ $this, 'save_post' ], 0, 2 ); + } - /** - * Add the hooks necessary. - * - * @return void - */ - private function add_hooks() { - add_action( 'save_post', [ $this, 'save_post' ], 0, 2 ); - add_action( 'updated_option', [ $this, 'update_last_updated_option' ], 10, 3 ); - add_action( 'updated_option', [ $this, 'update_last_save_post' ], 10, 3 ); - add_action( 'generate_rewrite_rules', [ $this, 'generate_rewrite_rules' ] ); + /** + * Run the caching functionality that is executed on save post. + * + * @param int $post_id The post_id. + * @param WP_Post $post The current post object being saved.w + */ + public function save_post( $post_id, $post ) { + if ( in_array( $post->post_type, Tribe__Main::get_post_types() ) ) { + $this->cache->set_last_occurrence( self::TRIGGER_SAVE_POST ); } + } - /** - * Run the caching functionality that is executed on save post. - * - * @param int $post_id The post_id. - * @param WP_Post $post The current post object being saved. - */ - public function save_post( $post_id, $post ) { - if ( in_array( $post->post_type, Tribe__Main::get_post_types() ) ) { - $this->cache->set_last_occurrence( self::TRIGGER_SAVE_POST ); - } + /** + * Run the caching functionality that is executed on saving tribe calendar options. + * + * @see 'updated_option' + * + * @param string $option_name Name of the updated option. + * @param mixed $old_value The old option value. + * @param mixed $value The new option value. + */ + public function update_last_save_post( $option_name, $old_value, $value ) { + $triggers = [ + 'tribe_events_calendar_options' => true, + 'permalink_structure' => true, + 'rewrite_rules' => true, + 'start_of_week' => true, + ]; + + $triggers = $this->filter_action_last_occurrence_triggers( $triggers, static::TRIGGER_SAVE_POST, func_get_args() ); + + if ( ! empty( $triggers[ $option_name ] ) ) { + $this->cache->set_last_occurrence( self::TRIGGER_SAVE_POST ); } + } - /** - * Run the caching functionality that is executed on saving tribe calendar options. - * - * @see 'updated_option' - * - * @param string $option_name Name of the updated option. - * @param mixed $old_value The old option value. - * @param mixed $value The new option value. - */ - public function update_last_save_post( $option_name, $old_value, $value ) { - $triggers = [ - 'tribe_events_calendar_options' => true, - 'permalink_structure' => true, - 'rewrite_rules' => true, - 'start_of_week' => true, - ]; - if ( ! empty( $triggers[ $option_name ] ) ) { - $this->cache->set_last_occurrence( self::TRIGGER_SAVE_POST ); - } + /** + * Run the caching functionality that is executed on saving tribe calendar options. + * + * @see 'updated_option' + * + * @since 4.11.0 + * + * @param string $option_name Name of the updated option. + * @param mixed $old_value The old option value. + * @param mixed $value The new option value. + */ + public function update_last_updated_option( $option_name, $old_value, $value ) { + $triggers = [ + 'active_plugins' => true, + 'tribe_events_calendar_options' => true, + 'permalink_structure' => true, + 'rewrite_rules' => true, + 'start_of_week' => true, + 'sidebars_widgets' => true, + 'stylesheet' => true, + 'template' => true, + 'WPLANG' => true, + ]; + + $triggers = $this->filter_action_last_occurrence_triggers( $triggers, static::TRIGGER_UPDATED_OPTION, func_get_args() ); + + if ( ! empty( $triggers[ $option_name ] ) ) { + $this->cache->set_last_occurrence( self::TRIGGER_UPDATED_OPTION ); } + } + + /** + * Filtering for last occurrence triggers. + * + * @since 4.13.2 + * + * @param array $triggers Which options will trigger this given action last occurrence. + * @param string $action Which action this trigger will set. + * @param array $args Which arguments from the updated option method. + * + * @return array + */ + public function filter_action_last_occurrence_triggers( array $triggers = [], $action = '', array $args = [] ) { /** - * Run the caching functionality that is executed on saving tribe calendar options. - * - * @see 'updated_option' + * Filters the contents of which options will trigger expiring a given actions cache. * - * @since 4.11.0 + * @since 4.13.2 * - * @param string $option_name Name of the updated option. - * @param mixed $old_value The old option value. - * @param mixed $value The new option value. + * @param array $triggers Which options will trigger this given action last occurrence. + * @param string $action Which action this trigger will set. + * @param array $args Which arguments from the updated option method. */ - public function update_last_updated_option( $option_name, $old_value, $value ) { - $triggers = [ - 'active_plugins' => true, - 'tribe_events_calendar_options' => true, - 'permalink_structure' => true, - 'rewrite_rules' => true, - 'start_of_week' => true, - 'sidebars_widgets' => true, - 'stylesheet' => true, - 'template' => true, - 'WPLANG' => true, - ]; - - if ( ! empty( $triggers[ $option_name ] ) ) { - $this->cache->set_last_occurrence( self::TRIGGER_UPDATED_OPTION ); - } - } + $triggers = apply_filters( 'tribe_cache_last_occurrence_option_triggers', $triggers, $action, $args ); /** - * For any hook that doesn't need any additional filtering + * Filters the contents of which options will trigger expiring a given actions cache. + * Allows filtering a specific action. * - * @param $method - * @param $args - */ - public function __call( $method, $args ) { - $this->cache->set_last_occurrence( $method ); - } - - /** - * Instance method of the cache listener. + * @since 4.13.2 * - * @return Tribe__Cache_Listener + * @param array $triggers Which options will trigger this given action last occurrence. + * @param string $action Which action this trigger will set. + * @param array $args Which arguments from the updated option method. */ - public static function instance() { - if ( empty( self::$instance ) ) { - self::$instance = self::create_listener(); - } + return (array) apply_filters( "tribe_cache_last_occurrence_option_triggers:{$action}", $triggers, $action, $args ); + } + + /** + * For any hook that doesn't need any additional filtering + * + * @param $method + * @param $args + */ + public function __call( $method, $args ) { + $this->cache->set_last_occurrence( $method ); + } - return self::$instance; + /** + * Instance method of the cache listener. + * + * @return Tribe__Cache_Listener + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = self::create_listener(); } - /** - * Create a cache listener. - * - * @return Tribe__Cache_Listener - */ - private static function create_listener() { - $listener = new self(); - $listener->init(); + return self::$instance; + } - return $listener; - } + /** + * Create a cache listener. + * + * @return Tribe__Cache_Listener + */ + private static function create_listener() { + $listener = new self(); + $listener->init(); - /** - * Run the caching functionality that is executed when rewrite rules are generated. - * - * @since 4.9.11 - */ - public function generate_rewrite_rules() { - $this->cache->set_last_occurrence( self::TRIGGER_GENERATE_REWRITE_RULES ); - } + return $listener; + } + + /** + * Run the caching functionality that is executed when rewrite rules are generated. + * + * @since 4.9.11 + */ + public function generate_rewrite_rules() { + $this->cache->set_last_occurrence( self::TRIGGER_GENERATE_REWRITE_RULES ); } +} diff --git a/tribe-common/src/Tribe/Context.php b/tribe-common/src/Tribe/Context.php index b7188c0cfb..5429ebc5fc 100644 --- a/tribe-common/src/Tribe/Context.php +++ b/tribe-common/src/Tribe/Context.php @@ -282,7 +282,23 @@ public function is_editing_post( $post_or_type = null ) { } if ( ! empty( $post_or_type ) ) { - $lookup = [ $_GET, $_POST, $_REQUEST ]; + $lookup = []; + // Prevent a slew of warnings every time we call this. + if ( isset( $_REQUEST ) ) { + $lookup[] = (array) $_REQUEST; + } + + if ( isset( $_GET ) ) { + $lookup[] = (array) $_GET; + } + + if ( isset( $_POST ) ) { + $lookup[] = (array) $_POST; + } + + if ( empty( $lookup ) ) { + return false; + } $current_post = Tribe__Utils__Array::get_in_any( $lookup, 'post', get_post() ); diff --git a/tribe-common/src/Tribe/Context/locations.php b/tribe-common/src/Tribe/Context/locations.php index 0df3e81e23..6a433dd97e 100644 --- a/tribe-common/src/Tribe/Context/locations.php +++ b/tribe-common/src/Tribe/Context/locations.php @@ -47,6 +47,14 @@ static function( $struct ){ Tribe__Context::FUNC => static function () { global $wp_query; + if ( empty( $wp_query ) ) { + return false; + } + + if ( ! $wp_query instanceof WP_Query ) { + return false; + } + return $wp_query->is_main_query(); }, ], @@ -99,7 +107,7 @@ static function( $struct ){ 'objects' ); - foreach( $post_type_objs as $post_type ) { + foreach ( $post_type_objs as $post_type ) { if ( empty( $post_type->query_var ) ) { continue; } diff --git a/tribe-common/src/Tribe/Cost_Utils.php b/tribe-common/src/Tribe/Cost_Utils.php index 8e0aec0520..42a112a19f 100644 --- a/tribe-common/src/Tribe/Cost_Utils.php +++ b/tribe-common/src/Tribe/Cost_Utils.php @@ -111,7 +111,7 @@ public function maybe_replace_cost_with_free( $cost ) { is_numeric( $cost_with_period ) && '0.00' === number_format( $cost_with_period, 2, '.', ',' ) ) { - return esc_html__( 'Free', 'the-events-calendar' ); + return esc_html__( 'Free', 'tribe-common' ); } return $cost; @@ -346,7 +346,7 @@ public function parse_cost_range( $costs, $max_decimals = null, $sort = true ) { } $output_costs = []; - $costs = call_user_func_array( 'array_merge', $costs ); + $costs = call_user_func_array( 'array_merge', array_values( $costs ) ); foreach ( $costs as $cost ) { $numeric_cost = str_replace( $this->get_separators(), '.', $cost ); diff --git a/tribe-common/src/Tribe/Credits.php b/tribe-common/src/Tribe/Credits.php index 15ae9d6fd3..c87bc1f518 100755 --- a/tribe-common/src/Tribe/Credits.php +++ b/tribe-common/src/Tribe/Credits.php @@ -45,33 +45,47 @@ public function rating_nudge( $footer_text ) { add_filter( 'tribe_tickets_post_types', [ $this, 'tmp_return_tribe_events' ], 99 ); - // only display custom text on Tribe Admin Pages + $review_text_tec = esc_html__( 'Rate %1$sThe Events Calendar%2$s %3$s', 'tribe-common' ); + $review_url_tec = 'https://wordpress.org/support/plugin/the-events-calendar/reviews/?filter=5'; + + $review_text_et = esc_html__( 'If you like %1$sEvent Tickets%2$s please leave us a %3$s. It takes a minute and it helps a lot.', 'tribe-common' ); + $review_url_et = 'https://wordpress.org/support/plugin/event-tickets/reviews/?filter=5'; + + // Only display custom text on Tribe Admin Pages. if ( $admin_helpers->is_screen() || $admin_helpers->is_post_type_screen() ) { if ( class_exists( 'Tribe__Events__Main' ) ) { - $review_url = 'https://wordpress.org/support/plugin/the-events-calendar/reviews/?filter=5'; - - $footer_text = sprintf( - esc_html__( 'Rate %1$sThe Events Calendar%2$s %3$s', 'tribe-common' ), - '', - '', - '★★★★★' - ); + // If we have TEC and ET, split the impressions 50/50. + if ( class_exists( 'Tribe__Tickets__Main' ) && wp_rand( 0,1 ) ) { + $review_text = $review_text_et; + $review_url = $review_url_et; + } else { + $review_text = $review_text_tec; + $review_url = $review_url_tec; + } } else { - $review_url = 'https://wordpress.org/support/plugin/event-tickets/reviews/?filter=5'; - - $footer_text = sprintf( - esc_html__( 'Rate %1$sEvent Tickets%2$s %3$s', 'tribe-common' ), - '', - '', - '★★★★★' - ); + $review_text = $review_text_et; + $review_url = $review_url_et; } + + $footer_text = sprintf( + $review_text, + '', + '', + '★★★★★' + ); } remove_filter( 'tribe_tickets_post_types', [ $this, 'tmp_return_tribe_events' ], 99 ); - return $footer_text; + /** + * Filters the admin footer text. + * + * @since 4.15.0 + * + * @param $footer_text The admin footer text. + */ + return apply_filters( 'tec_admin_footer_text', $footer_text ); } /** diff --git a/tribe-common/src/Tribe/Customizer.php b/tribe-common/src/Tribe/Customizer.php index 7ce92b9399..a153ded51f 100644 --- a/tribe-common/src/Tribe/Customizer.php +++ b/tribe-common/src/Tribe/Customizer.php @@ -113,6 +113,7 @@ public function __construct() { add_action( 'customize_register', [ $this, 'register' ], 15 ); add_action( 'wp_print_footer_scripts', [ $this, 'print_css_template' ], 15 ); + add_action( 'customize_controls_print_footer_scripts', [ $this, 'customize_controls_print_footer_scripts' ], 15 ); // front end styles from customizer add_action( 'tribe_events_pro_widget_render', [ $this, 'inline_style' ], 101 ); @@ -191,7 +192,7 @@ public function get_loaded_sections() { * @deprecated * @since 4.0 * - * @param array $selection_class + * @param array $sections_class * @param self $customizer */ $this->sections_class = apply_filters( 'tribe_events_pro_customizer_sections_class', $this->sections_class, $this ); @@ -201,7 +202,7 @@ public function get_loaded_sections() { * * @since 4.4 * - * @param array $selection_class + * @param array $sections_class * @param self $customizer */ $this->sections_class = apply_filters( 'tribe_customizer_sections_class', $this->sections_class, $this ); @@ -209,6 +210,25 @@ public function get_loaded_sections() { return $this->sections_class; } + /** + * Returns the section requested by ID. + * + * @since 4.13.3 + * + * @param string $id The ID of the desired section. + * + * @return boolean|Tribe__Customizer__Section The requested section or boolean false if not found. + */ + public function get_section( $id ) { + $sections = $this->get_loaded_sections(); + + if ( empty( $sections[ $id ] ) ) { + return false; + } + + return $sections[ $id ]; + } + /** * A easy way to check if customize is active * @@ -267,11 +287,14 @@ public static function search_var( $variable = null, $indexes = [], $default = n } /** - * Get an option from the database, using index search you can retrieve the full panel, a section or even a setting + * Get an option from the database, using index search you can retrieve the full panel, a section or even a setting. + * + * @since 4.4 * - * @param array $search Index search, array( 'section_name', 'setting_name' ) - * @param mixed $default The default, if the requested variable doesn't exits - * @return mixed The requested option or the default + * @param array $search Index search, array( 'section_name', 'setting_name' ). + * @param mixed $default The default, if the requested variable doesn't exits. + * + * @return mixed The requested option or the default. */ public function get_option( $search = null, $default = null ) { $sections = get_option( $this->ID, $default ); @@ -369,6 +392,22 @@ public function has_option() { return ! empty( $option ); } + /** + * Add an action for some backwards compatibility. + * + * @since 4.14.2 + * + * @return void + */ + public function customize_controls_print_footer_scripts() { + /** + * Allows plugins to hook in and add any scripts they need at the right time. + * + * @param Tribe__Customizer $customizer The current instance of Tribe__Customizer. + */ + do_action( 'tribe_enqueue_customizer_scripts', $this ); + } + /** * Print the CSS for the customizer on `wp_print_footer_scripts` * @@ -497,7 +536,7 @@ public function inline_style( $force = false ) { if ( $just_print ) { printf( - "\n", + "\n", esc_attr( $sheet ), $inline_style ); @@ -553,7 +592,7 @@ private function parse_css_template( $template ) { * @return void */ public function register( WP_Customize_Manager $customizer ) { - // Set the Cutomizer on a class variable + // Set the Customizer on a class variable $this->manager = $customizer; /** @@ -647,6 +686,42 @@ private function register_panel() { return $this->manager->get_panel( $this->ID ); } + /** + * Returns a URL to the TEC Customizer panel. + * + * @since 4.14.0 + * + * @return string The URL to the TEC Customizer panel. + */ + public function get_panel_url() { + $query['autofocus[panel]'] = 'tribe_customizer'; + return add_query_arg( $query, admin_url( 'customize.php' ) ); + } + + /** + * Returns an HTML link directly to the (opened) TEC Customizer panel + * + * @since 4.14.0 + * + * @param string $link_text The (pre)translated text for the link. + * + * @return string The HTML anchor element, linking to the TEC Customizer panel. + * An empty string is returned if missing a parameter. + */ + public function get_panel_link( $link_text ) { + if ( empty( $link_text ) || ! is_string( $link_text ) ) { + return ''; + } + + $panel_url = $this->get_panel_url(); + + return sprintf( + '%2$s', + esc_url( $panel_url ), + esc_html( $link_text ) + ); + } + /** * Use a "alias" method to register sections to allow users to filter args and the ID * @@ -699,18 +774,65 @@ public function register_section( $id, $args ) { return $this->manager->get_section( $section_id ); } + /** + * Returns a URL to the a specific TEC Customizer section. + * + * @since 4.14.0 + * + * @param string $section The slug for the desired section. + * + * @return string The URL to the TEC Customizer section. + */ + public function get_section_url( $section ) { + if ( empty( $section ) ) { + return ''; + } + + $query['autofocus[section]'] = $section; + return add_query_arg( $query, admin_url( 'customize.php' ) ); + } + + /** + * Gets the HTML link to a section in the TEC Customizer. + * + * @since 4.14.0 + * + * @param string $section The section "slug" to link to. + * @param string $link_text The text for the link. + * + * @return string The HTML anchor element, linking to the TEC Customizer section. + * An empty string is returned if missing a parameter. + */ + public function get_section_link( $section, $link_text = '' ) { + if ( empty( $section ) || empty( $link_text ) || ! is_string($link_text ) ) { + return ''; + } + + + $panel_url = $this->get_section_url( $section ); + if ( empty( $panel_url ) ) { + return ''; + } + + return sprintf( + '%2$s', + esc_url( $panel_url ), + esc_html( $link_text ) + ); + } + /** * Build the Setting name using the HTML format for Arrays * * @since 4.0 * - * @param string $slug The actual Setting name - * @param string|WP_Customize_Section $section [description] + * @param string $slug The actual Setting name + * @param string|WP_Customize_Section $section The section the setting lives in. * - * @return string HTML name Attribute name o the setting + * @return string HTML name Attribute name of the setting. */ public function get_setting_name( $slug, $section = null ) { - $name = $this->panel->id; + $name = ! empty( $this->panel->id ) ? $this->panel->id : ''; // If there is a section set append it if ( $section instanceof WP_Customize_Section ) { @@ -725,21 +847,78 @@ public function get_setting_name( $slug, $section = null ) { return $name; } - /** * Adds a setting field name to the Array of Possible Selective refresh fields * * @since 4.2 * - * @param string $name The actual Setting name + * @param string $name The actual Setting name * - * @return array The list of existing Settings, the new one included + * @return array The list of existing Settings, the new one included */ public function add_setting_name( $name ) { $this->settings[] = $name; return $this->settings; } + /** + * Gets the URL to a specific control/setting in the TEC Customizer. + * + * @since 4.14.0 + * + * @param string $section The section "slug" to link into. + * @param string $setting The setting "slug" to link to. + * + * @return string The URL to the setting. + * An empty string is returned if a parameter is missing or the setting control cannot be found. + */ + public function get_setting_url( $section, $setting ) { + // Bail if something is missing. + if ( empty( $setting ) || empty( $section ) ) { + return ''; + } + + $control = $this->get_setting_name( $setting, $section ); + + if ( empty( $control ) ) { + return ''; + } + + $query['autofocus[control]'] = $control; + + return add_query_arg( $query, admin_url( 'customize.php' ) ); + } + + /** + * Gets the link to the a specific control/setting in the TEC Customizer. + * + * @since 4.14.0 + * + * @param string $section The section "slug" to link into. + * @param string $setting The setting "slug" to link to. + * @param string $link_text The translated text for the link. + * + * @return string The HTML anchor element, linking to the TEC Customizer setting. + * An empty string is returned if missing a parameter or the setting control cannot be found. + */ + public function get_setting_link( $section, $setting, $link_text ) { + // Bail if something is missing. + if ( empty( $setting ) || empty( $section ) || empty( $link_text ) ) { + return ''; + } + + $control_url = $this->get_setting_url( $section, $setting ); + + if ( empty( $control_url ) ) { + return ''; + } + + return sprintf( + '%2$s', + esc_url( $control_url ), + esc_html( $link_text ) + ); + } /** * Using the Previously created CSS element, we not just re-create it every setting change @@ -828,4 +1007,67 @@ public function get_styles_scripts() { return $result; } + + /** + * Inserts link to TEC Customizer section for FSE themes in admin (left) menu. + * + * @since 4.14.8 + */ + public function add_fse_customizer_link() { + _deprecated_function( __METHOD__, '4.14.18', 'No replacement. Customizer menu item is preserved as long as we activate it.'); + // Exit early if the current theme is not a FSE theme. + if ( ! tec_is_full_site_editor() ) { + return; + } + + // Add a link to the TEC panel in the Customizer. + add_submenu_page( + 'themes.php', + _x( 'Customize The Events Calendar', 'Page title for the TEC Customizer section.', 'tribe-common' ), + _x( 'Customize The Events Calendar', 'Menu item text for the TEC Customizer section link.', 'tribe-common' ), + 'edit_theme_options', + esc_url( add_query_arg( 'autofocus[panel]', 'tribe_customizer' , admin_url( 'customize.php' ) ) ) + ); + } + + /** + * Inserts link to TEC Customizer section for FSE themes in Events > Settings > Display. + * + * @since 4.14.8 + * + * @param array $settings The existing settings array. + * + * @return array $settings The modified settings array. + */ + public function add_fse_customizer_link_to_display_tab( $settings ) { + _deprecated_function( __METHOD__, '4.14.18', 'No replacement. Customizer link is preserved as long as we activate it.'); + // Exit early if the current theme is not a FSE theme. + if ( ! tec_is_full_site_editor() ) { + return $settings; + } + + $new_settings = [ + 'tribe-customizer-section-title' => [ + 'type' => 'html', + 'html' => '

' . __( 'Customizer', 'tribe-common' ) . '

', + ], + 'tribe-customizer-link-description' => [ + 'type' => 'html', + 'html' => '

' . __( 'Adjust colors, fonts, and more with the WordPress Customizer.', 'tribe-common' ) . '

', + ], + 'tribe-customizer-link' => [ + 'type' => 'html', + 'html' => sprintf( + /* translators: %1$s: opening anchor tag; %2$s: closing anchor tag */ + esc_html_x( '%1$sCustomize The Events Calendar%2$s', 'Link text added to the TEC->Settings->Display tab.', 'tribe-common' ), + '

', + '

' + ), + ], + ]; + + $settings = Tribe__Main::array_insert_after_key( 'tribe-form-content-start', $settings, $new_settings ); + + return $settings; + } } diff --git a/tribe-common/src/Tribe/Customizer/Controls/Heading.php b/tribe-common/src/Tribe/Customizer/Controls/Heading.php index a3edd4c83e..6e030b615a 100644 --- a/tribe-common/src/Tribe/Customizer/Controls/Heading.php +++ b/tribe-common/src/Tribe/Customizer/Controls/Heading.php @@ -19,6 +19,16 @@ * @package Tribe\Customizer\Controls */ class Heading extends Control { + + /** + * Control's Type. + * + * @since 4.13.3 + * + * @var string + */ + public $type = 'heading'; + /** * Anyone able to set theme options will be able to see the header. * @@ -42,11 +52,18 @@ class Heading extends Control { * * @since 4.12.14 */ - public function render_content() { - ?> -

+ public function render_content() { ?> + +

label ); ?>

- description ) ) : ?> + +

+ description ); ?> +

+ + + * so that it matches our other controls stylistically (spacing, etc). + * + * @since 4.12.13 + * + * @package Tribe\Customizer\Controls + */ + +namespace Tribe\Customizer\Controls; + +use Tribe\Customizer\Control; + +/** + * Class Number + * + * @since 4.12.13 + * + * @package Tribe\Customizer\Controls + */ +class Number extends Control { + + /** + * Control's Type. + * + * @since 4.12.13 + * + * @var string + */ + public $type = 'number'; + + /** + * Anyone able to set theme options will be able to see the input. + * + * @since 4.12.13 + * + * @var string + */ + public $capability = 'edit_theme_options'; + + /** + * Render the control's content + * + * @since 4.12.13 + */ + public function render_content() { + $input_id = '_customize-input-' . $this->id; + $description_id = '_customize-description-' . $this->id; + $describedby_attr = ( ! empty( $this->description ) ) ? ' aria-describedby="' . esc_attr( $description_id ) . '" ' : ''; + $name = '_customize-number-' . $this->id; + + if ( ! empty( $this->label ) ) : ?> + + + description ) ) : ?> + + description ); ?> + + +
+ + input_attrs(); ?> + input_attrs['value'] ) ) : ?> + value="value() ); ?>" + + link(); ?> + /> +
+ choices ) ) { + return; + } + + $input_id = '_customize-input-' . $this->id; + $description_id = '_customize-description-' . $this->id; + $describedby_attr = ( ! empty( $this->description ) ) ? ' aria-describedby="' . esc_attr( $description_id ) . '" ' : ''; + $name = '_customize-radio-' . $this->id; + ?> + label ) ) : ?> + label ); ?> + + description ) ) : ?> + + description ); ?> + + + + choices as $value => $label ) : ?> + + + value="" + name="" + link(); ?> + value(), $value ); ?> + /> + + + + id; + $description_id = '_customize-description-' . $this->id; + $describedby_attr = ( ! empty( $this->description ) ) ? ' aria-describedby="' . esc_attr( $description_id ) . '" ' : ''; + ?> + label ) ) : ?> + label ); ?> + + description ) ) : ?> + + description ); ?> + + + + + + + name="id ); ?>" + input_attrs(); ?> + link(); ?> + choices ) ) : ?> + list="" + + /> + choices ) ) : ?> +
+ choices as $label => $value ) : ?> +
+
+ +
+ +
+ + */ + public $settings = []; + + /** + * Render the control's content + * + * @since 4.13.3 + */ + public function render_content() { + ?> +

+


+

+ id; + $description_id = '_customize-description-' . $this->id; + $describedby_attr = ( ! empty( $this->description ) ) ? ' aria-describedby="' . esc_attr( $description_id ) . '" ' : ''; + $name = '_customize-toggle-' . $this->id; + + ?> + label ) ) : ?> + label ); ?> + + description ) ) : ?> + + description ); ?> + + + + + + + 10, + 'priority' => 10, 'capability' => 'edit_theme_options', - 'title' => null, + 'title' => null, 'description' => null, ]; /** - * Overwrite this method to create the Fields/Settings for this section + * Allows sections to be loaded in order for overrides. * - * @param WP_Customize_Section $section The WordPress section instance - * @param WP_Customize_Manager $manager The WordPress Customizer Manager + * @var integer + */ + public $queue_priority = 15; + + /** + * Private variable holding the class Instance. * - * @return void + * @since 4.0 + * + * @access private + * @var Tribe__Events__Pro__Customizer__Section */ - public function register_settings( WP_Customize_Section $section, WP_Customize_Manager $manager ) { + private static $instances; + /** + * Contains the arguments for the section headings. + * + * @since 4.14.2 + * + * @var array + */ + protected $content_headings = []; + + /** + * Contains the arguments for the section settings. + * + * @since 4.14.2 + * + * @var array + */ + protected $content_settings = []; + + /** + * Contains the arguments for the section controls. + * + * @since 4.14.2 + * + * @var array + */ + protected $content_controls = []; + + /** + * Setup and Load hooks for this Section. + * + * @since 4.0 + * + * @return Tribe__Customizer__Section + */ + final public function __construct() { + // If for weird reason we don't have the Section name + if ( ! is_string( $this->ID ) ){ + $this->ID = self::get_section_slug( get_class( $this ) ); + } + + // Allow child classes to setup the section. + $this->setup(); + + // Hook the Register methods + add_action( "tribe_customizer_register_{$this->ID}_settings", [ $this, 'register_settings' ], 10, 2 ); + add_filter( 'tribe_customizer_pre_sections', [ $this, 'register' ], 10, 2 ); + + // Append this section CSS template + add_filter( 'tribe_customizer_css_template', [ $this, 'setup_css_template' ], $this->queue_priority ); + add_filter( "tribe_customizer_section_{$this->ID}_defaults", [ $this, 'get_defaults' ], 10 ); + + // Create the Ghost Options + add_filter( 'tribe_customizer_pre_get_option', [ $this, 'filter_settings' ], 10, 2 ); + + // By Default Invoking a new Section will load, unless `load` is set to false + if ( true === (bool) $this->load ) { + tribe( 'customizer' )->load_section( $this ); + } } /** - * Overwrite this method to be able to implement the CSS template related to this section + * This method will be executed when the Class is Initialized. + * Overwrite this method to be able to setup the arguments of your section. * - * @return string + * @return void */ - public function get_css_template( $template ) { - return $template; + public function setup() { + $this->arguments = $this->get_arguments(); + $this->setup_content_arguments(); } /** - * Overwrite this method to be able to creaty dynamic settings + * Register this Section. * - * @param array $settings The actual options on the database - * @return array + * @param array $sections Array of Sections. + * @param Tribe__Customizer $customizer Our internal Cutomizer Class Instance. + * + * @return array Return the modified version of the Section array. */ - public function create_ghost_settings( $settings = [] ) { - return $settings; + public function register( $sections, Tribe__Customizer $customizer ) { + $sections[ $this->ID ] = $this->arguments; + + return $sections; } /** - * This method will be executed when the Class in Initialized - * Overwrite this method to be able to setup the arguments of your section + * Overwrite this method to create the Fields/Settings for this section. + * + * @param WP_Customize_Section $section The WordPress section instance. + * @param WP_Customize_Manager $manager The WordPress Customizer Manager. * * @return void */ - abstract public function setup(); + public function register_settings( WP_Customize_Section $section, WP_Customize_Manager $manager ) { + $customizer = tribe( 'customizer' ); + + $settings = $this->get_content_settings(); + + if ( ! empty( $settings ) ) { + foreach ( $settings as $name => $args ) { + $this->add_setting( + $manager, + $customizer->get_setting_name( $name, $section ), + $name, + $args + ); + } + } + + $headings = $this->get_content_headings(); + + if ( ! empty( $headings ) ) { + foreach ( $headings as $name => $args ) { + $this->add_heading( + $section, + $manager, + $customizer->get_setting_name( $name, $section ), + $args + ); + } + } + + $controls = $this->get_content_controls(); + + if ( ! empty( $controls ) ) { + foreach ( $controls as $name => $args ) { + $this->add_control( + $section, + $manager, + $customizer->get_setting_name( $name, $section ), + $args + ); + } + } + } /** - * Private variable holding the class Instance + * Function that encapsulates the logic for if a setting should be added to the Customizer style template. + * Note: this depends on a default value being set - + * if the setting value is empty OR set to the default value, it's not displayed. * - * @since 4.0 + * @since 4.13.3 * - * @access private - * @var Tribe__Events__Pro__Customizer__Section + * @param string $setting The setting slug, like 'grid_lines_color'. + * @param int $section_id The ID for the section - defaults to the current one if not set. + * + * @return boolean If the setting should be added to the style template. */ - private static $instances; + public function should_include_setting_css( $setting, $section_id = null ) { + if ( empty( $setting ) || ! is_string( $setting ) ) { + return false; + } + + if ( empty( $section_id ) ) { + $section_id = $this->ID; + } + + $setting_value = tribe( 'customizer' )->get_option( [ $section_id, $setting ] ); + $section = tribe( 'customizer' )->get_section( $section_id ); + + // Something has gone wrong and we can't get the section. + if ( false === $section ) { + return; + } + + return ! empty( $setting_value ) && $section->get_default( $setting ) !== $setting_value; + } + + /** + * Function to simplify getting an option value. + * + * @since 4.13.3 + * + * @param string $setting The setting slug, like 'grid_lines_color'. + * + * @return string The setting value; + */ + public function get_option( $setting ) { + if ( empty( $setting ) ) { + return ''; + } + + return tribe( 'customizer' )->get_option( [ $this->ID, $setting ] ); + } /** - * Get the section slug based on the Class name + * Overwrite this method to be able to create dynamic settings. * - * @param string $class_name The name of this Class - * @return the slug for this class + * @param array $settings The actual options on the database. + * + * @return array $settings The modified settings. + */ + public function create_ghost_settings( $settings = [] ) { + return $settings; + } + + /** + * Get the section slug based on the Class name. + * + * @param string $class_name The name of this Class. + * @return string $slug The slug for this Class. */ final public static function get_section_slug( $class_name ) { $abstract_name = __CLASS__; $reflection = new ReflectionClass( $class_name ); - // Get the Slug without the Base name - $slug = str_replace( $abstract_name . '_', '', $reflection->getName() ); + // Get the Slug without the Base name. + $slug = str_replace( $abstract_name . '_', '', $reflection->getShortName() ); if ( false !== strpos( $slug, '__Customizer__' ) ) { $slug = explode( '__Customizer__', $slug ); @@ -129,52 +300,153 @@ final public static function get_section_slug( $class_name ) { } /** - * Setup and Load hooks for this Section + * Hooks to the `tribe_customizer_pre_get_option`. This applies the `$this->create_ghost_settings()` method + * to the settings on the correct section. * - * @since 4.0 + * @param array $settings Values from the Database from Customizer actions. + * @param array $search Indexed search @see Tribe__Customizer::search_var(). * - * @return Tribe__Customizer__Section + * @return array */ - final public function __construct() { - $slug = self::get_section_slug( get_class( $this ) ); + public function filter_settings( $settings, $search ) { + // Exit early. + if ( null === $search ) { + return $settings; + } - // If for weird reason we don't have the Section name - if ( ! is_string( $this->ID ) ){ - $this->ID = $slug; + // Only Apply if getting the full options or Section. + if ( is_array( $search ) && count( $search ) > 1 ) { + return $settings; } - // Allow child classes to setup the section - $this->setup(); + if ( is_array( $search ) && count( $search ) === 1 ) { + $settings = $this->create_ghost_settings( $settings ); + } else { + $settings[ $this->ID ] = $this->create_ghost_settings( $settings[ $this->ID ] ); + } - // Hook the Register methods - add_action( "tribe_customizer_register_{$this->ID}_settings", [ $this, 'register_settings' ], 10, 2 ); - add_filter( 'tribe_customizer_pre_sections', [ $this, 'register' ], 10, 2 ); + return $settings; + } - // Append this section CSS template - add_filter( 'tribe_customizer_css_template', [ $this, 'get_css_template' ], 15 ); - add_filter( "tribe_customizer_section_{$this->ID}_defaults", [ $this, 'get_defaults' ], 10 ); + /* Arguments */ - // Create the Ghost Options - add_filter( 'tribe_customizer_pre_get_option', [ $this, 'filter_settings' ], 10, 2 ); + /** + * Set up section arguments. + * + * @since 4.13.3 + * + * @return void + */ + public function setup_arguments() {} - // By Default Invoking a new Section will load, unless `load` is set to false - if ( true === (bool) $this->load ) { - Tribe__Customizer::instance()->load_section( $this ); - } + /** + * Filter section arguments. + * + * @since 4.14.0 + * + * @return void + */ + public function filter_arguments( $arguments ) { + /** + * Applies a filter to the argument map for settings. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + $arguments = apply_filters( 'tribe_customizer_section_arguments', $arguments, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Applies a filter to the argument map for settings for a specific section. Based on the section slug. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + return apply_filters( "tribe_customizer_section_{$section_slug}_arguments", $arguments, $this ); } /** - * A way to apply filters when getting the Customizer options - * @return array + * Retrieve section arguments. + * + * @since 4.14.0 + * + * @return void + */ + public function get_arguments() { + return $this->filter_arguments( $this->setup_arguments() ); + } + + /** + * Sets up the Customizer section content. + * + * @since 4.13.3 + */ + public function setup_content_arguments(){ + $this->defaults = $this->setup_defaults(); + $this->content_settings = $this->setup_content_settings(); + $this->content_headings = $this->setup_content_headings(); + $this->content_controls = $this->setup_content_controls(); + } + + /* Default Values */ + + /** + * Set up default values. + * + * @since 4.13.3 + */ + public function setup_defaults() {} + + /** + * Get the (filtered) default settings. + * + * @return array The filtered defaults. */ public function get_defaults( $settings = [] ) { // Create Ghost Options - return $this->create_ghost_settings( wp_parse_args( $settings, $this->defaults ) ); + $settings = $this->create_ghost_settings( wp_parse_args( $settings, $this->setup_defaults() ) ); + + return $this->filter_defaults( $settings ); + } + + public function filter_defaults( $settings ) { + + /** + * Allows filtering the default values for all sections. + * + * @since 4.13.3 + * + * @param array $settings The default settings + * @param Tribe__Customizer__Section $section The section object. + */ + $settings = apply_filters( 'tribe_customizer_section_default_settings', $settings, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Allows filtering the default values for a specific section. + * + * @since 4.13.3 + * + * @param array $settings The default settings + * @param Tribe__Customizer__Section $section The section object. + */ + $settings = apply_filters( "tribe_customizer_section_{$section_slug}_default_settings", $settings, $this ); + + return $settings; } /** - * Get the Default Value requested - * @return mixed + * Get a single Default Value by key. + * + * @param string $key The key for the requested value. + * + * @return mixed The requested value. */ public function get_default( $key ) { $defaults = $this->get_defaults(); @@ -186,46 +458,473 @@ public function get_default( $key ) { return $defaults[ $key ]; } + /* Utility Functions */ + /** - * Hooks to the `tribe_customizer_pre_get_option`, this applies - * the `$this->create_ghost_settings()` method to the settings on the correct section + * Sugar function that returns the results of Tribe__Customizer->get_section_url() for the current section. * - * @param array $settings Values from the Database from Customizer actions - * @param array $search Indexed search @see Tribe__Customizer::search_var() + * @since 4.14.0 * - * @return array + * @return string The URL to the TEC Customizer section. */ - public function filter_settings( $settings, $search ) { - // Exit early. - if ( null === $search ) { - return $settings; + public function get_section_url() { + return tribe( 'customizer' )->get_section_url( $this->ID ); + } + + /** + * Sugar function that returns the results of Tribe__Customizer->get_section_link() for the current section. + * Gets the HTML link to the current section in the TEC Customizer. + * + * @since 4.14.0 + * + * @param string $link_text The text for the link. + * + * @return string The HTML anchor element, linking to the TEC Customizer section. + * An empty string is returned if missing a parameter. + */ + public function get_section_link( $link_text ) { + return tribe( 'customizer' )->get_section_link( $this->ID, $link_text ); + } + + /** + * Sugar function that returns the results of Tribe__Customizer->get_settings_url() + * for the specified setting in the _current section_. + * + * @since 4.14.0 + * + * @param string $setting The setting "slug" to link to. + * + * @return string The URL to the setting. + */ + public function get_setting_url( $setting ) { + return tribe( 'customizer' )->get_setting_url( $this->ID, $setting ); + } + + /** + * Sugar function that returns the results of Tribe__Customizer->get_settings_url() + * for the specified setting in the _current section_. + * + * @since 4.14.0 + * + * @param string $setting The setting "slug" to link to. + * @param string $link_text The translated text for the link. + * + * @return string The HTML anchor element, linking to the TEC Customizer setting. + */ + public function get_setting_link( $setting, $link_text ) { + return tribe( 'customizer' )->get_setting_link( $this->ID, $setting, $link_text ); + } + + /* Settings */ + + /** + * Sets up the Customizer settings arguments. + * + * @since 4.13.3 + */ + public function setup_content_settings() {} + + /** + * Get the (filtered) content setting arguments. + * @see filter_content_settings() + * + * @since 4.13.3 + * + * @return array The filtered arguments. + */ + public function get_content_settings() { + return $this->filter_content_settings( $this->setup_content_settings() ); + } + + /** + * Filter the content settings arguments + * + * @since 4.13.3 + * + * @param array $arguments The list of arguments for settings. + * + * @return array $arguments The filtered array of arguments. + */ + public function filter_content_settings( $arguments ) { + /** + * Applies a filter to the validation map for settings. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + $arguments = apply_filters( 'tribe_customizer_section_content_settings', $arguments, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Applies a filter to the validation map for settings for a specific section. Based on the section slug. + * Ex: tribe_customizer_section_tec_events_bar_default_settings + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + $arguments = apply_filters( "tribe_customizer_section_{$section_slug}_content_settings", $arguments, $this ); + + return $arguments; + } + + /** + * Sugar syntax to add a setting to the customizer content. + * + * @since 4.13.3 + * + * @param WP_Customize_Manager $manager The instance of the Customizer Manager. + * @param string $setting_name HTML name Attribute name of the setting. + * @param string $key The key for the default value. + * @param array $arguments The control arguments. + */ + protected function add_setting( $manager, $setting_name, $key, $args ) { + // Get the default values. + $defaults = [ + 'default' => $this->get_default( $key ), + 'type' => 'option', + ]; + + // Add a setting. + $manager->add_setting( + $setting_name, + array_merge( $defaults, $args ) + ); + } + + /* Headings */ + + /** + * Sets up the Customizer section Header and Separator arguments. + * + * @since 4.13.3 + */ + public function setup_content_headings() {} + + /** + * Get the (filtered) content headings and separator arguments. + * @see filter_content_headings() + * + * @since 4.13.3 + * + * @return array The filtered arguments. + */ + public function get_content_headings() { + return $this->filter_content_headings( $this->setup_content_headings() ); + } + + /** + * Filter the content headings arguments + * + * @since 4.13.3 + * + * @param array $arguments The list of arguments for headings and separators. + * + * @return array $arguments The filtered array of arguments. + */ + public function filter_content_headings( $arguments ) { + /** + * Applies a filter to the validation map for headings. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + $arguments = apply_filters( 'tribe_customizer_section_content_headings', $arguments, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Applies a filter to the validation map for headings for a specific section. Based on the section slug. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + return apply_filters( "tribe_customizer_section_{$section_slug}_content_headings", $arguments, $this ); + } + + /** + * Sugar syntax to add heading and separator sections to the customizer content. + * These are controls only in name: they do not actually control or save any setting. + * + * @since 4.13.3 + * + * @param WP_Customize_Manager $manager The instance of the Customizer Manager. + * @param string $name HTML name Attribute name of the setting. + * @param array $arguments The control arguments. + * + */ + protected function add_heading( $section, $manager, $name, $args ) { + $this->add_control( $section, $manager, $name, $args ); + } + + /* Controls */ + + /** + * Sets up the Customizer controls arguments. + * + * @since 4.13.3 + */ + public function setup_content_controls() {} + + /** + * Get a list (array) of accepted control types. + * In the format slug => control class name. + * + * @since 4.13.3 + * + * @return array The array of control types and their associated classes. + */ + public function get_accepted_control_types() { + $accepted_control_types = [ + 'checkbox' => WP_Customize_Control::class, + 'color' => WP_Customize_Color_Control::class, + 'default' => WP_Customize_Control::class, + 'dropdown-pages' => WP_Customize_Control::class, + 'heading' => Heading::class, + 'image' => WP_Customize_Image_Control::class, + 'radio' => Radio::class, + 'select' => WP_Customize_Control::class, + 'separator' => Separator::class, + 'text' => WP_Customize_Control::class, + 'textarea' => WP_Customize_Control::class, + 'number' => Number::class, + 'range-slider' => Range_Slider::class, + 'toggle' => Toggle::class, + ]; + + /** + * Allows filtering the accepted control types. + * + * @since 4.13.3 + * + * @param array $control_types The map of keys to WP Control classes. + */ + return apply_filters( 'tribe_customizer_accepted_control_types', $accepted_control_types, $this ); + } + + /** + * Determine if a control type is in our list of accepted ones. + * + * @since 4.13.3 + * + * @param string $type The "slug" of the control type. + * + * @return boolean If a control type is in our list of accepted ones. + */ + public function is_control_type_accepted( $type ) { + $types = $this->get_accepted_control_types(); + + if ( empty( $type ) ) { + return false; } - // Only Apply if getting the full options or Section - if ( is_array( $search ) && count( $search ) > 1 ) { - return $settings; + if ( empty( $types[ $type ] ) ) { + return false; } - if ( is_array( $search ) && count( $search ) === 1 ) { - $settings = $this->create_ghost_settings( $settings ); - } else { - $settings[ $this->ID ] = $this->create_ghost_settings( $settings[ $this->ID ] ); + if ( ! class_exists( $types[ $type ] ) ) { + return false; } - return $settings; + return true; } /** - * Register this Section + * Gets the class object associated with a control type. + * + * @since 4.13.3 * - * @param array $sections Array of Sections - * @param Tribe__Customizer $customizer Our internal Cutomizer Class Instance + * @param string $type The "slug" of the control type. * - * @return array Return the modified version of the Section array + * @return object|false The control type class or false if type not found. */ - public function register( $sections, Tribe__Customizer $customizer ) { - $sections[ $this->ID ] = $this->arguments; + public function get_control_type( $type ) { + $types = $this->get_accepted_control_types(); - return $sections; + if ( empty( $type ) ) { + return $types[ 'default' ]; + } + + if ( empty( $types[ $type ] ) ) { + return false; + } + + return $types[ $type ]; + } + + /** + * Get the (filtered) content control arguments. + * @see filter_content_controls() + * + * @since 4.13.3 + * + * @return array The filtered arguments. + */ + public function get_content_controls() { + return $this->filter_content_controls( $this->setup_content_controls() ); + } + + /** + * Filter the content control arguments + * + * @since 4.13.3 + * + * @param array $arguments The list of arguments for controls. + * + * @return array $arguments The filtered array of arguments. + */ + public function filter_content_controls( $arguments ) { + /** + * Applies a filter to the validation map for controls. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + $arguments = apply_filters( 'tribe_customizer_section_content_controls', $arguments, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Applies a filter to the validation map for controls for a specific section. Based on the section slug. + * + * @since 4.13.3 + * + * @param array $arguments Current set of callbacks for arguments. + * @param static $instance The section instance we are dealing with. + */ + return apply_filters( "tribe_customizer_section_{$section_slug}_content_controls", $arguments, $this ); + } + + /** + * Sugar syntax to add a control to the customizer content. + * + * @since 4.13.3 + * + * @param WP_Customize_Manager $manager The instance of the Customizer Manager. + * @param string $setting_name HTML name Attribute name of the setting. + * @param array $arguments The control arguments. + */ + protected function add_control( $section, $manager, $setting_name, $args ) { + // Validate our control choice. + if ( ! isset( $args['type'] ) ) { + return; + } + + $type = (string) $args['type']; + + if ( ! $this->is_control_type_accepted( $type ) ) { + return; + } + + $type = $this->get_control_type( $type ); + + if ( $section instanceof WP_Customize_Section ) { + $section = (string) $section->id; + } + + if ( ! is_string( $section ) ) { + return; + } + + // Get the default values. + $defaults = [ + 'section' => $section, + ]; + + $args = array_merge( $defaults, $args ); + + $manager->add_control( + new $type( + $manager, + $setting_name, + $args + ) + ); + } + + /* CSS Output Functions */ + + public function setup_css_template( $template ) { + $template = $this->filter_css_template( $this->get_css_template( $template ) ); + + return $template; + } + + /** + * Overwrite this method to be able to implement the CSS template related to this section. + * + * @return string The CSS template. + */ + public function get_css_template( $template ) { + return $template; + } + + /** + * Filter the content headings arguments + * + * @since 4.13.3 + * + * @param array $arguments The list of arguments for headings and separators. + * + * @return array $arguments The filtered array of arguments. + */ + public function filter_css_template( $template ) { + /** + * Applies a filter to the css output. + * Note this is appended to the output - so it's not inside any selectors! + * + * @since 4.13.3 + * + * @param array $template Current set of callbacks for css output. + * @param static $instance The section instance we are dealing with. + */ + $template = apply_filters( 'tribe_customizer_section_css_template', $template, $this ); + + $section_slug = static::get_section_slug( get_class( $this ) ); + + /** + * Applies a filter to the css output for a specific section. Based on the section slug. + * Note this is appended to the output - so it's not inside any selectors! + * + * @since 4.13.3 + * + * @param array $template Current set of callbacks for css output. + * @param static $instance The section instance we are dealing with. + */ + $template = apply_filters( "tribe_customizer_section_{$section_slug}_css_template", $template, $this ); + + return $template; + } + + /** + * Utility function for when we need a color in RGB format, + * since the Customizer always works with hex. Keepin' it DRY. + * + * @since 4.14.2 + * + * @param string $option The option slug, like "grid-lines-color" + * @param string $section The optional section slug, like 'global_elements' + * + * @return string $color_rgb The hex color expressed as an rgb string, like "255,255,255" + */ + public function get_rgb_color( $option, $section = null ) { + $color = is_null( $section ) + ? tribe( 'customizer' )->get_option( [ $this->ID, $option ] ) + : tribe( 'customizer' )->get_option( [ $section, $option ] ); + + $color_obj = new Tribe__Utils__Color( $color ); + $color_arr = $color_obj->getRgb(); + $color_rgb = $color_arr['R'] . ',' . $color_arr['G'] . ',' . $color_arr['B']; + return $color_rgb; } } diff --git a/tribe-common/src/Tribe/Data.php b/tribe-common/src/Tribe/Data.php index ab39e283ad..7679fa9438 100644 --- a/tribe-common/src/Tribe/Data.php +++ b/tribe-common/src/Tribe/Data.php @@ -67,7 +67,6 @@ public function __construct( $data = [], $default = false ) { * The return value will be casted to boolean if non-boolean was returned. * @since 4.11.0 */ - #[\ReturnTypeWillChange] public function offsetExists( $offset ) { return isset( $this->data[ $offset ] ); } @@ -82,7 +81,6 @@ public function offsetExists( $offset ) { * @return mixed Can return all value types. * @since 4.11.0 */ - #[\ReturnTypeWillChange] public function offsetGet( $offset ) { return isset( $this->data[ $offset ] ) ? $this->data[ $offset ] @@ -102,7 +100,6 @@ public function offsetGet( $offset ) { * @return void * @since 4.11.0 */ - #[\ReturnTypeWillChange] public function offsetSet( $offset, $value ) { $this->data[ $offset ] = $value; } @@ -117,7 +114,6 @@ public function offsetSet( $offset, $value ) { * @return void * @since 4.11.0 */ - #[\ReturnTypeWillChange] public function offsetUnset( $offset ) { unset( $this->data[ $offset ] ); } diff --git a/tribe-common/src/Tribe/Date_Utils.php b/tribe-common/src/Tribe/Date_Utils.php index ec5880a2b1..d521915b14 100755 --- a/tribe-common/src/Tribe/Date_Utils.php +++ b/tribe-common/src/Tribe/Date_Utils.php @@ -572,6 +572,78 @@ public static function is_all_day( $all_day_value ) { ); } + /** + * Determine if "now" is between two dates. + * + * @since 5.0.2 + * + * @param string|DateTime|int $start_date A `strtotime` parsable string, a DateTime object or a timestamp. + * @param string|DateTime|int $end_date A `strtotime` parsable string, a DateTime object or a timestamp. + * @param string|DateTime|int $now A `strtotime` parsable string, a DateTime object or a timestamp. Defaults to 'now'. + * + * @return boolean Whether the current datetime (or passed "now") is between the passed start and end dates. + */ + public static function is_now( $start_date, $end_date, $now = 'now' ) : bool { + $now = self::build_date_object( $now ); + $start_date = self::build_date_object( $start_date ); + $end_date = self::build_date_object( $end_date ); + + // If the dates are identical, bail early. + if ( $start_date === $end_date ) { + return false; + } + + // Handle dates passed out of chronological order. + [ $start_date, $end_date ] = self::sort( [ $start_date, $end_date ] ); + + // If span starts after now, return false. + if ( $start_date > $now ) { + return false; + } + + // If span ends on or before now, return false. + if ( $end_date <= $now ) { + return false; + } + + return true; + } + + /** + * Sort an array of dates. + * + * @since 5.0.2 + * + * @param mixed $dates A single array of dates, or dates passed as individual params. + * Individual dates can be a `strtotime` parsable string, a DateTime object or a timestamp. + * @param string $direction 'ASC' or 'DESC' for ascending/descending sorting. Defaults to 'ASC'. + * + * @return array A sorted array of DateTime objects. + */ + public static function sort( array $dates, string $direction = 'ASC' ) :array { + // If we get passed a single array, break it out of the containing array. + if ( is_array( $dates[0] ) ) { + $dates = $dates[0]; + } + + // Ensure we're always dealing with date objects here. + $dates = array_map( + function( $date ) { + return self::build_date_object( $date ); + }, + $dates + ); + + // If anything other than 'DESC' gets passed (or nothing) we sort ascending. + if ( 'DESC' === $direction ) { + rsort( $dates ); + } else { + sort( $dates ); + } + + return $dates; + } + /** * Given 2 datetime ranges, return whether the 2nd one occurs during the 1st one * Note: all params should be unix timestamps @@ -635,7 +707,15 @@ public static function wp_strtotime( $string ) { return strtotime( $string ); } - $tz = get_option( 'timezone_string' ); + $cache = tribe( 'cache' ); + if ( ! isset( $cache['option_timezone_string'] ) ) { + $cache['option_timezone_string'] = get_option( 'timezone_string' ); + } + if ( ! isset( $cache['option_gmt_offset'] ) ) { + $cache['option_gmt_offset'] = get_option( 'gmt_offset' ); + } + + $tz = $cache['option_timezone_string']; if ( ! empty( $tz ) ) { $date = date_create( $string, new DateTimeZone( $tz ) ); if ( ! $date ) { @@ -644,7 +724,7 @@ public static function wp_strtotime( $string ) { $date->setTimezone( new DateTimeZone( 'UTC' ) ); return $date->format( 'U' ); } else { - $offset = (float) get_option( 'gmt_offset' ); + $offset = (float) $cache['option_gmt_offset']; $seconds = intval( $offset * HOUR_IN_SECONDS ); $timestamp = strtotime( $string ) - $seconds; return $timestamp; @@ -1233,7 +1313,7 @@ public static function unescape_date_format( $date_format ) { * * @since 4.9.5 * - * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or + * @param string|DateTime|int $datetime A `strtotime` parsable string, a DateTime object or * a timestamp; defaults to `now`. * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object; * defaults to the site timezone; this parameter is ignored @@ -1294,7 +1374,7 @@ public static function build_date_object( $datetime = 'now', $timezone = null, $ * * @param string $date The date string that should validated. * - * @return bool Whether the date string can be used to build DateTime objects, and is thus parse-able by functions + * @return bool Whether the date string can be used to build DateTime objects, and is thus parsable by functions * like `strtotime`, or not. */ public static function is_valid_date( $date ) { @@ -1532,7 +1612,7 @@ public static function interval( $interval_spec ) { * * @since 4.10.2 * - * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or + * @param string|DateTime|int $datetime A `strtotime` parsable string, a DateTime object or * a timestamp; defaults to `now`. * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object; * defaults to the site timezone; this parameter is ignored @@ -1580,7 +1660,7 @@ static function immutable( $datetime = 'now', $timezone = null, $with_fallback = * * @since 4.10.2 * - * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or + * @param string|DateTime|int $datetime A `strtotime` parsable string, a DateTime object or * a timestamp; defaults to `now`. * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object; * defaults to the site timezone; this parameter is ignored diff --git a/tribe-common/src/Tribe/Dialog/View.php b/tribe-common/src/Tribe/Dialog/View.php index 9ae15ca673..3e560ccc46 100644 --- a/tribe-common/src/Tribe/Dialog/View.php +++ b/tribe-common/src/Tribe/Dialog/View.php @@ -171,7 +171,7 @@ public function render_modal( $content, $args = [], $id = null, $echo = true ) { $args = wp_parse_args( $args, $default_args ); - $this->render_dialog( $content, $args, $id, $echo ); + return $this->render_dialog( $content, $args, $id, $echo ); } /** @@ -240,7 +240,82 @@ public function render_confirm( $content, $args = [], $id = null, $echo = true ) $args = wp_parse_args( $args, $default_args ); - $this->render_dialog( $content, $args, $id, $echo ); + return $this->render_dialog( $content, $args, $id, $echo ); + } + + /** + * Syntactic sugar for `render_dialog()` to make creating custom confirmation dialogs easier. + * Adds sensible defaults for warning dialogs. + * + * @since 4.12.13 + * + * @param string $content Content as an HTML string. + * @param array $args { + * List of arguments to override dialog template. + * + * @type string $button_id The ID for the trigger button (optional). + * @type array $button_classes Any desired classes for the trigger button (optional). + * @type array $button_attributes Any desired attributes for the trigger button (optional). + * @type boolean $button_disabled Should the button be disabled (optional). + * @type string $button_text The text for the dialog trigger button ("Open the dialog window"). + * @type string $button_type The type for the trigger button (optional). + * @type string $button_value The value for the trigger button (optional). + * @type boolean $button_display If the dialog button should be displayed or not (optional). + * @type string $cancel_button_text Text for the "Cancel" button ("Cancel"). + * @type string $cancel_button_classes Any desired classes for the cancel button (optional). + * @type string $content_classes The dialog content classes ("tribe-dialog__content tribe-confirm__content"). + * @type string $continue_button_text Text for the "Continue" button ("Confirm"). + * @type string $continue_button_classes Any desired classes for the continue button (optional). + * @type array $context Any additional context data you need to expose to this file (optional). + * @type string $id The unique ID for this dialog (`uniqid()`). + * @type string $template The dialog template name (confirm). + * @type string $title The dialog title (optional). + * @type string $trigger_classes Classes for the dialog trigger ("tribe_dialog_trigger"). + * + * Dialog script option overrides. + * + * @type string $append_target The dialog will be inserted after the button, you could supply a selector string here to override (optional). + * @type boolean $body_lock Whether to lock the body while dialog open (true). + * @type string $close_button_aria_label Aria label for the close button (optional). + * @type string $close_button_classes Classes for the close button ("tribe-dialog__close-button--hidden"). + * @type string $close_event The dialog close event hook name (`tribe_dialog_close_confirm`). + * @type string $content_wrapper_classes Dialog content wrapper classes. This wrapper includes the close button ("tribe-dialog__wrapper tribe-confirm__wrapper"). + * @type string $effect CSS effect on open. none or fade (optional). + * @type string $effect_easing A css easing string to apply ("ease-in-out"). + * @type int $effect_speed CSS effect speed in milliseconds (optional). + * @type string $overlay_classes The dialog overlay classes ("tribe-dialog__overlay tribe-confirm__overlay"). + * @type boolean $overlay_click_closes If clicking the overlay closes the dialog (false). + * @type string $show_event The dialog event hook name (`tribe_dialog_show_confirm`). + * @type string $wrapper_classes The wrapper class for the dialog ("tribe-dialog"). + * } + * @param string $id The unique ID for this dialog. Gets prepended to the data attributes. Generated if not passed (`uniqid()`). + * @param boolean $echo Whether to echo the script or to return it (default: true). + * + * @return string An HTML string of the dialog. + */ + public function render_warning( $content, $args = [], $id = null, $echo = true ) { + $default_args = [ + 'body_lock' => true, + 'button_display' => false, + 'cancel_button_text' => __( 'Cancel', 'tribe-common' ), + 'cancel_button_classes' => 'tribe-dialog__button tribe-dialog__button-cancel tribe-common-c-btn tribe-common-c-btn-border', + 'continue_button_text' => __( 'OK', 'tribe-common' ), + 'continue_button_classes' => 'tribe-dialog__button tribe-dialog__button-continue tribe-common-c-btn-border tribe-common-c-btn-border--alt', + 'close_button_aria_label' => '', + 'close_button_classes' => 'tribe-dialog__close-button--hidden', + 'close_event' => 'tribe_dialog_close_confirm', + 'content_classes' => 'tribe-dialog__content tribe-confirm__content', + 'content_wrapper_classes' => 'tribe-dialog__wrapper tribe-confirm__wrapper', + 'overlay_classes' => 'tribe-dialog__overlay tribe-modal__overlay tribe-warning__overlay', + 'overlay_click_closes' => false, + 'show_event' => 'tribe_dialog_show_confirm', + 'template' => 'warning', + 'title_classes' => [ 'tribe-dialog__title', 'tribe-confirm__title' ], + ]; + + $args = wp_parse_args( $args, $default_args ); + + return $this->render_dialog( $content, $args, $id, $echo ); } /** @@ -308,7 +383,7 @@ public function render_alert( $content, $args = [], $id = null, $echo = true ) { $args = wp_parse_args( $args, $default_args ); - $this->render_dialog( $content, $args, $id, $echo ); + return $this->render_dialog( $content, $args, $id, $echo ); } /** @@ -329,8 +404,10 @@ public function render_alert( $content, $args = [], $id = null, $echo = true ) { * @type string $button_type The type for the trigger button (optional). * @type string $button_value The value for the trigger button (optional). * @type boolean $button_display If the dialog button should be displayed or not (optional). + * @type string $cancel_button_classes Any desired classes for the cancel button (optional). * @type string $close_event The dialog event hook name (`tribe_dialog_close_dialog`). * @type string $content_classes The dialog content classes ("tribe-dialog__content"). + * @type string $continue_button_classes Any desired classes for the continue button (optional). * @type string $title_classes The dialog title classes ("tribe-dialog__title"). * @type array $context Any additional context data you need to expose to this file (optional). * @type string $id The unique ID for this dialog (`uniqid()`). @@ -367,6 +444,8 @@ private function build_dialog( $content, $id, $args ) { 'button_type' => '', 'button_value' => '', 'button_display' => true, + 'cancel_button_classes' => 'tribe-dialog__button tribe-dialog__button-cancel tribe-common-c-btn-border tribe-common-c-btn-border--alt', + 'continue_button_classes' => 'tribe-dialog__button tribe-dialog__button-continue tribe-common-c-btn tribe-common-c-btn-border', 'close_event' => 'tribe_dialog_close_dialog', 'content_classes' => 'tribe-dialog__content', 'context' => '', diff --git a/tribe-common/src/Tribe/Editor.php b/tribe-common/src/Tribe/Editor.php index 81d6f13073..7d4277e83e 100644 --- a/tribe-common/src/Tribe/Editor.php +++ b/tribe-common/src/Tribe/Editor.php @@ -8,7 +8,16 @@ class Tribe__Editor { /** - * Meta key for flaging if a post is from classic editor + * Key we store the toggle under in the tribe_events_calendar_options array. + * + * @since 4.14.13 + * + * @var string + */ + public static $blocks_editor_key = 'toggle_blocks_editor'; + + /** + * Meta key for flagging if a post is from Classic Editor * * @since 4.8 * @@ -17,34 +26,53 @@ class Tribe__Editor { public $key_flag_classic_editor = '_tribe_is_classic_editor'; /** - * Utility function to check if we should load the blocks or not based on two assumptions - * - * a) Is gutenberg active? - * b) Is the blocks editor active? + * Utility function to check if we should load the blocks or not. * * @since 4.8 * * @return bool */ public function should_load_blocks() { - $gutenberg = $this->is_gutenberg_active() || $this->is_wp_version(); - $blocks = $this->is_blocks_editor_active(); - $classic = $this->is_classic_plugin_active() || $this->is_classic_option_active(); - - $should_load_blocks = $gutenberg && $blocks && ! $classic; + $should_load_blocks = (boolean) $this->are_blocks_enabled(); /** - * Filters whether the Blocks Editor should be activated or not. + * Filters whether the Blocks Editor should be activated or not for events. * * @since 4.12.0 * - * @param bool $should_load_blocks Whether the blocks editor should be activated or not. + * @param bool $should_load_blocks Whether the blocks editor should be activated or not for events. */ - $should_load_blocks = (bool) apply_filters( 'tribe_editor_should_load_blocks', $should_load_blocks ); + $should_load_blocks= (bool) apply_filters( 'tribe_editor_should_load_blocks', $should_load_blocks ); return $should_load_blocks; } + /** + * Utility function to check if blocks are enabled based on two assumptions + * + * a) Is gutenberg active? + * 1) Via plugin or WP version + * b) Is the blocks editor active? + * 1) Based on the enqueue_block_assets action. + * + * @since 4.14.13 + * + * @return bool + */ + public function are_blocks_enabled() { + $gutenberg = $this->is_gutenberg_active() || $this->is_wp_version(); + $blocks_enabled = $gutenberg && $this->is_blocks_editor_active(); + + /** + * Filters whether the Blocks Editor is enabled or not. + * + * @since 4.14.13 + * + * @param bool $should_load_blocks Whether the Blocks Editor is enabled or not. + */ + return (bool) apply_filters( 'tribe_editor_are_blocks_enabled', $blocks_enabled ); + } + /** * Checks if we are on version 5.0-alpha or higher where we no longer have * Gutenberg Project, but the Blocks Editor @@ -60,49 +88,26 @@ public function is_wp_version() { } /** - * Checks if we have Gutenberg Project online, only useful while - * its a external plugin + * Checks if we have the Gutenberg Project plugin active. * * @since 4.8 * * @return boolean */ - public function is_gutenberg_active() { - return function_exists( 'the_gutenberg_project' ); + private function is_gutenberg_active() { + return function_exists( 'gutenberg_register_scripts_and_styles' ); } /** - * Checks if we have Editor Block active + * Checks if we have Editor Block active. * * @since 4.8 + * @since 4.14.13 Switch to using the `enqueue_block_assets` check that the Classic Editor plugin uses * * @return boolean */ public function is_blocks_editor_active() { - return function_exists( 'register_block_type' ) && function_exists( 'unregister_block_type' ); - } - - /** - * Adds the required fields into the Events Post Type so that we can use Block Editor - * - * @since 4.8 - * - * @param array $args Arguments used to setup the Post Type - * - * @return array - */ - public function add_support( $args = [] ) { - // Make sure we have the Support argument and it's an array - if ( ! isset( $args['supports'] ) || ! is_array( $args['supports'] ) ) { - $args['supports'] = []; - } - - // Add Editor Support - if ( ! in_array( 'editor', $args['supports'] ) ) { - $args['supports'][] = 'editor'; - } - - return $args; + return has_action( 'enqueue_block_assets' ); } /** @@ -161,91 +166,125 @@ public function add_rest_support( $args = [] ) { } /** - * classic_editor_replace is function that is created by the plugin: - * - * @see https://wordpress.org/plugins/classic-editor/ - * - * prior 1.3 version the classic editor plugin was bundle inside of a unique function: - * `classic_editor_replace` now all is bundled inside of a class `Classic_Editor` + * Detect if the Classic Editor is force-activated via plugin or if it comes from a request. * * @since 4.8 + * @todo Deprecate before 6.0. * * @return bool */ - public function is_classic_plugin_active() { - $is_plugin_active = function_exists( 'classic_editor_replace' ) || class_exists( 'Classic_Editor' ); + public function is_classic_editor() { + return ! $this->should_load_blocks(); + + _deprecated_function( __FUNCTION__, '4.14.13', 'should_load_blocks' ); /** - * Filter to change the output of calling: `is_classic_plugin_active` + * Allow other addons to disable Classic Editor based on options. * - * @since 4.9.12 + * @since 4.8.5 + * @deprecated 4.14.13 * - * @param $is_plugin_active bool Value that indicates if the plugin is active or not. + * @param bool $classic_is_active Whether the Classic Editor should be used. */ - return apply_filters( 'tribe_is_classic_editor_plugin_active', $is_plugin_active ); + return apply_filters_deprecated( + 'tribe_editor_classic_is_active', + [false], + '4.14.13', + 'tribe_editor_should_load_blocks', + 'This has been deprecated in favor of the filter in should_load_blocks(). Note however that the logic is inverted!' + ); } + /* DEPRECATED FUNCTIONS */ + /** - * Check if the setting `'classic-editor-replace'` is set to `replace` that option means to - * replace the gutenberg editor with the classic editor. - * - * Prior to 1.3 on classic editor plugin the value to identify if is on classic the value - * was `replace`, now the value is `classic` + * Adds the required fields into the Events Post Type so that we can use Block Editor * * @since 4.8 + * @deprecated 4.14.13 This is not used anywhere. * - * @return bool + * @param array $args Arguments used to setup the Post Type + * + * @return array */ - public function is_classic_option_active() { - $valid_values = [ 'replace', 'classic' ]; + public function add_support( $args = [] ) { + _deprecated_function( __FUNCTION__, '4.14.13' ); + // Make sure we have the Support argument and it's an array + if ( ! isset( $args['supports'] ) || ! is_array( $args['supports'] ) ) { + $args['supports'] = []; + } + + // Add Editor Support + if ( ! in_array( 'editor', $args['supports'] ) ) { + $args['supports'][] = 'editor'; + } - return in_array( (string) get_option( 'classic-editor-replace' ), $valid_values, true ); + return $args; } /** - * Detect if the classic editor is force-activated via plugin or if it comes from a request. + * classic_editor_replace is function that is created by the plugin: + * used in ECP recurrence and TEC Meta + * + * @see https://wordpress.org/plugins/classic-editor/ + * + * prior 1.3 version the Classic Editor plugin was bundled inside of a unique function: + * `classic_editor_replace` now all is bundled inside of a class `Classic_Editor` * * @since 4.8 + * @deprecated 4.14.13 * * @return bool */ - public function is_classic_editor() { - $disabled_by_plugin = $this->is_classic_plugin_active() && $this->is_classic_option_active(); + public function is_classic_plugin_active() { + _deprecated_function( __FUNCTION__, '4.14.13', 'Tribe\Editor\Compatibility\Classic_Editor::is_classic_plugin_active' ); - /** - * Allow other addons to disable classic editor based on options. - * - * @since 4.8.5 - * - * @param bool $classic_is_active Whether the classic editor should be used. - */ - $disabled_by_filter = apply_filters( 'tribe_editor_classic_is_active', false ); + return Tribe\Editor\Compatibility\Classic_Editor::is_classic_plugin_active(); + } - $is_classic_editor_request = tribe_get_request_var( 'classic-editor', null ); + /** + * Check if the setting `classic-editor-replace` is set to `replace` that option means to + * replace the gutenberg editor with the Classic Editor. + * + * Prior to 1.3 on Classic Editor plugin the value to identify if is on classic the value + * was `replace`, now the value is `classic` + * + * @since 4.8 + * @deprecated 4.14.13 + * + * @return bool + */ + public function is_classic_option_active() { + // _deprecated_function( __FUNCTION__, '4.14.13', 'Tribe\Editor\Compatibility\Classic_Editor::is_classic_option_active' ); - return $is_classic_editor_request || $disabled_by_plugin || $disabled_by_filter; + return Tribe\Editor\Compatibility\Classic_Editor::is_classic_option_active(); } /** - * Whether the events are being served using Blocks or the Classical Editor. + * Whether the TEC setting dictates Blocks or the Classic Editor. + * used in ET, ET+ and TEC * * @since 4.12.0 + * @todo Deprecate before 6.0. * - * @return bool True if using Blocks. False if using the Classical Editor. + * @return bool True if using Blocks. False if using the Classic Editor. */ public function is_events_using_blocks() { + return $this->should_load_blocks(); + + _deprecated_function( __FUNCTION__, '4.14.13', 'should_load_blocks'); /** * Whether the event is being served through blocks - * or the classical editor. + * or the Classic Editor. * * @since 4.12.0 * - * @param bool $is_using_blocks True if using blocks. False if using the classical editor. + * @param bool $is_using_blocks True if using blocks. False if using the Classic Editor. */ - $is_using_blocks = apply_filters( 'tribe_is_using_blocks', null ); + $is_using_blocks = apply_filters_deprecated( 'tribe_is_using_blocks', null, '4.14.13', 'tribe_editor_should_load_blocks', 'Function is slated for deprecation. Please use should_load_blocks, above.' ); // Early bail: The filter was overridden to return either true or false. if ( null !== $is_using_blocks ) { - return $is_using_blocks; + return (bool) $is_using_blocks; } // Early bail: The site itself is not using blocks. @@ -253,6 +292,6 @@ public function is_events_using_blocks() { return false; } - return tribe_is_truthy( tribe_get_option( 'toggle_blocks_editor' ) ); + return tribe_is_truthy( tribe_get_option( 'toggle_blocks_editor', false ) ); } } diff --git a/tribe-common/src/Tribe/Editor/Assets.php b/tribe-common/src/Tribe/Editor/Assets.php index 8f4a363b27..c816f29e1e 100644 --- a/tribe-common/src/Tribe/Editor/Assets.php +++ b/tribe-common/src/Tribe/Editor/Assets.php @@ -36,17 +36,7 @@ public function register() { /** * @todo revise this dependencies */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -71,20 +61,7 @@ public function register() { $plugin, 'tribe-common-gutenberg-utils', 'app/utils.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -92,24 +69,12 @@ public function register() { 'priority' => 12, ] ); + tribe_asset( $plugin, 'tribe-common-gutenberg-store', 'app/store.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -117,24 +82,12 @@ public function register() { 'priority' => 13, ] ); + tribe_asset( $plugin, 'tribe-common-gutenberg-icons', 'app/icons.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -142,24 +95,12 @@ public function register() { 'priority' => 14, ] ); + tribe_asset( $plugin, 'tribe-common-gutenberg-hoc', 'app/hoc.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -167,24 +108,12 @@ public function register() { 'priority' => 15, ] ); + tribe_asset( $plugin, 'tribe-common-gutenberg-components', 'app/components.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -192,24 +121,12 @@ public function register() { 'priority' => 16, ] ); + tribe_asset( $plugin, 'tribe-common-gutenberg-elements', 'app/elements.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -217,6 +134,7 @@ public function register() { 'priority' => 17, ] ); + /** * @todo: figure out why element styles are loading for tickets but not events. */ @@ -224,20 +142,7 @@ public function register() { $plugin, 'tribe-common-gutenberg-components', 'app/components.js', - /** - * @todo revise this dependencies - */ - [ - 'react', - 'react-dom', - 'wp-components', - 'wp-api', - 'wp-api-request', - 'wp-blocks', - 'wp-i18n', - 'wp-element', - 'wp-editor', - ], + [ $this, 'filter_event_blocks_editor_deps' ], 'enqueue_block_editor_assets', [ 'in_footer' => false, @@ -256,4 +161,37 @@ public function register() { ] ); } + + /** + * Filter the dependencies for event blocks + * + * @since 4.14.2 + * + * @param array|object|null $assets Array of asset objects, single asset object, or null. + * + * @return array An array of dependency slugs. + */ + public function filter_event_blocks_editor_deps( $asset ) { + global $pagenow; + + $deps = [ + 'react', + 'react-dom', + 'wp-components', + 'wp-api', + 'wp-api-request', + 'wp-blocks', + 'wp-i18n', + 'wp-element', + 'wp-editor', + ]; + + if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) { + if ( ( $key = array_search( 'wp-editor', $deps ) ) !== false ) { + unset( $deps[ $key ] ); + } + } + + return $deps; + } } diff --git a/tribe-common/src/Tribe/Editor/Blocks/Abstract.php b/tribe-common/src/Tribe/Editor/Blocks/Abstract.php index 7474ce2f5e..919223ac69 100644 --- a/tribe-common/src/Tribe/Editor/Blocks/Abstract.php +++ b/tribe-common/src/Tribe/Editor/Blocks/Abstract.php @@ -124,8 +124,8 @@ public function ajax() { } /** - * Does the registration for PHP rendering for the Block, important due to been - * an dynamic Block + * Does the registration for PHP rendering for the Block, + * important due to being a dynamic Block * * @since 4.8 * @@ -136,8 +136,20 @@ public function register() { 'render_callback' => [ $this, 'render' ], ]; + // Prevents a block from being registered twice. + if ( ! class_exists( 'WP_Block_Type_Registry' ) || WP_Block_Type_Registry::get_instance()->is_registered( $this->name() ) ) { + return; + } + register_block_type( $this->name(), $block_args ); + } + /** + * Registering the block and loading the assets and hooks should be handled separately. + * + * @since 4.14.13 + */ + public function load() { add_action( 'wp_ajax_' . $this->get_ajax_action(), [ $this, 'ajax' ] ); $this->assets(); diff --git a/tribe-common/src/Tribe/Editor/Compatibility.php b/tribe-common/src/Tribe/Editor/Compatibility.php new file mode 100644 index 0000000000..62eaf87796 --- /dev/null +++ b/tribe-common/src/Tribe/Editor/Compatibility.php @@ -0,0 +1,31 @@ +container->singleton( self::class, $this ); + $this->container->singleton( 'editor.compatibility', $this ); + + // Conditionally load compatibility for the Classic Editor plugin. + if ( Classic_Editor::is_classic_plugin_active() ) { + $this->container->singleton( 'editor.compatibility.classic-editor', Classic_Editor::class ); + tribe( 'editor.compatibility.classic-editor' )->init(); + } + + // Conditionally load compatibility for Divi themes. + if ( Divi::is_divi_active() ) { + $this->container->singleton( 'editor.compatibility.divi', Divi::class ); + tribe( 'editor.compatibility.divi' )->init(); + } + + } +} diff --git a/tribe-common/src/Tribe/Editor/Compatibility/Classic_Editor.php b/tribe-common/src/Tribe/Editor/Compatibility/Classic_Editor.php new file mode 100644 index 0000000000..2c2d0db339 --- /dev/null +++ b/tribe-common/src/Tribe/Editor/Compatibility/Classic_Editor.php @@ -0,0 +1,386 @@ +classic. + * Can be overridden by user choice. + * + * @since 4.14.13 + * + * @var string + */ + public static $classic_param = 'classic-editor'; + + /** + * "Classic Editor" term used for comparisons. + * + * @since 4.14.13 + * + * @var string + */ + public static $classic_term = 'classic'; + + /** + * "Blocks Editor" term used for comparisons. + * + * @since 4.14.13 + * + * @var string + */ + public static $block_term = 'block'; + + /** + * "Classic Editor" param for user override + * + * @since 4.14.13 + * + * @var string + */ + public static $classic_override = 'classic-editor__forget'; + + /** + * "User Choice" key for user override + * + * @since 4.14.13 + * + * @var string + */ + public static $user_choice_key = 'classic-editor-allow-users'; + + /** + * User meta "User Choice" key for user override + * + * @since 4.14.13 + * + * @var string + */ + public static $user_meta_choice_key = 'classic-editor-settings'; + + /** + * Post meta key used for CE "remembering" the last editor used. + * The bane of my existence. + * + * @since 4.14.13 + * + * @var string + */ + public static $post_remember_meta_key = 'classic-editor-remember'; + + /** + * Stores the values used by the Classic Editor plugin to indicate we're using the classic editor. + * + * @since 4.14.13 + * + * @var array + */ + public static $classic_values = [ + 'replace', + 'classic', + ]; + + /** + * Placeholders + * + * @since 4.14.13 + * + * @var [type] + */ + + /** + * Holds whether Classic Editor allows user choice of editors. + * + * @since 4.14.13 + * + * @var null|boolean + */ + public static $user_choice_allowed = null; + + /** + * Holds the user's preferred editor - set in user profile. + * + * @since 4.14.13 + * + * @var null|string + */ + public static $user_profile_choice = null; + + /** + * Holds the GET variable value for enabling the classic editor, if set. + * (ie the default editor set) + * + * @since 4.14.13 + * + * @var null|string + */ + public static $classic_url_param = null; + + /** + * Holds the GET variable value for overriding the classic editor, if set. + * (ie default is classic, this will change it to blocks) + * + * @since 4.14.13 + * + * @var null|string + */ + public static $classic_url_override = null; + + /** + * Registers the hooks and filters required based on if the Classic Editor plugin is active. + * + * @since 4.14.13 + */ + public function init() { + if ( static::is_classic_plugin_active() ) { + $this->hooks(); + } + } + + /** + * Hooks for loading logic outside this class. + * + * @since 4.14.13 + */ + public function hooks() { + add_action( 'tribe_plugins_loaded', [ $this, 'set_classic_url_params' ], 22 ); + add_filter( 'tribe_editor_should_load_blocks', [ $this, 'filter_tribe_editor_should_load_blocks' ], 20 ); + } + + /** + * Sets the placeholders for the URL params. + * + * @since 4.14.13 + */ + public function set_classic_url_params() { + static::$classic_url_param = static::get_classic_param(); + static::$classic_url_override = static::get_classic_override(); + } + + /** + * Gets the $classic_url_param placeholder if it's set. + * Sets it then returns it if it's not yet set. + * + * @since 4.14.13 + * + * @return boolean + */ + public static function get_classic_param () { + if ( null !== static::$classic_url_param ) { + return static::$classic_url_param; + } + + static::$classic_url_param = isset( $_GET[ static::$classic_param ] ) || isset( $_POST[ static::$classic_param ] ); + + return static::$classic_url_param; + } + + /** + * Gets the $classic_url_override placeholder if it's set. + * Sets it then returns it if it's not yet set. + * + * @since 4.14.13 + * + * @return boolean + */ + public static function get_classic_override() { + if ( null !== static::$classic_url_override ) { + return static::$classic_url_override; + } + + static::$classic_url_override = isset( $_GET[ static::$classic_override ] ) || isset( $_POST[ static::$classic_override ] ); + + return static::$classic_url_override; + } + + /** + * Filters tribe_editor_should_load_blocks based on internal logic. + * + * @since 4.14.13 + * + * @param boolean $should_load_blocks Whether we should force blocks over classic. + * + * @return boolean Whether we should force blocks or classic. + */ + public function filter_tribe_editor_should_load_blocks( bool $should_load_blocks ) { + + if ( ! static::is_classic_plugin_active() ) { + return $should_load_blocks; + } + + if ( static::is_classic_option_active() ) { + $should_load_blocks = false; + } + + if ( ! static::get_user_choice_allowed() ) { + return $should_load_blocks; + } + + if ( static::get_classic_param() ) { + $should_load_blocks = false; + } + + // The override param inverts whatever else is set via parameter/preference. + if ( static::get_classic_override() ) { + $should_load_blocks = ! $should_load_blocks; + } + + global $pagenow; + + // The profile and remember settings only apply to new posts/etc so bail out now if we're not in the admin and creating a new event. + if ( ! empty( $pagenow ) && ! in_array( $pagenow, [ 'post-new.php' ] ) ) { + $remember = static::classic_editor_remembers(); + + if ( false !== $remember ) { + $should_load_blocks = static::$block_term === $remember; + } + + return $should_load_blocks; + } + + $profile_choice = static::user_profile_choice(); + + // Only override via $profile_choice if it is actually set. + if ( empty( $profile_choice ) ) { + return $should_load_blocks; + } + + // Only override via $profile_choice if it contains an expected value. + if ( static::$block_term === $profile_choice ) { + $should_load_blocks = true; + } else if ( static::$classic_term === $profile_choice ) { + $should_load_blocks = false; + } + + // The override param inverts whatever else is set via parameter/preference. + if ( static::get_classic_override() ) { + $should_load_blocks = ! $should_load_blocks; + } + + return $should_load_blocks; + } + + /** + * classic_editor_replace is function that is created by the plugin: + * used in ECP recurrence and TEC Meta + * + * @see https://wordpress.org/plugins/classic-editor/ + * + * prior 1.3 version the Classic Editor plugin was bundle inside of a unique function: + * `classic_editor_replace` now all is bundled inside of a class `Classic_Editor` + * + * @since 4.14.13 + * + * @return bool + */ + public static function is_classic_plugin_active() { + $is_plugin_active = class_exists( 'Classic_Editor', false ); + /** + * Filter to change the output of calling: `is_classic_plugin_active` + * + * @since 4.9.12 + * @since 4.14.13 moved to separate class. + * + * @param $is_plugin_active bool Value that indicates if the plugin is active or not. + */ + return (boolean) apply_filters( 'tribe_is_classic_editor_plugin_active', $is_plugin_active ); + } + + /** + * Check if the setting `classic-editor-replace` is set to `replace` that option means to + * replace the gutenberg editor with the Classic Editor. + * + * Prior to 1.3 on Classic Editor plugin the value to identify if is on classic the value + * was `replace`, now the value is `classic` + * + * @since 4.8 + * @since 4.14.13 moved to separate class. + * + * @return bool + */ + public static function is_classic_option_active() { + if ( ! static::is_classic_plugin_active() ) { + return false; + } + + $valid_values = [ 'replace', 'classic' ]; + $replace = in_array( (string) get_option( static::$classic_option_key ), $valid_values, true ); + + return (boolean) $replace; + } + + + /** + * Get and store wether user choice is allowed - lets us bypass some checks. + * + * @since 4.14.13 + * + * @return boolean + */ + public static function get_user_choice_allowed() { + if ( null !== static::$user_choice_allowed ) { + return static::$user_choice_allowed; + } + + static::$user_choice_allowed = 'allow' === get_option( static::$user_choice_key, 'disallow' ); + + return static::$user_choice_allowed; + } + + /** + * Get the and store user's editor of choice - set in the user profile. + * + * @since 4.14.13 + * + * @return string + */ + public static function user_profile_choice() { + if ( null !== static::$user_profile_choice ) { + return static::$user_profile_choice; + } + + global $wpdb; + + $user = get_current_user_id(); + static::$user_profile_choice = get_user_option( $wpdb->prefix . static::$user_meta_choice_key, $user ); + + return static::$user_profile_choice; + } + + /** + * Get whether CE has "remembered" the last editor for a given post. + * If so, this is what the default edit link will send us to. + * + * @since 4.14.13 + * + * @return bool|string The string of the editor choice or false on fails. + */ + public static function classic_editor_remembers( $id = null ) { + if ( empty( $id ) ) { + $id = isset( $_GET[ 'post' ] ) ? (int) $_GET[ 'post' ] : null; + } + + + $remember = get_post_meta( $id, static::$post_remember_meta_key, true ); + + if ( empty( $remember ) ) { + return static::$block_term; + } + + // Why WP, why did you use a different term here? + return str_replace( '-editor', '', $remember ); + } +} diff --git a/tribe-common/src/Tribe/Editor/Compatibility/Divi.php b/tribe-common/src/Tribe/Editor/Compatibility/Divi.php new file mode 100644 index 0000000000..8c0e40fec2 --- /dev/null +++ b/tribe-common/src/Tribe/Editor/Compatibility/Divi.php @@ -0,0 +1,109 @@ +hooks(); + } + + /** + * Hooks for loading logic outside this class. + * + * @since 4.14.13 + */ + public function hooks() { + // Trying to filter out instances where we shouldn't add this to save cycles is futile. + add_filter( 'tribe_editor_should_load_blocks', [ $this, 'filter_tribe_editor_should_load_blocks' ], 20 ); + } + + public static function is_divi_active() { + /** @var Tribe__Cache $cache */ + $cache = tribe( 'cache' ); + + $divi = $cache->get( 'is_divi' ); + + if ( false !== $divi ) { + // Stored as an int - convert to a boolean. + return (bool) $divi; + } + + // OK, do it the hard way. + $theme = wp_get_theme(); + // Handle theme children and variations. + $divi = 'Divi' == $theme->name || 'Divi' == $theme->template || 'Divi' == $theme->parent_theme; + + // Cache to save us this work next time. + $cache->set( 'is_divi', (int) $divi, Tribe__Cache::NON_PERSISTENT, Tribe__Cache_Listener::TRIGGER_UPDATED_OPTION ); + + // Stored as an int - convert to a boolean. + return (bool) $divi; + } + + /** + * Filters tribe_editor_should_load_blocks based on internal logic. + * + * @since 4.14.13 + * + * @param boolean $should_load_blocks Whether we should force blocks over classic. + * + * @return boolean Whether we should force blocks or classic. + */ + public function filter_tribe_editor_should_load_blocks( $should_load_blocks ) { + // et_enable_classic_editor + $divi_option = get_option( 'et_divi', [] ); + + if ( empty( $divi_option[ static::$classic_key ] ) ) { + return $should_load_blocks; + } else if ( static::$classic_on === $divi_option[ static::$classic_key ] ) { + return false; + } + + return $should_load_blocks; + } +} diff --git a/tribe-common/src/Tribe/Editor/Configuration.php b/tribe-common/src/Tribe/Editor/Configuration.php index 3ad13e799b..349d377734 100644 --- a/tribe-common/src/Tribe/Editor/Configuration.php +++ b/tribe-common/src/Tribe/Editor/Configuration.php @@ -39,7 +39,7 @@ public function localize() { ], 'dateSettings' => $this->get_date_settings(), 'constants' => [ - 'hideUpsell' => ( defined( 'TRIBE_HIDE_UPSELL' ) && TRIBE_HIDE_UPSELL ), + 'hideUpsell' => tec_should_hide_upsell(), ], 'countries' => $languages_locations->get_countries( true ), 'usStates' => Tribe__View_Helpers::loadStates(), diff --git a/tribe-common/src/Tribe/Editor/Provider.php b/tribe-common/src/Tribe/Editor/Provider.php index 029537a010..912db4a7fe 100644 --- a/tribe-common/src/Tribe/Editor/Provider.php +++ b/tribe-common/src/Tribe/Editor/Provider.php @@ -14,9 +14,7 @@ public function register() { $this->container->singleton( 'editor.utils', 'Tribe__Editor__Utils' ); $this->container->singleton( 'common.editor.configuration', 'Tribe__Editor__Configuration' ); - if ( ! tribe( 'editor' )->should_load_blocks() ) { - return; - } + tribe_register_provider( Tribe\Editor\Compatibility::class ); $this->container->singleton( 'editor.assets', 'Tribe__Editor__Assets', [ 'hook' ] ); diff --git a/tribe-common/src/Tribe/Field.php b/tribe-common/src/Tribe/Field.php index ad14799e85..aa79712c62 100755 --- a/tribe-common/src/Tribe/Field.php +++ b/tribe-common/src/Tribe/Field.php @@ -1,6 +1,9 @@ valid_field_types = apply_filters( 'tribe_valid_field_types', $this->valid_field_types ); @@ -143,20 +148,21 @@ public function __construct( $id, $field, $value = null ) { $label_attributes = $args['label_attributes']; $tooltip = wp_kses( $args['tooltip'], [ - 'a' => [ 'href' => [], 'title' => [], 'target' => [] ], + 'a' => [ 'class' => [], 'href' => [], 'title' => [], 'target' => [], 'rel' => [] ], 'br' => [], - 'em' => [], - 'strong' => [], - 'b' => [], - 'i' => [], - 'u' => [], + 'em' => [ 'class' => [] ], + 'strong' => [ 'class' => [] ], + 'b' => [ 'class' => [] ], + 'i' => [ 'class' => [] ], + 'u' => [ 'class' => [] ], 'img' => [ + 'class' => [], 'title' => [], 'src' => [], 'alt' => [], ], 'code' => [ 'span' => [] ], - 'span' => [], + 'span' => [ 'class' => [] ], ] ); $fieldset_attributes = []; @@ -231,7 +237,7 @@ public function do_field() { } else { // fail, log the error - Tribe__Main::debug( esc_html__( 'Invalid field type specified', 'tribe-common' ), $this->type, 'notice' ); + Tribe__Debug::debug( esc_html__( 'Invalid field type specified', 'tribe-common' ), $this->type, 'notice' ); } } @@ -701,6 +707,80 @@ public function license_key() { return $field; } + /** + * Generate a color field. + * + * @since 5.0.0 + * + * @return string The field. + */ + public function color() { + + tribe( Settings::class )->maybe_load_color_field_assets(); + + $field = $this->do_field_start(); + $field .= $this->do_field_label(); + $field .= $this->do_field_div_start(); + $field .= 'do_field_name(); + $field .= $this->do_field_value(); + $field .= $this->do_field_attributes(); + $field .= '/>'; + $field .= $this->do_screen_reader_label(); + $field .= $this->do_field_div_end(); + $field .= $this->do_field_end(); + + return $field; + } + + /** + * Generate an image field. + * + * @since 5.0.0 + * + * @return string The field. + */ + public function image() { + + tribe( Settings::class )->maybe_load_image_field_assets(); + + $image_exists = ! empty( $this->value ); + $upload_image_text = esc_html__( 'Select Image', 'tribe-common' ); + $remove_image_text = esc_html__( 'Remove Image', 'tribe-common' ); + + // Add default fieldset attributes if none exist. + $image_fieldset_attributes = [ + 'data-select-image-text' => esc_html__( 'Select an image', 'tribe-common' ), + 'data-use-image-text' => esc_html__( 'Use this image', 'tribe-common' ), + ]; + $this->fieldset_attributes = array_merge( $image_fieldset_attributes, $this->fieldset_attributes ); + + $field = $this->do_field_start(); + $field .= $this->do_field_label(); + $field .= $this->do_field_div_start(); + $field .= 'do_field_name(); + $field .= $this->do_field_value(); + $field .= $this->do_field_attributes(); + $field .= '/>'; + $field .= ''; + $field .= ''; + $field .= ''; + $field .= $this->do_screen_reader_label(); + $field .= $this->do_field_div_end(); + $field .= $this->do_field_end(); + + return $field; + } + /* deprecated camelCase methods */ public function doField() { _deprecated_function( __METHOD__, '4.3', __CLASS__ . '::do_field' ); diff --git a/tribe-common/src/Tribe/JSON_LD/Abstract.php b/tribe-common/src/Tribe/JSON_LD/Abstract.php index 04ed7e21fe..fc86b90541 100755 --- a/tribe-common/src/Tribe/JSON_LD/Abstract.php +++ b/tribe-common/src/Tribe/JSON_LD/Abstract.php @@ -185,15 +185,6 @@ public function get_markup( $post = null, $args = [] ) { public function markup( $post = null, $args = [] ) { $html = $this->get_markup( $post, $args ); - /** - * Allows users to filter the end markup of JSON-LD - * - * @deprecated - * @todo Remove on 4.4 - * - * @param string The HTML for the JSON LD markup - */ - $html = apply_filters( 'tribe_google_data_markup_json', $html ); /** * Allows users to filter the end markup of JSON-LD * diff --git a/tribe-common/src/Tribe/Log/Admin.php b/tribe-common/src/Tribe/Log/Admin.php index 4a60af9db8..c5aa2472df 100644 --- a/tribe-common/src/Tribe/Log/Admin.php +++ b/tribe-common/src/Tribe/Log/Admin.php @@ -1,4 +1,6 @@ [ Tribe__Admin__Help_Page::instance(), 'is_current_page' ], + 'conditionals' => [ $this, 'should_enqueue_assets' ], 'localize' => (object) [ 'name' => 'tribe_logger_data', 'data' => [ @@ -117,6 +119,17 @@ public function register_script() { ); } + /** + * Checks wether the assets should be enqueued. + * + * @since 4.15.0 + * + * @return boolean True if the assets should be enqueued. + */ + public function should_enqueue_assets() { + return Tribe__Admin__Help_Page::instance()->is_current_page() || tribe( Troubleshooting::class )->is_current_page(); + } + /** * Returns a list of logs that are available for perusal. * @@ -165,7 +178,7 @@ protected function get_log_engines() { * * @return array */ - protected function get_log_entries( $log = null ) { + public function get_log_entries( $log = null ) { if ( $logger = $this->current_logger() ) { $logger->use_log( $log ); return (array) $logger->retrieve(); diff --git a/tribe-common/src/Tribe/Log/Service_Provider.php b/tribe-common/src/Tribe/Log/Service_Provider.php index 15370d4107..44480768c6 100644 --- a/tribe-common/src/Tribe/Log/Service_Provider.php +++ b/tribe-common/src/Tribe/Log/Service_Provider.php @@ -1,13 +1,12 @@ plugins_loaded(); + } } /** @@ -95,6 +107,7 @@ public function plugins_loaded() { $this->init_autoloading(); + $this->init_early_libraries(); $this->bind_implementations(); $this->init_libraries(); $this->add_hooks(); @@ -124,7 +137,10 @@ protected function init_autoloading() { $autoloader = Tribe__Autoloader::instance(); - $prefixes = [ 'Tribe__' => dirname( __FILE__ ) ]; + $prefixes = [ + 'TEC\\Common\\' => dirname( __DIR__ ) . '/Common', + 'Tribe__' => __DIR__, + ]; $autoloader->register_prefixes( $prefixes ); foreach ( glob( $this->plugin_path . 'src/deprecated/*.php' ) as $file ) { @@ -153,14 +169,25 @@ public function context_class() { return $this->plugin_context_class; } + /** + * Initializes all libraries used/required by our singletons. + * + * @since 4.14.18 + */ + public function init_early_libraries() { + require_once $this->plugin_path . 'src/functions/editor.php'; + } + /** * initializes all required libraries */ public function init_libraries() { require_once $this->plugin_path . 'src/functions/utils.php'; + require_once $this->plugin_path . 'src/functions/conditionals.php'; require_once $this->plugin_path . 'src/functions/url.php'; require_once $this->plugin_path . 'src/functions/query.php'; require_once $this->plugin_path . 'src/functions/multibyte.php'; + require_once $this->plugin_path . 'src/functions/files.php'; require_once $this->plugin_path . 'src/functions/template-tags/general.php'; require_once $this->plugin_path . 'src/functions/template-tags/date.php'; require_once $this->plugin_path . 'src/functions/template-tags/html.php'; @@ -172,7 +199,6 @@ public function init_libraries() { tribe( 'settings.manager' ); tribe( 'tracker' ); tribe( 'plugins.api' ); - tribe( 'pue.notices' ); tribe( 'ajax.dropdown' ); tribe( 'logger' ); } @@ -187,12 +213,13 @@ public function load_assets() { [ [ 'tribe-accessibility-css', 'accessibility.css' ], [ 'tribe-query-string', 'utils/query-string.js' ], - [ 'tribe-clipboard', 'vendor/clipboard/clipboard.js' ], + [ 'tribe-clipboard', 'node_modules/clipboard/dist/clipboard.min.js' ], [ 'datatables', 'vendor/datatables/datatables.js', [ 'jquery' ] ], [ 'tribe-select2', 'vendor/tribe-selectWoo/dist/js/selectWoo.full.js', [ 'jquery' ] ], [ 'tribe-select2-css', 'vendor/tribe-selectWoo/dist/css/selectWoo.css' ], [ 'tribe-utils-camelcase', 'utils-camelcase.js', [ 'underscore' ] ], [ 'tribe-moment', 'vendor/momentjs/moment.js' ], + [ 'tribe-moment-locales', 'vendor/momentjs/locale.min.js' ], [ 'tribe-tooltipster', 'vendor/tooltipster/tooltipster.bundle.js', [ 'jquery' ] ], [ 'tribe-tooltipster-css', 'vendor/tooltipster/tooltipster.bundle.css' ], [ 'datatables-css', 'datatables.css' ], @@ -205,14 +232,18 @@ public function load_assets() { [ 'tribe-jquery-timepicker-css', 'vendor/jquery-tribe-timepicker/jquery.timepicker.css' ], [ 'tribe-timepicker', 'timepicker.js', [ 'jquery', 'tribe-jquery-timepicker' ] ], [ 'tribe-attrchange', 'vendor/attrchange/js/attrchange.js' ], + [ 'tec-ky-module', 'vendor/ky/ky.js', [], null, [ 'module' => true ] ], + [ 'tec-ky', 'vendor/ky/tec-ky.js', [ 'tec-ky-module' ], null, [ 'module' => true ] ], ] ); tribe_assets( $this, [ - [ 'tribe-common-skeleton-style', 'common-skeleton.css' ], - [ 'tribe-common-full-style', 'common-full.css', [ 'tribe-common-skeleton-style' ] ], + [ 'tec-variables-skeleton', 'variables-skeleton.css', ], + [ 'tribe-common-skeleton-style', 'common-skeleton.css', [ 'tec-variables-skeleton' ] ], + [ 'tec-variables-full', 'variables-full.css', [ 'tec-variables-skeleton' ] ], + [ 'tribe-common-full-style', 'common-full.css', [ 'tec-variables-full', 'tribe-common-skeleton-style' ] ], ], null ); @@ -221,11 +252,11 @@ public function load_assets() { tribe_assets( $this, [ - [ 'tribe-ui', 'tribe-ui.css' ], + [ 'tribe-ui', 'tribe-ui.css', [ 'tec-variables-full' ] ], [ 'tribe-buttonset', 'buttonset.js', [ 'jquery', 'underscore' ] ], - [ 'tribe-common-admin', 'tribe-common-admin.css', [ 'tribe-dependency-style', 'tribe-bumpdown-css', 'tribe-buttonset-style', 'tribe-select2-css' ] ], + [ 'tribe-common-admin', 'tribe-common-admin.css', [ 'tec-variables-skeleton', 'tec-variables-full', 'tribe-dependency-style', 'tribe-bumpdown-css', 'tribe-buttonset-style', 'tribe-select2-css' ] ], [ 'tribe-validation', 'validation.js', [ 'jquery', 'underscore', 'tribe-common', 'tribe-utils-camelcase', 'tribe-tooltipster' ] ], - [ 'tribe-validation-style', 'validation.css', [ 'tribe-tooltipster-css' ] ], + [ 'tribe-validation-style', 'validation.css', [ 'tec-variables-full', 'tribe-tooltipster-css' ] ], [ 'tribe-dependency', 'dependency.js', [ 'jquery', 'underscore', 'tribe-common' ] ], [ 'tribe-dependency-style', 'dependency.css', [ 'tribe-select2-css' ] ], [ 'tribe-pue-notices', 'pue-notices.js', [ 'jquery' ] ], @@ -261,9 +292,51 @@ public function load_assets() { ] ); + tribe_asset( + $this, + 'tec-admin-settings-image-field', + 'admin-image-field.js', + [ 'jquery' ], + 'in_admin_footer', + [ + 'conditionals' => [ tribe( Settings::class ), 'should_load_image_field_assets' ] + ] + ); + + // Register the asset for Customizer controls. + tribe_asset( + $this, + 'tribe-customizer-controls', + 'customizer-controls.css', + [ 'tec-variables-full' ], + 'customize_controls_print_styles' + ); + + // Register the asset for color fields. + tribe_asset( + $this, + 'tec-settings-color-field', + 'admin-color-field.js', + [ 'jquery', 'wp-color-picker' ], + 'admin_footer', + [ + 'conditionals' => [ tribe( Settings::class ), 'should_load_color_field_assets' ] + ] + ); + tribe( Tribe__Admin__Help_Page::class )->register_assets(); } + /** + * Ensure that the customizer styles get the variables they need. + * + * @since 4.14.13 + */ + public function load_tec_variables() { + tribe_asset_enqueue( 'tec-variables-skeleton' ); + tribe_asset_enqueue( 'tec-variables-full' ); + } + /** * Load Common's text domain, then fire the hook for other plugins to do the same. * @@ -339,12 +412,12 @@ public function load_localize_data() { 'monthNames' => $datepicker_months, 'monthNamesShort' => $datepicker_months, // We deliberately use full month names here, 'monthNamesMin' => array_values( Tribe__Date_Utils::get_localized_months_short() ), - 'nextText' => esc_html__( 'Next', 'the-events-calendar' ), - 'prevText' => esc_html__( 'Prev', 'the-events-calendar' ), - 'currentText' => esc_html__( 'Today', 'the-events-calendar' ), - 'closeText' => esc_html__( 'Done', 'the-events-calendar' ), - 'today' => esc_html__( 'Today', 'the-events-calendar' ), - 'clear' => esc_html__( 'Clear', 'the-events-calendar' ), + 'nextText' => esc_html__( 'Next', 'tribe-common' ), + 'prevText' => esc_html__( 'Prev', 'tribe-common' ), + 'currentText' => esc_html__( 'Today', 'tribe-common' ), + 'closeText' => esc_html__( 'Done', 'tribe-common' ), + 'today' => esc_html__( 'Today', 'tribe-common' ), + 'clear' => esc_html__( 'Clear', 'tribe-common' ), ], ] ); } @@ -548,7 +621,20 @@ public static function array_insert_before_key( $key, $source_array, $insert_arr public static function post_id_helper( $candidate = null ) { $candidate_post = get_post( $candidate ); - return $candidate_post instanceof WP_Post ? $candidate_post->ID : false; + $post_id = $candidate_post instanceof WP_Post ? $candidate_post->ID : false; + + /** + * Allows modifying the post ID in order to allow redirection of values before any other additional + * WordPress action is called from on result. + * + * @since 4.12.13 + * + * @param int|bool $post_id The ID of the post if the $candidate value is a valid WP_Post Object, `false` otherwise. + * @param null|int|WP_Post $candidate Post ID or object, `null` to get the ID of the global post object. + * + * @return int|bool The ID of the post. + */ + return apply_filters( 'tribe_post_id', $post_id, $candidate ); } /** @@ -567,16 +653,12 @@ public function store_admin_notices( $page ) { * Runs tribe_plugins_loaded action, should be hooked to the end of plugins_loaded */ public function tribe_plugins_loaded() { - tribe( 'admin.notice.php.version' ); tribe( 'cache' ); tribe_singleton( 'feature-detection', 'Tribe__Feature_Detection' ); tribe_register_provider( 'Tribe__Service_Providers__Processes' ); - if ( ! defined( 'TRIBE_HIDE_MARKETING_NOTICES' ) ) { - tribe( 'admin.notice.marketing' ); - } - tribe( \Tribe\Admin\Notice\WP_Version::class ); + tribe( \Tribe\Admin\Troubleshooting::class ); /** * Runs after all plugins including Tribe ones have loaded @@ -616,17 +698,13 @@ public function bind_implementations() { tribe_singleton( 'db-lock', DB_Lock::class ); tribe_singleton( 'freemius', 'Tribe__Freemius' ); tribe_singleton( 'customizer', 'Tribe__Customizer' ); - tribe_singleton( Tribe__Dependency::class, Tribe__Dependency::class ); + tribe_singleton( \Tribe\Admin\Troubleshooting::class, \Tribe\Admin\Troubleshooting::class, [ 'hook' ] ); tribe_singleton( 'callback', 'Tribe__Utils__Callback' ); - tribe_singleton( 'pue.notices', 'Tribe__PUE__Notices' ); - - tribe_singleton( Tribe__Admin__Help_Page::class, Tribe__Admin__Help_Page::class ); - - tribe_singleton( 'admin.notice.php.version', 'Tribe__Admin__Notice__Php_Version', [ 'hook' ] ); - tribe_singleton( 'admin.notice.marketing', 'Tribe__Admin__Notice__Marketing', [ 'hook' ] ); - tribe_singleton( \Tribe\Admin\Notice\WP_Version::class, \Tribe\Admin\Notice\WP_Version::class, [ 'hook' ] ); + tribe_singleton( Tribe__Admin__Help_Page::class, Tribe__Admin__Help_Page::class, [ 'hook' ] ); + tribe_singleton( 'admin.pages', '\Tribe\Admin\Pages' ); + tribe_singleton( 'admin.activation.page', 'Tribe__Admin__Activation_Page' ); tribe_register_provider( Tribe__Editor__Provider::class ); tribe_register_provider( Tribe__Service_Providers__Debug_Bar::class ); @@ -639,6 +717,9 @@ public function bind_implementations() { tribe_register_provider( Tribe\Log\Service_Provider::class ); tribe_register_provider( Tribe\Service_Providers\Crons::class ); tribe_register_provider( Tribe\Service_Providers\Widgets::class ); + tribe_register_provider( Tribe\Service_Providers\Onboarding::class ); + tribe_register_provider( Tribe\Admin\Notice\Service_Provider::class ); + tribe_register_provider( Tribe\Admin\Conditional_Content\Service_Provider::class ); } /** diff --git a/tribe-common/src/Tribe/Models/Post_Types/Base.php b/tribe-common/src/Tribe/Models/Post_Types/Base.php index 176e3f22c5..439e1faf99 100644 --- a/tribe-common/src/Tribe/Models/Post_Types/Base.php +++ b/tribe-common/src/Tribe/Models/Post_Types/Base.php @@ -21,6 +21,13 @@ * @package Tribe\Models\Post_Types */ abstract class Base { + /** + * The key used to store pre-serializes properties data in the cache. + * + * @since 5.0.3 + */ + public const PRE_SERIALIZED_PROPERTY = '_tec_pre_serialized'; + /** * The post object base for this post type instance. * @@ -78,7 +85,7 @@ protected function get_cached_properties( $filter ) { } // Cache by post ID and filter. - $cache_key = $cache_slug . '_' . $this->post->ID . '_' . $filter; + $cache_key = $this->get_properties_cache_key( $filter ); return ( new Cache() )->get( $cache_key, Cache_Listener::TRIGGER_SAVE_POST ); } @@ -102,14 +109,52 @@ abstract protected function build_properties( $filter ); * @since 4.9.18 * * @param string $filter The type of filter to get the properties for. + * @param bool $force Whether to force a rebuild of the properties or not. * * @return array The model properties. This value might be cached. */ - protected function get_properties( $filter ) { - $cached = $this->get_cached_properties( $filter); + protected function get_properties( $filter, bool $force = false ) { + $cached = ! $force ? $this->get_cached_properties( $filter ) : false; if ( false !== $cached ) { - return $cached; + // Un-serialize the pre-serialized properties now, when classes will be most likely defined. + $pre_serialized_properties = $cached[ self::PRE_SERIALIZED_PROPERTY ] ?? []; + + foreach ( $pre_serialized_properties as $key => $value ) { + try { + $cached[ $key ] = unserialize( $value, [ 'allowed_classes' => true ] ); + } catch ( \Throwable $t ) { + /* + * Deal with the case where plugin A, B, C were active when the cache was built, + * but B and C are now inactive. In this case the un-serialization will fail for + * any pre-serialized value using classes from B and C: here we gracefully ignore + * each one of those. + */ + } + } + + try { + // Allow models to apply further unserialization operations. + $cached = $this->scalar_unserialize_properties( $cached ); + + /** + * Allows filtering the properties of the post type model after they have been unserialized from the + * cache.. + * + * @since 5.0.3 + * + * @param array $cached The key-value map of the properties of the post type model. + * @param \WP_Post $post The post object of the post type model. + */ + $cached = apply_filters( "tec_model_{$this->get_cache_slug()}_read_cache_properties", $cached, $this->post ); + + // Remove the pre-serialized properties from the cached properties. + unset( $cached[ self::PRE_SERIALIZED_PROPERTY ] ); + + return $cached; + } catch ( \Throwable $t ) { + // Rebuid the properties from cache failed, move on. + } } $props = $this->build_properties( $filter ); @@ -134,33 +179,34 @@ protected function get_properties( $filter ) { * Returns the WP_Post version of this model. * * @since 4.9.18 + * @since 5.0.3 Added the `$force` parameter. * * @param string $output The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to a WP_Post * object,an associative array, or a numeric array, respectively. * @param string $filter Type of filter to apply. Accepts 'raw', 'edit', 'db', or 'display' and other formats * supported by the specific type implementation. + * @param bool $force Whether to force the post to be reloaded from the database or not. * * @return \WP_Post|array|null The post object version of this post type model or `null` if the post is not valid. */ - public function to_post( $output = OBJECT, $filter = 'raw' ) { - $properties = $this->get_properties( $filter ); - - // Clone the post to avoid side effects. - $post = clone $this->post; - - // And decorate the clone with the properties. - foreach ( $properties as $key => $value ) { - $post->{$key} = $value; - } + public function to_post( $output = OBJECT, $filter = 'raw', bool $force = false ) { + $properties = $this->get_properties( $filter, $force ); switch ( $output ) { case ARRAY_A: - return (array) $post; + return array_merge( (array) $this->post, $properties ); case ARRAY_N: - return array_values( (array) $post ); + return array_values( array_merge( (array) $this->post, $properties ) ); case OBJECT: - default; - return $post; + default: + // Clone the post to avoid side effects. + $clone = clone $this->post; + // And decorate the clone with the properties. + foreach ( $properties as $key => $value ) { + $clone->{$key} = $value; + } + + return $clone; } } @@ -170,7 +216,6 @@ public function to_post( $output = OBJECT, $filter = 'raw' ) { * @since 4.9.18 * * @param string $filter The kind of filter applied to the model. - * * @return callable The closure, or callable, that should be used to cache this model when, and if, required. */ protected function get_caching_callback( $filter ) { @@ -183,24 +228,142 @@ protected function get_caching_callback( $filter ) { $callback = null; if ( wp_using_ext_object_cache() ) { - /* - * If any real caching is in place , then define a function to cache this event when, and if, one of the - * lazy properties is loaded. - * Cache by post ID and filter. - */ - $cache_key = $cache_slug . '_' . $this->post->ID . '_' . $filter; - $cache = new Cache(); - $callback = function () use ( $cache, $cache_key, $filter ) { - $properties = $this->get_properties( $filter ); - - /* - * Cache without expiration, but only until a post of the types managed by The Events Calendar is - * updated or created. - */ - $cache->set( $cache_key, $properties, 0, Cache_Listener::TRIGGER_SAVE_POST ); - }; + $callback = $this->get_object_cache_callback( $filter ); } return $callback; } + + /** + * Further scalarizes the properties of the post type model. + * + * Extending classes should implement this method to handle + * specific scalarization of the model properties. + * + * @since 5.0.3 + * + * @param array $properties A key-value map of the + * properties of the post type model. + * + * @return array The scalarized properties key-value map. + */ + protected function scalar_serialize_properties( array $properties ): array { + return $properties; + } + + /** + * Further un-scalarizes the properties of the post type model. + * + * Extending classes should implement this method to handle + * specific un-scalarization of the model properties. + * + * @since 5.0.3 + * + * @param array $properties A key-value map of the + * properties of the post type model. + * + * @return array The un-scalarized properties key-value map. + */ + protected function scalar_unserialize_properties( array $properties ): array { + return $properties; + } + + /** + * Returns the callback function that should be used to cache the model using object caching. + * + * If any real caching is in place , then define a function to cache this event when, and if, one of the + * lazy properties is loaded. + * Cache by post ID and filter. + * Cache could be pre-fetched: in that case only built-in PHP classes will be supported: for this reason + * object properties will be "scalarized". + * + * @since 5.0.3 + * + * @param string $cache_slug The cache slug of the post type model. + * @param string $filter The filter to cache the model for. + * + * @return \Closure The callback function that should be used to cache the model using object caching. + */ + protected function get_object_cache_callback( string $filter ): \Closure { + $cache_key = $this->get_properties_cache_key( $filter ); + $cache = new Cache(); + + return function () use ( $cache, $cache_key, $filter ) { + $properties = $this->get_properties( $filter ); + $pre_serialized_properties = []; + + try { + // Pre-serialize each object property and store it in a separate cache entry. + foreach ( $properties as $key => &$value ) { + try { + if ( is_object( $value ) && ! $value instanceof \stdClass ) { + // We might end up pre-serializing other built-in objects here, but let's play it safe. + $pre_serialized_properties[ $key ] = serialize( $value ); + } + } catch ( \Throwable $t ) { + // Null the property: an object that cannot be serialized correctly is not cacheable. + $value = null; + } + } + unset( $value ); + + // Remove the pre-serialized properties from the main cache entry. + $properties = array_diff_key( $properties, $pre_serialized_properties ); + + // Allow models to customize the pre-serialization further. + $properties = $this->scalar_serialize_properties( $properties ); + + // Add the pre-serialized properties to the main cache entry. + if ( count( $pre_serialized_properties ) ) { + $properties[ self::PRE_SERIALIZED_PROPERTY ] = $pre_serialized_properties; + } + + /** + * Allows filtering the properties of the post type model before they are cached. + * + * @since 5.0.3 + * + * @param array $properties The key-value map of the properties of the post type model. + * @param \WP_Post $post The post object of the post type model. + */ + $properties = apply_filters( "tec_model_{$this->get_cache_slug()}_put_cache_properties", $properties, $this->post ); + } catch ( \Throwable $t ) { + // If we can't serialize the properties, bail. + return; + } + + /* + * Cache without expiration, but only until a post of the types managed by The Events Calendar is + * updated or created. + */ + $cache->set( $cache_key, $properties, 0, Cache_Listener::TRIGGER_SAVE_POST ); + }; + } + + /** + * Returns the cache key to be used to cache the model properties. + * + * @since 5.0.3 + * + * @param string $filter The filter to cache the model for. + * + * @return string The cache key to be used to cache the model properties. + */ + public function get_properties_cache_key( string $filter ): string { + return $this->get_cache_slug() . '_' . $this->post->ID . '_' . $filter; + } + + /** + * Commits the model properties to cache immediately. + * + * @since 5.0.3 + * + * @param string $filter The filter to cache the model properties for. + * + * @return void The model properties are cached immediately. + */ + public function commit_to_cache( string $filter = 'raw' ): void { + $caching_callback = $this->get_object_cache_callback( $filter ); + $caching_callback(); + } } diff --git a/tribe-common/src/Tribe/Models/Post_Types/Nothing.php b/tribe-common/src/Tribe/Models/Post_Types/Nothing.php index 90e24a44eb..b238e22106 100644 --- a/tribe-common/src/Tribe/Models/Post_Types/Nothing.php +++ b/tribe-common/src/Tribe/Models/Post_Types/Nothing.php @@ -38,7 +38,7 @@ protected function build_properties( $filter ) { /** * {@inheritDoc} */ - public function to_post( $output = OBJECT, $filter = 'raw' ) { + public function to_post( $output = OBJECT, $filter = 'raw', bool $force = false ) { return null; } } \ No newline at end of file diff --git a/tribe-common/src/Tribe/Onboarding/Hints_Abstract.php b/tribe-common/src/Tribe/Onboarding/Hints_Abstract.php new file mode 100644 index 0000000000..e654a27831 --- /dev/null +++ b/tribe-common/src/Tribe/Onboarding/Hints_Abstract.php @@ -0,0 +1,98 @@ +is_on_page() ) { + return false; + } + + // Bail if the `Times to display` is set and it was reached. + if ( + is_numeric( $this->times_to_display ) + && ( tribe( 'onboarding' )->get_views( $this->hints_id ) > $this->times_to_display ) + ) { + return false; + } + + return true; + } + + /** + * Return the hints data. + * + * @since 4.14.9 + * + * @return array The hints. + */ + abstract function hints(); + + /** + * Return the CSS classes. + * + * @since 4.14.9 + * + * @return array The CSS classes. + */ + public function css_classes() { + return []; + } + + /** + * The hints data, publicly accessible. + * + * @since 4.14.9. + * + * @param array $data An array with the hints data. + * @return array + */ + public function hints_data( array $data = [] ) { + $data['hints'] = $this->hints(); + $data['classes'] = $this->css_classes(); + + return $data; + } +} diff --git a/tribe-common/src/Tribe/Onboarding/Main.php b/tribe-common/src/Tribe/Onboarding/Main.php new file mode 100644 index 0000000000..3b9e77e562 --- /dev/null +++ b/tribe-common/src/Tribe/Onboarding/Main.php @@ -0,0 +1,223 @@ +get_registered_tours(); + + // Try to populate, if it should display. + foreach ( $registered_tours as $tour => $class_name ) { + $tour_class = new $class_name(); + + if ( $tour_class->should_display() ) { + // Increment the views when the tour is displayed. + $this->increment_views( $tour_class->tour_id ); + $data = $tour_class->tour_data( $data ); + + /** + * We're displaying the tour. + * + * @since 4.14.9. + * + * @param string $tour_id The tour id. + */ + do_action( 'tribe_onboarding_tour_display', $tour_class->tour_id ); + + break; + } + } + + /** + * Filter the data we're using to localize the tour steps. + * + * Since 4.14.9 + * + * @param array $data An array with the tour data. + * + * @return array $data An array with the tour data. + */ + $data = apply_filters( 'tribe_onboarding_tour_data', $data ); + + return $data; + } + + /** + * Get the hints. + * + * @since 4.14.9 + * + * @return array $steps The hints data. + */ + protected function hints_data() { + $data = []; + $registered_hints = $this->get_registered_hints(); + + // Try to populate, and check if it should display. + foreach ( $registered_hints as $hints => $class_name ) { + $hints_class = new $class_name(); + + if ( $hints_class->should_display() ) { + // Increment the views when the tour is displayed. + $this->increment_views( $hints_class->tour_id ); + $data = $hints_class->hints_data( $data ); + + /** + * We're displaying the hints. + * + * @since 4.14.9. + * + * @param string $hints_id The hints id. + */ + do_action( 'tribe_onboarding_hints_display', $hints_class->hints_id ); + + break; + } + } + + /** + * Filter the data we're using to localize the hints. + * + * Since 4.14.9 + * + * @param array $data An array with the hints data. + * + * @return array $data An array with the hints data. + */ + $data = apply_filters( 'tribe_onboarding_hints_data', $data ); + + return $data; + } + + /** + * Localize tour data. + * + * @since 4.14.9 + * + * @param string $hook The current admin page. + */ + public function localize_tour( $hook ) { + $data = $this->tour_data(); + + wp_localize_script( 'tec-onboarding-js', 'TribeOnboardingTour', $data ); + } + + /** + * Localize hints data. + * + * @since 4.14.9 + * + * @param string $hook The current admin page. + */ + public function localize_hints( $hook ) { + $data = $this->hints_data(); + + wp_localize_script( 'tec-onboarding-js', 'TribeOnboardingHints', $data ); + } + + /** + * Get the views for an onboarding element. + * + * @since 4.14.9 + * + * @param string $id The onboarding ID (tour or hint). + * + * @return mixed The views for the given ID. + */ + public function get_views( $id = '' ) { + + if ( empty( $id ) ) { + return; + } + + $option = tribe_get_option( 'tribe_onboarding_views', [] ); + + if ( ! isset( $option[ $id ] ) ) { + return; + } + + return intval( $option[ $id ] ); + } + + /** + * Increment views for an onboarding element. + * + * @since 4.14.9 + * + * @param string $id The onboarding ID (tour or hint). + * @return int The views count for the particular `$id`. + */ + public function increment_views( $id ) { + $option = tribe_get_option( 'tribe_onboarding_views', [] ); + $views = 0; + + if ( isset( $option[ $id ] ) ) { + $views = intval( $option[ $id ] ); + } + + // Increment views and save. + $views++; + $option[ $id ] = $views; + + tribe_update_option( 'tribe_onboarding_views', $option ); + + return $views; + } + + /** + * Get the list of tours available for handling. + * + * @since 4.14.9 + * + * @return array An associative array of shortcodes in the shape `[ => ]` + */ + public function get_registered_tours() { + $tours = []; + + /** + * Allow the registering of tours into our plugins. + * + * @since 4.14.9 + * + * @var array An associative array of tours in the shape `[ => ]`. + */ + $tours = apply_filters( 'tribe_onboarding_tours', $tours ); + + return $tours; + } + + /** + * Get the list of hints available for handling. + * + * @since 4.14.9 + * + * @return array An associative array of hints in the shape `[ => ]` + */ + public function get_registered_hints() { + $hints = []; + + /** + * Allow the registering of tours into our plugins. + * + * @since 4.14.9 + * + * @var array An associative array of hints in the shape `[ => ]`. + */ + $tours = apply_filters( 'tribe_onboarding_hints', $hints ); + + return $hints; + } +} diff --git a/tribe-common/src/Tribe/Onboarding/README.md b/tribe-common/src/Tribe/Onboarding/README.md new file mode 100644 index 0000000000..d13a01a0b7 --- /dev/null +++ b/tribe-common/src/Tribe/Onboarding/README.md @@ -0,0 +1,242 @@ +# Onboarding + +Onboarding consists of two components. Tours & Hints. The idea of this components is to enhance the onboarding experience and add some contextual help for elements. + +These components work as a wrapper of [IntroJS](https://introjs.com/). + +If for any reason you want to disable the Onboarding library, you can use the following filter: + +`add_filter( 'tribe_onboarding_disable, '__return_true' );` + +## Tours + +**Tours** provides an easy way to onboard users on a step by step basis. The information is provided to the user on a modal. + +Users can navigate through the different steps and close the modal at any time by clicking outside of it. + +Setting up tours is fairly simple. It all comes down to hooking onto `tribe_onboarding_tour_data`. + +The information to be sent there is an array in the following format: + +``` +$tour_data = [ + 'steps' = [], // An array of the steps you'd like for the tour. + 'classes' = [], // An array of CSS classes to apply to the modal. (Optional) +]; +``` + +The format of each step can contain the following: + +``` +$step = [ + 'title' => __( 'Welcome to this screen' ), // The step title. + 'intro' => __( 'This is the description of the "Welcome to this screen" message.' ); + 'element' => '#my-html-id', // If you want to highlight a certain part of the HTML for this step. If not defined, it'll show just the modal with the information. (Optional) +]; +``` + +So for example, if you want to add a simple welcome tour for a settings panel you could add the following. + +``` +add_filter( 'tribe_onboarding_tour_data', 'my_fancy_tour' ); + +function my_fancy_tour( $data ) { + + // Here you can do some checks to see if you're in the page you want to show to tour. + + $steps = [ + [ + 'title' => __( '🤘 Welcome to the settings panel' ), + 'intro' => __( 'It is actually great that you are using our plugins! From this settings panel you should be able to access all the settings to configure your site.' ), + ], + [ + 'title' => __( '⚙️ Different sections' ), + 'element' => '#tribe-settings-tabs', + 'intro' => __( 'On this section you can access all of the different settings of our plugins, if you have questions about which settings we have you can go to our knowledgebase article' ), + ], + [ + 'title' => __( '🛠️ Change the settings' ), + 'element' => '#tribe-field-postsPerPage', + 'intro' => __( 'If you need to change any configuration, you can do it! If you have questions about which settings we have you can go to our knowledgebase article' ), + ], + [ + 'title' => __( '💡 Save the Settings' ), + 'element' => '#tribeSaveSettings', + 'intro' => __( 'Please remember to save the settings, if you have questions about which settings we have you can go to our knowledgebase article' ), + ], + ]; + + $data['steps'] = $steps; + $data['classes'] = [ 'my__fancy-css-class', 'my__fancy-css-class--modifier' ]; + + return $data; +} +``` + +### Setting up Tours from TEC plugins + +Setting up new tours from our plugins should be easy with the abstract classes we have in place. + +We should be registering the tours we want, hooking them into the `tribe_onboarding_tours` filter. + +The function to hook onto `tribe_onboarding_tours` should have the following format: + +``` +/** + * Register tours. + * + * @see \Tribe\Onboarding\Main::get_registered_tours() + * + * @since 1.0 + * + * @param array $tours An associative array of tours in the shape `[ => ]`. + * + * @return array +*/ +public function filter_register_tours( array $tours ) { + $tours['my_awesome_tour_id'] = MyAwesomeTourClass::class; + + return $tours; +} +``` + +And then `MyAwesomeTourClass` should have the following format: + +``` +use Tribe\Onboarding\Tour_Abstract; +/** + * Class MyAwesomeTourClass + */ +class MyAwesomeTourClass extends Tour_Abstract { + + /** + * The tour ID. + * + * @var string + */ + public $tour_id = 'my_awesome_tour_id'; + + /** + * Times to display the tour. + * If you set '5', then it'll be displayed FIVE times. + * + * @var int + */ + public $times_to_display = 5; + + /** + * Returns if it's on the page we want to display the tour for. + * + * @return bool True if it's on page. + */ + public function is_on_page() { + + // Perform any check you want, to see if the tour should display or not. + return $admin_helpers->is_screen( 'tribe_events_page_tribe-common' ); + } + + /** + * Tour steps. + * + * @since 1.0 + * + * @return array $steps The tour steps + */ + public function steps() { + + $steps = [ + [ + 'title' => __( '🤘 Welcome to the settings panel' ), + 'intro' => __( 'It is actually great that you are using our plugins! From this settings panel you should be able to access all the settings to configure your site.' ), + ], + [ + 'title' => __( '⚙️ Different sections' ), + 'element' => '#tribe-settings-tabs', + 'intro' => __( 'On this section you can access all of the different settings of our plugins, if you have questions about which settings we have you can go to our knowledgebase article' ), + ], + [ + 'title' => __( '🛠️ Change the settings' ), + 'element' => '#tribe-field-postsPerPage', + 'intro' => __( 'If you need to change any configuration, you can do it! If you have questions about which settings we have you can go to our knowledgebase article' ), + ], + [ + 'title' => __( '💡 Save the Settings' ), + 'element' => '#tribeSaveSettings', + 'intro' => __( 'Please remember to save the settings, if you have questions about which settings we have you can go to our knowledgebase article' ), + ], + ]; + + return $steps; + } + + /** + * Tour CSS Classes. + * + * Here you can set additional CSS classes for the particular tour. + * + * @return array $css_classes The tour extra CSS classes. + */ + public function css_classes() { + + return [ 'my-awesome-css-class' ]; + } +} + +``` + +## Hints + +**Hints** are great for providing non-intrusive contextual help. Each hing will be associated to a particular HTML element (which you can define by a CSS class or an ID) and it'll add kind of a "infinite bouncing dot" besides that element. When clicked you'll have some more context on what's the purpose of that. + +Technically speaking **Hints** work pretty similarly to how **Tours** do. The mechanics of adding a set of hints is almost the same. + +It comes down to hooking onto `tribe_onboarding_hints_data`. + +The information to be sent there is an array in the following format: + +``` +$hints_data = [ + 'hints' = [], // An array of the hints you'd like to have. + 'classes' = [], // An array of CSS classes to apply to the modal/tooltip. (Optional) +]; +``` + +So for example, if you want to add a hint for a newly added button: + +``` +add_filter( 'tribe_onboarding_hints_data', 'my_fancy_hints' ); + +function my_fancy_hints( $data ) { + $hints = [ + [ + 'hint' => __( 'You can now add attendees for this event!' ), + 'element' => '.add_attendee', + ], + ]; + + $data['hints'] = $hints; + $data['classes'] = [ 'my__fancy-css-class', 'my__fancy-css-class--modifier' ]; + + return $data; +} +``` + +## CSS classes that you may want to use: + +- `.tribe-onboarding__tooltip--large` - Use if if you want your tooltip to be bigger/wider. +- `.tribe-onboarding__tooltip--dark` - Use if if you want your tooltip to have a dark skin (to use an image for the background, or just a plain dark color). +- `.tribe-onboarding__tooltip--squared` - Use it if you want a squared tooltip. +- `.tribe-onboarding__tooltip--no-bullets` - Use it if you want to hide the navigation bullets. +- `.tribe-onboarding__tooltip--title-large` - Use it if you want to have a bigger title. +- `.tribe-onboarding__tooltip--content-centered` - Use it if you want to center the content. +- `.tribe-onboarding__tooltip--button-centered` - Use it if you want to center the buttons. +- `.tribe-onboarding__tooltip--button-large` - Use it if you want to have a bigger button. +- `.tribe-onboarding__tooltip--button-rounded` - Use it if you want to have a rounded button. +- `.tribe-onboarding__tooltip--button-dark-skin` - Use it if you want to have a button for dark skin (white background / dark text button). + + +### 💡 To-Do's / Ideas: + +- [ ] Add some more styles variations. +- [ ] Maybe add the possibility of having animated GIFs/images on each step. +- [ ] Add some abstraction to extend this anywhere, and make it easier to check if it's in the page, and load the tours and/or hints we would like to add. diff --git a/tribe-common/src/Tribe/Onboarding/Tour_Abstract.php b/tribe-common/src/Tribe/Onboarding/Tour_Abstract.php new file mode 100644 index 0000000000..adfbb0068a --- /dev/null +++ b/tribe-common/src/Tribe/Onboarding/Tour_Abstract.php @@ -0,0 +1,98 @@ +is_on_page() ) { + return false; + } + + // Bail if the `Times to display` is set and it was reached. + if ( + is_numeric( $this->times_to_display ) + && ( tribe( 'onboarding' )->get_views( $this->tour_id ) > $this->times_to_display ) + ) { + return false; + } + + return true; + } + + /** + * Return the tour steps. + * + * @since 4.14.9 + * + * @return array The tour steps. + */ + abstract function steps(); + + /** + * Return the CSS classes. + * + * @since 4.14.9 + * + * @return array The CSS classes. + */ + public function css_classes() { + return []; + } + + /** + * The tour data, publicly accessible. + * + * @since 4.14.9. + * + * @param array $data An array with the tour data. + * @return array + */ + public function tour_data( array $data = [] ) { + $data['steps'] = $this->steps(); + $data['classes'] = $this->css_classes(); + + return $data; + } +} diff --git a/tribe-common/src/Tribe/PUE/Checker.php b/tribe-common/src/Tribe/PUE/Checker.php index a5ae6e6e50..6c29d5ec30 100755 --- a/tribe-common/src/Tribe/PUE/Checker.php +++ b/tribe-common/src/Tribe/PUE/Checker.php @@ -87,6 +87,26 @@ class Tribe__PUE__Checker { */ public $pue_option_name = ''; + /** + * Where to store the temporary status info. + * + * @todo remove transient in a major feature release where we release all plugins. + * + * @since 4.14.14 + * + * @var string + */ + public $pue_key_status_transient_name; + + /** + * Where to store the temporary status info. + * + * @since 4.14.9 + * + * @var string + */ + public $pue_key_status_option_name; + /** * used to hold the install_key if set (included here for addons that will extend PUE to use install key checks) * @@ -171,6 +191,91 @@ public function __construct( $pue_update_url, $slug = '', $options = [], $plugin $this->set_plugin_file( $plugin_file ); $this->set_options( $options ); $this->hooks(); + $this->set_key_status_name(); + } + + /** + * Gets whether the license key is valid or not. + * + * @since 4.14.9 + */ + public function is_key_valid() { + // @todo remove transient in a major feature release where we release all plugins. + $status = get_transient( $this->pue_key_status_transient_name ); + + if ( empty( $status ) ) { + $status = get_option( $this->pue_key_status_option_name, 'invalid' ); + } + + return 'valid' === $status; + } + + /** + * Gets whether or not the PUE key validation check is expired. + * + * @since 4.14.9 + */ + public function is_key_validation_expired() { + // If we have a transient, then we're good. Not expired. + // @todo remove transient in a major feature release where we release all plugins. + if ( get_transient( $this->pue_key_status_transient_name ) ) { + return false; + } + + $option_expiration = get_option( "{$this->pue_key_status_option_name}_timeout", null ); + return is_null( $option_expiration ) || ( time() > $option_expiration ); + } + + /** + * Set the PUE key status property names. + * + * @since 4.14.9 + */ + public function set_key_status_name() { + $this->pue_key_status_option_name = 'pue_key_status_' . $this->get_slug() . '_' . $this->get_site_domain(); + + // @todo remove transient in a major feature release where we release all plugins. + $this->pue_key_status_transient_name = md5( $this->get_slug() . $this->get_site_domain() ); + } + + /** + * Creates a hash for the transient name that holds the current key status. + * + * @todo remove transient in a major feature release where we release all plugins. + * + * @since 4.14.14 + */ + public function set_key_status_transient_name() { + _deprecated_function( __METHOD__, '4.14.9', __CLASS__ . '::set_key_status_name()' ); + } + + /** + * Sets the key status based on the key validation check results. + * + * @since 4.14.9 + * + * @param int $valid 0 for invalid, 1 or 2 for valid. + */ + public function set_key_status( $valid ) { + $status = tribe_is_truthy( $valid ) ? 'valid' : 'invalid'; + update_option( $this->pue_key_status_option_name, $status ); + update_option( "{$this->pue_key_status_option_name}_timeout", $this->check_period * HOUR_IN_SECONDS ); + + // We set a transient in addition to an option for compatibility reasons. + // @todo remove transient in a major feature release where we release all plugins. + set_transient( $this->pue_key_status_transient_name, $status, $this->check_period * HOUR_IN_SECONDS ); + } + + /** + * Sets the key status transient based on the key validation check results. + * + * @since 4.14.9 + * + * @param int $valid 0 for invalid, 1 or 2 for valid. + */ + public function set_key_status_transient( $valid ) { + _deprecated_function( __METHOD__, '4.14.9', __CLASS__ . '::set_key_status()' ); + $this->set_key_status( $valid ); } /** @@ -446,8 +551,11 @@ public function get_domain() { $domain = self::$domain; if ( empty( $domain ) ) { - if ( isset( $_SERVER['SERVER_NAME'] ) ) { - $domain = $_SERVER['SERVER_NAME']; + $url = wp_parse_url( get_option( 'siteurl' ) ); + if ( ! empty( $url ) && isset( $url['host'] ) ) { + $domain = $url['host']; + } elseif ( isset( $_SERVER['SERVER_NAME'] ) ) { + $domain = $_SERVER['SERVER_NAME']; } if ( is_multisite() ) { @@ -941,16 +1049,22 @@ public function validate_key( $key, $network = false ) { } $current_install_key = $this->get_key( $key_type ); + $replacement_key = $query_args['key']; + + if ( ! empty( $plugin_info->replacement_key ) ) { + // The PUE service might send over a new key upon validation. + $replacement_key = $plugin_info->replacement_key; + } - if ( $current_install_key && $current_install_key === $query_args['key'] ) { + if ( $current_install_key && $current_install_key === $replacement_key ) { $default_success_msg = esc_html( sprintf( __( 'Valid Key! Expires on %s', 'tribe-common' ), $expiration ) ); } else { - // Set the key - $this->update_key( $query_args['key'], $key_type ); + // Set the key. + $this->update_key( $replacement_key, $key_type ); $default_success_msg = esc_html( sprintf( __( 'Thanks for setting up a valid key. It will expire on %s', 'tribe-common' ), $expiration ) ); - //Set SysInfo Key on Tec.com After Successful Validation of License + // Set system info key on TEC.com after successful validation of license. $optin_key = get_option( 'tribe_systeminfo_optin' ); if ( $optin_key ) { Tribe__Support::send_sysinfo_key( $optin_key, $query_args['domain'], false, true ); @@ -970,6 +1084,8 @@ public function validate_key( $key, $network = false ) { $response['message'] = wp_kses( $response['message'], 'data' ); + $this->set_key_status( $response['status'] ); + return $response; } @@ -1770,5 +1886,32 @@ public function should_show_overrideable_license() { public function should_show_network_editable_license() { return is_network_admin() && is_super_admin(); } + + /** + * Determines if the value on the DB is the correct format. + * + * @since 4.15.0 + * + * @return bool + */ + public function is_valid_key_format() { + $license_opt = (string) get_option( $this->get_license_option_key() ); + if ( empty( $license_opt ) ) { + return false; + } + + if ( ! preg_match( "/([0-9a-z]+)/i", $license_opt, $matches ) ) { + return false; + } + + // Pull the matching string into a variable + $license = $matches[1]; + + if ( 40 !== strlen( $license ) ) { + return false; + } + + return true; + } } } diff --git a/tribe-common/src/Tribe/PUE/Notices.php b/tribe-common/src/Tribe/PUE/Notices.php index 8946c9ac7a..4f9c8702df 100644 --- a/tribe-common/src/Tribe/PUE/Notices.php +++ b/tribe-common/src/Tribe/PUE/Notices.php @@ -276,6 +276,15 @@ public function render_invalid_key() { $plugin_names = $this->get_formatted_plugin_names( self::INVALID_KEY ); + /** + * Filters the list of plugins that should trigger an invalid key notice in PUE. + * + * @since 5.0.0 + * + * @param array $plugin_names Array of plugin names that should trigger the invalid key notice. + */ + $plugin_names = apply_filters( 'tec_pue_invalid_key_notice_plugins', $plugin_names ); + if ( empty( $plugin_names ) ) { return; } @@ -322,6 +331,15 @@ public function render_expired_key() { $plugin_names = $this->get_formatted_plugin_names( self::EXPIRED_KEY ); + /** + * Filters the list of plugins that should trigger an expired key notice in PUE. + * + * @since 5.0.0 + * + * @param array $plugin_names Array of plugin names that should trigger the expired key notice. + */ + $plugin_names = apply_filters( 'tec_pue_expired_key_notice_plugins', $plugin_names ); + if ( empty( $plugin_names ) ) { return; } @@ -354,6 +372,15 @@ public function render_expired_key() { public function render_upgrade_key() { $plugin_names = $this->get_formatted_plugin_names( self::UPGRADE_KEY ); + /** + * Filters the list of plugins that should trigger an upgrade key notice in PUE. + * + * @since 5.0.0 + * + * @param array $plugin_names Array of plugin names that should trigger the upgrade key notice. + */ + $plugin_names = apply_filters( 'tec_pue_upgrade_key_notice_plugins', $plugin_names ); + if ( empty( $plugin_names ) ) { return; } diff --git a/tribe-common/src/Tribe/Plugin_Meta_Links.php b/tribe-common/src/Tribe/Plugin_Meta_Links.php index 48269df78f..fd674cbfbf 100644 --- a/tribe-common/src/Tribe/Plugin_Meta_Links.php +++ b/tribe-common/src/Tribe/Plugin_Meta_Links.php @@ -124,7 +124,7 @@ public function filter_meta_links( $links, $basename ) { * * @return void */ - final private function __clone() { + final public function __clone() { _doing_it_wrong( __FUNCTION__, 'Can not use this method on singletons.', @@ -137,7 +137,7 @@ final private function __clone() { * * @return void */ - final private function __wakeup() { + final public function __wakeup() { _doing_it_wrong( __FUNCTION__, 'Can not use this method on singletons.', diff --git a/tribe-common/src/Tribe/Plugins_API.php b/tribe-common/src/Tribe/Plugins_API.php index 27e57a4a47..54137ea0a1 100644 --- a/tribe-common/src/Tribe/Plugins_API.php +++ b/tribe-common/src/Tribe/Plugins_API.php @@ -25,7 +25,10 @@ public function get_products() { 'title' => __( 'The Events Calendar', 'tribe-common' ), 'slug' => 'the-events-calendar', 'link' => 'https://evnt.is/1ai-', + 'plugin-dir' => 'the-events-calendar', + 'main-file' => 'the-events-calendar.php', 'description' => __( 'Our flagship free calendar', 'tribe-common' ), + 'description-help' => __( 'The #1 calendar for WordPress', 'tribe-common' ), 'features' => [ __( 'Customizable', 'tribe-common' ), __( 'Import & export events', 'tribe-common' ), @@ -42,7 +45,10 @@ public function get_products() { 'title' => __( 'Event Aggregator', 'tribe-common' ), 'slug' => 'event-aggregator', 'link' => 'https://evnt.is/1aj0', + 'plugin-dir' => '', + 'main-file' => '', 'description' => __( 'Automated imports for your calendar', 'tribe-common' ), + 'description-help' => __( 'Import events from Meetup, Eventbrite, iCal, Google Calendar, and more.', 'tribe-common' ), 'features' => [ __( 'Schedule automated imports', 'tribe-common' ), __( 'Customizable', 'tribe-common' ), @@ -59,12 +65,16 @@ public function get_products() { 'title' => __( 'Events Calendar Pro', 'tribe-common' ), 'slug' => 'events-calendar-pro', 'link' => 'https://evnt.is/1ai-', + 'plugin-dir' => 'events-calendar-pro', + 'main-file' => 'events-calendar-pro.php', 'description' => __( 'Power up your calendar with Pro', 'tribe-common' ), + 'description-help' => __( 'The #1 calendar for WordPress', 'tribe-common' ), 'features' => [ __( 'Premium support', 'tribe-common' ), - __( 'Recurring events', 'tribe-common' ), + __( 'Recurring events & series', 'tribe-common' ), __( 'Additional views', 'tribe-common' ), __( 'Shortcodes', 'tribe-common' ), + __( 'Duplicate events', 'tribe-common' ), ], 'image' => 'images/shop/pro.jpg', 'logo' => 'images/logo/events-calendar-pro.svg', @@ -76,7 +86,10 @@ public function get_products() { 'title' => __( 'Event Tickets', 'tribe-common' ), 'slug' => 'event-tickets', 'link' => 'https://evnt.is/1aj1', + 'plugin-dir' => 'event-tickets', + 'main-file' => 'event-tickets.php', 'description' => __( 'Manage ticketing and RSVPs', 'tribe-common' ), + 'description-help' => __( 'Collect RSVPs and sell tickets', 'tribe-common' ), 'features' => [ __( 'Add tickets and RSVP to any post', 'tribe-common' ), __( 'Paypal integration', 'tribe-common' ), @@ -93,7 +106,10 @@ public function get_products() { 'title' => __( 'Event Tickets Plus', 'tribe-common' ), 'slug' => 'event-tickets-plus', 'link' => 'http://evnt.is/1aj1', + 'plugin-dir' => 'event-tickets-plus', + 'main-file' => 'event-tickets-plus.php', 'description' => __( 'Monetize your events', 'tribe-common' ), + 'description-help' => __( 'Collect RSVPs and sell tickets', 'tribe-common' ), 'features' => [ __( 'Custom registration fields', 'tribe-common' ), __( 'WooCommerce compatibility', 'tribe-common' ), @@ -110,7 +126,10 @@ public function get_products() { 'title' => __( 'Promoter', 'tribe-common' ), 'slug' => 'promoter', 'link' => 'https://evnt.is/1acy', + 'plugin-dir' => '', + 'main-file' => '', 'description' => __( 'An email marketing solution for events and the people running them', 'tribe-common' ), + 'description-help' => __( 'Email marketing to promote your events', 'tribe-common' ), 'features' => [ __( 'Automate email touchpoints', 'tribe-common' ), __( 'Customize email templates', 'tribe-common' ), @@ -127,7 +146,10 @@ public function get_products() { 'title' => __( 'Filter Bar', 'tribe-common' ), 'slug' => 'tribe-filterbar', 'link' => 'https://evnt.is/19o6', + 'plugin-dir' => 'the-events-calendar-filterbar', + 'main-file' => 'the-events-calendar-filter-view.php', 'description' => __( 'Help users find exactly the right event', 'tribe-common' ), + 'description-help' => __( 'Allow users to search for events by category, tag, venue, organizer, day of the week, time of day, and price.', 'tribe-common' ), 'features' => [ __( 'Configurable set of filters', 'tribe-common' ), __( 'Horizontal or vertical', 'tribe-common' ), @@ -144,7 +166,10 @@ public function get_products() { 'title' => __( 'Community Events', 'tribe-common' ), 'slug' => 'events-community', 'link' => 'https://evnt.is/19o7', + 'plugin-dir' => 'the-events-calendar-community-events', + 'main-file' => 'tribe-community-events.php', 'description' => __( 'Users submit events to your calendar', 'tribe-common' ), + 'description-help' => __( 'Enable 3rd party event submissions.', 'tribe-common' ), 'features' => [ __( 'Publishing Control', 'tribe-common' ), __( 'Event Submission Form', 'tribe-common' ), @@ -161,7 +186,10 @@ public function get_products() { 'title' => __( 'Community Tickets', 'tribe-common' ), 'slug' => 'events-community-tickets', 'link' => 'https://evnt.is/19o8', + 'plugin-dir' => 'the-events-calendar-community-events-tickets', + 'main-file' => 'events-community-tickets.php', 'description' => __( 'Run your own events marketplace', 'tribe-common' ), + 'description-help' => __( 'Let users create and sell tickets for events they submit to your calendar.', 'tribe-common' ), 'features' => [ __( 'Users submit events and sell tickets', 'tribe-common' ), __( 'Split commission with users', 'tribe-common' ), @@ -179,7 +207,10 @@ public function get_products() { 'title' => __( 'Eventbrite Tickets', 'tribe-common' ), 'slug' => 'tribe-eventbrite', 'link' => 'https://evnt.is/19o9', + 'plugin-dir' => 'the-events-calendar-eventbrite-tickets', + 'main-file' => 'tribe-eventbrite.php', 'description' => __( 'Unite the power of TEC with the ticketing of Eventbrite', 'tribe-common' ), + 'description-help' => __( 'Create Eventbrite tickets and events right from your WordPress dashboard.', 'tribe-common' ), 'features' => [ __( 'Manage tickets from WordPress', 'tribe-common' ), __( 'Ticket availability automatically updates', 'tribe-common' ), @@ -196,6 +227,8 @@ public function get_products() { 'title' => __( 'Image Widget Plus', 'tribe-common' ), 'slug' => 'image-widget-plus', 'link' => 'https://evnt.is/19nv', + 'plugin-dir' => 'image-widget-plus', + 'main-file' => 'image-widget-plus.php', 'description' => __( 'Beautiful display options for your favorite photos.', 'tribe-common' ), 'features' => [ __( 'Multi-Image Support', 'tribe-common' ), @@ -213,7 +246,10 @@ public function get_products() { 'title' => __( 'Virtual Events', 'tribe-common' ), 'slug' => 'events-virtual', 'link' => 'http://evnt.is/virtual-events', + 'plugin-dir' => 'events-virtual', + 'main-file' => 'events-virtual.php', 'description' => __( 'Features to optimize your calendar for virtual events.', 'tribe-common' ), + 'description-help' => __( 'Highlight virtual events on you calendar and integrate with your favorite online meeting tools.', 'tribe-common' ), 'features' => [ __( 'Zoom integration', 'tribe-common' ), __( 'Virtual event labels', 'tribe-common' ), diff --git a/tribe-common/src/Tribe/Process/Queue.php b/tribe-common/src/Tribe/Process/Queue.php index 650f920b10..b1dc37fcc8 100644 --- a/tribe-common/src/Tribe/Process/Queue.php +++ b/tribe-common/src/Tribe/Process/Queue.php @@ -445,7 +445,7 @@ protected function save_split_data( $key, array $data ) { foreach ( $split_data as $i => $iValue ) { $postfix = 0 === $i ? '' : "_{$i}"; - update_option( $key . $postfix, $split_data[ $i ] ); + update_option( $key . $postfix, $split_data[ $i ], false ); } return count( $split_data ); diff --git a/tribe-common/src/Tribe/Promoter/Connector.php b/tribe-common/src/Tribe/Promoter/Connector.php index ad22acd351..75049b952f 100644 --- a/tribe-common/src/Tribe/Promoter/Connector.php +++ b/tribe-common/src/Tribe/Promoter/Connector.php @@ -54,7 +54,7 @@ public function authorize_with_connector( $user_id, $secret_key, $promoter_key, 'userId' => $user_id, ]; - $token = \Firebase\JWT\JWT::encode( $payload, $promoter_key ); + $token = \Firebase\JWT\JWT::encode( $payload, $promoter_key, 'HS256' ); $response = $this->make_call( $url, [ 'body' => [ 'token' => $token ], @@ -196,7 +196,7 @@ public function notify_promoter_of_changes( $post_id ) { 'sourceId' => $post_id instanceof WP_Post ? $post_id->ID : $post_id, ]; - $token = \Firebase\JWT\JWT::encode( $payload, $secret_key ); + $token = \Firebase\JWT\JWT::encode( $payload, $secret_key, 'HS256' ); $url = $this->base_url() . 'connect/notify'; diff --git a/tribe-common/src/Tribe/REST/Main.php b/tribe-common/src/Tribe/REST/Main.php index 73b3504136..5fcc28dcc4 100644 --- a/tribe-common/src/Tribe/REST/Main.php +++ b/tribe-common/src/Tribe/REST/Main.php @@ -79,9 +79,9 @@ public function get_url( $path = '/', $scheme = 'rest', $blog_id = null ) { global $wp_rewrite; if ( $wp_rewrite->using_index_permalinks() ) { - $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . self::get_url_prefix(), $scheme ); + $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . $this->get_url_prefix(), $scheme ); } else { - $url = get_home_url( $blog_id, self::get_url_prefix(), $scheme ); + $url = get_home_url( $blog_id, $this->get_url_prefix(), $scheme ); } $url .= '/' . ltrim( $path, '/' ); diff --git a/tribe-common/src/Tribe/REST/System.php b/tribe-common/src/Tribe/REST/System.php index eaa4f36a3d..0b9a05e389 100644 --- a/tribe-common/src/Tribe/REST/System.php +++ b/tribe-common/src/Tribe/REST/System.php @@ -11,4 +11,23 @@ class Tribe__REST__System { public function supports_wp_rest_api() { return function_exists( 'get_rest_url' ); } + + /** + * Determines if we are coming from a REST API request. + * + * @since 5.0.0 + * + * @return bool + */ + public static function is_rest_api() { + if ( empty( $_SERVER['REQUEST_URI'] ) ) { + // Probably a CLI request + return false; + } + + $rest_prefix = trailingslashit( rest_get_url_prefix() ); + $is_rest_api_request = strpos( $_SERVER['REQUEST_URI'], $rest_prefix ) !== false; + + return $is_rest_api_request; + } } diff --git a/tribe-common/src/Tribe/Repository.php b/tribe-common/src/Tribe/Repository.php index eb830bfac1..716b37c298 100644 --- a/tribe-common/src/Tribe/Repository.php +++ b/tribe-common/src/Tribe/Repository.php @@ -871,6 +871,16 @@ public function first() { } $query->set( 'fields', 'ids' ); + + /** + * Filters the query object by reference before getting the first post from the query. + * + * @since 4.14.8 + * + * @param WP_Query $query The WP_Query object before get_posts() is called. + */ + do_action( "tribe_repository_{$this->filter_name}_pre_first_post", $query ); + $ids = $query->get_posts(); $query->set( 'fields', $original_fields_value ); @@ -935,6 +945,16 @@ public function last() { } $query->set( 'fields', 'ids' ); + + /** + * Filters the query object by reference before getting the last post from the query. + * + * @since 4.14.8 + * + * @param WP_Query $query The WP_Query object before get_posts() is called. + */ + do_action( "tribe_repository_{$this->filter_name}_pre_last_post", $query ); + $ids = $query->get_posts(); $query->set( 'fields', $original_fields_value ); @@ -1378,6 +1398,15 @@ public function get_ids() { if ( empty( $query->request ) ) { $query->set( 'fields', 'ids' ); + /** + * Filters the query object by reference before getting the post IDs from the query. + * + * @since 4.14.8 + * + * @param WP_Query $query The WP_Query object before get_posts() is called. + */ + do_action( "tribe_repository_{$this->filter_name}_pre_get_ids_for_posts", $query ); + return $query->get_posts(); } @@ -1794,7 +1823,9 @@ protected function apply_default_modifier( $key, $value ) { $args = [ 'p' => $value ]; break; case 'search': - $args = [ 's' => $value ]; + if ( '' !== $value ) { + $args = [ 's' => $value ]; + } break; case 'post_status': $this->query_args['post_status'] = (array) $value; @@ -2398,6 +2429,40 @@ public function by_related_to_between( $by_meta_keys, $min, $max, $keys = null, return $this; } + /** + * {@inheritdoc} + */ + public function by_not_related_to( $by_meta_keys, $keys = null, $values = null ) { + + /** @var wpdb $wpdb */ + global $wpdb; + + $by_meta_keys = $this->prepare_interval( $by_meta_keys ); + + $join = ''; + $and_where = ''; + if ( ! empty( $keys ) || ! empty( $values ) ) { + $join = "\nJOIN {$wpdb->postmeta} pm2 ON pm1.post_id = pm2.post_id\n"; + } + if ( ! empty( $keys ) ) { + $keys = $this->prepare_interval( $keys ); + $and_where .= "\nAND pm2.meta_key IN {$keys}\n"; + } + if ( ! empty( $values ) ) { + $values = $this->prepare_interval( $values ); + $and_where .= "\nAND pm2.meta_value IN {$values}\n"; + } + + $this->where_clause( "{$wpdb->posts}.ID NOT IN ( + SELECT pm1.meta_value + FROM {$wpdb->postmeta} pm1 {$join} + WHERE pm1.meta_key IN {$by_meta_keys} {$and_where} + GROUP BY( pm1.meta_value ) + )" ); + + return $this; + } + /** * {@inheritdoc} */ @@ -2891,7 +2956,10 @@ public function build_postarr( $id = null ) { } foreach ( $this->updates as $key => $value ) { - if ( is_callable( $value ) ) { + if ( + $value instanceof Closure || + ( is_array( $value ) && is_callable( $value ) ) + ) { $value = $value( $id, $key, $this ); } @@ -3694,4 +3762,13 @@ public function void_query( $void_query = true ) { return $this; } + + /** + * {@inheritDoc} + */ + public function get_last_sql(): ?string { + return $this->last_built_query instanceof WP_Query ? + $this->last_built_query->request + : null; + } } diff --git a/tribe-common/src/Tribe/Repository/Decorator.php b/tribe-common/src/Tribe/Repository/Decorator.php index 642e83f42d..6947f609db 100644 --- a/tribe-common/src/Tribe/Repository/Decorator.php +++ b/tribe-common/src/Tribe/Repository/Decorator.php @@ -354,6 +354,15 @@ public function by_related_to_between( $by_meta_keys, $min, $max, $keys = null, return $this; } + /** + * {@inheritdoc} + */ + public function by_not_related_to( $by_meta_keys, $keys = null, $values = null ) { + $this->decorated->by_not_related_to( $by_meta_keys, $keys, $values ); + + return $this; + } + /** * {@inheritdoc} */ @@ -685,4 +694,11 @@ public function void_query( $void_query = true ) { return $this; } + + /** + * {@inheritDoc} + */ + public function get_last_sql(): ?string { + return $this->decorated->get_last_sql(); + } } diff --git a/tribe-common/src/Tribe/Repository/Interface.php b/tribe-common/src/Tribe/Repository/Interface.php index 410fce90c0..6ac4f49399 100644 --- a/tribe-common/src/Tribe/Repository/Interface.php +++ b/tribe-common/src/Tribe/Repository/Interface.php @@ -186,6 +186,24 @@ public function by_related_to_max( $by_meta_keys, $max, $keys = null, $values = */ public function by_related_to_between( $by_meta_keys, $min, $max, $keys = null, $values = null ); + /** + * Filters the query to return posts that are not related to posts that have a specific meta value. + * + * @since 5.0.2.1 + * + * @param string|array $by_meta_keys One or more `meta_keys` relating + * another post TO this post type. + * + * @param string|array $keys One or more meta_keys to check on the post type in relation + * with the query post type(s); if the `$values` parameter is + * not provided then this will trigger an EXISTS check. + * @param string|array $values One or more value the meta_key specified with `$keys` should + * match. + * + * @return $this + */ + public function by_not_related_to( $by_meta_keys, $keys = null, $values = null ); + /** * Adds an entry to the repository filter schema. * @@ -320,4 +338,13 @@ public function set_found_rows( $found_rows ); * @return Tribe__Repository__Interface $this The repository instance. */ public function void_query( $void_query = true ); + + /** + * Returns the SQL code for the last query built and ran by the repository, if any. + * + * @since 5.0.1 + * + * @return string|null The SQL code for the last query built and ran by the repository, if any. + */ + public function get_last_sql(): ?string; } diff --git a/tribe-common/src/Tribe/Repository/Query_Filters.php b/tribe-common/src/Tribe/Repository/Query_Filters.php index c04f602621..4eb5f908b5 100644 --- a/tribe-common/src/Tribe/Repository/Query_Filters.php +++ b/tribe-common/src/Tribe/Repository/Query_Filters.php @@ -1012,7 +1012,7 @@ public function filter_posts_orderby( $orderby, WP_Query $query ) { $frags[] = implode( ', ', array_map( $build_entry, $this->query_vars[ static::AFTER . 'orderby' ] ) ); } - return implode( ', ', $frags ); + return implode( ', ', array_filter( $frags ) ); } /** diff --git a/tribe-common/src/Tribe/Service_Providers/Dialog.php b/tribe-common/src/Tribe/Service_Providers/Dialog.php index b5e5acc5e1..b511966f3f 100644 --- a/tribe-common/src/Tribe/Service_Providers/Dialog.php +++ b/tribe-common/src/Tribe/Service_Providers/Dialog.php @@ -75,7 +75,7 @@ public function register_dialog_assets() { $main, 'tribe-dialog', 'dialog.css', - [], + [ 'tec-variables-full' ], [], [ 'groups' => 'tribe-dialog' ] ); diff --git a/tribe-common/src/Tribe/Service_Providers/Onboarding.php b/tribe-common/src/Tribe/Service_Providers/Onboarding.php new file mode 100644 index 0000000000..1f4af1abc8 --- /dev/null +++ b/tribe-common/src/Tribe/Service_Providers/Onboarding.php @@ -0,0 +1,147 @@ +is_enabled() ) { + return; + } + + $this->container->singleton( Onboarding_Main::class, Onboarding_Main::class ); + $this->container->singleton( static::class, static::class ); + + $this->hooks(); + } + + /** + * Set up hooks for classes. + * + * @since 4.14.9 + */ + protected function hooks() { + add_action( 'tribe_common_loaded', [ $this, 'register_assets' ] ); + + add_action( 'admin_enqueue_scripts', tribe_callback( Onboarding_Main::class, 'localize_tour' ) ); + add_action( 'admin_enqueue_scripts', tribe_callback( Onboarding_Main::class, 'localize_hints' ) ); + } + + /** + * Register assets associated with onboarding. + * + * @since 4.14.9 + */ + public function register_assets() { + $main = \Tribe__Main::instance(); + + tribe_asset( + $main, + 'tec-intro-js', + 'node_modules/intro.js/intro.js', + [], + [ 'admin_enqueue_scripts' ], + [ + 'groups' => self::$group_key, + 'conditionals' => [ $this, 'should_enqueue_assets' ], + ] + ); + + tribe_asset( + $main, + 'tec-intro-styles', + 'node_modules/intro.js/introjs.css', + [], + [ 'admin_enqueue_scripts' ], + [ + 'groups' => self::$group_key, + 'conditionals' => [ $this, 'should_enqueue_assets' ], + ] + ); + + tribe_asset( + $main, + 'tec-onboarding-styles', + 'onboarding.css', + [ 'tec-intro-styles', 'tec-variables-skeleton', 'tec-variables-full' ], + [ 'admin_enqueue_scripts' ], + [ + 'groups' => self::$group_key, + 'conditionals' => [ $this, 'should_enqueue_assets' ], + ] + ); + + tribe_asset( + $main, + 'tec-onboarding-js', + 'onboarding.js', + [ + 'tribe-common', + 'tec-intro-js', + ], + [ 'admin_enqueue_scripts' ], + [ + 'groups' => self::$group_key, + 'in_footer' => false, + 'localize' => [ + 'name' => 'TribeOnboarding', + 'data' => [ + 'hintButtonLabel' => __( 'Got it', 'tribe-common' ), + ], + ], + 'conditionals' => [ $this, 'should_enqueue_assets' ], + ] + ); + } + + /** + * Define if the assets for `Onboarding` should be enqueued or not. + * + * @since 4.14.9 + * + * @return bool If the Onboarding assets should be enqueued or not. + */ + public function should_enqueue_assets() { + return $this->is_enabled(); + } + + /** + * Check if the onboarding is enabled or not. + * + * @since 4.14.9 + * + * @return bool + */ + public function is_enabled() { + /** + * Filter to disable tribe onboarding + * + * @since 4.14.9 + * + * @param bool $disabled If we want to disable the on boarding. + */ + $is_enabled = (bool) apply_filters( 'tec_onboarding_enabled', false ); + + return $is_enabled && is_admin(); + } +} diff --git a/tribe-common/src/Tribe/Service_Providers/Tooltip.php b/tribe-common/src/Tribe/Service_Providers/Tooltip.php index 6cbad8bf8d..9520fb538c 100644 --- a/tribe-common/src/Tribe/Service_Providers/Tooltip.php +++ b/tribe-common/src/Tribe/Service_Providers/Tooltip.php @@ -1,4 +1,5 @@ hook(); } @@ -43,8 +44,10 @@ public function add_tooltip_assets() { 'tribe-tooltip', 'tooltip.css', [ 'tribe-common-skeleton-style' ], - [ 'wp_enqueue_scripts', 'admin_enqueue_scripts' ], - [ 'groups' => 'tribe-tooltip' ] + null, + [ + 'groups' => 'tribe-tooltip', + ] ); tribe_asset( @@ -52,8 +55,10 @@ public function add_tooltip_assets() { 'tribe-tooltip-js', 'tooltip.js', [ 'jquery', 'tribe-common' ], - [], - [ 'groups' => 'tribe-tooltip' ] + null, + [ + 'groups' => 'tribe-tooltip' + ] ); } } diff --git a/tribe-common/src/Tribe/Settings.php b/tribe-common/src/Tribe/Settings.php index 1b556721e7..a589226681 100755 --- a/tribe-common/src/Tribe/Settings.php +++ b/tribe-common/src/Tribe/Settings.php @@ -5,6 +5,8 @@ die( '-1' ); } +use Tribe\Admin\Pages as AdminPages; + if ( ! class_exists( 'Tribe__Settings' ) ) { /** * helper class that allows registration of settings @@ -14,13 +16,15 @@ */ class Tribe__Settings { /** - * Slug of the parent menu slug + * Slug of the parent menu slug. + * * @var string */ public static $parent_slug = 'tribe-common'; /** - * Page of the parent menu + * Page of the parent menu. + * * @var string */ public static $parent_page = 'edit.php'; @@ -31,134 +35,153 @@ class Tribe__Settings { public $live_date_preview; /** - * the tabs that will appear in the settings page - * filtered on class construct + * The tabs that will appear in the settings page + * filtered on class construct. + * * @var array */ public $tabs; /** - * All the tabs registered, not just the ones that will appear + * All the tabs registered, not just the ones that will appear. + * * @var array */ public $allTabs; /** - * multidimensional array of the fields that will be generated - * for the entire settings panel, tabs are represented in the array keys + * Multidimensional array of the fields that will be generated + * for the entire settings panel, tabs are represented in the array keys. + * * @var array */ public $fields; /** - * the default tab for the settings panel - * this should be a tab ID + * The default tab for the settings panel + * this should be a tab ID. + * * @var string */ public $defaultTab; /** - * the current tab being displayed + * The current tab being displayed. + * * @var string */ public $currentTab; /** - * tabs that shouldn't show the save button + * Tabs that shouldn't show the save button. + * * @var array */ public $noSaveTabs; /** - * The slug used in the admin to generate the settings page + * The slug used in the admin to generate the settings page. + * * @var string */ public $adminSlug; /** - * The slug used in the admin to generate the help page + * The slug used in the admin to generate the help page. + * * @var string */ protected $help_slug; - /** - * the menu name used for the settings page + * The menu name used for the settings page. + * * @var string */ public $menuName; /** - * the required capability for the settings page + * The required capability for the settings page. + * * @var string */ public $requiredCap; /** - * errors that occur after a save operation + * Errors that occur after a save operation. + * * @var mixed */ public $errors; /** - * POST data before/after save + * POST data before/after save. + * * @var mixed */ public $sent_data; /** - * the $current_screen name corresponding to the admin page + * The $current_screen name corresponding to the admin page. + * * @var string */ public $admin_page; /** - * true if a major error that prevents saving occurred + * True if a major error that prevents saving occurred. + * * @var bool */ public $major_error; /** - * holds validated fields + * Holds validated fields. + * * @var array */ public $validated; /** - * Static Singleton Holder + * Static Singleton Holder. + * * @var Tribe__Settings|null */ private static $instance; /** * The settings page URL. + * * @var string */ protected $url; /** * An array defining the suite root plugins. + * * @var array */ protected $root_plugins = [ 'the-events-calendar/the-events-calendar.php', - 'event-tickets/event-ticket.php', + 'event-tickets/event-tickets.php', ]; /** * An associative array in the form [ => array(...) ] + * * @var array */ protected $fields_for_save = []; /** * An array that contains the fields that are currently being validated. + * * @var array */ protected $current_fields = []; /** - * Static Singleton Factory Method + * Static Singleton Factory Method. * * @return Tribe__Settings */ @@ -167,13 +190,13 @@ public static function instance() { } /** - * Class constructor + * Class constructor. * * @return void */ public function __construct() { - // set instance variables + // Set instance variables. $this->menuName = apply_filters( 'tribe_settings_menu_name', esc_html__( 'Events', 'tribe-common' ) ); $this->requiredCap = apply_filters( 'tribe_settings_req_cap', 'manage_options' ); $this->adminSlug = apply_filters( 'tribe_settings_admin_slug', 'tribe-common' ); @@ -192,9 +215,7 @@ public function __construct() { * Hooks the actions and filters required for the class to work. */ public function hook() { - // run actions & filters - add_action( 'admin_menu', [ $this, 'addPage' ] ); - add_action( 'network_admin_menu', [ $this, 'addNetworkPage' ] ); + // Run actions & filters. add_action( 'admin_init', [ $this, 'initTabs' ] ); add_action( 'tribe_settings_below_tabs', [ $this, 'displayErrors' ] ); add_action( 'tribe_settings_below_tabs', [ $this, 'displaySuccess' ] ); @@ -203,22 +224,11 @@ public function hook() { /** * Determines whether or not the full admin pages should be initialized. * - * When running in parallel with TEC 3.12.4, TEC should be relied on to handle the admin screens - * that version of TEC (and lower) is tribe-common ignorant. Therefore, tribe-common has to be - * the smarter, more lenient codebase. - * * @return boolean */ public function should_setup_pages() { - if ( ! class_exists( 'Tribe__Events__Main' ) ) { - return true; - } - - if ( version_compare( Tribe__Events__Main::VERSION, '4.0beta', '>=' ) ) { - return true; - } - - return false; + // @todo: Deprecate this and update where needed. + return true; } /** @@ -227,36 +237,7 @@ public function should_setup_pages() { * @return void */ public function addPage() { - if ( ! $this->should_setup_pages() ) { - return; - } - - if ( ! is_multisite() || ( is_multisite() && '0' == Tribe__Settings_Manager::get_network_option( 'allSettingsTabsHidden', '0' ) ) ) { - if ( post_type_exists( 'tribe_events' ) ) { - self::$parent_page = 'edit.php?post_type=tribe_events'; - } else { - self::$parent_page = 'admin.php?page=tribe-common'; - - add_menu_page( - esc_html__( 'Events', 'tribe-common' ), - esc_html__( 'Events', 'tribe-common' ), - apply_filters( 'tribe_common_event_page_capability', 'manage_options' ), - self::$parent_slug, - null, - 'dashicons-calendar', - 6 - ); - } - - $this->admin_page = add_submenu_page( - $this->get_parent_slug(), - esc_html__( 'Events Settings', 'tribe-common' ), - esc_html__( 'Settings', 'tribe-common' ), - $this->requiredCap, - self::$parent_slug, - [ $this, 'generatePage' ] - ); - } + _deprecated_function( __METHOD__, '4.15.0' ); } /** @@ -265,102 +246,127 @@ public function addPage() { * @return void */ public function addNetworkPage() { - if ( ! $this->should_setup_network_pages() ) { - return; - } - - $this->admin_page = add_submenu_page( - 'settings.php', esc_html__( 'Events Settings', 'tribe-common' ), esc_html__( 'Events Settings', 'tribe-common' ), $this->requiredCap, $this->adminSlug, [ - $this, - 'generatePage', - ] - ); - - $this->admin_page = add_submenu_page( - 'settings.php', - esc_html__( 'Events Help', 'tribe-common' ), - esc_html__( 'Events Help', 'tribe-common' ), - $this->requiredCap, - $this->help_slug, - [ - tribe( 'settings.manager' ), - 'do_help_tab', - ] - ); + _deprecated_function( __METHOD__, '4.15.0' ); } /** - * init all the tabs + * Init all the tabs. * * @return void */ public function initTabs() { - if ( - empty( $_GET['page'] ) - || $_GET['page'] != $this->adminSlug - ) { + $admin_pages = tribe( 'admin.pages' ); + $admin_page = $admin_pages->get_current_page(); + + if ( empty( $admin_pages->has_tabs( $admin_page ) ) ) { return; } - // Load settings tab-specific helpers and enhancements + // Load settings tab-specific helpers and enhancements. Tribe__Admin__Live_Date_Preview::instance(); - do_action( 'tribe_settings_do_tabs' ); // this is the hook to use to add new tabs - $this->tabs = (array) apply_filters( 'tribe_settings_tabs', [] ); - $this->allTabs = (array) apply_filters( 'tribe_settings_all_tabs', [] ); - $this->noSaveTabs = (array) apply_filters( 'tribe_settings_no_save_tabs', [] ); + do_action( 'tribe_settings_do_tabs', $admin_page ); // This is the hook to use to add new tabs. + + $this->tabs = (array) apply_filters( 'tribe_settings_tabs', [], $admin_page ); + $this->allTabs = (array) apply_filters( 'tribe_settings_all_tabs', [], $admin_page ); + $this->noSaveTabs = (array) apply_filters( 'tribe_settings_no_save_tabs', [], $admin_page ); if ( is_network_admin() ) { - $this->defaultTab = apply_filters( 'tribe_settings_default_tab_network', 'network' ); - $this->currentTab = apply_filters( 'tribe_settings_current_tab', ( isset( $_GET['tab'] ) && $_GET['tab'] ) ? esc_attr( $_GET['tab'] ) : $this->defaultTab ); - $this->url = apply_filters( - 'tribe_settings_url', add_query_arg( - [ - 'page' => $this->adminSlug, - 'tab' => $this->currentTab, - ], network_admin_url( 'settings.php' ) - ) - ); + $this->defaultTab = apply_filters( 'tribe_settings_default_tab_network', 'network', $admin_page ); + $current_tab = ( isset( $_GET['tab'] ) && $_GET['tab'] ) ? esc_attr( $_GET['tab'] ) : $this->defaultTab; + $this->currentTab = apply_filters( 'tribe_settings_current_tab', $current_tab, $admin_page ); + $this->url = $this->get_tab_url( $this->currentTab ); } else { $tabs_keys = array_keys( $this->tabs ); - $this->defaultTab = in_array( apply_filters( 'tribe_settings_default_tab', 'general' ), $tabs_keys ) ? apply_filters( 'tribe_settings_default_tab', 'general' ) : $tabs_keys[0]; + $default_tab = apply_filters( 'tribe_settings_default_tab', 'general', $admin_page ); + $this->defaultTab = in_array( $default_tab, $tabs_keys ) ? $default_tab : $tabs_keys[0]; $this->currentTab = apply_filters( 'tribe_settings_current_tab', ( isset( $_GET['tab'] ) && $_GET['tab'] ) ? esc_attr( $_GET['tab'] ) : $this->defaultTab ); - $this->url = apply_filters( - 'tribe_settings_url', add_query_arg( - [ - 'page' => $this->adminSlug, - 'tab' => $this->currentTab, - ], - admin_url( self::$parent_page ) - ) - ); + $this->url = $this->get_tab_url( $this->currentTab ); } - $this->fields_for_save = (array) apply_filters( 'tribe_settings_fields', [] ); - do_action( 'tribe_settings_after_do_tabs' ); - $this->fields = (array) apply_filters( 'tribe_settings_fields', [] ); + $this->fields_for_save = (array) apply_filters( 'tribe_settings_fields', [], $admin_page ); + do_action( 'tribe_settings_after_do_tabs', $admin_page ); + $this->fields = (array) apply_filters( 'tribe_settings_fields', [], $admin_page ); $this->validate(); } /** - * generate the main option page - * includes the view file + * Get the current settings page URL + * + * @since 4.15.0 + * + * @return string The current settings page URL. + */ + public function get_settings_page_url( array $args = [] ) { + $admin_pages = tribe( 'admin.pages' ); + $page = $admin_pages->get_current_page(); + $tab = tribe_get_request_var( 'tab', $this->defaultTab ); + $defaults = [ + 'page' => $page, + 'tab' => $tab, + ]; + + // Allow the link to be "changed" on the fly. + $args = wp_parse_args( $args, $defaults ); + + $url = add_query_arg( + $args, + is_network_admin() ? network_admin_url( 'settings.php' ) : admin_url( 'admin.php' ) + ); + + return apply_filters( 'tribe_settings_page_url', $url, $page, $tab ); + } + + /** + * Get the settings page title. + * + * @since 4.15.0 + * + * @param string $admin_page The admin page ID. + * @return string The settings page title. + */ + public function get_page_title( $admin_page ) { + $page_title = sprintf( + // Translators: %s is the name of the menu item. + __( '%s Settings', 'tribe-common' ), + $this->menuName + ); + + /** + * Filter the tribe settings page title. + * + * @since 4.15.0 + * + * @param string $page_title The settings page title. + * @param string $admin_page The admin page ID. + */ + return apply_filters( 'tribe_settings_page_title', $page_title, $admin_page ); + } + + /** + * Generate the main option page. + * includes the view file. + * + * @since 4.15.0 Add the current page as parameter for the actions. * * @return void */ public function generatePage() { - do_action( 'tribe_settings_top' ); + $admin_pages = tribe( 'admin.pages' ); + $admin_page = $admin_pages->get_current_page(); + + do_action( 'tribe_settings_top', $admin_page ); echo '
'; echo '

'; - printf( esc_html__( '%s Settings', 'tribe-common' ), $this->menuName ); + echo esc_html( $this->get_page_title( $admin_page ) ); echo '

'; do_action( 'tribe_settings_above_tabs' ); - $this->generateTabs( $this->currentTab ); + $this->generateTabs( $this->currentTab, $admin_page ); do_action( 'tribe_settings_below_tabs' ); - do_action( 'tribe_settings_below_tabs_tab_' . $this->currentTab ); + do_action( 'tribe_settings_below_tabs_tab_' . $this->currentTab, $admin_page ); echo '
'; do_action( 'tribe_settings_above_form_element' ); - do_action( 'tribe_settings_above_form_element_tab_' . $this->currentTab ); + do_action( 'tribe_settings_above_form_element_tab_' . $this->currentTab, $admin_page ); echo apply_filters( 'tribe_settings_form_element_tab_' . $this->currentTab, '
' ); do_action( 'tribe_settings_before_content' ); do_action( 'tribe_settings_before_content_tab_' . $this->currentTab ); @@ -369,7 +375,7 @@ public function generatePage() { echo '

' . esc_html__( "You've requested a non-existent tab.", 'tribe-common' ) . '

'; } do_action( 'tribe_settings_after_content_tab_' . $this->currentTab ); - do_action( 'tribe_settings_after_content' ); + do_action( 'tribe_settings_after_content', $this->currentTab ); if ( has_action( 'tribe_settings_content_tab_' . $this->currentTab ) && ! in_array( $this->currentTab, $this->noSaveTabs ) ) { wp_nonce_field( 'saving', 'tribe-save-settings' ); echo '
'; @@ -378,7 +384,7 @@ public function generatePage() { } echo apply_filters( 'tribe_settings_closing_form_element', '
' ); do_action( 'tribe_settings_after_form_element' ); - do_action( 'tribe_settings_after_form_element_tab_' . $this->currentTab ); + do_action( 'tribe_settings_after_form_element_tab_' . $this->currentTab, $admin_page ); echo '
'; do_action( 'tribe_settings_after_form_div' ); echo '
'; @@ -386,7 +392,7 @@ public function generatePage() { } /** - * generate the tabs in the settings screen + * Generate the tabs in the settings screen. * * @return void */ @@ -394,13 +400,7 @@ public function generateTabs() { if ( is_array( $this->tabs ) && ! empty( $this->tabs ) ) { echo '