PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 1.9.12 1.9.11 1.9.10 1.9.9 All 158 releases
woocommerce-pos / includes / Services / Extensions.php

Extensions.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Services/Extensions.php

219 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Extensions service.
4 *
5 * Fetches the remote extension catalog and enriches it with local plugin status.
6 *
7 * @package WCPOS\WooCommercePOS\Services
8 */
9
10 namespace WCPOS\WooCommercePOS\Services;
11
12 /**
13 * Extensions service class.
14 */
15 class Extensions {
16
17 /**
18 * Singleton instance.
19 *
20 * @var Extensions|null
21 */
22 private static $instance = null;
23
24 /**
25 * Remote catalog URL.
26 *
27 * @var string
28 */
29 const CATALOG_URL = 'https://raw.githubusercontent.com/wcpos/extensions/main/catalog.json';
30
31 /**
32 * Transient key for cached catalog.
33 *
34 * @var string
35 */
36 const TRANSIENT_KEY = 'wcpos_extensions_catalog';
37
38 /**
39 * Cache TTL in seconds (1 hour).
40 *
41 * @var int
42 */
43 const CACHE_TTL = HOUR_IN_SECONDS;
44
45 /**
46 * Constructor is private to prevent direct instantiation.
47 */
48 private function __construct() {
49 add_action( 'activated_plugin', array( $this, 'clear_cache_on_plugin_change' ) );
50 add_action( 'deactivated_plugin', array( $this, 'clear_cache_on_plugin_change' ) );
51 }
52
53 /**
54 * Get singleton instance.
55 *
56 * @return Extensions
57 */
58 public static function instance(): Extensions {
59 if ( null === self::$instance ) {
60 self::$instance = new self();
61 }
62
63 return self::$instance;
64 }
65
66 /**
67 * Get the raw catalog from remote or cache.
68 *
69 * @return array
70 */
71 public function get_catalog(): array {
72 $cached = get_transient( self::TRANSIENT_KEY );
73
74 if ( false !== $cached ) {
75 return $cached;
76 }
77
78 /**
79 * Filters the URL used to fetch the extensions catalog.
80 *
81 * @since 1.9.0
82 *
83 * @param string $url The catalog URL.
84 */
85 $url = apply_filters( 'woocommerce_pos_extensions_catalog_url', self::CATALOG_URL );
86
87 $response = wp_remote_get(
88 $url,
89 array( 'timeout' => 15 )
90 );
91
92 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
93 return array();
94 }
95
96 $body = wp_remote_retrieve_body( $response );
97 $catalog = json_decode( $body, true );
98
99 if ( ! \is_array( $catalog ) ) {
100 return array();
101 }
102
103 set_transient( self::TRANSIENT_KEY, $catalog, self::CACHE_TTL );
104
105 return $catalog;
106 }
107
108 /**
109 * Get extensions enriched with local install/active status.
110 *
111 * @return array
112 */
113 public function get_extensions(): array {
114 $catalog = $this->get_catalog();
115
116 if ( empty( $catalog ) ) {
117 return array();
118 }
119
120 if ( ! \function_exists( 'get_plugins' ) ) {
121 require_once ABSPATH . 'wp-admin/includes/plugin.php';
122 }
123
124 $installed_plugins = get_plugins();
125 $active_plugins = get_option( 'active_plugins', array() );
126
127 $network_plugins = array();
128 if ( is_multisite() ) {
129 $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
130 }
131
132 $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
133
134 $extensions = array();
135
136 foreach ( $catalog as $entry ) {
137 $slug = $entry['slug'] ?? '';
138 $plugin_file = $this->find_plugin_file( $slug, $installed_plugins );
139 $status = 'not_installed';
140
141 if ( $plugin_file ) {
142 $local_version = $installed_plugins[ $plugin_file ]['Version'] ?? '';
143 $remote_version = $entry['latest_version'] ?? $entry['version'] ?? '';
144 $is_active = \in_array( $plugin_file, $active_plugins, true )
145 || \in_array( $plugin_file, $network_plugins, true );
146
147 $has_update = $remote_version && version_compare( $local_version, $remote_version, '<' );
148
149 if ( $has_update && $is_active ) {
150 $status = 'update_available';
151 } elseif ( $is_active ) {
152 $status = 'active';
153 } else {
154 $status = 'inactive';
155 }
156
157 $entry['installed_version'] = $local_version;
158 $entry['plugin_file'] = $plugin_file;
159 $entry['auto_update'] = \in_array( $plugin_file, $auto_updates, true );
160
161 if ( $has_update ) {
162 $entry['has_update'] = true;
163 }
164
165 if ( ! empty( $entry['settings_url'] ) && \is_string( $entry['settings_url'] ) ) {
166 $entry['settings_url'] = admin_url( $entry['settings_url'] );
167 }
168 }
169
170 $entry['status'] = $status;
171 $extensions[] = $entry;
172 }
173
174 return $extensions;
175 }
176
177 /**
178 * Find the plugin file path for a given extension slug.
179 *
180 * Looks for a plugin directory matching the slug.
181 *
182 * @param string $slug Extension slug.
183 * @param array $installed_plugins Installed plugins from get_plugins().
184 *
185 * @return string|null Plugin file path or null if not found.
186 */
187 private function find_plugin_file( string $slug, array $installed_plugins ): ?string {
188 foreach ( array_keys( $installed_plugins ) as $plugin_file ) {
189 if ( 0 === strpos( $plugin_file, $slug . '/' ) ) {
190 return $plugin_file;
191 }
192 }
193
194 return null;
195 }
196
197 /**
198 * Clear the catalog transient cache.
199 *
200 * @return bool
201 */
202 public function clear_cache(): bool {
203 if ( false === get_transient( self::TRANSIENT_KEY ) ) {
204 return true;
205 }
206
207 return delete_transient( self::TRANSIENT_KEY );
208 }
209
210 /**
211 * Clear the catalog cache after a plugin change.
212 *
213 * @return void
214 */
215 public function clear_cache_on_plugin_change(): void {
216 $this->clear_cache();
217 }
218 }
219