From 4e6423ffd2adaeed190799c498f50d35e8c0bb0b Mon Sep 17 00:00:00 2001 From: Steve <110924473+stevennoad@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:15:30 +0100 Subject: [PATCH] Auto update (#1) * Enables plugin update from within the admin area, * V2.1.0 allows updating the plugin from wordpress --- apple-maps.php | 289 ++-- apple-maps.css => assets/css/map.css | 0 places.js => assets/js/places.js | 35 +- apple-maps.js => assets/js/standard.js | 0 .../Puc/v5/PucFactory.php | 10 + .../Puc/v5p4/Autoloader.php | 86 + .../Puc/v5p4/DebugBar/Extension.php | 199 +++ .../Puc/v5p4/DebugBar/Panel.php | 178 ++ .../Puc/v5p4/DebugBar/PluginExtension.php | 40 + .../Puc/v5p4/DebugBar/PluginPanel.php | 41 + .../Puc/v5p4/DebugBar/ThemePanel.php | 25 + .../Puc/v5p4/InstalledPackage.php | 105 ++ .../Puc/v5p4/Metadata.php | 162 ++ .../Puc/v5p4/OAuthSignature.php | 102 ++ .../Puc/v5p4/Plugin/Package.php | 188 ++ .../Puc/v5p4/Plugin/PluginInfo.php | 136 ++ .../Puc/v5p4/Plugin/Ui.php | 294 ++++ .../Puc/v5p4/Plugin/Update.php | 116 ++ .../Puc/v5p4/Plugin/UpdateChecker.php | 425 +++++ .../Puc/v5p4/PucFactory.php | 362 ++++ .../Puc/v5p4/Scheduler.php | 300 ++++ .../Puc/v5p4/StateStore.php | 214 +++ .../Puc/v5p4/Theme/Package.php | 69 + .../Puc/v5p4/Theme/Update.php | 88 + .../Puc/v5p4/Theme/UpdateChecker.php | 159 ++ .../plugin-update-checker/Puc/v5p4/Update.php | 38 + .../Puc/v5p4/UpdateChecker.php | 1029 +++++++++++ .../Puc/v5p4/UpgraderStatus.php | 200 +++ .../plugin-update-checker/Puc/v5p4/Utils.php | 70 + .../Puc/v5p4/Vcs/Api.php | 379 ++++ .../Puc/v5p4/Vcs/BaseChecker.php | 29 + .../Puc/v5p4/Vcs/BitBucketApi.php | 272 +++ .../Puc/v5p4/Vcs/GitHubApi.php | 467 +++++ .../Puc/v5p4/Vcs/GitLabApi.php | 414 +++++ .../Puc/v5p4/Vcs/PluginUpdateChecker.php | 275 +++ .../Puc/v5p4/Vcs/Reference.php | 51 + .../Puc/v5p4/Vcs/ReleaseAssetSupport.php | 83 + .../Puc/v5p4/Vcs/ReleaseFilteringFeature.php | 108 ++ .../Puc/v5p4/Vcs/ThemeUpdateChecker.php | 83 + .../Puc/v5p4/Vcs/VcsCheckerMethods.php | 59 + .../Puc/v5p4/WpCliCheckTrigger.php | 84 + includes/plugin-update-checker/README.md | 372 ++++ includes/plugin-update-checker/composer.json | 23 + .../css/puc-debug-bar.css | 70 + .../plugin-update-checker/js/debug-bar.js | 54 + .../languages/plugin-update-checker-ca.mo | Bin 0 -> 1186 bytes .../languages/plugin-update-checker-ca.po | 48 + .../languages/plugin-update-checker-cs_CZ.mo | Bin 0 -> 1077 bytes .../languages/plugin-update-checker-cs_CZ.po | 45 + .../languages/plugin-update-checker-da_DK.mo | Bin 0 -> 1010 bytes .../languages/plugin-update-checker-da_DK.po | 42 + .../languages/plugin-update-checker-de_DE.mo | Bin 0 -> 980 bytes .../languages/plugin-update-checker-de_DE.po | 38 + .../languages/plugin-update-checker-es_AR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_AR.po | 48 + .../languages/plugin-update-checker-es_CL.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CL.po | 48 + .../languages/plugin-update-checker-es_CO.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CO.po | 48 + .../languages/plugin-update-checker-es_CR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CR.po | 48 + .../languages/plugin-update-checker-es_DO.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_DO.po | 48 + .../languages/plugin-update-checker-es_ES.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_ES.po | 48 + .../languages/plugin-update-checker-es_GT.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_GT.po | 48 + .../languages/plugin-update-checker-es_HN.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_HN.po | 48 + .../languages/plugin-update-checker-es_MX.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_MX.po | 48 + .../languages/plugin-update-checker-es_PE.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_PE.po | 48 + .../languages/plugin-update-checker-es_PR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_PR.po | 48 + .../languages/plugin-update-checker-es_UY.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_UY.po | 48 + .../languages/plugin-update-checker-es_VE.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_VE.po | 48 + .../languages/plugin-update-checker-fa_IR.mo | Bin 0 -> 1128 bytes .../languages/plugin-update-checker-fa_IR.po | 38 + .../languages/plugin-update-checker-fr_CA.mo | Bin 0 -> 1208 bytes .../languages/plugin-update-checker-fr_CA.po | 48 + .../languages/plugin-update-checker-fr_FR.mo | Bin 0 -> 1066 bytes .../languages/plugin-update-checker-fr_FR.po | 42 + .../languages/plugin-update-checker-hu_HU.mo | Bin 0 -> 982 bytes .../languages/plugin-update-checker-hu_HU.po | 41 + .../languages/plugin-update-checker-it_IT.mo | Bin 0 -> 1135 bytes .../languages/plugin-update-checker-it_IT.po | 48 + .../languages/plugin-update-checker-ja.mo | Bin 0 -> 1454 bytes .../languages/plugin-update-checker-ja.po | 57 + .../languages/plugin-update-checker-nl_BE.mo | Bin 0 -> 1211 bytes .../languages/plugin-update-checker-nl_BE.po | 48 + .../languages/plugin-update-checker-nl_NL.mo | Bin 0 -> 1211 bytes .../languages/plugin-update-checker-nl_NL.po | 48 + .../languages/plugin-update-checker-pt_BR.mo | Bin 0 -> 1014 bytes .../languages/plugin-update-checker-pt_BR.po | 48 + .../languages/plugin-update-checker-ru_RU.mo | Bin 0 -> 1337 bytes .../languages/plugin-update-checker-ru_RU.po | 48 + .../languages/plugin-update-checker-sl_SI.mo | Bin 0 -> 1203 bytes .../languages/plugin-update-checker-sl_SI.po | 48 + .../languages/plugin-update-checker-sv_SE.mo | Bin 0 -> 1006 bytes .../languages/plugin-update-checker-sv_SE.po | 42 + .../languages/plugin-update-checker-tr_TR.mo | Bin 0 -> 1118 bytes .../languages/plugin-update-checker-tr_TR.po | 48 + .../languages/plugin-update-checker-uk_UA.mo | Bin 0 -> 1309 bytes .../languages/plugin-update-checker-uk_UA.po | 48 + .../languages/plugin-update-checker-zh_CN.mo | Bin 0 -> 1174 bytes .../languages/plugin-update-checker-zh_CN.po | 57 + .../languages/plugin-update-checker.pot | 49 + includes/plugin-update-checker/license.txt | 7 + includes/plugin-update-checker/load-v5p4.php | 34 + .../plugin-update-checker.php | 10 + .../vendor/Parsedown.php | 4 + .../vendor/ParsedownModern.php | 1538 +++++++++++++++++ .../vendor/PucReadmeParser.php | 352 ++++ 116 files changed, 11138 insertions(+), 145 deletions(-) rename apple-maps.css => assets/css/map.css (100%) rename places.js => assets/js/places.js (77%) rename apple-maps.js => assets/js/standard.js (100%) create mode 100644 includes/plugin-update-checker/Puc/v5/PucFactory.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Autoloader.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/DebugBar/Extension.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/DebugBar/Panel.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginExtension.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginPanel.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Metadata.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/OAuthSignature.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Plugin/Package.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Plugin/PluginInfo.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Plugin/Ui.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Plugin/Update.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Plugin/UpdateChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/PucFactory.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Scheduler.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/StateStore.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Theme/Package.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Theme/Update.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Theme/UpdateChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Update.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/UpdateChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/UpgraderStatus.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Utils.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/Api.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/BaseChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/BitBucketApi.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/GitHubApi.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/GitLabApi.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/PluginUpdateChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/Reference.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/ReleaseAssetSupport.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/ReleaseFilteringFeature.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/ThemeUpdateChecker.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/Vcs/VcsCheckerMethods.php create mode 100644 includes/plugin-update-checker/Puc/v5p4/WpCliCheckTrigger.php create mode 100644 includes/plugin-update-checker/README.md create mode 100644 includes/plugin-update-checker/composer.json create mode 100644 includes/plugin-update-checker/css/puc-debug-bar.css create mode 100644 includes/plugin-update-checker/js/debug-bar.js create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ca.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ca.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-cs_CZ.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-cs_CZ.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-da_DK.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-da_DK.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-de_DE.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-de_DE.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_AR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_AR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CL.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CL.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CO.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CO.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_CR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_DO.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_DO.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_ES.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_ES.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_GT.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_GT.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_HN.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_HN.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_MX.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_MX.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_PE.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_PE.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_PR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_PR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_UY.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_UY.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_VE.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-es_VE.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fr_CA.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fr_CA.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fr_FR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-fr_FR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-hu_HU.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-hu_HU.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-it_IT.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-it_IT.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ja.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ja.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-nl_BE.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-nl_BE.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-nl_NL.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-nl_NL.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-pt_BR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-pt_BR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ru_RU.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-ru_RU.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-sl_SI.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-sl_SI.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-sv_SE.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-sv_SE.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-tr_TR.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-tr_TR.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-uk_UA.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-uk_UA.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.po create mode 100644 includes/plugin-update-checker/languages/plugin-update-checker.pot create mode 100644 includes/plugin-update-checker/license.txt create mode 100644 includes/plugin-update-checker/load-v5p4.php create mode 100644 includes/plugin-update-checker/plugin-update-checker.php create mode 100644 includes/plugin-update-checker/vendor/Parsedown.php create mode 100644 includes/plugin-update-checker/vendor/ParsedownModern.php create mode 100644 includes/plugin-update-checker/vendor/PucReadmeParser.php diff --git a/apple-maps.php b/apple-maps.php index 1aac5b6..5070495 100644 --- a/apple-maps.php +++ b/apple-maps.php @@ -1,114 +1,117 @@ post_content, 'apple_map')) { - wp_enqueue_script('apple-mapkit', 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js', [], null, true); - wp_enqueue_script('apple-maps-custom-js', plugins_url('apple-maps.js', __FILE__), ['apple-mapkit'], null, true); - } elseif (has_shortcode($post->post_content, 'apple_map_places')) { - wp_enqueue_script('apple-mapkit', 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js', [], null, true); - wp_enqueue_script('apple-maps-custom-js', plugins_url('places.js', __FILE__), ['apple-mapkit'], null, true); - } - - $apple_map_settings = get_option('apple_map_settings'); - $localize_data = [ - 'token' => $apple_map_settings['apple_map_token'] ?? '', - 'glyph' => $apple_map_settings['apple_map_glyph'] ?? '' - ]; - - wp_localize_script('apple-maps-custom-js', 'appleMapsSettings', $localize_data); - wp_enqueue_style('apple-maps-custom-css', plugins_url('apple-maps.css', __FILE__)); - - // Debug logs - if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Apple Maps scripts and styles enqueued.'); - } - } + if (is_singular() || \Elementor\Plugin::instance()->preview->is_preview_mode()) { + global $post; + if (has_shortcode($post->post_content, 'apple_map')) { + wp_enqueue_script('apple-mapkit', 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js', [], null, true); + wp_enqueue_script('apple-maps-custom-js', plugins_url('assets/js/standard.js', __FILE__), ['apple-mapkit'], null, true); + } elseif (has_shortcode($post->post_content, 'apple_map_places')) { + wp_enqueue_script('apple-mapkit', 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js', [], null, true); + wp_enqueue_script('apple-maps-custom-js', plugins_url('assets/js/places.js', __FILE__), ['apple-mapkit'], null, true); + } + + $apple_map_settings = get_option('apple_map_settings'); + $localize_data = [ + 'token' => $apple_map_settings['apple_map_token'] ?? '', + 'glyph' => $apple_map_settings['apple_map_glyph'] ?? '' + ]; + + wp_localize_script('apple-maps-custom-js', 'appleMapsSettings', $localize_data); + wp_enqueue_style('apple-maps-custom-css', plugins_url('assets/css/map.css', __FILE__)); + + // Debug logs + if (defined('WP_DEBUG') && WP_DEBUG) { + error_log('Apple Maps scripts and styles enqueued.'); + } + } } add_action('wp_enqueue_scripts', 'apple_maps_enqueue_scripts'); // Shortcode for the standard Apple Map function apple_map_shortcode($atts, $content = null) { - static $map_counter = 0; - $map_counter++; - - $map_id = 'apple-map-' . $map_counter; - $locations = json_decode($content, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - return '
Invalid JSON format.
'; - } - - // Debug log - if (defined('WP_DEBUG') && WP_DEBUG) { - error_log('Apple Maps shortcode rendered with map ID: ' . $map_id); - } - - ob_start(); - ?> -" . __($description, 'apple_map') . "
"; - } + $options = get_option('apple_map_settings'); + $value = $options[$field_id] ?? ''; + + if ($type === 'textarea') { + echo ""; + } else { + echo ""; + } + + if ($description) { + echo "" . __($description, 'apple_map') . "
"; + } } function apple_map_apple_map_token_render() { - apple_map_render_field('apple_map_token', 'textarea'); + apple_map_render_field('apple_map_token', 'textarea'); } function apple_map_apple_map_glyph_render() { - apple_map_render_field('apple_map_glyph', 'text', 'Enter the glyph for the map here (e.g., "i", "M", "A").'); + apple_map_render_field('apple_map_glyph', 'text', 'Enter the glyph for the map here (e.g., "i", "M", "A").'); } // Dummy callback function for the settings section (adjust as needed) function apple_map_settings_section_callback() { - echo 'This is the Apple Maps settings section.
'; + echo 'This is the Apple Maps settings section.
'; } function apple_map_options_page() { - ?> -', esc_html(print_r($update, true)), ''; + } else { + echo 'No updates found.'; + } + + $errors = $this->updateChecker->getLastRequestApiErrors(); + if ( !empty($errors) ) { + printf('
The update checker encountered %d API error%s.
', count($errors), (count($errors) > 1) ? 's' : ''); + + foreach (array_values($errors) as $num => $item) { + $wpError = $item['error']; + /** @var \WP_Error $wpError */ + printf('%s
%s
%s
)%d %s
'; + foreach (wp_remote_retrieve_headers($item['httpResponse']) as $name => $value) { + printf("%s: %s\n", esc_html($name), esc_html($value)); + } + echo '
%s
' . htmlentities(print_r($value, true)) . ''; + } else if ($value === null) { + $value = '
null
';
+ }
+ printf(
+ '', esc_html(print_r($info, true)), ''; + } else { + echo 'Failed to retrieve plugin info from the metadata URL.'; + } + exit; + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginPanel.php b/includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginPanel.php new file mode 100644 index 0000000..4c1d230 --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginPanel.php @@ -0,0 +1,41 @@ +row('Plugin file', htmlentities($this->updateChecker->pluginFile)); + parent::displayConfigHeader(); + } + + protected function getMetadataButton() { + $requestInfoButton = ''; + if ( function_exists('get_submit_button') ) { + $requestInfoButton = get_submit_button( + 'Request Info', + 'secondary', + 'puc-request-info-button', + false, + array('id' => $this->updateChecker->getUniqueName('request-info-button')) + ); + } + return $requestInfoButton; + } + + protected function getUpdateFields() { + return array_merge( + parent::getUpdateFields(), + array('homepage', 'upgrade_notice', 'tested',) + ); + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php b/includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php new file mode 100644 index 0000000..7b9d99a --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php @@ -0,0 +1,25 @@ +row('Theme directory', htmlentities($this->updateChecker->directoryName)); + parent::displayConfigHeader(); + } + + protected function getUpdateFields() { + return array_merge(parent::getUpdateFields(), array('details_url')); + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php b/includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php new file mode 100644 index 0000000..341e7a3 --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php @@ -0,0 +1,105 @@ +updateChecker = $updateChecker; + } + + /** + * Get the currently installed version of the plugin or theme. + * + * @return string|null Version number. + */ + abstract public function getInstalledVersion(); + + /** + * Get the full path of the plugin or theme directory (without a trailing slash). + * + * @return string + */ + abstract public function getAbsoluteDirectoryPath(); + + /** + * Check whether a regular file exists in the package's directory. + * + * @param string $relativeFileName File name relative to the package directory. + * @return bool + */ + public function fileExists($relativeFileName) { + return is_file( + $this->getAbsoluteDirectoryPath() + . DIRECTORY_SEPARATOR + . ltrim($relativeFileName, '/\\') + ); + } + + /* ------------------------------------------------------------------- + * File header parsing + * ------------------------------------------------------------------- + */ + + /** + * Parse plugin or theme metadata from the header comment. + * + * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php. + * It's intended as a utility for subclasses that detect updates by parsing files in a VCS. + * + * @param string|null $content File contents. + * @return string[] + */ + public function getFileHeader($content) { + $content = (string)$content; + + //WordPress only looks at the first 8 KiB of the file, so we do the same. + $content = substr($content, 0, 8192); + //Normalize line endings. + $content = str_replace("\r", "\n", $content); + + $headers = $this->getHeaderNames(); + $results = array(); + foreach ($headers as $field => $name) { + $success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches); + + if ( ($success === 1) && $matches[1] ) { + $value = $matches[1]; + if ( function_exists('_cleanup_header_comment') ) { + $value = _cleanup_header_comment($value); + } + $results[$field] = $value; + } else { + $results[$field] = ''; + } + } + + return $results; + } + + /** + * @return array Format: ['HeaderKey' => 'Header Name'] + */ + abstract protected function getHeaderNames(); + + /** + * Get the value of a specific plugin or theme header. + * + * @param string $headerName + * @return string Either the value of the header, or an empty string if the header doesn't exist. + */ + abstract public function getHeaderValue($headerName); + + } +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Metadata.php b/includes/plugin-update-checker/Puc/v5p4/Metadata.php new file mode 100644 index 0000000..2d93d8e --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/Metadata.php @@ -0,0 +1,162 @@ + + */ + protected $extraProperties = array(); + + /** + * Create an instance of this class from a JSON document. + * + * @abstract + * @param string $json + * @return self + */ + public static function fromJson($json) { + throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); + } + + /** + * @param string $json + * @param self $target + * @return bool + */ + protected static function createFromJson($json, $target) { + /** @var \StdClass $apiResponse */ + $apiResponse = json_decode($json); + if ( empty($apiResponse) || !is_object($apiResponse) ){ + $errorMessage = "Failed to parse update metadata. Try validating your .json file with https://jsonlint.com/"; + do_action('puc_api_error', new WP_Error('puc-invalid-json', $errorMessage)); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($errorMessage), E_USER_NOTICE); + return false; + } + + $valid = $target->validateMetadata($apiResponse); + if ( is_wp_error($valid) ){ + do_action('puc_api_error', $valid); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($valid->get_error_message()), E_USER_NOTICE); + return false; + } + + foreach(get_object_vars($apiResponse) as $key => $value){ + $target->$key = $value; + } + + return true; + } + + /** + * No validation by default! Subclasses should check that the required fields are present. + * + * @param \StdClass $apiResponse + * @return bool|\WP_Error + */ + protected function validateMetadata($apiResponse) { + return true; + } + + /** + * Create a new instance by copying the necessary fields from another object. + * + * @abstract + * @param \StdClass|self $object The source object. + * @return self The new copy. + */ + public static function fromObject($object) { + throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); + } + + /** + * Create an instance of StdClass that can later be converted back to an + * update or info container. Useful for serialization and caching, as it + * avoids the "incomplete object" problem if the cached value is loaded + * before this class. + * + * @return \StdClass + */ + public function toStdClass() { + $object = new stdClass(); + $this->copyFields($this, $object); + return $object; + } + + /** + * Transform the metadata into the format used by WordPress core. + * + * @return object + */ + abstract public function toWpFormat(); + + /** + * Copy known fields from one object to another. + * + * @param \StdClass|self $from + * @param \StdClass|self $to + */ + protected function copyFields($from, $to) { + $fields = $this->getFieldNames(); + + if ( property_exists($from, 'slug') && !empty($from->slug) ) { + //Let plugins add extra fields without having to create subclasses. + $fields = apply_filters($this->getPrefixedFilter('retain_fields') . '-' . $from->slug, $fields); + } + + foreach ($fields as $field) { + if ( property_exists($from, $field) ) { + $to->$field = $from->$field; + } + } + } + + /** + * @return string[] + */ + protected function getFieldNames() { + return array(); + } + + /** + * @param string $tag + * @return string + */ + protected function getPrefixedFilter($tag) { + return 'puc_' . $tag; + } + + public function __set($name, $value) { + $this->extraProperties[$name] = $value; + } + + public function __get($name) { + return isset($this->extraProperties[$name]) ? $this->extraProperties[$name] : null; + } + + public function __isset($name) { + return isset($this->extraProperties[$name]); + } + + public function __unset($name) { + unset($this->extraProperties[$name]); + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/OAuthSignature.php b/includes/plugin-update-checker/Puc/v5p4/OAuthSignature.php new file mode 100644 index 0000000..ccb62b4 --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/OAuthSignature.php @@ -0,0 +1,102 @@ +consumerKey = $consumerKey; + $this->consumerSecret = $consumerSecret; + } + + /** + * Sign a URL using OAuth 1.0. + * + * @param string $url The URL to be signed. It may contain query parameters. + * @param string $method HTTP method such as "GET", "POST" and so on. + * @return string The signed URL. + */ + public function sign($url, $method = 'GET') { + $parameters = array(); + + //Parse query parameters. + $query = wp_parse_url($url, PHP_URL_QUERY); + if ( !empty($query) ) { + parse_str($query, $parsedParams); + if ( is_array($parsedParams) ) { + $parameters = $parsedParams; + } + //Remove the query string from the URL. We'll replace it later. + $url = substr($url, 0, strpos($url, '?')); + } + + $parameters = array_merge( + $parameters, + array( + 'oauth_consumer_key' => $this->consumerKey, + 'oauth_nonce' => $this->nonce(), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_version' => '1.0', + ) + ); + unset($parameters['oauth_signature']); + + //Parameters must be sorted alphabetically before signing. + ksort($parameters); + + //The most complicated part of the request - generating the signature. + //The string to sign contains the HTTP method, the URL path, and all of + //our query parameters. Everything is URL encoded. Then we concatenate + //them with ampersands into a single string to hash. + $encodedVerb = urlencode($method); + $encodedUrl = urlencode($url); + $encodedParams = urlencode(http_build_query($parameters, '', '&')); + + $stringToSign = $encodedVerb . '&' . $encodedUrl . '&' . $encodedParams; + + //Since we only have one OAuth token (the consumer secret) we only have + //to use it as our HMAC key. However, we still have to append an & to it + //as if we were using it with additional tokens. + $secret = urlencode($this->consumerSecret) . '&'; + + //The signature is a hash of the consumer key and the base string. Note + //that we have to get the raw output from hash_hmac and base64 encode + //the binary data result. + $parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true)); + + return ($url . '?' . http_build_query($parameters)); + } + + /** + * Generate a random nonce. + * + * @return string + */ + private function nonce() { + $mt = microtime(); + + $rand = null; + if ( is_callable('random_bytes') ) { + try { + $rand = random_bytes(16); + } catch (\Exception $ex) { + //Fall back to mt_rand (below). + } + } + if ( $rand === null ) { + //phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand + $rand = function_exists('wp_rand') ? wp_rand() : mt_rand(); + } + + return md5($mt . '_' . $rand); + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Plugin/Package.php b/includes/plugin-update-checker/Puc/v5p4/Plugin/Package.php new file mode 100644 index 0000000..30deaee --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/Plugin/Package.php @@ -0,0 +1,188 @@ +pluginAbsolutePath = $pluginAbsolutePath; + $this->pluginFile = plugin_basename($this->pluginAbsolutePath); + + parent::__construct($updateChecker); + + //Clear the version number cache when something - anything - is upgraded or WP clears the update cache. + add_filter('upgrader_post_install', array($this, 'clearCachedVersion')); + add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); + } + + public function getInstalledVersion() { + if ( isset($this->cachedInstalledVersion) ) { + return $this->cachedInstalledVersion; + } + + $pluginHeader = $this->getPluginHeader(); + if ( isset($pluginHeader['Version']) ) { + $this->cachedInstalledVersion = $pluginHeader['Version']; + return $pluginHeader['Version']; + } else { + //This can happen if the filename points to something that is not a plugin. + $this->updateChecker->triggerError( + sprintf( + "Cannot read the Version header for '%s'. The filename is incorrect or is not a plugin.", + $this->updateChecker->pluginFile + ), + E_USER_WARNING + ); + return null; + } + } + + /** + * Clear the cached plugin version. This method can be set up as a filter (hook) and will + * return the filter argument unmodified. + * + * @param mixed $filterArgument + * @return mixed + */ + public function clearCachedVersion($filterArgument = null) { + $this->cachedInstalledVersion = null; + return $filterArgument; + } + + public function getAbsoluteDirectoryPath() { + return dirname($this->pluginAbsolutePath); + } + + /** + * Get the value of a specific plugin or theme header. + * + * @param string $headerName + * @param string $defaultValue + * @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty. + */ + public function getHeaderValue($headerName, $defaultValue = '') { + $headers = $this->getPluginHeader(); + if ( isset($headers[$headerName]) && ($headers[$headerName] !== '') ) { + return $headers[$headerName]; + } + return $defaultValue; + } + + protected function getHeaderNames() { + return array( + 'Name' => 'Plugin Name', + 'PluginURI' => 'Plugin URI', + 'Version' => 'Version', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + 'TextDomain' => 'Text Domain', + 'DomainPath' => 'Domain Path', + 'Network' => 'Network', + + //The newest WordPress version that this plugin requires or has been tested with. + //We support several different formats for compatibility with other libraries. + 'Tested WP' => 'Tested WP', + 'Requires WP' => 'Requires WP', + 'Tested up to' => 'Tested up to', + 'Requires at least' => 'Requires at least', + ); + } + + /** + * Get the translated plugin title. + * + * @return string + */ + public function getPluginTitle() { + $title = ''; + $header = $this->getPluginHeader(); + if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) { + $title = translate($header['Name'], $header['TextDomain']); + } + return $title; + } + + /** + * Get plugin's metadata from its file header. + * + * @return array + */ + public function getPluginHeader() { + if ( !is_file($this->pluginAbsolutePath) ) { + //This can happen if the plugin filename is wrong. + $this->updateChecker->triggerError( + sprintf( + "Can't to read the plugin header for '%s'. The file does not exist.", + $this->updateChecker->pluginFile + ), + E_USER_WARNING + ); + return array(); + } + + if ( !function_exists('get_plugin_data') ) { + require_once(ABSPATH . '/wp-admin/includes/plugin.php'); + } + return get_plugin_data($this->pluginAbsolutePath, false, false); + } + + public function removeHooks() { + remove_filter('upgrader_post_install', array($this, 'clearCachedVersion')); + remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); + } + + /** + * Check if the plugin file is inside the mu-plugins directory. + * + * @return bool + */ + public function isMuPlugin() { + static $cachedResult = null; + + if ( $cachedResult === null ) { + if ( !defined('WPMU_PLUGIN_DIR') || !is_string(WPMU_PLUGIN_DIR) ) { + $cachedResult = false; + return $cachedResult; + } + + //Convert both paths to the canonical form before comparison. + $muPluginDir = realpath(WPMU_PLUGIN_DIR); + $pluginPath = realpath($this->pluginAbsolutePath); + //If realpath() fails, just normalize the syntax instead. + if (($muPluginDir === false) || ($pluginPath === false)) { + $muPluginDir = PucFactory::normalizePath(WPMU_PLUGIN_DIR); + $pluginPath = PucFactory::normalizePath($this->pluginAbsolutePath); + } + + $cachedResult = (strpos($pluginPath, $muPluginDir) === 0); + } + + return $cachedResult; + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Plugin/PluginInfo.php b/includes/plugin-update-checker/Puc/v5p4/Plugin/PluginInfo.php new file mode 100644 index 0000000..2428488 --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/Plugin/PluginInfo.php @@ -0,0 +1,136 @@ +sections = (array)$instance->sections; + $instance->icons = (array)$instance->icons; + + return $instance; + } + + /** + * Very, very basic validation. + * + * @param \StdClass $apiResponse + * @return bool|\WP_Error + */ + protected function validateMetadata($apiResponse) { + if ( + !isset($apiResponse->name, $apiResponse->version) + || empty($apiResponse->name) + || empty($apiResponse->version) + ) { + return new \WP_Error( + 'puc-invalid-metadata', + "The plugin metadata file does not contain the required 'name' and/or 'version' keys." + ); + } + return true; + } + + + /** + * Transform plugin info into the format used by the native WordPress.org API + * + * @return object + */ + public function toWpFormat(){ + $info = new \stdClass; + + //The custom update API is built so that many fields have the same name and format + //as those returned by the native WordPress.org API. These can be assigned directly. + $sameFormat = array( + 'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice', + 'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated', + 'requires_php', + ); + foreach($sameFormat as $field){ + if ( isset($this->$field) ) { + $info->$field = $this->$field; + } else { + $info->$field = null; + } + } + + //Other fields need to be renamed and/or transformed. + $info->download_link = $this->download_url; + $info->author = $this->getFormattedAuthor(); + $info->sections = array_merge(array('description' => ''), $this->sections); + + if ( !empty($this->banners) ) { + //WP expects an array with two keys: "high" and "low". Both are optional. + //Docs: https://wordpress.org/plugins/about/faq/#banners + $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners; + $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true)); + } + + return $info; + } + + protected function getFormattedAuthor() { + if ( !empty($this->author_homepage) ){ + /** @noinspection HtmlUnknownTarget */ + return sprintf('%s', $this->author_homepage, $this->author); + } + return $this->author; + } + } + +endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Plugin/Ui.php b/includes/plugin-update-checker/Puc/v5p4/Plugin/Ui.php new file mode 100644 index 0000000..a9cdbda --- /dev/null +++ b/includes/plugin-update-checker/Puc/v5p4/Plugin/Ui.php @@ -0,0 +1,294 @@ +updateChecker = $updateChecker; + $this->manualCheckErrorTransient = $this->updateChecker->getUniqueName('manual_check_errors'); + + add_action('admin_init', array($this, 'onAdminInit')); + } + + public function onAdminInit() { + if ( $this->updateChecker->userCanInstallUpdates() ) { + $this->handleManualCheck(); + + add_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10, 3); + add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2); + add_action('all_admin_notices', array($this, 'displayManualCheckResult')); + } + } + + /** + * Add a "View Details" link to the plugin row in the "Plugins" page. By default, + * the new link will appear before the "Visit plugin site" link (if present). + * + * You can change the link text by using the "puc_view_details_link-$slug" filter. + * Returning an empty string from the filter will disable the link. + * + * You can change the position of the link using the + * "puc_view_details_link_position-$slug" filter. + * Returning 'before' or 'after' will place the link immediately before/after + * the "Visit plugin site" link. + * Returning 'append' places the link after any existing links at the time of the hook. + * Returning 'replace' replaces the "Visit plugin site" link. + * Returning anything else disables the link when there is a "Visit plugin site" link. + * + * If there is no "Visit plugin site" link 'append' is always used! + * + * @param array $pluginMeta Array of meta links. + * @param string $pluginFile + * @param array $pluginData Array of plugin header data. + * @return array + */ + public function addViewDetailsLink($pluginMeta, $pluginFile, $pluginData = array()) { + if ( $this->isMyPluginFile($pluginFile) && !isset($pluginData['slug']) ) { + $linkText = apply_filters($this->updateChecker->getUniqueName('view_details_link'), __('View details')); + if ( !empty($linkText) ) { + $viewDetailsLinkPosition = 'append'; + + //Find the "Visit plugin site" link (if present). + $visitPluginSiteLinkIndex = count($pluginMeta) - 1; + if ( $pluginData['PluginURI'] ) { + $escapedPluginUri = esc_url($pluginData['PluginURI']); + foreach ($pluginMeta as $linkIndex => $existingLink) { + if ( strpos($existingLink, $escapedPluginUri) !== false ) { + $visitPluginSiteLinkIndex = $linkIndex; + $viewDetailsLinkPosition = apply_filters( + $this->updateChecker->getUniqueName('view_details_link_position'), + 'before' + ); + break; + } + } + } + + $viewDetailsLink = sprintf('%s', + esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . urlencode($this->updateChecker->slug) . + '&TB_iframe=true&width=600&height=550')), + esc_attr(sprintf(__('More information about %s'), $pluginData['Name'])), + esc_attr($pluginData['Name']), + $linkText + ); + switch ($viewDetailsLinkPosition) { + case 'before': + array_splice($pluginMeta, $visitPluginSiteLinkIndex, 0, $viewDetailsLink); + break; + case 'after': + array_splice($pluginMeta, $visitPluginSiteLinkIndex + 1, 0, $viewDetailsLink); + break; + case 'replace': + $pluginMeta[$visitPluginSiteLinkIndex] = $viewDetailsLink; + break; + case 'append': + default: + $pluginMeta[] = $viewDetailsLink; + break; + } + } + } + return $pluginMeta; + } + + /** + * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default, + * the new link will appear after the "Visit plugin site" link if present, otherwise + * after the "View plugin details" link. + * + * You can change the link text by using the "puc_manual_check_link-$slug" filter. + * Returning an empty string from the filter will disable the link. + * + * @param array $pluginMeta Array of meta links. + * @param string $pluginFile + * @return array + */ + public function addCheckForUpdatesLink($pluginMeta, $pluginFile) { + if ( $this->isMyPluginFile($pluginFile) ) { + $linkUrl = wp_nonce_url( + add_query_arg( + array( + 'puc_check_for_updates' => 1, + 'puc_slug' => $this->updateChecker->slug, + ), + self_admin_url('plugins.php') + ), + 'puc_check_for_updates' + ); + + $linkText = apply_filters( + $this->updateChecker->getUniqueName('manual_check_link'), + __('Check for updates', 'plugin-update-checker') + ); + if ( !empty($linkText) ) { + /** @noinspection HtmlUnknownTarget */ + $pluginMeta[] = sprintf('%s', esc_attr($linkUrl), $linkText); + } + } + return $pluginMeta; + } + + protected function isMyPluginFile($pluginFile) { + return ($pluginFile == $this->updateChecker->pluginFile) + || (!empty($this->updateChecker->muPluginFile) && ($pluginFile == $this->updateChecker->muPluginFile)); + } + + /** + * Check for updates when the user clicks the "Check for updates" link. + * + * @see self::addCheckForUpdatesLink() + * + * @return void + */ + public function handleManualCheck() { + $shouldCheck = + isset($_GET['puc_check_for_updates'], $_GET['puc_slug']) + && $_GET['puc_slug'] == $this->updateChecker->slug + && check_admin_referer('puc_check_for_updates'); + + if ( $shouldCheck ) { + $update = $this->updateChecker->checkForUpdates(); + $status = ($update === null) ? 'no_update' : 'update_available'; + $lastRequestApiErrors = $this->updateChecker->getLastRequestApiErrors(); + + if ( ($update === null) && !empty($lastRequestApiErrors) ) { + //Some errors are not critical. For example, if PUC tries to retrieve the readme.txt + //file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates + //from working. Maybe the plugin simply doesn't have a readme. + //Let's only show important errors. + $foundCriticalErrors = false; + $questionableErrorCodes = array( + 'puc-github-http-error', + 'puc-gitlab-http-error', + 'puc-bitbucket-http-error', + ); + + foreach ($lastRequestApiErrors as $item) { + $wpError = $item['error']; + /** @var \WP_Error $wpError */ + if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) { + $foundCriticalErrors = true; + break; + } + } + + if ( $foundCriticalErrors ) { + $status = 'error'; + set_site_transient($this->manualCheckErrorTransient, $lastRequestApiErrors, 60); + } + } + + wp_redirect(add_query_arg( + array( + 'puc_update_check_result' => $status, + 'puc_slug' => $this->updateChecker->slug, + ), + self_admin_url('plugins.php') + )); + exit; + } + } + + /** + * Display the results of a manual update check. + * + * @see self::handleManualCheck() + * + * You can change the result message by using the "puc_manual_check_message-$slug" filter. + */ + public function displayManualCheckResult() { + //phpcs:disable WordPress.Security.NonceVerification.Recommended -- Just displaying a message. + if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->updateChecker->slug) ) { + $status = sanitize_key($_GET['puc_update_check_result']); + $title = $this->updateChecker->getInstalledPackage()->getPluginTitle(); + $noticeClass = 'updated notice-success'; + $details = ''; + + if ( $status == 'no_update' ) { + $message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title); + } else if ( $status == 'update_available' ) { + $message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title); + } else if ( $status === 'error' ) { + $message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title); + $noticeClass = 'error notice-error'; + + $details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient)); + delete_site_transient($this->manualCheckErrorTransient); + } else { + $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), $status); + $noticeClass = 'error notice-error'; + } + + $message = esc_html($message); + + //Plugins can replace the message with their own, including adding HTML. + $message = apply_filters( + $this->updateChecker->getUniqueName('manual_check_message'), + $message, + $status + ); + + printf( + '
%s
%s%2$s
%1$s %2$s
?Y+5Q5_Zx+>QQSw1WxG&*
zrLezHK%nsfpZ9cg4~#38`f*cCNTU+7K=@3Cu}vkLqX%#+mrMKJtT(ZFrW-cj$W7rV
zv{;=R70#L<*`3?;IGcIb$xR=co*aM{%|dP>3?vVbeK7*2 ')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "hGx51C-cI*DmKm7sfZ|UStu|vFUgWo2?dJ#
zgc^qh{C1>$@u>QfL4lPtBz%E^BAdmA{e{1!XIS;LY0s>hQtXC3j-Uzxi-(oXvt)N~
e(TmOhac^_B%Vwy(g_1+_)VeFXzd5TNhy4cu&FZoM
literal 0
HcmV?d00001
diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-uk_UA.po b/includes/plugin-update-checker/languages/plugin-update-checker-uk_UA.po
new file mode 100644
index 0000000..b84b16e
--- /dev/null
+++ b/includes/plugin-update-checker/languages/plugin-update-checker-uk_UA.po
@@ -0,0 +1,48 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: plugin-update-checker\n"
+"POT-Creation-Date: 2020-08-08 14:36+0300\n"
+"PO-Revision-Date: 2021-12-20 17:55+0200\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2\n"
+"X-Poedit-Basepath: ..\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
+"Last-Translator: \n"
+"Language: uk_UA\n"
+"X-Poedit-SearchPath-0: .\n"
+
+#: Puc/v4p11/Plugin/Ui.php:128
+msgid "Check for updates"
+msgstr "Перевірити оновлення"
+
+#: Puc/v4p11/Plugin/Ui.php:213
+#, php-format
+msgctxt "the plugin title"
+msgid "The %s plugin is up to date."
+msgstr "Плагін %s оновлено."
+
+#: Puc/v4p11/Plugin/Ui.php:215
+#, php-format
+msgctxt "the plugin title"
+msgid "A new version of the %s plugin is available."
+msgstr "Нова версія %s доступна."
+
+#: Puc/v4p11/Plugin/Ui.php:217
+#, php-format
+msgctxt "the plugin title"
+msgid "Could not determine if updates are available for %s."
+msgstr "Не вдалося визначити, чи доступні оновлення для %s."
+
+#: Puc/v4p11/Plugin/Ui.php:223
+#, php-format
+msgid "Unknown update checker status \"%s\""
+msgstr "Невідомий статус перевірки оновлень \"%s\""
+
+#: Puc/v4p11/Vcs/PluginUpdateChecker.php:98
+msgid "There is no changelog available."
+msgstr "Немає доступного журналу змін."
diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo b/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo
new file mode 100644
index 0000000000000000000000000000000000000000..86d114472907c99814f248ed3e063f8bd0ce5819
GIT binary patch
literal 1174
zcmZ`&U2hXd6dfQGu!_|3hCt{lC@;Wl*DeoXO%Py0#Ye!DfOx{1*b{qUGh^+{Ccsnp
zsMHjpBDD|{EI|Z8RfXaT5KkZ;`Um(06$QNJjR*b!XT2c?MT~TGcIMuD?zywO|E=q}
z!LZf<8-WV27I+9G>lN@l@ISB~_y7>Fb{S*6U
\n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text']);
+ }
+ else
+ {
+ $markup .= $Element['text'];
+ }
+
+ $markup .= ''.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '$2
', $_sections[$i+1]);
+ $content = $this->filter_text( $content, true );
+ } else {
+ $content = '';
+ }
+ $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $content);
+ }
+
+
+ // Special sections
+ // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
+ // upgrade_notice is not a section, but parse it like it is for now
+ $final_sections = array();
+ foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) {
+ if ( isset($sections[$special_section]) ) {
+ $final_sections[$special_section] = $sections[$special_section]['content'];
+ unset($sections[$special_section]);
+ }
+ }
+ if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) )
+ $final_sections['changelog'] = $final_sections['change_log'];
+
+
+ $final_screenshots = array();
+ if ( isset($final_sections['screenshots']) ) {
+ preg_match_all('|(.*?)
#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
+ if ( count($split) >= 2 ) {
+ for ( $i = 0; $i < count( $split ); $i += 2 ) {
+ $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 );
+ }
+ }
+ unset( $final_sections['upgrade_notice'] );
+ }
+
+ // No description?
+ // No problem... we'll just fall back to the old style of description
+ // We'll even let you use markup this time!
+ $excerpt = false;
+ if ( !isset($final_sections['description']) ) {
+ $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections);
+ $excerpt = true;
+ }
+
+
+ // dump the non-special sections into $remaining_content
+ // their order will be determined by their original order in the readme.txt
+ $remaining_content = '';
+ foreach ( $sections as $s_name => $s_data ) {
+ $remaining_content .= "\n{$s_data['title']}
\n{$s_data['content']}";
+ }
+ $remaining_content = trim($remaining_content);
+
+
+ // All done!
+ // $r['tags'] and $r['contributors'] are simple arrays
+ // $r['sections'] is an array with named elements
+ $r = array(
+ 'name' => $name,
+ 'tags' => $tags,
+ 'requires_at_least' => $requires_at_least,
+ 'tested_up_to' => $tested_up_to,
+ 'requires_php' => $requires_php,
+ 'stable_tag' => $stable_tag,
+ 'contributors' => $contributors,
+ 'donate_link' => $donate_link,
+ 'short_description' => $short_description,
+ 'screenshots' => $final_screenshots,
+ 'is_excerpt' => $excerpt,
+ 'is_truncated' => $truncated,
+ 'sections' => $final_sections,
+ 'remaining_content' => $remaining_content,
+ 'upgrade_notice' => $upgrade_notice
+ );
+
+ return $r;
+ }
+
+ function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
+ if ( $_string = strstr($string, $chop) ) {
+ $_string = substr($_string, strlen($chop));
+ return trim($_string);
+ } else {
+ return trim($string);
+ }
+ }
+
+ function user_sanitize( $text, $strict = false ) { // whitelisted chars
+ if ( function_exists('user_sanitize') ) // bbPress native
+ return user_sanitize( $text, $strict );
+
+ if ( $strict ) {
+ $text = preg_replace('/[^a-z0-9-]/i', '', $text);
+ $text = preg_replace('|-+|', '-', $text);
+ } else {
+ $text = preg_replace('/[^a-z0-9_-]/i', '', $text);
+ }
+ return $text;
+ }
+
+ function sanitize_text( $text ) { // not fancy
+ $text = function_exists('wp_strip_all_tags')
+ ? wp_strip_all_tags($text)
+ //phpcs:ignore WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter -- Using wp_strip_all_tags() if available
+ : strip_tags($text);
+
+ $text = esc_html($text);
+ $text = trim($text);
+ return $text;
+ }
+
+ function filter_text( $text, $markdown = false ) { // fancy, Markdown
+ $text = trim($text);
+
+ $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
+
+ if ( $markdown ) { // Parse markdown.
+ if ( !class_exists('Parsedown', false) ) {
+ /** @noinspection PhpIncludeInspection */
+ require_once(dirname(__FILE__) . '/Parsedown' . (version_compare(PHP_VERSION, '5.3.0', '>=') ? '' : 'Legacy') . '.php');
+ }
+ $instance = Parsedown::instance();
+ $text = $instance->text($text);
+ }
+
+ $allowed = array(
+ 'a' => array(
+ 'href' => array(),
+ 'title' => array(),
+ 'rel' => array()),
+ 'blockquote' => array('cite' => array()),
+ 'br' => array(),
+ 'p' => array(),
+ 'code' => array(),
+ 'pre' => array(),
+ 'em' => array(),
+ 'strong' => array(),
+ 'ul' => array(),
+ 'ol' => array(),
+ 'li' => array(),
+ 'h3' => array(),
+ 'h4' => array()
+ );
+
+ $text = balanceTags($text);
+
+ $text = wp_kses( $text, $allowed );
+ $text = trim($text);
+ return $text;
+ }
+
+ function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown
+ // If doing markdown, first take any user formatted code blocks and turn them into backticks so that
+ // markdown will preserve things like underscores in code blocks
+ if ( $markdown )
+ $text = preg_replace_callback("!(
|)!s", array( __CLASS__,'decodeit'), $text);
+
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+ if ( !$markdown ) {
+ // This gets the "inline" code blocks, but can't be used with Markdown.
+ $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
+ // This gets the "block level" code blocks and converts them to PRE CODE
+ $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
+ } else {
+ // Markdown can do inline code, we convert bbPress style block level code to Markdown style
+ $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
+ }
+ return $text;
+ }
+
+ function indent( $matches ) {
+ $text = $matches[3];
+ $text = preg_replace('|^|m', $matches[2] . ' ', $text);
+ return $matches[1] . $text;
+ }
+
+ function encodeit( $matches ) {
+ if ( function_exists('encodeit') ) // bbPress native
+ return encodeit( $matches );
+
+ $text = trim($matches[2]);
+ $text = htmlspecialchars($text, ENT_QUOTES);
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+ $text = preg_replace("|\n\n\n+|", "\n\n", $text);
+ $text = str_replace('<', '<', $text);
+ $text = str_replace('>', '>', $text);
+ $text = "|
)(.*?)(
$text
";
+ if ( "`" != $matches[1] )
+ $text = "$text
";
+ return $text;
+ }
+
+ function decodeit( $matches ) {
+ if ( function_exists('decodeit') ) // bbPress native
+ return decodeit( $matches );
+
+ $text = $matches[2];
+ $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES));
+ $text = strtr($text, $trans_table);
+ $text = str_replace('
', '', $text);
+ $text = str_replace('&', '&', $text);
+ $text = str_replace(''', "'", $text);
+ if ( '' == $matches[1] )
+ $text = "\n$text\n";
+ return "`$text`";
+ }
+
+} // end class
+
+endif;