إذا سبق لك أن رأيت مربع "غير موجود" بعد التخزين المؤقت العدواني، أو لم يتم العثور على معالج البرنامج النصي بما أن عملية البناء الخاصة بك قد غيرت قيمة التجزئة الخاصة بها، فقد تطرقت إلى المشكلة الحقيقية: كتلة حديثة، في WordPress 6.9.4، يتم التحكم فيه مبدئيًا بواسطة block.json وعبر واجهة برمجة تطبيقات الكتل، وليس عبر مجموعة من wp_enqueue_script() متناثرة.

المشكلة / الحاجة

تريد حفظ كتل غوتنبرغ "الحديثة" (للتحرير والواجهة الأمامية)، مع تعريف الأصول بشكل صحيح، وإصدارها، وقابليتها للترجمة، والأهم من ذلك، سهولة صيانتها. كما تريد تجنب الطرق القديمة التي تُلقي بكل شيء في مكان واحد. functions.phpيحدث عطل عند أول عملية إعادة هيكلة، ويجعل عملية تصحيح الأخطاء أمرًا مزعجًا بمجرد وجود بيئتين (التطوير/الإنتاج) ومكون إضافي للتخزين المؤقت.

هذا المحتوى موجه للمدونين المتقدمين (ومطوريهم) الذين ينشرون بانتظام، ولديهم متطلبات أداء، ويريدون أن يكونوا قادرين على تطوير وحداتهم البرمجية دون خوف من كسرها. محتوى القائمة.

في النهاية، ستعرف:

  • قم بتعريف كتلة عبر block.json (البيانات الوصفية، والخصائص، والوسائط، والأنماط، والاختلافات)،
  • احفظ كتلة جانب PHP باستخدام register_block_type() عن طريق الإشارة إلى المجلد،
  • شاحن النصوص البرمجية/الأنماط عبر wp_register_script()/wp_register_style() + تبعيات موثوقة (بدون "تخمين")،
  • قم بإنشاء الكتلة نموذج ديناميكي (PHP) مع مراعاة النظافة والسلامة المناسبة (التعقيم/الهروب)،
  • اختبار واستكشاف الأخطاء وإصلاحها في حالات العالم الحقيقي (ذاكرة التخزين المؤقت، خطاف سيئ، بناء مفقود، PHP قديم، إلخ).

ملخص سريع

  • نقوم بإنشاء إضافة (موصى بها) تعرض كتلة عبر مجلد build/ تحتوي block.json + الأصول المجمعة.
  • نحفظ الكتلة باستخدام register_block_type( __DIR__ . '/build/mon-bloc' )والذي ينص على block.json.
  • يتم تعريف معالجات JS/CSS وتوابعها بشكل صريح (ويفضل أن يكون ذلك عبر الملف). .asset.php تم إنشاؤه بواسطة @wordpress/scripts).
  • نقوم بإنشاء كتلة ديناميكية (PHP) لعرض قوي (تحسين محركات البحث، الأداء، توافق القوالب)، مع الحفاظ على معاينة لائقة في المحرر.
  • نضيف نسخة "بسيطة" (كتلة ثابتة) ونسخة "متقدمة" (عرض الخادم + ذاكرة التخزين المؤقت + اختلافات).

متى يستخدم هذا الحل

  • أنت بحاجة إلى كتلة قابلة لإعادة الاستخدام وقابلة للتحديث (على سبيل المثال، إدراج النشرة الإخبارية، إشعار، مربع "للتذكير"، ملخص، دعوة لاتخاذ إجراء).
  • أنت تريد عرض الواجهة الأمامية الذي يتم التحكم فيه من جانب الخادم (على سبيل المثال، الطلبات، والتخصيص القائم على السياق، واختبار A/B، والبيانات الخارجية).
  • أنت تعمل ضمن فريق وتريد مصدرًا واضحًا للمعلومات الصحيحة (block.json) بدلاً من التعليمات البرمجية المجزأة.
  • أنت بحاجة إلى إدارة تبعيات جافا سكريبت الخاصة بـ WordPress (الحزم) @wordpress/*) دون تعطيلها مع كل تحديث.
  • أنت تستهدف WordPress 6.9.4+ و PHP 8.1+ (مما يسمح بكتابة أكواد أنظف، وكتابة خفيفة الوزن، وأدوات حديثة).

متى لا يجب استخدام هذا الحل

  • إذا كنت ترغب فقط في تصميم بسيط ومستقر، فغالبًا ما يكون استخدام قالب أصلي (Core) بالإضافة إلى الأنماط العامة والقوالب كافيًا. ألقِ نظرة على أنماط الكتلة et اختلافات النمط قبل كتابة جافا سكريبت.
  • أنت تستخدم موقعًا مبنيًا بالكامل على Elementor/Divi/Avada، ولا يتم استخدام محرر الكتل للمحتوى. في هذه الحالة، ستكون أداة/وحدة من مُنشئ المواقع أكثر ملاءمة لفريق التحرير.
  • لا يمكنك إعداد سلسلة بناء (أو أنك ترفض Node): يمكنك إنشاء كتلة بسيطة للغاية "بدون بناء"، لكنك ستفقد بسرعة DX والمتانة.
  • ينبع احتياجك من رمز مختصر قديم مستخدم على نطاق واسع. يُعدّ الانتقال إلى كتلة برمجية أمرًا ممكنًا، ولكنه يتطلب استراتيجية (إيقاف مؤقت، تحويل، اختبار المحتوى).

المتطلبات الأساسية / قبل البدء

أفترض أنك تستخدم ووردبريس 6.9.4 (أبريل 2026) وPHP 8.1 أو أحدث. إذا كنت تستخدم إصدارًا أقدم من ذلك، فستواجه العديد من المشاكل (أخطاء فادحة، وتضارب في تبعيات جافا سكريبت، وعدم إمكانية إعادة إنتاج المشكلة).

  • بيئة موقع تجريبي + نسخة احتياطية (قاعدة بيانات + wp-content)، لا توجد اختبارات مباشرة في الإنتاج.
  • أدوات Node.js LTS (للتجميع)، npm/pnpm، ومن الأفضل @wordpress/scripts.
  • معرفة : خطافات ووردبريس، وقاعدة REST/nonce، ومفاهيم البناء (webpack/vite عبر نصوص ووردبريس).

الوثائق الرسمية المفيدة:

النهج الساذج (ولماذا يجب تجنبه)

النموذج الكلاسيكي الذي ما زلت أراه في عام 2026: أ functions.php (أو إضافة مقتطفات) التي تسجل نصًا برمجيًا عامًا وتحاول "إنشاء كتلة" باستخدام جافا سكريبت المضمنة، بدون block.jsonبدون ملف .asset.php، بدون إدارة مناسبة للتبعيات.

<?php
// Exemple volontairement naïf : à éviter.
add_action('init', function () {
    wp_enqueue_script(
        'mon-bloc-js',
        get_stylesheet_directory_uri() . '/js/mon-bloc.js',
        array('wp-blocks', 'wp-element', 'wp-editor'),
        '1.0.0',
        true
    );
});

وبالتحديد، لماذا تُعد هذه مشكلة؟

  • خطاف سيء : wp_enqueue_script() في init لا يستهدف الناشر بشكل صحيح (وقد يُشوّه واجهة المستخدم). أنت تريد enqueue_block_editor_assets أو الأفضل من ذلك، أعلن عبر block.json + التسجيل.
  • التبعيات الهشة أنت "تخمّن" المقابض. مع تطور محرر الكتل، ينتهي بك الأمر إلى أخطاء مثل لم يتم تعريف wp أو حزم تم تحميلها مرتين.
  • ذاكرة التخزين المؤقت والإصدارات : بدون نظام إصدار قائم على البناء، فإنك تقوم بتقديم ملفات جافا سكريبت قديمة (وهي أكثر الأخطاء شيوعًا في المواقع التي تستخدم Cloudflare + إضافة التخزين المؤقت).
  • الدورية من المستحيل فهم الكتلة البرمجية دون البحث في العديد من الملفات والخطافات.
  • أمن عندما يتم العرض على جانب جافا سكريبت فقط، ينتهي الأمر بالعديد من الأشخاص بإدخال بيانات غير مُعالجة في الواجهة الأمامية.

النهج الصحيح - دليل خطوة بخطوة

الخطوة 1 - إنشاء إضافة مخصصة (بدلاً من قالب)

يُعدّ وضع المحتوى في إضافة ميزة. وحسب تجربتي، فإنّ وضعه في إضافة يمنع فقدان المحتوى عند تغيير القوالب.

# wp-content/plugins/bpcab-blocs/
mkdir -p wp-content/plugins/bpcab-blocs
<?php
/**
 * Plugin Name: BPCAB Blocs
 * Description: Blocs personnalisés modernes via block.json (WP 6.9.4+).
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 * Author: Votre Nom
 */

defined('ABSPATH') || exit;

require_once __DIR__ . '/src/Plugin.php';

add_action('plugins_loaded', static function () {
    BPCABBlocksPlugin::instance()->boot();
});

الخطوة 2 - أضف "حاوية خدمة" صغيرة (نظيفة وقابلة للاختبار)

ليس من الضروري استخدام حقن التبعية، ولكن بمجرد أن يصبح لديك 2-3 كتل برمجية، يصبح الأمر مفيدًا. كما أنه يمنع تعارض الدوال العامة.

<?php
// wp-content/plugins/bpcab-blocs/src/Plugin.php

namespace BPCABBlocks;

defined('ABSPATH') || exit;

final class Plugin {
    private static ?self $instance = null;

    /** @var array<string, object> */
    private array $services = [];

    public static function instance(): self {
        return self::$instance ??= new self();
    }

    public function boot(): void {
        $this->register_services();
        $this->init_hooks();
    }

    private function register_services(): void {
        $this->services['blocks'] = new ServiceBlocksRegistry(__DIR__ . '/../build');
    }

    private function init_hooks(): void {
        add_action('init', [$this->services['blocks'], 'register_blocks']);
    }
}

الخطوة 3 - إنشاء مجلد الكتلة وملف block.json الخاص به

سنقوم بإنشاء كتلة "مربع ملاحظات" (ثابتة في جانب المحرر، ويتم عرضها ديناميكيًا في الواجهة الأمامية). block.json هو مصدر الحقيقة.

mkdir -p wp-content/plugins/bpcab-blocs/build/note-box
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "bpcab/note-box",
  "version": "1.0.0",
  "title": "Encart Note (BPCAB)",
  "category": "text",
  "icon": "info-outline",
  "description": "Encart éditorial avec titre, contenu et style.",
  "textdomain": "bpcab-blocs",
  "attributes": {
    "title": {
      "type": "string",
      "default": "À retenir"
    },
    "tone": {
      "type": "string",
      "default": "info"
    }
  },
  "supports": {
    "anchor": true,
    "align": ["wide", "full"],
    "html": false,
    "spacing": {
      "margin": true,
      "padding": true
    },
    "color": {
      "background": true,
      "text": true
    },
    "typography": {
      "fontSize": true,
      "lineHeight": true
    }
  },
  "editorScript": "bpcab-note-box-editor",
  "style": "bpcab-note-box-style",
  "render": "file:./render.php"
}

ملاحظات فنية:

  • apiVersion: 3 وهو الأساس للكتل الحديثة (ويتوافق مع WP 6.9.x).
  • render يشير إلى ملف PHP: هذا عنصر ديناميكي. مفيد جدًا للحفاظ على استقرار واجهة HTML الأمامية، حتى في حال تغيير المحرر.
  • editorScript et style مرجع مقابض ووردبريس. سنقوم بتعريفها بشكل صحيح.

الخطوة 4 - تعريف الأصول باستخدام .asset.php (التبعيات الموثوقة)

النمط القوي هو: بناء جافا سكريبت ← إنشاء ملف index.asset.php والذي يحتوي dependencies + versionوهذا تحديداً ما يمنع "التخمينات المتعلقة بالمقابض".

ولضمان سهولة نسخ ولصق الكود هنا بنسبة 100%، سأعرض لكم جانب PHP الذي الاستخدامات هذا الملف. (يمكنك إنشاء هذه النسخة عبر @wordpress/scripts.)

الخطوة 5 — تسجيل الكتلة باستخدام الدالة register_block_type()، مع الإشارة إلى المجلد

نقوم بإنشاء سجل يقوم بفحص مجلد build/ ويسجل كل كتلة تحتوي على block.jsonهذا نمط أستخدمه غالبًا في مواقع الويب متعددة الكتل.

<?php
// wp-content/plugins/bpcab-blocs/src/Service/BlocksRegistry.php

namespace BPCABBlocksService;

use WP_Error;

defined('ABSPATH') || exit;

final class BlocksRegistry {
    public function __construct(
        private readonly string $build_dir
    ) {}

    public function register_blocks(): void {
        if (!function_exists('register_block_type')) {
            // Sécurité : si l’API n’est pas disponible (cas rarissime), on stoppe.
            return;
        }

        $block_dirs = glob($this->build_dir . '/*', GLOB_ONLYDIR) ?: [];

        foreach ($block_dirs as $dir) {
            $block_json = $dir . '/block.json';
            if (!is_readable($block_json)) {
                continue;
            }

            // 1) Enregistrer les assets référencés par block.json (handles)
            $this->register_assets_for_block_dir($dir);

            // 2) Enregistrer le bloc à partir de son dossier (lit block.json)
            $result = register_block_type($dir);

            if ($result === false) {
                // En prod, évitez de spammer les logs. Ici, c’est volontairement visible.
                error_log('[BPCAB] Échec register_block_type pour: ' . $dir);
            }
        }
    }

    private function register_assets_for_block_dir(string $dir): void {
        // Convention : build/note-box/index.js + index.asset.php + style.css
        $slug = basename($dir);

        $editor_handle = 'bpcab-' . $slug . '-editor';
        $style_handle  = 'bpcab-' . $slug . '-style';

        $editor_js     = $dir . '/index.js';
        $editor_asset  = $dir . '/index.asset.php';
        $style_css     = $dir . '/style.css';

        if (is_readable($editor_js) && is_readable($editor_asset)) {
            $asset = require $editor_asset;

            $deps = isset($asset['dependencies']) && is_array($asset['dependencies'])
                ? $asset['dependencies']
                : [];

            $ver = isset($asset['version']) && is_string($asset['version'])
                ? $asset['version']
                : filemtime($editor_js);

            wp_register_script(
                $editor_handle,
                plugins_url('build/' . $slug . '/index.js', dirname(__DIR__, 2) . '/bpcab-blocs.php'),
                $deps,
                $ver,
                true
            );

            // Optionnel : traductions JS si vous utilisez i18n côté JS.
            // wp_set_script_translations($editor_handle, 'bpcab-blocs', plugin_dir_path(...). 'languages');
        }

        if (is_readable($style_css)) {
            wp_register_style(
                $style_handle,
                plugins_url('build/' . $slug . '/style.css', dirname(__DIR__, 2) . '/bpcab-blocs.php'),
                [],
                filemtime($style_css)
            );
        }
    }
}

نعم ال plugins_url() استخدام مسار "الرابط" أمرٌ مُرهِق. أُبقيه واضحًا لأنه أحد الأخطاء التي أراها كثيرًا: عناوين URL للأصول تُشير إلى المُلحق الخاطئ بعد إعادة هيكلة الكود. بديل: تعريف ثابت BPCAB_BLOCKS_FILE في الملف الرئيسي.

الخطوة 6 - كتابة عملية العرض من جانب الخادم (render.php) مع مراعاة النظافة والأمان.

ينبغي أن يتعامل العرض من جانب الخادم مع السمات كمدخلات للمستخدم. حتى لو بدا المحرر "آمناً"، يمكن إدخال سمة عبر REST أو استيراد المحتوى.

<?php
// wp-content/plugins/bpcab-blocs/build/note-box/render.php

defined('ABSPATH') || exit;

/**
 * Rendu serveur du bloc bpcab/note-box.
 *
 * @param array $attributes Attributs du bloc (non fiables).
 * @param string $content Contenu interne (si InnerBlocks).
 * @param WP_Block $block Instance du bloc.
 */
return function (array $attributes, string $content, $block): string {
    $title = isset($attributes['title']) ? sanitize_text_field($attributes['title']) : 'À retenir';
    $tone  = isset($attributes['tone']) ? sanitize_key($attributes['tone']) : 'info';

    $allowed_tones = ['info', 'warning', 'success'];
    if (!in_array($tone, $allowed_tones, true)) {
        $tone = 'info';
    }

    // Récupère des classes générées par WordPress (align, spacing, colors, typography, etc.)
    $wrapper_attributes = get_block_wrapper_attributes([
        'class' => 'bpcab-note-box bpcab-note-box--' . $tone,
    ]);

    ob_start();
    ?>
    <div <?php echo $wrapper_attributes; ?>>
        <div class="bpcab-note-box__title"><?php echo esc_html($title); ?></div>
        <div class="bpcab-note-box__content">
            <?php
            // Ici, on autorise seulement du HTML “post” standard.
            // Si vous n’avez pas besoin de HTML, préférez esc_html().
            echo wp_kses_post($content);
            ?>
        </div>
    </div>
    <?php
    return (string) ob_get_clean();
};

الخطوة 7 - الحد الأدنى من CSS الأمامي المستقر (style.css)

/* wp-content/plugins/bpcab-blocs/build/note-box/style.css */
.bpcab-note-box {
  border-left: 4px solid currentColor;
  padding: 1rem;
  background: rgba(0,0,0,.03);
}

.bpcab-note-box__title {
  font-weight: 700;
  margin-bottom: .5rem;
}

.bpcab-note-box--info { color: #0b5fff; }
.bpcab-note-box--warning { color: #b45309; }
.bpcab-note-box--success { color: #15803d; }

الخطوة 8 - محرر جافا سكريبت (index.js): تحرير واجهة المستخدم البسيطة

أقدم مثالاً بسيطاً. عملياً، ستقوم بتجميعه (وتوليده). index.asset.php).

// wp-content/plugins/bpcab-blocs/build/note-box/index.js
import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, RichText, useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { PanelBody, SelectControl, TextControl } from '@wordpress/components';

registerBlockType('bpcab/note-box', {
	edit({ attributes, setAttributes }) {
		const { title, tone } = attributes;

		const blockProps = useBlockProps();

		return (
			<>
				<InspectorControls>
					<PanelBody title="Réglages">
						<TextControl
							label="Titre"
							value={ title }
							onChange={ (value) => setAttributes({ title: value }) }
						/>
						<SelectControl
							label="Ton"
							value={ tone }
							options={ [
								{ label: 'Info', value: 'info' },
								{ label: 'Avertissement', value: 'warning' },
								{ label: 'Succès', value: 'success' },
							] }
							onChange={ (value) => setAttributes({ tone: value }) }
						/>
					</PanelBody>
				</InspectorControls>

				<div { ...blockProps }>
					<div className="bpcab-note-box__title">
						<RichText
							tagName="span"
							value={ title }
							allowedFormats={ [] }
							onChange={ (value) => setAttributes({ title: value }) }
							placeholder="Titre…"
						/>
					</div>
					<div className="bpcab-note-box__content">
						<InnerBlocks
							template={ [
								['core/paragraph', { placeholder: 'Votre note…' }],
							] }
							templateLock={ false }
						/>
					</div>
				</div>
			</>
		);
	},

	// Bloc dynamique : le HTML front est rendu par render.php
	save() {
		return <InnerBlocks.Content />;
	},
});

الرمز الكامل

هذه هي الحزمة الوظيفية الأساسية (المكوّن الإضافي + السجل + الكتلة). ستحتاج إلى إضافة إصدار جافا سكريبت الفعلي (أو استخدام مسار العمل الخاص بك) لإنشائها. index.asset.phpيمكن نسخ الباقي ولصقه كما هو.

شجرة

wp-content/plugins/bpcab-blocs/
  bpcab-blocs.php
  src/
    Plugin.php
    Service/
      BlocksRegistry.php
  build/
    note-box/
      block.json
      render.php
      style.css
      index.js
      index.asset.php   # généré par build (ex: @wordpress/scripts)

bpcab-blocs.php

<?php
/**
 * Plugin Name: BPCAB Blocs
 * Description: Blocs personnalisés modernes via block.json (WP 6.9.4+).
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 */

defined('ABSPATH') || exit;

define('BPCAB_BLOCKS_FILE', __FILE__);
define('BPCAB_BLOCKS_DIR', __DIR__);

require_once BPCAB_BLOCKS_DIR . '/src/Plugin.php';

add_action('plugins_loaded', static function () {
    BPCABBlocksPlugin::instance()->boot();
});

src/Plugin.php

<?php
namespace BPCABBlocks;

defined('ABSPATH') || exit;

final class Plugin {
    private static ?self $instance = null;

    /** @var array<string, object> */
    private array $services = [];

    public static function instance(): self {
        return self::$instance ??= new self();
    }

    public function boot(): void {
        $this->register_services();
        $this->init_hooks();
    }

    private function register_services(): void {
        $this->services['blocks'] = new ServiceBlocksRegistry(BPCAB_BLOCKS_DIR . '/build');
    }

    private function init_hooks(): void {
        add_action('init', [$this->services['blocks'], 'register_blocks']);
    }
}

src/Service/BlocksRegistry.php

<?php
namespace BPCABBlocksService;

defined('ABSPATH') || exit;

final class BlocksRegistry {
    public function __construct(
        private readonly string $build_dir
    ) {}

    public function register_blocks(): void {
        $block_dirs = glob($this->build_dir . '/*', GLOB_ONLYDIR) ?: [];

        foreach ($block_dirs as $dir) {
            if (!is_readable($dir . '/block.json')) {
                continue;
            }

            $this->register_assets_for_block_dir($dir);

            $result = register_block_type($dir);
            if ($result === false) {
                error_log('[BPCAB] Échec register_block_type pour: ' . $dir);
            }
        }
    }

    private function register_assets_for_block_dir(string $dir): void {
        $slug = basename($dir);

        $editor_handle = 'bpcab-' . $slug . '-editor';
        $style_handle  = 'bpcab-' . $slug . '-style';

        $editor_js     = $dir . '/index.js';
        $editor_asset  = $dir . '/index.asset.php';
        $style_css     = $dir . '/style.css';

        if (is_readable($editor_js) && is_readable($editor_asset)) {
            $asset = require $editor_asset;

            $deps = (isset($asset['dependencies']) && is_array($asset['dependencies']))
                ? $asset['dependencies']
                : [];

            $ver = (isset($asset['version']) && is_string($asset['version']))
                ? $asset['version']
                : (string) filemtime($editor_js);

            wp_register_script(
                $editor_handle,
                plugins_url('build/' . $slug . '/index.js', BPCAB_BLOCKS_FILE),
                $deps,
                $ver,
                true
            );
        }

        if (is_readable($style_css)) {
            wp_register_style(
                $style_handle,
                plugins_url('build/' . $slug . '/style.css', BPCAB_BLOCKS_FILE),
                [],
                (string) filemtime($style_css)
            );
        }
    }
}

build/note-box/block.json

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "bpcab/note-box",
  "version": "1.0.0",
  "title": "Encart Note (BPCAB)",
  "category": "text",
  "icon": "info-outline",
  "description": "Encart éditorial avec titre, contenu et style.",
  "textdomain": "bpcab-blocs",
  "attributes": {
    "title": { "type": "string", "default": "À retenir" },
    "tone": { "type": "string", "default": "info" }
  },
  "supports": {
    "anchor": true,
    "align": ["wide", "full"],
    "html": false,
    "spacing": { "margin": true, "padding": true },
    "color": { "background": true, "text": true },
    "typography": { "fontSize": true, "lineHeight": true }
  },
  "editorScript": "bpcab-note-box-editor",
  "style": "bpcab-note-box-style",
  "render": "file:./render.php"
}

build/note-box/render.php

<?php
defined('ABSPATH') || exit;

return function (array $attributes, string $content, $block): string {
    $title = isset($attributes['title']) ? sanitize_text_field($attributes['title']) : 'À retenir';
    $tone  = isset($attributes['tone']) ? sanitize_key($attributes['tone']) : 'info';

    $allowed_tones = ['info', 'warning', 'success'];
    if (!in_array($tone, $allowed_tones, true)) {
        $tone = 'info';
    }

    $wrapper_attributes = get_block_wrapper_attributes([
        'class' => 'bpcab-note-box bpcab-note-box--' . $tone,
    ]);

    ob_start();
    ?>
    <div <?php echo $wrapper_attributes; ?>>
        <div class="bpcab-note-box__title"><?php echo esc_html($title); ?></div>
        <div class="bpcab-note-box__content"><?php echo wp_kses_post($content); ?></div>
    </div>
    <?php
    return (string) ob_get_clean();
};

build/note-box/style.css

.bpcab-note-box {
  border-left: 4px solid currentColor;
  padding: 1rem;
  background: rgba(0,0,0,.03);
}

.bpcab-note-box__title {
  font-weight: 700;
  margin-bottom: .5rem;
}

.bpcab-note-box--info { color: #0b5fff; }
.bpcab-note-box--warning { color: #b45309; }
.bpcab-note-box--success { color: #15803d; }

build/note-box/index.asset.php (مثال)

يتم إنشاء هذا الملف بواسطة أداة البناء الخاصة بك. مثال واقعي (تختلف التبعيات حسب الكود الخاص بك).

<?php
// Fichier généré automatiquement par le build.
return array(
    'dependencies' => array(
        'wp-blocks',
        'wp-element',
        'wp-components',
        'wp-block-editor',
        'wp-i18n',
    ),
    'version' => 'b7c1b2d9f1a2',
);

شرح الكود

لماذا يُعد ملف block.json نقلة نوعية؟

block.json يُركّز هذا النظام البيانات الوصفية (الاسم، الأيقونة، السمات، الوسائط) وتعريفات الأصول (editorScript/style/render). يستطيع ووردبريس قراءة هذا الملف وإنشاء سجل الكتلة. والنتيجة: تقليل أكواد PHP المُعلقة، وبنية تفهمها أدواتك (البناء، التدقيق، التكامل المستمر).

المرجع الرسمي موجود هنا: بيانات تعريف الكتلة.

لماذا تُعتبر الدالة register_block_type($dir) نقطة الدخول المناسبة؟

register_block_type() يقبل مسار مجلد. يبحث ووردبريس هناك block.jsonويستنتج الكتلة. وبذلك تتجنب مصفوفات PHP الضخمة التي تكرر المعلومات.

دكتور: register_block_type().

لماذا نقوم بتسجيل المقابض قبل register_block_type()؟

في الخاص بك block.json, editorScript et style الإشارة إلى المقابض (على سبيل المثال، bpcab-note-box-editorلا يتعرف ووردبريس على عنوان URL الخاص بهم. لذلك، من الضروري... wp_register_script() / wp_register_style() قبل حفظ الكتلة، وإلا ستكون لديك أصول مفقودة (أو كتلة يتم تحريرها بدون واجهة مستخدم).

المستندات: wp_register_script() et wp_register_style().

لماذا يكون العرض من جانب الخادم (render.php) أكثر موثوقية في كثير من الأحيان

عندما يكون العنصر ديناميكيًا، لا يتم "حفظ" كود HTML الخاص بالواجهة الأمامية في المحتوى، بل يُعاد حسابه. وهذا مفيد للأسباب التالية:

  • صحيح خطأ في لغة HTML/CSS بدون "نقل" جميع المنشورات،
  • أضف فئة، وغلافًا، وبيانات منظمة،
  • إدارة السياقات (الفئات، المؤلفون، ACF، الخيارات) على جانب PHP.

في المقابل، يجب عليك الانتباه إلى ذاكرة التخزين المؤقت (ذاكرة التخزين المؤقت للصفحة، وذاكرة التخزين المؤقت للأجزاء) واستقرار الترميز (لتجنب مفاجآت تحسين محركات البحث).

التعقيم والهروب (كلاهما، وليس أحدهما دون الآخر)

  • التعقيم عند المدخل (هنا، السمات): sanitize_text_field(), sanitize_key().
  • التحقق المجال (هنا، $allowed_tones): أنت ترفض أي قيمة غير متوقعة.
  • الهروب الناتج (HTML): esc_html()و wp_kses_post() إذا سمحت بمجموعة فرعية من لغة HTML.

كثيراً ما رأيت كتل "داخلية" تُحقن بقيم غريبة عبر الاستيراد/التصدير أو واجهة برمجة التطبيقات. تعامل مع السمات على أنها غير موثوقة، بشكل منهجي.

المتغيرات وحالات الاستخدام

الخيار 1 - كتلة ثابتة (حفظ كامل) للمواقع التي لا تستخدم العرض من جانب الخادم

إذا كنت تريد كتلة تحفظ كود HTML النهائي في المنشور (مما يقلل من استخدام PHP ويسهل الاستضافة)، فقم بإزالتها. "render" في block.json واصنع save() وهذا يُكمِل البنية. ملاحظة: إذا قمت بتغيير الترميز لاحقًا، فستحتاج إلى إدارته. deprecated على جانب جافا سكريبت حتى لا تتعطل المحتويات القديمة.

الخيار الثاني - إضافة نمط "للمحرر فقط"

في الكتل ذات واجهة المستخدم الكثيفة، غالبًا ما أفصل CSS الخاص بالمحرر (لتحسين سهولة الاستخدام) عن CSS الخاص بالواجهة الأمامية (لتحسين الأداء). أضف إلى block.json مفتاح editorStyle (المقبض) واحفظه باسم style.

{
  "editorStyle": "bpcab-note-box-editor-style"
}

الخيار 3 - ذاكرة التخزين المؤقت للعرض (ذاكرة التخزين المؤقت للأجزاء) للكتل الديناميكية المكلفة

إذا كانت عملية عرض المحتوى لديك تتطلب طلبًا خارجيًا أو طلبًا مكثفًا من WP_Query، فقم بتطبيق ذاكرة تخزين مؤقتة لكل منشور وسماته. ومن الحالات النادرة: إبطال صلاحية ذاكرة التخزين المؤقتة عند تحديث المنشور.

<?php
// Exemple de pattern (à adapter) : cache par post ID + hash attributs.
$key = 'bpcab_nb_' . (int) ($block->context['postId'] ?? 0) . '_' . md5(wp_json_encode($attributes));

$cached = get_transient($key);
if (is_string($cached) && $cached !== '') {
    return $cached;
}

$html = '...'; // générez votre HTML

set_transient($key, $html, HOUR_IN_SECONDS);
return $html;

احذر من عمليات الإبطال: إذا قمت بالإخفاء عن طريق البريد، فسيتم إبطاله عند save_post وعلى تعديل الخيارات العالمية.

التوافق مع Divi 5 / Elementor / Avada

تتعايش كتل Gutenberg بشكل عام بشكل جيد مع Divi 5 و Elementor و Avada، ولكن هناك بعض التفاصيل العملية.

ديفي 5

  • إذا كانت صفحاتك مبنية باستخدام Divi، فسيكون استخدام هذا العنصر مفيدًا للغاية في محرر ووردبريس الأصلي (المنشورات، أنواع المنشورات المخصصة). حافظ على تنسيق CSS الخاص بهذا العنصر بشكل واضح (مثل: .bpcab-note-box) لتجنب أنماط Divi العامة.
  • إذا كان Divi يستخدم أنماطًا طباعية عالمية جريئة، فاستخدم الدعامات. typography ويتيح للمستخدم تعديله في المحرر.

Elementor

  • يُمكن لـ Elementor تغليف المحتوى داخل عناصر حاوية. تجنّب استخدام مُحدِّدات CSS التي تعتمد بشكل كبير على بنية DOM الأصلية.
  • إذا قمت بإدراج الكتلة في منطقة "الرمز المختصر" أو "HTML" في Elementor عبر عرض المحتوى، فاختبر العرض (alignwide/fullتقوم بعض قوالب Elementor بتقييد العرض عبر max-width.

أفادا (منشئ الاندماج)

  • غالبًا ما يمتلك Avada نظامًا هامًا لتخزين ملفات CSS والأصول مؤقتًا. بعد إضافة/تعديل أي عنصر، امسح ذاكرة التخزين المؤقت لـ Avada وذاكرة التخزين المؤقت للمتصفح، وإلا ستظن أن... style.css لا يتم التحميل.
  • إذا قام برنامج Avada بتصغير/دمج الملفات، فتحقق من ثبات مؤشرات الملفات (ومن هنا تأتي أهمية التحكم في الإصدارات). filemtime أو بناء التجزئة).

فحوصات ما بعد التثبيت

  1. كتلة مرئية في المحرر، ابحث عن "إدراج ملاحظة (BPCAB)". إذا لم يظهر، فهناك مشكلة في التسجيل (التهيئة، المسارات، ملف block.json غير صالح).
  2. الأصول المحملة افتح مُفتش الشبكة في المُحرر، وتحقق من ذلك. build/note-box/index.js et style.css تم تحميلها.
  3. واجهة مُصممة انشر منشورًا، ثم تحقق من كود HTML المُنشأ. يجب أن ترى class="bpcab-note-box ...".
  4. الدعم اختبر نقطة الارتكاز والألوان والحشو/الهوامش. تأكد من ذلك. get_block_wrapper_attributes() يعكس ذلك خياراتك.
  5. مخبأ إذا كان لديك مكون إضافي للتخزين المؤقت، فقم بإجراء عملية مسح كاملة بعد إضافة المكون الإضافي وبعد عملية البناء.

إذا لم ينجح الأمر

الإجراء الذي أطبقه عندما يفشل تحميل كتلة (والذي يتجنب الخروج في جميع الاتجاهات).

1) تحقق من وجود أخطاء في لغة PHP (السجلات)

  • مكن WP_DEBUG et WP_DEBUG_LOG على خشبة المسرح.
  • بحث wp-content/debug.log.
  • الأخطاء الشائعة: PHP < 8.1، مسار ملف غير صحيح، require ملف مفقود.

2) تحقق من تشغيل init

إذا وضعت الكود في المكان الخطأ (مقتطف لا يتم تحميله على init(أو إضافة MU في غير موضعها)، register_block_type() لا ينعطف أبدًا. أضف مؤقتًا error_log() في register_blocks() للتأكد.

3) تحقق من ملف block.json (ملف JSON غير صالح)

إضافة فاصلة زائدة تجعل ووردبريس يتجاهل الكتلة. تحقق من صحة ملف JSON. في بعض الفرق، رأيت ملفات محفوظة بترميز UTF-8 مع علامة ترتيب البايتات (BOM) تُسبب مشاكل غير متوقعة.

4) تحقق من مقابض الأصول

Si block.json يحتوي على "editorScript": "bpcab-note-box-editor"ثم يحتاج برنامج PHP الخاص بك إلى التسجيل بالضبط هذا هو المقبض. حرف مختلف، ولن يحتوي المحرر على واجهة المستخدم الخاصة بك.

5) امسح ذاكرة التخزين المؤقت (فعلاً)

  • إضافة التخزين المؤقت (صفحة/كائن)،
  • ذاكرة التخزين المؤقت للخادم (Nginx fastcgi، varnish)،
  • شبكة توصيل المحتوى (Cloudflare)،
  • ذاكرة التخزين المؤقت للمتصفح (إعادة تحميل كاملة).

مخطط تشخيصي (سريع)

عرض السبب المحتمل التحقق الحلول
لا يظهر هذا الجزء في القائمة المُدرجة. block.json غير مقروءة / خطأ في JSON / لم يتم تنفيذ الخطاف سجلات PHP + فحص build/*/block.json JSON صحيح، تحقق منه add_action('init', ...)المسارات
يظهر المربع، لكن واجهة المستخدم معطلة. لم يتم تحميل نص المحرر (handle/dependencies) وحدة تحكم المتصفح (المحرر) + علامة تبويب الشبكة تحقق من المقبض، ثم أنشئ index.asset.phpمسح ذاكرة التخزين المؤقت
لا يتم تطبيق CSS الخاص بالواجهة الأمامية style غير مسجل أو ذاكرة تخزين مؤقتة لبرنامج Avada/Autoptimize انظر إلى مصدر HTML + link rel="stylesheet" متحقق wp_register_style، التطهير/التصغير
خطأ "استدعاء دالة غير معرّفة register_block_type" تم تنفيذ الكود مبكراً جداً / بيئة معطوبة تتبع المكدس استمر initتحقق من تحميل ملفات ووردبريس الأساسية
الواجهة الأمامية فارغة render.php لا تُرجع دالة رد نداء صالحة تفعيل وضع التصحيح + اختبار أ return '<div>ok</div>'; لتصحيح return function (...) { ... }تحقق من الأذونات/الأخطاء

الأخطاء الشائعة والمزالق

خطأ سبب الحلول
تم نسخ الكود إلى الملف الخاطئ (ملف القالب بدلاً من ملف الإضافة). تغيير المظهر = اختفاء الكتلة ضع التسجيل في إضافة، واحتفظ بالثيم لأغراض التصميم.
خطأ في التحليل: خطأ في بناء الجملة، غير متوقع... قوس مفقود/فاصلة منقوطة في مقتطف قم بتشغيل الملحق في بيئة اختبار/بيئة تطوير متكاملة، وقم بتمكين التسجيل في بيئة الاختبار.
استعمال wp_enqueue_script بدلا من wp_register_script يتم تحميل البرنامج النصي في كل مكان و/أو مبكراً جداً قم بتعريف المعرّف، ودع ووردبريس يقوم بتحميله عند الحاجة عبر ملف block.json.
خطاف غير لائق (plugins_loaded بدلا من init) وحدات واجهة برمجة التطبيقات غير جاهزة / ترتيب التحميل وفر على init (الأولوية الافتراضية)، أو اضبطها إذا لزم الأمر
تعارض في ذاكرة التخزين المؤقت (تم تقديم نسخة قديمة من جافا سكريبت) نسخة الأصول المجمدة استعمال .asset.php (هاش) أو filemtimeتنظيف شبكة توصيل المحتوى (CDN)
index.asset.php مفقود لم يتم تنفيذ عملية البناء، النشر غير مكتمل أضف خطوة CI/CD، أو خطة احتياطية (لكن وثّقها).
الروابط الدائمة لا يتم إعادة إنشائها (حالة غير مباشرة) بعد عملية الترحيل، نقاط نهاية غير متناسقة/إعادة كتابة أعد حفظ الروابط الدائمة (الإعدادات > الروابط الدائمة > حفظ)
الخلط بين المخزونات والمرشحات إن إرجاع قيمة في إجراء ما أمر لا طائل منه. استعمال add_action للتنفيذ، add_filter لتحويل
لم يتم تحميل ملفات CSS/JS بسبب عنوان URL غير صحيح plugins_url مثبت على الملف الخاطئ Définir BPCAB_BLOCKS_FILE وأشر إليه في كل مكان
خطأ متعلق بإصدار PHP قديم صيغة PHP 8.1 (الأنواع، الخصائص للقراءة فقط) قم بترقية لغة PHP، أو قم بإزالة الميزات الحديثة (غير مستحسن)
الكود من برنامج تعليمي قديم (pre-block.json) غير متوافق النهج التقليدي، والتبعيات غير المؤكدة العودة إلى block.json + register_block_type($dir)

نصائح السلامة والأداء والصيانة

  • أمن تعامل مع السمات على أنها غير موثوقة. التنظيف + التحقق + الهروب. لـ HTML غني، wp_kses_post() هذا هو الحد الأدنى، ولكن قلل منه إن أمكن.
  • هاملت تجنب تحميل البرامج النصية العامة. الترابط block.json تسمح المعرفات المسجلة لـ WordPress بالتحميل في الموقع الصحيح.
  • استقرار بالنسبة للكتلة الثابتة، خطط deprecated من جانب جافا سكريبت، إذا قمت بتغيير الترميز، فبالنسبة للكتلة الديناميكية، حافظ على ثبات ترميز الواجهة الأمامية (الفئات، البنية)، وإلا ستحدث مشاكل في CSS.
  • بناء قابل للتكرار السبب الرئيسي لظهور الكتل "المعطلة" في بيئة الإنتاج هو وجود نسخة محلية غير منشورة. أضف خطوة تكامل مستمر (CI) تُنتج build/ والأرشيف.
  • التوافق المستقبلي تجنب استخدام واجهات برمجة تطبيقات جافا سكريبت غير المستقرة. التزم بالحزم الموثقة. اتبع المستودع الرسمي. ووردبريس/جوتنبرج.

لمتابعة التغييرات الأساسية (وفهم سبب تغير السلوك)، أتابع طلبات السحب في Trac و Gutenberg. Trac: core.trac.wordpress.org.

الموارد

الأسئلة الشائعة

هل يجب عليّ استخدام إضافة بدلاً من قالب؟

إذا تم استخدام الكتلة ضمن المحتوى، فالإجابة نعم في معظم الحالات. وإلا، فإنك تربط محتواك بقالب معين. لقد رأيتُ عمليات نقل بيانات فقدت فيها 300 مقالة كتلها بسبب تغيير القالب.

لماذا يظهر المربع الخاص بي، لكن عناصر التحكم (InspectorControls) لا تظهر؟

في أغلب الأحيان: لم يتم تحميل نص المحرر. تحقق من المقبض في block.json وذلك من wp_register_script()ثم تحقق من ذلك index.asset.php موجود ويحتوي على التبعيات الصحيحة.

أستطيع أن أضع file:./index.js مباشرة في ملف block.json؟

يمكنك الرجوع إلى الملفات عبر file: في بعض السياقات، ولكن عمليًا (وفي الإنتاج)، أفضل استخدام مقابض + wp_register_script مع .asset.phpيصبح هذا الأمر أكثر قابلية للتنبؤ مع التخزين المؤقت/شبكات توصيل المحتوى (CDNs) وخطوط أنابيب التكامل المستمر (CI).

كيفية إدارة الترجمات من جانب جافا سكريبت؟

استعمال wp_set_script_translations() على مقبض نص المحرر، و textdomain حافظ على اتساق ملفاتك. .po/.mo في مجلد languagesدكتور: wp_set_script_translations().

الكتلة الديناميكية أم الثابتة: أيهما نختار؟

  • ديناميكية إذا كانت هناك حاجة لتغيير طريقة العرض، أو إذا كانت تعتمد على السياق، أو إذا كنت ترغب في إجراء تصحيحات دون نقل المحتوى.
  • الحالة إذا كنت تريد كود HTML "مُجمد" في المنشور، سهل التصدير، وبدون الاعتماد على PHP.

لماذا استخدام get_block_wrapper_attributes() بدلاً من بناء الفصول الدراسية باليد؟

لأن ووردبريس يضيف تلقائيًا سمات/فئات متعلقة بالعناصر (المحاذاة، التباعد، الألوان، إلخ). إذا قمت بإعادة بناء كل شيء يدويًا، ستفقد بعض الوظائف ويتعطل سلوك القالب.

تم تحميل ملف CSS الخاص بي، لكن القالب (أو Divi/Avada) يستبدله. ماذا أفعل؟

تجنب زيادة التخصيص إلى ما لا نهاية. استخدم مساحات الأسماء لفئاتك (مثل .bpcab-note-boxيُعد استخدام متغيرات CSS أو دعم الألوان/الخطوط أكثر فعالية في كثير من الأحيان. اختبر أيضًا CSS الخاص بالقالب في ظروف استخدام واقعية.

هل يمكنني مسح جميع الكتل في مجلد build/ تلقائيًا كما في المثال؟

نعم، إنه عملي. بالنسبة للإضافات الكبيرة جدًا، أفضل أحيانًا استخدام قائمة صريحة (للتحكم في الترتيب، أو إدارة التبعيات، أو تعطيل جزء معين). المسح جيد طالما أنك تلتزم باتفاقية صارمة.

ماذا لو قام أحد ملحقات تصغير الملفات بدمج الملفات وتسبب في تعطل المحرر؟

عطّل خاصية تصغير الملفات في لوحة التحكم/المحرر، أو استبعد ملفاتك. لاحظتُ مرارًا أن برنامج Autoptimize/LSCache يُعطّل نصوص محرر الكتل إذا كانت إعداداته خاطئة.

كيفية تتبع التغييرات التي تطرأ على واجهة برمجة تطبيقات الكتل بمرور الوقت؟

تابع ملاحظات إصدار ووردبريس على developer.wordpress.org/newsوالمستودع ووردبريس/جوتنبرجبالنسبة للتغييرات الأساسية، يظل نظام Trac المصدر الرئيسي: core.trac.wordpress.org.