diff --git a/README.md b/README.md index 158fb35..dac09fe 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # AI Sentiments Analysis -AI-powered multi-dimensional text analysis measuring trust, objectivity, audience -targeting, and reading levels. +AI-powered multi-dimensional text analysis measuring trust, objectivity, +audience targeting, and reading levels. ## Features -- **Four Analysis Dimensions**: Trust & Credibility, Objectivity & Bias, Audience - Vibe Check, CEFR Reading Level +- **Four Analysis Dimensions**: Trust & Credibility, Objectivity & Bias, + Audience Vibe Check, CEFR Reading Level - **Flexible Configuration**: Add, remove, reorder sentiments dimensions via UI - **Content Type Control**: Enable/disable specific analysis types per content type diff --git a/analyze_ai_sentiments.install b/analyze_ai_sentiments.install index 8d4d7c8..b5eafa0 100644 --- a/analyze_ai_sentiments.install +++ b/analyze_ai_sentiments.install @@ -118,7 +118,8 @@ function analyze_ai_sentiments_uninstall() { } /** - * Update plugin ID from ai_sentiments_analyzer to analyze_ai_sentiments_analyzer. + * Update plugin ID from ai_sentiments_analyzer to + * analyze_ai_sentiments_analyzer. */ function analyze_ai_sentiments_update_8001() { $config_factory = \Drupal::configFactory(); diff --git a/analyze_ai_sentiments.links.action.yml b/analyze_ai_sentiments.links.action.yml deleted file mode 100644 index 8ae98a0..0000000 --- a/analyze_ai_sentiments.links.action.yml +++ /dev/null @@ -1,5 +0,0 @@ -analyze_ai_sentiments.add_sentiments: - route_name: analyze_ai_sentiments.add_sentiments - title: 'Add sentiments' - appears_on: - - analyze_ai_sentiments.settings diff --git a/analyze_ai_sentiments.module b/analyze_ai_sentiments.module index a11a171..593a480 100644 --- a/analyze_ai_sentiments.module +++ b/analyze_ai_sentiments.module @@ -6,6 +6,9 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Link; +use Drupal\Core\Url; +use Drupal\views\ViewExecutable; /** * Implements hook_entity_update(). @@ -44,3 +47,35 @@ function _analyze_ai_sentiments_is_supported_entity(EntityInterface $entity): bo return isset($status[$entity_type_id][$bundle]['ai_sentiments_analyzer']); } + +/** + * Implements hook_views_pre_view(). + */ +function analyze_ai_sentiments_views_pre_view(ViewExecutable $view, $display_id, array &$args) { + // Add settings link to the sentiments analysis view header. + if ($view->id() === 'ai_sentiments_analysis_results' && $display_id === 'page_1') { + // Build the settings link if user has permission. + $current_user = \Drupal::currentUser(); + if ($current_user->hasPermission('administer analyze settings')) { + $settings_url = Url::fromRoute('analyze_ai_sentiments.settings'); + if ($settings_url->access()) { + $link = Link::fromTextAndUrl(t('Configure settings'), $settings_url); + $link = $link->toRenderable(); + $link['#attributes']['class'][] = 'button'; + $link['#attributes']['class'][] = 'button--small'; + $link['#attributes']['class'][] = 'button--primary'; + + // Add content before the view using attachment_before as render array. + $view->attachment_before = [ + '#type' => 'container', + '#attributes' => ['class' => ['form-actions', 'views-configure-actions']], + '#weight' => -10, + 'configure_link' => $link, + '#attached' => [ + 'library' => ['system/base'], + ], + ]; + } + } + } +} diff --git a/analyze_ai_sentiments.routing.yml b/analyze_ai_sentiments.routing.yml index a2e51ab..2ed89a2 100644 --- a/analyze_ai_sentiments.routing.yml +++ b/analyze_ai_sentiments.routing.yml @@ -1,7 +1,7 @@ analyze_ai_sentiments.settings: path: '/admin/config/analyze/sentiments' defaults: - _form: '\Drupal\analyze_ai_sentiments\Form\SentimentsettingsForm' + _form: '\Drupal\analyze_ai_sentiments\Form\SentimentsSettingsForm' _title: 'Sentiments Analysis Settings' requirements: _permission: 'administer site configuration' diff --git a/analyze_ai_sentiments.services.yml b/analyze_ai_sentiments.services.yml index ba89fbd..17b7c46 100644 --- a/analyze_ai_sentiments.services.yml +++ b/analyze_ai_sentiments.services.yml @@ -1,6 +1,6 @@ services: analyze_ai_sentiments.storage: - class: Drupal\analyze_ai_sentiments\Service\SentimentstorageService + class: Drupal\analyze_ai_sentiments\Service\SentimentsStorageService arguments: ['@database', '@config.factory', '@entity_type.manager', '@renderer', '@datetime.time'] analyze_ai_sentiments.batch_service: diff --git a/config/install/views.view.ai_sentiments_analysis_results.yml b/config/install/views.view.ai_sentiments_analysis_results.yml index eebf088..1564bf4 100644 --- a/config/install/views.view.ai_sentiments_analysis_results.yml +++ b/config/install/views.view.ai_sentiments_analysis_results.yml @@ -440,8 +440,9 @@ display: sort_asc_label: Asc sort_desc_label: Desc access: - type: none - options: { } + type: perm + options: + perm: 'view analyze results' cache: type: tag options: { } diff --git a/src/Form/SentimentsSettingsForm.php b/src/Form/SentimentsSettingsForm.php index 7143195..8eda80f 100644 --- a/src/Form/SentimentsSettingsForm.php +++ b/src/Form/SentimentsSettingsForm.php @@ -7,32 +7,32 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\TypedConfigManagerInterface; -use Drupal\analyze_ai_sentiments\Service\SentimentstorageService; +use Drupal\analyze_ai_sentiments\Service\SentimentsStorageService; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Configure sentiments analysis settings. */ -class SentimentsettingsForm extends ConfigFormBase { +class SentimentsSettingsForm extends ConfigFormBase { /** * The sentiments storage service. */ - protected SentimentstorageService $sentimentstorage; + protected SentimentsStorageService $sentimentstorage; /** - * Constructs a SentimentsettingsForm object. + * Constructs a SentimentsSettingsForm object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager * The typed config manager. - * @param \Drupal\analyze_ai_sentiments\Service\SentimentstorageService $sentiments_storage + * @param \Drupal\analyze_ai_sentiments\Service\SentimentsStorageService $sentiments_storage * The sentiments storage service. */ public function __construct( ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config_manager, - SentimentstorageService $sentiments_storage, + SentimentsStorageService $sentiments_storage, ) { parent::__construct($config_factory, $typed_config_manager); $this->sentimentstorage = $sentiments_storage; @@ -129,6 +129,27 @@ public function buildForm(array $form, FormStateInterface $form_state): array { ], ]; + // Add link to reports page if user has permission. + $current_user = \Drupal::currentUser(); + if ($current_user->hasPermission('access site reports')) { + $reports_url = Url::fromRoute('view.ai_sentiments_analysis_results.page_1'); + if ($reports_url->access()) { + $form['actions_top'] = [ + '#type' => 'container', + '#attributes' => ['class' => ['form-actions']], + '#weight' => -10, + 'report_link' => [ + '#type' => 'link', + '#title' => $this->t('View reports'), + '#url' => $reports_url, + '#attributes' => [ + 'class' => ['button', 'button--small', 'button--primary'], + ], + ], + ]; + } + } + $form['table'] = [ '#type' => 'container', '#attributes' => ['class' => ['sentiments-table-container']], diff --git a/src/Plugin/Analyze/AISentimentsAnalyzer.php b/src/Plugin/Analyze/AISentimentsAnalyzer.php index 813e6ba..66de429 100644 --- a/src/Plugin/Analyze/AISentimentsAnalyzer.php +++ b/src/Plugin/Analyze/AISentimentsAnalyzer.php @@ -15,7 +15,7 @@ use Drupal\Core\Messenger\MessengerInterface; use Drupal\ai\Service\PromptJsonDecoder\PromptJsonDecoderInterface; use Drupal\Core\Link; -use Drupal\analyze_ai_sentiments\Service\SentimentstorageService; +use Drupal\analyze_ai_sentiments\Service\SentimentsStorageService; /** * A sentiments analyzer that uses AI to analyze content sentiments. @@ -58,9 +58,9 @@ final class AISentimentsAnalyzer extends AnalyzePluginBase { /** * The sentiments storage service. * - * @var \Drupal\analyze_ai_sentiments\Service\SentimentstorageService + * @var \Drupal\analyze_ai_sentiments\Service\SentimentsStorageService */ - protected SentimentstorageService $storage; + protected SentimentsStorageService $storage; /** * Creates the plugin. @@ -89,7 +89,7 @@ final class AISentimentsAnalyzer extends AnalyzePluginBase { * The messenger service. * @param \Drupal\ai\Service\PromptJsonDecoder\PromptJsonDecoderInterface $promptJsonDecoder * The prompt JSON decoder service. - * @param \Drupal\analyze_ai_sentiments\Service\SentimentstorageService $storage + * @param \Drupal\analyze_ai_sentiments\Service\SentimentsStorageService $storage * The sentiments storage service. */ public function __construct( @@ -105,7 +105,7 @@ public function __construct( protected LanguageManagerInterface $languageManager, MessengerInterface $messenger, PromptJsonDecoderInterface $promptJsonDecoder, - SentimentstorageService $storage, + SentimentsStorageService $storage, ) { parent::__construct($configuration, $plugin_id, $plugin_definition, $helper, $currentUser); $this->aiProvider = $aiProvider; @@ -156,7 +156,7 @@ protected function getConfiguredSentiments(): array { if (empty($sentiments)) { // Load defaults from the settings form. $form = \Drupal::classResolver() - ->getInstanceFromDefinition('\Drupal\analyze_ai_sentiments\Form\SentimentsettingsForm'); + ->getInstanceFromDefinition('\Drupal\analyze_ai_sentiments\Form\SentimentsSettingsForm'); return $form->getDefaultSentiments(); } @@ -379,13 +379,6 @@ public function renderFullReport(EntityInterface $entity): array { ], ]; - $build['title'] = [ - '#type' => 'html_tag', - '#tag' => 'h2', - // @phpstan-ignore-next-line - '#value' => $this->t('Sentiments Analysis'), - ]; - foreach ($enabled_sentiments as $id => $sentiments) { if (isset($scores[$id])) { // Convert -1 to +1 range to 0 to 1 for gauge. @@ -604,6 +597,7 @@ public function saveSettings(string $entity_type_id, ?string $bundle, array $set * {@inheritdoc} * * @return array + * Default settings array. */ public function getDefaultSettings(): array { $sentiments = $this->getConfiguredSentiments(); diff --git a/src/Service/SentimentsBatchService.php b/src/Service/SentimentsBatchService.php index ceb662e..6941ce7 100644 --- a/src/Service/SentimentsBatchService.php +++ b/src/Service/SentimentsBatchService.php @@ -4,10 +4,11 @@ namespace Drupal\analyze_ai_sentiments\Service; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\DependencyInjection\DependencySerializationTrait; -use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\analyze\AnalyzePluginManager; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; @@ -20,10 +21,10 @@ final class SentimentsBatchService { public function __construct( private readonly EntityTypeManagerInterface $entityTypeManager, - private readonly SentimentstorageService $storage, + private readonly SentimentsStorageService $storage, private readonly ConfigFactoryInterface $configFactory, private readonly EntityTypeBundleInfoInterface $bundleInfo, - private readonly DefaultPluginManager $analyzePluginManager, + private readonly AnalyzePluginManager $analyzePluginManager, ) { } @@ -137,10 +138,12 @@ public function processBatch(array $entities, bool $force_refresh, int $total_en } } } - catch (\Exception $e) { + catch (PluginException $e) { $context['results']['errors'][] = $this->t('Batch processing error: @message', [ '@message' => $e->getMessage(), ])->render(); + // If the analyzer cannot be created, we cannot proceed. + return; } $context['message'] = $this->t('Processed @current of @max entities...', [ diff --git a/src/Service/SentimentsStorageService.php b/src/Service/SentimentsStorageService.php index 05e25a7..10919e7 100644 --- a/src/Service/SentimentsStorageService.php +++ b/src/Service/SentimentsStorageService.php @@ -15,7 +15,7 @@ /** * Service for storing and retrieving sentiments analysis results. */ -final class SentimentstorageService { +final class SentimentsStorageService { use DependencySerializationTrait; public function __construct(