Plugin Directory

Changeset 3484579


Ignore:
Timestamp:
03/17/2026 09:02:21 AM (11 days ago)
Author:
semust
Message:

Version 1.2.0 - UPDATED

Location:
semust
Files:
2 added
10 edited
1 copied

Legend:

Unmodified
Added
Removed
  • semust/tags/1.2.0/includes/class-semust-api.php

    r3427143 r3484579  
    88    private $auth;
    99    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) {
    1213        $this->auth = $auth;
    1314        $this->posts = $posts;
     15        $this->media = $media;
    1416    }
    1517
     
    4042            'permission_callback' => [$this->auth, 'verify_request']
    4143        ]);
     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        ]);
    4267    }
    4368
     
    6590
    6691    /**
    67      * POST /update-post - Update post content
     92     * POST /update-post - Update post content or create new post
    6893     */
    6994    public function update_post($request) {
    7095        $url = $request->get_param('url');
    7196        $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        }
    72113
    73114        // Sanitize content with wp_kses_post to allow safe HTML
    74115        $content = wp_kses_post($content);
    75116
     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) {
    76194        $post = $this->posts->find_by_url($url);
    77195
     
    94212        }
    95213
    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);
    97233
    98234        if (is_wp_error($result)) {
     
    104240        }
    105241
     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
    106259        return rest_ensure_response([
    107260            'success' => true,
    108261            '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']),
    109265            'message' => __('Post updated successfully', 'semust')
    110266        ]);
     
    130286
    131287    /**
     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    /**
    132331     * Arguments for GET /post
    133332     */
     
    149348        return [
    150349            'url' => [
    151                 'required' => true,
     350                'required' => false,
    152351                'type' => 'string',
    153352                'sanitize_callback' => 'esc_url_raw',
    154                 'description' => __('Post URL to update', 'semust')
     353                'description' => __('Post URL to update (required for action=update)', 'semust')
    155354            ],
    156355            'content' => [
    157356                'required' => true,
    158357                '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')
    160371            ]
    161372        ];
    162373    }
     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    }
    163476}
  • semust/tags/1.2.0/includes/class-semust-auth.php

    r3427143 r3484579  
    1010     */
    1111    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
    1221        $stored_key = $this->get_api_key();
    1322
     
    3847        }
    3948
    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;
    4556        }
    4657
     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
    4773        return true;
    4874    }
  • semust/tags/1.2.0/includes/class-semust-core.php

    r3427143 r3484579  
    99    public $auth;
    1010    public $posts;
     11    public $media;
    1112    public $admin;
    1213
     
    2627        $this->auth = new SEMUST_Auth();
    2728        $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);
    2931
    3032        if (is_admin()) {
  • semust/tags/1.2.0/readme.txt

    r3427148 r3484579  
    44Requires at least: 5.0
    55Tested up to: 6.9
    6 Stable tag: 1.0.1
     6Stable tag: 1.2.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    8383
    8484For 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 release
    90 * REST API endpoints for post management
    91 * Admin settings page with v2 design
  • semust/tags/1.2.0/semust-seo.php

    r3427145 r3484579  
    33 * Plugin Name: SEMUST
    44 * Description: Connect your WordPress site to SEMUST SEO platform
    5  * Version: 1.0.1
     5 * Version: 1.2.0
    66 * Author: SEMUST
    77 * Author URI: https://semust.com/hakkimizda
     
    1313 * Requires PHP: 7.4
    1414 * 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
    1520 */
    1621
    1722if (!defined('ABSPATH')) exit;
    1823
    19 define('SEMUST_VERSION', '1.0.0');
     24define('SEMUST_VERSION', '1.2.0');
    2025define('SEMUST_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2126define('SEMUST_PLUGIN_URL', plugin_dir_url(__FILE__));
  • semust/trunk/includes/class-semust-api.php

    r3427143 r3484579  
    88    private $auth;
    99    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) {
    1213        $this->auth = $auth;
    1314        $this->posts = $posts;
     15        $this->media = $media;
    1416    }
    1517
     
    4042            'permission_callback' => [$this->auth, 'verify_request']
    4143        ]);
     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        ]);
    4267    }
    4368
     
    6590
    6691    /**
    67      * POST /update-post - Update post content
     92     * POST /update-post - Update post content or create new post
    6893     */
    6994    public function update_post($request) {
    7095        $url = $request->get_param('url');
    7196        $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        }
    72113
    73114        // Sanitize content with wp_kses_post to allow safe HTML
    74115        $content = wp_kses_post($content);
    75116
     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) {
    76194        $post = $this->posts->find_by_url($url);
    77195
     
    94212        }
    95213
    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);
    97233
    98234        if (is_wp_error($result)) {
     
    104240        }
    105241
     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
    106259        return rest_ensure_response([
    107260            'success' => true,
    108261            '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']),
    109265            'message' => __('Post updated successfully', 'semust')
    110266        ]);
     
    130286
    131287    /**
     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    /**
    132331     * Arguments for GET /post
    133332     */
     
    149348        return [
    150349            'url' => [
    151                 'required' => true,
     350                'required' => false,
    152351                'type' => 'string',
    153352                'sanitize_callback' => 'esc_url_raw',
    154                 'description' => __('Post URL to update', 'semust')
     353                'description' => __('Post URL to update (required for action=update)', 'semust')
    155354            ],
    156355            'content' => [
    157356                'required' => true,
    158357                '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')
    160371            ]
    161372        ];
    162373    }
     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    }
    163476}
  • semust/trunk/includes/class-semust-auth.php

    r3427143 r3484579  
    1010     */
    1111    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
    1221        $stored_key = $this->get_api_key();
    1322
     
    3847        }
    3948
    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;
    4556        }
    4657
     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
    4773        return true;
    4874    }
  • semust/trunk/includes/class-semust-core.php

    r3427143 r3484579  
    99    public $auth;
    1010    public $posts;
     11    public $media;
    1112    public $admin;
    1213
     
    2627        $this->auth = new SEMUST_Auth();
    2728        $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);
    2931
    3032        if (is_admin()) {
  • semust/trunk/readme.txt

    r3427148 r3484579  
    44Requires at least: 5.0
    55Tested up to: 6.9
    6 Stable tag: 1.0.1
     6Stable tag: 1.2.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    8383
    8484For 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 release
    90 * REST API endpoints for post management
    91 * Admin settings page with v2 design
  • semust/trunk/semust-seo.php

    r3427145 r3484579  
    33 * Plugin Name: SEMUST
    44 * Description: Connect your WordPress site to SEMUST SEO platform
    5  * Version: 1.0.1
     5 * Version: 1.2.0
    66 * Author: SEMUST
    77 * Author URI: https://semust.com/hakkimizda
     
    1313 * Requires PHP: 7.4
    1414 * 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
    1520 */
    1621
    1722if (!defined('ABSPATH')) exit;
    1823
    19 define('SEMUST_VERSION', '1.0.0');
     24define('SEMUST_VERSION', '1.2.0');
    2025define('SEMUST_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2126define('SEMUST_PLUGIN_URL', plugin_dir_url(__FILE__));
Note: See TracChangeset for help on using the changeset viewer.