Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

settings page updates #11

Merged
merged 12 commits into from
Aug 26, 2024
Merged
32 changes: 32 additions & 0 deletions inc/rest/datasource-controller/datasource-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ public function register_routes() {
'permission_callback' => [ $this, 'delete_item_permissions_check' ],
]
);

// item_slug_conflicts
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/slug-conflicts',
[
'methods' => 'POST',
'callback' => [ $this, 'item_slug_conflicts' ],
'permission_callback' => [ $this, 'item_slug_conflicts_permissions_check' ],
]
);
}

public function create_item( $request ) {
Expand All @@ -99,6 +110,23 @@ public function delete_item( $request ) {
return rest_ensure_response( $result );
}

public function item_slug_conflicts( $request ) {
$slug = $request->get_param( 'slug' );
$uuid = $request->get_param( 'uuid' ) ?? '';
if ( empty( $slug ) ) {
return new \WP_Error(
'missing_slug',
__( 'Missing slug parameter.', 'remote-data-blocks' ),
array( 'status' => 400 )
);
}
$validation_status = DatasourceCRUD::validate_slug( $slug, $uuid );
$result = [
'exists' => true !== $validation_status,
];
return rest_ensure_response( $result );
}

// These all require manage_options for now, but we can adjust as needed

public function get_item_permissions_check( $request ) {
Expand All @@ -120,4 +148,8 @@ public function update_item_permissions_check( $request ) {
public function delete_item_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}

public function item_slug_conflicts_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
}
124 changes: 96 additions & 28 deletions inc/rest/datasource-crud/datasource-crud.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,88 @@ public static function is_uuid4( string $maybe_uuid ) {
return preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $maybe_uuid );
}

/**
* Validate the slug to verify
* - is not empty
* - only contains lowercase alphanumeric characters and hyphens
* - is not already taken
*
* @param string $slug The slug to validate.
* @param string [$uuid] The UUID of the data source to exclude from the check.
* @return WP_Error|true Returns true if the slug is valid, or a WP_Error object if not.
*/
public static function validate_slug( string $slug, string $uuid = '' ): WP_Error|true {
if ( empty( $slug ) ) {
return new WP_Error( 'missing_slug', __( 'Missing slug.', 'remote-data-blocks' ) );
}

if ( ! preg_match( '/^[a-z0-9-]+$/', $slug ) ) {
return new WP_Error( 'invalid_slug', __( 'Invalid slug.', 'remote-data-blocks' ) );
}

$data_sources = self::get_data_sources();
$data_sources = array_filter( $data_sources, function ( $source ) use ( $uuid ) {
return $source->uuid !== $uuid;
} );

$slug_exists = array_filter( $data_sources, function ( $source ) use ( $slug ) {
return $source->slug === $slug;
} );

if ( ! empty( $slug_exists ) ) {
return new WP_Error( 'slug_already_taken', __( 'Slug already taken.', 'remote-data-blocks' ) );
}

return true;
}

private static function validate_airtable_source( $source ) {
if ( empty( $source->token ) ) {
return new WP_Error( 'missing_token', __( 'Missing token.', 'remote-data-blocks' ) );
}

// Validate base is not empty and is an object with id and name fields with string values
if ( empty( $source->base ) ) {
return new WP_Error( 'missing_base', __( 'Missing base.', 'remote-data-blocks' ) );
}

if ( empty( $source->base['id'] ) || empty( $source->base['name'] ) ) {
return new WP_Error( 'invalid_base', __( 'Invalid base. Must have id and name fields.', 'remote-data-blocks' ) );
}

// Validate table is not empty and is an object with id and name fields with string values
if ( empty( $source->table ) ) {
return new WP_Error( 'missing_table', __( 'Missing table.', 'remote-data-blocks' ) );
}

if ( empty( $source->table['id'] ) || empty( $source->table['name'] ) ) {
return new WP_Error( 'invalid_table', __( 'Invalid table. Must have id and name fields.', 'remote-data-blocks' ) );
}

return (object) [
'uuid' => $source->uuid,
'token' => sanitize_text_field( $source->token ),
'service' => 'airtable',
'base' => $source->base,
'table' => $source->table,
'slug' => sanitize_text_field( $source->slug ),
];
}

public static function validate_shopify_source( $source ) {
if ( empty( $source->token ) ) {
return new WP_Error( 'missing_token', __( 'Missing token.', 'remote-data-blocks' ) );
}

return (object) [
'uuid' => $source->uuid,
'token' => sanitize_text_field( $source->token ),
'service' => 'shopify',
'store' => sanitize_text_field( $source->store ),
'slug' => sanitize_text_field( $source->slug ),
];
}

public static function validate_source( $source ) {
if ( ! is_object( $source ) ) {
return new WP_Error( 'invalid_data_source', __( 'Invalid data source.', 'remote-data-blocks' ) );
Expand All @@ -21,43 +103,29 @@ public static function validate_source( $source ) {
return new WP_Error( 'missing_uuid', __( 'Missing UUID.', 'remote-data-blocks' ) );
}


if ( ! self::is_uuid4( $source->uuid ) ) {
return new WP_Error( 'invalid_uuid', __( 'Invalid UUID.', 'remote-data-blocks' ) );
}

if ( ! in_array( $source->service, self::DATA_SOURCE_TYPES ) ) {
return new WP_Error( 'unsupported_data_source_type', __( 'Unsupported data source type.', 'remote-data-blocks' ) );
}

if ( 'airtable' === $source->service ) {
if ( empty( $source->token ) ) {
return new WP_Error( 'missing_token', __( 'Missing token.', 'remote-data-blocks' ) );
}
$slug_validation = self::validate_slug( $source->slug, $source->uuid );

return (object) [
'uuid' => $source->uuid,
'token' => sanitize_text_field( $source->token ),
'service' => 'airtable',
'base' => sanitize_text_field( $source->base ),
'table' => sanitize_text_field( $source->table ),
];
if ( is_wp_error( $slug_validation ) ) {
return $slug_validation;
}


if ( 'shopify' === $source->service ) {
if ( empty( $source->token ) ) {
return new WP_Error( 'missing_token', __( 'Missing token.', 'remote-data-blocks' ) );
}

return (object) [
'uuid' => $source->uuid,
'store' => sanitize_text_field( $source->store ),
'token' => sanitize_text_field( $source->token ),
'service' => 'shopify',
];
if ( ! in_array( $source->service, self::DATA_SOURCE_TYPES ) ) {
return new WP_Error( 'unsupported_data_source_type', __( 'Unsupported data source type.', 'remote-data-blocks' ) );
}

return new WP_Error( 'unsupported_data_source', __( 'Unsupported data source.', 'remote-data-blocks' ) );
switch ( $source->service ) {
case 'airtable':
return self::validate_airtable_source( $source );
case 'shopify':
return self::validate_shopify_source( $source );
default:
return new WP_Error( 'unsupported_data_source', __( 'Unsupported data source.', 'remote-data-blocks' ) );
}
}

public static function register_new_data_source( $settings ) {
Expand Down
42 changes: 42 additions & 0 deletions src/components/tag/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { __experimentalText as Text } from '@wordpress/components';
import React from 'react';
import './style.scss';

interface TagProps {
label?: string;
value: string;
id: string;
}

const styles = {
label: {
backgroundColor: '#17a2b8',
padding: '2px',
fontSize: '10px',
},
value: {
backgroundColor: '#e6e6fa',
padding: '2px',
fontSize: '10px',
},
container: {
border: '1px solid #17a2b8',
borderRadius: '8px',
display: 'inline-flex',
color: '#fff',
overflow: 'hidden',
},
};

export const Tag = ( { label, value, id }: TagProps ) => (
<div id={ id } style={ styles.container }>
{ label && (
<Text style={ styles.label } optimizeReadabilityFor={ styles.label.backgroundColor }>
{ label }
</Text>
) }
<Text style={ styles.value } optimizeReadabilityFor={ styles.value.backgroundColor }>
{ value }
</Text>
</div>
);
5 changes: 5 additions & 0 deletions src/components/tag/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.tag {
gap: 10px;
border-radius: 4px;
border: 1px solid #e0e0e0;
}
6 changes: 5 additions & 1 deletion src/data-sources/AddDataSourceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ const AddDataSourceModal = ( { onSubmit }: AddDataSourceModalProps ) => {
{ __( 'Add Data Source', 'remote-data-blocks' ) }
</Button>
{ isOpen && (
<Modal title={ __( 'Add a Data Source' ) } onRequestClose={ resetModal }>
<Modal
title={ __( 'Add a Data Source' ) }
onRequestClose={ resetModal }
className="add-data-source-modal"
>
<Panel>
<PanelBody>
<PanelRow>
Expand Down
Loading