Plugin Directory

Changeset 3286707


Ignore:
Timestamp:
05/03/2025 11:00:37 AM (11 months ago)
Author:
th23
Message:

v3.1.3 release

  • fix: upgrade to th23 Admin class 1.7.0, removing some inconsistencies regarding own updater incl removal of option to chose update source and ensure proper escaping of all output
Location:
th23-contact/tags/3.1.3
Files:
14 copied

Legend:

Unmodified
Added
Removed
  • th23-contact/tags/3.1.3/inc/th23-admin-class.css

    r3253885 r3286707  
    100100  margin-left: 64px;
    101101}
     102@media screen and (min-width: 782px) {
     103  div.th23-admin-about .floating-right {
     104    float: right;
     105  }
     106  div.th23-admin-about .floating-right .separator-left {
     107    display: none;
     108  }
     109}
     110div.th23-admin-about .update-url {
     111  cursor: pointer;
     112}
    102113div.th23-admin-about a {
    103114  text-decoration: none;
  • th23-contact/tags/3.1.3/inc/th23-admin-class.php

    r3284653 r3286707  
    11<?php
    22/*
    3 th23 Admin (adjusted for WP.org)
     3th23 Admin
    44Basic admin functionality
    5 Version: 1.6.2-wp
     5Version: 1.7.0
    66
    77Coded 2024-2025 by Thorsten Hartmann (th23)
     
    1616}
    1717
    18 if(!class_exists('th23_admin_v162wp')) {
    19     class th23_admin_v162wp {
     18if(!class_exists('th23_admin_v170')) {
     19    class th23_admin_v170 {
    2020
    2121        private $parent;
     
    5151            add_filter('plugin_row_meta', array(&$this, 'contact_link'), 10, 2);
    5252
     53            // Handle plugin repository for plugin info and update for non-WP.org hosted plugin
     54            // note: "update_url" and main filter for site_transient... have to be set by the plugin admin script
     55            if(!empty($this->parent->plugin['update_url'])) {
     56                // replace plugin row info / links to ensure detailed info from alternative source is available
     57                // note: hook early to allow other modifications based on this eg adding support link and requirement notices
     58                add_filter('plugin_row_meta', array(&$this, 'update_details'), 1, 4);
     59                add_filter('plugins_api', array(&$this, 'update_info'), 20, 3);
     60                add_action('upgrader_process_complete', array(&$this, 'update_cache'), 10, 2);
     61            }
     62
    5363            // Add settings page and JS/ CSS
    5464            if(!empty($this->parent->plugin['settings'])) {
     
    105115            }
    106116            return $links;
     117        }
     118
     119        // Set plugin repository for plugin info and update
     120        // Replace plugin row info / links to ensure detailed info from alternative source is available
     121        function update_details($links, $file, $data, $status) {
     122            if($this->parent->plugin['basename'] == $file) {
     123                $links = array();
     124                // replicate default version and author
     125                if(!empty($data['Version'])) {
     126                    /* translators: parses in plugin version number */
     127                    $links[] = sprintf($this->__('Version %s'), $data['Version']);
     128                }
     129                if(!empty($data['Author'])) {
     130                    $author = $data['Author'];
     131                    if(!empty($data['AuthorURI'])) {
     132                        $author = '<a href="' . $data['AuthorURI'] . '">' . $data['Author'] . '</a>';
     133                    }
     134                    /* translators: parses in plugin author name / link */
     135                    $links[] = sprintf($this->__('By %s'), $author);
     136                }
     137                // add detailed info independed from source - original code only focuses on WP.org repository
     138                if(current_user_can('install_plugins')) {
     139                    $links[] = '<a href="' . esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . $this->parent->plugin['slug'] . '&TB_iframe=true&width=600&height=550')) . '" class="thickbox open-plugin-details-modal" data-title="' . esc_attr($data['Name']) . '">' . $this->__('View details') . '</a>';
     140                }
     141                elseif(!empty($data['PluginURI'])) {
     142                    $links[] = '<a href="' . esc_url($data['PluginURI']) . '">' . $this->__('Visit plugin site') . '</a>';
     143                }
     144            }
     145            return $links;
     146        }
     147
     148        // Get plugin (update) information
     149        function update_info($res, $action, $args) {
     150            // no action, if no plugin information request for this plugin
     151            if('plugin_information' !== $action || $this->parent->plugin['slug'] !== $args->slug) {
     152                return $res;
     153            }
     154            // note: returning $res defaults to WP.org repo, if exists and alternative is unreachable - nevertheless return error message, as versions might differ
     155            if(empty($remote = $this->update_request()) || !is_array($remote)) {
     156                $res = new stdClass();
     157                $res->slug = $this->parent->plugin['slug'];
     158                $res->name = $this->parent->plugin['data']['Name'];
     159                /* translators: parses in plugin information source url */
     160                $res->sections = array('other_notes' => '<div class="notice notice-error"><p>' . sprintf($this->__('Failed to load plugin information from %s'), '<code>' . $this->parent->plugin['update_url'] . '</code>') . '</p></div>');
     161                return $res;
     162            }
     163            // convert top array from response to class structure
     164            $res = new stdClass();
     165            foreach($remote as $id => $content) {
     166                $res->$id = $content;
     167            }
     168            return $res;
     169        }
     170
     171        // Retrieve plugin (update) information from cache or download from repository
     172        function update_request() {
     173            $json = get_transient($this->parent->plugin['slug'] . '_update_cache');
     174            if(false === $json) {
     175                $remote = wp_remote_get($this->parent->plugin['update_url'], array('timeout' => 10, 'headers' => array('Accept' => 'application/json')));
     176                if(is_wp_error($remote) || 200 !== wp_remote_retrieve_response_code($remote) || empty($json = wp_remote_retrieve_body($remote))) {
     177                    return false;
     178                }
     179                $json = json_decode($json, true);
     180                set_transient($this->parent->plugin['slug'] . '_update_cache', $json, DAY_IN_SECONDS);
     181            }
     182            return $json;
     183        }
     184
     185        // Insert plugin (update) information into data passed to wp updater
     186        function update_download($transient) {
     187            // plugin has own update url and participates in update checks
     188            if(empty($this->parent->plugin['update_url']) || empty($transient->checked[$this->parent->plugin['basename']])) {
     189                return $transient;
     190            }
     191            // ignore responses from default Wordpress repository
     192            unset($transient->response[$this->parent->plugin['basename']]);
     193            unset($transient->no_update[$this->parent->plugin['basename']]);
     194            // not yet checked during current page load - check if update available
     195            if(empty($this->data['update_cache'])) {
     196                $this->data['update_cache'] = array();
     197                // update server contacted and valid response received
     198                if(!empty($plugin = $this->update_request()) && is_array($plugin)) {
     199                    $res = new stdClass();
     200                    $res->id = $this->parent->plugin['slug'];
     201                    $res->slug = $this->parent->plugin['slug'];
     202                    $res->plugin = $this->parent->plugin['basename'];
     203                    $res->url = $plugin['homepage'];
     204                    $res->new_version = $plugin['version'];
     205                    $res->package = $plugin['download_link'];
     206                    $res->requires_php = $plugin['requires_php'];
     207                    $res->requires = $plugin['requires'];
     208                    $res->tested = $plugin['tested'];
     209                    $res->icons = (!empty($plugin['icons'])) ? $plugin['icons'] : array();
     210                    $res->banners = (!empty($plugin['banners'])) ? $plugin['banners'] : array();
     211                    $this->data['update_cache']['plugin_info'] = $res;
     212                    // newer version available and required wp and php versions are met
     213                    if(version_compare($this->parent->plugin['version'], $plugin['version'], '<') && version_compare($plugin['requires'], get_bloginfo('version'), '<=') && version_compare($plugin['requires_php'], PHP_VERSION, '<')) {
     214                        $this->data['update_cache']['status'] = 'update_available';
     215                    }
     216                    else {
     217                        $this->data['update_cache']['status'] = 'no_update';
     218                    }
     219                }
     220            }
     221            // (re-)add plugin to transient based on update availability (see above or cached)
     222            if(!empty($this->data['update_cache']['plugin_info'])) {
     223                if('update_available' == $this->data['update_cache']['status']) {
     224                    $transient->response[$this->parent->plugin['basename']] = $this->data['update_cache']['plugin_info'];
     225                }
     226                else {
     227                    $transient->no_update[$this->parent->plugin['basename']] = $this->data['update_cache']['plugin_info'];
     228                }
     229            }
     230            return $transient;
     231        }
     232
     233        // Clear plugin (update) information from cache upon installation of new version
     234        function update_cache($upgrader, $options){
     235            if('update' === $options['action'] && 'plugin' === $options['type'] && in_array($this->parent->plugin['basename'], $options['plugins'])) {
     236                delete_transient($this->parent->plugin['slug'] . '_update_cache');
     237            }
    107238        }
    108239
     
    184315        // update user preference for screen options via AJAX
    185316        function set_screen_options() {
    186             if(!empty($_POST['nonce']) && wp_verify_nonce($_POST['nonce'], 'th23-admin-screen-options-nonce')) {
     317            if(!empty($_POST['nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'th23-admin-screen-options-nonce') && !empty($_POST['plugin'])) {
    187318                $screen_options = $this->get_screen_options();
    188319                $new = array();
     
    270401                            // html input
    271402                            elseif($html_input) {
    272                                 if(isset($_POST['input_' . $option . '_' . $element . '_' . $sub_option])) {
    273                                     // if only single value allowed, only take first element from value array for validation
    274                                     if($sub_type == 'single' && is_array($_POST['input_' . $option . '_' . $element . '_' . $sub_option])) {
    275                                         $value = sanitize_text_field(wp_unslash(reset($_POST['input_' . $option . '_' . $element . '_' . $sub_option])));
     403                                $html_input_name = 'input_' . $option . '_' . $element . '_' . $sub_option;
     404                                if(isset($_POST[$html_input_name])) {
     405                                    // for textarea preserve linebreaks
     406                                    if(!empty($sub_option_details['element']) && 'textarea' == $sub_option_details['element']) {
     407                                        $value = sanitize_textarea_field(wp_unslash($_POST[$html_input_name]));
    276408                                    }
    277409                                    else {
    278                                         $value = sanitize_text_field(wp_unslash($_POST['input_' . $option . '_' . $element . '_' . $sub_option]));
     410                                        $value = (is_array($_POST[$html_input_name])) ? array_map('sanitize_text_field', wp_unslash($_POST[$html_input_name])) : sanitize_text_field(wp_unslash($_POST[$html_input_name]));
     411                                        // if only single value allowed, only take first element from value array for validation
     412                                        if($type == 'single' && is_array($value)) {
     413                                            $value = reset($value);
     414                                        }
    279415                                    }
    280416                                }
     
    320456                    if($html_input) {
    321457                        if(isset($_POST['input_' . $option])) {
    322                             // if only single value allowed, only take first element from value array for validation
    323                             if($type == 'single' && is_array($_POST['input_' . $option])) {
    324                                 $value = sanitize_text_field(wp_unslash(reset($_POST['input_' . $option])));
    325                             }
    326                             elseif($type == 'multiple' && is_array($_POST['input_' . $option])) {
    327                                 $value = array();
    328                                 foreach($_POST['input_' . $option] as $key => $val) {
    329                                     $value[$key] = sanitize_text_field(wp_unslash($val));
     458                            // for textarea preserve linebreaks
     459                            if(!empty($option_details['element']) && 'textarea' == $option_details['element']) {
     460                                $value = sanitize_textarea_field(wp_unslash($_POST['input_' . $option]));
     461                            }
     462                            else {
     463                                $value = (is_array($_POST['input_' . $option])) ? array_map('sanitize_text_field', wp_unslash($_POST['input_' . $option])) : sanitize_text_field(wp_unslash($_POST['input_' . $option]));
     464                                // if only single value allowed, only take first element from value array for validation
     465                                if($type == 'single' && is_array($value)) {
     466                                    $value = reset($value);
    330467                                }
    331                             }
    332                             // for textarea preserve linebreaks
    333                             elseif(!empty($option_details['element']) && 'textarea' == $option_details['element']) {
    334                                 $value = sanitize_textarea_field(wp_unslash($_POST['input_' . $option]));
    335                             }
    336                             else {
    337                                 $value = sanitize_text_field(wp_unslash($_POST['input_' . $option]));
    338468                            }
    339469                        }
     
    432562                    }
    433563                    elseif(!empty($value)) {
    434                         $form_classes[] = 'th23-admin-screen-option-' . $option . '-' . str_replace(' ', '_', $value);
     564                        $form_classes[] = 'th23-admin-screen-option-' . $option . '-' . esc_attr(str_replace(' ', '_', $value));
    435565                    }
    436566                }
     
    727857                echo ' | <a href="' . esc_url($this->parent->plugin['data']['PluginURI']) . '">' . esc_html($this->__('Visit plugin site')) . '</a>';
    728858            }
     859            if(!empty($this->parent->plugin['update_url'])) {
     860                $update_parsed = parse_url($this->parent->plugin['update_url']);
     861                /* translators: parses in host / domain part of repository url for non-WP.org hosted plugin */
     862                echo '<span class="floating-right"><span class="separator-left"> | </span>' . sprintf(esc_html($this->__('Updated via %s')), '<span class="update-url" title="' . esc_attr($this->parent->plugin['update_url']) . '">' . esc_html($update_parsed['host']) . '</span>') . '</span>';
     863            }
    729864            echo '</p></div>';
    730865
  • th23-contact/tags/3.1.3/readme.txt

    r3284653 r3286707  
    44Tags: contact, form, block, shortcode
    55Requires at least: 4.2
    6 Tested up to: 6.8
    7 Stable tag: 3.1.2
     6Tested up to: 6.8.1
     7Stable tag: 3.1.3
    88Requires PHP: 8.0
    99License: GPL-3.0
     
    8181== Changelog ==
    8282
    83 = v3.1.2 =
     83= v3.1.3 =
    8484
    85 * fix: ensure WP specific th23 Admin class is loaded
     85* fix: upgrade to th23 Admin class 1.7.0, removing some inconsistencies regarding own updater incl removal of option to chose update source and ensure proper escaping of all output
    8686
    8787= v3.1.1 =
     
    128128== Upgrade Notice ==
    129129
    130 = v3.1.2 =
     130= v3.1.3 =
    131131
    132132* n/a
  • th23-contact/tags/3.1.3/th23-contact-admin.php

    r3284653 r3286707  
    2222            'permission' => 'manage_options',
    2323        );
    24         // icon: "square" 48 x 48px (footer) / "horizontal" 36px height (header, width irrelevant) / both (resized if larger)
     24        // icons "square" 48 x 48px (footer) and "horizontal" 36px height (header, width irrelevant) / both (resized if larger)
    2525        $this->plugin['icon'] = array('square' => 'img/icon-square.png', 'horizontal' => 'img/icon-horizontal.png');
    2626        $this->plugin['support_url'] = 'https://github.com/th23x/th23-contact/issues';
    2727        $this->plugin['requirement_notices'] = array();
    28         // update: alternative update source
    29         $this->plugin['update_url'] = 'https://github.com/th23x/th23-contact/releases/latest/download/update.json';
    3028
    3129        // Load and setup required th23 Admin class
    3230        if(file_exists($this->plugin['dir_path'] . '/inc/th23-admin-class.php')) {
    3331            require($this->plugin['dir_path'] . '/inc/th23-admin-class.php');
    34             $this->admin = new th23_admin_v162wp($this);
     32            $this->admin = new th23_admin_v170($this);
    3533        }
    3634        if(!empty($this->admin)) {
    3735            add_action('init', array(&$this, 'setup_admin_class'));
     36            // alternative update source for non-WP.org hosted plugin
     37            // important: remove following two lines for WP.org-hosted plugin
     38            // ... removed
    3839        }
    3940        else {
     
    7475        // note: need to populate $this->i18n earliest at init hook to get user locale
    7576        $this->i18n = array(
    76             'Plugin' => __('Plugin', 'th23-specials'),
    77             'Settings' => __('Settings', 'th23-contact'),
    78             /* translators: parses in plugin version number */
    79             'Version %s' => __('Version %s', 'th23-contact'),
     77            // reviewer: to keep consistency some admin language strings are used in sync with core
     78            'Settings' => __('Settings'),
     79            /* translators: parses in version number */
     80            'Version %s' => __('Version %s'),
    8081            /* translators: parses in plugin name */
    8182            'Copy from %s' => __('Copy from %s', 'th23-contact'),
    82             'Support' => __('Support', 'th23-contact'),
    83             'Done' => __('Done', 'th23-contact'),
    84             'Settings saved.' => __('Settings saved.', 'th23-contact'),
    85             '+' => __('+', 'th23-contact'),
    86             '-' => __('-', 'th23-contact'),
    87             'Save Changes' => __('Save Changes', 'th23-contact'),
    88             /* translators: parses in plugin author name / link */
    89             'By %s' => __('By %s', 'th23-contact'),
    90             'View details' => __('View details', 'th23-specials'),
    91             'Visit plugin site' => __('Visit plugin site', 'th23-contact'),
    92             'Error' => __('Error', 'th23-contact'),
     83            'Support' => __('Support'),
     84            'Done' => __('Done'),
     85            'Settings saved.' => __('Settings saved.'),
     86            '+' => __('+'),
     87            '-' => __('-'),
     88            'Save Changes' => __('Save Changes'),
     89            /* translators: parses in author */
     90            'By %s' => __('By %s'),
     91            'View details' => __('View details'),
     92            'Visit plugin site' => __('Visit plugin site'),
     93            'Error' => __('Error'),
    9394            /* translators: 1: option name, 2: opening a tag of link to support/ plugin page, 3: closing a tag of link */
    9495            'Invalid combination of input field and default value for "%1$s" - please %2$scontact the plugin author%3$s' => __('Invalid combination of input field and default value for "%1$s" - please %2$scontact the plugin author%3$s', 'th23-contact'),
    95             'Updates' => __('Updates', 'th23-contact'),
    96             'If disabled or unreachable, updates will use default WordPress repository' => __('If disabled or unreachable, updates will use default WordPress repository', 'th23-contact'),
    97             /* translators: parses in repository url */
    98             'Update from %s' => __('Update from %s', 'th23-contact'),
     96            /* translators: parses in repository url for non-WP.org hosted plugin */
     97            'Updated via %s' => __('Updated via %s', 'th23-contact'),
     98            /* translators: parses in plugin information source url */
     99            'Failed to load plugin information from %s' => __('Failed to load plugin information from %s', 'th23-contact'),
    99100        );
    100101
  • th23-contact/tags/3.1.3/th23-contact.php

    r3284653 r3286707  
    1313License URI: https://github.com/th23x/th23-contact/blob/main/LICENSE
    1414
    15 Version: 3.1.2
     15Version: 3.1.3
    1616
    1717Requires at least: 4.2
    18 Tested up to: 6.8
     18Tested up to: 6.8.1
    1919Requires PHP: 8.0
    2020
     
    4242        $this->plugin['basename'] = plugin_basename($this->plugin['file']);
    4343        $this->plugin['dir_url'] = plugin_dir_url($this->plugin['file']);
    44         $this->plugin['version'] = '3.1.2';
     44        $this->plugin['version'] = '3.1.3';
    4545
    4646        // Load plugin options
Note: See TracChangeset for help on using the changeset viewer.