الأدوار
`roles->list` و`listAll` و`iterate` و`get` و`create` و`update` و`delete` و`listPermissions`.
كل الدوالّ
use OpenEmail\Constants\ApiScopes; $page = $client->roles->list();echo count($page), PHP_EOL; $role = $client->roles->get('role_8b1f4c2e9a7d3b60e5f1a2c4');echo $role['name'], PHP_EOL; $support = $client->roles->create([ 'name' => 'Support', 'description' => 'Answers the shared inboxes and nothing else.', 'permissions' => [ApiScopes::EMAILS_SEND, ApiScopes::THREADS_WRITE, ApiScopes::LABELS_WRITE],]); echo implode(', ', $support['permissions']), PHP_EOL; $client->roles->update($support['id'], ['permissions' => [...$support['permissions'], ApiScopes::TEMPLATES_READ]]); $client->roles->delete($support['id'], reassignTo: $role['id']); $vocabulary = $client->roles->listPermissions();echo implode(', ', array_column($vocabulary, 'id')), PHP_EOL;يحمل $support['permissions'] ستة عناصر لا ثلاثة: فـ emails:send يجلب معه emails:read، وthreads:write يجلب threads:read، وlabels:write يجلب labels:read. اقرأ القائمة من الاستجابة بدل افتراضها.
يعيد list صفحة OpenEmail\Result\Page واحدة، ويعيد listAll كل الأدوار في مصفوفة واحدة، ويعيد iterate كائن Generator يسلّم دورًا واحدًا في كل مرة. ويعود الدور في صورة مصفوفة مفاتيحها بصيغة camelCase، فيقرأ $role['permissions'] القائمة. ويأخذ create وupdate المتن في صورة مصفوفة واحدة بأسماء API، بينما يأخذ delete الوسيط reassignTo: في صورة وسيط مسمّى.
الدور يقول ما يجوز لشخص أن يفعله. أما العناوين التي يجوز له أن يفعله عليها فهي المحور الآخر وتقيم في $client->members: انظر members->grantAddress وmembers->revokeAddress في صفحة الأعضاء. فجملة «يجوز له إرسال البريد» وجملة «يجوز له الإرسال باسم invoices@» جملتان مختلفتان، ومساحة عمل توظّف وكيل دعم ثانيًا تغيّر الثانية دون المساس بالأولى. وثمة صلاحية واحدة تجيب عن الجملتين معًا: الدور الذي يحمل addresses:all يصل إلى كل عنوان، بما في ذلك العناوين المضافة لاحقًا، بلا منح، ولا يستطيع وضعها على دور إلا شخص في التطبيق.
تفرّع على editable وdeletable لا على builtin ولا على الاسم. فكلاهما false للمالك وحده، الذي قائمته هي «كل صلاحية، بما فيها ما سيُخترع العام المقبل» وتُحسب ولا تُخزَّن. وكل دور آخر يجيب true عليهما، بما في ذلك الأدوار الخمسة التي تبدأ بها مساحة العمل. والدور الذي أعاد أحدهم تسميته يظل يجيب عنهما بصدق، ولم يعد اسمه يخبرك بشيء.
يستبدل update قائمة الصلاحيات كاملةً. ولا يوجد استدعاء لمنح صلاحية واحدة، فاقرأ الدور، وغيّر العنصر الذي تقصده، وأعد إرسالها جميعًا، كما يفعل [...$support['permissions'], ApiScopes::TEMPLATES_READ] أعلاه. وإرسال صلاحية واحدة يترك الدور حاملًا تلك الصلاحية وحدها، مع ما تستلزمه.
يحتاج delete إلى reassignTo: فور أن يحمل أحدٌ الدور. ويرسله العميل بوصفه معامل الاستعلام reassignTo، لأن متن طلب DELETE تُسقطه عدة بيئات تشغيل وعدد من الوسطاء، ويترك المعامل حين لا تمرّر شيئًا. وتبلّغ النتيجة عن reassigned وkeysReassigned كلٍّ على حدة، فيتمكن السكربت من تسجيل ما فعله لا ما طلبه.
إن listPermissions هو GET /roles/permissions، مسار ثابت يقع تمامًا حيث يقع معرّف الدور. ويستدعي العميل ذلك المسار مباشرة بدل تمرير الكلمة عبر get، ويعيد قائمة بسيطة، لا OpenEmail\Result\Page: مصفوفة واحدة لكل صلاحية، فيها id وlabel وgroup وscope. وحين تكون scope بقيمة false فإنها تعلّم العناصر التي لا يمكن لأي مفتاح أن يحملها أبدًا. لا تمرّر الكلمة إلى get بنفسك. فـ $client->roles->get('permissions') يبني المسار نفسه، فيرسل الطلب نفسه ويعيد المفردات داخل غلاف القائمة الخاص بها لا دورًا ولا 404.
الدور هو سقف المفتاح
المفتاح الصادر مقابل دور يجوز له ما يقع في تقاطع نطاقاته الخاصة مع صلاحيات ذلك الدور، ويُحسم ذلك لكل طلب عند الحدود. فتضييق دور يسحب صلاحيات مفاتيحه فورًا دون تدوير أي منها. والمفتاح بلا دور ليس له سقف إطلاقًا، وهو ما يجعل roleId بقيمة null أوسع حالة يمكن للمفتاح أن يكون فيها لا أضيقها.
ولهذا أيضًا يصرّ roles->delete على وجهة تُنقل إليها المفاتيح. فتيتيمها كان سيسقط سقفها بالكامل، ويرفع صامتًا كل اعتماد كان الدور يحدّه.
يبلّغ GET /keys/self وGET /ping عن roleId وgrantedScopes إلى جانب scopes الفعلية. وهكذا يُجاب عن «مفتاحي يحمل emails:send ومع ذلك أتلقى insufficient_scope»: فكل ما هو في grantedScopes وغائب عن scopes قد أخذه الدور. ويعيد $client->me->get() و$client->me->ping() كليهما في المصفوفة الخاصة بهما، فيسرد array_diff($key['grantedScopes'], $key['scopes']) ما أخذه الدور. والرفض نفسه PermissionException تكون قيمة isScopeMissing() فيه true.
المعاملات
namestringمطلوب- ما تسمّي به مساحة العمل الدور: من 1 إلى 48 حرفًا، ويُشذَّب الفراغ قبل التخزين. والأسماء فريدة في كل مساحة عمل دون اعتبار لحالة الأحرف، فيُرفض دور ثانٍ باسم «Support» بـ `role_name_taken` (409)، ويُرمى في صورة `ConflictException`، بدل إنشائه إلى جانب الأول.
descriptionstring- جملة تقول ما الدور لأجله، يُشذَّب فراغها وطولها 240 حرفًا كحد أقصى. والسلسلة التي تصير فارغة بعد التشذيب تُخزَّن كـ null، فوصف مكوَّن من فراغات يعود null لا كما أرسلته. وفي `create`، أغفل المفتاح بدل تمرير null: فالعميل يرسل null كما هو، ويرفضه `create` بـ 422. وفي `update`، يمسحه `'description' => null`.
permissionsarrayمطلوب- ما يمنحه الدور، مأخوذًا من المفردات التي يقدّمها `listPermissions`. والسلسلة التي ليست منها تعطي 422 على `permissions`، يُرمى في صورة `ValidationException` مع ضبط `param` على `permissions`، بدل إسقاطها صامتةً، فيُبلَّغ عن الخطأ المطبعي بدل أن يكلّفك بعد ظهيرة كاملة. وتُوسَّع القائمة عند الدخول (فـ `templates:write` يخزّن `templates:read` إلى جانبه)، ويُزال تكرارها وتُعاد إلى الترتيب المعياري، فاقرأ القائمة المخزَّنة من الاستجابة بدل افتراض أنها التي أرسلتها.
الاستجابة
objectstring- دائمًا `role`. وتجيب علامة الحذف بالقيمة نفسها، و`id` الخاص بالدور، و`deleted` بقيمة true، وعددَي إعادة الإسناد، ولا شيء من الحقول الأخرى أدناه.
idstring- معرّف الدور، يُقرأ بوصفه `$role['id']`. وهو ما يسمّيه `roleId` الخاص بالعضو، وما يشير إليه سقف مفتاح API، وما يأخذه `reassignTo:` حين يُحذف دور آخر وينتقل حاملوه إلى هذا الدور.
namestring- اسم الدور في مساحة العمل، مشذَّب وفريد دون اعتبار لحالة الأحرف. ويمكن إعادة تسمية كل دور عدا دور المالك، بما فيها الأدوار الأولية (فـ `builtin` يقول من أين جاء الصف، لا ما يجب أن يبقى اسمه)، فالدور المسمّى «Admin» لا يقول شيئًا مؤكدًا عمّا يحمله. والاسم الذي يحمله دور آخر بالفعل يعطي `role_name_taken` (409، مع ضبط `param` على `name`). وإعادة تسمية دور المالك تعطي `role_immutable` (409)، مثل أي تعديل آخر عليه.
descriptionstring or null- الجملة الواصفة للدور، أو null حين لم تُعطَ. والإدخال الفارغ يُخزَّن كـnull في الإنشاء والتحديث معًا، فلا يكون هذا الحقل سلسلة فارغة أبدًا.
permissionsarray- كل ما يمنحه الدور، موسَّعًا مسبقًا وبالترتيب المعياري لا بالترتيب الذي كتبه أحد. وذلك الترتيب أساسي: فدوران يحملان الصلاحيات نفسها يحملان مصفوفتين متساويتين، وهو ما يتيح لشاشة الإعدادات أن تقارنهما بـ `===` لتقرر ما إذا كان زر الحفظ مفعّلًا.
builtinstring or null- أي الأدوار الأولية الستة جاء منه هذا الصف، `owner` أو `admin` أو `member` أو `viewer` أو `developer` أو `billing`، أو null لدور كتبته مساحة العمل بنفسها. وهو يسجّل الأصل لا الحالة: فالدور الأولي يُعاد تسميته وتُغيَّر صلاحياته ويُحذف كأي دور آخر. تفرّع على `editable` و`deletable` لا على هذا الحقل. فالدور الذي سمّاه أحدهم «Admin» ليس بالضرورة الدور الأولي، والدور الأولي قد لا يعود اسمه كذلك.
editablebool- يُحسب بوصفه `builtin !== 'owner'`، فيكون false لدور المالك وحده، ويُرفض كل `update` لذلك الدور بـ `role_immutable` (409). وكل دور آخر قابل للتعديل بالكامل (الاسم والوصف والصلاحيات)، بما في ذلك الأدوار الخمسة التي تبدأ بها مساحة العمل.
deletablebool- يُحسب بوصفه `builtin !== 'owner'`: false لدور المالك وحده، الذي يعود بـ `role_undeletable` (409)، وtrue لكل دور آخر بما فيها الأدوار الأولية. افحصه قبل عرض الزر لا بعد الرفض. والدور الذي ما زال أحد يحمله يحتاج أيضًا إلى `reassignTo:`، وإلا أعطى الحذف `role_in_use` (409). ويُرمى الرفضان في صورة `ConflictException`، ويميّز `errorCode` بينهما.
membersint- كم شخصًا يحمل هذا الدور، محسوبًا من صفوف أعضاء مساحة العمل. والمالك ليس بينهم: فليس له صف عضوية ولا يمكن منحه دورًا، فيبلّغ دور Owner بصفر حاملين رغم أن قائمة الأعضاء تعرضه.
apiKeysint- كم مفتاح API حي يحدّه هذا الدور. وتُستثنى المفاتيح الملغاة من العدّ، رغم أن الحذف يعيد توجيه كل صف مفتاح يشير إلى الدور، بما فيها الملغاة. وهي الفئة الثانية التي يجب نقلها قبل أن يذهب الدور، والفئة التي لا ينتبه إليها أحد: فالمفاتيح برامج، والبرنامج لا يشتكي.
createdAtstring- متى كُتب صف الدور، في صورة سلسلة نصية بصيغة ISO 8601. وتُنشأ الصفوف المدمجة بتكاسل في أول مرة يحتاج إليها شيء، مثل قراءة قائمة الأدوار أو إنشاء دور أو شاشة مفاتيح API، لا عند إنشاء مساحة العمل. فالطابع الزمني للدور المدمج هو وقت وصول ذلك الطلب الأول لا وقت إنشاء مساحة العمل.
updatedAtstring- متى تغيّر الدور آخر مرة، في صورة سلسلة نصية بصيغة ISO 8601. وكل `update` مقبول يحرّكه، بما في ذلك تحديث يضبط حقلًا على القيمة التي كان يحملها أصلًا.