PluginProbe
Tiered Pricing Table for WooCommerce / 8.1.0
Tiered Pricing Table for WooCommerce v8.1.0
8.1.0 8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 All 92 releases
tier-pricing-table / src / Addons / GlobalTieredPricing / CPT / StatusBox.php

StatusBox.php in Tiered Pricing Table for WooCommerce 8.1.0, at src/Addons/GlobalTieredPricing/CPT/StatusBox.php

425 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\GlobalTieredPricing\CPT;
2
3 use TierPricingTable\Addons\GlobalTieredPricing\GlobalPricingRule;
4 use TierPricingTable\Addons\GlobalTieredPricing\RuleOverlaps;
5 use TierPricingTable\Addons\Tools\PricingTest\PricingTester;
6 use WP_Post;
7
8 /**
9 * The plugin's own sidebar box in place of WordPress's Publish box: the rule's status (active, suspended,
10 * scheduled, expired, not published), the suspend switch, the priority, the date window, the rules it
11 * overlaps with, the Pricing test link, and the Update / Move to Trash actions.
12 *
13 * The classic editor's script and the save pipeline look for the hidden status inputs, the "#submitpost"
14 * wrapper, the "#publish" button and the "#major-publishing-actions" bar; they keep their ids and names.
15 */
16 class StatusBox {
17
18 /**
19 * Posted with the box, so other save paths (WP-CLI, bulk edits) keep the stored values.
20 */
21 const MARKER = 'tpt_status_box';
22
23 public function __construct() {
24 add_action( 'add_meta_boxes_' . GlobalTieredPricingCPT::SLUG, array( $this, 'register' ) );
25 }
26
27 public function register() {
28 remove_meta_box( 'submitdiv', GlobalTieredPricingCPT::SLUG, 'side' );
29
30 add_meta_box( 'tpt-rule-status-box', __( 'Rule status', 'tier-pricing-table' ), array( $this, 'render' ), GlobalTieredPricingCPT::SLUG, 'side', 'high' );
31 }
32
33 public function render( WP_Post $post ) {
34 $rule = GlobalPricingRule::build( $post->ID );
35 $isNew = 'auto-draft' === $post->post_status;
36 $published = 'publish' === $post->post_status;
37 $status = $this->status( $post, $rule );
38 // only a rule in force can be overridden, and only by rules in force (skipped ones never apply)
39 $overriding = ( $isNew || ! $published || ! $rule->canBeOverridden() )
40 ? array()
41 : ( new RuleOverlaps() )->findOverriding( $rule, GlobalTieredPricingCPT::getGlobalRules( true, false ) );
42 $format = get_option( 'date_format' );
43 ?>
44 <div class="submitbox" id="submitpost">
45 <div style="display: none;"><?php submit_button( __( 'Save', 'tier-pricing-table' ), '', 'save' ); ?></div>
46 <input type="hidden" name="hidden_post_status" id="hidden_post_status" value="<?php echo esc_attr( $isNew ? 'draft' : $post->post_status ); ?>">
47 <input type="hidden" name="post_status" id="post_status" value="<?php echo esc_attr( $isNew ? 'draft' : $post->post_status ); ?>">
48 <input type="hidden" name="<?php echo esc_attr( self::MARKER ); ?>" value="1">
49
50 <div class="tpt-rule-box">
51 <div class="tpt-rule-box__state tpt-rule-box__state--<?php echo esc_attr( $status['key'] ); ?>">
52 <span class="dashicons <?php echo esc_attr( $status['icon'] ); ?>"></span>
53 <div class="tpt-rule-box__text">
54 <strong><?php echo esc_html( $status['label'] ); ?></strong>
55 <div class="tpt-rule-box__detail"><?php echo esc_html( $status['detail'] ); ?></div>
56 </div>
57 <?php if ( $published ) : ?>
58 <?php // a link-styled submit: saves the form and flips the state in one click ?>
59 <?php if ( $rule->isSuspended() ) : ?>
60 <button type="submit" name="tpt_suspend_action" value="reactivate" class="button-link tpt-rule-box__toggle"><?php esc_html_e( 'Reactivate', 'tier-pricing-table' ); ?></button>
61 <?php else : ?>
62 <button type="submit" name="tpt_suspend_action" value="suspend" class="button-link tpt-rule-box__toggle"><?php esc_html_e( 'Suspend', 'tier-pricing-table' ); ?></button>
63 <?php endif; ?>
64 <?php endif; ?>
65 </div>
66
67 <details class="tpt-rule-box__section" <?php echo GlobalPricingRule::DEFAULT_PRIORITY !== $rule->getPriority() ? 'open' : ''; ?>>
68 <summary>
69 <span><?php esc_html_e( 'Priority', 'tier-pricing-table' ); ?></span>
70 <span class="tpt-rule-box__meta"><?php echo esc_html( number_format_i18n( $rule->getPriority() ) ); ?></span>
71 </summary>
72 <div class="tpt-rule-box__body">
73 <input type="number" id="tpt_priority" name="tpt_priority" min="0" step="1" value="<?php echo esc_attr( $rule->getPriority() ); ?>" aria-label="<?php esc_attr_e( 'Priority', 'tier-pricing-table' ); ?>">
74 <p class="description"><?php esc_html_e( 'The rule with the lowest number is applied first. If two rules have the same number, the newer rule is applied first.', 'tier-pricing-table' ); ?></p>
75 </div>
76 </details>
77
78 <details class="tpt-rule-box__section" <?php echo $rule->hasSchedule() ? 'open' : ''; ?>>
79 <summary>
80 <span><?php esc_html_e( 'Schedule', 'tier-pricing-table' ); ?></span>
81 <span class="tpt-rule-box__meta"><?php echo esc_html( self::scheduleSummary( $rule ) ); ?></span>
82 </summary>
83 <div class="tpt-rule-box__body">
84 <label for="tpt_start_date"><?php esc_html_e( 'Start date', 'tier-pricing-table' ); ?></label>
85 <input type="date" id="tpt_start_date" name="tpt_start_date" value="<?php echo esc_attr( (string) $rule->getStartDate() ); ?>">
86 <label for="tpt_end_date"><?php esc_html_e( 'End date', 'tier-pricing-table' ); ?></label>
87 <input type="date" id="tpt_end_date" name="tpt_end_date" value="<?php echo esc_attr( (string) $rule->getEndDate() ); ?>">
88 <p class="description">
89 <?php
90 printf(
91 /* translators: %s: timezone, e.g. "Europe/Kyiv" */
92 esc_html__( 'Whole days in the site timezone (%s). Leave a date empty for no limit.', 'tier-pricing-table' ),
93 esc_html( wp_timezone_string() )
94 );
95 ?>
96 </p>
97 </div>
98 </details>
99
100 <?php if ( $overriding ) : // only when another rule takes precedence somewhere ?>
101 <details class="tpt-rule-box__section" open>
102 <summary>
103 <span><?php esc_html_e( 'Overridden by', 'tier-pricing-table' ); ?></span>
104 <span class="tpt-rule-box__meta tpt-rule-box__meta--warn"><?php echo esc_html( number_format_i18n( count( $overriding ) ) ); ?></span>
105 </summary>
106 <div class="tpt-rule-box__body">
107 <ul class="tpt-rule-box__overlaps">
108 <?php foreach ( $overriding as $other ) : ?>
109 <li>
110 <a href="<?php echo esc_url( get_edit_post_link( $other->getId(), 'raw' ) ); ?>" title="<?php echo esc_attr( $other->getTitle() ); ?>"><?php echo esc_html( $other->getTitle() ); ?></a>
111 <span class="tpt-rule-box__meta">
112 <?php
113 /* translators: %d: priority number */
114 echo esc_html( sprintf( __( 'priority %d', 'tier-pricing-table' ), $other->getPriority() ) );
115 if ( $other->hasSchedule() ) {
116 echo ', ' . esc_html( self::scheduleSummary( $other ) );
117 }
118 ?>
119 </span>
120 </li>
121 <?php endforeach; ?>
122 </ul>
123 <p class="description"><?php esc_html_e( 'These rules have a higher priority. When one of them matches the same product and customer, that rule is applied and the rule you are editing is skipped.', 'tier-pricing-table' ); ?></p>
124 </div>
125 </details>
126 <?php endif; ?>
127
128 <?php // no test link for a skipped rule: the test could only say it changes nothing ?>
129 <?php if ( ! $isNew && PricingTester::isAvailable() && ! ( $published && 'skipped' === $rule->getEffectiveStatus() ) ) : ?>
130 <p class="tpt-rule-box__test">
131 <span class="dashicons dashicons-search"></span>
132 <a href="<?php echo esc_url( PricingTester::getUrl( $post->ID ) ); ?>" target="_blank" rel="noopener"><?php esc_html_e( 'Test this rule', 'tier-pricing-table' ); ?></a>
133 <span class="description"><?php esc_html_e( 'What a customer pays for a product, and which rule wins.', 'tier-pricing-table' ); ?></span>
134 </p>
135 <?php endif; ?>
136 </div>
137
138 <div id="major-publishing-actions">
139 <div id="delete-action">
140 <?php if ( ! $isNew && current_user_can( 'delete_post', $post->ID ) ) : ?>
141 <a class="submitdelete deletion" href="<?php echo esc_url( get_delete_post_link( $post->ID ) ); ?>">
142 <?php echo EMPTY_TRASH_DAYS ? esc_html__( 'Move to Trash', 'tier-pricing-table' ) : esc_html__( 'Delete permanently', 'tier-pricing-table' ); ?>
143 </a>
144 <?php endif; ?>
145 </div>
146 <div id="publishing-action">
147 <span class="spinner"></span>
148 <?php if ( $published ) : ?>
149 <input name="original_publish" type="hidden" id="original_publish" value="<?php esc_attr_e( 'Update', 'tier-pricing-table' ); ?>">
150 <?php submit_button( __( 'Update', 'tier-pricing-table' ), 'primary large', 'save', false, array( 'id' => 'publish' ) ); ?>
151 <?php else : ?>
152 <input name="original_publish" type="hidden" id="original_publish" value="<?php esc_attr_e( 'Publish', 'tier-pricing-table' ); ?>">
153 <?php submit_button( __( 'Publish', 'tier-pricing-table' ), 'primary large', 'publish', false ); ?>
154 <?php endif; ?>
155 </div>
156 <div class="clear"></div>
157 </div>
158 </div>
159 <style>
160 #tpt-rule-status-box .inside {
161 margin: 0;
162 padding: 0;
163 }
164
165 .tpt-rule-box {
166 padding: 12px;
167 }
168
169 .tpt-rule-box__state {
170 display: flex;
171 gap: 8px;
172 align-items: flex-start;
173 margin-bottom: 12px;
174 padding: 10px 12px;
175 border-radius: 4px;
176 line-height: 1.4;
177 }
178
179 .tpt-rule-box__state .dashicons {
180 flex: 0 0 auto;
181 font-size: 20px;
182 width: 20px;
183 height: 20px;
184 }
185
186 .tpt-rule-box__state--active {
187 background: #edfaef;
188 color: #007017;
189 }
190
191 .tpt-rule-box__state--scheduled {
192 background: #f0f6fc;
193 color: #2271b1;
194 }
195
196 .tpt-rule-box__state--suspended,
197 .tpt-rule-box__state--skipped {
198 background: #fcf9e8;
199 color: #996800;
200 }
201
202 .tpt-rule-box__state--expired,
203 .tpt-rule-box__state--draft {
204 background: #f0f0f1;
205 color: #50575e;
206 }
207
208 .tpt-rule-box__detail {
209 margin-top: 2px;
210 font-size: 12px;
211 }
212
213 .tpt-rule-box__text {
214 flex: 1 1 auto;
215 min-width: 0;
216 }
217
218 .tpt-rule-box__toggle {
219 flex: 0 0 auto;
220 align-self: flex-start;
221 margin-top: 1px;
222 font-size: 12px;
223 line-height: 1.4;
224 text-decoration: underline;
225 }
226
227 .tpt-rule-box__body label {
228 display: block;
229 margin: 0 0 4px;
230 font-weight: 600;
231 }
232
233 .tpt-rule-box__body label + label,
234 .tpt-rule-box__body input + label {
235 margin-top: 8px;
236 }
237
238 .tpt-rule-box__body input[type="date"],
239 .tpt-rule-box__body input[type="number"] {
240 width: 100%;
241 }
242
243 .tpt-rule-box .description {
244 margin: 6px 0 0;
245 font-size: 12px;
246 color: #646970;
247 }
248
249 .tpt-rule-box__section {
250 margin-top: 12px;
251 padding-top: 10px;
252 border-top: 1px solid #dcdcde;
253 }
254
255 .tpt-rule-box__section summary {
256 display: flex;
257 align-items: center;
258 gap: 8px;
259 cursor: pointer;
260 font-weight: 600;
261 list-style: none;
262 }
263
264 .tpt-rule-box__section summary::-webkit-details-marker {
265 display: none;
266 }
267
268 .tpt-rule-box__section summary > span:first-child {
269 flex: 1 1 auto;
270 }
271
272 .tpt-rule-box__section summary::after {
273 content: "\f140";
274 font-family: dashicons;
275 font-size: 18px;
276 color: #787c82;
277 transition: transform .15s;
278 }
279
280 .tpt-rule-box__section[open] summary::after {
281 transform: rotate(180deg);
282 }
283
284 .tpt-rule-box__meta {
285 font-weight: 400;
286 font-size: 12px;
287 color: #646970;
288 }
289
290 .tpt-rule-box__body {
291 padding: 10px 0 2px;
292 }
293
294 .tpt-rule-box__overlaps {
295 margin: 0;
296 }
297
298 .tpt-rule-box__overlaps li {
299 margin: 0 0 8px;
300 padding-left: 10px;
301 border-left: 3px solid #dba617;
302 line-height: 1.5;
303 }
304
305 .tpt-rule-box__overlaps a {
306 display: block;
307 overflow: hidden;
308 text-overflow: ellipsis;
309 white-space: nowrap;
310 font-weight: 600;
311 }
312
313 .tpt-rule-box__meta--warn {
314 color: #996800;
315 font-weight: 600;
316 }
317
318 .tpt-rule-box__test {
319 display: flex;
320 flex-wrap: wrap;
321 gap: 2px 6px;
322 align-items: center;
323 margin: 12px 0 0;
324 padding-top: 12px;
325 border-top: 1px solid #dcdcde;
326 }
327
328 .tpt-rule-box__test .dashicons {
329 color: #8c8f94;
330 }
331
332 .tpt-rule-box__test .description {
333 flex: 1 1 100%;
334 margin: 0;
335 }
336 </style>
337 <?php
338 }
339
340 /**
341 * @return array{key: string, label: string, detail: string, icon: string}
342 */
343 protected function status( WP_Post $post, GlobalPricingRule $rule ): array {
344 $format = get_option( 'date_format' );
345
346 if ( 'publish' !== $post->post_status ) {
347 return array(
348 'key' => 'draft',
349 'label' => __( 'Not published', 'tier-pricing-table' ),
350 'detail' => __( 'Publish the rule to put it in force.', 'tier-pricing-table' ),
351 'icon' => 'dashicons-edit',
352 );
353 }
354
355 switch ( $rule->getEffectiveStatus() ) {
356 case 'suspended':
357 return array(
358 'key' => 'suspended',
359 'label' => __( 'Suspended', 'tier-pricing-table' ),
360 'detail' => __( 'The rule is saved but does not apply until you reactivate it.', 'tier-pricing-table' ),
361 'icon' => 'dashicons-controls-pause',
362 );
363 case 'expired':
364 return array(
365 'key' => 'expired',
366 'label' => __( 'Expired', 'tier-pricing-table' ),
367 /* translators: %s: date */
368 'detail' => sprintf( __( 'Ended %s. Clear or move the end date to use the rule again.', 'tier-pricing-table' ), wp_date( $format, $rule->getEndTimestamp() ) ),
369 'icon' => 'dashicons-dismiss',
370 );
371 case 'skipped':
372 return array(
373 'key' => 'skipped',
374 'label' => __( 'Skipped', 'tier-pricing-table' ),
375 'detail' => __( 'The rule changes neither prices nor quantity limits, so the storefront skips it. Set a price, tiers or a limit on the tabs on the left.', 'tier-pricing-table' ),
376 'icon' => 'dashicons-warning',
377 );
378 case 'scheduled':
379 return array(
380 'key' => 'scheduled',
381 'label' => __( 'Scheduled', 'tier-pricing-table' ),
382 /* translators: %s: date */
383 'detail' => sprintf( __( 'Starts %s.', 'tier-pricing-table' ), wp_date( $format, $rule->getStartTimestamp() ) ),
384 'icon' => 'dashicons-clock',
385 );
386 }
387
388 return array(
389 'key' => 'active',
390 'label' => __( 'Active', 'tier-pricing-table' ),
391 'detail' => $rule->getEndDate()
392 /* translators: %s: date */
393 ? sprintf( __( 'Applies now, until %s.', 'tier-pricing-table' ), wp_date( $format, $rule->getEndTimestamp() ) )
394 : __( 'Applies now.', 'tier-pricing-table' ),
395 'icon' => 'dashicons-yes-alt',
396 );
397 }
398
399 /**
400 * "Always", "From …", "Until …" or "… to …".
401 */
402 public static function scheduleSummary( GlobalPricingRule $rule ): string {
403 if ( ! $rule->hasSchedule() ) {
404 return __( 'Always', 'tier-pricing-table' );
405 }
406
407 $format = get_option( 'date_format' );
408 $from = $rule->getStartDate() ? wp_date( $format, $rule->getStartTimestamp() ) : null;
409 $to = $rule->getEndDate() ? wp_date( $format, $rule->getEndTimestamp() ) : null;
410
411 if ( $from && $to ) {
412 /* translators: 1: start date, 2: end date */
413 return sprintf( __( '%1$s to %2$s', 'tier-pricing-table' ), $from, $to );
414 }
415
416 if ( $from ) {
417 /* translators: %s: start date */
418 return sprintf( __( 'From %s', 'tier-pricing-table' ), $from );
419 }
420
421 /* translators: %s: end date */
422 return sprintf( __( 'Until %s', 'tier-pricing-table' ), $to );
423 }
424 }
425