PluginProbe
Packeta / 1.6.3
Packeta v1.6.3
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / Options / FeatureFlagManager.php

FeatureFlagManager.php in Packeta 1.6.3, at src/Packetery/Module/Options/FeatureFlagManager.php

243 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class FeatureFlagManager
4 *
5 * @package Packetery
6 */
7
8 declare( strict_types=1 );
9
10 namespace Packetery\Module\Options;
11
12 use DateTimeImmutable;
13 use Exception;
14 use Packetery\GuzzleHttp\Exception\ConnectException;
15 use Packetery\Core\Helper;
16 use Packetery\Module\Plugin;
17 use Packetery\GuzzleHttp\Client;
18 use Packetery\GuzzleHttp\Exception\GuzzleException;
19 use Packetery\GuzzleHttp\Psr7\Response;
20 use Packetery\Latte\Engine;
21
22 /**
23 * Class FeatureFlagManager
24 *
25 * @package Packetery
26 */
27 class FeatureFlagManager {
28
29 private const ENDPOINT_URL = 'https://pes-features-prod-pes.prod.packeta-com.codenow.com/v1/wp';
30 private const VALID_FOR_HOURS = 4;
31 private const FLAGS_OPTION_ID = 'packeta_feature_flags';
32 private const TRANSIENT_SHOW_SPLIT_MESSAGE = 'packeta_show_split_message';
33 public const ACTION_HIDE_SPLIT_MESSAGE = 'dismiss_split_message';
34 private const DISABLED_DUE_ERRORS_OPTION_ID = 'packeta_feature_flags_disabled_due_errors';
35 private const ERROR_COUNTER_OPTION_ID = 'packeta_feature_flags_error_counter';
36
37 private const FLAG_LAST_DOWNLOAD = 'lastDownload';
38 private const FLAG_SPLIT_ACTIVE = 'splitActive';
39
40 /**
41 * Guzzle client.
42 *
43 * @var Client Guzzle client.
44 */
45 private $client;
46
47 /**
48 * Latte engine.
49 *
50 * @var Engine
51 */
52 private $latteEngine;
53
54 /**
55 * Options provider.
56 *
57 * @var Provider
58 */
59 private $optionsProvider;
60
61 /**
62 * Downloader constructor.
63 *
64 * @param Client $guzzleClient Guzzle client.
65 * @param Engine $latteEngine Latte engine.
66 * @param Provider $optionsProvider Options provider.
67 */
68 public function __construct( Client $guzzleClient, Engine $latteEngine, Provider $optionsProvider ) {
69 $this->client = $guzzleClient;
70 $this->latteEngine = $latteEngine;
71 $this->optionsProvider = $optionsProvider;
72 }
73
74 /**
75 * Downloads flags.
76 *
77 * @return array
78 * @throws Exception From DateTimeImmutable.
79 */
80 private function fetchFlags(): array {
81 /**
82 * Guzzle response.
83 *
84 * @var Response $response Guzzle response.
85 */
86 try {
87 $response = $this->client->get(
88 self::ENDPOINT_URL,
89 [
90 'query' => [
91 'api_key' => $this->optionsProvider->get_api_key(),
92 ],
93 'timeout' => 20,
94 ]
95 );
96 } catch ( ConnectException $exception ) {
97 $logger = new \WC_Logger();
98 $logger->warning( 'Packeta Feature flag API download error: ' . $exception->getMessage() );
99 $errorCount = get_option( self::ERROR_COUNTER_OPTION_ID, 0 );
100 update_option( self::ERROR_COUNTER_OPTION_ID, $errorCount + 1 );
101 if ( $errorCount > 5 ) {
102 update_option( self::DISABLED_DUE_ERRORS_OPTION_ID, true );
103 $logger->warning( 'Packeta Feature flag API download was disabled due to permanent connection errors.' );
104 }
105
106 return [];
107 } catch ( GuzzleException $exception ) {
108 // We need to not block the presentation. TODO: solve logging.
109 return [];
110 }
111
112 $responseJson = $response->getBody()->getContents();
113 $responseDecoded = json_decode( $responseJson, true );
114
115 $lastDownload = new DateTimeImmutable( 'now', new \DateTimeZone( 'UTC' ) );
116 $flags = [
117 self::FLAG_SPLIT_ACTIVE => (bool) $responseDecoded['features']['split'],
118 self::FLAG_LAST_DOWNLOAD => $lastDownload->format( Helper::MYSQL_DATETIME_FORMAT ),
119 ];
120
121 update_option( self::FLAGS_OPTION_ID, $flags );
122 update_option( self::ERROR_COUNTER_OPTION_ID, 0 );
123
124 return $flags;
125 }
126
127 /**
128 * Gets or downloads flags.
129 *
130 * @return array
131 * @throws Exception From DateTimeImmutable.
132 */
133 private function getFlags(): array {
134 static $flags;
135
136 if ( ! isset( $flags ) ) {
137 $flags = get_option( self::FLAGS_OPTION_ID );
138 }
139
140 if ( true === get_option( self::DISABLED_DUE_ERRORS_OPTION_ID ) ) {
141 return $flags ? $flags : [];
142 }
143
144 $hasApiKey = ( null !== $this->optionsProvider->get_api_key() );
145 if ( false === $flags ) {
146 if ( ! $hasApiKey ) {
147 $flags = [];
148
149 return $flags;
150 }
151
152 $flags = $this->fetchFlags();
153
154 return $flags;
155 }
156
157 if ( $hasApiKey && isset( $flags[ self::FLAG_LAST_DOWNLOAD ] ) ) {
158 $now = new DateTimeImmutable( 'now', new \DateTimeZone( 'UTC' ) );
159 $lastUpdate = DateTimeImmutable::createFromFormat(
160 Helper::MYSQL_DATETIME_FORMAT,
161 $flags[ self::FLAG_LAST_DOWNLOAD ],
162 new \DateTimeZone( 'UTC' )
163 );
164 $ageHours = ( ( $now->getTimestamp() - $lastUpdate->getTimestamp() ) / HOUR_IN_SECONDS );
165 if ( $ageHours >= self::VALID_FOR_HOURS ) {
166 $oldFlags = $flags;
167 $flags = $this->fetchFlags();
168 }
169
170 if (
171 isset( $oldFlags, $flags[ self::FLAG_SPLIT_ACTIVE ] ) &&
172 false === $oldFlags[ self::FLAG_SPLIT_ACTIVE ] &&
173 true === $flags[ self::FLAG_SPLIT_ACTIVE ]
174 ) {
175 set_transient( self::TRANSIENT_SHOW_SPLIT_MESSAGE, 'yes' );
176 }
177 }
178
179 return $flags;
180 }
181
182 /**
183 * Tells if split is active.
184 *
185 * @return bool
186 * @throws Exception From DateTimeImmutable.
187 */
188 public function isSplitActive(): bool {
189 $flags = $this->getFlags();
190 if ( isset( $flags[ self::FLAG_SPLIT_ACTIVE ] ) ) {
191 return (bool) $flags[ self::FLAG_SPLIT_ACTIVE ];
192 }
193
194 return false;
195 }
196
197 /**
198 * Dismiss split notice.
199 *
200 * @return void
201 */
202 public function dismissSplitActivationNotice(): void {
203 delete_transient( self::TRANSIENT_SHOW_SPLIT_MESSAGE );
204 }
205
206 /**
207 * Determines whether to display split notice.
208 *
209 * @return bool
210 */
211 public function hasSplitActivationNotice(): bool {
212 return ( 'yes' === get_transient( self::TRANSIENT_SHOW_SPLIT_MESSAGE ) );
213 }
214
215 /**
216 * Print split activation notice.
217 *
218 * @return void
219 */
220 public function renderSplitActivationNotice(): void {
221 $dismissUrl = add_query_arg( [ Plugin::PARAM_PACKETERY_ACTION => self::ACTION_HIDE_SPLIT_MESSAGE ] );
222 $this->latteEngine->render(
223 PACKETERY_PLUGIN_DIR . '/template/admin-notice.latte',
224 [
225 'message' => [
226 'type' => 'warning',
227 'escape' => false,
228 'message' => sprintf(
229 // translators: 1: documentation link start 2: link end 3: dismiss link start 4: link end.
230 __(
231 'We have just enabled new options for setting Packeta pickup points. You can now choose a different price for Z-Box and pickup points in the carrier settings. More information can be found in %1$sthe plugin documentation%2$s. %3$sDismiss this message%4$s',
232 'packeta'
233 ),
234 ...Plugin::createLinkParts( 'https://github.com/Zasilkovna/WooCommerce/wiki', '_blank' ),
235 ...Plugin::createLinkParts( $dismissUrl, null, 'button button-primary' )
236 ),
237 ],
238 ]
239 );
240 }
241
242 }
243