Drupal and LangChain: Building Multi-Step AI Pipelines for Enterprise CMS
Enterprise content teams have a problem that does not get talked about enough. It is not producing content, most large organisations have plenty of that. The problem is what happens to content before it gets published. Review queues that stretch for days, moderation bottlenecks where one editor is the single point of failure, policy checks that get skipped under deadline pressure, and taxonomy tagging that is inconsistent across a team of twenty people all making their own judgment calls.
I worked with an enterprise Drupal site last year that had over 3,000 pieces of content sitting in a moderation queue at any given time. Four editors, no automation, no triage. Good content was getting buried under low-quality submissions and the editors were spending most of their time on mechanical checks rather than actual editorial judgment.
What they needed was a multi-step AI pipeline sitting between content submission and human review. Something that could screen content automatically, flag policy violations, suggest taxonomy terms, score quality, and route content to the right reviewer based on what it found. That is what this post is about.
We will cover how LangChain fits into a Drupal architecture, the honest tradeoffs between the Python, JavaScript, and PHP approaches, and then go deep on building the full pipeline in PHP inside a custom Drupal module.
Why LangChain matters here
LangChain is a framework for building applications that chain multiple AI calls together, each step taking the output of the previous one as its input. Instead of sending one big prompt to an LLM and hoping for a good result, you break the problem into focused steps. One step checks for policy violations. The next scores content quality. The next suggests taxonomy terms. The next decides where to route the content for review. Each step does one thing well.
The reason this matters for an enterprise CMS is that single-prompt AI approaches get inconsistent quickly when content is varied and complex. A one-shot prompt that tries to check policy compliance, assess quality, tag taxonomy, and make a routing decision all at once tends to produce mediocre results across all four. Breaking it into a chain where each step is focused produces meaningfully better output, and more importantly, it makes each step auditable. You can see exactly where the pipeline flagged something and why.
LangChain was originally built in Python, which is where it is most mature. A JavaScript version called LangChain.js followed. There is no official PHP version, which creates an interesting architecture question for Drupal teams.
Three ways to use LangChain with Drupal
Before picking an approach, it is worth understanding what each option actually involves in practice. I have seen teams choose the wrong one based on familiarity rather than fit, and it costs them later.
Option 1: Python LangChain as a Separate Microservice
You build a small Python FastAPI or Flask service that runs LangChain pipelines. Drupal calls this service via HTTP when content needs processing and receives structured JSON back. The pipeline logic lives entirely in Python, Drupal just sends content and handles the response.
This is the most powerful option because you get the full LangChain Python ecosystem, including document loaders, vector stores, agents, and memory. The tradeoff is operational complexity. You are now running and maintaining two separate services, Python and PHP, and your team needs to be comfortable in both.
Good fit for: teams with Python expertise already on staff, complex pipelines that need LangChain agents or vector retrieval, and organisations with proper infrastructure for running multiple services.
Option 2: LangChain.js via a Node.js Microservice
Similar architecture to Option 1 but the sidecar service runs Node.js with LangChain.js instead of Python. Drupal calls it the same way via HTTP. LangChain.js has caught up significantly to the Python version in recent versions and covers most common pipeline patterns.
The advantage over Python is that JavaScript is more widely known across web development teams. The disadvantage is that LangChain.js still lags behind Python on some advanced features, and you still have the same two-service operational overhead.
Good fit for: teams with frontend JavaScript experience who want to avoid Python, simpler pipeline patterns, and organisations already running Node.js services.
Option 3: PHP Pipeline Mimicking LangChain Patterns (What We Are Building)
You implement the same chaining concepts directly in PHP using the OpenAI PHP client, no LangChain library involved. Each step in the pipeline is a focused PHP class. They chain together through a Pipeline orchestrator. The output of each step feeds into the next.
This approach keeps everything inside Drupal, no additional services to deploy or maintain, no cross-language boundaries, no HTTP overhead between steps. The tradeoff is that you implement the chaining logic yourself rather than using a ready-made framework.
Honestly, for most enterprise Drupal use cases this is the right call. The LangChain library provides a lot of features you will not need for a content moderation pipeline. What you need is the chaining pattern, structured prompts, and reliable JSON outputs, and all of that is straightforward to implement in PHP.
Good fit for: Drupal teams without Python or Node.js expertise, pipelines that do not require vector retrieval or complex agents, and organisations that want the full pipeline inside their existing Drupal infrastructure.
That is the option we are going deep on. Here is what we are building.
The Pipeline We Are Building
Four steps, each focused on one job:
Content submitted to Drupal
↓
Step 1: Policy Compliance Check
Does the content violate any publishing policies?
Output: pass / flag / reject + reason
↓
Step 2: Quality Assessment
Is the content well-written, complete, and suitable for publishing?
Output: quality score 1-10 + specific feedback
↓
Step 3: Taxonomy Suggestion
What terms should be applied to this content?
Output: suggested taxonomy terms with confidence scores
↓
Step 4: Routing Decision
Based on the above, where should this content go?
Output: auto-approve / send to editor / send to senior editor / reject
↓
Content routed to correct moderation state in Drupal
Each step receives the original content plus the outputs of all previous steps. By the time Step 4 runs, it has the policy check result, the quality score, and the taxonomy suggestions available to inform its routing decision. That context is what makes the routing intelligent rather than mechanical.
Setting up the custom Drupal module
We will build this as a custom Drupal module. Create the module structure:
modules/custom/ai_content_pipeline/
ai_content_pipeline.info.yml
ai_content_pipeline.services.yml
ai_content_pipeline.module
src/
Pipeline/
ContentModerationPipeline.php
Steps/
PolicyComplianceStep.php
QualityAssessmentStep.php
TaxonomySuggestionStep.php
RoutingDecisionStep.php
Contracts/
PipelineStepInterface.php
Service/
OpenAIService.php
The ai_content_pipeline.info.yml:
name: 'AI Content Pipeline'
type: module
description: 'Multi-step AI pipeline for intelligent content moderation'
core_version_requirement: ^10 || ^11
package: Custom
dependencies:
- drupal:node
Install the OpenAI PHP client via Composer in your Drupal project root:
composer require openai-php/client
Step 1: The Pipeline step interface
Every step in the pipeline implements this interface. It enforces a consistent contract across all steps, which makes the pipeline orchestrator simple to write and easy to extend with new steps later.
Create src/Contracts/PipelineStepInterface.php:
<?php
namespace Drupal\ai_content_pipeline\Contracts;
interface PipelineStepInterface
{
/**
* Execute this pipeline step.
*
* @param string $content The original content being processed.
* @param array $context Results from all previous steps.
*
* @return array Results from this step to pass forward.
*/
public function execute(string $content, array $context): array;
/**
* Human-readable name for this step, used in logging.
*/
public function name(): string;
}
Step 2: The OpenAI Service
Create src/Service/OpenAIService.php:
<?php
namespace Drupal\ai_content_pipeline\Service;
use OpenAI;
class OpenAIService
{
private $client;
public function __construct()
{
$api_key = \Drupal::config('ai_content_pipeline.settings')->get('openai_api_key');
$this->client = OpenAI::client($api_key);
}
public function chat(string $systemPrompt, string $userMessage): string
{
$response = $this->client->chat()->create([
'model' => 'gpt-4o',
'temperature' => 0.2,
'messages' => [
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $userMessage],
],
]);
return $response->choices[0]->message->content;
}
public function parseJson(string $raw): array
{
$clean = preg_replace('/^```json\s*/i', '', trim($raw));
$clean = preg_replace('/```$/', '', trim($clean));
$data = json_decode(trim($clean), true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['error' => 'JSON parse failed', 'raw' => $raw];
}
return $data;
}
}
The temperature is set to 0.2, lower than you might expect. For pipeline steps that are making structured decisions, you want as little creative variance as possible. The model should be analytical, not inventive.
Step 3: The Policy Compliance step
Create src/Steps/PolicyComplianceStep.php:
<?php
namespace Drupal\ai_content_pipeline\Steps;
use Drupal\ai_content_pipeline\Contracts\PipelineStepInterface;
use Drupal\ai_content_pipeline\Service\OpenAIService;
class PolicyComplianceStep implements PipelineStepInterface
{
public function __construct(private OpenAIService $ai) {}
public function name(): string
{
return 'Policy Compliance Check';
}
public function execute(string $content, array $context): array
{
$system = 'You are a content policy compliance reviewer for an enterprise CMS.
Review content against publishing policies and return JSON only.
No markdown, no explanation outside the JSON object.';
$prompt = <<<PROMPT
Review the following content against these publishing policies:
1. No hate speech, discrimination, or offensive language targeting any group.
2. No unverified factual claims presented as established fact.
3. No promotional or advertorial content disguised as editorial.
4. No personally identifiable information about private individuals.
5. No content that could create legal liability (defamation, copyright issues).
Return a JSON object with:
- "status": one of "pass", "flag", or "reject"
- "violations": array of specific violations found, empty array if none
- "reason": brief explanation of the status decision
Content to review:
{$content}
PROMPT;
$raw = $this->ai->chat($system, $prompt);
$result = $this->ai->parseJson($raw);
return [
'policy' => $result,
];
}
}
Step 4: The Quality Assessment step
Create src/Steps/QualityAssessmentStep.php:
<?php
namespace Drupal\ai_content_pipeline\Steps;
use Drupal\ai_content_pipeline\Contracts\PipelineStepInterface;
use Drupal\ai_content_pipeline\Service\OpenAIService;
class QualityAssessmentStep implements PipelineStepInterface
{
public function __construct(private OpenAIService $ai) {}
public function name(): string
{
return 'Quality Assessment';
}
public function execute(string $content, array $context): array
{
$policyStatus = $context['policy']['status'] ?? 'unknown';
$system = 'You are a senior editorial quality reviewer for an enterprise CMS.
Assess content quality objectively and return JSON only.';
$prompt = <<<PROMPT
Assess the quality of the following content. Consider:
- Clarity and readability for a general professional audience
- Completeness: does it cover the topic adequately?
- Structure: is it well organised with a logical flow?
- Accuracy indicators: does it make claims without apparent support?
- Tone: is it appropriate for professional publication?
Note: Policy compliance status from previous check is "{$policyStatus}".
Return a JSON object with:
- "score": integer from 1 to 10
- "strengths": array of what the content does well
- "weaknesses": array of specific quality issues found
- "publishable": boolean, true if quality is sufficient for publication
Content:
{$content}
PROMPT;
$raw = $this->ai->chat($system, $prompt);
$result = $this->ai->parseJson($raw);
return [
'quality' => $result,
];
}
}
Step 5: The Taxonomy Suggestion step
Create src/Steps/TaxonomySuggestionStep.php:
<?php
namespace Drupal\ai_content_pipeline\Steps;
use Drupal\ai_content_pipeline\Contracts\PipelineStepInterface;
use Drupal\ai_content_pipeline\Service\OpenAIService;
class TaxonomySuggestionStep implements PipelineStepInterface
{
private array $availableTerms = [
'topics' => ['Technology', 'Business', 'Health', 'Finance', 'Policy', 'Research', 'Opinion'],
'audience' => ['General', 'Technical', 'Executive', 'Academic'],
'content_type' => ['Analysis', 'News', 'Tutorial', 'Case Study', 'Interview', 'Report'],
];
public function __construct(private OpenAIService $ai) {}
public function name(): string
{
return 'Taxonomy Suggestion';
}
public function execute(string $content, array $context): array
{
$termsJson = json_encode($this->availableTerms);
$system = 'You are a content taxonomy specialist for an enterprise CMS.
Suggest appropriate taxonomy terms and return JSON only.';
$prompt = <<<PROMPT
Suggest taxonomy terms for the following content.
Only suggest terms from the available taxonomy list provided.
Available taxonomy terms:
{$termsJson}
Return a JSON object with:
- "suggestions": object with vocabulary names as keys, each containing:
- "terms": array of suggested term names from the available list
- "confidence": "high", "medium", or "low"
- "primary_topic": the single most relevant topic term
Content:
{$content}
PROMPT;
$raw = $this->ai->chat($system, $prompt);
$result = $this->ai->parseJson($raw);
return [
'taxonomy' => $result,
];
}
}
In a real deployment, replace the hardcoded $availableTerms array with a dynamic lookup from your Drupal taxonomy vocabularies. You can load terms using Drupal's entity query system and pass the full list to the prompt.
Step 6: The Routing Decision step
This is where the pipeline pays off. By the time this step runs, it has the policy result, quality score, and taxonomy confidence from the previous three steps. The routing decision is genuinely informed rather than based on a single signal.
Create src/Steps/RoutingDecisionStep.php:
<?php
namespace Drupal\ai_content_pipeline\Steps;
use Drupal\ai_content_pipeline\Contracts\PipelineStepInterface;
use Drupal\ai_content_pipeline\Service\OpenAIService;
class RoutingDecisionStep implements PipelineStepInterface
{
public function __construct(private OpenAIService $ai) {}
public function name(): string
{
return 'Routing Decision';
}
public function execute(string $content, array $context): array
{
$policyStatus = $context['policy']['status'] ?? 'unknown';
$policyReason = $context['policy']['reason'] ?? '';
$qualityScore = $context['quality']['score'] ?? 0;
$publishable = $context['quality']['publishable'] ?? false;
$weaknesses = json_encode($context['quality']['weaknesses'] ?? []);
$violations = json_encode($context['policy']['violations'] ?? []);
$system = 'You are a content workflow manager for an enterprise CMS.
Make routing decisions based on pipeline analysis results.
Return JSON only.';
$prompt = <<<PROMPT
Based on the pipeline analysis below, decide how this content should be routed.
Pipeline results:
- Policy status: {$policyStatus}
- Policy reason: {$policyReason}
- Policy violations: {$violations}
- Quality score: {$qualityScore} / 10
- Publishable assessment: {$publishable}
- Quality weaknesses: {$weaknesses}
Routing options:
- "auto_approve": policy passed, quality score 9-10, no issues
- "editor_review": policy passed, quality score 6-8, minor issues only
- "senior_editor_review": policy flagged or quality score 4-5, needs experienced judgment
- "reject": policy status is reject, or quality score below 4
Return a JSON object with:
- "decision": one of the four routing options above
- "reason": clear explanation of why this routing was chosen
- "reviewer_notes": specific things the human reviewer should check, as an array
- "priority": "high", "normal", or "low"
PROMPT;
$raw = $this->ai->chat($system, $prompt);
$result = $this->ai->parseJson($raw);
return [
'routing' => $result,
];
}
}
Step 7: The Pipeline Orchestrator
This is the class that wires everything together. It runs each step in sequence, collects the context, handles failures gracefully, and returns the full pipeline result.
Create src/Pipeline/ContentModerationPipeline.php:
<?php
namespace Drupal\ai_content_pipeline\Pipeline;
use Drupal\ai_content_pipeline\Contracts\PipelineStepInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
class ContentModerationPipeline
{
private array $steps = [];
private $logger;
public function __construct(LoggerChannelFactoryInterface $loggerFactory)
{
$this->logger = $loggerFactory->get('ai_content_pipeline');
}
public function addStep(PipelineStepInterface $step): self
{
$this->steps[] = $step;
return $this;
}
public function run(string $content): array
{
$context = [];
$stepLog = [];
$startTime = microtime(true);
foreach ($this->steps as $step) {
$stepName = $step->name();
$stepStart = microtime(true);
try {
$result = $step->execute($content, $context);
$context = array_merge($context, $result);
$stepLog[] = [
'step' => $stepName,
'status' => 'completed',
'duration' => round(microtime(true) - $stepStart, 2) . 's',
];
$this->logger->info('Pipeline step completed: @step', ['@step' => $stepName]);
} catch (\Exception $e) {
$this->logger->error('Pipeline step failed: @step, Error: @error', [
'@step' => $stepName,
'@error' => $e->getMessage(),
]);
$stepLog[] = [
'step' => $stepName,
'status' => 'failed',
'error' => $e->getMessage(),
];
// On failure, route to senior editor for manual review
$context['routing'] = [
'decision' => 'senior_editor_review',
'reason' => "Pipeline step '{$stepName}' failed. Manual review required.",
'reviewer_notes' => ['Pipeline encountered an error, please review manually.'],
'priority' => 'high',
];
break;
}
}
return [
'context' => $context,
'steps' => $stepLog,
'total_duration' => round(microtime(true) - $startTime, 2) . 's',
];
}
}
Step 8: Wiring it into Drupal's Moderation workflow
Now we connect the pipeline to Drupal's content workflow. This hook fires when a node is presaved, runs the pipeline, and applies the routing decision as a moderation state.
In ai_content_pipeline.module:
<?php
use Drupal\node\NodeInterface;
use Drupal\ai_content_pipeline\Service\OpenAIService;
use Drupal\ai_content_pipeline\Pipeline\ContentModerationPipeline;
use Drupal\ai_content_pipeline\Steps\PolicyComplianceStep;
use Drupal\ai_content_pipeline\Steps\QualityAssessmentStep;
use Drupal\ai_content_pipeline\Steps\TaxonomySuggestionStep;
use Drupal\ai_content_pipeline\Steps\RoutingDecisionStep;
function ai_content_pipeline_node_presave(NodeInterface $node): void
{
// Only run on new nodes that are pending moderation
if (!$node->isNew()) {
return;
}
$content = $node->getTitle() . "\n\n" . $node->get('body')->value;
if (empty(trim($content))) {
return;
}
$ai = new OpenAIService();
$pipeline = new ContentModerationPipeline(\Drupal::service('logger.factory'));
$pipeline
->addStep(new PolicyComplianceStep($ai))
->addStep(new QualityAssessmentStep($ai))
->addStep(new TaxonomySuggestionStep($ai))
->addStep(new RoutingDecisionStep($ai));
$result = $pipeline->run($content);
$routing = $result['context']['routing'] ?? null;
if (!$routing) {
return;
}
// Map routing decision to Drupal moderation states
$stateMap = [
'auto_approve' => 'published',
'editor_review' => 'needs_review',
'senior_editor_review' => 'needs_review',
'reject' => 'rejected',
];
$decision = $routing['decision'] ?? 'editor_review';
$state = $stateMap[$decision] ?? 'needs_review';
if ($node->hasField('moderation_state')) {
$node->set('moderation_state', $state);
}
// Store pipeline results in a field for reviewer reference
if ($node->hasField('field_ai_review_notes')) {
$notes = "Routing: {$decision}\n";
$notes .= "Reason: {$routing['reason']}\n\n";
$notes .= "Reviewer notes:\n" . implode("\n", $routing['reviewer_notes'] ?? []);
$node->set('field_ai_review_notes', $notes);
}
}
What the Pipeline output looks like in practice
Here is a realistic example of the full pipeline result for a piece of content that passed policy checks but had quality issues. This is what your editors would see in the review notes field.
{
"context": {
"policy": {
"status": "pass",
"violations": [],
"reason": "Content meets all publishing policy requirements."
},
"quality": {
"score": 6,
"publishable": true,
"strengths": ["Clear headline", "Good factual grounding"],
"weaknesses": ["Conclusion is abrupt and underdeveloped", "Second section lacks supporting evidence"]
},
"taxonomy": {
"suggestions": {
"topics": { "terms": ["Technology", "Business"], "confidence": "high" },
"audience": { "terms": ["Executive"], "confidence": "medium" },
"content_type": { "terms": ["Analysis"], "confidence": "high" }
},
"primary_topic": "Technology"
},
"routing": {
"decision": "editor_review",
"reason": "Policy passed but quality score of 6 indicates minor issues that need editorial attention before publication.",
"reviewer_notes": [
"Strengthen the conclusion, currently ends abruptly",
"Add supporting evidence or sources to the second section",
"Taxonomy auto-applied, verify the Executive audience tag is correct"
],
"priority": "normal"
}
},
"steps": [
{ "step": "Policy Compliance Check", "status": "completed", "duration": "1.8s" },
{ "step": "Quality Assessment", "status": "completed", "duration": "2.1s" },
{ "step": "Taxonomy Suggestion", "status": "completed", "duration": "1.6s" },
{ "step": "Routing Decision", "status": "completed", "duration": "1.4s" }
],
"total_duration": "6.9s"
}
The reviewer opens the content, sees it has been routed to them with a quality score of 6, reads the specific reviewer notes, and knows exactly what to look at. No need to read the whole piece from scratch looking for problems. That is the practical value here.
Things worth knowing before you deploy
Seven seconds of pipeline processing on every content submission is not acceptable for a synchronous save operation. Move the pipeline into a queued job that fires after the initial save, using Drupal's Queue API or a custom queue worker. Store a "pending AI review" state that content sits in while the pipeline runs, then update the moderation state when the job completes.
The system prompts in each step are where the real customisation happens. The policy step above uses generic rules, but for a real enterprise deployment you would replace those with your organisation's actual editorial policies, pulled from a config form or a dedicated policy content type in Drupal itself. That way non-technical editors can update the policy rules without touching code.
On cost, four GPT-4o calls per content submission adds up across a high-volume site. For content that does not need the full pipeline, like very short pieces or resubmissions, consider a lighter first-pass check using GPT-4o-mini before deciding whether to run the full chain. The classification step costs a fraction of the full pipeline and can filter out a significant portion of submissions early.
Finally, keep the pipeline results. Store each step's output against the content revision in a custom table or a long text field. After a few months you will have data on what the pipeline flags most often, how accurate the routing decisions are, and where editors are overriding the AI recommendations. That feedback loop is what lets you improve the system prompts over time and actually measure whether the pipeline is helping.
The pattern here, focused steps, structured JSON outputs, full context passed forward, graceful failure handling, is the same pattern LangChain formalises in its framework. Building it directly in PHP means you keep it inside your existing Drupal infrastructure with no additional services to run. For most enterprise Drupal teams, that is the right tradeoff.
AI Category Recommendation System for Drupal 11 Using PHP and OpenAI
Categorization in Drupal is one of those things that looks fine on the surface but gets messy fast. Editors are busy, categories get picked in a hurry, and before long you've got a dozen articles filed under the wrong taxonomy term or spread inconsistently across three different ones that mean almost the same thing.
The fix isn't enforcing stricter rules on editors. It's removing the guesswork entirely.
In this tutorial, I'll walk you through building a custom Drupal 11 module that reads a node's actual content and uses OpenAI to pick the most appropriate category automatically, no manual selection needed.
It hooks into the node save process, pulls your existing taxonomy terms, and asks the AI to match the content against them. The result gets assigned before the node is stored. It's a small module but it solves a real problem, especially on sites with large editorial teams or high publishing volume.
What This Module Will Do
Our AI category system will:
- Analyze node body content on save
- Compare it against existing taxonomy terms
- Recommend the most relevant category
- Automatically assign it (or display it to editors)
Use cases include:
- Blog posts
- Documentation pages
- News articles
- Knowledge bases
Prerequisites
Make sure you have:
- Drupal 11
- PHP 8.1+
- Composer
- A taxonomy vocabulary (example: categories)
- An OpenAI API key
Step 1: Create the Custom Module
Create a new folder:
/modules/custom/ai_category/
Inside it, create the below files:
- ai_category.info.yml
- ai_category.module
ai_category.info.yml
name: AI Category Recommendation
type: module
description: Automatically recommend and assign taxonomy categories using AI.
core_version_requirement: ^11
package: Custom
version: 1.0.0
Step 2: Hook Into Node Save
We’ll use hook_entity_presave() to analyze content before it’s stored.
ai_category.module
use Drupal\Core\Entity\EntityInterface;
use Drupal\taxonomy\Entity\Term;
/**
* Implements hook_entity_presave().
*/
function ai_category_entity_presave(EntityInterface $entity) {
if ($entity->getEntityTypeId() !== 'node') {
return;
}
// Only apply to articles (adjust as needed)
if ($entity->bundle() !== 'article') {
return;
}
$body = $entity->get('body')->value ?? '';
if (empty($body)) {
return;
}
$category = ai_category_recommend_term($body);
if ($category) {
$entity->set('field_category', ['target_id' => $category]);
}
}
This ensures our logic runs only for specific content types and avoids unnecessary processing.
Step 3: Ask AI for Category Recommendation
We’ll send the node content plus a list of available categories to OpenAI and ask it to pick the best one.
function ai_category_recommend_term(string $text): ?int {
$apiKey = 'YOUR_OPENAI_API_KEY';
$endpoint = 'https://api.openai.com/v1/chat/completions';
$terms = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadTree('categories');
$categoryNames = array_map(fn($t) => $t->name, $terms);
$prompt = "Choose the best category from this list:\n"
. implode(', ', $categoryNames)
. "\n\nContent:\n"
. strip_tags($text)
. "\n\nReturn only the category name.";
$payload = [
"model" => "gpt-4o-mini",
"messages" => [
["role" => "system", "content" => "You are a content classification assistant."],
["role" => "user", "content" => $prompt]
],
"temperature" => 0
];
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer {$apiKey}"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 15
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
$chosen = trim($data['choices'][0]['message']['content'] ?? '');
foreach ($terms as $term) {
if (strcasecmp($term->name, $chosen) === 0) {
return $term->tid;
}
}
return null;
}
What’s happening here:
- Drupal loads all available categories
- AI receives both content + allowed categories
- AI returns one matching category name
- Drupal maps it back to a taxonomy term ID
Step 4: Enable the Module
- Place the module in /modules/custom/ai_category
- Go to Extend → Enable module
- Enable AI Category Recommendation
- That’s it — no UI needed yet.
Step 5: Test It
- Create a new Article
- Write content related to PHP, Drupal, AI, or CMS topics
- Click Save
- The Category field is auto-filled
Example:
Article content:
“This tutorial explains how to build a custom Drupal 11 module using PHP hooks…”
AI-selected category:
Drupal
Optional Enhancements
Once the basics work, you can extend this system:
- Show AI recommendation as a suggestion, not auto-assignment
- Add admin settings (API key, confidence threshold)
- Use Queue API for bulk classification
- Switch to embeddings for higher accuracy
- Log category confidence scores
- Support multi-term assignment
Security & Performance Tips
- Never hard-code API keys (use settings.php or environment variables)
- Limit text length before sending to AI
- Cache recommendations to reduce API calls
- Add fallbacks if the AI response is invalid
AI Text Summarization for Drupal 11 Using PHP and OpenAI API
Drupal's body field has a built-in summary subfield that almost nobody fills in properly. On high-volume editorial sites I've worked on, it's either blank, copy-pasted from the first paragraph, or written by someone who clearly didn't read the article. It shows up in teasers, RSS feeds, and meta descriptions, so bad summaries actually hurt.
The fix is straightforward. Hook into hook_entity_presave, grab the body content, send it to OpenAI, write the result back into body->summary before the node hits the database. Editors never have to touch it, and the summaries are actually coherent.
This is a single-file custom module. No services, no config forms, no dependencies beyond cURL. If you want to wire it up properly with Drupal's config system later you can, but this gets you running in under 20 minutes.
Prerequisites
- Drupal 11
- PHP 8.1 or higher
- Composer
- cURL enabled
- An OpenAI API key
Step 1: Create a Custom Module
Create a new module called ai_summary.
/modules/custom/ai_summary/Inside that folder, create two files:
- ai_summary.info.yml
- ai_summary.module
ai_summary.info.yml
Add the below code in the info.yml file.
name: AI Summary type: module description: Automatically generate summaries for Drupal nodes using OpenAI API. core_version_requirement: ^11 package: Custom version: 1.0.0
ai_summary.module
This is where the logic lives.
To run our code just before a node is saved we will use hook_entity_presave of Drupal.
use Drupal\node\Entity\Node;
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_entity_presave().
*/
function ai_summary_entity_presave(EntityInterface $entity) {
if ($entity->getEntityTypeId() !== 'node') {
return;
}
// Only summarize articles (you can change this as needed)
if ($entity->bundle() !== 'article') {
return;
}
$body = $entity->get('body')->value ?? '';
if (empty($body)) {
return;
}
// Generate AI summary
$summary = ai_summary_generate_summary($body);
if ($summary) {
// Save it in the summary field
$entity->get('body')->summary = $summary;
}
}
/**
* Generate summary using OpenAI API.
*/
function ai_summary_generate_summary($text) {
$api_key = 'YOUR_OPENAI_API_KEY';
$endpoint = 'https://api.openai.com/v1/chat/completions';
$payload = [
"model" => "gpt-4o-mini",
"messages" => [
["role" => "system", "content" => "Summarize the following text in 2-3 sentences. Keep it concise and human-readable."],
["role" => "user", "content" => $text]
],
"temperature" => 0.7
];
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer {$api_key}"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 15
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
return trim($data['choices'][0]['message']['content'] ?? '');
}
This functionality performs three primary functions:
- Identifies article saving in Drupal.
- Sends the content to OpenAI to be summarized.
- The summary is stored in the body summary field of the article.
Step 2: Enable the Module
- Place the new module folder directly in /modules/custom/.
- In Drupal Admin panel, go to: Extend → Install new module (or Enable module).
- Check AI Summary and turn it on.
Step 3: Test the AI Summary
- Select Content -> Add content -> Article.
- Enter the long paragraph in the body field.
- Save the article.
- On reloading the page, open it one more time — the summary field will be already filled automatically.
Example:
Input Body:
Artificial Intelligence has been changing how developers build and deploy applications...
Generated Summary:
AI is reshaping software development by automating repetitive tasks and improving decision-making through data-driven insights.
Step 4: Extend It Further
The following are some of the ideas that can be used to improve the module:
- Add settings: Add a form to enable the user to add the API key and the select the type of model.
- Queue processing: Queue processing Use the drugndrup queue API to process the existing content in batches.
- Custom field storage: Store summaries in object now: field_ai_summary.
- Views integration: Show or hide articles in terms of length of summary or its presence.
Security & Performance Tips
- Never hardcode your API key but keep it in the configuration or in the.env file of Drupal.
- Shorten long text in order to send (OpenAI token limit = cost).
- Gracefully manage API timeouts.
- Watchdoging errors to log API.
Unleashing the Power of Drupal 10: Advantages and Features
1. Enhanced Accessibility
Accessibility is no longer an option; it's a necessity. Drupal 10 understands this, and its commitment to making the web inclusive for all is evident through its enhanced accessibility features. The platform comes with improved semantic HTML, ARIA enhancements, and focus management, ensuring that websites built on Drupal 10 are accessible to people with disabilities. By prioritizing accessibility, you can reach a wider audience and demonstrate your commitment to providing a positive user experience for everyone.
2. API-First Approach
Drupal 10 embraces the API-first approach, a trend that's gaining momentum in the web development community. This feature allows developers to separate the frontend and backend of their websites, giving them the freedom to choose the best technology stack for each aspect. With Drupal's robust APIs, you can build headless or decoupled applications, providing seamless user experiences across various devices and platforms. This versatility is ideal for creating dynamic applications, progressive web apps (PWAs), and mobile experiences.
3. Streamlined Content Editing with Claro
Drupal 10 introduces the Claro administration theme, a significant improvement to the user interface for content editors and site administrators. Claro is designed to be visually appealing, intuitive, and user-friendly, making content management a breeze. With a modern and clean interface, content editors can focus on creating engaging content without getting bogged down by complicated backend systems. Claro streamlines the content editing process, increasing efficiency and productivity for website maintenance tasks.
4. Effortless Migration from Drupal 9
For organizations currently using Drupal 9, upgrading to Drupal 10 is a smooth and hassle-free process. The Drupal community has worked diligently to ensure backward compatibility between the versions, making it easier to migrate your existing projects seamlessly. This allows you to take advantage of Drupal 10's new features while leveraging your existing codebase and modules.
5. Improved Performance and Scalability
Performance is critical for any website, and Drupal 10 comes packed with performance improvements that enhance the overall speed and efficiency of your site. The platform has undergone codebase optimizations, caching enhancements, and database query optimizations, resulting in faster page load times and improved responsiveness. Whether you run a small blog or a large enterprise platform, Drupal 10 can handle the scale, ensuring a smooth user experience for all visitors.
6. Robust Security Features
Security is a paramount concern for any CMS, and Drupal has a long-standing reputation for its robust security architecture. Drupal 10 continues this tradition, providing a secure foundation for your web projects. Regular security updates, improved password hashing algorithms, and adherence to industry best practices make Drupal 10 a secure choice for building websites and applications.
Conclusion
Drupal 10 is a game-changer in the world of web development, offering a wide array of advantages and features that cater to the needs of modern websites and applications. With enhanced accessibility, an API-first approach, streamlined content editing, easy migration, improved performance, and robust security features, Drupal 10 empowers developers and site owners to build cutting-edge digital experiences.
Whether you're starting a new project or upgrading an existing one, Drupal 10 provides the flexibility, scalability, and security required to create exceptional websites that engage and delight users. Embrace the power of Drupal 10 and elevate your web development endeavors to new heights. Happy building!
Introducing Drupal 10: The Next Evolution in Web Development
Introduction
Welcome to the future of web development! Drupal, the powerful open-source content management system (CMS), has reached a new milestone with the release of Drupal 10. As we bid adieu to Drupal 9 and embrace the latest iteration, developers and site builders can look forward to a plethora of exciting features, enhancements, and optimizations that make building robust, secure, and scalable websites easier than ever. In this blog post, we'll delve into the highlights of Drupal 10 and explore the reasons why it's the ultimate choice for your web projects.
1. Accessibility First
Accessibility has always been a priority for Drupal, and with Drupal 10, this commitment is taken to new heights. The core development team has worked tirelessly to ensure that Drupal 10 adheres to the latest web accessibility standards, making it possible for everyone to access and interact with websites built on the platform, regardless of their physical abilities. Improved semantic HTML, ARIA enhancements, and focus management are some of the accessibility-focused improvements that will empower developers to create inclusive digital experiences effortlessly.
2. API-First Approach
Drupal has been at the forefront of embracing API-first design principles, and this trend continues with Drupal 10. The new version comes with enhanced support for headless and decoupled architectures, allowing developers to use Drupal as a content repository while building frontend experiences using the technologies of their choice, such as React, Angular, or Vue.js. This flexibility opens up a world of possibilities for creating seamless omnichannel experiences and empowers organizations to deliver content to various devices and platforms.
3. Easy Migration from Drupal 9
If you're already using Drupal 9 for your website, transitioning to Drupal 10 is a breeze. The development team has streamlined the upgrade process, making it easier and faster to move your existing projects to the latest version. Additionally, Drupal 10's backward compatibility ensures that modules and themes written for Drupal 9 will continue to function smoothly, allowing you to leverage your existing codebase and investments.
4. Enhanced Performance and Scalability
Drupal 10 brings significant performance improvements, optimizing the CMS to handle larger and more complex websites with ease. Whether you're running a small business site or a high-traffic enterprise platform, Drupal 10's enhancements in caching, database queries, and overall codebase optimization will ensure your website performs at its best, providing a seamless user experience even during peak traffic.
5. Streamlined Content Editing
Content editors and site administrators will love the enhanced content editing experience in Drupal 10. With the introduction of the Claro administration theme in Drupal 8 and further improvements in Drupal 9, Drupal 10 takes the user interface to the next level. The admin interface is now more intuitive, user-friendly, and visually appealing, reducing the learning curve and making content management a joyous task.
6. Robust Security Features
Security remains a top priority for Drupal, and Drupal 10 continues to build upon the platform's already robust security architecture. With regular security updates and features like improved password hashing algorithms, security-conscious organizations can trust Drupal 10 to safeguard their digital assets effectively.
Conclusion
Drupal 10 marks a significant milestone in the evolution of the CMS, presenting an impressive array of features that cater to the needs of modern web development. From enhanced accessibility to API-first capabilities, easy migration, improved performance, streamlined content editing, and robust security features, Drupal 10 promises to revolutionize the way websites are built and managed.
So, whether you're a developer, site builder, or organization looking to create dynamic and innovative digital experiences, Drupal 10 provides the ideal platform to bring your vision to life. Embrace the power of Drupal 10 and join the ever-growing community of web professionals shaping the future of the internet. Happy building!
Display personalised content in Drupal - Smart Content Paragraphs
In this tutorial we are going to discuss about, how to display the personalised content (Paragraphs) for each person (both anonymous and authenticated users) in Drupal.
Dependent Modules
- Paragraphs (https://www.drupal.org/project/paragraphs)
- Smart Content (https://www.drupal.org/project/smart_content)
- Smart Content Segments (https://www.drupal.org/project/smart_content_segments)
Create custom theme in Drupal 8
To create a new template, we have to follow the following steps.
- Create YAML (.yml) file
- Edit YAML (.yml) file
- Remove stylesheets
- Clear the cache
- Optimize the Website
- Add CSS
- Add Javascript
Step 1: Create YAML (.yml) file
- Create a folder with the theme name which we are going to create like (first_theme) inside the <root>/theme folder.
- Create a new file inside the folder with the name first_theme.info.yml.
Step 2: Edit YAML file
Step 3: Remove Stylesheets (Optional)
Step 4: Clear the Cache
Step 5: Optimize the Website
Step 6: Add CSS
Step 7: Add JavaScript Reference
Download and install Drupal modules using Drush
To set things up, you need to download Drush and add it to your path. For example, you might unpack it into /opt/drush and then add the following line to your ~/.bashrc:
PATH=/opt/drush:$PATH
export PATH
Reload your ~/.bashrc with source ~/.bashrc, and the drush command should become available. If you're on Microsoft Windows, it might need some more finagling. (Or you can just give up and use a virtual image of Linux to develop your Drupal websites.
Drush is a huge time-saver. For example, I install dozens of modules in the course of building a Drupal website. Instead of copying the download link, changing to my sites/all/modules directory, pasting the download URL into my terminal window after wget, unpacking the file, deleting the archive, and then clicking through the various module enablement screens, I can just issue the following commands to download and enable the module.
drush dl modulename
drush en -y modulename
(The -y option means say yes to all the prompts.)
So much faster and easier. You can use these commands with several modules (module1 module2 module3), and you can use drush cli to start a shell that's optimized for Drush.
Drush is also useful if you've screwed up your Drupal installation and you need to disable themes or modules before things can work again. In the past, I'd go into the {system} table and carefully set the status of the offending row to 0. Now, that's just a drush dis modulename.
Drush has a bucketload of other useful commands, and drush help is well worth browsing. Give it a try!
Rewriting Drupal Views Output for Custom Theming and CSS
The issue was, how to assign the color to the city with CSS using Views. We tried a custom View theme template but this got us nowhere as there seemed to be no way to out put the names of fields.
Finally we hit upon it, there is an option in Views under Fields called "Rewrite the output of this field". When you click on a field to edit and select the checkbox for this option, you are presented with possible "Replacement patterns". In our case, we used the city location field as our CSS selector for the city location.
Here is our custom output rewrite code we used:
<div class="[field_event_location_value]">[field_event_location_value]</div>
The HTML output for the above is:
<div class="Boston">Boston</div>
We can now create a custom CSS class for Boston like this:
.page-events .Boston {
color:#2B0082;
}
Note that to utilize this properly you can only use fields that have come before the one you are currently working on so you may need to just rearrange the order or add a field but select "Exclude from display" just to get the data in to be available for use.
The possibilities for uses for Drupal Views Output Rewriting are endless and we are already using it for many different purposes for custom Views theming.
Steps to Add HTTP authentication to your Drupal site
Step 1 :
Create the file that will keep the information of the usernames / passwords for authentication. (You can create more than one accounts). You can create this file in the project root folder. Like, the location /var/www/drupal_site/. Use the following command.
htpasswd [ -c ] passwdfilename username
So, here you have given the username that would be authenticated for the site. You will be asked twice to enter password for this username.
For example, in the following command, my file's name is ".dummyhtpasswd" and the username of the account is "user1".
htpasswd –c /var/www/drupal_site/.dummyhtpasswd user1
Step 2:
You are done with creation of account details, now you need to edit your .htaccess file to add the code for http authentication there and also to add the path of this newly created file that contains the credentials' information so that this newly created file's presence and the credentials are verified at every http access. For that you have to reach out to your .htaccess file. In my case, I found it in my project folder at /var/www/drupal_site/ . You can also find it in the folder of your project.
Step 3:
Now, edit the .htaccess file. By using the below command.
vi .htaccess
Now to go to edit mode, press "i", that's for "insert". Take the cursor to the very bottom of the code and then add the following lines.
# /var/www/drupal_site/.dummyhtpasswd
# AUTHENTICATION
## BASIC PASSWORD AUTHENTICATION
AuthName "Prompt"
AuthUserFile /var/www/drupal_site/.dummyhtpasswd
AuthType basic
Require valid-user
That is it. Now we have to save it. Press "Esc" and then :w <enter>. This saves it.
For saving and exiting press "Esc" and then press :wq! <enter>.
Now we have added the path of the newly created file. Please see that line 1 and 5 of the code would have the path of "your" created file, in above code I have given the path of "my" created file for reference.
Step 4:
You have to reach out to the newly created file. (You created in Step 1). After that the following command can be used to add a user to the already existing file.
htpasswd /var/www/ drupal_site/.dummyhtpasswd newuser <enter>
Enter the password twice and you are done. The path in above command will be the path for that newly created password file (of Step 1).
Done!!
Drupal 7 - Download Adaptive Theme free
If you are using Panels try the Browscap CTools module to control the visibility of panel panes in mobile devices or Browscap Block to do the same thing with normal blocks.
Panels, Display Suite and Gpanels are fully supported with many options for displaying panel type layouts in mobile.
Adaptivetheme is blindingly fast - it employs extensive use of drupal_static and other caching techniques to speed up page rendering and delivery. If you're not fast you're last, right?
Adaptivetheme is built around these basic concepts:
- Responsive design
- Pluggable layout system
- HTML5
- WGAC 2.0 Accessibility
Steps to configure google maps with nodes in Drupal
Step 1: Install Locations Module
Step 2: Install Gmap Module
Step 3: Enable Gmap Location Module Which is part of Gmap Module.
Step 4: Set the Google Map Key Here admin/settings/gmap
Step 5: Go here admin/settings/location/main and select default country and check Use a Google Map to set latitude and longitude
Step 6: Go to content type edit tab and select Locations Collapsible tab, under that go to Number Of Locations and set min, max, Number of locations that can be added at once as 1,1,1 respectively
Step 7: Go to your nodes click on edit and you can see location settings, there give the address and longitude and latitude which you can pick from map or you can set. Longitude and Latitude are very important to locate a node in map
Step 8: Save the node.
Step 9: admin/settings/gmap_location go here, under node map select marker action as open link.
Step 10: Now go to map/node you can see all your nodes here.
Step 11: If you want to enable map for nodes go to blocks, enable the block called Location Map
That's it you are done.
Drupal Performance - Disable CSS Aggregation from database
- Open phpmyadmin and select the database which is used by the drupal instance.
- Select the "variables" table.
- Search for "preprocess_css" in name field.
- Change the variable value to s:1:"0";
- And you are done.
Drupal 7 - Create custom blocks with custom fields
hook_block_configure() - allows you to set editable custom fields on your custom block.
hook_block_save() - allows you to save the contents on each custom fields edited by the user.
On your custom module file ( e.g. my_custom_block.module), we first setup hook_block_info() and hook_block_view().
/**
* Implements hook_block_info().
*/
function my_custom_block_block_info() {
$blocks['custom_block_with_custom_fields'] = array(
'info' => t('My custom block with custom fields'),
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function my_custom_block_block_view($delta = '') {
$block = array();
switch ($delta) {
case 'custom_block_with_custom_fields':
$block['title'] = 'My custom block with custom fields';
$block['content'] = my_custom_block_contents($delta),
break;
}
return $block;
}
my_custom_block_contents($delta) is a custom callback that we will be using for displaying the contents.
Now we are going to implement the hook_block_configure() for our custom fields.
/**
* Implements hook_block_configure().
*/
function my_custom_block_block_configure($delta = '') {
$form = array();
switch ($delta) {
case 'custom_block_with_custom_fields':
$form['custom_textfield'] = array(
'#type' => 'textfield',
'#title' => t('Textfield input'),
'#size' => 60,
);
$form['custom_text_format'] = array(
'#type' => 'text_format',
'#title' => t('Text format input'),
);
break;
}
return $form;
}
In creating custom fields, we still follow Drupal's Form API
We now implement hook_block_save() to allow Drupal to save our custom field values when a user edits our custom field using variable_set api.
/**
* Implements hook_block_save().
*/
function my_custom_block_block_save($delta = '', $edit = array()) {
switch ($delta) {
case 'custom_block_with_custom_fields':
variable_set('custom_textfield', $edit['custom_textfield']);
variable_set('custom_text_format', $edit['custom_text_format']['value']);
break;
}
}
Now that we've implemented variable_set on each custom fields to save values, we can now use variable_get api on our custom callback, to display the values that we've saved on each custom field
/**
* Returns block contents
*/
function my_custom_block_contents($block_id) {
$block = array();
switch ($block_id) {
case 'custom_block_with_custom_fields':
$block = array(
'custom_textfield' => variable_get('custom_textfield'),
'custom_text_format' => variable_get('custom_text_format'),
);
break;
}
return $block;
}
So, all of the snippets combined, would look like this.
/**
* Implements hook_block_info().
*/
function my_custom_block_block_info() {
$blocks['custom_block_with_custom_fields'] = array(
'info' => t('My custom block with custom fields'),
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function my_custom_block_block_view($delta = '') {
$block = array();
switch ($delta) {
case 'custom_block_with_custom_fields':
$block['title'] = 'My custom block with custom fields';
$block['content'] = my_custom_block_block_contents($delta),
break;
}
return $block;
}
/**
* Implements hook_block_configure().
*/
function my_custom_block_block_configure($delta = '') {
$form = array();
switch ($delta) {
case 'custom_block_with_custom_fields':
$form['custom_textfield'] = array(
'#type' => 'textfield',
'#title' => t('Textfield input'),
'#size' => 60,
);
$form['custom_text_format'] = array(
'#type' => 'text_format',
'#title' => t('Text format input'),
);
break;
}
return $form;
}
/**
* Implements hook_block_save().
*/
functionmy_custom_block_block_save($delta = '', $edit = array()) {
switch ($delta) {
case 'custom_block_with_custom_fields':
variable_set('custom_textfield', $edit['custom_textfield']);
variable_set('custom_text_format', $edit['custom_text_format']['value']);
break;
}
}
/**
* Returns block contents
*/
function my_custom_block_block_contents($block_id) {
$block = array();
switch ($block_id) {
case 'custom_block_with_custom_fields':
$block = array(
'custom_textfield' => variable_get('custom_textfield'),
'custom_text_format' => variable_get('custom_text_format'),
);
break;
}
return $block;
}
That's it! now go to your custom block and edit it, you will be able to see the custom fields we've created and at the same time be able to save the value we input on each field.
Steps to use Wordpress like pager in Drupal
<?php
function yourtheme_pager($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9) {
global $pager_total;
$li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹ previous')), $limit, $element, 1, $parameters);
$li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next ›')), $limit, $element, 1, $parameters);
if ($pager_total[$element] > 1) {
if ($li_previous) {
$items[] = array(
'class' => 'pager-previous',
'data' => $li_previous,
);
}
// End generation.
if ($li_next) {
$items[] = array(
'class' => 'pager-next',
'data' => $li_next,
);
}
return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
}
}
?>
Add the above code snippet in your theme's template.php file and clear the cache and then check it. It will work.
The above code will solve someone's headache.
Drupal - G2W (Goto Webinar) integration
The Most important thing that you know when you registering in gotowebinar is about the webinar key (Which is available after you schedule a webinar). Check details on how to schedule and host a webinar here. http://www.gotomeeting.com/fec/webinar. I decided to write a curl function to the registration URL and submit the values collected from my order information.
I used hook_order API to check whether the order is Completed and based on the Status i'm submitting the User Details to the gotowebinar registrant URL.
Below is my completed process/code to submit user information in G2W.
1. Created a new field in your product class "goto_webinars_key"( you will find out this,once you scheduled a webinar in go to webinar)
2. Create a custom module with the following functions
function YOURMODULE_order($opt, &$arg1, $arg2){
$order = &$arg1;
$user = user_load($order->uid); // user info from the Order.
switch($op){
//We only care about completed updates and that needs to be execute once in the System
case 'update':
if ($arg1->order_status == 'payment_received' && arg2 == 'completed'){
//loop through each product item in the cart
foreach ($order->products as $product){
$n = node_load($product->nid);
if ($n->field_goto_webinars_key[0][value]){
YOURMODULE_gotowebinar($n->field_goto_webinars_key[0][value], $user);
}}
break;
}}
// curl operation to submit the users into go to webinar registration.
function YOURMODULE_gotowebinar($WebinarKey, $account){
$gtwPost = "";
//Create string for GoToWebinar from same form POST data
$Form = "webinarRegistrationForm";
$gtwPost = "WebinarKey=" . urlencode($WebinarKey)
. "&Form=" . urlencode($Form)
. "&Name_First=" . urlencode($account->profile_firstname)
. "&Name_Last=" . urlencode($account->profile_lastname)
. "&Email=" . urlencode($account->mail)
. "&Company=" . urlencode($account->profile_company)
. "&Title=" . urlencode($account->profile_title);
//Set POST URL for GoToWebinar
$gtw_url = "https://attendee.gotowebinar.com/register/$WebinarKey”;// This URl will be got from the registration url after you scheduled a webinar
//Start GoToWebinar submission
$curl = curl_init();
curl_setopt($curl, CURLOPT_POSTFIELDS, $gtwPost);
curl_setopt($curl, CURLOPT_URL, $gtw_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // it skips the curl version check if your curl is different.
$er = curl_exec($curl);
$info = curl_getinfo($curl);
curl_close($curl);
}
3. Enable the module and now it will submit users in the Go to Webinar.
The above code is sample to do with the normal Ubercart Product. I used signup module also to track webinars in my drupal system( Let me know if you want me to post the workflow of that too)
Drupal - Override Exposed Views Filters with hook_form_alter
However, one simple mistake can result in the HUGELY annoying, "Illegal choice detected. Please contact your administrator".
As I mentioned, if you are looking to override exposed filters, you can do so with:
hook_form_alter ($form, &$form_state, $form_id) {
//add some kind of if statment here to check the form_id otherwise you'll affect all your forms
if ($form['#id'] == 'views-exposed-form-[VIEW-NAME]-[WHICHEVER VIEW NUMBER]') {
$form['EXPOSED FILTER FIELD'] = array(
'#type' => 'select',
'#default_value' => '',
'#options' => array(
"" => t("- Select -"), //be sure to add the t() function around the first value
"1" => "VALUE 1",
"2"=> "VALUE 2",
),
);
}
**additionally, you can use hook_form_FORM_ID_alter to specify the form you are altering, avoiding the initial if statment all together.
The common illegal choice detected error comes up if you don't add t() around your first option. This is because this value would be passed through views using the t() function.
As a result, Drupal checks all forms during rendering to make sure nothing modified without permission. If you don't use the t() function, Drupal will see your changes as unauthorized and potential hacking -- This is a good thing.
Hope this helps.
Steps to display the Twitter Feed in a Drupal Block
- They post tweets as nodes rather than in a block.
- If they post to a block, they require very complex setups normally involving creating Views.
We are going to create much simpler way to show tweets in a block on your site. Twitter Block Drupal module is used to display the twitter feeds in our site.
Step 1 : Installing Twitterblock module
- Click Here to download the Twitter block module.
- Extract the files into a folder on your desktop. The folder will be called "twitterblock".
- Login to your site's files via FTP and navigate to /sites/default/. If there isn't a folder called /modules/ here, create one.
- Upload the "twitterblock" folder to /sites/default/modules/
- Go to Administrator >> Modules. Check the box in-front of the "Twitter Block" and click Save Configuration button at the bottom of the page.
Step 2 : Placing the Twitter Feed in a Block
- Go to Administrator >> Structure >> Blocks. Scroll down to find Twitter Block.
- Click "Configure" next to the block and enter your Twitter username and password:
Click "Save Block", publish the block and check to see how it looks on your site. Twitter feeds will display on the site.
Steps to remove System CSS and JS files in Drupal 7
Zen theme is a best theme to start create a new Drupal 7 theme. In Zen theme certain system or module CSS and JavaScript files will creep into the theme as you develop the website. This makes it difficult to fully optimize a website as you end up serving requests for files that are often unused for displaying content to a visitor.
To remove these unwanted system CSS and JS files, add the below hook codes to your theme's template.php file:
To remove the system CSS files, use the below code.
function THEME_NAME_css_alter(&$css)
{
unset($css[drupal_get_path('module', 'system').'/system.theme.css']);
unset($css[drupal_get_path('module','system').'/system.base.css']);
unset($css[drupal_get_path('module', 'system').'/system.messages.css']);
unset($css[drupal_get_path('module', 'comment').'/comment.css']);
unset($css[drupal_get_path('module', 'field').'/theme/field.css']);
unset($css[drupal_get_path('module', 'mollom').'/mollom.css']);
unset($css[drupal_get_path('module', 'node').'/node.css']);
unset($css[drupal_get_path('module', 'search').'/search.css']);
unset($css[drupal_get_path('module', 'user').'/user.css']);
unset($css[drupal_get_path('module', 'views').'/css/views.css']);
unset($css[drupal_get_path('module', 'ctools').'/css/ctools.css']);
unset($css[drupal_get_path('module', 'panels').'/css/panels.css']);
}
To remove the system JS files, use the below code.
function THEME_NAME_js_alter(&$js)
{
unset($js[drupal_get_path('module', 'panels').'/js/panels.js']);
unset($js[drupal_get_path('module', 'views').'/js/views.js']);
unset($js[drupal_get_path('module', 'views').'/js/ajax_view.js']);
unset($js[drupal_get_path('module', 'views').'/js/base.js']);
}
By using the above drupal hooks code, we can remove the unwanted system CSS and JS files.
No more posts to load.
- Building a RAG System in Laravel from Scratch
- Steps to create a Contact Form in Symfony With SwiftMailer
- Build a WhatsApp AI Assistant Using Laravel, Twilio and OpenAI
- CIBB - Basic Forum With Codeigniter and Twitter Bootstrap
- Laravel and Prism PHP: The Modern Way to Work with AI Models
- Drupal 7 - Create your custom Hello World module
- Build an AI Code Review Bot with Laravel — Real-World Use Case
- Create Front End Component in Joomla - Step by step procedure
- Symfony Framework - Introduction
- A step by step procedure to develop wordpress plugin


