commit 979083cbd0593a4d72e508b839a7969dff2a289f Author: Roberto Guagliardo Date: Sun Feb 1 12:37:53 2026 +0000 Add Siti License Validator and Siti Web Updater classes for license management and plugin updates diff --git a/includes/SitiLicenseValidator.php b/includes/SitiLicenseValidator.php new file mode 100644 index 0000000..baadd8f --- /dev/null +++ b/includes/SitiLicenseValidator.php @@ -0,0 +1,181 @@ +license_key = $license_key ?: get_option( 'siti_license_key' ); + $this->hostname = $hostname ?: parse_url( home_url(), PHP_URL_HOST ); + $this->plugin_version = $plugin_version; + } + + /** + * Set the license key + */ + public function set_license_key( $key ) { + $this->license_key = $key; + } + + /** + * Set the API base URL + */ + public function set_api_base_url( $url ) { + $this->api_base_url = rtrim( $url, '/' ); + } + + /** + * Set the hostname + */ + public function set_hostname( $hostname ) { + $this->hostname = $hostname; + } + + /** + * Set the plugin version for reporting purposes + */ + public function set_plugin_version( $version ) { + $this->plugin_version = $version; + } + + /** + * Verify the license + * + * @return array|WP_Error License data on success, WP_Error on failure + */ + public function verify() { + if ( empty( $this->license_key ) ) { + return new WP_Error( 'missing_license', 'Geen licentiecode ingesteld.' ); + } + + if ( empty( $this->hostname ) ) { + return new WP_Error( 'missing_hostname', 'Kon hostname niet bepalen.' ); + } + + $response = wp_remote_post( $this->api_base_url . '/api/licenses/verify', array( + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( array( + 'key' => $this->license_key, + 'hostname' => $this->hostname, + 'currentVersion' => $this->plugin_version, + ) ), + 'timeout' => 15, + ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $code = wp_remote_retrieve_response_code( $response ); + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( ! is_array( $body ) ) { + return new WP_Error( 'verification_failed', 'Onverwacht antwoord van licentieserver.' ); + } + + $has_payload = isset( $body['license'] ) || isset( $body['pluginVersion'] ) || isset( $body['version'] ); + + if ( 200 !== $code ) { + if ( $has_payload ) { + $this->store_license_payload( $body ); + } + + $error_message = isset( $body['error'] ) ? $body['error'] : 'Licentie verificatie mislukt.'; + return new WP_Error( 'verification_failed', $error_message ); + } + + if ( empty( $body['valid'] ) ) { + if ( $has_payload ) { + $this->store_license_payload( $body ); + } + + return new WP_Error( 'invalid_license', 'Licentie is ongeldig.' ); + } + + $this->store_license_payload( $body ); + + return $body; + } + + /** + * Get stored license data + */ + public function get_license_data() { + return get_option( 'siti_license_data', array() ); + } + + /** + * Check if license is valid (cached) + */ + public function is_valid() { + $data = $this->get_license_data(); + return ! empty( $data ) && isset( $data['key'] ); + } + + /** + * Get the latest version from license data + */ + public function get_latest_version() { + $data = $this->get_license_data(); + return $data['pluginVersion'] ?? null; + } + + /** + * Download a specific version + * + * @param string $version Version to download (e.g., 'v1.2.3' or 'latest') + * @return string|WP_Error Download URL or error + */ + public function get_download_url( $version = 'latest' ) { + if ( empty( $this->license_key ) || empty( $this->hostname ) ) { + return new WP_Error( 'missing_credentials', 'Licentie of hostname ontbreekt.' ); + } + + // For now, return the API download endpoint + // In practice, you might want to proxy this through WordPress + return $this->api_base_url . '/api/licenses/download?key=' . urlencode( $this->license_key ) . '&hostname=' . urlencode( $this->hostname ) . '&version=' . urlencode( $version ); + } + + /** + * Persist payload data locally for UI purposes + */ + private function store_license_payload( $body ) { + $license_data = array(); + + if ( isset( $body['license'] ) && is_array( $body['license'] ) ) { + $license_data = $body['license']; + } + + if ( empty( $license_data['key'] ) ) { + $license_data['key'] = $this->license_key; + } + + $version = null; + if ( ! empty( $body['pluginVersion'] ) ) { + $version = $body['pluginVersion']; + } elseif ( ! empty( $body['version'] ) ) { + $version = $body['version']; + } + + if ( $version ) { + $license_data['pluginVersion'] = $version; + } + + if ( empty( $license_data ) ) { + return; + } + + update_option( 'siti_license_data', $license_data ); + update_option( 'siti_license_last_check', current_time( 'mysql' ) ); + } +} diff --git a/includes/SitiWebUpdater2.php b/includes/SitiWebUpdater2.php new file mode 100644 index 0000000..7756606 --- /dev/null +++ b/includes/SitiWebUpdater2.php @@ -0,0 +1,510 @@ +file = $file; + $this->option_prefix = 'siti_updater_' . sanitize_key(dirname(plugin_basename($this->file))) . '_'; + $this->set_plugin_properties(); + + // Register this instance globally + self::$instances[$this->option_prefix] = $this; + + add_action('admin_init', array($this, 'set_plugin_properties')); + add_action('admin_notices', array($this, 'admin_notices')); + add_action('siti_updater_daily_check_' . $this->option_prefix, array($this, 'daily_license_check')); + + if (!wp_next_scheduled('siti_updater_daily_check_' . $this->option_prefix)) { + wp_schedule_event(time(), 'daily', 'siti_updater_daily_check_' . $this->option_prefix); + } + + return $this; + } + + public function initialize() + { + add_filter('pre_set_site_transient_update_plugins', array($this, 'check_for_updates')); + add_filter('plugins_api', array($this, 'plugin_info'), 10, 3); + + // Register global settings page only once + if (!self::$global_settings_registered) { + add_action('admin_menu', array($this, 'add_global_settings_page')); + self::$global_settings_registered = true; + } + } + + public function set_plugin_properties() + { + if (!function_exists('get_plugin_data')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $this->plugin = get_plugin_data($this->file, false, false); + $this->basename = plugin_basename($this->file); + $this->active = is_plugin_active($this->basename); + } + + public static function get_instances() + { + return self::$instances; + } + + public function get_plugin_data() + { + return $this->plugin; + } + + public function set_owner($owner) + { + $this->owner = $owner; + } + + public function set_repository($repository) + { + $this->repository = $repository; + } + + public function set_license_key($key) + { + $sanitized_key = sanitize_text_field((string) $key); + $stored_key = get_option($this->option_prefix . 'license_key', ''); + + if ($stored_key !== $sanitized_key) { + $this->clear_cached_license_data(); + } + + $this->license_key = $sanitized_key; + update_option($this->option_prefix . 'license_key', $sanitized_key); + } + + public function get_license_key() + { + if (null === $this->license_key) { + $this->license_key = get_option($this->option_prefix . 'license_key', ''); + } + return $this->license_key; + } + + public function get_license_data() + { + return $this->get_stored_license_data(); + } + + public function set_api_base_url($api_base_url) + { + $this->api_base_url = rtrim((string) $api_base_url, '/'); + } + + private function normalize_version($version) + { + if (!is_string($version)) { + return $version; + } + + $version = trim($version); + if ('' === $version) { + return $version; + } + + if (0 === stripos($version, 'v')) { + $version = ltrim($version, 'vV'); + } + + return $version; + } + + private function get_stored_license_data() + { + $data = get_option($this->option_prefix . 'license_data', array()); + + return is_array($data) ? $data : array(); + } + + private function clear_cached_license_data() + { + delete_option($this->option_prefix . 'license_data'); + delete_option($this->option_prefix . 'last_check'); + } + + private function store_license_data($body) + { + if (!is_array($body)) { + $body = array(); + } + + $license_data = array(); + + if (isset($body['license']) && is_array($body['license'])) { + $license_data = $body['license']; + } + + if (empty($license_data['key'])) { + $license_data['key'] = $this->get_license_key(); + } + + $license_data['licenseValid'] = !empty($body['valid']); + + $version = null; + if (isset($body['pluginVersion']) && $body['pluginVersion']) { + $version = $body['pluginVersion']; + } elseif (isset($body['version']) && $body['version']) { + $version = $body['version']; + } elseif (isset($license_data['pluginVersion']) && $license_data['pluginVersion']) { + $version = $license_data['pluginVersion']; + } + + if ($version) { + $license_data['pluginVersion'] = $this->normalize_version($version); + } + + update_option($this->option_prefix . 'license_data', $license_data); + update_option($this->option_prefix . 'last_check', current_time('mysql')); + } + + private function get_plugin_info() + { + if (is_null($this->plugin_response)) { + $request_uri = sprintf('%s/api/plugins/%s/%s', $this->api_base_url, $this->owner, $this->repository); + + $response = wp_remote_get($request_uri, array('timeout' => 15)); + + if (is_wp_error($response)) { + $this->plugin_response = false; + return; + } + + $code = wp_remote_retrieve_response_code($response); + if (200 !== $code) { + $this->plugin_response = false; + return; + } + + $body = json_decode(wp_remote_retrieve_body($response), true); + if (is_array($body) && isset($body['version'])) { + $body['version'] = $this->normalize_version($body['version']); + } + $this->plugin_response = $body; + } + } + + public function check_for_updates($transient) + { + if (empty($transient->checked)) { + return $transient; + } + + $this->get_plugin_info(); + + if (false === $this->plugin_response || empty($this->plugin_response['version'])) { + return $transient; + } + + $remote_version = $this->normalize_version($this->plugin_response['version']); + $current_version = $this->normalize_version($this->plugin['Version']); + + if ($remote_version && version_compare($remote_version, $current_version, '>')) { + $download_url = $this->get_download_url('latest'); + + if (!is_wp_error($download_url)) { + $transient->response[$this->basename] = (object) array( + 'slug' => dirname($this->basename), + 'new_version' => $remote_version, + 'package' => $download_url, + 'tested' => get_bloginfo('version'), + 'url' => $this->plugin_response['homepage'] ?? '', + ); + } + } + + return $transient; + } + + public function plugin_info($result, $action, $args) + { + if ('plugin_information' !== $action || $args->slug !== dirname($this->basename)) { + return $result; + } + + $this->get_plugin_info(); + + if (false === $this->plugin_response) { + return $result; + } + + return (object) array( + 'name' => $this->plugin_response['name'] ?? $this->plugin['Name'], + 'slug' => $args->slug, + 'version' => $this->normalize_version($this->plugin_response['version'] ?? $this->plugin['Version']), + 'author' => $this->plugin_response['author'] ?? $this->plugin['Author'], + 'author_profile' => $this->plugin_response['author_url'] ?? $this->plugin['AuthorURI'], + 'homepage' => $this->plugin_response['homepage'] ?? '', + 'requires' => $this->plugin_response['requires'] ?? '', + 'tested' => $this->plugin_response['tested'] ?? get_bloginfo('version'), + 'downloaded' => $this->plugin_response['downloads'] ?? 0, + 'last_updated' => $this->plugin_response['last_updated'] ?? '', + 'sections' => array( + 'description' => $this->plugin_response['description'] ?? $this->plugin['Description'], + 'changelog' => $this->plugin_response['changelog'] ?? '', + ), + 'download_link' => $this->get_download_url('latest'), + ); + } + + private function get_download_url($version = 'latest') + { + $license_key = $this->get_license_key(); + if (empty($license_key)) { + return new WP_Error('no_license', 'Geen licentie ingesteld'); + } + + $hostname = parse_url(home_url(), PHP_URL_HOST); + return $this->api_base_url . '/api/licenses/download?key=' . urlencode($license_key) . '&hostname=' . urlencode($hostname) . '&version=' . urlencode($version); + } + + public function verify_license() + { + $license_key = $this->get_license_key(); + if (empty($license_key)) { + return new WP_Error('missing_license', 'Geen licentiecode ingesteld.'); + } + + $hostname = parse_url(home_url(), PHP_URL_HOST); + $current_version = isset($this->plugin['Version']) ? $this->plugin['Version'] : null; + $response = wp_remote_post($this->api_base_url . '/api/licenses/verify', array( + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode(array( + 'key' => $license_key, + 'hostname' => $hostname, + 'currentVersion' => $current_version, + 'owner' => $this->owner, + 'repository' => $this->repository, + )), + 'timeout' => 15, + )); + + if (is_wp_error($response)) { + return $response; + } + + $code = wp_remote_retrieve_response_code($response); + $body = json_decode(wp_remote_retrieve_body($response), true); + + if (!is_array($body)) { + $this->clear_cached_license_data(); + return new WP_Error('verification_failed', 'Onverwacht antwoord van licentieserver.'); + } + + $has_license_payload = isset($body['license']) || isset($body['pluginVersion']) || isset($body['version']); + + if (200 !== $code) { + if ($has_license_payload) { + $this->store_license_data($body); + } else { + $this->clear_cached_license_data(); + } + + $error_message = isset($body['error']) ? $body['error'] : 'Licentie verificatie mislukt.'; + return new WP_Error('verification_failed', $error_message); + } + + if (empty($body['valid'])) { + if ($has_license_payload) { + $this->store_license_data($body); + } else { + $this->clear_cached_license_data(); + } + + return new WP_Error('invalid_license', 'Licentie is ongeldig.'); + } + + $this->store_license_data($body); + + return $body; + } + + public function is_license_valid() + { + $data = $this->get_license_data(); + if (empty($data) || empty($data['key'])) { + return false; + } + + if (isset($data['licenseValid'])) { + return (bool) $data['licenseValid']; + } + + return true; + } + + public function get_latest_known_version() + { + $this->get_plugin_info(); + + if (!empty($this->plugin_response['version'])) { + return $this->normalize_version($this->plugin_response['version']); + } + + $license_data = $this->get_license_data(); + if (isset($license_data['pluginVersion']) && $license_data['pluginVersion']) { + return $this->normalize_version($license_data['pluginVersion']); + } + + return null; + } + + public function admin_notices() + { + if (!current_user_can('manage_options')) { + return; + } + + if (!$this->is_license_valid()) { + $settings_url = admin_url('options-general.php?page=siti-plugin-licenses'); + echo '

'; + printf( + esc_html__('%s: Licentie ongeldig of niet ingesteld. Ga naar %s om je licentie in te voeren.', 'siti-updater'), + esc_html($this->plugin['Name']), + '' . esc_html__('plugin licenties', 'siti-updater') . '' + ); + echo '

'; + } + } + + public function daily_license_check() + { + $result = $this->verify_license(); + if (is_wp_error($result)) { + error_log($this->plugin['Name'] . ' License check failed: ' . $result->get_error_message()); + } + } + + public function add_global_settings_page() + { + add_options_page( + __('Plugin Licenties', 'siti-updater'), + __('Plugin Licenties', 'siti-updater'), + 'manage_options', + 'siti-plugin-licenses', + array($this, 'render_global_settings_page') + ); + } + + public function render_global_settings_page() + { + if (!current_user_can('manage_options')) { + return; + } + + if (isset($_POST['siti_updater_save']) && wp_verify_nonce($_POST['_wpnonce'], 'siti_updater_save')) { + foreach (self::$instances as $prefix => $instance) { + $key = $prefix . 'license_key'; + if (isset($_POST[$key])) { + $instance->set_license_key(sanitize_text_field($_POST[$key])); + $result = $instance->verify_license(); + if (is_wp_error($result)) { + $plugin_data = $instance->get_plugin_data(); + add_settings_error('siti_updater', 'license_error_' . $prefix, $plugin_data['Name'] . ': ' . $result->get_error_message()); + } else { + $plugin_data = $instance->get_plugin_data(); + add_settings_error('siti_updater', 'license_success_' . $prefix, $plugin_data['Name'] . ': ' . __('Licentie succesvol opgeslagen en geverifieerd.', 'siti-updater'), 'updated'); + } + } + } + } + + ?> +
+

+

+ +

+ + + +
+ + + + + + + + + + + + + $instance): + $license_data = $instance->get_license_data(); + $is_valid = $instance->is_license_valid(); + $plugin_data = $instance->get_plugin_data(); + $current_version = $plugin_data['Version']; + $latest_known = $instance->get_latest_known_version(); + $latest_version = $latest_known ? $latest_known : (isset($license_data['pluginVersion']) ? $license_data['pluginVersion'] : '-'); + ?> + + + + + + + + + +
+ +
+ +
+ +

+ +

+
+ + ✓ ' . esc_html__('Ja', 'siti-updater') . '' : '✗ ' . esc_html__('Nee', 'siti-updater') . ''; ?> + + + + + + ')): ?> +
+ +
+

+ +

+
+
+ set_owner( 'roberto' ); +$updater->set_repository( 'siti-category-thumbnails' ); +$updater->initialize(); + +final class Siti_Category_Thumbnails { + const PAGE_SLUG = 'siti-category-thumbnails'; + + public static function init() { + add_action( 'admin_menu', array( __CLASS__, 'register_admin_page' ) ); + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + WP_CLI::add_command( 'siti update-category-thumbnails', array( __CLASS__, 'cli_command' ) ); + } + } + + public static function register_admin_page() { + add_management_page( + __( 'Category Thumbnails', 'livebetter' ), + __( 'Category Thumbnails', 'livebetter' ), + self::required_capability(), + self::PAGE_SLUG, + array( __CLASS__, 'render_admin_page' ) + ); + } + + public static function render_admin_page() { + $capability = self::required_capability(); + if ( ! current_user_can( $capability ) ) { + wp_die( esc_html__( 'Je hebt geen rechten om dit uit te voeren.', 'livebetter' ) ); + } + + $messages = array(); + $dry_run = false; + + if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['lb_category_thumbnails_nonce'] ) ) { + check_admin_referer( 'lb_category_thumbnails_run', 'lb_category_thumbnails_nonce' ); + $dry_run = isset( $_POST['lb_dry_run'] ); + self::run_update( + $dry_run, + function ( $message ) use ( &$messages ) { + $messages[] = $message; + } + ); + } + + ?> +
+

+

+ +
+ +

+ + +

+
+ + +

+
+ +

+ +
+ 'product_cat', + 'hide_empty' => false, + ) + ); + + if ( is_wp_error( $terms ) || empty( $terms ) ) { + $log( 'Geen categorieen gevonden.' ); + return; + } + + $log( sprintf( 'Start update voor %d categorieen%s...', count( $terms ), $dry_run ? ' (dry-run)' : '' ) ); + + foreach ( $terms as $term ) { + $query = new WP_Query( + array( + 'post_type' => 'product', + 'posts_per_page' => 1, + 'post_status' => 'publish', + 'orderby' => 'date', + 'order' => 'DESC', + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => 'product_cat', + 'field' => 'term_id', + 'terms' => $term->term_id, + ), + ), + 'meta_query' => array( + array( + 'key' => '_thumbnail_id', + 'compare' => 'EXISTS', + ), + ), + 'no_found_rows' => true, + ) + ); + + $latest_id = $query->posts[0] ?? 0; + wp_reset_postdata(); + + if ( ! $latest_id ) { + $log( sprintf( 'Skip: %s - geen producten gevonden.', $term->name ) ); + continue; + } + + $thumbnail_id = get_post_thumbnail_id( $latest_id ); + if ( ! $thumbnail_id ) { + $log( sprintf( 'Skip: %s - product %d heeft geen uitgelichte afbeelding.', $term->name, $latest_id ) ); + continue; + } + + if ( $dry_run ) { + $log( sprintf( 'Dry-run: %s zou thumbnail %d krijgen (product %d).', $term->name, $thumbnail_id, $latest_id ) ); + continue; + } + + update_term_meta( $term->term_id, 'thumbnail_id', $thumbnail_id ); + $log( sprintf( 'OK: %s bijgewerkt met afbeelding van product %d.', $term->name, $latest_id ) ); + } + } + + private static function required_capability() { + return current_user_can( 'manage_woocommerce' ) ? 'manage_woocommerce' : 'manage_options'; + } +} + +Siti_Category_Thumbnails::init();