Plugin Directory

Changeset 3749369


Ignore:
Timestamp:
10/15/2026 09:46:36 PM (13 days ago)
Author:
publisherkits
Message:

Release 0.18.0

Location:
publisherkits-plugintidy/trunk
Files:
1 deleted
10 edited

Legend:

Unmodified
Added
Removed
  • publisherkits-plugintidy/trunk/includes/class-pktidy-support-copilot-boot.php

    r3708042 r3749369  
    181181                                ),
    182182                                'diagnostics'         => array( __CLASS__, 'diagnostics' ),
     183                                // The customer's own AI answers wherever one is connected in
     184                                // WordPress, at their cost; the support service answers only where
     185                                // none is. The bridge below runs on the WordPress core AI Client
     186                                // and uses no AI code of this plugin's own.
     187                                'local_ai'            => array( __CLASS__, 'local_ai' ),
     188                                'local_ai_available'  => array( __CLASS__, 'local_ai_available' ),
     189                                'connectors_url'      => array( __CLASS__, 'connectors_url' ),
    183190                                'suite'               => array( __CLASS__, 'suite_manifest' ),
    184191                                'accent'              => '#c98a70',
    … …  
    189196                add_action( 'pktidy_settings_after_form', array( __CLASS__, 'render_panel' ) );
    190197                add_action( 'admin_post_pktidy_save_copilot_settings', array( __CLASS__, 'handle_save' ) );
     198        }
     199
     200        /**
     201         * Local answering on the site's own AI: one reply from the WordPress core AI
     202         * Client (Settings > Connectors), at the customer's own cost. The module
     203         * supplies only this plugin's documentation and the administrator's question;
     204         * nothing here reads Freemius, a token, a licence or any of this plugin's
     205         * data, and it uses no AI code of this plugin's own, so it is the same in
     206         * every build. The house standard (1 Oct 2026): the customer's AI answers
     207         * wherever one is connected; the support service answers only where none is.
     208         *
     209         * @since 0.18.0
     210         * @param  string $system System prompt.
     211         * @param  string $user   User prompt.
     212         * @param  array  $opts   max_tokens.
     213         * @return string|\WP_Error
     214         */
     215        public static function local_ai( $system, $user, array $opts = array() ) {
     216                if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
     217                        return new \WP_Error( 'pktidy_copilot_unavailable', __( 'The WordPress AI Client is not available on this site.', 'publisherkits-plugintidy' ) );
     218                }
     219                $max_tokens = isset( $opts['max_tokens'] ) ? (int) $opts['max_tokens'] : 1400;
     220                $system     = (string) $system;
     221                try {
     222                        // A combined prompt is the baseline every provider honours; a separate
     223                        // system instruction is preferred where the client supports it. The
     224                        // builder resolves methods via __call, so it is probed by calling inside
     225                        // try/catch - method_exists() reports false for magic methods.
     226                        $builder = wp_ai_client_prompt( '' === $system ? $user : $system . "\n\n" . $user )->using_max_tokens( $max_tokens );
     227                        if ( '' !== $system ) {
     228                                try {
     229                                        $b2 = wp_ai_client_prompt( $user )->using_max_tokens( $max_tokens )->using_system_instruction( $system );
     230                                        if ( is_object( $b2 ) ) {
     231                                                $builder = $b2;
     232                                        }
     233                                } catch ( \Throwable $e ) {
     234                                        unset( $e );
     235                                }
     236                        }
     237                        $text = $builder->generate_text();
     238                        return is_wp_error( $text ) ? $text : (string) $text;
     239                } catch ( \Throwable $e ) {
     240                        return new \WP_Error( 'pktidy_copilot_failed', __( 'The site AI provider could not produce a reply.', 'publisherkits-plugintidy' ) );
     241                }
     242        }
     243
     244        /**
     245         * Whether the WordPress core AI Client has a provider that can generate text
     246         * right now. The client ships with WordPress 7.0; on older sites this is false
     247         * and the support service answers.
     248         *
     249         * @since 0.18.0
     250         * @return bool
     251         */
     252        public static function local_ai_available() {
     253                if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
     254                        return false;
     255                }
     256                try {
     257                        return (bool) wp_ai_client_prompt( 'test' )->is_supported_for_text_generation();
     258                } catch ( \Throwable $e ) {
     259                        return false;
     260                }
     261        }
     262
     263        /**
     264         * Where an administrator connects an AI provider: Settings > Connectors from
     265         * WordPress 7.0, the general Settings screen before it.
     266         *
     267         * @since 0.18.0
     268         * @return string
     269         */
     270        public static function connectors_url() {
     271                return admin_url( function_exists( 'wp_get_connectors' ) ? 'options-connectors.php' : 'options-general.php' );
    191272        }
    192273
  • publisherkits-plugintidy/trunk/includes/support-copilot/class-copilot.php

    r3708042 r3749369  
    55 * Self-contained, host-agnostic popup help chat for the WordPress admin.
    66 * Renders a floating help launcher on the host plugin's admin screens and
    7  * proxies chat/escalation to the Black and White Digital support hub
    8  * (BW Support AI /copilot endpoints). The widget is strictly read-only:
     7 * proxies chat/escalation to the plugin author's support hub
     8 * (/copilot endpoints). The widget is strictly read-only:
    99 * beyond its own consent + settings options it never modifies site state.
    1010 *
    … …  
    1919 *
    2020 * @package BlackWhiteDigital\SupportCopilot
    21  * @version 2.1.4
     21 * @version 2.1.5
    2222 */
    2323
    … …  
    7272                 * Module version (independent of any host plugin version).
    7373                 */
    74                 const MODULE_VERSION = '2.1.4';
     74                const MODULE_VERSION = '2.1.5';
    7575
    7676                /**
    … …  
    203203                                'panel_subtitle'                   => 'Instant answers about this plugin',
    204204                                'consent_heading'                  => 'Before you start',
    205                                 'consent_body'                     => 'To answer your questions, this assistant sends your message, the chat history and a small read-only diagnostic snapshot — the plugin version, the versions of installed companion plugins, the admin screen you are on, and a short whitelist of non-sensitive settings — to the plugin author\'s support service at %host%. No API keys, passwords, contact records or site content are ever sent, and the assistant cannot change anything on your site. Chats are not stored on the support server; only if you later choose "Send to support" is the conversation retained, with the email address you give and your WordPress first and last name, so that it can become a support ticket.',
     205                                'consent_body'                     => 'To answer your questions, this assistant sends your message, the chat history and a small read-only diagnostic snapshot — the plugin version, the versions of installed companion plugins, the admin screen you are on, and a short whitelist of non-sensitive settings — to the plugin author\'s support service at %host%. If this site has its own AI provider configured, the assistant tries that provider first: your question goes to your provider, and the support service is asked only for documentation matched to your version — no question or chat text is sent to it. If that local answer fails and this site is authorised to use the support service, the assistant automatically falls back to the support service, and your question and recent chat history are then sent to it as described above. No API keys, passwords, contact records or site content are ever sent, and the assistant cannot change anything on your site. Chats are not stored on the support server; only if you later choose "Send to support" is the conversation retained, with the email address you give and your WordPress first and last name, so that it can become a support ticket.',
    206206                                'consent_accept'                   => 'Agree and start chatting',
    207207                                'consent_accept_hint'              => 'Records your agreement for this whole site and enables the assistant for everyone who can use it. Only a user with settings access can accept. You can withdraw consent on the settings page.',
    … …  
    244244                                'settings_mode_label'              => 'Answer mode',
    245245                                'settings_mode_local_title'        => 'This site\'s own AI (detected)',
    246                                 'settings_mode_local_desc'         => 'Questions are answered locally on the AI provider configured on this site, at this site\'s expense. The support service is used only to fetch version-matched knowledge files, and as an automatic fallback if the local AI fails. Locally answered replies are labelled in the chat.',
     246                                'settings_mode_local_desc'         => 'Questions are answered locally on the AI provider configured on this site, at this site\'s expense. The support service is asked only for version-matched knowledge files (no question or chat text). If the local AI fails and this site is authorised for the support service, the assistant automatically falls back to it, and the question and recent chat history are then sent to the support service. Locally answered replies are labelled in the chat.',
    247247                                'settings_mode_hub_title'          => 'Support service',
    248248                                'settings_mode_hub_desc'           => 'No local AI provider is configured on this site, so questions are answered by the plugin author\'s support service. If this site later gains its own AI key, the assistant switches to local answering automatically — no setting needed here.',
    … …  
    444444
    445445                /**
    446                  * Whether an endpoint URL may be used: https only. The hub token rides
    447                  * on every request, so a cleartext endpoint would expose it in transit.
     446                 * Whether an endpoint URL may be used: https only, except on a local
     447                 * development install. The hub token rides on every request, so a
     448                 * cleartext endpoint would expose it in transit.
    448449                 *
    449450                 * @param string $url Endpoint base or full request URL.
    … …  
    455456                        }
    456457
    457                         /**
    458                          * Filters whether a non-https support endpoint is allowed.
    459                          *
    460                          * Intended for local development only — never enable in production:
    461                          * the widget token would travel in cleartext.
    462                          *
    463                          * @param bool   $allow Default false.
    464                          * @param string $url   The endpoint URL being checked.
    465                          * @param string $id    The copilot instance id.
    466                          */
    467                         return (bool) apply_filters( 'black_white_digital_support_copilot_allow_insecure_endpoint', false, $url, $this->config['id'] );
     458                        // A cleartext endpoint is allowed only on a site that declares itself
     459                        // a local development install (WP_ENVIRONMENT_TYPE 'local'), never in
     460                        // production: the widget token would travel in cleartext. 2.1.5
     461                        // replaced the former allow-insecure filter, whose fixed name could
     462                        // carry no host prefix in a verbatim-vendored module.
     463                        return function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type();
    468464                }
    469465
  • publisherkits-plugintidy/trunk/includes/support-copilot/support-copilot.php

    r3708042 r3749369  
    2323 *
    2424 * @package BlackWhiteDigital\SupportCopilot
    25  * @version 2.1.4
     25 * @version 2.1.5
    2626 */
    2727
    … …  
    3030}
    3131
    32 /**
    33  * Version of the Support Copilot module vendored in THIS plugin.
     32/*
     33 * Register THIS copy's version with the shared loader.
    3434 *
    35  * Bump together with Copilot::MODULE_VERSION on any module change. The loader
    36  * compares this across all vendored copies with version_compare(); the highest
    37  * wins. A variable, not a constant: constants collide across copies, variables
    38  * do not.
     35 * $bwdsc_this_version is the version of the module vendored in this plugin.
     36 * Bump it together with Copilot::MODULE_VERSION on any module change. The
     37 * loader compares it across all vendored copies with version_compare(); the
     38 * highest wins. It is a closure-local variable: not a constant (constants
     39 * collide across copies) and, since 2.1.5, not a global either (a global in a
     40 * verbatim-vendored file can carry no host prefix, which Plugin Check flags).
     41 * Each copy's entry runs its own closure, so every copy registers its own
     42 * version and nothing leaks into the next copy's require.
    3943 */
    40 $bwdsc_this_version = '2.1.4';
     44( static function () {
     45        $bwdsc_this_version = '2.1.5';
    4146
    42 // The loader is a tiny, FROZEN registry shared by all vendored copies. The
    43 // first copy to load defines it; later copies reuse it (class_exists guard
    44 // inside). Versioned behaviour lives in the class file it loads, never here.
    45 require_once __DIR__ . '/class-loader.php';
     47        // The loader is a tiny, FROZEN registry shared by all vendored copies. The
     48        // first copy to load defines it; later copies reuse it (class_exists guard
     49        // inside). Versioned behaviour lives in the class file it loads, never here.
     50        require_once __DIR__ . '/class-loader.php';
    4651
    47 \BlackWhiteDigital\SupportCopilot\Loader::register( $bwdsc_this_version, __DIR__ );
    48 
    49 // Don't leak into the next copy's require of its own entry file.
    50 unset( $bwdsc_this_version );
     52        \BlackWhiteDigital\SupportCopilot\Loader::register( $bwdsc_this_version, __DIR__ );
     53} )();
    5154
    5255// Negotiate once every host has had a chance to register. Hosts commonly
  • publisherkits-plugintidy/trunk/publisherkits-plugintidy.php

    r3708042 r3749369  
    55 * Plugin URI:        https://publisherkits.com/plugintidy
    66 * Description:       Audits your installed plugins by real-world usage and risk, records every plugin change, and makes removal safe.
    7  * Version:           0.17.0
     7 * Version:           0.18.0
    88 * Author:            Black and White Digital
    99 * Author URI:        https://publisherkits.com
    … …  
    2020 */
    2121defined( 'ABSPATH' ) || exit;
    22 define( 'PKTIDY_VERSION', '0.17.0' );
     22define( 'PKTIDY_VERSION', '0.18.0' );
    2323define( 'PKTIDY_PLUGIN_FILE', __FILE__ );
    2424define( 'PKTIDY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • publisherkits-plugintidy/trunk/readme.txt

    r3708091 r3749369  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 0.17.0
     7Stable tag: 0.18.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    9090= Does it send my data anywhere? =
    9191
    92 The free version contacts WordPress.org to ask whether your plugins are still maintained, sending only the plugin's folder name. Nothing else leaves your site.
     92The free version contacts WordPress.org to ask whether your plugins are still maintained, sending only the plugin's folder name. The audit sends nothing else. The Help assistant sends your question only when you use it and have agreed to its privacy notice - see External services below.
    9393
    9494= It says three plugins do the same job. Which should I remove? =
    … …  
    123123What is sent when you ask a question: your question and the recent chat; your site address and, where available, your licence installation identifier, used to authorise the request and apply fair-use limits; the PluginTidy version; the admin screen you are on; and a few non-sensitive diagnostics - how many plugins are active and inactive, whether an audit has run, whether scheduled tasks work, and whether WordPress.org could be reached. **The names of the plugins on your site, your audit results, your notes and your change log are never sent.** Choosing "Send to support" keeps the conversation as a ticket, with the email you give and your WordPress first and last name.
    124124
     125If an AI provider is connected to your site under Settings > Connectors (WordPress 7.0 or later), the assistant answers on that provider instead, through the WordPress AI Client and under that provider's own terms and privacy policy: your question, the recent chat, the matching PluginTidy documentation and the diagnostic counts above go to the provider you chose, and the support service is asked only for the documentation, never your question. If that local answer fails and your site is authorised to use the support service, the question and recent chat are sent to the support service as described above.
     126
    125127Terms of service: https://publisherkits.com/terms/
    126128Privacy policy: https://publisherkits.com/privacy-policy/
    127129
    128130== Changelog ==
     131
     132= 0.18.0 =
     133* Updated Freemius SDK to the latest version.
     134* New: the Help assistant now answers on an AI provider you have already connected to your site (Settings > Connectors, WordPress 7.0 or later), at your own cost, instead of our support service. It runs on the WordPress core AI Client and reads no licence, token or audit data, so it works the same in the free and Pro builds. Where no provider is connected, the support service answers as before.
     135* Changed: the assistant's privacy notice now says that if a local answer fails, the question and recent chat are sent to the support service instead.
     136* PluginTidy still uses no AI to audit your plugins. Every reading in the audit remains a measurement.
    129137
    130138= 0.17.0 =
    … …  
    141149== Upgrade Notice ==
    142150
     151= 0.18.0 =
     152The Help assistant can now answer on the AI provider already connected to your site. The audit itself still uses no AI.
     153
    143154= 0.17.0 =
    144155Adds the Support Copilot, a Help button that answers PluginTidy questions in place. Read-only, consent first, and it never sends your plugin list or audit results.
  • publisherkits-plugintidy/trunk/vendor/freemius/includes/class-freemius.php

    r3707999 r3749369  
    45414541
    45424542            if ( $this->is_user_in_admin() ) {
    4543                 if ( $this->is_registered() && fs_request_has( 'purchase_completed' ) ) {
    4544                     $this->_admin_notices->add_sticky(
    4545                         sprintf(
    4546                         /* translators: %s: License type (e.g. you have a professional license) */
    4547                             $this->get_text_inline( 'You have purchased a %s license.', 'you-have-x-license' ),
    4548                             fs_request_get( 'purchased_plan' )
    4549                         ) .
    4550                         sprintf(
    4551                             $this->get_text_inline(" The %s's %sdownload link%s, license key, and installation instructions have been sent to %s. If you can't find the email after 5 min, please check your spam box.", 'post-purchase-email-sent-message' ),
    4552                             $this->get_module_label( true ),
    4553                             ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? "products' " : '' ),
    4554                             ( FS_Plugin::is_valid_id( $this->get_bundle_id() ) ? 's' : '' ),
    4555                             sprintf(
    4556                                 '<strong>%s</strong>',
    4557                                 fs_request_get( 'purchase_email' )
    4558                             )
    4559                         ),
    4560                         'plan_purchased',
    4561                         $this->get_text_x_inline( 'Yee-haw', 'interjection expressing joy or exuberance', 'yee-haw' ) . '!'
    4562                     );
    4563                 }
    4564 
    45654543                if ( $this->is_addon() ) {
    45664544                    if ( ! $this->is_parent_plugin_installed() ) {
    … …  
    48644842                $code = empty( $result->error->code ) ? '' : " Code: {$result->error->code}";
    48654843
    4866                 $error_message .= "<b>{$result->error->message}{$code}</b>";
     4844                // We do not send HTML from exceptions from Freemius servers, but we escape it nonetheless.
     4845                $error_message .= "<b>" . esc_html( $result->error->message . $code ) . "</b>";
    48674846            } else {
    4868                 $error_message .= var_export( $result, true );
     4847                $error_message .= esc_html( var_export( $result, true ) );
    48694848            }
    48704849
    … …  
    67986777            }
    67996778
     6779            $escaped_email_address = esc_html( $email_address );
     6780
    68006781            $formatted_message_args = array(
    68016782                "<b>{$this->get_plugin_name()}</b>",
    6802                 "<b>{$email_address}</b>",
     6783                "<b>{$escaped_email_address}</b>",
    68036784            );
    68046785
    … …  
    68456826
    68466827                $formatted_message_args[] = ( ! empty( $support_email_address ) ) ?
    6847                     ( "<b>{$support_email_address}</b>" ) :
     6828                    ( "<b>" . esc_html( $support_email_address ) . "</b>" ) :
    68486829                    $this->get_text_x_inline(
    68496830                        "the product's support email address",
    … …  
    1756517546            }
    1756617547
     17548            // Only site administrators who can activate plugins may opt-in.
     17549            if ( ! $this->is_user_admin() ) {
     17550                return;
     17551            }
     17552
    1756717553            $has_pending_activation_confirmation_param = fs_request_has( 'pending_activation' );
    1756817554
    … …  
    1814418130                    $this->_admin_notices->add(
    1814518131                        sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
    18146                         $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $result->error->message . '</b>',
     18132                        $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . esc_html( $result->error->message ) . '</b>',
    1814718133                        $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
    1814818134                        'error'
    … …  
    1826418250                    $this->_admin_notices->add(
    1826518251                        sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
    18266                         $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $error_message . '</b>',
     18252                        $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . esc_html( $error_message ) . '</b>',
    1826718253                        $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
    1826818254                        'error'
    … …  
    1844118427                $this->_admin_notices->add(
    1844218428                    sprintf( $this->get_text_inline( 'Couldn\'t activate %s.', 'could-not-activate-x' ), $this->get_plugin_name() ) . ' ' .
    18443                     $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . $parent_install->error->message . '</b>',
     18429                    $this->get_text_inline( 'Please contact us with the following message:', 'contact-us-with-error-message' ) . ' ' . '<b>' . esc_html( $parent_install->error->message ) . '</b>',
    1844418430                    $this->get_text_x_inline( 'Oops', 'exclamation', 'oops' ) . '...',
    1844518431                    'error'
    … …  
    2115921145                            // Authentication params are broken.
    2116021146                            $this->_admin_notices->add(
    21161                                 $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '<br> ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . var_export( $result->error, true ),
     21147                                $this->get_text_inline( 'It seems like one of the authentication parameters is wrong. Update your Public Key, Secret Key & User ID, and try again.', 'wrong-authentication-param-message' ) . '<br> ' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . esc_html( var_export( $result->error, true ) ),
    2116221148                                '',
    2116321149                                'error'
    … …  
    2148021466                            sprintf(
    2148121467                                '<a href="%s">%s</a>',
    21482                                 $this->contact_url( 'bug' ),
     21468                                esc_url( $this->contact_url( 'bug' ) ),
    2148321469                                $this->get_text_inline( 'Please contact us here', 'contact-us-here' )
    2148421470                            ),
    … …  
    2157321559                '<div class="fs-api-request-error-details" style="display: none">' .
    2157421560                    '<strong>' . $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . '</strong><br>' .
    21575                     $result->error->message .
     21561                    esc_html( $result->error->message ) .
    2157621562                '</div>';
    2157721563
    … …  
    2167821664                        $this->get_text_inline( 'It looks like the license could not be activated.', 'license-activation-failed-message' ),
    2167921665                        ( is_object( $license ) && isset( $license->error ) ?
    21680                             $license->error->message :
     21666                            esc_html( $license->error->message ) :
    2168121667                            sprintf( '%s<br><code>%s</code>',
    2168221668                                $this->get_text_inline( 'Error received from the server:', 'server-error-message' ),
    21683                                 var_export( $license, true )
     21669                                esc_html( var_export( $license, true ) )
    2168421670                            )
    2168521671                        )
    … …  
    2176921755                $this->_admin_notices->add(
    2177021756                    $this->get_text_inline( 'It looks like the license deactivation failed.', 'license-deactivation-failed-message' ) . '<br> ' .
    21771                     $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . var_export( $license->error, true ),
     21757                    $this->get_text_inline( 'Error received from the server:', 'server-error-message' ) . ' ' . esc_html( var_export( $license->error, true ) ),
    2177221758                    $hmm_text,
    2177321759                    'error'
    … …  
    2241822404                            sprintf(
    2241922405                                '<a href="%s" target="_blank" rel="noopener">%s</a>',
    22420                                 $this->get_account_url( 'download_latest' ),
     22406                                esc_url( $this->get_account_url( 'download_latest' ) ),
    2242122407                                sprintf(
    2242222408                                /* translators: %s: plan name (e.g. latest "Professional" version) */
    … …  
    2283622822                $this->_admin_notices->add( sprintf(
    2283722823                    $this->get_text_inline( 'Verification mail was just sent to %s. If you can\'t find it after 5 min, please check your spam box.', 'verification-email-sent-message' ),
    22838                     sprintf( '<a href="mailto:%1$s">%2$s</a>', esc_url( $this->_user->email ), $this->_user->email )
     22824                    sprintf( '<a href="mailto:%1$s">%2$s</a>', esc_url( $this->_user->email ), esc_html( $this->_user->email ) )
    2283922825                ) );
    2284022826            } else {
    … …  
    2316023146                            if ( $this->init_change_owner( $candidate_email, $transfer_type ) ) {
    2316123147                                if ( 'transfer' === $transfer_type ) {
    23162                                     $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.', 'change-owner-request-sent-x-transfer' ), '<b>' . $this->_user->email . '</b>' ) );
     23148                                    $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. The email owner must confirm the update within the next 4 hours.', 'change-owner-request-sent-x-transfer' ), '<b>' . esc_html( $this->_user->email ) . '</b>' ) );
    2316323149                                } else {
    23164                                     $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '<b>' . $this->_user->email . '</b>' ) );
     23150                                    $this->_admin_notices->add( sprintf( $this->get_text_inline( 'A confirmation email was just sent to %s. You must confirm the update within the next 4 hours. If you cannot find the email, please check your spam folder.', 'change-owner-request-sent-x' ), '<b>' . esc_html( $this->_user->email ) . '</b>' ) );
    2316523151                                }
    2316623152                            }
    … …  
    2317423160                            }
    2317523161
    23176                             $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '<b>' . $candidate_email . '</b>' ) );
     23162                            $this->_admin_notices->add( sprintf( $this->get_text_inline( 'Thanks for confirming the ownership change. An email was just sent to %s for final approval.', 'change-owner-request_owner-confirmed' ), '<b>' . esc_html( $candidate_email ) . '</b>' ) );
    2317723163                            break;
    2317823164                        case 'candidate_confirmed':
    … …  
    2318023166                            if ( $this->complete_change_owner() ) {
    2318123167                                $this->_admin_notices->add_sticky(
    23182                                     sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '<b>' . $this->_user->email . '</b>' ),
     23168                                    sprintf( $this->get_text_inline( '%s is the new owner of the account.', 'change-owner-request_candidate-confirmed' ), '<b>' . esc_html( $this->_user->email ) . '</b>' ),
    2318323169                                    'ownership_changed',
    2318423170                                    $this->get_text_x_inline( 'Congrats', 'as congratulations', 'congrats' ) . '!'
  • publisherkits-plugintidy/trunk/vendor/freemius/includes/managers/class-fs-clone-manager.php

    r3707999 r3749369  
    12241224
    12251225                foreach ( $product_titles as $product_title ) {
    1226                     $products_list .= sprintf( '<li>%s</li>', $product_title );
     1226                    $products_list .= sprintf( '<li>%s</li>', esc_html( $product_title ) );
    12271227                }
    12281228
    … …  
    12321232                    $sites_list .= sprintf(
    12331233                        '<li><a href="%s" target="_blank">%s</a></li>',
    1234                         $site_url,
    1235                         fs_strip_url_protocol( $site_url )
     1234                        esc_url( $site_url ),
     1235                        esc_html( fs_strip_url_protocol( $site_url ) )
    12361236                    );
    12371237                }
    … …  
    12431243                sprintf(
    12441244                    '<a href="%s" target="_blank">%s</a>',
    1245                     $site_urls[0],
    1246                     fs_strip_url_protocol( $site_urls[0] )
     1245                    esc_url( $site_urls[0] ),
     1246                    esc_html( fs_strip_url_protocol( $site_urls[0] ) )
    12471247                ) :
    12481248                fs_text_inline( 'the above-mentioned sites', 'above-mentioned-sites' )) . '</b>';
    … …  
    12501250            $current_site_link = sprintf(
    12511251                '<b><a href="%s" target="_blank">%s</a></b>',
    1252                 $current_url,
    1253                 fs_strip_url_protocol( $current_url )
     1252                esc_url( $current_url ),
     1253                esc_html( fs_strip_url_protocol( $current_url ) )
    12541254            );
    12551255
    … …  
    13241324                // %1$s
    13251325                ( 1 === $total_products ?
    1326                     sprintf( '<b>%s</b>', $product_titles[0] ) :
     1326                    sprintf( '<b>%s</b>', esc_html( $product_titles[0] ) ) :
    13271327                    ( 1 === $total_sites ?
    13281328                        sprintf( '<div>%s</div>', $products_list ) :
  • publisherkits-plugintidy/trunk/vendor/freemius/start.php

    r3707999 r3749369  
    1616         * @var string
    1717         */
    18         // BWD LOCAL PATCH (2026-08-12): guarded the unguarded count( $this->_plans )
    19         // calls in includes/class-freemius.php against a non-array (bool) value, which
    20         // fataled on PHP 8 during the multisite network-admin opt-in (_store_plans).
    21         // Freemius 2.13.4 / master carry the bug unfixed. Version nudged to 2.13.5 so
    22         // this fixed copy wins the newest-wins loader over unpatched 2.13.4 copies on
    23         // the same site. See vendor/freemius/BWD-PATCH-NOTES.md. Revert to the stock
    24         // version string when Freemius ships an official fix.
    2518        $this_sdk_version = '2.13.5';
    2619
  • publisherkits-plugintidy/trunk/vendor/freemius/templates/add-ons.php

    r3707999 r3749369  
    123123                                                $open_addon = ( $open_addon || ( $open_addon_slug === $addon->slug ) );
    124124
    125                                                 $price     = 0;
    126                                                 $has_trial = false;
     125                                                $price         = 0;
     126                        $min_price     = 999999;
     127                                                $has_trial     = false;
    127128                                                $has_free_plan = false;
    128129                                                $has_paid_plan = false;
    … …  
    142143                                                                        }
    143144
    144 
    145145                                                                        $has_paid_plan = true;
    146146                                                                        $has_trial     = $has_trial || ( is_numeric( $plan->trial_period ) && ( $plan->trial_period > 0 ) );
    147147
    148                                                                         $min_price = 999999;
    149148                                                                        foreach ( $plan->pricing as $pricing ) {
    150149                                        $pricing = new FS_Pricing( $pricing );
    … …  
    164163                                                                                } else if ( $pricing->has_monthly() ) {
    165164                                                                                        $min_price = min( $min_price, 12 * $pricing->monthly_price );
    166                                                                                 }
     165                                                                                } else if ( $pricing->has_lifetime() ) {
     166                                            $min_price = min( $min_price, $pricing->lifetime_price );
     167                                        }
    167168                                                                        }
    168169
    … …  
    170171                                                                                $price = $min_price;
    171172                                                                        }
    172 
    173173                                                                }
    174174                                                        }
  • publisherkits-plugintidy/trunk/vendor/freemius/templates/pricing.php

    r3707999 r3749369  
    2525
    2626        $context_params = array(
     27        // Plugin information
    2728                'plugin_id'         => $fs->get_id(),
    2829                'plugin_public_key' => $fs->get_public_key(),
    2930                'plugin_version'    => $fs->get_plugin_version(),
     31
     32        // React pricing app context
     33        'contact_url'            => $fs->contact_url(),
     34        'is_production'          => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ),
     35        'menu_slug'              => $fs->get_menu_slug(),
     36        'mode'                   => 'dashboard',
     37        'fs_wp_endpoint_url'     => WP_FS__ADDRESS,
     38        'request_handler_url'    => admin_url(
     39            'admin-ajax.php?' . http_build_query( array(
     40                'module_id' => $fs->get_id(),
     41                'action'    => $fs->get_ajax_action( 'pricing_ajax_action' ),
     42                'security'  => $fs->get_ajax_security( 'pricing_ajax_action' )
     43            ) )
     44        ),
     45        'selector'               => '#fs_pricing_wrapper',
     46        'unique_affix'           => $fs->get_unique_affix(),
     47        'show_annual_in_monthly' => $fs->apply_filters( 'pricing/show_annual_in_monthly', true ),
     48        'license'                => $fs->has_active_valid_license() ? $fs->_get_license() : null,
     49        'plugin_icon'            => $fs->get_local_icon_url(),
     50        'disable_single_package' => $fs->apply_filters( 'pricing/disable_single_package', false ),
    3051        );
    3152
    … …  
    5576        }
    5677
    57         $query_params = array_merge( $context_params, $_GET, array(
     78        $query_params = array_merge( $_GET, array(
    5879                'next'             => $fs->_get_sync_license_url( false, false ),
    59                 'plugin_version'   => $fs->get_plugin_version(),
    6080                // Billing cycle.
    6181                'billing_cycle'    => fs_request_get( 'billing_cycle', WP_FS__PERIOD_ANNUALLY ),
    … …  
    6484        'discounts_model'  => $fs->apply_filters( 'pricing/discounts_model', 'absolute' ),
    6585        ) );
     86
     87        $has_tabs = $fs->_add_tabs_before_content();
     88
     89        if ( $has_tabs ) {
     90                $query_params['tabs'] = 'true';
     91        }
     92
     93    $pricing_config = array_merge( $query_params, $context_params );
    6694
    6795    $pricing_js_url = fs_asset_url( $fs->get_pricing_js_path() );
    … …  
    73101        wp_enqueue_style( 'freemius-pricing', fs_asset_url( $pricing_css_path ) );
    74102    }
    75 
    76         $has_tabs = $fs->_add_tabs_before_content();
    77 
    78         if ( $has_tabs ) {
    79                 $query_params['tabs'] = 'true';
    80         }
    81103?>
    82104        <div id="fs_pricing" class="wrap fs-section fs-full-size-wrapper">
    83105        <div id="fs_pricing_wrapper" data-public-url="<?php echo trailingslashit( dirname( $pricing_js_url ) ) ?>"></div>
    84         <?php
    85         $pricing_config = array_merge( array(
    86             'contact_url'            => $fs->contact_url(),
    87             'is_production'          => ( defined( 'WP_FS__IS_PRODUCTION_MODE' ) ? WP_FS__IS_PRODUCTION_MODE : null ),
    88             'menu_slug'              => $fs->get_menu_slug(),
    89             'mode'                   => 'dashboard',
    90             'fs_wp_endpoint_url'     => WP_FS__ADDRESS,
    91             'request_handler_url'    => admin_url(
    92                 'admin-ajax.php?' . http_build_query( array(
    93                     'module_id' => $fs->get_id(),
    94                     'action'    => $fs->get_ajax_action( 'pricing_ajax_action' ),
    95                     'security'  => $fs->get_ajax_security( 'pricing_ajax_action' )
    96                 ) )
    97             ),
    98             'selector'               => '#fs_pricing_wrapper',
    99             'unique_affix'           => $fs->get_unique_affix(),
    100             'show_annual_in_monthly' => $fs->apply_filters( 'pricing/show_annual_in_monthly', true ),
    101             'license'                => $fs->has_active_valid_license() ? $fs->_get_license() : null,
    102             'plugin_icon'            => $fs->get_local_icon_url(),
    103             'disable_single_package' => $fs->apply_filters( 'pricing/disable_single_package', false ),
    104         ), $query_params );
    105 
    106         wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' );
    107         ?>
     106        <?php wp_add_inline_script( 'freemius-pricing', 'Freemius.pricing.new( ' . json_encode( $pricing_config ) . ' )' ); ?>
    108107        </div>
    109108<?php
Note: See TracChangeset for help on using the changeset viewer.