استعلامات الشارة الافتراضية

هذا دليل مرجعي لاستعلامات SQL الخاصة بالشارات الافتراضية، ومعلومات المشغّل الخاصة بها (حيثما يتوفر ذلك).

الشارات الأساسية

:2nd_place_medal: الذكرى السنوية

(تتضمن هذه الشارة بعض السحر الخلفي الإضافي لاختيار التواريخ، لكنني سأدرجها على أي حال)

 start_date = start_date.iso8601(6)
    end_date = end_date.iso8601(6)

      SELECT u.id
        FROM users AS u
        JOIN posts AS p ON p.user_id = u.id
        JOIN topics AS t ON p.topic_id = t.id
       WHERE u.id > 0
         AND u.active
         AND NOT u.staged
         AND (u.silenced_till IS NULL OR u.silenced_till < '#{start_date}')
         AND (u.suspended_till IS NULL OR u.suspended_till < '#{start_date}')
         AND u.created_at <= '#{start_date}'
         AND NOT p.hidden
         AND p.deleted_at IS NULL
         AND p.created_at BETWEEN '#{start_date}' AND '#{end_date}'
         AND t.visible
         AND t.archetype <> 'private_message'
         AND t.deleted_at IS NULL
         AND NOT EXISTS (SELECT 1 FROM user_badges AS ub WHERE ub.user_id = u.id AND ub.badge_id = #{Badge::Anniversary} AND ub.granted_at BETWEEN '#{start_date}' AND '#{end_date}')
         AND NOT EXISTS (SELECT 1 FROM anonymous_users AS au WHERE au.user_id = u.id)
       GROUP BY u.id
      HAVING COUNT(p.id) > 0
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: مُقدَّر (عدد الإعجابات على منشورات متعددة)

تتبع شارات :3rd_place_medal: مُقدَّر، و:2nd_place_medal: محترم، و:1st_place_medal: مُعجب نفس النمط ولكن بقيم مختلفة لـ p.like_count و HAVING COUNT(*).

SELECT p.user_id, CURRENT_TIMESTAMP AS granted_at
      FROM posts AS p
      WHERE p.like_count >= #{like_count}
        AND (:backfill OR p.user_id IN (:user_ids))
      GROUP BY p.user_id
      HAVING COUNT(*) > #{post_count}
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: السيرة الذاتية

SELECT u.id user_id, CURRENT_TIMESTAMP granted_at
    FROM users u
    JOIN user_profiles up on u.id = up.user_id
    WHERE bio_raw IS NOT NULL AND LENGTH(TRIM(bio_raw)) > 10 AND
          uploaded_avatar_id IS NOT NULL AND
          (:backfill OR u.id IN (:user_ids) )
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تعديل مستخدم أو إنشائه

:3rd_place_medal: أساسي (مستويات الثقة)

تتبع شارات :3rd_place_medal: أساسي، و:3rd_place_medal: عضو، و:2nd_place_medal: عادي، و:1st_place_medal: قائد نفس النمط ولكن بقيمة trust_level مختلفة.

SELECT u.id user_id, current_timestamp granted_at FROM users u
      WHERE trust_level >= #{level.to_i} AND (
        :backfill OR u.id IN (:user_ids)
      )
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تغيير مستوى ثقة المستخدم

:3rd_place_medal: معتمد و :2nd_place_medal: مرخص

هذه الشارات لا تحتوي على استعلام SQL. وهي جزء من Discourse Narrative Bot ويتم منحها برمجياً عند إكمال المستخدم للدروس التفاعلية (Discobot).


:3rd_place_medal: محرر

 SELECT p.user_id, min(p.id) post_id, min(p.created_at) granted_at
    FROM badge_posts p
    WHERE p.self_edits > 0 AND
        (:backfill OR p.id IN (:post_ids) )
    GROUP BY p.user_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

:3rd_place_medal: متحمس

تتبع شارات :3rd_place_medal: متحمس، و:2nd_place_medal: خبير، و:1st_place_medal: مخلص نفس النمط ولكن بقيمة عتبة مختلفة لـ HAVING COUNT(*).

WITH consecutive_visits AS (
        SELECT user_id
             , visited_at
             , visited_at - (DENSE_RANK() OVER (PARTITION BY user_id ORDER BY visited_at))::int s
          FROM user_visits
      ), visits AS (
        SELECT user_id
             , MIN(visited_at) "start"
             , DENSE_RANK() OVER (PARTITION BY user_id ORDER BY s) "rank"
          FROM consecutive_visits
      GROUP BY user_id, s
        HAVING COUNT(*) >= #{days}
      )
      SELECT user_id
           , "start" + interval '#{days} days' "granted_at"
        FROM visits
       WHERE "rank" = 1
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: أول إيموجي

لا يوجد استعلام SQL — تُمنح الشارة في اللحظة التي تتم فيها معالجة منشور مؤهل عبر CookedPostProcessor#grant_badges.

المعيار: يجب أن يحتوي المنشور المطبوخ على عنصر img.emoji واحد على الأقل ليس داخل كتلة aside.quote. بعبارة أخرى، تُحتسب الإيموجي التي تُكتب مباشرة في جسم المنشور؛ أما الإيموجي التي تظهر فقط داخل اقتباس فلا تُحتسب.

تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل طباعة المنشور (CookedPostProcessor)

:3rd_place_medal: أول علم

SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id
    FROM (
      SELECT pa.user_id, MIN(pa.id) id
      FROM post_actions pa
      JOIN badge_posts p on p.id = pa.post_id
      WHERE post_action_type_id IN (
        SELECT f.id
        FROM flags f
        WHERE name != 'like'
        AND score_type IS FALSE
        AND require_message IS FALSE
      )
      AND (:backfill OR pa.post_id IN (:post_ids))
      GROUP BY pa.user_id
    ) x
    JOIN post_actions pa1 on pa1.id = x.id
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند قيام مستخدم بإجراء على منشور

:3rd_place_medal: أول إعجاب

SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id
    FROM (
      SELECT pa.user_id, MIN(pa.id) id
      FROM post_actions pa
      JOIN badge_posts p on p.id = pa.post_id
      WHERE post_action_type_id = 2 AND
        (:backfill OR pa.post_id IN (:post_ids) )
      GROUP BY pa.user_id
    ) x
    JOIN post_actions pa1 on pa1.id = x.id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند قيام مستخدم بإجراء على منشور

:3rd_place_medal: أول رابط

SELECT l.user_id, l.post_id, l.created_at granted_at
    FROM
    (
      SELECT MIN(l1.id) id
      FROM topic_links l1
      JOIN badge_posts p1 ON p1.id = l1.post_id
      JOIN badge_posts p2 ON p2.id = l1.link_post_id
      WHERE NOT reflection AND p1.topic_id <> p2.topic_id AND not quote AND
        (:backfill OR ( p1.id in (:post_ids) ))
      GROUP BY l1.user_id
    ) ids
    JOIN topic_links l ON l.id = ids.id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

:3rd_place_medal: أول ذكر

SELECT acting_user_id AS user_id, MIN(target_post_id) AS post_id, MIN(p.created_at) AS granted_at
    FROM user_actions
    JOIN posts p ON p.id = target_post_id
    JOIN topics t ON t.id = topic_id
    JOIN categories c on c.id = category_id
    WHERE action_type = 7
      AND NOT read_restricted
      AND p.deleted_at IS  NULL
      AND t.deleted_at IS  NULL
      AND t.visible
      AND t.archetype <> 'private_message'
      AND (:backfill OR p.id IN (:post_ids))
    GROUP BY acting_user_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

:3rd_place_medal: أول Onebox

لا يوجد استعلام SQL. تُمنح أثناء طباعة المنشور عبر CookedPostProcessor#grant_badges.

المعيار: يجب أن يكون المنشور المطبوخ قد أنتج Onebox واحدًا على الأقل — وهي معاينة رابط غنية يتم إنشاؤها عندما يؤدي رابط URL خام على سطر منفرد إلى مورد قابل للتضمين.

تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل طباعة المنشور (CookedPostProcessor)

:3rd_place_medal: أول اقتباس

SELECT ids.user_id, q.post_id, p3.created_at granted_at
    FROM
    (
      SELECT p1.user_id, MIN(q1.id) id
      FROM quoted_posts q1
      JOIN badge_posts p1 ON p1.id = q1.post_id
      JOIN badge_posts p2 ON p2.id = q1.quoted_post_id
      WHERE (:backfill OR ( p1.id IN (:post_ids) ))
      GROUP BY p1.user_id
    ) ids
    JOIN quoted_posts q ON q.id = ids.id
    JOIN badge_posts p3 ON q.post_id = p3.id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

:3rd_place_medal: أول رد عبر البريد الإلكتروني

لا يوجد استعلام SQL. تُمنح أثناء طباعة المنشور عبر CookedPostProcessor#grant_badges.

المعيار: يجب أن تكون post.is_reply_by_email? صحيحة true — أي أن المنشور تم تقديمه بالرد على رسالة إشعار من Discourse بدلاً من واجهة الويب. يتطلب تفعيل ميزة الرد على البريد الوارد في الموقع.

تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل طباعة المنشور (CookedPostProcessor)

اقتراح المجتمع الأصلي 'Reply by email' badge - #3 by lrossouw

:3rd_place_medal: أول مشاركة

SELECT views.user_id, i2.post_id, i2.created_at granted_at
    FROM
    (
      SELECT i.user_id, MIN(i.id) i_id
      FROM incoming_links i
      JOIN badge_posts p on p.id = i.post_id
      JOIN users u on u.id = i.user_id
      GROUP BY i.user_id
    ) as views
    JOIN incoming_links i2 ON i2.id = views.i_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل تحديث يومي

:3rd_place_medal: مستخدم جديد في الشهر

لا يوجد استعلام SQL مسجل مقابل الشارة. تُمنح بواسطة مهمة مجدولة يوميًا (Jobs::GrantNewUserOfTheMonthBadges). تعمل المهمة كل يوم ولكنها تمنح الجائزة فقط عن الشهر التقويمي السابق، ومرة واحدة فقط في الشهر.

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

التقييم: يتم ترتيب المرشحين بناءً على درجة مرجحة للإعجابات. يتم ترجيح كل إعجاب تم الحصول عليه بناءً على مستوى ثقة الشخص الذي قدمه:

مقدم الإعجاب الوزن
مدير أو مشرف 3.0
مستوى ثقة 4 2.0
مستوى ثقة 3 1.5
مستوى ثقة 2 1.0
مستوى ثقة 1 0.25
مستوى ثقة 0 0.1

الدرجة النهائية هي SUM(الإعجابات المرجحة) / (5 + عدد المنشورات) — حيث يؤدي القسمة على عدد المنشورات إلى تقليل ميزة النشطين في النشر. يمكن لـ مستخدمين اثنين كحد أقصى الحصول على الشارة شهريًا. يتلقى كل فائز أيضًا رسالة نظام.

تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل مهمة مجدولة يوميًا (تُمنح الجائزة عن الشهر التقويمي السابق)

:3rd_place_medal: رد لطيف (إعجابات على منشور)

تتبع شارات :3rd_place_medal: رد لطيف، و:2nd_place_medal: رد جيد، و:1st_place_medal: رد رائع نفس النمط ولكن بعتبة مختلفة لـ p.like_count.

SELECT p.user_id, p.id post_id, CURRENT_TIMESTAMP granted_at
      FROM badge_posts p
      WHERE p.post_number > 1 AND p.like_count >= #{count.to_i} AND
        (:backfill OR p.id IN (:post_ids) )
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند قيام مستخدم بإجراء على منشور

:3rd_place_medal: مشاركة لطيفة (مشاركة الروابط)

تتبع شارات :3rd_place_medal: مشاركة لطيفة، و:2nd_place_medal: مشاركة جيدة، و:1st_place_medal: مشاركة رائعة نفس النمط ولكن بعتبة مختلفة لـ HAVING COUNT(*).

SELECT views.user_id, i2.post_id, CURRENT_TIMESTAMP granted_at
      FROM
      (
        SELECT i.user_id, MIN(i.id) i_id
        FROM incoming_links i
        JOIN badge_posts p on p.id = i.post_id
        JOIN users u on u.id = i.user_id
        GROUP BY i.user_id,i.post_id
        HAVING COUNT(DISTINCT(i.ip_address, i.current_user_id)) >= #{count}
      ) as views
      JOIN incoming_links i2 ON i2.id = views.i_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل تحديث يومي

:3rd_place_medal: موضوع لطيف (إعجابات على موضوع)

تتبع شارات :3rd_place_medal: موضوع لطيف، و:2nd_place_medal: موضوع جيد، و:1st_place_medal: موضوع رائع نفس النمط ولكن بعتبة مختلفة لـ p.like_count.

SELECT p.user_id, p.id post_id, CURRENT_TIMESTAMP granted_at
      FROM badge_posts p
      WHERE p.post_number = 1 AND p.like_count >= #{count.to_i} AND
        (:backfill OR p.id IN (:post_ids) )
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند قيام مستخدم بإجراء على منشور

:3rd_place_medal: من الحب (أقصى عدد من الإعجابات في يوم)

تتبع شارات :3rd_place_medal: من الحب، و:2nd_place_medal: حب أعلى، و:1st_place_medal: جنون في الحب نفس النمط ولكن بقيمة عتبة مختلفة لـ HAVING COUNT(*).

SELECT gdl.user_id, CURRENT_TIMESTAMP AS granted_at
      FROM given_daily_likes AS gdl
      WHERE gdl.limit_reached
        AND (:backfill OR gdl.user_id IN (:user_ids))
      GROUP BY gdl.user_id
      HAVING COUNT(*) >= #{count}
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: رابط شعبي (نقرات على الروابط)

تتبع شارات :3rd_place_medal: رابط شعبي، و:2nd_place_medal: رابط ساخن، و:1st_place_medal: رابط مشهور نفس النمط ولكن بعتبة مختلفة لـ tl.clicks.

SELECT tl.user_id, post_id, CURRENT_TIMESTAMP granted_at
        FROM topic_links tl
        JOIN badge_posts p ON p.id = post_id
       WHERE NOT tl.internal
         AND tl.clicks >= #{count}
      GROUP BY tl.user_id, tl.post_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل تحديث يومي

:3rd_place_medal: مروج (دعوات)

تتبع شارات :3rd_place_medal: مروج، و:2nd_place_medal: مخطط حملة، و:1st_place_medal: بطل نفس النمط ولكن بقيمة مستوى ثقة مختلفة مطلوبة للمدعوين، وعتبة مختلفة لـ HAVING COUNT(*).

SELECT u.id user_id, CURRENT_TIMESTAMP granted_at
      FROM users u
      WHERE u.id IN (
        SELECT invited_by_id
        FROM invites i
        JOIN invited_users iu ON iu.invite_id = i.id
        JOIN users u2 ON u2.id = iu.user_id
        WHERE i.deleted_at IS NULL
        AND i.invited_by_id <> u2.id
        AND u2.active
        AND u2.trust_level >= #{trust_level.to_i}
        AND u2.silenced_till IS NULL
        GROUP BY invited_by_id
        HAVING COUNT(*) >= #{count.to_i}
      ) AND u.active AND u.silenced_till IS NULL AND u.id > 0 AND
      (:backfill OR u.id IN (:user_ids) )
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: قراءة الإرشادات

 SELECT user_id, read_faq granted_at
    FROM user_stats
    WHERE read_faq IS NOT NULL AND (user_id IN (:user_ids) OR :backfill)
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تعديل مستخدم أو إنشائه

:3rd_place_medal: قارئ

SELECT id user_id, CURRENT_TIMESTAMP granted_at
    FROM users
    WHERE id IN
    (
      SELECT pt.user_id
      FROM post_timings pt
      JOIN badge_posts b ON b.post_number = pt.post_number AND
                            b.topic_id = pt.topic_id
      JOIN topics t ON t.id = pt.topic_id
      LEFT JOIN user_badges ub ON ub.badge_id = 17 AND ub.user_id = pt.user_id
      WHERE ub.id IS NULL AND t.posts_count > 100
      GROUP BY pt.user_id, pt.topic_id, t.posts_count
      HAVING COUNT(*) >= t.posts_count
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل

:3rd_place_medal: شكرًا لك (إعجابات مُعطاة + إعجابات مُستلمة)

تتبع شارات :3rd_place_medal: شكرًا لك، و:2nd_place_medal: يعيد العطاء، و:1st_place_medal: متعاطف نفس النمط ولكن بقيم مختلفة لـ us.likes_given و HAVING COUNT(*).

SELECT us.user_id, CURRENT_TIMESTAMP granted_at
      FROM user_stats AS us
      INNER JOIN posts AS p ON p.user_id = us.user_id
      WHERE p.like_count > 0
        AND us.likes_given >= #{likes_given}
        AND (:backfill OR us.user_id IN (:user_ids))
      GROUP BY us.user_id, us.likes_given
      HAVING COUNT(*) > #{likes_received}
تشغيل استعلام الإلغاء يوميًا
يستهدف الاستعلام المنشورات
المشغّل تحديث يومي

:3rd_place_medal: ترحيب

SELECT p.user_id, MIN(post_id) post_id, MIN(pa.created_at) granted_at
    FROM post_actions pa
    JOIN badge_posts p on p.id = pa.post_id
    WHERE post_action_type_id = 2 AND
        (:backfill OR pa.post_id IN (:post_ids) )
    GROUP BY p.user_id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند قيام مستخدم بإجراء على منشور

:3rd_place_medal: محرر ويكي

SELECT pr2.user_id, pr2.post_id, pr2.created_at granted_at
    FROM
    (
      SELECT MIN(pr.id) id
      FROM post_revisions pr
      JOIN badge_posts p on p.id = pr.post_id
      WHERE p.wiki
          AND NOT pr.hidden
          AND (:backfill OR p.id IN (:post_ids))
      GROUP BY pr.user_id
    ) as X
    JOIN post_revisions pr2 ON pr2.id = X.id
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

ردود فعل Discourse

:3rd_place_medal: أول رد فعل

SELECT user_id, created_at AS granted_at, post_id
  FROM (
           SELECT ru.post_id, ru.user_id, ru.created_at,
                  ROW_NUMBER() OVER (PARTITION BY ru.user_id ORDER BY ru.created_at) AS row_number
           FROM discourse_reactions_reaction_users ru
                JOIN badge_posts p ON ru.post_id = p.id
           WHERE :backfill
              OR ru.post_id IN (:post_ids)
       ) x
  WHERE row_number = 1
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات :white_check_mark:
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

Discourse Solved

:3rd_place_medal: تم الحل!

 SELECT post_id, user_id, created_at AS granted_at
  FROM (
           SELECT p.id AS post_id, p.user_id, dsst.created_at,
              ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY dsst.created_at) AS row_number
           FROM discourse_solved_solved_topics dsst
              JOIN badge_posts p ON dsst.answer_post_id = p.id
              JOIN topics t ON p.topic_id = t.id
           WHERE p.user_id <> t.user_id -- تجاهل المواضيع التي تم حلها من قبل صاحب الموضوع الأصلي
              AND (:backfill OR p.id IN (:post_ids))
       ) x
  WHERE row_number = 1
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

:2nd_place_medal: مرشد إرشادي

تتبع شارات :2nd_place_medal: مرشد إرشادي، و:1st_place_medal: عارف بكل شيء، و:1st_place_medal: مؤسسة الحلول نفس النمط ولكن بعتبة مختلفة لـ HAVING COUNT (*) >=:

SELECT p.user_id, MAX(pcf.created_at) AS granted_at
   FROM post_custom_fields pcf
        JOIN badge_posts p ON pcf.post_id = p.id
        JOIN topics t ON p.topic_id = t.id
   WHERE pcf.name = 'is_accepted_answer'
     AND p.user_id <> t.user_id -- تجاهل المواضيع التي تم حلها من قبل صاحب الموضوع الأصلي
     AND (:backfill OR p.id IN (:post_ids))
   GROUP BY p.user_id
   HAVING COUNT(*) >= #{min_count}
تشغيل استعلام الإلغاء يوميًا :white_check_mark:
يستهدف الاستعلام المنشورات
المشغّل عند تعديل مستخدم لمنشور أو إنشائه

Discourse Github

لا أستطيع قراءة هذه. :upside_down:

المصادر:

هذا رائع يا @JammyDodger شكراً لك على هذا الموضوع المفيد! :slight_smile: