breznflow/includes/Features/NodeTypeRegistry.php
Michael fd83e4810b BreznFlow 1.0.0 — WordPress.org submission
Initial public release of BreznFlow, an n8n workflow renderer for WordPress.
Fully PHPCS-compliant (WordPress Coding Standards), security-hardened,
and ready for WordPress.org plugin review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 11:27:36 +00:00

813 lines
20 KiB
PHP

<?php
/**
* Registry of known n8n node types with display metadata.
*
* @package BreznFlow
* @since 1.0.0
*/
namespace BreznFlow\Features;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Registry of known n8n node types with their display metadata.
*
* @since 1.0.0
*/
class NodeTypeRegistry {
/**
* Returns the full node type registry.
* Keys are n8n node slugs (part after last dot in type string).
*
* @return array<string, array{label: string, icon: string, symbol?: string, color: string, bg: string}>
*/
public static function get_registry(): array {
return array(
// Triggers.
'scheduleTrigger' => array(
'label' => 'Schedule Trigger',
'icon' => 'symbol',
'symbol' => '⏰',
'color' => '#31C48D',
'bg' => '#1a3a2e',
),
'webhook' => array(
'label' => 'Webhook',
'icon' => 'symbol',
'symbol' => '⇆',
'color' => '#5277D4',
'bg' => '#1a2040',
),
'manualTrigger' => array(
'label' => 'Manual Trigger',
'icon' => 'symbol',
'symbol' => '▶',
'color' => '#FF6B35',
'bg' => '#3a1a00',
),
'formTrigger' => array(
'label' => 'Form Trigger',
'icon' => 'symbol',
'symbol' => '▤',
'color' => '#9B59B6',
'bg' => '#2a1040',
),
'emailReadImap' => array(
'label' => 'Email (IMAP)',
'icon' => 'symbol',
'symbol' => '✉',
'color' => '#E74C3C',
'bg' => '#3a1010',
),
'respondToWebhook' => array(
'label' => 'Respond to Webhook',
'icon' => 'symbol',
'symbol' => '↩',
'color' => '#5277D4',
'bg' => '#1a2040',
),
// Core logic.
'code' => array(
'label' => 'Code',
'icon' => 'symbol',
'symbol' => '{ }',
'color' => '#FF9500',
'bg' => '#3a2000',
),
'function' => array(
'label' => 'Function',
'icon' => 'symbol',
'symbol' => 'ƒ',
'color' => '#FF9500',
'bg' => '#3a2000',
),
'executeWorkflow' => array(
'label' => 'Execute Workflow',
'icon' => 'symbol',
'symbol' => '↺',
'color' => '#7F8C8D',
'bg' => '#2d2d2d',
),
'httpRequest' => array(
'label' => 'HTTP Request',
'icon' => 'symbol',
'symbol' => '⊕',
'color' => '#5277D4',
'bg' => '#1a2040',
),
'wait' => array(
'label' => 'Wait',
'icon' => 'symbol',
'symbol' => 'II',
'color' => '#9B59B6',
'bg' => '#2a1040',
),
'if' => array(
'label' => 'If',
'icon' => 'symbol',
'symbol' => '?',
'color' => '#F39C12',
'bg' => '#3a2800',
),
'switch' => array(
'label' => 'Switch',
'icon' => 'symbol',
'symbol' => '⋈',
'color' => '#F39C12',
'bg' => '#3a2800',
),
'merge' => array(
'label' => 'Merge',
'icon' => 'symbol',
'symbol' => '⊕',
'color' => '#26C6DA',
'bg' => '#003040',
),
'splitInBatches' => array(
'label' => 'Split in Batches',
'icon' => 'symbol',
'symbol' => '⋮',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'set' => array(
'label' => 'Set',
'icon' => 'symbol',
'symbol' => '=',
'color' => '#26C6DA',
'bg' => '#003040',
),
'editFields' => array(
'label' => 'Edit Fields',
'icon' => 'symbol',
'symbol' => '=',
'color' => '#26C6DA',
'bg' => '#003040',
),
'filter' => array(
'label' => 'Filter',
'icon' => 'symbol',
'symbol' => '▽',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'aggregate' => array(
'label' => 'Aggregate',
'icon' => 'symbol',
'symbol' => '∑',
'color' => '#26C6DA',
'bg' => '#003040',
),
'splitOut' => array(
'label' => 'Split Out',
'icon' => 'symbol',
'symbol' => '↔',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'sort' => array(
'label' => 'Sort',
'icon' => 'symbol',
'symbol' => '↕',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'limit' => array(
'label' => 'Limit',
'icon' => 'symbol',
'symbol' => '⊐',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'removeDuplicates' => array(
'label' => 'Remove Duplicates',
'icon' => 'symbol',
'symbol' => '≠',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'itemLists' => array(
'label' => 'Item Lists',
'icon' => 'symbol',
'symbol' => '≡',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'compareDatasets' => array(
'label' => 'Compare Datasets',
'icon' => 'symbol',
'symbol' => '△',
'color' => '#26C6DA',
'bg' => '#003040',
),
'noOp' => array(
'label' => 'No Operation',
'icon' => 'symbol',
'symbol' => '∅',
'color' => '#BDC3C7',
'bg' => '#2d2d2d',
),
'stickyNote' => array(
'label' => 'Sticky Note',
'icon' => 'symbol',
'symbol' => '📌',
'color' => '#F1C40F',
'bg' => '#3a3000',
),
'stopAndError' => array(
'label' => 'Stop and Error',
'icon' => 'symbol',
'symbol' => '✕',
'color' => '#E74C3C',
'bg' => '#3a1010',
),
// Data transformation.
'toFile' => array(
'label' => 'To File',
'icon' => 'symbol',
'symbol' => '↓',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'convertToFile' => array(
'label' => 'Convert to File',
'icon' => 'symbol',
'symbol' => '↓',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'html' => array(
'label' => 'HTML',
'icon' => 'symbol',
'symbol' => '</>',
'color' => '#E34F26',
'bg' => '#3a1000',
),
'xml' => array(
'label' => 'XML',
'icon' => 'symbol',
'symbol' => '</>',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'markdown' => array(
'label' => 'Markdown',
'icon' => 'symbol',
'symbol' => 'M↓',
'color' => '#083FA1',
'bg' => '#001040',
),
'crypto' => array(
'label' => 'Crypto',
'icon' => 'symbol',
'symbol' => '🔒',
'color' => '#F39C12',
'bg' => '#3a2800',
),
'compression' => array(
'label' => 'Compression',
'icon' => 'symbol',
'symbol' => '⇩',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
'extractFromFile' => array(
'label' => 'Extract from File',
'icon' => 'symbol',
'symbol' => '↑',
'color' => '#95A5A6',
'bg' => '#2d2d2d',
),
// Databases.
'mySql' => array(
'label' => 'MySQL',
'icon' => 'initials',
'symbol' => 'My',
'color' => '#4479A1',
'bg' => '#001530',
),
'postgres' => array(
'label' => 'PostgreSQL',
'icon' => 'initials',
'symbol' => 'PG',
'color' => '#336791',
'bg' => '#001030',
),
'redis' => array(
'label' => 'Redis',
'icon' => 'initials',
'symbol' => 'Re',
'color' => '#DC382D',
'bg' => '#3a1010',
),
'mongodb' => array(
'label' => 'MongoDB',
'icon' => 'initials',
'symbol' => 'Mg',
'color' => '#4DB33D',
'bg' => '#103010',
),
'sqlite' => array(
'label' => 'SQLite',
'icon' => 'initials',
'symbol' => 'SL',
'color' => '#003B57',
'bg' => '#001020',
),
'microsoftSql' => array(
'label' => 'MS SQL',
'icon' => 'initials',
'symbol' => 'MS',
'color' => '#CC2927',
'bg' => '#3a1010',
),
'supabase' => array(
'label' => 'Supabase',
'icon' => 'initials',
'symbol' => 'Sb',
'color' => '#3ECF8E',
'bg' => '#103020',
),
// Communication.
'slack' => array(
'label' => 'Slack',
'icon' => 'initials',
'symbol' => 'Sl',
'color' => '#4A154B',
'bg' => '#200020',
),
'telegram' => array(
'label' => 'Telegram',
'icon' => 'symbol',
'symbol' => '✈',
'color' => '#2CA5E0',
'bg' => '#003040',
),
'discord' => array(
'label' => 'Discord',
'icon' => 'initials',
'symbol' => 'Dc',
'color' => '#5865F2',
'bg' => '#101080',
),
'mattermost' => array(
'label' => 'Mattermost',
'icon' => 'initials',
'symbol' => 'Mm',
'color' => '#0058CC',
'bg' => '#001840',
),
'emailSend' => array(
'label' => 'Send Email',
'icon' => 'symbol',
'symbol' => '✉',
'color' => '#E74C3C',
'bg' => '#3a1010',
),
'sendEmail' => array(
'label' => 'Send Email',
'icon' => 'symbol',
'symbol' => '✉',
'color' => '#E74C3C',
'bg' => '#3a1010',
),
'gmail' => array(
'label' => 'Gmail',
'icon' => 'initials',
'symbol' => 'Gm',
'color' => '#EA4335',
'bg' => '#3a1010',
),
'microsoftOutlook' => array(
'label' => 'Outlook',
'icon' => 'initials',
'symbol' => 'Ol',
'color' => '#0078D4',
'bg' => '#001840',
),
'whatsapp' => array(
'label' => 'WhatsApp',
'icon' => 'initials',
'symbol' => 'WA',
'color' => '#25D366',
'bg' => '#103020',
),
'twilio' => array(
'label' => 'Twilio',
'icon' => 'initials',
'symbol' => 'Tw',
'color' => '#F22F46',
'bg' => '#3a1010',
),
// Google.
'googleSheets' => array(
'label' => 'Google Sheets',
'icon' => 'initials',
'symbol' => 'GS',
'color' => '#34A853',
'bg' => '#103020',
),
'googleDrive' => array(
'label' => 'Google Drive',
'icon' => 'initials',
'symbol' => 'GD',
'color' => '#4285F4',
'bg' => '#001840',
),
'googleCalendar' => array(
'label' => 'Google Calendar',
'icon' => 'initials',
'symbol' => 'GC',
'color' => '#4285F4',
'bg' => '#001840',
),
'googleDocs' => array(
'label' => 'Google Docs',
'icon' => 'initials',
'symbol' => 'Gd',
'color' => '#4285F4',
'bg' => '#001840',
),
'youTube' => array(
'label' => 'YouTube',
'icon' => 'initials',
'symbol' => 'YT',
'color' => '#FF0000',
'bg' => '#3a0000',
),
// Dev / Version control.
'github' => array(
'label' => 'GitHub',
'icon' => 'initials',
'symbol' => 'GH',
'color' => '#24292E',
'bg' => '#000000',
),
'gitlab' => array(
'label' => 'GitLab',
'icon' => 'initials',
'symbol' => 'GL',
'color' => '#FC6D26',
'bg' => '#3a1a00',
),
'jira' => array(
'label' => 'Jira',
'icon' => 'initials',
'symbol' => 'Ji',
'color' => '#0052CC',
'bg' => '#001840',
),
'confluence' => array(
'label' => 'Confluence',
'icon' => 'initials',
'symbol' => 'Cf',
'color' => '#0052CC',
'bg' => '#001840',
),
'linear' => array(
'label' => 'Linear',
'icon' => 'initials',
'symbol' => 'Li',
'color' => '#5E6AD2',
'bg' => '#101040',
),
'notion' => array(
'label' => 'Notion',
'icon' => 'initials',
'symbol' => 'No',
'color' => '#FFFFFF',
'bg' => '#1a1a1a',
),
// AI.
'openAi' => array(
'label' => 'OpenAI',
'icon' => 'initials',
'symbol' => 'OA',
'color' => '#00A67E',
'bg' => '#003020',
),
'anthropicClaude' => array(
'label' => 'Anthropic',
'icon' => 'initials',
'symbol' => 'An',
'color' => '#D97757',
'bg' => '#3a1a00',
),
'anthropic' => array(
'label' => 'Anthropic',
'icon' => 'initials',
'symbol' => 'An',
'color' => '#D97757',
'bg' => '#3a1a00',
),
'gemini' => array(
'label' => 'Gemini',
'icon' => 'initials',
'symbol' => 'Gm',
'color' => '#4285F4',
'bg' => '#001840',
),
'googleAi' => array(
'label' => 'Google AI',
'icon' => 'initials',
'symbol' => 'GA',
'color' => '#4285F4',
'bg' => '#001840',
),
'huggingFace' => array(
'label' => 'HuggingFace',
'icon' => 'initials',
'symbol' => 'HF',
'color' => '#FFD21E',
'bg' => '#3a2a00',
),
'mistral' => array(
'label' => 'Mistral',
'icon' => 'initials',
'symbol' => 'Mi',
'color' => '#FF7000',
'bg' => '#3a1a00',
),
'ollama' => array(
'label' => 'Ollama',
'icon' => 'initials',
'symbol' => 'Ol',
'color' => '#7C3AED',
'bg' => '#200040',
),
'lmChatOllama' => array(
'label' => 'Ollama Chat',
'icon' => 'initials',
'symbol' => 'Ol',
'color' => '#7C3AED',
'bg' => '#200040',
),
'cohere' => array(
'label' => 'Cohere',
'icon' => 'initials',
'symbol' => 'Co',
'color' => '#39594D',
'bg' => '#102020',
),
'lmChatOpenAi' => array(
'label' => 'OpenAI Chat',
'icon' => 'initials',
'symbol' => 'OA',
'color' => '#00A67E',
'bg' => '#003020',
),
'lmChatAnthropic' => array(
'label' => 'Anthropic Chat',
'icon' => 'initials',
'symbol' => 'An',
'color' => '#D97757',
'bg' => '#3a1a00',
),
'agent' => array(
'label' => 'AI Agent',
'icon' => 'symbol',
'symbol' => '🤖',
'color' => '#7C3AED',
'bg' => '#200040',
),
'chainLlm' => array(
'label' => 'LLM Chain',
'icon' => 'symbol',
'symbol' => '⛓',
'color' => '#7C3AED',
'bg' => '#200040',
),
'vectorStore' => array(
'label' => 'Vector Store',
'icon' => 'symbol',
'symbol' => '⊞',
'color' => '#7C3AED',
'bg' => '#200040',
),
'memoryBufferWindow' => array(
'label' => 'Memory Buffer',
'icon' => 'symbol',
'symbol' => '◫',
'color' => '#7C3AED',
'bg' => '#200040',
),
// Storage / Files.
'ftp' => array(
'label' => 'FTP',
'icon' => 'initials',
'symbol' => 'FT',
'color' => '#F39C12',
'bg' => '#3a2800',
),
'ssh' => array(
'label' => 'SSH',
'icon' => 'symbol',
'symbol' => '$_',
'color' => '#2ECC71',
'bg' => '#103020',
),
'readWriteFile' => array(
'label' => 'Read/Write Files',
'icon' => 'symbol',
'symbol' => '📁',
'color' => '#F39C12',
'bg' => '#3a2800',
),
'airtable' => array(
'label' => 'Airtable',
'icon' => 'initials',
'symbol' => 'At',
'color' => '#18BFFF',
'bg' => '#003040',
),
'baserow' => array(
'label' => 'Baserow',
'icon' => 'initials',
'symbol' => 'Br',
'color' => '#4A90D9',
'bg' => '#001840',
),
// CRM / Marketing.
'hubspot' => array(
'label' => 'HubSpot',
'icon' => 'initials',
'symbol' => 'Hs',
'color' => '#FF7A59',
'bg' => '#3a1a00',
),
'salesforce' => array(
'label' => 'Salesforce',
'icon' => 'initials',
'symbol' => 'Sf',
'color' => '#00A1E0',
'bg' => '#003040',
),
'mailchimp' => array(
'label' => 'Mailchimp',
'icon' => 'initials',
'symbol' => 'Mc',
'color' => '#FFE01B',
'bg' => '#3a3000',
),
'brevo' => array(
'label' => 'Brevo',
'icon' => 'initials',
'symbol' => 'Bv',
'color' => '#0092FF',
'bg' => '#003040',
),
// WordPress / CMS.
'wordpress' => array(
'label' => 'WordPress',
'icon' => 'initials',
'symbol' => 'WP',
'color' => '#21759B',
'bg' => '#002030',
),
'rssFeedRead' => array(
'label' => 'RSS Feed',
'icon' => 'symbol',
'symbol' => '◉',
'color' => '#F26522',
'bg' => '#3a1000',
),
);
}
/**
* Looks up a node by its full n8n type string.
* Extracts the slug (last segment after dot) for registry lookup.
*
* @param string $type Full n8n type string e.g. "n8n-nodes-base.httpRequest".
* @return array Node type data or fallback entry.
*/
public static function lookup( string $type ): array {
$registry = self::get_registry();
$slug = self::extract_slug( $type );
// Case-sensitive exact match first.
if ( isset( $registry[ $slug ] ) ) {
return $registry[ $slug ];
}
// Case-insensitive fallback.
$slug_lower = strtolower( $slug );
foreach ( $registry as $key => $entry ) {
if ( strtolower( $key ) === $slug_lower ) {
return $entry;
}
}
// Generate fallback entry.
return self::generate_fallback( $type, $slug );
}
/**
* Extracts the node slug from a full n8n type string.
*
* @param string $type e.g. "n8n-nodes-base.httpRequest" → "httpRequest".
*/
public static function extract_slug( string $type ): string {
$parts = explode( '.', $type );
return end( $parts );
}
/**
* Generates a deterministic fallback entry for unknown node types.
* Uses djb2 hash for a consistent color.
*
* @since 1.0.0
* @param string $type Full n8n node type string.
* @param string $slug Extracted slug portion.
* @return array Fallback display metadata.
*/
private static function generate_fallback( string $type, string $slug ): array {
$hue = self::djb2_color_hue( $type );
$hex = self::hsl_to_hex( $hue, 60, 55 );
$bg = self::hsl_to_hex( $hue, 40, 15 );
// Derive 2-letter initials from slug.
$initials = self::derive_initials( $slug );
return array(
'label' => $slug,
'icon' => 'initials',
'symbol' => $initials,
'color' => $hex,
'bg' => $bg,
);
}
/**
* Derives 2-letter initials from a camelCase or PascalCase slug.
*
* @since 1.0.0
* @param string $slug Node type slug.
* @return string Two uppercase initials.
*/
private static function derive_initials( string $slug ): string {
// Split on camelCase boundaries.
$parts = preg_split( '/(?=[A-Z])/', $slug, -1, PREG_SPLIT_NO_EMPTY );
if ( ! $parts ) {
return strtoupper( substr( $slug, 0, 2 ) );
}
if ( count( $parts ) >= 2 ) {
return strtoupper( substr( $parts[0], 0, 1 ) . substr( $parts[1], 0, 1 ) );
}
return strtoupper( substr( $slug, 0, 2 ) );
}
/**
* Computes a djb2 hash and maps it to a color hue in the 0-359 range.
*
* @since 1.0.0
* @param string $str Input string to hash.
* @return int Hue value between 0 and 359.
*/
private static function djb2_color_hue( string $str ): int {
$hash = 5381;
$len = strlen( $str );
for ( $i = 0; $i < $len; $i++ ) {
$hash = ( ( $hash << 5 ) + $hash ) + ord( $str[ $i ] );
$hash &= 0xFFFFFFFF;
}
return abs( $hash ) % 360;
}
/**
* Converts HSL to 6-digit hex color string.
*
* @since 1.0.0
* @param int $h Hue (0-359).
* @param int $s Saturation (0-100).
* @param int $l Lightness (0-100).
* @return string Hex color string (e.g. "#ff8800").
*/
private static function hsl_to_hex( int $h, int $s, int $l ): string {
$s /= 100;
$l /= 100;
$a = $s * min( $l, 1 - $l );
$f = static function ( $n ) use ( $h, $l, $a ) {
$k = fmod( $n + $h / 30, 12 );
return $l - $a * max( min( $k - 3, 9 - $k, 1 ), -1 );
};
$r = (int) round( $f( 0 ) * 255 );
$g = (int) round( $f( 8 ) * 255 );
$b = (int) round( $f( 4 ) * 255 );
return sprintf( '#%02x%02x%02x', max( 0, min( 255, $r ) ), max( 0, min( 255, $g ) ), max( 0, min( 255, $b ) ) );
}
}