Skip to content

Commit

Permalink
Merge pull request #606 from 10up/enhancement/588
Browse files Browse the repository at this point in the history
Add support for role/user based access
  • Loading branch information
dkotter authored Nov 21, 2023
2 parents fffd3ec + 1211035 commit 65cdddf
Show file tree
Hide file tree
Showing 55 changed files with 5,142 additions and 2,297 deletions.
49 changes: 24 additions & 25 deletions includes/Classifai/Admin/BulkActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,14 @@ public function register() {
* Register bulk actions for language processing.
*/
public function register_language_processing_hooks() {
$this->chat_gpt = new ChatGPT( false );
$this->embeddings = new Embeddings( false );
$this->chat_gpt = new ChatGPT( false );
$this->embeddings = new Embeddings( false );
$this->text_to_speech = new TextToSpeech( false );

$user_roles = wp_get_current_user()->roles ?? [];
$embedding_settings = $this->embeddings->get_settings();
$embeddings_post_types = [];
$nlu_post_types = get_supported_post_types();
$text_to_speech_post_types = get_tts_supported_post_types();
$chat_gpt_post_types = [];
$chat_gpt_settings = $this->chat_gpt->get_settings();

// Set up the save post handler if we have any post types.
if ( ! empty( $nlu_post_types ) || ! empty( $text_to_speech_post_types ) ) {
Expand All @@ -87,23 +85,26 @@ public function register_language_processing_hooks() {

// Set up the ChatGPT post types if the feature is enabled. Otherwise clear our handler.
if (
isset( $chat_gpt_settings['enable_excerpt'] ) &&
1 === (int) $chat_gpt_settings['enable_excerpt'] &&
! empty( $chat_gpt_settings['roles'] ) &&
empty( array_diff( $user_roles, $chat_gpt_settings['roles'] ) )
$this->chat_gpt &&
$this->chat_gpt->is_feature_enabled( 'excerpt_generation' )
) {
$chat_gpt_post_types = array_keys( get_post_types_for_language_settings() );
} else {
$this->chat_gpt = null;
}

// Set up the embeddings post types if the feature is enabled. Otherwise clear our embeddings handler.
if ( isset( $embedding_settings['enable_classification'] ) && 1 === (int) $embedding_settings['enable_classification'] ) {
if ( $this->embeddings && $this->embeddings->is_feature_enabled( 'classification' ) ) {
$embeddings_post_types = $this->embeddings->supported_post_types();
} else {
$this->embeddings = null;
}

// Clear our TextToSpeech handler if no post types are set up.
if ( empty( $text_to_speech_post_types ) ) {
$this->text_to_speech = null;
}

// Merge our post types together and make them unique.
$post_types = array_unique( array_merge( $chat_gpt_post_types, $embeddings_post_types, $nlu_post_types, $text_to_speech_post_types ) );

Expand Down Expand Up @@ -161,7 +162,8 @@ public function register_bulk_actions( $bulk_actions ) {

if (
is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) &&
in_array( get_current_screen()->post_type, $this->text_to_speech->get_supported_post_types(), true )
in_array( get_current_screen()->post_type, get_tts_supported_post_types(), true ) &&
$this->text_to_speech->is_feature_enabled( 'text_to_speech' )
) {
$bulk_actions['text_to_speech'] = __( 'Text to speech', 'classifai' );
}
Expand All @@ -177,17 +179,16 @@ public function register_bulk_actions( $bulk_actions ) {
* @return array
*/
public function register_media_bulk_actions( $bulk_actions ) {
$computer_vision_settings = $this->computer_vision->get_settings();
$whisper_enabled = $this->whisper->is_feature_enabled();
$whisper_enabled = $this->whisper->is_feature_enabled( 'speech_to_text' );

if (
'no' !== $computer_vision_settings['enable_image_tagging'] ||
! empty( $this->computer_vision->get_alt_text_settings() )
$this->computer_vision->is_feature_enabled( 'image_tagging' ) ||
$this->computer_vision->is_feature_enabled( 'image_captions' )
) {
$bulk_actions['scan_image'] = __( 'Scan image', 'classifai' );
}

if ( isset( $computer_vision_settings['enable_smart_cropping'] ) && '1' === $computer_vision_settings['enable_smart_cropping'] ) {
if ( $this->computer_vision && $this->computer_vision->is_feature_enabled( 'smart_cropping' ) ) {
$bulk_actions['smart_crop'] = __( 'Smart crop', 'classifai' );
}

Expand Down Expand Up @@ -415,14 +416,12 @@ public function register_row_action( $actions, $post ) {
}
}

if ( is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) ) {
if ( in_array( $post->post_type, $this->text_to_speech->get_supported_post_types(), true ) ) {
$actions['text_to_speech'] = sprintf(
'<a href="%s">%s</a>',
esc_url( wp_nonce_url( admin_url( sprintf( 'edit.php?action=text_to_speech&ids=%d&post_type=%s', $post->ID, $post->post_type ) ), 'bulk-posts' ) ),
esc_html__( 'Text to speech', 'classifai' )
);
}
if ( is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) && $this->text_to_speech->is_feature_enabled( 'text_to_speech' ) && in_array( $post->post_type, get_tts_supported_post_types(), true ) ) {
$actions['text_to_speech'] = sprintf(
'<a href="%s">%s</a>',
esc_url( wp_nonce_url( admin_url( sprintf( 'edit.php?action=text_to_speech&ids=%d&post_type=%s', $post->ID, $post->post_type ) ), 'bulk-posts' ) ),
esc_html__( 'Text to speech', 'classifai' )
);
}

return $actions;
Expand All @@ -437,7 +436,7 @@ public function register_row_action( $actions, $post ) {
*/
public function register_media_row_action( $actions, $post ) {
$whisper_settings = $this->whisper->get_settings();
$whisper_enabled = $this->whisper->is_feature_enabled( $post->ID );
$whisper_enabled = $this->whisper->is_feature_enabled( 'speech_to_text', $post->ID );

if ( is_wp_error( $whisper_enabled ) ) {
return $actions;
Expand Down
182 changes: 182 additions & 0 deletions includes/Classifai/Admin/UserProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

namespace Classifai\Admin;

use Classifai\Providers\AccessControl;
use Classifai\Providers\Provider;
use Classifai\Services\Service;

use function Classifai\get_plugin;

/**
* The UserProfile class provides opt-out settings for ClassifAI feature on user profile page.
*
* @since 2.4.0
*/
class UserProfile {

/**
* The user meta key for storing the list of opted out features.
*
* @var string $opt_out_key
*/
private $opt_out_key = 'classifai_opted_out_features';

/**
* Initialize the class.
*/
public function init() {
add_action( 'show_user_profile', array( $this, 'user_settings' ) );
add_action( 'edit_user_profile', array( $this, 'user_settings' ) );

add_action( 'personal_options_update', array( $this, 'save_user_settings' ) );
add_action( 'edit_user_profile_update', array( $this, 'save_user_settings' ) );
}

/**
* Add ClassifAI features opt-out checkboxes to user profile and edit user.
*
* @param \WP_User $user User object.
* @return void
*/
public function user_settings( \WP_User $user ) {
$user_id = $user->ID;

// Bail if user is not current user or current user cannot edit the user.
if ( get_current_user_id() !== $user_id && ! current_user_can( 'edit_user', $user_id ) ) {
return;
}

// Bail if user is not allowed to access ClassifAI features.
$features = $this->get_allowed_features( $user->ID );
if ( empty( $features ) ) {
return;
}
?>
<div id="classifai-profile-features-section">
<h3><?php esc_html_e( 'ClassifAI features', 'classifai' ); ?></h3>

<table class="form-table" role="presentation">
<?php
$opted_out_features = (array) get_user_meta( $user->ID, $this->opt_out_key, true );
foreach ( $features as $feature => $feature_name ) {
?>
<tr class="classifai-features-row">
<th scope="row"><?php echo esc_html( $feature_name ); ?></th>
<td >
<label for="<?php echo esc_attr( $this->opt_out_key . '_' . $feature ); ?>">
<input
name="<?php echo esc_attr( $this->opt_out_key . '[]' ); ?>"
type="checkbox"
id="<?php echo esc_attr( $this->opt_out_key . '_' . $feature ); ?>"
value="<?php echo esc_attr( $feature ); ?>"
<?php checked( true, in_array( $feature, $opted_out_features, true ), true ); ?>
/>
<?php
/* translators: %s: Feature name. */
echo esc_html( sprintf( __( 'Opt out of using the %s feature.', 'classifai' ), strtolower( $feature_name ) ) );
?>
</label>
</td>
</tr>
<?php
}
wp_nonce_field( 'classifai_out_out_features', 'classifai_out_out_features_nonce' );
?>
</table>
</div>
<?php
}

/**
* Save ClassifAI features opt-out settings.
*
* @param int $user_id User ID.
* @return void
*/
public function save_user_settings( $user_id ) {
if (
! isset( $_POST['classifai_out_out_features_nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_out_out_features_nonce'] ) ), 'classifai_out_out_features' )
) {
return;
}

$user_id = (int) $user_id;

if ( get_current_user_id() !== $user_id && ! current_user_can( 'edit_user', $user_id ) ) {
return;
}

$opted_out_feautures = isset( $_POST['classifai_opted_out_features'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['classifai_opted_out_features'] ) ) : array();

update_user_meta( $user_id, $this->opt_out_key, $opted_out_feautures );
}

/**
* Get the list of features user has access to and user opt-out is enabled.
*
* @param int $user_id User ID.
* @return array List of features.
*/
public function get_allowed_features( $user_id ) {
$user = get_user_by( 'id', $user_id );
if ( ! $user ) {
return array();
}

$user_roles = $user->roles ?? [];
$allowed_features = array();

$services = get_plugin()->services;
if ( ! isset( $services['service_manager'] ) || ! $services['service_manager']->service_classes ) {
return $allowed_features;
}

$service_classes = $services['service_manager']->service_classes;
foreach ( $service_classes as $service_class ) {
if ( ! $service_class instanceof Service || empty( $service_class->provider_classes ) ) {
continue;
}

foreach ( $service_class->provider_classes as $provider_class ) {
if ( ! $provider_class instanceof Provider ) {
continue;
}
$provider_features = $provider_class->get_features();
if ( empty( $provider_features ) ) {
continue;
}
foreach ( $provider_features as $feature => $feature_name ) {
$access_control = new AccessControl( $provider_class, $feature );

// Check if feature has user based opt-out enabled.
if ( $access_control->is_user_based_opt_out_enabled() ) {
// Check if user has access to the feature by role.
$allowed_roles = $access_control->get_allowed_roles();
if (
$access_control->is_role_based_access_enabled() &&
! empty( $allowed_roles ) &&
! empty( array_intersect( $user_roles, $allowed_roles ) )
) {
$allowed_features[ $feature ] = $feature_name;
continue;
}

// Check if user has access to the feature.
$allowed_users = $access_control->get_allowed_users();
if (
$access_control->is_user_based_access_enabled() &&
! empty( $allowed_users ) &&
in_array( $user_id, $allowed_users, true )
) {
$allowed_features[ $feature ] = $feature_name;
}
}
}
}
}

return $allowed_features;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@
"supports": {
"html": false
},
"editorScript": "file:../../../../dist/recommended-content-block.js",
"editorScript": "recommended-content-block-editor-script",
"style": "recommended-content-block-style"
}
15 changes: 15 additions & 0 deletions includes/Classifai/Blocks/recommended-content-block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ const RecommendedContentBlockEdit = ( props ) => {
isActive: displayLayout === 'grid',
},
];
const { hasRecommendedContentAccess } = window;
if ( ! hasRecommendedContentAccess ) {
return (
<Placeholder
label={ __( 'ClassifAI Recommended Content', 'classifai' ) }
>
<p>
{ __(
"You don't have access to the Recommended Content block. Please contact the administrator to request access.",
'classifai'
) }
</p>
</Placeholder>
);
}

return (
<div { ...blockProps }>
Expand Down
20 changes: 20 additions & 0 deletions includes/Classifai/Blocks/recommended-content-block/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Classifai\Blocks\RecommendedContentBlock;

use function Classifai\get_asset_info;
use Classifai\Providers\Azure\Personalizer;

/**
Expand All @@ -16,6 +17,25 @@ function register() {
$n = function( $function ) {
return __NAMESPACE__ . "\\$function";
};

$personalizer = new Personalizer( false );
wp_register_script(
'recommended-content-block-editor-script',
CLASSIFAI_PLUGIN_URL . 'dist/recommended-content-block.js',
get_asset_info( 'recommended-content-block', 'dependencies' ),
get_asset_info( 'recommended-content-block', 'version' ),
true
);

wp_add_inline_script(
'recommended-content-block-editor-script',
sprintf(
'var hasRecommendedContentAccess = %d;',
$personalizer->is_feature_enabled( 'recommended_content' )
),
'before'
);

// Register the block.
register_block_type_from_metadata(
CLASSIFAI_PLUGIN_DIR . '/includes/Classifai/Blocks/recommended-content-block', // this is the directory where the block.json is found.
Expand Down
Loading

0 comments on commit 65cdddf

Please sign in to comment.