diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..93a561e --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,19 @@ +name: WordPress Plugin Build Test + +on: + push: + branches: + - main + - "releases/*" + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Get latest code + uses: actions/checkout@v4 + + - name: Run plugin check + uses: WordPress/plugin-check-action@v1.0.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8946dbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Packages and Dependencies +/node_mudules +/vendor + +# SVN Files +/svn + +# Plugin Zip File +thumbnail-remover.zip \ No newline at end of file diff --git a/assets/img/bmc-button.png b/assets/img/bmc-button.png new file mode 100644 index 0000000..9a4e617 Binary files /dev/null and b/assets/img/bmc-button.png differ diff --git a/assets/js/script.js b/assets/js/script.js new file mode 100644 index 0000000..4aba915 --- /dev/null +++ b/assets/js/script.js @@ -0,0 +1,77 @@ +jQuery(document).ready(function ($) { + var sectionTemplate = $("#api_sections .api-section").first().clone(); + var sectionCount = $("#api_sections .api-section").length; + + // Add new section + $("#add_section").on("click", function () { + var newSection = sectionTemplate.clone(); + sectionCount++; + + newSection.find("h4").text("Section " + sectionCount); + newSection.find("select, input").each(function () { + var name = $(this).attr("name"); + if (name) { + $(this).attr( + "name", + name.replace("[0]", "[" + (sectionCount - 1) + "]") + ); + } + }); + + // Clear the section name field + newSection.find('input[name$="[name]"]').val(sectionCount); + + newSection.attr("data-index", sectionCount - 1); + newSection.find(".remove-section").show(); + + $("#api_sections").append(newSection); + resetRemoveButtons(); + }); + + // Remove section + $("#api_sections").on("click", ".remove-section", function () { + $(this).closest(".api-section").remove(); + resetSectionIndexes(); + resetRemoveButtons(); + }); + + // Reset section indexes + function resetSectionIndexes() { + $("#api_sections .api-section").each(function (index) { + $(this) + .find("h4") + .text("Section " + (index + 1)); + $(this).attr("data-index", index); + $(this) + .find("select, input") + .each(function () { + var name = $(this).attr("name"); + if (name) { + $(this).attr("name", name.replace(/\[\d+\]/, "[" + index + "]")); + } + }); + }); + sectionCount = $("#api_sections .api-section").length; + } + + // Reset remove buttons + function resetRemoveButtons() { + var sections = $("#api_sections .api-section"); + sections.find(".remove-section").show(); + if (sections.length === 1) { + sections.first().find(".remove-section").hide(); + } + } + + // Handle access type switch + $('input[name="custom_api_access_type"]').on("change", function () { + if ($(this).val() === "private") { + $("#custom_api_roles_row").show(); + } else { + $("#custom_api_roles_row").hide(); + } + }); + + // Initialize + resetRemoveButtons(); +}); diff --git a/custom-api-creator.php b/custom-api-creator.php new file mode 100644 index 0000000..bbba716 --- /dev/null +++ b/custom-api-creator.php @@ -0,0 +1,494 @@ + _x('Custom APIs', 'post type general name', 'custom-api-creator'), + 'singular_name' => _x('Custom API', 'post type singular name', 'custom-api-creator'), + 'menu_name' => _x('Custom APIs', 'admin menu', 'custom-api-creator'), + 'name_admin_bar' => _x('Custom API', 'add new on admin bar', 'custom-api-creator'), + 'add_new' => _x('Add New', 'custom api', 'custom-api-creator'), + 'add_new_item' => __('Add New Custom API', 'custom-api-creator'), + 'new_item' => __('New Custom API', 'custom-api-creator'), + 'edit_item' => __('Edit Custom API', 'custom-api-creator'), + 'view_item' => __('View Custom API', 'custom-api-creator'), + 'all_items' => __('All Custom APIs', 'custom-api-creator'), + 'search_items' => __('Search Custom APIs', 'custom-api-creator'), + 'parent_item_colon' => __('Parent Custom APIs:', 'custom-api-creator'), + 'not_found' => __('No custom apis found.', 'custom-api-creator'), + 'not_found_in_trash' => __('No custom apis found in Trash.', 'custom-api-creator') + ); + + $args = array( + 'labels' => $labels, + 'public' => false, + 'publicly_queryable' => false, + 'show_ui' => true, + 'show_in_menu' => false, + 'query_var' => true, + 'rewrite' => array('slug' => 'custom-api'), + 'capability_type' => 'post', + 'has_archive' => false, + 'hierarchical' => false, + 'menu_position' => null, + 'supports' => array('title') + ); + + register_post_type('custom_api', $args); + } + + public function load_textdomain() + { + load_plugin_textdomain('custom-api-creator', false, dirname(plugin_basename(__FILE__)) . '/languages'); + } + + public function add_admin_menu() + { + add_menu_page( + __('Custom API', 'custom-api-creator'), + __('Custom API', 'custom-api-creator'), + 'manage_options', + 'edit.php?post_type=custom_api', + null, + 'dashicons-rest-api', + 30 + ); + } + + public function enqueue_admin_scripts($hook) + { + if ('post.php' != $hook && 'post-new.php' != $hook) { + return; + } + + global $post; + if ('custom_api' !== $post->post_type) { + return; + } + + wp_enqueue_script('custom-api-admin', plugin_dir_url(__FILE__) . 'assets/js/script.js', array('jquery'), '1.0', true); + } + + public function add_custom_api_meta_boxes() + { + add_meta_box( + 'custom_api_details', + __('API Details', 'custom-api-creator'), + array($this, 'render_api_details_meta_box'), + 'custom_api', + 'normal', + 'high' + ); + } + + public function render_api_details_meta_box($post) + { + wp_nonce_field('custom_api_meta_box', 'custom_api_meta_box_nonce'); + + $endpoint = get_post_meta($post->ID, '_custom_api_endpoint', true); + $sections = get_post_meta($post->ID, '_custom_api_sections', true); + $access_type = get_post_meta($post->ID, '_custom_api_access_type', true) ?: 'public'; + $roles = get_post_meta($post->ID, '_custom_api_roles', true) ?: array(); + + $post_types = get_post_types(array('public' => true), 'objects'); + $all_roles = wp_roles()->get_names(); + $all_taxonomies = get_taxonomies(array('public' => true), 'objects'); + ?> + + + + + + + + + + + + + + + + + +
+ +

+
+
+ $section) { + $this->render_section_fields($post_types, $all_taxonomies, $index, $section); + } + } else { + $this->render_section_fields($post_types, $all_taxonomies, 0); + } + ?> +
+ +
+
+ + +
+ +
+
+ $name): + $checked = in_array($role, $roles); + ?> +
+ +
+ '1', 'post_type' => '', 'fields' => array(), 'taxonomies' => array()); + ?> +
+

+

+ +

+

+ +

+

+
+ +
+ +

+

+
+ name, $section['taxonomies']); + ?> +
+ +

+ 0): ?> + + +
+ 'custom_api_endpoint', + '_custom_api_sections' => 'custom_api_sections', + '_custom_api_access_type' => 'custom_api_access_type', + '_custom_api_roles' => 'custom_api_roles' + ); + + foreach ($fields as $meta_key => $post_key) { + if (isset($_POST[$post_key])) { + $value = $post_key === 'custom_api_sections' ? $this->sanitize_sections($_POST[$post_key]) : $this->sanitize_array_or_string($_POST[$post_key]); + update_post_meta($post_id, $meta_key, $value); + } else { + delete_post_meta($post_id, $meta_key); + } + } + } + + private function sanitize_sections($sections) + { + $sanitized = array(); + foreach ($sections as $index => $section) { + $sanitized[$index] = array( + 'name' => sanitize_text_field($section['name']), + 'post_type' => sanitize_text_field($section['post_type']), + 'fields' => isset($section['fields']) ? array_map('sanitize_text_field', $section['fields']) : array(), + 'taxonomies' => isset($section['taxonomies']) ? array_map('sanitize_text_field', $section['taxonomies']) : array() + ); + } + return $sanitized; + } + + private function sanitize_array_or_string($data) + { + if (is_array($data)) { + return array_map('sanitize_text_field', $data); + } + return sanitize_text_field($data); + } + + public function register_custom_apis() + { + $custom_apis = get_posts(array( + 'post_type' => 'custom_api', + 'posts_per_page' => -1, + )); + + foreach ($custom_apis as $api) { + $endpoint = get_post_meta($api->ID, '_custom_api_endpoint', true); + $roles = get_post_meta($api->ID, '_custom_api_roles', true); + + register_rest_route('custom-api/v1', '/' . ltrim($endpoint, '/'), array( + 'methods' => 'GET', + 'callback' => array($this, 'handle_api_request'), + 'permission_callback' => function () use ($roles) { + return $this->check_api_permissions($roles); + }, + )); + } + } + + public function handle_api_request($request) + { + $params = $request->get_params(); + $endpoint = $request->get_route(); + + $api_post = $this->get_api_by_endpoint(substr($endpoint, strlen('/custom-api/v1/'))); + if (!$api_post) { + return new WP_Error('invalid_api', 'Invalid API endpoint', array('status' => 404)); + } + + $access_type = get_post_meta($api_post->ID, '_custom_api_access_type', true); + $roles = get_post_meta($api_post->ID, '_custom_api_roles', true); + + if ($access_type === 'private' && !$this->check_api_permissions($roles)) { + return new WP_Error('unauthorized', 'You do not have permission to access this API', array('status' => 403)); + } + + $sections = get_post_meta($api_post->ID, '_custom_api_sections', true); + + $response = array(); + foreach ($sections as $section) { + $section_name = !empty($section['name']) ? sanitize_title($section['name']) : 'section_' . $section['post_type']; + $query_args = $this->build_query_args($section['post_type'], $section['taxonomies'], $params); + $posts = $this->get_posts($query_args); + $response[$section_name] = $this->format_response($posts, $section['fields']); + } + + return $response; + } + + private function get_api_by_endpoint($endpoint) + { + $endpoint = str_replace('custom-api/v1/', '', $endpoint); + + $custom_apis = get_posts(array( + 'post_type' => 'custom_api', + 'posts_per_page' => 1, + 'meta_query' => array( + array( + 'key' => '_custom_api_endpoint', + 'value' => $endpoint, + 'compare' => '=' + ) + ) + )); + + return !empty($custom_apis) ? $custom_apis[0] : null; + } + + private function check_api_permissions($allowed_roles) + { + if (empty($allowed_roles)) { + return true; + } + + if (!is_user_logged_in()) { + return false; + } + + $user = wp_get_current_user(); + $user_roles = (array) $user->roles; + + return array_intersect($allowed_roles, $user_roles); + } + + private function build_query_args($post_type, $taxonomies, $params) + { + $query_args = array( + 'post_type' => $post_type, + 'posts_per_page' => -1, + ); + + if (!empty($taxonomies)) { + $tax_query = array(); + foreach ($taxonomies as $taxonomy) { + if (isset($params[$taxonomy])) { + $tax_query[] = array( + 'taxonomy' => $taxonomy, + 'field' => 'slug', + 'terms' => explode(',', $params[$taxonomy]), + ); + } + } + if (!empty($tax_query)) { + $query_args['tax_query'] = $tax_query; + } + } + + if (isset($params['search'])) { + $query_args['s'] = $params['search']; + } + + return $query_args; + } + + private function get_posts($query_args) + { + $query = new WP_Query($query_args); + return $query->posts; + } + + private function format_response($posts, $fields) + { + $response = array(); + foreach ($posts as $post) { + $item = array(); + foreach ($fields as $field) { + switch ($field) { + case 'title': + $item['title'] = get_the_title($post->ID); + break; + case 'content': + $item['content'] = get_the_content(null, false, $post->ID); + break; + case 'excerpt': + $item['excerpt'] = get_the_excerpt($post->ID); + break; + case 'categories': + $item['categories'] = wp_get_post_categories($post->ID, array('fields' => 'names')); + break; + case 'tags': + $item['tags'] = wp_get_post_tags($post->ID, array('fields' => 'names')); + break; + } + } + $response[] = $item; + } + return $response; + } + public function add_custom_columns($columns) + { + $new_columns = array(); + foreach ($columns as $key => $value) { + $new_columns[$key] = $value; + if ($key === 'title') { + $new_columns['endpoint'] = __('Endpoint', 'custom-api-creator'); + $new_columns['permission'] = __('Permission', 'custom-api-creator'); + } + } + return $new_columns; + } + + public function custom_column_content($column, $post_id) + { + switch ($column) { + case 'endpoint': + $endpoint = get_post_meta($post_id, '_custom_api_endpoint', true); + if ($endpoint) { + echo esc_html(home_url('/wp-json/custom-api/v1/' . ltrim($endpoint, '/'))); + } else { + echo '—'; + } + break; + + case 'permission': + $access_type = get_post_meta($post_id, '_custom_api_access_type', true); + if ($access_type === 'public') { + echo __('Public', 'custom-api-creator'); + } else { + $roles = get_post_meta($post_id, '_custom_api_roles', true); + if (!empty($roles)) { + $role_names = array_map(function ($role) { + return translate_user_role($role); + }, $roles); + echo esc_html(implode(', ', $role_names)); + } else { + echo __('No roles specified', 'custom-api-creator'); + } + } + break; + } + } +} + +new Custom_API_Creator(); \ No newline at end of file