News:

Bored?  Looking to kill some time?  Want to chat with other SMF users?  Join us in IRC chat or Discord

Main Menu

[WIP] Modding Documentation: Adding Permissions to 2.0

Started by Arantor, October 29, 2009, 12:18:46 PM

Previous topic - Next topic

Arantor

Well, we asked for feedback, and one of the things raised was better documentation for coders.

In that spirit, we went away and produced something for you. It's still something of a work in progress though so please feel free to critique :)




What is a permission?
A permission is nothing more than a flag that can be attached to a membergroup. It does not do anything unless the permission is checked.

A permission can be attached/checked at general level (i.e. available everywhere) or at board level (i.e. available only within boards they have that permission; this is what allows local moderators to work, as well as board specific permissions)

Adding the permission
Adding a permission is actually as simple as adding a single line.

This master array in ManagePermissions.php contains the entirety of permissions:
$permissionList = array(
'membergroup' => array(
'view_stats' => array(false, 'general', 'view_basic_info'),
'view_mlist' => array(false, 'general', 'view_basic_info'),
'who_view' => array(false, 'general', 'view_basic_info'),
'search_posts' => array(false, 'general', 'view_basic_info'),
'karma_edit' => array(false, 'general', 'moderate_general'),
'pm_read' => array(false, 'pm', 'use_pm_system'),
'pm_send' => array(false, 'pm', 'use_pm_system'),
'calendar_view' => array(false, 'calendar', 'view_basic_info'),
'calendar_post' => array(false, 'calendar', 'post_calendar'),
'calendar_edit' => array(true, 'calendar', 'post_calendar', 'moderate_general'),
'admin_forum' => array(false, 'maintenance', 'administrate'),
'manage_boards' => array(false, 'maintenance', 'administrate'),
'manage_attachments' => array(false, 'maintenance', 'administrate'),
'manage_smileys' => array(false, 'maintenance', 'administrate'),
'edit_news' => array(false, 'maintenance', 'administrate'),
'access_mod_center' => array(false, 'maintenance', 'moderate_general'),
'moderate_forum' => array(false, 'member_admin', 'moderate_general'),
'manage_membergroups' => array(false, 'member_admin', 'administrate'),
'manage_permissions' => array(false, 'member_admin', 'administrate'),
'manage_bans' => array(false, 'member_admin', 'administrate'),
'send_mail' => array(false, 'member_admin', 'administrate'),
'issue_warning' => array(false, 'member_admin', 'moderate_general'),
'profile_view' => array(true, 'profile', 'view_basic_info', 'view_basic_info'),
'profile_identity' => array(true, 'profile', 'edit_profile', 'moderate_general'),
'profile_extra' => array(true, 'profile', 'edit_profile', 'moderate_general'),
'profile_title' => array(true, 'profile', 'edit_profile', 'moderate_general'),
'profile_remove' => array(true, 'profile', 'delete_account', 'moderate_general'),
'profile_server_avatar' => array(false, 'profile', 'use_avatar'),
'profile_upload_avatar' => array(false, 'profile', 'use_avatar'),
'profile_remote_avatar' => array(false, 'profile', 'use_avatar'),
),
'board' => array(
'moderate_board' => array(false, 'general_board', 'moderate'),
'approve_posts' => array(false, 'general_board', 'moderate'),
'post_new' => array(false, 'topic', 'make_posts'),
'post_unapproved_topics' => array(false, 'topic', 'make_unapproved_posts'),
'post_unapproved_replies' => array(true, 'topic', 'make_unapproved_posts', 'make_unapproved_posts'),
'post_reply' => array(true, 'topic', 'make_posts', 'make_posts'),
'merge_any' => array(false, 'topic', 'moderate'),
'split_any' => array(false, 'topic', 'moderate'),
'send_topic' => array(false, 'topic', 'moderate'),
'make_sticky' => array(false, 'topic', 'moderate'),
'move' => array(true, 'topic', 'moderate', 'moderate'),
'lock' => array(true, 'topic', 'moderate', 'moderate'),
'remove' => array(true, 'topic', 'modify', 'moderate'),
'modify_replies' => array(false, 'topic', 'moderate'),
'delete_replies' => array(false, 'topic', 'moderate'),
'announce_topic' => array(false, 'topic', 'moderate'),
'delete' => array(true, 'post', 'modify', 'moderate'),
'modify' => array(true, 'post', 'modify', 'moderate'),
'report_any' => array(false, 'post', 'participate'),
'poll_view' => array(false, 'poll', 'participate'),
'poll_vote' => array(false, 'poll', 'participate'),
'poll_post' => array(false, 'poll', 'post_polls'),
'poll_add' => array(true, 'poll', 'post_polls', 'moderate'),
'poll_edit' => array(true, 'poll', 'modify', 'moderate'),
'poll_lock' => array(true, 'poll', 'moderate', 'moderate'),
'poll_remove' => array(true, 'poll', 'modify', 'moderate'),
'mark_any_notify' => array(false, 'notification', 'notification'),
'mark_notify' => array(false, 'notification', 'notification'),
'view_attachments' => array(false, 'attachment', 'participate'),
'post_unapproved_attachments' => array(false, 'attachment', 'make_unapproved_posts'),
'post_attachment' => array(false, 'attachment', 'attach'),
),
);


The first array contains the general permissions, the second board specific permissions.

Each array specifies 3 or 4 parameters:
1: required: true/false -> whether the permission has multiple options, either for the permission standing on its own, or whether it has own/any variations. For example, poll_add can be granted either to 'own' (adding to user's own topics) or 'any' (adding to any topic)
2: required: string. Details which group of 'Classic' permissions the permission gets added under:

  • general - General (e.g. View forum statistics)
  • pm - Personal Messages
  • calendar - Calendar permissions
  • maintenance - Forum administration
  • member_admin - Member adminstration
  • profile - Member Profiles

  • general_board - general board permissions (currently only moderate board)
  • topic - topic specific permissions
  • post - post related
  • poll - poll related
  • notification - notifications related
  • attachment - attachment related

To add a permission to any of these groups, simply state the permission's name as the second parameter for classic view.

3: required: string. Details which group a permission will be added into for 'Simple' permissions.

  • view_basic_info - "Use basic forum functionality"
  • use_pm_system - "Contact members using the personal messaging system"
  • post_calendar - "Post events on to the calendar"
  • edit_profile - "Personalize their profile"
  • delete_account - "Delete their account"
  • use_avatar - "Select or upload an avatar"
  • moderate_general - "Moderate the entire forum"
  • administrate - "Carry out administrative duties"

  • make_posts - "Post topics and replies to the board"
  • make_unapproved_posts - "Modify their posts"
  • post_polls - "Make new polls"
  • participate - "View additional board content"
  • modify - "Modify their posts"
  • notification - "Request notifications"
  • attach - "Post attachments"
  • moderate - "Moderate the board"

The fourth parameter is only required if the first was true, i.e. a permission has multiple components. Then, the third parameter names the group in Simple permissions for _own, the fourth parameter is the group in Simple permissions for _any.

The only thing remaining to do is to define the text that will be displayed. This can be done in the ManagePermissions.{language}.php, but is ideally better placed in Modifications.{language}.php.

There are multiple language strings to add, potentially. For a permission named 'do_something', the following entries in $txt can be listed:
$txt['permissionname_do_something'] - the description of the permission (this is the only one that is required)
$txt['permissionhelp_do_something'] - the help text of the permission (the ? next to it in the permissions screen)
$txt['permissionname_simple_do_something'] - the description of the permission for Simple view, not required but will be used in place of permissionname_do_something if declared.
$txt['permissionhelp_simple_do_something'] - the help text of the permission for Simple view, again not required but will be used in place of permissionhelp_do_something if declared.

Note also that if a permission is declared as having multiple options (first parameter), do_something would not be correct; do_something_own and do_something_any would be the generated permissions, with do_something being the placeholder for it:

$txt['permission_do_something'] -> the description of the permission (only)
$txt['permission_do_something_own'] -> typically "Own {something}" e.g Own replies, own polls
$txt['permission_do_something_any'] -> typically "Any {something}" e.g. Any replies, any polls

Adding a permission group
Adding a group of permissions is very simple if placement is not a concern.

Simply add an identifier to the new permissions for their new group - much like 'post_polls' or 'notification', though note that a group cannot be named the same between general and board specific permissions and should not be named the same between simple and classic views.

Once the identifier has been added to the new permission, e.g.:
'do_something' => array(false, 'something', 'something_opts'),

The remaining task is to specify language strings, again in Modifications.{language}.php. This time, the prefix is permissiongroup_ and permissiongroup_simple_:

$txt['permissiongroup_something'] = 'Something';
$txt['permissiongroup_simple_something_opts'] = 'Something that our users can do';


It is possible to modify the order in which groups appear. To do so, find this array:
$permissionGroups = array(
'membergroup' => array(
'simple' => array(
'view_basic_info',
'use_pm_system',
'post_calendar',
'edit_profile',
'delete_account',
'use_avatar',
'moderate_general',
'administrate',
),
'classic' => array(
'general',
'pm',
'calendar',
'maintenance',
'member_admin',
'profile',
),
),
'board' => array(
'simple' => array(
'make_posts',
'make_unapproved_posts',
'post_polls',
'participate',
'modify',
'notification',
'attach',
'moderate',
),
'classic' => array(
'general_board',
'topic',
'post',
'poll',
'notification',
'attachment',
),
),
);


Then simply add the new group in the relevant position in that array, using the same group identifier. Note that this is *not* required for just adding permissions; it simply controls where it will be if specific placement is desirable, rather than "at the end".

Checking a permission
No permission will of itself do anything. As part of SMF itself, or inside a mod, the permission must be checked by the code.

There are two levels of checking, notionally 'soft' and 'hard'.

The former, is done with allowedTo('permission_name'), e.g. allowedTo('do_something'). This returns a true/false that can be used within the code to achieve something. This is used, for example, in the menu, to determine whether a user can use the search functions. It will not generate an error, simply return true/false for any permission.

The latter, uses simply the isAllowedTo('permission_name') form. This will check the permission and if not granted, force an error that will immediately suspend execution.

The text used by the fatal error is derived from the contexts of Errors.language.php - $txt['cannot_permission_name'] will contain its contents, so if isAllowedTo('do_something') is not allowed, $txt['cannot_do_something'] holds the error message.

For permissions that have own and any variations, the string would be $txt['cannot_do_something_own'] and $txt['cannot_do_something_any'] as necessary.

There is also an extra variation for board-level permissions, for example which boards a given user has sufficient permissions to see attachments (for the Profile / Show Posts / Show Attachments page)

There is a function called boardsAllowedTo(), which achieves this, and returns an array of all the boards that a given user has the given permission.

In the case of the Show Attachments page, the relevant code is:
$boardsAllowed = boardsAllowedTo('view_attachments');

This would return an array of 0 or more items of board ids where the view_attachments permission is available.

SoLoGHoST

Excellent topic on Permissions Arantor.

I would also just like to add that in ManagePermissions.php, if you have added any permissions to the $permissionList array, noted by Arantor, that you do not want to be allowed within the Guests usergroup, you can add the permissions not allowed for guests within the loadIllegalGuestPermissions() function and will be placed within the $context['non_guest_permissions'] array.
$context['non_guest_permissions'] = array(
'karma_edit',
'pm_read',
'pm_send',
'profile_identity',
'profile_extra',
'profile_title',
'profile_remove',
'profile_server_avatar',
'profile_upload_avatar',
'profile_remote_avatar',
'mark_any_notify',
'mark_notify',
'admin_forum',
'manage_boards',
'manage_attachments',
'manage_smileys',
'edit_news',
'access_mod_center',
'moderate_forum',
'issue_warning',
'manage_membergroups',
'manage_permissions',
'manage_bans',
'send_mail',
);
and you just add the name of the permission that you don't want to be available from Permissions for Guests within the Admin Panel.

Cheers :)


Arantor

Good comment, SoLoGHoST, I knew I'd forget something :) (I don't generally set my own permissions up there, though)

Arantor

I learned something today :)

I discovered something I didn't know before and documented it - if you ever need to figure out which board(s) a user has a given permission in, rather than just allowedTo, there's a function for that.

Garou

I'm editing a block of code where the permissions are referenced as "$context['can_permission']" and If I try to add my permission in as "if (allowedto('my_permission'))" it not only looks sloppy compared to the rest of the code in there it also breaks it.

So my question is... Where would one set up something like "can_my_permission"?

Arantor

There are actually practical reasons for the $context['can_...'], e.g. $context['can_new_topic'], mostly it's performance. Permissions will have been loaded at that point anyway, and by referencing a variable in current scope versus function call there is a performance gain to be had.

I forget exactly where the $context['can_...'] are set up but all they do literally is $context['can_...'] = allowedTo('...');

A search against the source would tell you quickly enough.

Garou


Advertisement: