الجمعة، 18 مايو 2012

شرح استخدام reCAPTCHA في التطبيقات لمحاربة السخام

من طرف Mohammed Al-kenani  |  نشر في :  11:05 ص 0 تعليقات

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

كيف تساعد رﻱكابتشا على تحويل الكتب القديمة إلى كتب رقمية؟

رﻱكابتشا
كلما تم حل رﻱكابتشا من المستخدم، فإنه يساعد على تحويل الكتب القديمة إلى كتب إلكترونية. وطريقة عملها سهلة. فهي تظهر صورة لكلمتين، الكلمة الأولى هي كلمة يكون قد عجز أو تضارب في تحليلها أكثر من برنامج مختلف لتحويل صور النصوص المكتوبة إلى نص رقمي OCR. والكلمة الثانية هي كلمة تكون معروفة لدى النظام تسمى كلمة التحكم أو Control Word.
عندما يتم تقديم الكلمتين على حسب ترتيب عشوائي، يُسأل المستخدم لحلها. فإذا تم مطابقة كلمة التحكم المعروفة بشكل صحيح، يفترض النظام أن الكلمة الأخرى أيضاً صحيحة ويتم تسجيلها في قاعدة البيانات والاستفادة منها مستقبلاً لتحويل المزيد من الكتب على شكل رقمي.

كيفية استخدام رﻱكابتشا

توفر مكتبة رﻱكابتشا العديد من التطبيقات والإضافات الملحقة لمعظم لغات البرمجة وتطبيقات الويب. وفي هذا المقال، سنرى بعض أمثلة استخداماتها.
قبل الشروع في تطبيق الدروس، عليك أولاً القيام بالتسجيل في الخدمة. وبعد التسجيل يجب اختيار ما إذا كنت ترغب في استخدام هذه الخدمة على نطاق واحد، أو عدة نطاقات إذا كنت مديراً لمواقع كثيرة. سيتم تزويدك بمفتاحين، المفتاح العلني Public Key ويمكن إدراجه علناً على الصفحة لطلب ملفات واجهة برمجة التطبيقات API، والمفتاح الخاص، والذي يجب عليك الاحتفاظ به سراً للتواصل مع الخادم بإحدى لغات البرمجة على الويب.
عند الانتهاء، حمّل ملف المكتبة الجاهز على PHP. وفك ضغطه، سترى ملفاً باسم recaptchalib.php وهو ما سيتم استخدامه وإدراجه في الأمثلة اللاحقة. فضلاً تأكد أن يكون في مكان سهل ليتم إدراجه بدون عناء. شخصياً، أحب إدراجه في مجلد خاص بالمكتبات أسميه includes. ولك الحرية في اختيار الأنسب.

استخدام رﻱكابتشا مع PHP

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

الخطوة 1: إدراج المكتبة والمفاتيح

1.require_once('recaptchalib.php');
2. 
3.//استبدل XXXX مع المفاتيح التي موجودة على http://recaptcha.net/api/getkey
4.$publickey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
5.$privatekey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
نقوم بإدراج المكتبة بشكل عادي بواسطة require_once قبل نموذج الإدخال في HTML، ومن ثم نقوم بتسجيل المفاتيح العلنية والخاصة في متغيرات لسهولة طلبها لاحقاً من قبل البرنامج.

الخطوة 2: تعريب رسائل الخطأ في مصفوفة

01.$error_ar = array(
02.    'invalid-site-public-key' => 'المفتاح العلني للموقع غير صحيح.',
03.    'invalid-site-private-key' => 'المفتاح الخاص للموقع غير صحيح.',
04.    'invalid-request-cookie' => 'عامل التحدي للتحقق غير صحيح.',
05.    'incorrect-captcha-sol' => 'حل كابتشا غير صحيح.',
06.    'verify-params-incorrect' => 'عوامل التحقق كانت غير صحيحة.',
07.    'invalid-referrer' => 'المحيل غير صحيح.',
08.    'recaptcha-not-reachable' => 'خطأ من الخادم.'
09.);
قمت بتعريب بعض رسائل الأخطاء التي تظهر عند عدم التحقق من رﻱكابتشا، ووضعها في مصفوفة لاستدعائها لاحقاً.

الخطوة 3: التحقق من مدخلات رﻱكابتشا ومطابقتها

01.$resp = null;
02. 
03.if ($_POST["submit"])
04.{
05.    if (empty($_POST["name"]) || empty($_POST["recaptcha_response_field"]))
06.    {
07.        echo "بعض الحقول تركت فارغة!";
08.    }
09.    else
10.    {
11.        $resp = recaptcha_check_answer ($privatekey,
12.                        $_SERVER["REMOTE_ADDR"],
13.                        $_POST["recaptcha_challenge_field"],
14.                        $_POST["recaptcha_response_field"]);
15. 
16.        if ($resp->is_valid)
17.        {
18.            echo "مبروك يا " . $_POST["name"] . "، لقد أجبت إجابة صحيحة!";
19.        }
20.        else
21.        {
22.            echo $error_ar[$resp->error];
23.        }
24.    }
25.    echo ' <a href="">إعادة المحاولة</a>.';
26.}
في البداية نتأكد أولاً إذا كان النموذج قد تم إرساله واستقباله في PHP عبر المتغير $_POST["submit"]. حيث هذا المتغير سيكون مسجلاً إذا تم الإرسال عبر الضغظ على زر “موافق” لأنه يحمل الخاصية name تساوي submit.
إذا تم ترك أحد الحقول فارغة، سواء الاسم عبر متغير $_POST["name"] أو حقل جواب رﻱكابتشا $_POST["recaptcha_response_field"]. سيتم إظهار رسالة توضيحية “بعض الحقول تركت فارغة!”. وإذا كانت الحقول معبئة، سيتم متابعة التحقق.
الآن، سيتم إنشاء كائن باسم $resp عبر استخدام دالة الإنشاء recaptcha_check_answer والتي تم إدراجها عبر ملف recaptchalib.php. تهتم هذه الدالة بالاتصال بخادم رﻱكابتشا والتحقق منها. تقبل عدد من المتغيرات. الأول هو المفتاح الخاص، الثاني هو عنوان IP الخاص بالمستخدم، الثالث هو حقل معرفة نوع التحدي لرﻱكابتشا، والأخير هو الجواب لتحدي رﻱكابتشا من قبل المستخدم.
بعدها نقوم بالتحقق من جواب الخادم، فإذا كان جواباً صحيحاً، سيحمل متغير is_valid في كائن $resp قيمة صحيحة true. أما إذا كان غير ذلك، فسيتم إرجاع خطأ للمستخدم وتعريبه حسب نوعه باستخدام مصفوفة التعريب التي سبق إنشاءها.
أخيراً، نقوم بعرض رابط لإعادة المحاولة، وهو عبارة عن رابط فارغ فقط لإعادة تحميل الصفحة.

الخطوة 4: عرض أو عدم عرض النموذج

01.<?php if (!isset($_POST["submit"])): ?>
02. 
03.<form action="" method="post">
04.    <div>
05.        <label for="name">* ما هو اسمك؟</label>
06.        <input type="text" id="name" name="name" size="30" />
07.    </div>
08. 
09.    <div dir="ltr" style="float:right;">
10.        <label for="recaptcha_response_field" dir="rtl">* هل أنت انسان؟</label>
11.        <?php echo recaptcha_get_html($publickey, $error); ?>
12.    </div>
13. 
14.    <div style="clear:right;">* كل الحقول مطلوبة!</div>
15. 
16.    <div><input type="submit" name="submit" class="button" value="موافق" /></div>
17.</form>
18. 
19.<?php endif; ?>
ستلاحظ في البداية السؤال عن متغير $_POST["submit"] عن ما إذا كان يحمل قيمة. فإذا لم يكن يحمل أي قيمة، فمعناه أن المستخدم يعرض النموذج لأول مرة ولم يتم إرساله من قبل. وإذا كان لا يحمل قيمة، فمعناه أن النموذج تم إرساله، لذلك سيتم إخفاءه. ما يساعدنا على معرفة ذلك هو خاصية action في عنصر النموذج form الفارغة. وهي حيلة بسيطة لإعادة تحميل نفس الصفحة عند إرسال النموذج، وبعدها يتم تسجيل المتغيرات المطلوبة والتي تم شرحها في الخطوات السابقة.
يتم إدراج رﻱكابتشا بطريقة ديناميكية عبر دالة المساعدة recaptcha_get_html والتي تعرض الشيفرة المطلوبة لعرض رﻱكابتشا وكافة ملحقاتها.

الخطوة 5: تعريب رﻱكابتشا

تعريب رﻱكابتشا
01.var RecaptchaOptions = {
02.    lang: 'ar',
03.    custom_translations : {
04.                visual_challenge : "احصل على تحدي مرئي",
05.                audio_challenge : "احصل على تحدي مسموع",
06.                refresh_btn : "احصل على تحدي جديد",
07.                instructions_visual : "اكتب الكلمتين:",
08.                instructions_audio : "اكتب ما تسمعه:",
09.                help_btn : "مساعدة",
10.                play_again : "إعادة تشغيل الصوت مرة أخرى",
11.                cant_hear_this : "تحميل الصوت بصيغة MP3",
12.                incorrect_try_again : "خطأ. أعد المحاولة."
13.                }
14.};
سنضيف بعض الشيفرات الخاصة بتعريب محتوى رﻱكابتشا عن طريق الجافاسكربت في أعلى الصفحة.
1..recaptchatable label.recaptcha_input_area_text
2.{
3.    text-align:right !important;
4.    direction:rtl !important;
5.}
ومن ثم نضيف الشيفرات المتعلقة بالمظهر الجمالي بواسطة CSS أيضاً في الأعلى.

استخدام رﻱكابتشا بشكل مخصص

استخدام رﻱكابتشا بشكل مخصص
سنرى الآن كيف يمكننا تخصيص شكل رﻱكابتشا بشكل يتلائم أكثر مع الموقع المراد إدراجه فيه. ولأنها نفس طريقة الاستخدام مع PHP، فلن يتم إعادة شرح عمل الإرسال والتحقق.

الخطوة 1: تعديل خيارات رﻱكابتشا

01.var RecaptchaOptions = {
02.    theme: 'custom',
03.    lang: 'ar',
04.    custom_theme_widget: 'recaptcha_widget',
05.    custom_translations : {
06.                visual_challenge : "احصل على تحدي مرئي",
07.                audio_challenge : "احصل على تحدي مسموع",
08.                refresh_btn : "احصل على تحدي جديد",
09.                instructions_visual : "اكتب الكلمتين:",
10.                instructions_audio : "اكتب ما تسمعه:",
11.                help_btn : "مساعدة",
12.                play_again : "إعادة تشغيل الصوت مرة أخرى",
13.                cant_hear_this : "تحميل الصوت بصيغة MP3",
14.                incorrect_try_again : "خطأ. أعد المحاولة."
15.                }
16.};
لا شيء جديد هنا، سوى خيارين، الخيار الأول theme يجب أن يحتوي على custom ومعناها التخصيص. والخيار الآخر custom_theme_widget يجب أن يحتوي على اسم المعرّف للتقسيمة التي ستحتوي على رﻱكابتشا.

الخطوة 2: عناصر التخصيص لكل جزئية

01.<form action="" method="post">
02.    <div>
03.        <label for="name">* ما هو اسمك؟</label>
04.        <input type="text" id="name" name="name" size="30" />
05.    </div>
06.    <div>
07.        <label for="recaptcha_response_field" dir="rtl">* هل أنت انسان؟</label>
08.        <div id="recaptcha_widget" style="display:none">
09.            <div id="recaptcha_image" style="float:right;"></div>
10.            <div style="clear:right;">
11.                <div class="recaptcha_only_if_incorrect_sol" style="color:red">خطأ الرجاء المحاولة مرة أخرى</div>
12.                <label for="recaptcha_response_field" class="recaptcha_only_if_image">أدخل الكلمات الموجودة في المربع:</label>
13.                <label for="recaptcha_response_field" class="recaptcha_only_if_audio">أدخل الأرقام التي تسمعها:</label>
14.                <input type="text" id="recaptcha_response_field" name="recaptcha_response_field" dir="ltr" />
15.                <div><a href="javascript:Recaptcha.reload()">احصل على كابتشا آخر</a></div>
16.                <div class="recaptcha_only_if_image"><a href="javascript:Recaptcha.switch_type('audio')">احصل على كابتشا مسموعة</a></div>
17.                <div class="recaptcha_only_if_audio"><a href="javascript:Recaptcha.switch_type('image')">احصل على كابتشا مرئية</a></div>
18.                <div><a href="javascript:Recaptcha.showhelp()">مساعدة</a></div>
19.            </div>
20.            <!-- استبدل XXXX مع المفاتيح التي موجودة على http://recaptcha.net/api/getkey -->
21.            <script type="text/javascript" src="http://api.recaptcha.net/challenge?k=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"></script>
22.        </div>
23.        <noscript>
24.            <!-- استبدل XXXX مع المفاتيح التي موجودة على http://recaptcha.net/api/getkey -->
25.            <iframe src="http://api.recaptcha.net/noscript?k=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" height="300" width="500" frameborder="0"></iframe><br />
26.            <textarea name="recaptcha_challenge_field" rows="3" cols="40" dir="ltr"></textarea>
27.            <input type='hidden' name='recaptcha_response_field' value='manual_challenge' />
28.        </noscript>
29.    <div style="clear:right;">* كل الحقول مطلوبة!</div>
30.    <div><input type="submit" name="submit" class="button" value="موافق" /></div>
31.</form>
ستلاحظ إدراج العديد من التقاسيم والعناصر، وذلك لتخصيص الشكل تماماً بما يناسب الموقع الذي تريد إدراج رﻱكابتشا فيه.
التقسيمة التي ستحتوي على رﻱكابتشا والتي لها معرّف id باسم recaptcha_widget ستكون مخفية مبدأياً. وسيتم إظهارها باستخدام الجافاسكربت.
لن أتطرق إلى الشرح التفصيلي لكل عنصر منها، ولكن مجملاً، بعض هذه العناصر تظهر وتختفي بحسب طريقة رﻱكابتشا المستخدمة. فمثلاً عند التحويل إلى رﻱكابتشا مسموعة، فستختفي كلمة “احصل على كابتشا مسموعة” ويتم استبدالها بكلمة “احصل على كابتشا مرئية”.
عنصرا noscript وiframe يمكنانا من الرجوع بأمان إلى رﻱكابتشا بطريقة iframe عند عدم تفعيل الجافاسكربت لدى المستخدم.

استخدام رﻱكابتشا مع الأجاكس AJAX

استخدام رﻱكابتشا مع الأجاكس AJAX

الخطوة 1: إنشاء ملف الوكيل لطلب معلومات رﻱكابتشا من الخادم

01.<?php
02.require_once('recaptchalib.php');
03.//استبدل XXXX مع المفاتيح التي موجودة على http://recaptcha.net/api/getkey
04.$privatekey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
05. 
06.//ُError Codes translated to arabic
07.$error_ar = array(
08.    'invalid-site-public-key' => 'المفتاح العلني للموقع غير صحيح.',
09.    'invalid-site-private-key' => 'المفتاح الخاص للموقع غير صحيح.',
10.    'invalid-request-cookie' => 'عامل التحدي للتحقق غير صحيح.',
11.    'incorrect-captcha-sol' => 'حل كابتشا غير صحيح.',
12.    'verify-params-incorrect' => 'عوامل التحقق كانت غير صحيحة.',
13.    'invalid-referrer' => 'المحيل غير صحيح.',
14.    'recaptcha-not-reachable' => 'خطأ من الخادم.'
15.);
16. 
17.# the response from reCAPTCHA
18.$resp = null;
19. 
20.# was there a reCAPTCHA response?
21.if (empty($_POST["recaptcha_response_field"]))
22.{
23.    echo "حقل الكابتشا مطلوب!";
24.}
25.else
26.{
27.    $resp = recaptcha_check_answer ($privatekey,
28.                    $_SERVER["REMOTE_ADDR"],
29.                    $_POST["recaptcha_challenge_field"],
30.                    $_POST["recaptcha_response_field"]);
31. 
32.    if ($resp->is_valid)
33.    {
34.        echo "مبروك، لقد أجبت إجابة صحيحة!";
35.    }
36.    else
37.    {
38.        echo $error_ar[$resp->error];
39.    }
40.}
41.echo ' <a onclick="showRecaptcha(\'dynamic_recaptcha\', \'submit\', \'red\');" href="javascript:return false;">إعادة المحاولة</a>.';
42.?>
سيتم إنشاء ملف مساعد للأجاكس نسميه ajax.recaptcha.php يهتم بطلب المعلومة من خادم رﻱكابتشا وتقديم المعلومة. والسبب في إنشاء ملف كهذا يعمل بمثابة الوكيل Proxy بين الأجاكس في موقعنا وخوادم رﻱكابتشا، هو أنه لأسباب أمنية في المتصفحات، يتم منع طلب المعلومات مباشرة بطريقة الأجاكس بين نطاقات مختلفة Cross domain.
ستلاحظ أنه لا اختلاف جذري بين هذا الملف، وملف رﻱكابتشا باستخدام PHP. سوى أنه تم الاستغناء عن عناصر HTML، وذلك لأننا نريد فقط أن نأخذ معلومة بسيطة.

الخطوة 2: إنشاء دوال المساعدة في طلب واستقبال الأجاكس

01.function createRequestObject()
02.{
03.    var req;
04.    if(window.XMLHttpRequest)
05.    {
06.        req = new XMLHttpRequest();
07.    }
08.    else if(window.ActiveXObject)
09.    {
10.        req = new ActiveXObject("Microsoft.XMLHTTP");
11.    }
12.    else
13.    {
14.        alert('حصل خطأ في طلب الصفحة...الرجاء المحاولة مرة أخرى وإذا لم تنفع جرب أن تنزل نسخة جديدة من المتصفح');
15.    }
16.    return req;
17.}
18. 
19.var http = createRequestObject();
20. 
21.function sendRequestPost(recaptcha_challenge_field, recaptcha_response_field)
22.{
23.    http.open('post', 'ajax.recaptcha.php');
24.    http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
25.    http.onreadystatechange = handleResponse;
26.    http.send('recaptcha_challenge_field='+recaptcha_challenge_field+'&recaptcha_response_field='+recaptcha_response_field);
27.}
28. 
29.function handleResponse()
30.{
31.    document.getElementById("ajaxResp").style.display = 'block';
32.    document.getElementById("dynamic_recaptcha").style.display = 'none';
33.    document.getElementById("submitDiv").style.display = 'none';
34.    var ajaxTest = document.getElementById("ajaxResp");
35.    ajaxTest.innerHTML = 'جاري التحقق...';
36.    if(http.readyState == 4 && http.status == 200)
37.    {
38.        var response = http.responseText;
39.        if(response)
40.        {
41.            ajaxTest.innerHTML = response;
42.        }
43.    }
44.}
45. 
46.function getAnswer()
47.{
48.    sendRequestPost(Recaptcha.get_challenge(), Recaptcha.get_response());
49.}

الخطوة 3: إعادة إنشاء رﻱكابتشا بتقنية الأجاكس

view sourceprint?
01.function showRecaptcha(element)
02.{
03.    //استبدل XXXX مع المفاتيح التي موجودة على http://recaptcha.net/api/getkey
04.    Recaptcha.create("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", element, {
05.        theme: 'blackglass',
06.        lang: 'ar',
07.        custom_translations : {
08.                    visual_challenge : "احصل على تحدي مرئي",
09.                    audio_challenge : "احصل على تحدي مسموع",
10.                    refresh_btn : "احصل على تحدي جديد",
11.                    instructions_visual : "اكتب الكلمتين:",
12.                    instructions_audio : "اكتب ما تسمعه:",
13.                    help_btn : "مساعدة",
14.                    play_again : "إعادة تشغيل الصوت مرة أخرى",
15.                    cant_hear_this : "تحميل الصوت بصيغة MP3",
16.                    incorrect_try_again : "خطأ. أعد المحاولة."
17.                    },
18.        callback: Recaptcha.focus_response_field
19.    });
20.    document.getElementById("dynamic_recaptcha").style.display = 'block';
21.    document.getElementById("ajaxResp").style.display = 'none';
22.    document.getElementById("submitDiv").style.display = 'block';
23.}
يتم إنشاء رﻱكابتشا بالأجاكس عبر استدعاء كائن Recaptcha.create. حيث يستقبل الكائن عدد من المتغيرات، أولاً المفتاح العلني، ثانياً اسم المعرّف Id للعنصر المراد إدراج رﻱكابتشا فيه، وأخيراً الخيارات.
الخيارات تكون مشابهة تماماً للخيارات التي سبق شرحها باستخدام PHP. ولكن هناك خيار جديد وهو callback. ويتيح هذا الخيار استدعاء دالة بعد الإنتهاء من تحميل رﻱكابتشا.
في نهاية الدالة نقوم بإخفاء بعض العناصر وإظهار البعض الآخر لتحسين قابلية الاستخدام.

الخطوة 4: عرض النموذج في HTML


1.<form action="" method="post" onsubmit="getAnswer();return false;">
2.    <p><a onclick="showRecaptcha('dynamic_recaptcha');" href="javascript:return false;">أظهر الكابتشا!</a></p>
3.    <div dir="ltr" style="float:right;" id="dynamic_recaptcha"></div>
4.    <div style="clear:right;" id="ajaxResp"></div>
5.    <div style="clear:right;" id="submitDiv"><input type="submit" name="submit" id="submit" class="button" value="موافق" /></div>
6.</form>
في عنصر النموذج form، تم استخدام حدث عند الإرسال onsubmit نستدعي دالة getAnswer() والتي تهتم بطلب المعلومة من الخادم وعرضها. من المهم إلحاق استدعاء الدالة بإرجاع خطأ return false، والسبب هو أننا لا نريد أن يتم إرسال النموذج بطريقة HTML، إنما بطريقة الأجاكس. وإرجاع الخطأ يوقف الإرسال عن طريق HTML.
في عنصر الرابط، سنرى إدراج الحدث عند النقر onclick، نستدعي الدالة showRecaptcha وذلك لإظهار رﻱكابتشا في تقسيمة div التي لها معرّف dynamic_recaptcha.
المصدر 

التسميات :
Mohammed Al-kenani

كاتب مختص في مدونة مفكر التقنية

اشتراك

الحصول على كل المشاركات لدينا مباشرة في صندوق البريد الإلكتروني

شارك الموضوع

مواضيع ذات صلة

0 التعليقات:

back to top