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

Before starting, make sure you have:

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

  1. Place the new module folder directly in /modules/custom/.
  2. In Drupal Admin panel, go to: Extend → Install new module (or Enable module).
  3. Check AI Summary and turn it on.

Step 3: Test the AI Summary

  1. Select Content -> Add content -> Article.
  2. Enter the long paragraph in the body field.
  3. Save the article.
  4. 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

As technology continues to evolve, so does the web development landscape, and Drupal, the renowned open-source content management system (CMS), stays at the forefront of innovation. With the release of Drupal 10, developers and site owners have access to a host of exciting advantages and features that take web development to new heights. In this blog post, we'll explore the key advantages of Drupal 10 and highlight the cutting-edge features that make it the preferred choice for building powerful and scalable websites.

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.

Smart content module allows personalised blocks only. If we need to display the personalised Paragraphs, then we need to go for Smart Content Paragraphs module which will supports Paragraphs as well. 

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) 
Smart Content Paragraphs uses conditions, within Segments, to determine whether or not to display the reaction(s) defined within a Decision Block. A Condition is a single case that can be tested and determined to be either true or false, multiple conditions can be included in a Segment and a Segment Set is one or more Segments grouped together.

Click Here to Donwload the Module

Click Here to view the releases

Create custom theme in Drupal 8

In this tutorial we are going to discuss about, How to create custom theme (template) in Drupal 8 PHP CMS. By creating custom theme, we can customize the look and feel of the website as per our wish instead of using the pre-defined template or third party templates.

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

In Drupal 8, YAML file (.yml) is used in the place of .INFO files (used in Drupal 7) to tell the website that the theme exists. Similarly, the directories that contain the theme and the files with theme details have also been changed.
  • 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

Open the YAML file in your preferred text editor and enter the following details:

name: first_theme
description: Enter some description about your template
type: theme
core: 8.x

Type is theme and core is the version of Drupal you are creating the theme for (In this is case it is Drupal 8, so 8.x).

Now go to your Drupal website and check if the new theme appears in the Drupal appearance section. If all the steps have been correctly followed, the theme will appear in the uninstalled section of your website’s appearance tab.

Click install and set as default to set this theme as default.

Step 3: Remove Stylesheets (Optional)

After you have set the new theme as default, and then navigate to the website’s homepage, you will notice that nothing has changed. This is because Drupal includes several stylesheets that loads by default. In many cases, the best strategy is to disable some of these stylesheets. 

To find out these stylesheets,  you will need to inspect the source code by right-clicking and selecting View Source from the context menu and determine which CSS files you wish to remove, the rest of the process is pretty straightforward.

Go back to the first_theme.info.yml file and edit it. To remove the stylesheets you want, add this text:

stylesheets-remove
 -“stylesheet to be removed”

Step 4: Clear the Cache

Now login to your Drupal website’s admin panel. Next, go to Configuration >> Performance and click Clear all caches.

Step 5: Optimize the Website

Next, go back to the Performance page and uncheck Aggregate CSS files and Aggregate JavaScript files in the Bandwidth Optimization section. This will help in speeding up the performance of the website.

Step 6: Add CSS

It is time to add reference to the CSS file that will be used for/by the theme. To do this, go to the theme’s folder and create a new file named first_theme.libraries.yml and update the file with css files which we are going to use in the theme.

global-css:
 css:
  theme:
   css/style.css:{}
   
Now, add the library in the first_theme.info.yml file as well. To do this, add the below code in the file.

libraries:
 -first_theme/global-css

Step 7: Add JavaScript Reference

To add reference to the JavaScript reference to the theme, add the below code snippet to the first_theme.libraries.yml file.

global-js:
 js:
  js/site.js:{}
 dependencies:
  -core/jquery
  
Now, add the the JavaScript library in the first_theme.info.yml file.

-first_theme/global-js

Finally the  first_theme.info.yml will looks like below,

name: first_theme
description: Enter some description about your template
type: theme
core: 8.x

libraries:
 -first_theme/global-css
 -first_theme/global-js

Download and install Drupal modules using Drush

In this article, we are going to discuss about How to download and install Drupal modules using Drush command. One of the best things about building websites with Drupal is that there are thousands of modules that help you quickly create functionality.

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

In this article, we are going to discuss about How to rewrite the Drupal views output for custom theming and CSS. We were recently tasked with creating an events calendar that has a custom CCK field called "City". There's a drop down menu with a list of cities to assign the event to. So along with an event listing, you also see a city such as New York or Boston. The client wanted these cities to be color coded.

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

In this article, we are going o discuss about how to add HTTp authentication to the Drupal website. HTTP authentication is quite useful to be added on your live site when you need only the authenticated people to have the access.

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

Adaptivetheme is a powerful theme framework designed from the ground up to power modern, cross browser/cross device websites using responsive design techniques. Adaptivetheme allows you to set up specific layouts for different device groups - such as desktop, tablet and smartphone - all with zero coding. Its all easy point-and-click configuration.

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:

  1. Responsive design
  2. Pluggable layout system
  3. HTML5
  4. WGAC 2.0 Accessibility
To download the theme Click Here

For Documentation Click Here

Steps to configure google maps with nodes in Drupal

In this article, we are going to discuss about How to configure google maps with nodes in Drupal. Drupal is a free software package that allows you to easily organize, manage and publish your content, with an endless variety of customization.

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

Sometimes on our localbox [wamp : localhost] we found that when enabled css aggregation and javascript aggregation then we cannot able to access our admin area and user login area. I mean we cannot able to access the site because we enabled css aggregation. Now you cannot access the admin area and you cannot even login so what to do.
You have to disable your css aggregation from database using your phpmyadmin. Follow the below steps:
  1. Open phpmyadmin and select the database which is used by the drupal instance.
  2. Select the "variables" table.
  3. Search for "preprocess_css" in name field.
  4. Change the variable value to s:1:"0";
  5. And you are done.

So this is the way you can disable your css aggregation with the use of phpmyadmin. If you want to disable javascript aggregation, you have to follow the same steps and you have to search for "preprocess_js" in "variables" table in step 3. That's it!
If you have any tips like this let share here in comments.

Drupal 7 - Create custom blocks with custom fields

In this article, we are going to discuss about How to create custom blocks with custom fields in Drupal 7. In Drupal 7, we can create custom blocks with custom fields using hook_block_configure() and hook_block_save() hooks.

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

In this article, we are going to discuss about How to use the WordPress like pager in Drupal website. Most of the people preferred the Wordpress kind of pagination in drupal based site. But by default, Drupal provides the number pagination < 1 2 3..> . so here is the snippet to get the Wordpress kind of pagination which overrides the default pagination of 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

In this article, we are going to discuss about How to integrate the G2W (Goto Webinar) module with Drupal. G2W is useful for the User to signup for the webinars (payment based) through our site. Once the user completed the payment, it will registering the user to the registrants list in G2W (Goto Webinar) ( where actually they are hosting the webinars).

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&#8221;;// 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

In this article, we are going to discuss about How to override the Exposed views filters with hook_form_alter function. Override the exposed filters is easy and a good way of customizing what users can select. A common example I've found is when you want users to choose from a select list but views only provides you with a textfield.  Using hook_form_alter, you can easily change the #type attirbute of the exposed filter form.

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

In this article, we are going to discuss about How to display the Twitter Feeds in Drupal Block. By using this, we can display that Twitter feed anywhere in the site. There are many different Twitter options out there but most have a couple of limitations. The limitations are below

  1. They post tweets as nodes rather than in a block.
  2. 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

  1. Click Here to download the Twitter block module.
  2. Extract the files into a folder on your desktop. The folder will be called "twitterblock".
  3. Login to your site's files via FTP and navigate to /sites/default/. If there isn't a folder called /modules/ here, create one.
  4. Upload the "twitterblock" folder to /sites/default/modules/
  5. 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

  1. Go to Administrator >> Structure >> Blocks. Scroll down to find Twitter Block.
  2. 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

When developing a Drupal 7 theme, some of the CSS and JS files will get automatically included. In this article, we are going to discuss about How to remove the System CSS and JS files from the Drupal 7 themes. We can achive this by using Drupal hook system. The hook system allows the developer to apply global controls and functionality without hacking theme files with PHP.

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.