PluginProbe
Code Engine – PHP Snippets, AI Functions & Automation for WordPress / 0.2.9
Code Engine – PHP Snippets, AI Functions & Automation for WordPress v0.2.9
0.5.6 0.5.5 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.9 0.4.8 0.4.7 0.4.6 trunk 0.0.1 0.0.2 0.2.8 0.2.9 0.3.0 0.3.1 0.3.2 0.3.3 0.3.4 0.3.5 0.3.6 0.3.7 0.3.8 All 32 releases
← All changes | common/rest.php +119 -291 trunk0.2.9 View file →
@@ -1,308 +1,136 @@
1 1 <?php
2 2
3 -class MeowKit_MWCODE_Rest {
4 - private $namespace = 'meow-common/v1';
5 - public static $instance = null;
3 +class MeowCommon_Rest
4 +{
5 + private $namespace = "meow-common/v1";
6 + static public $instance = null;
6 7
7 - public static function init_once() {
8 - if ( !MeowKit_MWCODE_Rest::$instance ) {
9 - MeowKit_MWCODE_Rest::$instance = new self();
10 - }
11 - }
8 + static public function init_once() {
9 + if ( !MeowCommon_Rest::$instance ) {
10 + MeowCommon_Rest::$instance = new self();
11 + }
12 + }
12 13
13 - private function __construct() {
14 - add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
15 - }
14 + private function __construct() {
15 + add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
16 + }
16 17
17 - /**
18 - * Capability gate for plugin admin endpoints. Defaults to manage_options but
19 - * honours a per-plugin filter so a site can grant access to other roles
20 - * without re-implementing every permission_callback. The filter name is
21 - * derived from the class name (`MeowKit_<PREFIX>_Rest` => `<prefix>_allow_setup`),
22 - * so each plugin gets its own filter automatically after Nekofy substitution.
23 - */
24 - private function can_setup() {
25 - static $filter = null;
26 - if ( $filter === null ) {
27 - $parts = explode( '_', __CLASS__ );
28 - $prefix = isset( $parts[1] ) ? strtolower( $parts[1] ) : '';
29 - $filter = $prefix !== '' ? $prefix . '_allow_setup' : '';
30 - }
31 - $default = current_user_can( 'manage_options' );
32 - return $filter !== '' ? apply_filters( $filter, $default ) : $default;
33 - }
18 + function rest_api_init() {
19 + if ( !current_user_can( 'manage_options' ) ) {
20 + return;
21 + }
22 + register_rest_route( $this->namespace, '/empty_request/', [
23 + 'methods' => 'POST',
24 + 'permission_callback' => function () {
25 + return current_user_can( 'manage_options' );
26 + },
27 + 'callback' => [ $this, 'empty_request' ]
28 + ]);
29 + register_rest_route( $this->namespace, '/file_operation/', [
30 + 'methods' => 'POST',
31 + 'permission_callback' => function () {
32 + return current_user_can( 'manage_options' );
33 + },
34 + 'callback' => [ $this, 'file_operation' ]
35 + ]);
36 + register_rest_route( $this->namespace, '/sql_request/', [
37 + 'methods' => 'POST',
38 + 'permission_callback' => function () {
39 + return current_user_can( 'manage_options' );
40 + },
41 + 'callback' => [ $this, 'sql_request' ]
42 + ]);
43 + register_rest_route( $this->namespace, '/error_logs/', [
44 + 'methods' => 'POST',
45 + 'permission_callback' => function () {
46 + $ok = current_user_can( 'manage_options' );
47 + return $ok;
48 + },
49 + 'callback' => [ $this, 'rest_error_logs' ]
50 + ]);
51 + register_rest_route( $this->namespace, '/all_settings/', [
52 + 'methods' => 'POST',
53 + 'permission_callback' => function () {
54 + $ok = current_user_can( 'manage_options' );
55 + return $ok;
56 + },
57 + 'callback' => [ $this, 'rest_all_settings' ]
58 + ]);
59 + register_rest_route( $this->namespace, '/update_option/', [
60 + 'methods' => 'POST',
61 + 'permission_callback' => function () {
62 + $ok = current_user_can( 'manage_options' );
63 + return $ok;
64 + },
65 + 'callback' => [ $this, 'rest_update_option' ]
66 + ]);
67 + }
34 68
35 - public function rest_api_init() {
36 - if ( !$this->can_setup() ) {
37 - return;
38 - }
39 - $permission = function () {
40 - return $this->can_setup();
41 - };
42 - register_rest_route( $this->namespace, '/empty_request/', [
43 - 'methods' => 'POST',
44 - 'permission_callback' => $permission,
45 - 'callback' => [ $this, 'empty_request' ]
46 - ] );
47 - register_rest_route( $this->namespace, '/file_operation/', [
48 - 'methods' => 'POST',
49 - 'permission_callback' => $permission,
50 - 'callback' => [ $this, 'file_operation' ]
51 - ] );
52 - register_rest_route( $this->namespace, '/sql_request/', [
53 - 'methods' => 'POST',
54 - 'permission_callback' => $permission,
55 - 'callback' => [ $this, 'sql_request' ]
56 - ] );
57 - register_rest_route( $this->namespace, '/error_logs/', [
58 - 'methods' => 'POST',
59 - 'permission_callback' => $permission,
60 - 'callback' => [ $this, 'rest_error_logs' ]
61 - ] );
62 - register_rest_route( $this->namespace, '/all_settings/', [
63 - 'methods' => 'POST',
64 - 'permission_callback' => $permission,
65 - 'callback' => [ $this, 'rest_all_settings' ]
66 - ] );
67 - register_rest_route( $this->namespace, '/update_option/', [
68 - 'methods' => 'POST',
69 - 'permission_callback' => $permission,
70 - 'callback' => [ $this, 'rest_update_option' ]
71 - ] );
72 - register_rest_route( $this->namespace, '/installed_plugins/', [
73 - 'methods' => 'POST',
74 - 'permission_callback' => $permission,
75 - 'callback' => [ $this, 'rest_installed_plugins' ]
76 - ] );
77 - // The analysis needs PHP for one reason: $mwai is a PHP global, so the AI
78 - // call cannot be made from the dashboard's JavaScript. Everything it
79 - // analyses is gathered in the browser and posted here.
80 - register_rest_route( $this->namespace, '/analysis_status/', [
81 - 'methods' => 'POST',
82 - 'permission_callback' => $permission,
83 - 'callback' => [ $this, 'rest_analysis_status' ]
84 - ] );
85 - register_rest_route( $this->namespace, '/analysis_run/', [
86 - 'methods' => 'POST',
87 - 'permission_callback' => $permission,
88 - 'callback' => [ $this, 'rest_analysis_run' ]
89 - ] );
90 - register_rest_route( $this->namespace, '/analysis_forget/', [
91 - 'methods' => 'POST',
92 - 'permission_callback' => $permission,
93 - 'callback' => [ $this, 'rest_analysis_forget' ]
94 - ] );
95 - }
69 + function file_rand( $filesize ) {
70 + $tmp_file = tmpfile();
71 + fseek( $tmp_file, $filesize - 1, SEEK_CUR );
72 + fwrite( $tmp_file, 'a');
73 + fclose( $tmp_file );
74 + }
96 75
97 - /**
98 - * Throw away the stored analysis and the consent that went with it.
99 - *
100 - * The analysis writes a verdict about this site into the options table and
101 - * keeps it. That is what makes it survive a reload, but it also means a
102 - * description of your server's weaknesses, written by a third-party model,
103 - * sits there indefinitely with nothing to remove it. Somebody should be able
104 - * to take it back, and clearing the consent means the panel explaining what
105 - * gets sent is shown again before anything is sent a second time.
106 - */
107 - public function rest_analysis_forget() {
108 - delete_option( 'meowapps_analysis_last' );
109 - delete_option( 'meowapps_analysis_consented' );
76 + function empty_request() {
110 77 return new WP_REST_Response( [ 'success' => true ], 200 );
111 - }
112 -
113 - /**
114 - * Whether an analysis can be run, and the last one if there is one.
115 - *
116 - * Three states matter and they are not the same: AI Engine absent, AI Engine
117 - * present but with no API key configured, and ready. Telling the second from
118 - * the first is what lets the dashboard say "finish setting it up" rather than
119 - * "install this", which would be wrong and mildly insulting.
120 - */
121 - public function rest_analysis_status() {
122 - global $mwai;
123 - $installed = !empty( $mwai );
124 - $ready = $installed && method_exists( $mwai, 'hasAI' ) && $mwai->hasAI();
125 - return new WP_REST_Response( [ 'success' => true, 'data' => [
126 - 'installed' => $installed,
127 - 'ready' => $ready,
128 - 'consented' => (bool) get_option( 'meowapps_analysis_consented', false ),
129 - 'last' => get_option( 'meowapps_analysis_last', null ),
130 - ] ], 200 );
131 - }
132 -
133 - public function rest_analysis_run( $request ) {
134 - global $mwai;
135 - if ( empty( $mwai ) || !method_exists( $mwai, 'simpleJsonQuery' ) ) {
136 - return new WP_REST_Response( [ 'success' => false,
137 - 'message' => 'AI Engine is not available on this site.' ], 200 );
138 - }
139 - if ( !method_exists( $mwai, 'hasAI' ) || !$mwai->hasAI() ) {
140 - return new WP_REST_Response( [ 'success' => false,
141 - 'message' => 'AI Engine has no AI environment configured yet.' ], 200 );
142 - }
143 -
144 - $params = $request->get_json_params();
145 - $facts = isset( $params['facts'] ) ? $params['facts'] : [];
146 -
147 - // Consent is recorded when a run is actually asked for, so the panel is
148 - // shown once rather than on every visit.
149 - update_option( 'meowapps_analysis_consented', '1' );
150 -
151 - try {
152 - $reply = $mwai->simpleJsonQuery( $this->analysis_prompt( $facts ) );
153 - }
154 - catch ( Exception $e ) {
155 - return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 200 );
156 - }
157 -
158 - $verdict = is_string( $reply ) ? json_decode( $reply, true ) : $reply;
159 - if ( empty( $verdict ) || !is_array( $verdict ) ) {
160 - return new WP_REST_Response( [ 'success' => false,
161 - 'message' => 'The AI reply could not be read as JSON.' ], 200 );
162 - }
163 -
164 - $verdict['ranAt'] = current_time( 'mysql' );
165 - update_option( 'meowapps_analysis_last', $verdict );
166 -
167 - return new WP_REST_Response( [ 'success' => true, 'data' => $verdict ], 200 );
168 - }
169 -
170 - /**
171 - * Written for the person who owns the site, not for a developer: somebody who
172 - * installed a plugin, not somebody who knows what max_execution_time is.
173 - *
174 - * Meow Apps plugins may be named as a remedy, but only where one genuinely
175 - * fixes the finding. An analysis that recommends its own author's plugins for
176 - * everything is an advert, and would be read as one.
177 - */
178 - private function analysis_prompt( $facts ) {
179 - $json = wp_json_encode( $facts, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
180 - return "You are reviewing a WordPress site's health for the person who owns it.\n"
181 - . "They are not a developer: explain what something means and why it matters before saying "
182 - . "what to do, and never assume they know what a PHP directive is.\n\n"
183 - . "Here is what was measured on their site:\n\n$json\n\n"
184 - . "Reply with JSON in exactly this shape:\n"
185 - . "{\n"
186 - . ' "areas": [ { "area": "Speed|Environment|Errors", "rating": 1-5, '
187 - . '"label": "a two or three word verdict", "summary": "one plain sentence" } ],' . "\n"
188 - . ' "findings": [ { "area": "Speed|Environment|Errors", "severity": "high|medium|low", '
189 - . '"title": "the recommendation itself, plain, under 60 characters", '
190 - . '"detail": "at most two short sentences on what it means and why it matters", '
191 - . '"action": "one sentence saying what to do", '
192 - . '"plugin": "meow plugin slug or null" } ],' . "\n"
193 - . ' "conclusion": "two short sentences tying it together"' . "\n"
194 - . "}\n\n"
195 - . "Rules:\n"
196 - . "- Rate each of the three areas from 1 (bad) to 5 (good). Do not invent an overall score.\n"
197 - . "- Only report findings genuinely worth acting on. An empty findings list is a perfectly "
198 - . "good answer for a healthy site. Do not manufacture problems.\n"
199 - . "- Order findings by how much they matter, worst first.\n"
200 - . "- Be brief. The reader sees the titles first and opens the ones they care about, so a "
201 - . "title has to work on its own and the detail must not repeat it. No preamble, no restating "
202 - . "the question, no filler.\n"
203 - . "- Set \"plugin\" to one of these Meow Apps slugs ONLY when that plugin genuinely fixes the "
204 - . "finding, otherwise null: ai-engine, code-engine, contact-form-block, database-cleaner, "
205 - . "media-cleaner, media-file-renamer, meow-gallery, meow-lightbox, meow-mailer, seo-engine, "
206 - . "social-engine, wp-retina-2x, wplr-sync.\n"
207 - . "- If an error in the log comes from a specific plugin, say which one.\n"
208 - . "- Write in the language of this site: " . get_bloginfo( 'language' ) . ".";
209 - }
210 -
211 - public function file_rand( $filesize ) {
212 - // Write the benchmark file inside a dedicated subfolder of the uploads
213 - // directory (created on demand), then remove it. wp.org forbids writing to
214 - // the plugin folder or the uploads root, only a sanctioned subfolder.
215 - $upload = wp_upload_dir();
216 - if ( !empty( $upload['error'] ) || empty( $upload['basedir'] ) ) { return; }
217 - $dir = trailingslashit( $upload['basedir'] ) . 'meowapps';
218 - if ( !wp_mkdir_p( $dir ) ) { return; }
219 - $path = trailingslashit( $dir ) . 'speedtest-' . wp_generate_password( 12, false ) . '.tmp';
220 - $fh = @fopen( $path, 'wb' );
221 - if ( $fh === false ) { return; }
222 - fseek( $fh, $filesize - 1, SEEK_CUR );
223 - fwrite( $fh, 'a' );
224 - fclose( $fh );
225 - @unlink( $path );
226 - }
227 -
228 - public function empty_request() {
78 + }
79 +
80 + function file_operation() {
81 + $this->file_rand( 1024 * 10 );
229 82 return new WP_REST_Response( [ 'success' => true ], 200 );
230 - }
231 -
232 - public function file_operation() {
233 - $this->file_rand( 1024 * 10 );
234 - return new WP_REST_Response( [ 'success' => true ], 200 );
235 - }
236 -
237 - public function sql_request() {
238 - global $wpdb;
239 - $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
83 + }
84 +
85 + function sql_request() {
86 + global $wpdb;
87 + $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" );
240 88 return new WP_REST_Response( [ 'success' => true, 'data' => $count ], 200 );
241 - }
89 + }
242 90
243 - // List all the options with their default values.
244 - public function list_options() {
245 - return [
246 - 'meowapps_hide_meowapps' => false,
247 - 'force_sslverify' => false
248 - ];
249 - }
91 + // List all the options with their default values.
92 + function list_options() {
93 + return array(
94 + 'meowapps_hide_meowapps' => false,
95 + 'force_sslverify' => false
96 + );
97 + }
250 98
251 - public function get_all_options() {
252 - $options = $this->list_options();
253 - $current_options = [];
254 - foreach ( $options as $option => $default ) {
255 - $current_options[$option] = get_option( $option, $default );
256 - }
257 - return $current_options;
258 - }
99 + function get_all_options() {
100 + $options = $this->list_options();
101 + $current_options = array();
102 + foreach ( $options as $option => $default ) {
103 + $current_options[$option] = get_option( $option, $default );
104 + }
105 + return $current_options;
106 + }
259 107
260 - public function rest_all_settings() {
261 - return new WP_REST_Response( [ 'success' => true, 'data' => $this->get_all_options() ], 200 );
262 - }
108 + function rest_all_settings() {
109 + return new WP_REST_Response( [ 'success' => true, 'data' => $this->get_all_options() ], 200 );
110 + }
263 111
264 - public function rest_installed_plugins() {
265 - if ( !function_exists( 'get_plugins' ) ) {
266 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
267 - }
268 - $all_plugins = get_plugins();
269 - $result = [];
270 - foreach ( $all_plugins as $plugin_file => $plugin_data ) {
271 - // Plugin file looks like "ai-engine/ai-engine.php", the slug is the
272 - // first path segment. Some plugins live at the root (single file),
273 - // those we just skip; we only care about Meow Apps directories anyway.
274 - $parts = explode( '/', $plugin_file );
275 - if ( count( $parts ) < 2 ) {
276 - continue;
277 - }
278 - $slug = $parts[0];
279 - $result[ $slug ] = is_plugin_active( $plugin_file ) ? 'active' : 'inactive';
280 - }
281 - return new WP_REST_Response( [ 'success' => true, 'data' => $result ], 200 );
282 - }
112 + function rest_update_option( $request ) {
113 + $params = $request->get_json_params();
114 + try {
115 + $name = $params['name'];
116 + $options = $this->list_options();
117 + if ( !array_key_exists( $name, $options ) ) {
118 + return new WP_REST_Response([ 'success' => false, 'message' => 'This option does not exist.' ], 200 );
119 + }
120 + $value = is_bool( $params['value'] ) ? ( $params['value'] ? '1' : '' ) : $params['value'];
121 + $success = update_option( $name, $value );
122 + if ( !$success ) {
123 + return new WP_REST_Response( [ 'success' => false, 'message' => 'Could not update option.' ], 200 );
124 + }
125 + return new WP_REST_Response( [ 'success' => true, 'data' => $value ], 200 );
126 + }
127 + catch (Exception $e) {
128 + return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
129 + }
130 + }
283 131
284 - public function rest_update_option( $request ) {
285 - $params = $request->get_json_params();
286 - try {
287 - $name = $params['name'];
288 - $options = $this->list_options();
289 - if ( !array_key_exists( $name, $options ) ) {
290 - return new WP_REST_Response( [ 'success' => false, 'message' => 'This option does not exist.' ], 200 );
291 - }
292 - $value = is_bool( $params['value'] ) ? ( $params['value'] ? '1' : '' ) : $params['value'];
293 - $success = update_option( $name, $value );
294 - if ( !$success ) {
295 - return new WP_REST_Response( [ 'success' => false, 'message' => 'Could not update option.' ], 200 );
296 - }
297 - return new WP_REST_Response( [ 'success' => true, 'data' => $value ], 200 );
298 - }
299 - catch ( Exception $e ) {
300 - return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
301 - }
302 - }
303 -
304 - public function rest_error_logs( $request ) {
305 - return new WP_REST_Response( [ 'success' => true, 'data' => MeowKit_MWCODE_Helpers::php_error_logs() ], 200 );
306 - }
132 + function rest_error_logs( $request ) {
133 + return new WP_REST_Response( [ 'success' => true, 'data' => MeowCommon_Helpers::php_error_logs() ], 200 );
134 + }
307 135
308 136 }