PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / utilities / REST / UtilitiesController.php

UtilitiesController.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at modules/utilities/REST/UtilitiesController.php

221 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The only bridge between the cleanup engine and the Utilities tab.
4 *
5 * @package Templately
6 */
7
8 namespace Templately\Modules\Utilities\REST;
9
10 use Templately\API\API;
11 use Templately\Modules\Utilities\Cleanup\Activation;
12 use Templately\Modules\Utilities\Cleanup\Context;
13 use Templately\Modules\Utilities\Cleanup\RetentionPolicy;
14 use Templately\Modules\Utilities\Cleanup\RunJournal;
15 use Templately\Modules\Utilities\Cleanup\Scheduler;
16 use Templately\Modules\Utilities\Cleanup\Settings;
17 use Templately\Modules\Utilities\Cleanup\TaskRegistry;
18 use Templately\Modules\Utilities\Cleanup\UsageReport;
19 use WP_Error;
20 use WP_REST_Request;
21
22 /**
23 * Four routes, all administrator-only — READS INCLUDED. The storage report
24 * exposes absolute server paths and sizes, which is not information to hand to
25 * a subscriber.
26 *
27 * The destructive rails live HERE, on the server, not in the interface:
28 *
29 * - a run without an explicit `confirm` executes as a dry run;
30 * - an unactivated site executes as a dry run whatever `confirm` says;
31 * - a run while another holds a live claim is refused.
32 *
33 * A UI that forgot any of these could not cause a deletion the user did not ask
34 * for, which is the point of putting them at this layer.
35 */
36 class UtilitiesController extends API {
37
38 public function permission_check( WP_REST_Request $request ) {
39 return current_user_can( 'manage_options' );
40 }
41
42 public function register_routes() {
43 $this->get( 'utilities/storage', [ $this, 'get_storage' ] );
44 $this->get( 'utilities/tasks', [ $this, 'get_tasks' ] );
45 $this->post( 'utilities/cleanup', [ $this, 'run_cleanup' ] );
46 $this->post( 'utilities/settings', [ $this, 'update_settings' ] );
47 }
48
49 /**
50 * GET utilities/storage — everything the tab needs for a first paint, in one
51 * round trip.
52 */
53 public function get_storage() {
54 $continuation = $this->get_param( 'continuation', '', 'sanitize_text_field' );
55
56 return $this->envelope(
57 [
58 'usage' => UsageReport::build( $this->bool_param( 'refresh' ), $continuation ?: null ),
59 'activation' => Activation::state(),
60 'journal' => RunJournal::all(),
61 'settings' => Settings::get(),
62 'cleanup_disabled' => Settings::is_disabled(),
63 'running' => Scheduler::is_running(),
64 ]
65 );
66 }
67
68 /**
69 * GET utilities/tasks — descriptors plus a per-task estimate.
70 *
71 * The estimate comes from the same code path as the run, with dry-run set,
72 * so the number shown in the confirmation is the number the run produces.
73 */
74 public function get_tasks() {
75 $registry = TaskRegistry::get_instance();
76 $context = Context::create( Context::TRIGGER_MANUAL, RetentionPolicy::from_settings(), true );
77
78 $tasks = [];
79 foreach ( $registry->descriptors() as $id => $descriptor ) {
80 $estimate = $registry->estimate( $id, $context );
81
82 $tasks[] = array_merge(
83 $descriptor->to_array(),
84 [
85 'available' => ! $estimate->unavailable,
86 'estimate' => $estimate->to_array(),
87 ]
88 );
89 }
90
91 return $this->envelope( [ 'tasks' => $tasks ] );
92 }
93
94 /**
95 * POST utilities/cleanup — run one task, or every schedulable task.
96 */
97 public function run_cleanup() {
98 $task_id = $this->get_param( 'task_id', '', 'sanitize_key' );
99 $confirm = $this->bool_param( 'confirm' );
100
101 $registry = TaskRegistry::get_instance();
102
103 if ( $task_id ) {
104 if ( null === $registry->get_descriptor( $task_id ) ) {
105 return $this->error(
106 'unknown_task',
107 __( 'That cleanup does not exist.', 'templately' ),
108 'run_cleanup',
109 404
110 );
111 }
112 $ids = [ $task_id ];
113 } else {
114 // The batch action deliberately excludes manual-only tasks — those
115 // exist precisely because running them unattended is wrong.
116 $ids = $registry->schedulable_ids();
117 }
118
119 // RAIL: no explicit confirmation means this is a preview, not a run.
120 // Enforced here rather than in the UI, so a caller that forgets cannot
121 // delete anything.
122 $dry_run = ! $confirm;
123
124 $outcome = Scheduler::run( $ids, Context::TRIGGER_MANUAL, $dry_run );
125
126 if ( ! empty( $outcome['skipped'] ) ) {
127 return $this->error(
128 'cleanup_in_progress',
129 __( 'A cleanup is already running. Try again in a moment.', 'templately' ),
130 'run_cleanup',
131 409
132 );
133 }
134
135 $results = [];
136 foreach ( $outcome['results'] as $id => $result ) {
137 $results[ $id ] = $result->to_array();
138 }
139
140 return $this->envelope(
141 [
142 'dry_run' => $outcome['dry_run'],
143 // True when the site has never been activated: the caller asked
144 // for a real run and got a preview, and the UI must say so
145 // rather than reporting "0 bytes removed".
146 'activation_required' => Activation::forces_dry_run(),
147 'trigger' => Context::TRIGGER_MANUAL,
148 'results' => $results,
149 'journal_entry' => $outcome['entry'],
150 ]
151 );
152 }
153
154 /**
155 * POST utilities/settings — retention values and activation actions.
156 */
157 public function update_settings() {
158 $changes = [];
159
160 foreach ( [ 'age_days', 'ceiling_bytes', 'log_keep' ] as $field ) {
161 $value = $this->get_param( $field, null, 'absint' );
162 if ( null !== $value && '' !== $value ) {
163 $changes[ $field ] = (int) $value;
164 }
165 }
166
167 foreach ( [ 'ceiling_enabled', 'disabled' ] as $field ) {
168 $raw = $this->get_param( $field, null, null );
169 if ( null !== $raw ) {
170 $changes[ $field ] = rest_sanitize_boolean( $raw );
171 }
172 }
173
174 if ( $changes ) {
175 $updated = Settings::update( $changes );
176
177 // Out-of-range values are REFUSED with a reason rather than silently
178 // clamped — telling someone their "0 days" was accepted when it was
179 // quietly turned into 1 is worse than rejecting it.
180 if ( $updated instanceof WP_Error ) {
181 return $updated;
182 }
183
184 if ( array_key_exists( 'disabled', $changes ) ) {
185 // Turning cleanup off returns the site to unactivated: the
186 // backlog that builds while it is off is exactly what the gate
187 // exists for, so re-enabling must ask again.
188 if ( $changes['disabled'] ) {
189 Activation::deactivate();
190 }
191 Scheduler::ensure_scheduled();
192 }
193 }
194
195 if ( rest_sanitize_boolean( $this->get_param( 'activate', false, null ) ) ) {
196 Activation::activate( get_current_user_id() );
197 }
198
199 if ( rest_sanitize_boolean( $this->get_param( 'dismiss_prompt', false, null ) ) ) {
200 // Dismissal hides the notice; it is NOT consent, so the tab keeps
201 // showing the pending state.
202 Activation::dismiss_prompt();
203 }
204
205 return $this->envelope(
206 [
207 'settings' => Settings::get(),
208 'activation' => Activation::state(),
209 ]
210 );
211 }
212
213 /**
214 * `null` sanitizer plus a manual cast: there is no WordPress boolean
215 * sanitizer, and `sanitize_text_field` would turn `false` into `''`.
216 */
217 private function bool_param( string $name ): bool {
218 return rest_sanitize_boolean( $this->get_param( $name, false, null ) );
219 }
220 }
221