إذا سبق لك أن حاولت "الصعود إلى الطائرة" WordPress في WordPressوأنك انتهيت بصفحة فارغة، أو حلقات لا نهائية، أو خطأ فادح: لا يمكن إعادة التصريحلقد تطرقت إلى مشكلة كلاسيكية. غالبًا ما نرغب في عرض موقع مصغر في صفحة، أو تنفيذ استعلام ووردبريس "كما لو كنا في مكان آخر"، أو عزل عملية عرض لمنشئ المواقع - وينتهي بنا الأمر بتحميل النظام الأساسي مرتين.
المشكلة / الحاجة
يشمل مصطلح "ووردبريس داخل ووردبريس" العديد من الاحتياجات الحقيقية:
- عرض محتوى من موقع ووردبريس آخر (موقع متعدد المواقع أو موقع بعيد) في صفحة، أو وحدة Divi/Elementor، أو منطقة أدوات.
- قم بتشغيل ملف ووردبريس معزول (قالب، حلقة، كتلة) دون الإخلال بالاستعلام الرئيسي أو المتغيرات العامة.
- قم بإنشاء "رؤية" داخلية (HTML لصفحة أو مقال أو نوع منشور مخصص) لملف PDF أو بريد إلكتروني أو ذاكرة تخزين مؤقتة للخادم أو واجهة برمجة تطبيقات.
الفخ: الكثير من المطورين حاول أن تصنع require 'wp-load.php' أو لمحاكاة عملية تهيئة ثانية لموقع ووردبريس. في ووردبريس 6.9.4 (أبريل 2026) مع PHP 8.1+، غالبًا ما ينتهي الأمر بما يلي:
- الدوال المعاد تعريفها (الأساسية أو الإضافية)،
- تم تفعيل الخطافات مرتين،
- استهلاك الذاكرة المتزايد بشكل كبير
- الآثار الجانبية (ذاكرة التخزين المؤقت، ملفات تعريف الارتباط، متغيرات الاستعلام، عمليات إعادة التوجيه).
في النهاية، ستعرف كيفية تطبيق حل نظيف وفقًا لثلاثة سيناريوهات: عرض داخلي معزول, عرض موقع آخر ضمن نفس الموقع المتعددو عرض لموقع بعيد — دون إعادة تحميل ووردبريس بلا داعٍ.
ملخص سريع
- نتجنب إعادة تحميل ووردبريس (لا
require wp-load.php(في إضافة/قالب). - نحن نخلق رمز قصير + واحد مسار الراحة اختياري لإنتاج عرض "مغلف".
- نعزل الحلقة باستخدام
WP_Queryونستعيد الإعدادات العامة عبرwp_reset_postdata(). - بالنسبة للمواقع المتعددة، ننتقل مؤقتًا من المدونة إلى
switch_to_blog()ثمrestore_current_blog(). - بالنسبة للمواقع البعيدة، نمر عبر
wp_remote_get()(أو الأفضل: مسار REST الذي يعرضه الموقع البعيد) ونقوم بالتخزين المؤقت باستخدام البيانات العابرة.
متى يستخدم هذا الحل
- تحتاج إلى عرض "مقالة مصغرة" (عنوان + مقتطف + صورة) في أداة إنشاء الصفحات دون تكرار المحتوى.
- لديك موقع متعدد المواقع وتريد عرض أحدث المقالات من الموقع أ في الموقع ب.
- تقوم بإنشاء رسائل بريد إلكتروني للمعاملات من ووردبريس وتريد إعادة استخدام قالب موجود.
- تريد توفير تضمين داخلي مستقر (HTML) لتطبيق الواجهة الأمامية، دون الكشف عن واجهة برمجة التطبيقات بالكامل.
لقد رأيت هذه الحاجة كثيراً في مواقع Avada/Divi حيث يريد العميل "قسم مدونة" في صفحة الهبوط، ولكن دون أن يقوم منشئ الموقع بمعالجة الطلبات بشكل صحيح أو دون مضاعفة الوحدات.
متى لا يجب استخدام هذا الحل
- إذا كنت ترغب فقط في عرض المقالات: فغالبًا ما يكون استخدام كتلة حلقة الاستعلام (محرر الموقع) أو أداة أصلية من أداة الإنشاء كافيًا.
- إذا كان هدفك هو مصادقة SSO أو "بوابة": انظر بدلاً من ذلك إلى OAuth/JWT وبنية بدون واجهة رسومية.
- إذا كنت ترغب في "دمج تثبيت ووردبريس كامل" في صفحة واحدة (لوحة تحكم ووردبريس، القوالب، إلخ): فهذا غير عملي. استخدم نطاقًا فرعيًا أو خادم وكيل عكسي (مثل Nginx) وتقبّل القيود.
- إذا كنت تفكر "سأقوم بتضمين wp-load.php للوصول إلى وظائف WP من خلال برنامج نصي": بدلاً من ذلك، استخدم نقطة نهاية REST آمنة أو WP-CLI.
المتطلبات الأساسية / قبل البدء
- وورد 6.9.4 (أو أحدث)، PHP 8.1 +.
- بيئة اختبار: محلية (WP-ENV، Local، DDEV) أو بيئة تجريبية.
- قم بعمل نسخة احتياطية (للملفات وقاعدة البيانات) قبل النشر.
- إذا كنت تستخدم إضافةً لقصاصات التعليمات البرمجية: انتبه إلى أن قصاصةً معطوبةً قد تُعطّل لوحة التحكم. حافظ على إمكانية الوصول عبر بروتوكول نقل الملفات (FTP) أو بروتوكول SSH.
مصادر رسمية مفيدة:
- WP_Query (مرجع)
- دليل واجهة برمجة تطبيقات REST
- switch_to_blog()
- wp_remote_get()
- JSON في PHP (php.net)
الأمن: بمجرد أن يتم عرض عملية العرض عبر REST، يجب مراعاة الأذونات والتخزين المؤقت وسطح الهجوم (الاستخراج والتضخيم وتسريب المحتوى الخاص).
النهج الساذج (ولماذا يجب تجنبه)
الكود الذي ما زلت أراه في عام 2026، والذي تم نسخه من دروس تعليمية قديمة، يبدو كالتالي:
<?php
// ❌ À NE PAS FAIRE : recharger WordPress depuis WordPress.
require_once __DIR__ . '/wp-load.php';
$post_id = (int) $_GET['post_id'];
$post = get_post( $post_id );
echo apply_filters( 'the_content', $post->post_content );
?>
PROBLEMES أسمنت :
- التمهيد المزدوج إذا تم تنفيذ هذا الكود من داخل بيئة ووردبريس موجودة (إضافة، قالب، رمز مختصر)، فسيتم إعادة تحميل كل من النظام الأساسي والإضافات. والنتيجة: تعارضات، وتكرار في استدعاءات الخطافات، واستهلاك مفرط للذاكرة.
- أمن :
$_GETدون إشعار أو إذن. لقد كشفت للتو عن محتوى قد يكون خاصاً. - هاملت : لا يوجد تخزين مؤقت. في أداة إنشاء صفحات ذات زيارات عالية، تقوم بإنشاء هجوم DDoS "داخلي".
- قابلية الصيانة يعتمد ذلك على المسارات النسبية. عند إجراء عملية ترحيل أو إضافة مكون إضافي، يتعطل كل شيء.
النهج الصحيح - دليل خطوة بخطوة
الهدف التقني
سنقوم بإنشاء إضافة صغيرة توفر ما يلي:
- un رمز قصير
[wp_in_wp]لعرض صورة مغلفة، - ل مسار الراحة
/wp-json/wp-in-wp/v1/renderلاسترجاع هذا العرض عبر AJAX (مفيد للمطورين)، - دعم متعددة المواقع (تبديل المدونة)
- دعم موقع بعيد (عبر REST عن بعد + ذاكرة تخزين مؤقتة).
الخطوة 1 - إنشاء الملحق
إنشاء ملف:
wp-content/plugins/wp-in-wp-render/wp-in-wp-render.php
قم بتفعيله في ملحقاتالعمل على بيئة الاختبار: قد يتسبب المكون الإضافي الذي يحلل الرموز المختصرة ويعرض مسار REST في تعطل الصفحة إذا قمت بـ خطأ التركيب النحوي (عادةً ما يكون هناك فاصلة منقوطة مفقودة).
الخطوة 2 - تحديد عملية عرض داخلية "معزولة"
الفكرة: توليد كود HTML دقيق من post_id وخيارات أخرى (عرض الصورة، والمقتطف، وما إلى ذلك)، دون التأثير على الاستعلام الرئيسي. نستخدم WP_Query (أو get_post) ونهرب من كل ما ينبغي الهروب منه.
الخطوة 3 - إضافة وضع المواقع المتعددة
إذا كنت تستخدم إعدادًا متعدد المواقع، فإننا نقبل معلمة blog_idنحن بصدد التحول مؤقتًا إلى switch_to_blog()نعيدها، ثم نصلحها. إنها موثوقة، لكن يجب أن تكون منضبطاً. toujours دعوة restore_current_blog()، حتى في حالة الخطأ.
الخطوة 4 - إضافة وضع التحكم عن بعد
بالنسبة للموقع البعيد، لا تقوم بتحميل تثبيت ووردبريس البعيد. بل تستدعي نقطة نهاية بعيدة (ويُفضل أن تكون مسار REST تتحكم به على الموقع البعيد) وتخزن الاستجابة مؤقتًا. إذا لم يكن لديك تحكم في الموقع البعيد، فيمكنك استهلاكها. /wp-json/wp/v2/postsلكنك ستكون مقيدًا بالمصادقة والحقول والهيكل.
الخطوة 5 - عرض مسار REST (اختياري ولكنه مفيد)
قد يقوم منشئو المواقع بتحميل أقسام عبر تقنية AJAX، أو قد ترغب في تجنب إعادة عرض صفحة كبيرة جدًا من جانب PHP. كما يتيح لك مسار REST استهلاك المخرجات المعروضة من برنامج نصي خارجي (مع التحقق من الهوية).
الرمز الكامل
انسخ والصق هذا الملف بالكامل في wp-content/plugins/wp-in-wp-render/wp-in-wp-render.phpثم قم بتفعيل الإضافة.
<?php
/**
* Plugin Name: WP in WP Render (pratique)
* Description: Rend du contenu WordPress "dans WordPress" sans recharger le core : shortcode + REST + multisite + distant.
* Version: 1.0.0
* Requires at least: 6.9
* Requires PHP: 8.1
* Author: Votre Nom
* License: GPL-2.0-or-later
*/
defined( 'ABSPATH' ) || exit;
final class BPCAB_WP_In_WP_Render {
const REST_NAMESPACE = 'wp-in-wp/v1';
const TRANSIENT_PREFIX = 'bpcab_wpinwp_';
public static function init(): void {
add_shortcode( 'wp_in_wp', array( __CLASS__, 'shortcode' ) );
add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
// Optionnel : un petit style minimal pour que le rendu soit présentable partout.
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'register_assets' ) );
}
public static function register_assets(): void {
$handle = 'bpcab-wp-in-wp';
wp_register_style(
$handle,
plugins_url( 'assets/wp-in-wp.css', __FILE__ ),
array(),
'1.0.0'
);
}
/**
* Shortcode : [wp_in_wp post_id="123" mode="local|multisite|remote" blog_id="2" remote_url="https://exemple.com" template="card"]
*/
public static function shortcode( array $atts = array() ): string {
$atts = shortcode_atts(
array(
'mode' => 'local', // local | multisite | remote
'post_id' => 0,
'blog_id' => 0, // multisite uniquement
'remote_url' => '', // remote uniquement (base URL du site)
'template' => 'card', // card | excerpt | title
'thumb' => '1', // 1/0
'excerpt' => '1', // 1/0
'cache_ttl' => 300, // secondes
'class' => '',
),
$atts,
'wp_in_wp'
);
$mode = sanitize_key( (string) $atts['mode'] );
$post_id = absint( $atts['post_id'] );
$blog_id = absint( $atts['blog_id'] );
$template = sanitize_key( (string) $atts['template'] );
$thumb = ( (string) $atts['thumb'] === '1' );
$excerpt = ( (string) $atts['excerpt'] === '1' );
$cache_ttl = max( 0, absint( $atts['cache_ttl'] ) );
$class = sanitize_html_class( (string) $atts['class'] );
if ( $post_id <= 0 ) {
return self::wrap_error( 'post_id manquant ou invalide.' );
}
$args = array(
'template' => $template,
'thumb' => $thumb,
'excerpt' => $excerpt,
'class' => $class,
);
try {
if ( $mode === 'multisite' ) {
if ( ! is_multisite() ) {
return self::wrap_error( 'Mode multisite demandé, mais WordPress n’est pas en multisite.' );
}
if ( $blog_id <= 0 ) {
return self::wrap_error( 'blog_id manquant pour le mode multisite.' );
}
return self::render_multisite( $blog_id, $post_id, $args, $cache_ttl );
}
if ( $mode === 'remote' ) {
$remote_url = esc_url_raw( (string) $atts['remote_url'] );
if ( empty( $remote_url ) ) {
return self::wrap_error( 'remote_url manquant pour le mode remote.' );
}
return self::render_remote( $remote_url, $post_id, $args, $cache_ttl );
}
// Par défaut : local.
return self::render_local( $post_id, $args );
} catch ( Throwable $e ) {
// On évite de casser la page en front. En debug, loggez l’exception.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[WP in WP Render] ' . $e->getMessage() );
}
return self::wrap_error( 'Erreur de rendu. Consultez les logs si WP_DEBUG est activé.' );
}
}
public static function register_routes(): void {
register_rest_route(
self::REST_NAMESPACE,
'/render',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( __CLASS__, 'rest_render' ),
'permission_callback' => array( __CLASS__, 'rest_permissions' ),
'args' => array(
'mode' => array(
'type' => 'string',
'required' => false,
'default' => 'local',
'sanitize_callback' => 'sanitize_key',
),
'post_id' => array(
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
'blog_id' => array(
'type' => 'integer',
'required' => false,
'sanitize_callback' => 'absint',
),
'remote_url' => array(
'type' => 'string',
'required' => false,
'sanitize_callback' => 'esc_url_raw',
),
'template' => array(
'type' => 'string',
'required' => false,
'default' => 'card',
'sanitize_callback' => 'sanitize_key',
),
'thumb' => array(
'type' => 'boolean',
'required' => false,
'default' => true,
'sanitize_callback' => array( __CLASS__, 'sanitize_bool' ),
),
'excerpt' => array(
'type' => 'boolean',
'required' => false,
'default' => true,
'sanitize_callback' => array( __CLASS__, 'sanitize_bool' ),
),
'cache_ttl' => array(
'type' => 'integer',
'required' => false,
'default' => 300,
'sanitize_callback' => 'absint',
),
),
),
)
);
}
public static function sanitize_bool( mixed $value ): bool {
// REST peut envoyer "true"/"false", 1/0, etc.
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
}
public static function rest_permissions( WP_REST_Request $request ): bool {
/**
* Politique par défaut :
* - autoriser en public si le post est publié et public
* - sinon exiger la capacité de lecture du post (edit_post est trop strict pour certains rôles)
*
* Vous pouvez durcir : exiger un nonce (wp_rest) côté front ou un token applicatif.
*/
$post_id = absint( $request->get_param( 'post_id' ) );
if ( $post_id <= 0 ) {
return false;
}
$post = get_post( $post_id );
if ( ! $post ) {
return false;
}
if ( $post->post_status === 'publish' && $post->post_password === '' ) {
return true;
}
// Si non publié/protégé, on vérifie les droits.
return current_user_can( 'read_post', $post_id );
}
public static function rest_render( WP_REST_Request $request ): WP_REST_Response {
$mode = sanitize_key( (string) $request->get_param( 'mode' ) );
$post_id = absint( $request->get_param( 'post_id' ) );
$blog_id = absint( $request->get_param( 'blog_id' ) );
$remote_url = (string) $request->get_param( 'remote_url' );
$args = array(
'template' => sanitize_key( (string) $request->get_param( 'template' ) ),
'thumb' => (bool) $request->get_param( 'thumb' ),
'excerpt' => (bool) $request->get_param( 'excerpt' ),
'class' => '',
);
$cache_ttl = max( 0, absint( $request->get_param( 'cache_ttl' ) ) );
if ( $post_id <= 0 ) {
return new WP_REST_Response(
array(
'ok' => false,
'error' => 'post_id invalide.',
),
400
);
}
if ( $mode === 'multisite' ) {
if ( ! is_multisite() ) {
return new WP_REST_Response(
array( 'ok' => false, 'error' => 'Multisite non activé.' ),
400
);
}
if ( $blog_id <= 0 ) {
return new WP_REST_Response(
array( 'ok' => false, 'error' => 'blog_id manquant.' ),
400
);
}
$html = self::render_multisite( $blog_id, $post_id, $args, $cache_ttl );
return new WP_REST_Response( array( 'ok' => true, 'html' => $html ), 200 );
}
if ( $mode === 'remote' ) {
$remote_url = esc_url_raw( $remote_url );
if ( empty( $remote_url ) ) {
return new WP_REST_Response(
array( 'ok' => false, 'error' => 'remote_url manquant.' ),
400
);
}
$html = self::render_remote( $remote_url, $post_id, $args, $cache_ttl );
return new WP_REST_Response( array( 'ok' => true, 'html' => $html ), 200 );
}
$html = self::render_local( $post_id, $args );
return new WP_REST_Response( array( 'ok' => true, 'html' => $html ), 200 );
}
private static function render_local( int $post_id, array $args ): string {
$post = get_post( $post_id );
if ( ! $post ) {
return self::wrap_error( 'Article introuvable.' );
}
// Respecte les règles de visibilité.
if ( $post->post_status !== 'publish' ) {
if ( ! current_user_can( 'read_post', $post_id ) ) {
return self::wrap_error( 'Contenu non accessible.' );
}
}
return self::render_post_html( $post_id, $args );
}
private static function render_multisite( int $blog_id, int $post_id, array $args, int $cache_ttl ): string {
$cache_key = self::TRANSIENT_PREFIX . 'ms_' . $blog_id . '_' . $post_id . '_' . md5( wp_json_encode( $args ) );
if ( $cache_ttl > 0 ) {
$cached = get_transient( $cache_key );
if ( is_string( $cached ) && $cached !== '' ) {
return $cached;
}
}
switch_to_blog( $blog_id );
try {
$html = self::render_local( $post_id, $args );
} finally {
restore_current_blog();
}
if ( $cache_ttl > 0 ) {
set_transient( $cache_key, $html, $cache_ttl );
}
return $html;
}
private static function render_remote( string $remote_url, int $post_id, array $args, int $cache_ttl ): string {
/**
* Stratégie recommandée :
* - le site distant expose un endpoint "render" contrôlé (même plugin)
* - on récupère directement du HTML déjà “propre”
*
* Fallback :
* - si l’endpoint n’existe pas, on tente wp/v2/posts/{id} et on rend localement (limité).
*/
$remote_url = untrailingslashit( $remote_url );
$cache_key = self::TRANSIENT_PREFIX . 'remote_' . md5( $remote_url . '|' . $post_id . '|' . wp_json_encode( $args ) );
if ( $cache_ttl > 0 ) {
$cached = get_transient( $cache_key );
if ( is_string( $cached ) && $cached !== '' ) {
return $cached;
}
}
// 1) Endpoint dédié (si vous installez le plugin aussi sur le site distant).
$endpoint = $remote_url . '/wp-json/' . self::REST_NAMESPACE . '/render?mode=local&post_id=' . $post_id
. '&template=' . rawurlencode( (string) $args['template'] )
. '&thumb=' . ( $args['thumb'] ? '1' : '0' )
. '&excerpt=' . ( $args['excerpt'] ? '1' : '0' );
$response = wp_remote_get(
$endpoint,
array(
'timeout' => 8,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( ! is_wp_error( $response ) ) {
$code = (int) wp_remote_retrieve_response_code( $response );
$body = (string) wp_remote_retrieve_body( $response );
if ( $code >= 200 && $code < 300 && $body !== '' ) {
$data = json_decode( $body, true );
if ( is_array( $data ) && ! empty( $data['ok'] ) && isset( $data['html'] ) && is_string( $data['html'] ) ) {
$html = $data['html'];
if ( $cache_ttl > 0 ) {
set_transient( $cache_key, $html, $cache_ttl );
}
return $html;
}
}
}
// 2) Fallback : WP REST posts. On rend une “carte” minimaliste.
$fallback = $remote_url . '/wp-json/wp/v2/posts/' . $post_id . '?_fields=id,link,title,excerpt,featured_media';
$response = wp_remote_get(
$fallback,
array(
'timeout' => 8,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( is_wp_error( $response ) ) {
return self::wrap_error( 'Site distant injoignable : ' . esc_html( $response->get_error_message() ) );
}
$code = (int) wp_remote_retrieve_response_code( $response );
$body = (string) wp_remote_retrieve_body( $response );
if ( $code < 200 || $code >= 300 || $body === '' ) {
return self::wrap_error( 'Réponse distante invalide (HTTP ' . esc_html( (string) $code ) . ').' );
}
$data = json_decode( $body, true );
if ( ! is_array( $data ) || empty( $data['id'] ) ) {
return self::wrap_error( 'JSON distant invalide.' );
}
$title = '';
if ( isset( $data['title']['rendered'] ) ) {
// Le contenu "rendered" peut contenir du HTML.
$title = wp_strip_all_tags( (string) $data['title']['rendered'] );
}
$excerpt = '';
if ( isset( $data['excerpt']['rendered'] ) ) {
$excerpt = wp_strip_all_tags( (string) $data['excerpt']['rendered'] );
}
$link = isset( $data['link'] ) ? esc_url( (string) $data['link'] ) : '';
$html = self::render_card_from_remote_fields( $title, $excerpt, $link, $args );
if ( $cache_ttl > 0 ) {
set_transient( $cache_key, $html, $cache_ttl );
}
return $html;
}
private static function render_card_from_remote_fields( string $title, string $excerpt, string $link, array $args ): string {
$template = (string) ( $args['template'] ?? 'card' );
$class = (string) ( $args['class'] ?? '' );
$classes = trim( 'bpcab-wp-in-wp bpcab-wp-in-wp--remote bpcab-wp-in-wp--' . $template . ' ' . $class );
ob_start();
?>
<div class="<?php echo esc_attr( $classes ); ?>">
<div class="bpcab-wp-in-wp__body">
<p class="bpcab-wp-in-wp__title">
<a href="<?php echo esc_url( $link ); ?>" target="_blank" rel="noopener">
<?php echo esc_html( $title ); ?>
</a>
</p>
<?php if ( ! empty( $args['excerpt'] ) && $excerpt !== '' ) : ?>
<p class="bpcab-wp-in-wp__excerpt"><?php echo esc_html( $excerpt ); ?></p>
<?php endif; ?>
</div>
</div>
<?php
return (string) ob_get_clean();
}
private static function render_post_html( int $post_id, array $args ): string {
$template = (string) ( $args['template'] ?? 'card' );
$thumb = ! empty( $args['thumb'] );
$excerpt = ! empty( $args['excerpt'] );
$class = (string) ( $args['class'] ?? '' );
$classes = trim( 'bpcab-wp-in-wp bpcab-wp-in-wp--local bpcab-wp-in-wp--' . $template . ' ' . $class );
// On charge la feuille de style si elle existe.
if ( wp_style_is( 'bpcab-wp-in-wp', 'registered' ) ) {
wp_enqueue_style( 'bpcab-wp-in-wp' );
}
$title = get_the_title( $post_id );
$link = get_permalink( $post_id );
$img_html = '';
if ( $thumb && has_post_thumbnail( $post_id ) ) {
// wp_get_attachment_image() gère déjà l’escaping des attributs.
$img_html = wp_get_attachment_image( get_post_thumbnail_id( $post_id ), 'medium', false, array(
'class' => 'bpcab-wp-in-wp__thumb',
'loading' => 'lazy',
) );
}
$excerpt_text = '';
if ( $excerpt ) {
$excerpt_text = get_the_excerpt( $post_id );
}
ob_start();
?>
<div class="<?php echo esc_attr( $classes ); ?>">
<?php if ( $img_html ) : ?>
<div class="bpcab-wp-in-wp__media">
<a href="<?php echo esc_url( $link ); ?>"><?php echo $img_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
</div>
<?php endif; ?>
<div class="bpcab-wp-in-wp__body">
<?php if ( $template === 'title' ) : ?>
<p class="bpcab-wp-in-wp__title">
<a href="<?php echo esc_url( $link ); ?>"><?php echo esc_html( $title ); ?></a>
</p>
<?php else : ?>
<p class="bpcab-wp-in-wp__title">
<a href="<?php echo esc_url( $link ); ?>"><?php echo esc_html( $title ); ?></a>
</p>
<?php if ( $excerpt_text !== '' && $template !== 'title' ) : ?>
<p class="bpcab-wp-in-wp__excerpt"><?php echo esc_html( $excerpt_text ); ?></p>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php
return (string) ob_get_clean();
}
private static function wrap_error( string $message ): string {
return '<div class="bpcab-wp-in-wp bpcab-wp-in-wp--error"><strong>WP in WP:</strong> ' . esc_html( $message ) . '</div>';
}
}
BPCAB_WP_In_WP_Render::init();
اختياري: أضف ملف CSS (وإلا سيتم عرض المحتوى بشكل غير مُحسّن). إنشاء:
wp-content/plugins/wp-in-wp-render/assets/wp-in-wp.css
.bpcab-wp-in-wp{border:1px solid rgba(0,0,0,.08);padding:14px;border-radius:10px;background:#fff}
.bpcab-wp-in-wp__title{margin:0 0 8px 0;font-weight:600}
.bpcab-wp-in-wp__excerpt{margin:0;color:rgba(0,0,0,.75)}
.bpcab-wp-in-wp__media{margin:0 0 10px 0}
.bpcab-wp-in-wp__thumb{display:block;max-width:100%;height:auto;border-radius:8px}
.bpcab-wp-in-wp--error{background:#fff6f6;border-color:#f2b8b8}
شرح الكود
1) لماذا رمز مختصر؟
لأنه تنسيق عالمي. فبرامج Divi وElementor وAvada جميعها تدعم إدراج الكود المختصر، ويظل متوافقًا مع محرر الكتل. عمليًا، يُعد هذا التنسيق الأمثل لدمج عناصر العرض في بيئات متنوعة.
2) لماذا مسار REST إضافي؟
عندما يقوم مُنشئ الصفحات بتحميل قسم ديناميكيًا، أو عندما تريد تحديث عرض الصفحة دون إعادة تحميلها، فإن استخدام REST يكون أنظف من... admin-ajax.php مُجمّعة بشكل غير متقن. أما بالنسبة لـ WordPress 6.9 والإصدارات الأحدث، فإن واجهة برمجة تطبيقات REST تُعتبر معيارًا مستقرًا.
نسجل المسار باستخدام register_rest_route() ونحدد ما يلي:
- وسائط مع
sanitize_callback(يتجنب المداخل المتسخة) - رد الاتصال الخاص بالصلاحيات لتجنب الكشف عن محتوى غير منشور،
- استجابة JSON بسيطة:
{ ok: true, html: "..." }.
إشارة: register_rest_route().
3) العزلة: لا يوجد "ووردبريس ثانٍ"
جوهر "WordPress داخل WordPress" هو قبول قيد واحد: أنت بالفعل في WordPress، لذلك تستخدم واجهات برمجة التطبيقات الموجودة (المنشورات، والروابط الدائمة، والصور المصغرة) بدلاً من إعادة التحميل. wp-load.phpهذا ما يمنع 80% من الأخطاء البرمجية.
4) المواقع المتعددة: switch_to_blog() with finally
عبر مواقع متعددة، switch_to_blog() يقوم بتعديل السياق العام (بادئة قاعدة البيانات، الخيارات، عناوين URL، إلخ). finally يضمن ذلك restore_current_blog() سيتم استدعاء هذه الدالة حتى في حالة حدوث استثناء. وبدونها، ستظهر أخطاء "وهمية": روابط تشير إلى الموقع الخطأ، وخيارات غير متناسقة، وذاكرة تخزين مؤقتة للكائنات ملوثة.
إشارة: استعادة_المدونة_الحالية().
5) عن بُعد: wp_remote_get() + ذاكرة تخزين مؤقتة
بالنسبة للموقع البعيد، نقوم بإرسال طلب HTTP. ونقوم بالتخزين المؤقت عبر set_transient() لتجنب الاعتماد على الشبكة في كل عملية عرض. هذه قاعدة أساسية للبقاء إذا كنت تعرض هذا المحتوى على الصفحة الرئيسية.
إشارة: set_transient ().
6) التعقيم / الهروب
- الإدخالات:
absint(),sanitize_key(),esc_url_raw(),sanitize_html_class(). - المخارج:
esc_html(),esc_attr(),esc_url().
عند استخدام بيانات JSON من خادم خارجي، يُنصح بالحذر: حتى لو أعاد واجهة برمجة تطبيقات ووردبريس (WP API) كود HTML "مُعالَجًا"، فلا تُضِف هذا الكود مباشرةً إلى صفحتك. هنا أقوم بـ wp_strip_all_tags() للحصول على نتيجة بسيطة وآمنة.
المتغيرات وحالات الاستخدام
الخيار 1 - عرض قائمة (أحدث المقالات) بدلاً من عرض معرف منشور واحد
إذا كان المطلوب هو "آخر 3 مقالات"، فلا تستخدم ثلاثة أكواد مختصرة (فهذا يزيد من وقت العرض والتخزين المؤقت). أضف كودًا مختصرًا ثانيًا مخصصًا، أو قم بتوسيع هذا الكود باستخدام count + post_type و واحد WP_Query.
مثال (إضافة سريعة): إنشاء رمز مختصر جديد [wp_in_wp_list] من يصنع WP_Query والمكالمات render_post_html() لكل هوية. انتبه إلى wp_reset_postdata() كما ترى the_post().
الخيار الثاني - يتم عرضه كجزء من "قالب" بدلاً من خريطة HTML
إذا كان قالب موقعك يحتوي بالفعل على جزء مخصص (على سبيل المثال، template-parts/card.phpيمكنك استبدال render_post_html() من قبل:
- un
set_query_var()+get_template_part(), - أو
locate_template()+load_template().
أقوم بذلك غالبًا في المواقع المصممة بشكل احترافي: حيث تعيد استخدام نفس ترميز القالب، وتتجنب بذلك صيانة خريطتين مختلفتين.
الخيار الثالث - استرجاع العرض باستخدام جافا سكريبت (fetch) في الواجهة الأمامية
إذا كنت ترغب في تحميل العرض لاحقًا (بشكل كسول)، يمكنك استدعاء ما يلي:
/wp-json/wp-in-wp/v1/render?mode=local&post_id=123
بالنسبة للمحتوى غير العام، ستحتاج إلى تمرير قيمة عشوائية (nonce) خاصة بـ REST (wp_rest) وأرسلها في رأس الطلب X-WP-Nonce. مرجع : مصادقة REST.
التوافق مع Divi 5 / Elementor / Avada
ديفي 5
- استخدم وحدة "الرمز" أو "النص" والصق الرمز المختصر.
- إذا كان تطبيق Divi يستخدم التخزين المؤقت بشكل مكثف: قم بمسح ذاكرة التخزين المؤقت لتطبيق Divi (ديفي > خيارات القالب > أداة الإنشاء) وذاكرة التخزين المؤقت للخادم الخاص بك.
على سبيل المثال:
[wp_in_wp mode="local" post_id="123" template="card" thumb="1" excerpt="1"]
Elementor
- أداة "الرمز المختصر": الصق الرمز المختصر.
- إذا كنت تستخدم استدعاءات REST في جافا سكريبت: يمكن لـ Elementor تصغير/تأجيل تحميل النصوص البرمجية. قم بتشغيل اختباراتك بدون تحسين، ثم أعد تفعيله.
أفادا (منشئ الاندماج)
- عنصر "الرمز المختصر" أو "كتلة النص".
- إذا كان لديك ذاكرة تخزين مؤقتة لبرنامج Avada: قم بمسحها بعد تغيير الإعدادات
cache_ttl.
فحوصات ما بعد التثبيت
- اختبر صفحة بسيطة باستخدام رمز مختصر واحد (ليس في أداة إنشاء صفحات ضخمة في البداية).
- تحقق من كود HTML المُنشأ: وجود الحاوية
.bpcab-wp-in-wp. - اختبر منشورًا منشورًا ومنشورًا مسودة:
- عند عدم الاتصال بالإنترنت: يجب أن يعرض المسودة "المحتوى غير متاح".
- بصفتك مسؤولاً: يجب عرض المسودة (إذا كان لديك
read_post).
- المواقع المتعددة: تحقق من أن الروابط التي تم إنشاؤها تشير إلى النطاق/الموقع الصحيح.
- عن بعد: قم بفصل الشبكة مؤقتًا (أو تغيير عنوان URL) للتأكد من أن ذاكرة التخزين المؤقت المؤقتة تمنع حدوث مهلات متكررة.
إذا لم ينجح الأمر
إجراء سريع (بالترتيب)
- قم بتعطيل ذاكرة التخزين المؤقت (مكون إضافي للتخزين المؤقت، ذاكرة التخزين المؤقت للخادم، أداة إنشاء ذاكرة التخزين المؤقت) ثم أعد الاختبار.
- تفعيل وضع تصحيح الأخطاء WP_DEBUG فيما يتعلق بالتجهيز:
define('WP_DEBUG', true);define('WP_DEBUG_LOG', true);define('WP_DEBUG_DISPLAY', false);
- الاختيار
wp-content/debug.log: أخطاء في بناء الجملة، عدم العثور على الفئات، إلخ. - تأكد من تحميل الملحق بشكل صحيح (تمكين الإضافات) وأن الرمز المختصر غير مُصفى بواسطة حقل WYSIWYG.
- إذا كان REST: اختبر المسار في المتصفح وانظر إلى رمز HTTP.
مخطط تشخيصي
| عرض | السبب المحتمل | التحقق | الحلول |
|---|---|---|---|
| يتم عرض الرمز المختصر كنص (وليس تفسيره). | لا يقوم المُنشئ/المنطقة بتنفيذ الرموز المختصرة. | اختبره في كتلة "الرمز المختصر" الأصلية | استخدم عنصر/أداة "الرمز المختصر" المخصصة (Divi/Elementor/Avada) |
| صفحات فارغة بعد التفعيل | خطأ في لغة PHP (فاصلة منقوطة، قوسان) | تحقق من ملف debug.log أو خطأ الخادم. | صحّح الصيغة، وعطّل الإضافة عبر بروتوكول نقل الملفات (FTP) إذا لزم الأمر. |
| "المحتوى غير متاح." في منشور منشور | المنشور محمي بكلمة مرور أو غير منشور | الاختيار post_status وكلمة المرور |
انشر المنشور أو عدّل خاصية رد الاتصال الخاصة بصلاحيات REST |
| في حالة إعداد المواقع المتعددة، تشير الروابط إلى الموقع الخطأ. | restore_current_blog() لم يتم التنفيذ (أو تعارض) |
أضف السجلات، وتحقق من الكود | إحتفظ به try/finally، تجنبهم return قبل الاستعادة |
| عن بُعد: مهلة / عرض بطيء | بطء بروتوكول HTTP عن بُعد + عدم وجود ذاكرة تخزين مؤقتة | قم بقياس TTFB، وانظر إلى التغيرات العابرة. | زيادة cache_ttlقم بإعداد نقطة النهاية المخصصة على الموقع البعيد |
| لا يتم تحميل ملف CSS | ملف مفقود أو مسار غير صحيح | افحص علامة تبويب الشبكة | Créez assets/wp-in-wp.css أو قم بإزالة الذيل |
الأخطاء الشائعة والمزالق
| خطأ | سبب | الحلول |
|---|---|---|
انسخ الكود إلى functions.php من الموضوع الرئيسي |
تحديث القالب = فقدان الكود | استخدم إضافة (مثل هذه) أو قالبًا فرعيًا |
| خطأ فادح: لا يمكن إعادة تعريف... | لقد قمت بتضمين wp-load.php أو تحميل نفس الملف مرتين |
لا تقم بإعادة تحميل ووردبريس من داخل ووردبريس؛ استخدم واجهات برمجة التطبيقات الداخلية. |
| الرمز المختصر يعمل بشكل صحيح في صفحة، لكنه لا يعمل في صفحة أخرى. | تعارض بين مُنشئ ذاكرة التخزين المؤقت/التصغير | اختبرها بدون تحسين، ثم أعد تنشيطها واحدة تلو الأخرى. |
| الخطاف غير مناسب لحفظ مسار الراحة | تم وضع الكود على init بدلا من rest_api_init |
احتفظ add_action('rest_api_init', ...) |
| خطأ "بيانات JSON عن بُعد غير صالحة" | يقوم الموقع البعيد بإرجاع HTML (الصيانة، جدار حماية تطبيقات الويب، إعادة التوجيه) | افحص استجابة HTTP؛ عدّل عنوان URL؛ أضف وكيل المستخدم إذا لزم الأمر |
| يحتوي العرض عن بُعد على HTML تالف/غير متوقع | أنت تعرض rendered بدون تصفية |
قم بإزالة الوسوم أو استخدم نقطة نهاية بعيدة مخصصة تُرجع HTML مُتحكمًا به |
| الاختبار في بيئة الإنتاج بدون نسخ احتياطية | الضغط / العادة | انشر إلى بيئة الاختبار، ثم إلى بيئة الإنتاج مع إمكانية التراجع. |
| خطأ متعلق بإصدار قديم من لغة PHP | خادم يعمل بنظام PHP 7.x | قم بالترقية إلى PHP 8.1+ (مطلوب من قبل الإضافة) أو قم بتعديل الكود (وهو أمر أقل استحسانًا). |
| روابط دائمة غريبة بعد التبديل إلى مواقع متعددة | يقوم الملحق بتعديل المتغيرات العامة ولا يعيدها إلى حالتها الأصلية. | قم بتعطيل الإضافات مؤقتًا، وعزل التعارض، وتسجيل السياق. |
نصائح السلامة والأداء والصيانة
- لا تجعل المحتوى الخاص عاماً : إن
permission_callbackيُعدّ مسار REST بمثابة ضمانة لك. قم بتعزيزه إذا لزم الأمر (بدون REST، مصادقة التطبيق). - أخفِ جهاز التحكم عن بعد بدون بيانات مؤقتة، يمكن لوحدة بسيطة على الصفحة الرئيسية أن تُطلق استدعاءات HTTP في كل زيارة.
- تحديد حقول REST مع
_fieldsفي حالة الطوارئwp/v2: نطاق ترددي أقل، تحليل أقل. - راقب مستوى الصوت إذا كنت تعرض 20 عنصرًا مضمنًا عن بعد، فأنت بحاجة إلى التبديل إلى استراتيجية الدفعات (نقطة نهاية تُرجع قائمة) أو ذاكرة تخزين مؤقتة دائمة (ذاكرة تخزين مؤقتة لكائنات Redis).
- الدورية احتفظ بهذا الملحق في نظام Git وقم بمراقبة إصداراته. لقد رأيت الكثير من الأجزاء البرمجية غير المُتعقبة التي يصبح من المستحيل مراجعتها بعد ستة أشهر.
- تحسين محركات البحث (SEO) إذا قمت بتحميل المحتوى عبر واجهة REST من جانب العميل، فقد لا يقوم جوجل بفهرسة المحتوى بناءً على السياق. لتحسين محركات البحث، يُفضل استخدام العرض من جانب الخادم (رمز مختصر) أو SSR.
لمراقبة تطور النواة (REST، المواقع المتعددة، WP_Query)، راقب ما يلي:
الموارد
- add_shortcode()
- register_rest_route()
- wp_remote_get()
- switch_to_blog()
- بيانات واجهة برمجة التطبيقات العابرة
- wordpress-develop (GitHub)
- filter_var() (php.net)
- مصادقة واجهة برمجة تطبيقات REST
الأسئلة الشائعة
هل تعني عبارة "ووردبريس داخل ووردبريس" تحميل تطبيق ووردبريس كامل في صفحة واحدة؟
في معظم الحالات، لا. الحاجة الحقيقية هي أن جعل تحميل نسخة ثانية من ووردبريس في سياق مختلف هو في الغالب فكرة سيئة.
هل يمكنني استخدام هذا لعرض صفحة كاملة (وليس مجرد خريطة)؟
نعم، ولكن أنصحك بإنشاء قالب مخصص (على سبيل المثال: template="full") وتحكم بدقة فيما تعيده. تجنب الحقن the_content البيانات الأولية من موقع بعيد.
لماذا لا تستخدم إطار iframe؟
يُعد الإطار المضمن (iframe) أحيانًا الحل الأمثل (عزل CSS/JS)، ولكنك تفقد تكامل تحسين محركات البحث (SEO) والأسلوب، وتفرض قيودًا (CSP، والاستجابة، وملفات تعريف الارتباط، والنطاقات المتعددة).
الكود المختصر بطيء على صفحة Divi مزدحمة للغاية. ماذا أفعل؟
ابدأ بتفعيل ذاكرة التخزين المؤقت (ذاكرة تخزين مؤقتة، ثم ذاكرة تخزين مؤقتة دائمة للكائنات). بعد ذلك، قلل عدد مثيلات الكود المختصر، وفضل استخدام صيغة "القائمة" التي تعرض N منشورًا في عملية واحدة.
أريد عرض مقالات من موقع خاص بعيد. كيف يمكنني تأمينه؟
قم بتوفير مسار REST على الموقع البعيد يتطلب مصادقة (كلمة مرور التطبيق، أو رمز مميز، أو OAuth). تجنب جعل نقطة النهاية عامة تعرض المسودات.
لماذا استخدام wp_strip_all_tags() عبر المسافة؟
لأنّ HTML البعيد ليس تحت سيطرتك. حتى مع وجود فلاتر ووردبريس، لا يمكنك معرفة أيّ الإضافات تُدخل أيّ بيانات. إزالة الوسوم تُقلّل من احتمالية الهجوم. إذا كنت ترغب في HTML غني، فقم بعرضه عبر نقطة نهاية بعيدة مُتحكّم بها.
هل هو متوافق مع إضافة التخزين المؤقت مثل WP Rocket / LiteSpeed Cache؟
نعم، ولكن تذكر: إذا كان عرض الصفحة يعتمد على المستخدم (المسودات مرئية للمسؤولين)، فقد تعرض الصفحة المخزنة مؤقتًا نسخة "المسؤولين" للزائر. في هذه الحالة، عطّل التخزين المؤقت لتلك الصفحات أو فرض عرضها للجمهور فقط.
ماذا أفعل إذا رأيت رسالة "استجابة عن بعد غير صالحة (HTTP 403)"؟
غالباً ما يكون الخطأ 403 ناتجاً عن جدار حماية تطبيقات الويب (WAF)، أو إجراء حماية ضد البرامج الآلية، أو قيد على واجهة برمجة تطبيقات REST. اختبر عنوان URL في متصفح، ثم من الخادم (باستخدام curl). قد تحتاج إلى السماح بعنوان IP الخاص بالخادم أو إضافة مصادقة.
هل يمكنني وضع هذا الكود في إضافة mu؟
نعم، بل إنه خيار جيد إذا كنت ترغب في منع العميل من تعطيل الإضافة. ضعها في wp-content/mu-plugins/ وقم بتعديل مسارات الأصول (أو قم بإزالة المكون الإضافي CSS).
أتلقى رسالة "استدعاء دالة غير معرّفة register_rest_route()"
ربما قمت بتشغيل هذا الكود مبكرًا جدًا (خارج بيئة ووردبريس) أو في سكربت منفصل. يفترض هذا الملحق أنه يعمل داخل ووردبريس. إذا كنت بحاجة إلى استدعاء ووردبريس من خارج بيئة ووردبريس، فاستخدم واجهة برمجة تطبيقات REST الخاصة بالموقع.