Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Review

on: [pull_request]

env:
TARGET_DRUPAL_CORE_VERSION: 11

jobs:
drupal-lint:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- name: Lint Drupal
run: |
docker compose --profile lint run drupal-lint

drupal-check:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- name: Check Drupal compatibility
run: |
docker compose --profile lint run drupal-check
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# AI Sorting

Intelligent content ordering for Drupal Views using machine learning. Content automatically learns which items engage users most and surfaces the best-performing content.
Intelligent content ordering for Drupal Views using machine learning. Content
automatically learns which items engage users most and surfaces the
best-performing content.

## Features

Expand Down Expand Up @@ -36,12 +38,14 @@ Intelligent content ordering for Drupal Views using machine learning. Content au
## Configuration

- **Cache Lifetime** - How often content order refreshes
- **Automatic Cache Setup** - Views cache automatically configured for optimal performance
- **Automatic Cache Setup** - Views cache automatically configured for optimal
performance

## Dependencies

- [RL (Reinforcement Learning) module](https://www.drupal.org/project/rl)

## Related Modules

- [RL module](https://www.drupal.org/project/rl) - Core Thompson Sampling algorithm and API for developers
- [RL module](https://www.drupal.org/project/rl) - Core Thompson Sampling
algorithm and API for developers
2 changes: 1 addition & 1 deletion ai_sorting.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ core_version_requirement: ^10.3 | ^11
package: Custom
dependencies:
- drupal:views
- rl
- rl
2 changes: 1 addition & 1 deletion ai_sorting.libraries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ ai_sorting_rewards:
js/ai-sorting-rewards.js: {}
dependencies:
- core/drupal
- core/drupalSettings
- core/drupalSettings
44 changes: 27 additions & 17 deletions ai_sorting.module
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Primary module file for AI Sorting.
*/

use Drupal\node\NodeInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\views\ViewExecutable;
use Drupal\Core\Url;
Expand All @@ -17,7 +18,10 @@ function ai_sorting_help($route_name, RouteMatchInterface $route_match) {
case 'help.page.ai_sorting':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The AI Sorting module provides an intelligent sorting mechanism for Drupal Views using reinforcement learning algorithms.') . '</p>';
$output .= '<p>' . t(
'The AI Sorting module provides an intelligent sorting mechanism
for Drupal Views using reinforcement learning algorithms.'
) . '</p>';
return $output;
}
}
Expand All @@ -32,44 +36,50 @@ function ai_sorting_views_pre_render(ViewExecutable $view) {
return;
}

// Collect node IDs and URLs for JavaScript tracking
// Collect node IDs and URLs for JavaScript tracking.
$nids = [];
$nid_url_map = [];

foreach ($view->result as $index => $row) {

$nid = NULL;
// Try different ways to get the node ID

// Try different ways to get the node ID.
if (isset($row->nid)) {
$nid = $row->nid;
} elseif (isset($row->node_field_data_nid)) {
}
elseif (isset($row->node_field_data_nid)) {
$nid = $row->node_field_data_nid;
} elseif (isset($row->_entity) && $row->_entity instanceof \Drupal\node\NodeInterface) {
}
elseif (isset($row->_entity) && $row->_entity instanceof NodeInterface) {
$nid = $row->_entity->id();
} elseif (isset($row->_object) && method_exists($row->_object, 'id')) {
}
elseif (isset($row->_object) && method_exists($row->_object, 'id')) {
$nid = $row->_object->id();
}

if ($nid) {
$nids[] = $nid;
$nid_url_map[$nid] = Url::fromRoute('entity.node.canonical', ['node' => $nid])->toString();
$nid_url_map[$nid] = Url::fromRoute('entity.node.canonical',
['node' => $nid])->toString();
}
}

if (!empty($nids)) {
// Generate experiment UUID from view and display
// Generate experiment UUID from view and display.
$experiment_uuid = sha1($view->id() . ':' . $view->current_display);

// Register this experiment with the RL module
$registration_service = \Drupal::service('ai_sorting.experiment_registration');
// Register this experiment with the RL module.
$registration_service = \Drupal::service(
'ai_sorting.experiment_registration'
);
$registration_service->registerExperiment($experiment_uuid);

// Get RL module path for optimized endpoint
// Get RL module path for optimized endpoint.
$rl_path = \Drupal::service('extension.list.module')->getPath('rl');
$base_path = \Drupal::request()->getBasePath();

// Attach JavaScript libraries and settings
// Attach JavaScript libraries and settings.
$view->element['#attached']['library'][] = 'ai_sorting/ai_sorting_turns';
$view->element['#attached']['library'][] = 'ai_sorting/ai_sorting_rewards';
$view->element['#attached']['drupalSettings']['aiSorting']['views'][$view->id()] = [
Expand All @@ -93,4 +103,4 @@ function ai_sorting_views_data_alter(array &$data) {
'id' => 'ai_sorting',
],
];
}
}
2 changes: 1 addition & 1 deletion ai_sorting.permissions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
administer ai sorting:
title: 'Administer AI Sorting'
description: 'Configure AI sorting parameters in Views'
description: 'Configure AI sorting parameters in Views'
1 change: 0 additions & 1 deletion ai_sorting.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ services:
ai_sorting.experiment_registration:
class: Drupal\ai_sorting\Service\ExperimentRegistrationService
arguments: ['@rl.experiment_registry']

32 changes: 32 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:

drupal-lint:
image: composer:2.7
profiles: ["lint"]
working_dir: /src
command: bash -c "./scripts/run-drupal-lint.sh"
environment:
TARGET_DRUPAL_CORE_VERSION: 11
volumes:
- .:/src

drupal-lint-auto-fix:
image: composer:2.7
profiles: ["lint"]
working_dir: /src
command: bash -c "./scripts/run-drupal-lint-auto-fix.sh"
environment:
TARGET_DRUPAL_CORE_VERSION: 11
volumes:
- .:/src

drupal-check:
image: composer:2.7
profiles: ["lint"]
working_dir: /
command: bash -c "/src/scripts/run-drupal-check.sh"
tty: true
environment:
DRUPAL_RECOMMENDED_PROJECT: 11.2.x-dev
volumes:
- .:/src
21 changes: 19 additions & 2 deletions docs/ai_sorting_project_desc.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
<h2>About AI Sorting</h2>
<p>This module is part of the <a href="https://www.drupal.org/project/ai">AI module</a> ecosystem and included in <a href="https://www.drupal.org/project/dxpr_cms">DXPR CMS</a>.</p>

<blockquote>Transform your content marketing with AI Sorting - the Drupal module that automatically learns which content engages your audience and promotes it for maximum impact. Stop guessing what works and let machine learning optimize your content visibility.</blockquote>

<p>Transform your content marketing with <strong>AI Sorting</strong> - the Drupal module that automatically learns which content engages your audience and promotes it for maximum impact. Stop guessing what works and let machine learning optimize your content visibility.</p>
<h3>You need AI Sorting if</h3>
<ul>
<li>You want your best-performing content to automatically rise to the top</li>
<li>You need machine learning to optimize content visibility without manual work</li>
<li>You manage blogs, news feeds, or product catalogs with varying engagement</li>
<li>You want real-time content optimization based on user interactions</li>
<li>You prefer data-driven content ranking over manual curation</li>
</ul>

<h2>About AI Sorting</h2>

<h3>Boost Your Content Performance</h3>

Expand Down Expand Up @@ -68,6 +79,12 @@ <h3>Enterprise-Ready</h3>
<li><strong>Mobile Responsive</strong> - Works across all devices and platforms</li>
</ul>

<div class="note-tip">
<h4>Prefer a turnkey demo site?</h4>
<p>Spin up <strong>DXPR CMS</strong>—Drupal pre-configured with DXPR Builder, DXPR Theme, AI Sorting module, and security best practices.</p>
<p><a href="https://www.drupal.org/project/dxpr_cms" title="DXPR CMS platform">Get DXPR CMS »</a></p>
</div>

<h3>Dependencies</h3>

<ul>
Expand Down
22 changes: 22 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<ruleset name="Module">
<description>Drupal 11 coding standards for module.</description>
<file>.</file>
<arg name="extensions" value="php,module,inc,install,test,profile,theme,info,txt,md,yml"/>
<config name="drupal_core_version" value="11"/>

<exclude-pattern>*/.git/*</exclude-pattern>
<exclude-pattern>*/config/*</exclude-pattern>
<exclude-pattern>*/css/*</exclude-pattern>
<exclude-pattern>*/js/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/.github/*</exclude-pattern>

<rule ref="Drupal">
<!-- Drupal.org packages add info keys automatically -->
<exclude name="Drupal.InfoFiles.AutoAddedKeys"/>
</rule>

<rule ref="DrupalPractice"/>
</ruleset>
33 changes: 33 additions & 0 deletions scripts/prepare-drupal-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
set -e

if [ -z "$TARGET_DRUPAL_CORE_VERSION" ]; then
# default to target Drupal 11, you can override this by setting the secrets value on your github repo
TARGET_DRUPAL_CORE_VERSION=11
fi

echo "php --version"
php --version
echo "composer --version"
composer --version

echo "\$COMPOSER_HOME: $COMPOSER_HOME"
echo "TARGET_DRUPAL_CORE_VERSION: $TARGET_DRUPAL_CORE_VERSION"

# Add this line to avoid the plugin prompt
composer config --global allow-plugins.dealerdirect/phpcodesniffer-composer-installer true

composer global require drupal/coder --dev
composer global require phpcompatibility/php-compatibility --dev

export PATH="$PATH:$COMPOSER_HOME/vendor/bin"

composer global require dealerdirect/phpcodesniffer-composer-installer --dev

composer global show -P
phpcs -i

phpcs --config-set colors 1
phpcs --config-set drupal_core_version 11$TARGET_DRUPAL_CORE_VERSION

phpcs --config-show
38 changes: 38 additions & 0 deletions scripts/run-drupal-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
set -vo pipefail

DRUPAL_RECOMMENDED_PROJECT=${DRUPAL_RECOMMENDED_PROJECT:-10.3.x-dev}
PHP_EXTENSIONS="gd"
DRUPAL_CHECK_TOOL="mglaman/drupal-check"

# Install required PHP extensions
for ext in $PHP_EXTENSIONS; do
if ! php -m | grep -q $ext; then
apk update && apk add --no-cache ${ext}-dev
docker-php-ext-install $ext
fi
done

# Create Drupal project if it doesn't exist
if [ ! -d "/drupal" ]; then
composer create-project drupal/recommended-project=$DRUPAL_RECOMMENDED_PROJECT drupal --no-interaction --stability=dev
fi

cd drupal
mkdir -p web/modules/contrib/

# Symlink analyze_ai_brand_voice if not already linked
if [ ! -L "web/modules/contrib/analyze_ai_brand_voice" ]; then
ln -s /src web/modules/contrib/analyze_ai_brand_voice
fi

# Install the statistic modules if D11 (removed from core).
if [[ $DRUPAL_RECOMMENDED_PROJECT == 11.* ]]; then
composer require drupal/statistics
fi

# Install drupal-check
composer require $DRUPAL_CHECK_TOOL --dev

# Run drupal-check
./vendor/bin/drupal-check --drupal-root . -ad web/modules/contrib/analyze_ai_brand_voice
13 changes: 13 additions & 0 deletions scripts/run-drupal-lint-auto-fix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

source scripts/prepare-drupal-lint.sh

phpcbf --standard=Drupal \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,ai_sorting/vendor,.github,vendor \
.

phpcbf --standard=DrupalPractice \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,ai_sorting/vendor,.github,vendor \
.
42 changes: 42 additions & 0 deletions scripts/run-drupal-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
source scripts/prepare-drupal-lint.sh

EXIT_CODE=0

echo "---- Checking with PHPCompatibility PHP 8.3 and up ----"
phpcs --standard=PHPCompatibility \
--runtime-set testVersion 8.3- \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,ai_sorting/vendor,.github,vendor \
-v \
.
status=$?
if [ $status -ne 0 ]; then
EXIT_CODE=$status
fi

echo "---- Checking with Drupal standard... ----"
phpcs --standard=Drupal \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,ai_sorting/vendor,.github,vendor \
-v \
.
status=$?
if [ $status -ne 0 ]; then
EXIT_CODE=$status
fi

echo "---- Checking with DrupalPractice standard... ----"
phpcs --standard=DrupalPractice \
--extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml \
--ignore=node_modules,ai_sorting/vendor,.github,vendor \
-v \
.

status=$?
if [ $status -ne 0 ]; then
EXIT_CODE=$status
fi

# Exit with failure if any of the checks failed
exit $EXIT_CODE
Loading
Loading