() translation by (you can also view the original English article)
جعلت WordPress من المناسب لنشر المكونات الإضافية للجمهور. ضع المكوّن الإضافي في مستودع المكونات الإضافية لـ WordPress ويمكن العثور عليه بسهولة من قبل أشخاص من داخل لوحة القيادة.
ربما يكون أحد أفضل ميزات المستودع هو أنه يسهل تحديث المكونات الإضافية من داخل لوحة القيادة. يتم عرض إعلامات التحديث داخل لوحة القيادة ، كما أن إجراء التحديث أمر سهل مثل النقر فوق الزر "تحديث الآن".
ولكن ماذا لو أردت استضافة البرنامج المساعد بنفسك؟ لن تكون ميزة التحديث المريحة متاحة إذا قمت باستضافة المكون الإضافي الخاص بك خارج ملحق البرنامج المساعد WordPress. ولن يتم إخطار المستخدمين بالتحديث المتاح.
ستعلمك هذه المقالة أنه مع القليل من الترميز الإبداعي ، يمكنك استضافة ملحقات WordPress الخاصة بك في جيثب مع الاستمرار في الاحتفاظ بميزة التحديث التلقائي.
لماذا استضافة البرنامج المساعد الخاص بك في جيثب؟
قد يكون هناك عدد من الأسباب وراء رغبتك في استضافة المكون الإضافي الخاص بك خارج مستودع المكونات الإضافية لـ WordPress. أحد القيود الرئيسية التي يفرضها المستودع هي أنك مطالب بترخيص البرنامج المساعد الخاص بك كـ GPLv2. لن أتطرق إلى تفاصيل الترخيص ، ولكن باختصار يعني ذلك أنه يمكن لأي شخص مشاركة عملك. لذلك إذا كنت ترغب في بيع المكوّن الإضافي الخاص بك ، فاستضافته في مستودع GitHub الخاص هو الآن خيار يمكنك التفكير فيه.
ومع ذلك ، نظرًا للطريقة التي تم بها تصميم WordPress ، فإن استضافة المكون الإضافي في GitHub سيعطل التحديثات التلقائية للمكون الإضافي الخاص بنا. لفهم سبب حدوث ذلك ، علينا أن نفهم كيف يتعامل WordPress مع تحديثات المكونات الإضافية.
كيفية تحديث ملحقات ووردبريس
أولاً ، دعنا نتحقق من كيفية قيام WordPress بإجراء تحديثات المكونات الإضافية من أجل فهم أفضل لسبب عدم تمكن الإضافات المستضافة ذاتياً من الحصول على تحديثات تلقائية
يقوم WordPress بالتحقق بشكل دوري من مستودع WordPress لمعرفة التحديثات التي قمت بتركيبها. يتلقى مجموعة من المعلومات حول كل مكون إضافي مثل أحدث إصدار وعنوان URL الخاص بحزمة المكون الإضافي. هذا ما نراه عادةً في صفحة مسؤول المكون الإضافي عندما يتم إخطارنا بالتحديث:



عندما نقر على رابط عرض تفاصيل الإصدار x.x ، يقوم WordPress بإجراء بحث آخر في البرنامج المساعد repo. هذه المرة تحصل على معلومات أكثر تفصيلاً حول المكون الإضافي مثل الوصف ، سجل التغيير ، إصدار WordPress الذي تم اختباره فيه ، والكثير غير ذلك. يتم عرض هذه المعلومات لنا في صندوق الضوء:



أخيرًا ، عند النقر فوق ارتباط التحديث الآن ، يتم تنزيل وتثبيت حزمة البرنامج المساعد المحدثة.
إذن لماذا لا تعمل التحديثات التلقائية للإضافات المستضافة ذاتيًا؟ ذلك لأن WordPress يحاول العثور عليه في مستودع البرنامج المساعد WordPress ولا يمكن العثور عليه هناك!
خطة اللعب
تتمثل خطتنا في تمكين التحديثات التلقائية من خلال المكون الإضافي المستضاف من GitHub.
فيما يلي قائمة بما يتعين علينا القيام به لإنجاحه:
- نحن بحاجة إلى وسيلة لنشر تحديثات المكونات الإضافية في جيثب.
- يجب أن نظهر إعلامات تحديث إصدار البرنامج المساعد ،
- سيكون من الجيد عرض تفاصيل المكون الإضافي عند النقر فوق ارتباط عرض تفاصيل الإصدار x.x.
- نريد أيضًا تثبيت تحديث المكون الإضافي بنجاح عند النقر فوق ارتباط التحديث الآن ،
كيفية تنفيذ الخطة
سنستخدم بعض مرشحات WordPress حتى نتمكن من تنفيذ خطة لعبتنا. هؤلاء هم:
-
pre_set_site_transient_update_plugins
. يسمى هذا الفلتر عندما يحاول WordPress البحث عن تحديثات البرنامج المساعد. -
plugins_api.
يتم استخدام هذا التطبيق عندما يعرض WordPress تفاصيل تحديث البرنامج المساعد. -
upgrader_post_install.
أخيرًا ، يسمى هذا بعد تثبيت المكون الإضافي بنجاح.
سنربط هذه الفلاتر ، ثم ندفع بياناتنا إلى النتائج لجعل WordPress يفكر في أن المكون الإضافي لدينا موجود في مستودع مكون WordPress الإضافي. البيانات التي سنضعها ستأتي من تقرير GitHub الخاص بنا ، ويجب أن تحاكي البيانات المقدمة من مستودع البرنامج المساعد.
إعداد مشروع جيثب
قبل المضي قدمًا في عملية الترميز ، دعنا نتحدث أولاً عن GitHub وكيف سنستخدمها لإعطاء البيانات التي نحتاجها لإطعام WordPress.
ستحتاج إلى مستودع GitHub خاص أو عام. يجب أن يحتوي الريبو الخاص بك على جميع ملفات البرنامج المساعد ، وليس نسخة مضغوطة من البرنامج المساعد.
سنستخدم ميزة رائعة لـ GitHub تسمى الإصدارات.



الشيء الجيد في الإصدارات هو أنه يحصل على قاعدة الشفرة الحالية في المستودع ويقوم بإنشاء ملف مضغوط قابل للتنزيل لكل إصدار محدد. يمكننا إخبار WordPress بتنزيل هذا الملف المضغوط عند تحديث المكون الإضافي الخاص بنا.
شيء جيد آخر حول الإصدارات هو أنه يمكننا وضع تفاصيل تحديث المكون الإضافي في ملاحظات الإصدار. يمكننا بعد ذلك تحليل هذا وعرضه في برنامج WordPress الخاص ببرنامج lightbox للحصول على تفاصيل تحديث البرنامج المساعد. يمكننا أن نذهب أبعد من ذلك وحتى نسمح بخفض سعر جيثب في سجل التغيير الخاص بنا.



عندما يحين وقت نشر تحديث على المكوِّن الإضافي لدينا ، اتبع التنسيق في الصورة أعلاه عندما تقوم بإنشاء إصدار جديد:
- اسم العلامة: إصدار البرنامج المساعد (الرقم فقط)
- ملاحظات الإصدار: وصف التحديث
إنشاء فئة التحديث الخاصة بنا
الآن حان الوقت لترميز البرنامج المساعد لدينا!
أولاً ، نخلق نقطة البداية لفصلنا:
1 |
class BFIGitHubPluginUpdater { |
2 |
|
3 |
private $slug; // plugin slug |
4 |
private $pluginData; // plugin data |
5 |
private $username; // GitHub username |
6 |
private $repo; // GitHub repo name |
7 |
private $pluginFile; // __FILE__ of our plugin |
8 |
private $githubAPIResult; // holds data from GitHub |
9 |
private $accessToken; // GitHub private repo token |
10 |
|
11 |
function __construct( $pluginFile, $gitHubUsername, $gitHubProjectName, $accessToken = '' ) { |
12 |
add_filter( "pre_set_site_transient_update_plugins", array( $this, "setTransitent" ) ); |
13 |
add_filter( "plugins_api", array( $this, "setPluginInfo" ), 10, 3 ); |
14 |
add_filter( "upgrader_post_install", array( $this, "postInstall" ), 10, 3 ); |
15 |
|
16 |
$this->pluginFile = $pluginFile; |
17 |
$this->username = $gitHubUsername; |
18 |
$this->repo = $gitHubProjectName; |
19 |
$this->accessToken = $accessToken; |
20 |
}
|
21 |
|
22 |
// Get information regarding our plugin from WordPress
|
23 |
private function initPluginData() { |
24 |
// code here
|
25 |
}
|
26 |
|
27 |
// Get information regarding our plugin from GitHub
|
28 |
private function getRepoReleaseInfo() { |
29 |
// code here
|
30 |
}
|
31 |
|
32 |
// Push in plugin version information to get the update notification
|
33 |
public function setTransitent( $transient ) { |
34 |
// code here
|
35 |
return $transient; |
36 |
}
|
37 |
|
38 |
// Push in plugin version information to display in the details lightbox
|
39 |
public function setPluginInfo( $false, $action, $response ) { |
40 |
// code ehre
|
41 |
return $response; |
42 |
}
|
43 |
|
44 |
// Perform additional actions to successfully install our plugin
|
45 |
public function postInstall( $true, $hook_extra, $result ) { |
46 |
// code here
|
47 |
return $result; |
48 |
}
|
49 |
}
|
هذه هي بنية الفصل التي سنستخدمها. لقد حددنا بشكل أساسي جميع الوظائف التي سنستخدمها وقمنا بإنشاء خطافات الفلتر. لا تعمل هذه الفئة في الوقت الحالي ، باستثناء تخصيص قيم لخصائص الفصل.
وسيطات المنشئ
لتنفيذ فصلنا ، سنحتاج إلى بعض الحجج:
-
$ pluginFile:
سنقوم باستدعاء هذه الفئة من البرنامج النصي الرئيسي للمكون الإضافي ، ويجب أن يكون لهذا القيمة__FILE__.
سنحصل على تفاصيل حول المكون الإضافي الخاص بنا من هذا لاحقًا. -
$ gitHubUsername:
اسم مستخدم GitHub الخاص بك -
$ gitHubProjectName:
اسم مستودع GitHub الخاص بك -
رمز الوصول إلى $:
وميزة رمز الوصول التي تتيح لنا عرض تفاصيل إعادة الشراء الخاصة بـ GitHub. إذا كان مشروعك مستضافًا في ريبو GitHub عام ، فما عليك سوى ترك هذا فارغًا.
الآن دعونا ملء وظائف صفنا مع بعض التعليمات البرمجية.
دالة initPluginData
هذه هي أبسط وظيفة في فصلنا. سنحتاج إلى سبيكة إضافية للمكونات الإضافية وغيرها من المعلومات في بقية البرنامج النصي ، لذلك نضع المكالمات الضرورية في وظيفة من أجل الراحة.
1 |
$this->slug = plugin_basename( $this->pluginFile ); |
2 |
$this->pluginData = get_plugin_data( $this->pluginFile ); |
دالة getRepoReleaseInfo Function
تتعلق هذه الوظيفة بالتواصل مع GitHub للحصول على معلومات الإصدار الخاصة بنا. سنستخدم واجهة برمجة تطبيقات GitHub للحصول على تفاصيل بشأن أحدث إصدار لدينا. ثم قم بتخزين كل ما نحصل عليه في خاصية githubAPIResult
للمعالجة المستقبلية.
يتم استدعاء عامل التصفية pre_set_site_transient_update_plugins
مرتين بواسطة WordPress ، بمجرد التحقق من وجود تحديثات المكونات الإضافية ، ثم مرة أخرى بعد الحصول على النتائج. نظرًا لأننا سنستخدم هذه الوظيفة في هذا الفلتر ، فسنتساءل عن واجهة برمجة تطبيقات GitHub مرتين. نحتاج فقط إلى الحصول على معلومات من GitHub مرة واحدة:
1 |
// Only do this once
|
2 |
if ( ! empty( $this->githubAPIResult ) ) { |
3 |
return; |
4 |
}
|
بعد ذلك ، سوف نستخدم واجهة برمجة تطبيقات GitHub للحصول على معلومات حول إصداراتنا:
1 |
// Query the GitHub API
|
2 |
$url = "https://api.github.com/repos/{$this->username}/{$this->repo}/releases"; |
3 |
|
4 |
// We need the access token for private repos
|
5 |
if ( ! empty( $this->accessToken ) ) { |
6 |
$url = add_query_arg( array( "access_token" => $this->accessToken ), $url ); |
7 |
}
|
8 |
|
9 |
// Get the results
|
10 |
$this->githubAPIResult = wp_remote_retrieve_body( wp_remote_get( $url ) ); |
11 |
if ( ! empty( $this->githubAPIResult ) ) { |
12 |
$this->githubAPIResult = @json_decode( $this->githubAPIResult ); |
13 |
}
|
أخيرًا ، سنحتفظ فقط بالبيانات للحصول على أحدث إصدار من المكون الإضافي:
1 |
// Use only the latest release
|
2 |
if ( is_array( $this->githubAPIResult ) ) { |
3 |
$this->githubAPIResult = $this->githubAPIResult[0]; |
4 |
}
|
الآن يمكننا الحصول على بيانات البرنامج المساعد لدينا من GitHub. سنقوم بتحليل هذه البيانات في الوظائف اللاحقة.
دالة setTransitent Function
تسمى هذه الوظيفة عندما يتحقق WordPress من تحديثات البرنامج المساعد. مهمتنا هنا هي استخدام بيانات إصدار GitHub الخاصة بنا لتوفير معلومات لتحديث المكون الإضافي الخاص بنا.
أول شيء نفعله هو التحقق مما إذا كان WordPress قد بحث بالفعل عن تحديثات المكونات الإضافية من قبل. إذا كان الأمر كذلك ، فلا يتعين علينا تشغيل بقية الوظيفة مرة أخرى.
1 |
// If we have checked the plugin data before, don't re-check
|
2 |
if ( empty( $transient->checked ) ) { |
3 |
return $transient; |
4 |
}
|
بعد ذلك ، سوف نحصل على معلومات البرنامج المساعد التي سنستخدمها:
1 |
// Get plugin & GitHub release information
|
2 |
$this->initPluginData(); |
3 |
$this->getRepoReleaseInfo(); |
بعد استدعاء هاتين الوظيفتين ، يمكننا التحقق من إصدار المكون الإضافي المحلي الخاص بنا من الإصدار (اسم العلامة) الموجود في GitHub. يمكننا استخدام وظيفة version_compare
المناسبة لـ PHP لمقارنة القيمتين:
1 |
// Check the versions if we need to do an update
|
2 |
$doUpdate = version_compare( $this->githubAPIResult->tag_name, $transient->checked[$this->slug] ); |
أخيرًا ، يتوفر تحديث مكون إضافي ، نحتاج إلى مطالبة المسؤول بعرض إشعار تحديث. نقوم بذلك عن طريق ملء المتغير $transient
بمعلومات المكون الإضافي المحدثة.
1 |
// Update the transient to include our updated plugin data
|
2 |
if ( $doUpdate == 1 ) { |
3 |
$package = $this->githubAPIResult->zipball_url; |
4 |
|
5 |
// Include the access token for private GitHub repos
|
6 |
if ( !empty( $this->accessToken ) ) { |
7 |
$package = add_query_arg( array( "access_token" => $this->accessToken ), $package ); |
8 |
}
|
9 |
|
10 |
$obj = new stdClass(); |
11 |
$obj->slug = $this->slug; |
12 |
$obj->new_version = $this->githubAPIResult->tag_name; |
13 |
$obj->url = $this->pluginData["PluginURI"]; |
14 |
$obj->package = $package; |
15 |
$transient->response[$this->slug] = $obj; |
16 |
}
|
17 |
|
18 |
return $transient; |
بعد أن تعالج هذه الوظيفة معلومات GitHub الخاصة بنا ، سيكون مشرفنا قادرًا على عرض الإشعارات في صفحة مسؤول المكون الإضافي:



دالة setPluginInfo Function
الغرض من هذه الوظيفة هو جمع التفاصيل المتعلقة بالبرنامج المساعد المحدث من ملاحظات الإصدار. سيتم عرض جميع هذه المعلومات داخل صندوق الضوء عند النقر فوق ارتباط عرض تفاصيل الإصدار x.x.
أولاً ، دعنا نحصل على معلومات المكون الإضافي لدينا:
1 |
// Get plugin & GitHub release information
|
2 |
$this->initPluginData(); |
3 |
$this->getRepoReleaseInfo(); |
بعد ذلك نتحقق مما إذا كان الوقت قد حان لعرض أي شيء أم لا. يمكننا التحقق مما إذا كنا نحاول تحميل معلومات عن المكون الإضافي الحالي الخاص بنا عن طريق التحقق من slug
:
1 |
// If nothing is found, do nothing
|
2 |
if ( empty( $response->slug ) || $response->slug != $this->slug ) { |
3 |
return false; |
4 |
}
|
لعرض تفاصيل المكون الإضافي لدينا ، نحتاج إلى إضافة معلومات المكون الإضافي يدويًا إلى متغير استجابة $
، وعادةً ما يتم ملء هذا المتغير بالنتائج من مستودع مكون WordPress:
1 |
// Add our plugin information
|
2 |
$response->last_updated = $this->githubAPIResult->published_at; |
3 |
$response->slug = $this->slug; |
4 |
$response->plugin_name = $this->pluginData["Name"]; |
5 |
$response->version = $this->githubAPIResult->tag_name; |
6 |
$response->author = $this->pluginData["AuthorName"]; |
7 |
$response->homepage = $this->pluginData["PluginURI"]; |
8 |
|
9 |
// This is our release download zip file
|
10 |
$downloadLink = $this->githubAPIResult->zipball_url; |
11 |
|
12 |
// Include the access token for private GitHub repos
|
13 |
if ( !empty( $this->accessToken ) ) { |
14 |
$downloadLink = add_query_arg( |
15 |
array( "access_token" => $this->accessToken ), |
16 |
$downloadLink
|
17 |
);
|
18 |
}
|
19 |
$response->download_link = $downloadLink; |
لقد أضفنا حتى الآن تفاصيل المكوّن الإضافي لدينا ، لكننا لم نحلل بعد ملاحظات الإصدار الخاصة بنا من إصدار GitHub. دعونا نتحقق مما لدينا في ملاحظات الإصدار:



في ملاحظات الإصدار ، حددنا ثلاثة تفاصيل تتعلق بإصدارنا: سجل التغيير ، متبوعًا بالحد الأدنى المطلوب من إصدار WordPress ، ثم أحدث إصدار من WordPress تم اختبار المكون الإضافي فيه. سنقوم بتحليل هذا النص واستخراج هذه القيم.
نظرًا لأننا نستضيف المكوِّن الإضافي الخاص بنا في GitHub ، سيكون من الرائع أن نتمكن من استخدام أداة تخفيض السعر في GitHub في ملاحظات الإصدار. سأستخدم فئة PHP تسمى ParseDown لتحويل نص تخفيض السعر إلى HTML:
1 |
// We're going to parse the GitHub markdown release notes, include the parser
|
2 |
require_once( plugin_dir_path( __FILE__ ) . "Parsedown.php" ); |
سنقوم أيضًا بإنشاء علامات تبويب في صندوق الضوء لجعلها متوافقة مع كيفية عرض مستودع WordPress لمستضافات الإضافات معلوماتهم. واحد سيكون لوصف البرنامج المساعد والآخر سيكون في سجل التغيير لدينا:
1 |
// Create tabs in the lightbox
|
2 |
$response->sections = array( |
3 |
'description' => $this->pluginData["Description"], |
4 |
'changelog' => class_exists( "Parsedown" ) |
5 |
? Parsedown::instance()->parse( $this->githubAPIResult->body ) |
6 |
: $this->githubAPIResult->body |
7 |
);
|
أخيرًا ، سنقوم باستخراج قيم المتطلبات المطلوبة والمختبرة:
1 |
// Gets the required version of WP if available
|
2 |
$matches = null; |
3 |
preg_match( "/requires:\s([\d\.]+)/i", $this->githubAPIResult->body, $matches ); |
4 |
if ( ! empty( $matches ) ) { |
5 |
if ( is_array( $matches ) ) { |
6 |
if ( count( $matches ) > 1 ) { |
7 |
$response->requires = $matches[1]; |
8 |
}
|
9 |
}
|
10 |
}
|
11 |
|
12 |
// Gets the tested version of WP if available
|
13 |
$matches = null; |
14 |
preg_match( "/tested:\s([\d\.]+)/i", $this->githubAPIResult->body, $matches ); |
15 |
if ( ! empty( $matches ) ) { |
16 |
if ( is_array( $matches ) ) { |
17 |
if ( count( $matches ) > 1 ) { |
18 |
$response->tested = $matches[1]; |
19 |
}
|
20 |
}
|
21 |
}
|
22 |
|
23 |
return $response; |
دالة postInstall Function
هذه الوظيفة الأخيرة سنتعامل مع تنفيذ عمليات إضافية نحتاجها لتثبيت المكون الإضافي بالكامل بعد تنزيله.
عند إنشاء إصدار لريتو GitHub ، يقوم تلقائيًا بإنشاء ملف مضغوط لهذا الإصدار المحدد. يتم إنشاء اسم الملف لملف zip بواسطة GitHub بالتنسيق reponame-tagname.zip. يحتوي هذا أيضًا على دليل حيث توجد ملفات المكونات الإضافية لدينا. وبالمثل ، فإن اسم الدليل لهذا يتبع أيضًا تنسيق reponame-tagname.
عادةً ، عندما يقوم WordPress بتنزيل أرشيف البرنامج المساعد وفك ضغطه ، لا يتغير اسم دليل البرنامج المساعد. إذا كان دليل المكون الإضافي الخاص بك هو المكون الإضافي my-awesome-plugin ، فبعد حذف ملفات المكون الإضافي القديمة وإلغاء ضغط الملف المحدّث ، سيظل الدليل يدعى my-awesome-plugin. ولكن نظرًا لأن GitHub يغير اسم دليل المكون الإضافي الخاص بنا في كل مرة ننشر فيها إصدارًا ، فلن يتمكن WordPress من العثور على المكون الإضافي الخاص بنا. سيظل بإمكانه تثبيته ، لكنه لن يكون قادرًا على إعادة تنشيطه. يمكننا حل هذه المشكلة عن طريق إعادة تسمية الدليل الجديد لمطابقة الدليل القديم.
اهم الاشياء اولا:
1 |
// Get plugin information
|
2 |
$this->initPluginData(); |
بعد ذلك ، يتعين علينا التحقق مما إذا كان المكوِّن الإضافي قيد التشغيل حاليًا ، حتى نتمكن من إعادة تنشيطه بعد ذلك:
1 |
// Remember if our plugin was previously activated
|
2 |
$wasActivated = is_plugin_active( $this->slug ); |
نحن الآن نعيد تسمية دليل المكوّن الإضافي المحدّث ليطابق الدليل القديم. نحن نستخدم الوظيفة move
هنا ، لكن بما أننا نحدد نفس الدليل ، فسيكون الأمر مثل إعادة تسميته:
1 |
// Since we are hosted in GitHub, our plugin folder would have a dirname of
|
2 |
// reponame-tagname change it to our original one:
|
3 |
global $wp_filesystem; |
4 |
$pluginFolder = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( $this->slug ); |
5 |
$wp_filesystem->move( $result['destination'], $pluginFolder ); |
6 |
$result['destination'] = $pluginFolder; |
ستكون الخطوة الأخيرة لإعادة تنشيط المكون الإضافي:
1 |
// Re-activate plugin if needed
|
2 |
if ( $wasActivated ) { |
3 |
$activate = activate_plugin( $this->slug ); |
4 |
}
|
5 |
|
6 |
return $result; |
الاتصال بفئة محدثة من جيثب
الآن بعد الانتهاء من الفصل ، كل ما تبقى هو استدعاء هذا في ملف المكون الإضافي الرئيسي:
1 |
require_once( 'BFIGitHubPluginUploader.php' ); |
2 |
if ( is_admin() ) { |
3 |
new BFIGitHubPluginUpdater( __FILE__, 'myGitHubUsername', "Repo-Name" ); |
4 |
}
|
قم بتجربته :
هذا هو! فقط قم بتضمين هذه الفئة واتصل بها في البرنامج المساعد ، ويجب أن تبدأ في البحث عن التحديثات تلقائيًا.
لاختبار ما إذا كان يعمل ، قم بإنشاء إصدار جديد لـ GitHub repo واتبع الإرشادات الموضحة مسبقًا:



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