Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:10up/classifai into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Sidsector9 committed Feb 9, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 8f0655e + 9f4ee48 commit ba93cbe
Showing 46 changed files with 631 additions and 1,120 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/repo-automator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: 'Repo Automator'
on:
issues:
types:
- opened
push:
branches:
- develop
pull_request:
types:
- opened
- edited
- synchronize
- converted_to_draft
- ready_for_review
branches:
- develop

jobs:
Validate:
runs-on: ubuntu-latest
steps:
- uses: 10up/action-repo-automator@trunk
with:
fail-label: needs:feedback
pass-label: needs:code-review
conflict-label: needs:refresh
reviewers: |
dkotter
team:open-source-practice
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ Tap into leading cloud-based services like [OpenAI](https://openai.com/), [Micro
* Moderate incoming comments for sensitive content using [OpenAI's Moderation API](https://platform.openai.com/docs/guides/moderation)
* Convert text content into audio and output a "read-to-me" feature on the front-end to play this audio using [Microsoft Azure's Text to Speech API](https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/text-to-speech)
* Classify post content using [IBM Watson's Natural Language Understanding API](https://www.ibm.com/watson/services/natural-language-understanding/) and [OpenAI's Embedding API](https://platform.openai.com/docs/guides/embeddings)
* BETA: Recommend content based on overall site traffic via [Microsoft Azure's AI Personalizer API](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) _(note that we're gathering feedback on this feature and may significantly iterate depending on community input)_
* BETA: Recommend content based on overall site traffic via [Microsoft Azure's AI Personalizer API](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) *(note that this service has been [deprecated by Microsoft](https://learn.microsoft.com/en-us/azure/ai-services/personalizer/) and as such, will no longer work. We are looking to replace this with a new provider to maintain the same functionality (see [issue#392](https://github.com/10up/classifai/issues/392))*
* Generate image alt text, image tags, and smartly crop images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/)
* Scan images and PDF files for embedded text and save for use in post meta using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/)
* Bulk classify content with [WP-CLI](https://wp-cli.org/)
@@ -393,6 +393,8 @@ Note that [Azure AI Vision](https://docs.microsoft.com/en-us/azure/cognitive-ser

## Set Up Recommended Content (via Microsoft Azure AI Personalizer)

Azure AI Personalizer has been retired by Microsoft [as of September 2023](https://learn.microsoft.com/en-us/azure/ai-services/personalizer/). The service will continue to work until 2026 but Personalizer resources can no longer be created. As such, consider this provider deprecated and be aware that we will be removing this in the near future. We are hoping to replace with a new provider to maintain the same functionality (see [issue#392](https://github.com/10up/classifai/issues/392)).

Note that [Personalizer](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) requires sufficient data volume to enable Personalizer to learn. In general, we recommend a minimum of ~1,000 events per day to ensure Personalizer learns effectively. If Personalizer doesn't receive sufficient data, the service takes longer to determine the best actions.

### 1. Sign up for Azure services
136 changes: 136 additions & 0 deletions includes/Classifai/Admin/Notifications.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

namespace Classifai\Admin;

use Classifai\Features\DescriptiveTextGenerator;

class Notifications {

/**
@@ -24,6 +26,9 @@ public function can_register(): bool {
public function register() {
add_action( 'classifai_activation_hook', [ $this, 'add_activation_notice' ] );
add_action( 'admin_notices', [ $this, 'maybe_render_notices' ], 0 );
add_action( 'admin_notices', [ $this, 'thresholds_update_notice' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'add_dismiss_script' ] );
add_action( 'wp_ajax_classifai_dismiss_notice', [ $this, 'ajax_maybe_dismiss_notice' ] );
}

/**
@@ -76,4 +81,135 @@ public function maybe_render_notices() {
delete_transient( 'classifai_activation_notice' );
}
}

/**
* Print out a script to dismiss a notice.
*
* This allows us to save that a user has dismissed a notice.
*
* Influenced by https://github.com/WPTT/admin-notices/blob/af52f563398b42cff82d38eefa55c8121d698ebe/src/Dismiss.php#L77
*/
public function add_dismiss_script() {
$nonce = wp_create_nonce( 'classifai_dismissible_notice' );
$admin_ajax_url = esc_url( admin_url( 'admin-ajax.php' ) );

$script = <<<EOD
jQuery( function() {
const dismissBtn = document.querySelector( '.classifai-dismissible-notice' );
if ( ! dismissBtn ) {
return;
}
// Add an event listener to the dismiss button.
dismissBtn.addEventListener( 'click', function( event ) {
const id = dismissBtn.getAttribute( 'data-notice' );
if ( ! id ) {
return;
}
const httpRequest = new XMLHttpRequest();
let postData = '';
// Build the data to send in our request.
// Data has to be formatted as a string here.
postData += 'notice_id=' + id;
postData += '&action=classifai_dismiss_notice';
postData += '&nonce=$nonce';
httpRequest.open( 'POST', '$admin_ajax_url' );
httpRequest.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
httpRequest.send( postData );
});
});
EOD;

wp_add_inline_script( 'common', $script, 'after' );
}

/**
* Verify ajax request and dismiss the notice.
*
* Influenced by https://github.com/WPTT/admin-notices/blob/af52f563398b42cff82d38eefa55c8121d698ebe/src/Dismiss.php#L133
*/
public function ajax_maybe_dismiss_notice() {
if ( ! isset( $_POST['action'] ) || 'classifai_dismiss_notice' !== $_POST['action'] ) {
return;
}

if ( ! isset( $_POST['notice_id'] ) ) {
return;
}

check_ajax_referer( 'classifai_dismissible_notice', 'nonce' );

$notice_id = sanitize_text_field( wp_unslash( $_POST['notice_id'] ) );

update_user_meta( get_current_user_id(), "classifai_dismissed_{$notice_id}", true );
}

/**
* Display a dismissable admin notice when a threshold may need updating.
*
* We used to recommend thresholds between 70-75% but in the latest
* version of the AI Vision API, seems 55% is a better threshold.
*/
public function thresholds_update_notice() {
$features = [
'feature_descriptive_text_generator' => 'Classifai\Features\DescriptiveTextGenerator',
];

foreach ( $features as $name => $feature_class ) {
if ( ! class_exists( $feature_class ) ) {
continue;
}

$feature_instance = new $feature_class();

// Don't show the notice if the feature is not enabled.
if ( ! $feature_instance->is_feature_enabled() ) {
continue;
}

$settings = $feature_instance->get_settings( 'ms_computer_vision' );
$key = '';
$message = '';

switch ( $feature_instance::ID ) {
case DescriptiveTextGenerator::ID:
$key = 'descriptive_confidence_threshold';
$message = __( 'The previous recommended threshold for descriptive text generation was 75% but we find better results now at around 55%.', 'classifai' );
break;
}

// Don't show the notice if the user has already dismissed it.
if ( get_user_meta( get_current_user_id(), "classifai_dismissed_{$key}", true ) ) {
continue;
}

// Don't show the notice if the threshold is already at 55% or lower.
if ( $key && isset( $settings[ $key ] ) && $settings[ $key ] <= 55 ) {
continue;
}
?>

<div class="notice notice-warning is-dismissible classifai-dismissible-notice" data-notice="<?php echo esc_attr( $key ); ?>">
<p>
<?php
echo wp_kses_post(
sprintf(
// translators: %1$s: Feature specific message; %2$s: URL to Feature settings.
__( 'ClassifAI has updated to the v3.2 of the Azure AI Vision API. %1$s <a href="%2$s">Click here to adjust those settings</a>.', 'classifai' ),
esc_html( $message ),
esc_url( admin_url( "tools.php?page=classifai&tab=image_processing&feature=$name" ) )
)
);
?>
</p>
</div>

<?php
}
}
}
9 changes: 5 additions & 4 deletions includes/Classifai/Admin/UserProfile.php
Original file line number Diff line number Diff line change
@@ -147,8 +147,6 @@ public function get_allowed_features( int $user_id ): array {
continue;
}

$role_based_access_enabled = isset( $settings['role_based_access'] ) && 1 === (int) $settings['role_based_access'];
$user_based_access_enabled = isset( $settings['user_based_access'] ) && 1 === (int) $settings['user_based_access'];
$user_based_opt_out_enabled = isset( $settings['user_based_opt_out'] ) && 1 === (int) $settings['user_based_opt_out'];

// Bail if user opt-out is not enabled.
@@ -158,8 +156,12 @@ public function get_allowed_features( int $user_id ): array {

// Check if user has access to the feature by role.
$allowed_roles = $settings['roles'] ?? [];
// For super admins that don't have a specific role on a site, treat them as admins.
if ( is_multisite() && is_super_admin( $user_id ) && empty( $user_roles ) ) {
$user_roles = [ 'administrator' ];
}

if (
$role_based_access_enabled &&
! empty( $allowed_roles ) &&
! empty( array_intersect( $user_roles, $allowed_roles ) )
) {
@@ -170,7 +172,6 @@ public function get_allowed_features( int $user_id ): array {
// Check if user has access to the feature.
$allowed_users = $settings['users'] ?? [];
if (
$user_based_access_enabled &&
! empty( $allowed_users ) &&
in_array( $user_id, $allowed_users, true )
) {
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@

namespace Classifai\Blocks\RecommendedContentBlock;

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

@@ -18,7 +19,6 @@ function register() {
return __NAMESPACE__ . "\\$function_name";
};

$personalizer = new Personalizer( false );
wp_register_script(
'recommended-content-block-editor-script',
CLASSIFAI_PLUGIN_URL . 'dist/recommended-content-block.js',
@@ -31,7 +31,7 @@ function register() {
'recommended-content-block-editor-script',
sprintf(
'var hasRecommendedContentAccess = %d;',
$personalizer->is_feature_enabled( 'recommended_content' )
( new RecommendedContent() )->is_feature_enabled()
),
'before'
);
50 changes: 21 additions & 29 deletions includes/Classifai/Command/ClassifaiCommand.php
Original file line number Diff line number Diff line change
@@ -4,15 +4,14 @@

use Classifai\Features\AudioTranscriptsGeneration;
use Classifai\Features\Classification;
use Classifai\Features\DescriptiveTextGenerator;
use Classifai\Features\ExcerptGeneration;
use Classifai\Features\ImageCropping;
use Classifai\Features\TextToSpeech;
use Classifai\Providers\Watson\APIRequest;
use Classifai\Providers\Watson\Classifier;
use Classifai\Normalizer;
use Classifai\Providers\Watson\PostClassifier;
use Classifai\Providers\Azure\ComputerVision;
use Classifai\Providers\Azure\SmartCropping;
use Classifai\Providers\OpenAI\Embeddings;

use function Classifai\Providers\Watson\get_username;
@@ -788,8 +787,7 @@ public function image( $args = [], $opts = [] ) {
$attachment_ids = $this->get_attachment_to_classify( $opts );
}

$total = count( $attachment_ids );
$classifier = new ComputerVision( false );
$total = count( $attachment_ids );

if ( empty( $total ) ) {
return \WP_CLI::log( 'No images to classify.' );
@@ -804,16 +802,26 @@ public function image( $args = [], $opts = [] ) {
$message = "Classifying $limit_total images ...";

$progress_bar = \WP_CLI\Utils\make_progress_bar( $message, $limit_total );
$text_feature = new DescriptiveTextGenerator();
$crop_feature = new ImageCropping();

for ( $index = 0; $index < $limit_total; $index++ ) {
$attachment_id = $attachment_ids[ $index ];

$progress_bar->tick();

$current_meta = wp_get_attachment_metadata( $attachment_id );
\WP_CLI::line( 'Processing ' . $attachment_id );
$classifier->generate_image_alt_tags( $current_meta, $attachment_id );
$classifier->smart_crop_image( $current_meta, $attachment_id );

$text_result = $text_feature->run( $attachment_id, 'descriptive_text' );
if ( $text_result && ! is_wp_error( $text_result ) ) {
$text_feature->save( $text_result, $attachment_id );
}

$crop_result = $crop_feature->run( $attachment_id, 'crop' );
if ( ! empty( $crop_result ) && ! is_wp_error( $crop_result ) ) {
$meta = $crop_feature->save( $crop_result, $attachment_id );
wp_update_attachment_metadata( $attachment_id, $meta );
}
}

$progress_bar->finish();
@@ -883,28 +891,12 @@ public function crop( $args = [], $opts = [] ) {

$progress_bar->tick();

$current_meta = wp_get_attachment_metadata( $attachment_id );

foreach ( $current_meta['sizes'] as $size => $size_data ) {
switch ( $provider_class::ID ) {
case ComputerVision::ID:
$smart_cropping = new SmartCropping( $settings );

if ( ! $smart_cropping->should_crop( $size ) ) {
break;
}

$data = [
'width' => $size_data['width'],
'height' => $size_data['height'],
];

$smart_thumbnail = $smart_cropping->get_cropped_thumbnail( $attachment_id, $data );
if ( is_wp_error( $smart_thumbnail ) ) {
$errors[ $attachment_id . ':' . $size_data['width'] . 'x' . $size_data['height'] ] = $smart_thumbnail;
}
break;
}
$crop_result = $image_cropping->run( $attachment_id, 'crop' );
if ( ! empty( $crop_result ) && ! is_wp_error( $crop_result ) ) {
$meta = $image_cropping->save( $crop_result, $attachment_id );
wp_update_attachment_metadata( $attachment_id, $meta );
} else {
$errors[ $attachment_id ] = $crop_result;
}
}

Loading

0 comments on commit ba93cbe

Please sign in to comment.