Changeset 3484579
- Timestamp:
- 03/17/2026 09:02:21 AM (11 days ago)
- Location:
- semust
- Files:
-
- 2 added
- 10 edited
- 1 copied
-
tags/1.2.0 (copied) (copied from semust/trunk)
-
tags/1.2.0/includes/class-semust-api.php (modified) (7 diffs)
-
tags/1.2.0/includes/class-semust-auth.php (modified) (2 diffs)
-
tags/1.2.0/includes/class-semust-core.php (modified) (2 diffs)
-
tags/1.2.0/includes/class-semust-media.php (added)
-
tags/1.2.0/readme.txt (modified) (2 diffs)
-
tags/1.2.0/semust-seo.php (modified) (2 diffs)
-
trunk/includes/class-semust-api.php (modified) (7 diffs)
-
trunk/includes/class-semust-auth.php (modified) (2 diffs)
-
trunk/includes/class-semust-core.php (modified) (2 diffs)
-
trunk/includes/class-semust-media.php (added)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/semust-seo.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
semust/tags/1.2.0/includes/class-semust-api.php
r3427143 r3484579 8 8 private $auth; 9 9 private $posts; 10 11 public function __construct(SEMUST_Auth $auth, SEMUST_Posts $posts) { 10 private $media; 11 12 public function __construct(SEMUST_Auth $auth, SEMUST_Posts $posts, SEMUST_Media $media) { 12 13 $this->auth = $auth; 13 14 $this->posts = $posts; 15 $this->media = $media; 14 16 } 15 17 … … 40 42 'permission_callback' => [$this->auth, 'verify_request'] 41 43 ]); 44 45 // GET /list-posts - List posts 46 register_rest_route(self::NAMESPACE, '/list-posts', [ 47 'methods' => WP_REST_Server::READABLE, 48 'callback' => [$this, 'list_posts'], 49 'permission_callback' => [$this->auth, 'verify_request'], 50 'args' => $this->list_posts_args() 51 ]); 52 53 // GET /media-library - Get media library items 54 register_rest_route(self::NAMESPACE, '/media-library', [ 55 'methods' => WP_REST_Server::READABLE, 56 'callback' => [$this, 'get_media_library'], 57 'permission_callback' => [$this->auth, 'verify_request'], 58 'args' => $this->media_library_args() 59 ]); 60 61 // POST /upload-media - Upload media file 62 register_rest_route(self::NAMESPACE, '/upload-media', [ 63 'methods' => WP_REST_Server::CREATABLE, 64 'callback' => [$this, 'upload_media'], 65 'permission_callback' => [$this->auth, 'verify_request'] 66 ]); 42 67 } 43 68 … … 65 90 66 91 /** 67 * POST /update-post - Update post content 92 * POST /update-post - Update post content or create new post 68 93 */ 69 94 public function update_post($request) { 70 95 $url = $request->get_param('url'); 71 96 $content = $request->get_param('content'); 97 $meta = $request->get_param('meta'); 98 $action = $request->get_param('action'); 99 100 // Default action is update 101 if (empty($action)) { 102 $action = 'update'; 103 } 104 105 // Validate URL parameter for update action 106 if ($action === 'update' && empty($url)) { 107 return new WP_Error( 108 'semust_missing_url', 109 __('URL parameter is required when action is "update"', 'semust'), 110 ['status' => 400] 111 ); 112 } 72 113 73 114 // Sanitize content with wp_kses_post to allow safe HTML 74 115 $content = wp_kses_post($content); 75 116 117 // Sanitize metadata if provided 118 $sanitized_meta = null; 119 if (!empty($meta) && is_array($meta)) { 120 $sanitized_meta = [ 121 'title' => isset($meta['title']) ? sanitize_text_field($meta['title']) : '', 122 'excerpt' => isset($meta['excerpt']) ? sanitize_text_field($meta['excerpt']) : '', 123 'slug' => isset($meta['slug']) ? sanitize_title($meta['slug']) : '', 124 'featured_media' => isset($meta['featured_media']) ? intval($meta['featured_media']) : null 125 ]; 126 } 127 128 // Handle create action 129 if ($action === 'create') { 130 return $this->create_new_post($content, $sanitized_meta); 131 } 132 133 // Handle update action (existing logic) 134 return $this->update_existing_post($url, $content, $sanitized_meta); 135 } 136 137 /** 138 * Create new post with content and metadata 139 */ 140 private function create_new_post($content, $meta) { 141 $post_data = [ 142 'post_content' => $content, 143 'post_status' => 'draft', // Create as draft for safety 144 'post_type' => 'post', 145 ]; 146 147 // Add metadata if provided 148 if (!empty($meta)) { 149 if (!empty($meta['title'])) { 150 $post_data['post_title'] = $meta['title']; 151 } 152 if (!empty($meta['excerpt'])) { 153 $post_data['post_excerpt'] = $meta['excerpt']; 154 } 155 if (!empty($meta['slug'])) { 156 $post_data['post_name'] = $meta['slug']; 157 } 158 } 159 160 // Insert the post 161 $post_id = wp_insert_post($post_data, true); 162 163 if (is_wp_error($post_id)) { 164 return new WP_Error( 165 'semust_create_failed', 166 $post_id->get_error_message(), 167 ['status' => 500] 168 ); 169 } 170 171 // Handle featured image if provided 172 if (!empty($meta['featured_media'])) { 173 $featured_result = $this->media->set_featured_image($post_id, intval($meta['featured_media'])); 174 if (is_wp_error($featured_result)) { 175 // Log warning but don't fail the post creation 176 error_log('SEMUST: Failed to set featured image: ' . $featured_result->get_error_message()); 177 } 178 } 179 180 return rest_ensure_response([ 181 'success' => true, 182 'post_id' => $post_id, 183 'action' => 'created', 184 'url' => get_permalink($post_id), 185 'featured_media' => $this->media->get_featured_image($post_id), 186 'message' => __('Post created successfully', 'semust') 187 ]); 188 } 189 190 /** 191 * Update existing post with content and metadata 192 */ 193 private function update_existing_post($url, $content, $meta) { 76 194 $post = $this->posts->find_by_url($url); 77 195 … … 94 212 } 95 213 96 $result = $this->posts->update_content($post['post_id'], $content); 214 $post_data = [ 215 'ID' => $post['post_id'], 216 'post_content' => $content, 217 ]; 218 219 // Update metadata if provided 220 if (!empty($meta)) { 221 if (!empty($meta['title'])) { 222 $post_data['post_title'] = $meta['title']; 223 } 224 if (!empty($meta['excerpt'])) { 225 $post_data['post_excerpt'] = $meta['excerpt']; 226 } 227 if (!empty($meta['slug'])) { 228 $post_data['post_name'] = $meta['slug']; 229 } 230 } 231 232 $result = wp_update_post($post_data, true); 97 233 98 234 if (is_wp_error($result)) { … … 104 240 } 105 241 242 // Handle featured image if provided 243 if (isset($meta['featured_media'])) { 244 if ($meta['featured_media'] === 0 || $meta['featured_media'] === null) { 245 // Remove featured image 246 $this->media->remove_featured_image($post['post_id']); 247 } else { 248 // Set featured image 249 $featured_result = $this->media->set_featured_image($post['post_id'], intval($meta['featured_media'])); 250 if (is_wp_error($featured_result)) { 251 error_log('SEMUST: Failed to set featured image: ' . $featured_result->get_error_message()); 252 } 253 } 254 } 255 256 // Clear cache 257 clean_post_cache($post['post_id']); 258 106 259 return rest_ensure_response([ 107 260 'success' => true, 108 261 'post_id' => $post['post_id'], 262 'action' => 'updated', 263 'url' => get_permalink($post['post_id']), 264 'featured_media' => $this->media->get_featured_image($post['post_id']), 109 265 'message' => __('Post updated successfully', 'semust') 110 266 ]); … … 130 286 131 287 /** 288 * GET /list-posts - List posts with search support 289 */ 290 public function list_posts($request) { 291 $search = $request->get_param('search'); 292 $per_page = $request->get_param('per_page') ?: 50; 293 $page = $request->get_param('page') ?: 1; 294 295 $args = [ 296 'post_type' => 'post', 297 'post_status' => ['publish', 'draft', 'pending', 'private'], 298 'posts_per_page' => $per_page, 299 'paged' => $page, 300 'orderby' => 'modified', 301 'order' => 'DESC', 302 ]; 303 304 // Add search if provided 305 if (!empty($search)) { 306 $args['s'] = sanitize_text_field($search); 307 } 308 309 $query = new \WP_Query($args); 310 $posts = []; 311 312 foreach ($query->posts as $post) { 313 $posts[] = [ 314 'id' => $post->ID, 315 'title' => $post->post_title, 316 'url' => get_permalink($post->ID), 317 'status' => $post->post_status, 318 'modified' => $post->post_modified, 319 ]; 320 } 321 322 return rest_ensure_response([ 323 'success' => true, 324 'posts' => $posts, 325 'total' => $query->found_posts, 326 'pages' => $query->max_num_pages, 327 ]); 328 } 329 330 /** 132 331 * Arguments for GET /post 133 332 */ … … 149 348 return [ 150 349 'url' => [ 151 'required' => true,350 'required' => false, 152 351 'type' => 'string', 153 352 'sanitize_callback' => 'esc_url_raw', 154 'description' => __('Post URL to update ', 'semust')353 'description' => __('Post URL to update (required for action=update)', 'semust') 155 354 ], 156 355 'content' => [ 157 356 'required' => true, 158 357 'type' => 'string', 159 'description' => __('New post content', 'semust') 358 'description' => __('Post content', 'semust') 359 ], 360 'meta' => [ 361 'required' => false, 362 'type' => 'object', 363 'description' => __('Post metadata (title, excerpt, slug, featured_media)', 'semust') 364 ], 365 'action' => [ 366 'required' => false, 367 'type' => 'string', 368 'default' => 'update', 369 'enum' => ['create', 'update'], 370 'description' => __('Action to perform: create or update', 'semust') 160 371 ] 161 372 ]; 162 373 } 374 375 /** 376 * Arguments for GET /list-posts 377 */ 378 private function list_posts_args() { 379 return [ 380 'search' => [ 381 'required' => false, 382 'type' => 'string', 383 'sanitize_callback' => 'sanitize_text_field', 384 'description' => __('Search posts by title', 'semust') 385 ], 386 'per_page' => [ 387 'required' => false, 388 'type' => 'integer', 389 'default' => 50, 390 'description' => __('Number of posts per page', 'semust') 391 ], 392 'page' => [ 393 'required' => false, 394 'type' => 'integer', 395 'default' => 1, 396 'description' => __('Page number', 'semust') 397 ] 398 ]; 399 } 400 401 /** 402 * GET /media-library - Get media library items 403 */ 404 public function get_media_library($request) { 405 $args = [ 406 'search' => $request->get_param('search'), 407 'per_page' => $request->get_param('per_page') ?: 50, 408 'page' => $request->get_param('page') ?: 1 409 ]; 410 411 $result = $this->media->get_media_library($args); 412 413 return rest_ensure_response([ 414 'success' => true, 415 'data' => $result 416 ]); 417 } 418 419 /** 420 * POST /upload-media - Upload media file 421 */ 422 public function upload_media($request) { 423 // Get uploaded files 424 $files = $request->get_file_params(); 425 426 if (empty($files['file'])) { 427 return new WP_Error( 428 'semust_no_file', 429 __('No file uploaded', 'semust'), 430 ['status' => 400] 431 ); 432 } 433 434 // Upload the file 435 $result = $this->media->upload_media($files['file']); 436 437 if (is_wp_error($result)) { 438 return $result; 439 } 440 441 return rest_ensure_response([ 442 'success' => true, 443 'data' => $result, 444 'message' => __('Media uploaded successfully', 'semust') 445 ]); 446 } 447 448 /** 449 * Arguments for GET /media-library 450 */ 451 private function media_library_args() { 452 return [ 453 'search' => [ 454 'required' => false, 455 'type' => 'string', 456 'sanitize_callback' => 'sanitize_text_field', 457 'description' => __('Search media by title', 'semust') 458 ], 459 'per_page' => [ 460 'required' => false, 461 'type' => 'integer', 462 'default' => 50, 463 'minimum' => 1, 464 'maximum' => 100, 465 'description' => __('Number of items per page', 'semust') 466 ], 467 'page' => [ 468 'required' => false, 469 'type' => 'integer', 470 'default' => 1, 471 'minimum' => 1, 472 'description' => __('Page number', 'semust') 473 ] 474 ]; 475 } 163 476 } -
semust/tags/1.2.0/includes/class-semust-auth.php
r3427143 r3484579 10 10 */ 11 11 public function verify_request($request) { 12 // Enforce HTTPS in production 13 if (!is_ssl() && !defined('WP_DEBUG')) { 14 return new WP_Error( 15 'semust_https_required', 16 __('HTTPS is required for API requests', 'semust'), 17 ['status' => 403] 18 ); 19 } 20 12 21 $stored_key = $this->get_api_key(); 13 22 … … 38 47 } 39 48 40 // Set current user to admin after successful API key auth 41 // This is needed for current_user_can() checks in update operations 42 $admins = get_users(['role' => 'administrator', 'number' => 1]); 43 if (!empty($admins)) { 44 wp_set_current_user($admins[0]->ID); 49 // Rate limiting check 50 $key_hash = md5($provided_key); 51 $transient_key = 'semust_rate_limit_' . $key_hash; 52 $requests = get_transient($transient_key); 53 54 if ($requests === false) { 55 $requests = 0; 45 56 } 46 57 58 // Limit: 100 requests per minute 59 if ($requests >= 100) { 60 return new WP_Error( 61 'semust_rate_limit', 62 __('Rate limit exceeded. Maximum 100 requests per minute.', 'semust'), 63 ['status' => 429] 64 ); 65 } 66 67 // Increment request counter 68 set_transient($transient_key, $requests + 1, 60); 69 70 // API key authentication successful 71 // Note: We don't set current user to avoid privilege escalation 72 // API key represents site-level authorization 47 73 return true; 48 74 } -
semust/tags/1.2.0/includes/class-semust-core.php
r3427143 r3484579 9 9 public $auth; 10 10 public $posts; 11 public $media; 11 12 public $admin; 12 13 … … 26 27 $this->auth = new SEMUST_Auth(); 27 28 $this->posts = new SEMUST_Posts(); 28 $this->api = new SEMUST_Api($this->auth, $this->posts); 29 $this->media = new SEMUST_Media(); 30 $this->api = new SEMUST_Api($this->auth, $this->posts, $this->media); 29 31 30 32 if (is_admin()) { -
semust/tags/1.2.0/readme.txt
r3427148 r3484579 4 4 Requires at least: 5.0 5 5 Tested up to: 6.9 6 Stable tag: 1. 0.16 Stable tag: 1.2.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 83 83 84 84 For support and questions, please visit [semust.com](https://semust.com) or contact us through the SEMUST dashboard. 85 86 == Changelog ==87 88 = 1.0.0 =89 * Initial release90 * REST API endpoints for post management91 * Admin settings page with v2 design -
semust/tags/1.2.0/semust-seo.php
r3427145 r3484579 3 3 * Plugin Name: SEMUST 4 4 * Description: Connect your WordPress site to SEMUST SEO platform 5 * Version: 1. 0.15 * Version: 1.2.0 6 6 * Author: SEMUST 7 7 * Author URI: https://semust.com/hakkimizda … … 13 13 * Requires PHP: 7.4 14 14 * Tested up to: 6.9 15 * 16 * Changelog: 17 * 1.2.0 - Added media library management, featured image support, and WordPress post management 18 * 1.0.1 - Previous version 19 * 1.0.0 - Initial release 15 20 */ 16 21 17 22 if (!defined('ABSPATH')) exit; 18 23 19 define('SEMUST_VERSION', '1. 0.0');24 define('SEMUST_VERSION', '1.2.0'); 20 25 define('SEMUST_PLUGIN_DIR', plugin_dir_path(__FILE__)); 21 26 define('SEMUST_PLUGIN_URL', plugin_dir_url(__FILE__)); -
semust/trunk/includes/class-semust-api.php
r3427143 r3484579 8 8 private $auth; 9 9 private $posts; 10 11 public function __construct(SEMUST_Auth $auth, SEMUST_Posts $posts) { 10 private $media; 11 12 public function __construct(SEMUST_Auth $auth, SEMUST_Posts $posts, SEMUST_Media $media) { 12 13 $this->auth = $auth; 13 14 $this->posts = $posts; 15 $this->media = $media; 14 16 } 15 17 … … 40 42 'permission_callback' => [$this->auth, 'verify_request'] 41 43 ]); 44 45 // GET /list-posts - List posts 46 register_rest_route(self::NAMESPACE, '/list-posts', [ 47 'methods' => WP_REST_Server::READABLE, 48 'callback' => [$this, 'list_posts'], 49 'permission_callback' => [$this->auth, 'verify_request'], 50 'args' => $this->list_posts_args() 51 ]); 52 53 // GET /media-library - Get media library items 54 register_rest_route(self::NAMESPACE, '/media-library', [ 55 'methods' => WP_REST_Server::READABLE, 56 'callback' => [$this, 'get_media_library'], 57 'permission_callback' => [$this->auth, 'verify_request'], 58 'args' => $this->media_library_args() 59 ]); 60 61 // POST /upload-media - Upload media file 62 register_rest_route(self::NAMESPACE, '/upload-media', [ 63 'methods' => WP_REST_Server::CREATABLE, 64 'callback' => [$this, 'upload_media'], 65 'permission_callback' => [$this->auth, 'verify_request'] 66 ]); 42 67 } 43 68 … … 65 90 66 91 /** 67 * POST /update-post - Update post content 92 * POST /update-post - Update post content or create new post 68 93 */ 69 94 public function update_post($request) { 70 95 $url = $request->get_param('url'); 71 96 $content = $request->get_param('content'); 97 $meta = $request->get_param('meta'); 98 $action = $request->get_param('action'); 99 100 // Default action is update 101 if (empty($action)) { 102 $action = 'update'; 103 } 104 105 // Validate URL parameter for update action 106 if ($action === 'update' && empty($url)) { 107 return new WP_Error( 108 'semust_missing_url', 109 __('URL parameter is required when action is "update"', 'semust'), 110 ['status' => 400] 111 ); 112 } 72 113 73 114 // Sanitize content with wp_kses_post to allow safe HTML 74 115 $content = wp_kses_post($content); 75 116 117 // Sanitize metadata if provided 118 $sanitized_meta = null; 119 if (!empty($meta) && is_array($meta)) { 120 $sanitized_meta = [ 121 'title' => isset($meta['title']) ? sanitize_text_field($meta['title']) : '', 122 'excerpt' => isset($meta['excerpt']) ? sanitize_text_field($meta['excerpt']) : '', 123 'slug' => isset($meta['slug']) ? sanitize_title($meta['slug']) : '', 124 'featured_media' => isset($meta['featured_media']) ? intval($meta['featured_media']) : null 125 ]; 126 } 127 128 // Handle create action 129 if ($action === 'create') { 130 return $this->create_new_post($content, $sanitized_meta); 131 } 132 133 // Handle update action (existing logic) 134 return $this->update_existing_post($url, $content, $sanitized_meta); 135 } 136 137 /** 138 * Create new post with content and metadata 139 */ 140 private function create_new_post($content, $meta) { 141 $post_data = [ 142 'post_content' => $content, 143 'post_status' => 'draft', // Create as draft for safety 144 'post_type' => 'post', 145 ]; 146 147 // Add metadata if provided 148 if (!empty($meta)) { 149 if (!empty($meta['title'])) { 150 $post_data['post_title'] = $meta['title']; 151 } 152 if (!empty($meta['excerpt'])) { 153 $post_data['post_excerpt'] = $meta['excerpt']; 154 } 155 if (!empty($meta['slug'])) { 156 $post_data['post_name'] = $meta['slug']; 157 } 158 } 159 160 // Insert the post 161 $post_id = wp_insert_post($post_data, true); 162 163 if (is_wp_error($post_id)) { 164 return new WP_Error( 165 'semust_create_failed', 166 $post_id->get_error_message(), 167 ['status' => 500] 168 ); 169 } 170 171 // Handle featured image if provided 172 if (!empty($meta['featured_media'])) { 173 $featured_result = $this->media->set_featured_image($post_id, intval($meta['featured_media'])); 174 if (is_wp_error($featured_result)) { 175 // Log warning but don't fail the post creation 176 error_log('SEMUST: Failed to set featured image: ' . $featured_result->get_error_message()); 177 } 178 } 179 180 return rest_ensure_response([ 181 'success' => true, 182 'post_id' => $post_id, 183 'action' => 'created', 184 'url' => get_permalink($post_id), 185 'featured_media' => $this->media->get_featured_image($post_id), 186 'message' => __('Post created successfully', 'semust') 187 ]); 188 } 189 190 /** 191 * Update existing post with content and metadata 192 */ 193 private function update_existing_post($url, $content, $meta) { 76 194 $post = $this->posts->find_by_url($url); 77 195 … … 94 212 } 95 213 96 $result = $this->posts->update_content($post['post_id'], $content); 214 $post_data = [ 215 'ID' => $post['post_id'], 216 'post_content' => $content, 217 ]; 218 219 // Update metadata if provided 220 if (!empty($meta)) { 221 if (!empty($meta['title'])) { 222 $post_data['post_title'] = $meta['title']; 223 } 224 if (!empty($meta['excerpt'])) { 225 $post_data['post_excerpt'] = $meta['excerpt']; 226 } 227 if (!empty($meta['slug'])) { 228 $post_data['post_name'] = $meta['slug']; 229 } 230 } 231 232 $result = wp_update_post($post_data, true); 97 233 98 234 if (is_wp_error($result)) { … … 104 240 } 105 241 242 // Handle featured image if provided 243 if (isset($meta['featured_media'])) { 244 if ($meta['featured_media'] === 0 || $meta['featured_media'] === null) { 245 // Remove featured image 246 $this->media->remove_featured_image($post['post_id']); 247 } else { 248 // Set featured image 249 $featured_result = $this->media->set_featured_image($post['post_id'], intval($meta['featured_media'])); 250 if (is_wp_error($featured_result)) { 251 error_log('SEMUST: Failed to set featured image: ' . $featured_result->get_error_message()); 252 } 253 } 254 } 255 256 // Clear cache 257 clean_post_cache($post['post_id']); 258 106 259 return rest_ensure_response([ 107 260 'success' => true, 108 261 'post_id' => $post['post_id'], 262 'action' => 'updated', 263 'url' => get_permalink($post['post_id']), 264 'featured_media' => $this->media->get_featured_image($post['post_id']), 109 265 'message' => __('Post updated successfully', 'semust') 110 266 ]); … … 130 286 131 287 /** 288 * GET /list-posts - List posts with search support 289 */ 290 public function list_posts($request) { 291 $search = $request->get_param('search'); 292 $per_page = $request->get_param('per_page') ?: 50; 293 $page = $request->get_param('page') ?: 1; 294 295 $args = [ 296 'post_type' => 'post', 297 'post_status' => ['publish', 'draft', 'pending', 'private'], 298 'posts_per_page' => $per_page, 299 'paged' => $page, 300 'orderby' => 'modified', 301 'order' => 'DESC', 302 ]; 303 304 // Add search if provided 305 if (!empty($search)) { 306 $args['s'] = sanitize_text_field($search); 307 } 308 309 $query = new \WP_Query($args); 310 $posts = []; 311 312 foreach ($query->posts as $post) { 313 $posts[] = [ 314 'id' => $post->ID, 315 'title' => $post->post_title, 316 'url' => get_permalink($post->ID), 317 'status' => $post->post_status, 318 'modified' => $post->post_modified, 319 ]; 320 } 321 322 return rest_ensure_response([ 323 'success' => true, 324 'posts' => $posts, 325 'total' => $query->found_posts, 326 'pages' => $query->max_num_pages, 327 ]); 328 } 329 330 /** 132 331 * Arguments for GET /post 133 332 */ … … 149 348 return [ 150 349 'url' => [ 151 'required' => true,350 'required' => false, 152 351 'type' => 'string', 153 352 'sanitize_callback' => 'esc_url_raw', 154 'description' => __('Post URL to update ', 'semust')353 'description' => __('Post URL to update (required for action=update)', 'semust') 155 354 ], 156 355 'content' => [ 157 356 'required' => true, 158 357 'type' => 'string', 159 'description' => __('New post content', 'semust') 358 'description' => __('Post content', 'semust') 359 ], 360 'meta' => [ 361 'required' => false, 362 'type' => 'object', 363 'description' => __('Post metadata (title, excerpt, slug, featured_media)', 'semust') 364 ], 365 'action' => [ 366 'required' => false, 367 'type' => 'string', 368 'default' => 'update', 369 'enum' => ['create', 'update'], 370 'description' => __('Action to perform: create or update', 'semust') 160 371 ] 161 372 ]; 162 373 } 374 375 /** 376 * Arguments for GET /list-posts 377 */ 378 private function list_posts_args() { 379 return [ 380 'search' => [ 381 'required' => false, 382 'type' => 'string', 383 'sanitize_callback' => 'sanitize_text_field', 384 'description' => __('Search posts by title', 'semust') 385 ], 386 'per_page' => [ 387 'required' => false, 388 'type' => 'integer', 389 'default' => 50, 390 'description' => __('Number of posts per page', 'semust') 391 ], 392 'page' => [ 393 'required' => false, 394 'type' => 'integer', 395 'default' => 1, 396 'description' => __('Page number', 'semust') 397 ] 398 ]; 399 } 400 401 /** 402 * GET /media-library - Get media library items 403 */ 404 public function get_media_library($request) { 405 $args = [ 406 'search' => $request->get_param('search'), 407 'per_page' => $request->get_param('per_page') ?: 50, 408 'page' => $request->get_param('page') ?: 1 409 ]; 410 411 $result = $this->media->get_media_library($args); 412 413 return rest_ensure_response([ 414 'success' => true, 415 'data' => $result 416 ]); 417 } 418 419 /** 420 * POST /upload-media - Upload media file 421 */ 422 public function upload_media($request) { 423 // Get uploaded files 424 $files = $request->get_file_params(); 425 426 if (empty($files['file'])) { 427 return new WP_Error( 428 'semust_no_file', 429 __('No file uploaded', 'semust'), 430 ['status' => 400] 431 ); 432 } 433 434 // Upload the file 435 $result = $this->media->upload_media($files['file']); 436 437 if (is_wp_error($result)) { 438 return $result; 439 } 440 441 return rest_ensure_response([ 442 'success' => true, 443 'data' => $result, 444 'message' => __('Media uploaded successfully', 'semust') 445 ]); 446 } 447 448 /** 449 * Arguments for GET /media-library 450 */ 451 private function media_library_args() { 452 return [ 453 'search' => [ 454 'required' => false, 455 'type' => 'string', 456 'sanitize_callback' => 'sanitize_text_field', 457 'description' => __('Search media by title', 'semust') 458 ], 459 'per_page' => [ 460 'required' => false, 461 'type' => 'integer', 462 'default' => 50, 463 'minimum' => 1, 464 'maximum' => 100, 465 'description' => __('Number of items per page', 'semust') 466 ], 467 'page' => [ 468 'required' => false, 469 'type' => 'integer', 470 'default' => 1, 471 'minimum' => 1, 472 'description' => __('Page number', 'semust') 473 ] 474 ]; 475 } 163 476 } -
semust/trunk/includes/class-semust-auth.php
r3427143 r3484579 10 10 */ 11 11 public function verify_request($request) { 12 // Enforce HTTPS in production 13 if (!is_ssl() && !defined('WP_DEBUG')) { 14 return new WP_Error( 15 'semust_https_required', 16 __('HTTPS is required for API requests', 'semust'), 17 ['status' => 403] 18 ); 19 } 20 12 21 $stored_key = $this->get_api_key(); 13 22 … … 38 47 } 39 48 40 // Set current user to admin after successful API key auth 41 // This is needed for current_user_can() checks in update operations 42 $admins = get_users(['role' => 'administrator', 'number' => 1]); 43 if (!empty($admins)) { 44 wp_set_current_user($admins[0]->ID); 49 // Rate limiting check 50 $key_hash = md5($provided_key); 51 $transient_key = 'semust_rate_limit_' . $key_hash; 52 $requests = get_transient($transient_key); 53 54 if ($requests === false) { 55 $requests = 0; 45 56 } 46 57 58 // Limit: 100 requests per minute 59 if ($requests >= 100) { 60 return new WP_Error( 61 'semust_rate_limit', 62 __('Rate limit exceeded. Maximum 100 requests per minute.', 'semust'), 63 ['status' => 429] 64 ); 65 } 66 67 // Increment request counter 68 set_transient($transient_key, $requests + 1, 60); 69 70 // API key authentication successful 71 // Note: We don't set current user to avoid privilege escalation 72 // API key represents site-level authorization 47 73 return true; 48 74 } -
semust/trunk/includes/class-semust-core.php
r3427143 r3484579 9 9 public $auth; 10 10 public $posts; 11 public $media; 11 12 public $admin; 12 13 … … 26 27 $this->auth = new SEMUST_Auth(); 27 28 $this->posts = new SEMUST_Posts(); 28 $this->api = new SEMUST_Api($this->auth, $this->posts); 29 $this->media = new SEMUST_Media(); 30 $this->api = new SEMUST_Api($this->auth, $this->posts, $this->media); 29 31 30 32 if (is_admin()) { -
semust/trunk/readme.txt
r3427148 r3484579 4 4 Requires at least: 5.0 5 5 Tested up to: 6.9 6 Stable tag: 1. 0.16 Stable tag: 1.2.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 83 83 84 84 For support and questions, please visit [semust.com](https://semust.com) or contact us through the SEMUST dashboard. 85 86 == Changelog ==87 88 = 1.0.0 =89 * Initial release90 * REST API endpoints for post management91 * Admin settings page with v2 design -
semust/trunk/semust-seo.php
r3427145 r3484579 3 3 * Plugin Name: SEMUST 4 4 * Description: Connect your WordPress site to SEMUST SEO platform 5 * Version: 1. 0.15 * Version: 1.2.0 6 6 * Author: SEMUST 7 7 * Author URI: https://semust.com/hakkimizda … … 13 13 * Requires PHP: 7.4 14 14 * Tested up to: 6.9 15 * 16 * Changelog: 17 * 1.2.0 - Added media library management, featured image support, and WordPress post management 18 * 1.0.1 - Previous version 19 * 1.0.0 - Initial release 15 20 */ 16 21 17 22 if (!defined('ABSPATH')) exit; 18 23 19 define('SEMUST_VERSION', '1. 0.0');24 define('SEMUST_VERSION', '1.2.0'); 20 25 define('SEMUST_PLUGIN_DIR', plugin_dir_path(__FILE__)); 21 26 define('SEMUST_PLUGIN_URL', plugin_dir_url(__FILE__));
Note: See TracChangeset
for help on using the changeset viewer.