__( 'Read submissions', 'accessible-forms-by-accessible-org' ), 'manage_accessible_form_entries' => Extensions::registered( 'pro' ) ? __( 'Review, assign and annotate submissions', 'accessible-forms-by-accessible-org' ) : __( 'Review submissions', 'accessible-forms-by-accessible-org' ), 'export_accessible_form_entries' => Extensions::registered( 'pro' ) ? __( 'Export submissions', 'accessible-forms-by-accessible-org' ) : __( 'Export submissions (requires Pro)', 'accessible-forms-by-accessible-org' ), 'delete_accessible_form_entries' => __( 'Delete submissions', 'accessible-forms-by-accessible-org' ), 'send_accessible_form_notifications' => __( 'Send and retry notifications', 'accessible-forms-by-accessible-org' ), 'manage_accessible_forms_settings' => __( 'Manage settings and team access', 'accessible-forms-by-accessible-org' ), ); } public static function install() { $admin = get_role( 'administrator' ); if ( $admin ) { foreach ( array_merge( self::forms(), array_keys( self::labels() ) ) as $cap ) { $admin->add_cap( $cap ); } } // Preserve editors' form-building access without granting private-entry access. $editor = get_role( 'editor' ); if ( $editor ) { foreach ( self::forms() as $cap ) { $editor->add_cap( $cap ); } } Access::install( self::$restore_roles ); self::$restore_roles = false; // Add-ons grant their own capabilities again after a reset. Extensions::notify( 'accessible_forms_capabilities_installed' ); } /** * Whether a user may act on a form's submissions. Add-ons may narrow this per * form through `accessible_forms_user_can_form`; settings administrators * always keep access. */ public static function form( $form_id, $cap = 'read_accessible_form_entries', $user_id = 0 ) { $user_id = $user_id ?: get_current_user_id(); if ( ! user_can( $user_id, $cap ) ) { return false; } if ( user_can( $user_id, 'manage_accessible_forms_settings' ) ) { return true; } if ( 'accessible_form' !== get_post_type( $form_id ) ) { return false; } $raw = get_post_meta( $form_id, self::ACCESS_META, true ); if ( '' !== $raw ) { $users = self::restriction( $raw ); if ( null === $users || ( $users && ! in_array( (string) $user_id, $users, true ) ) ) { return false; } } return (bool) Extensions::filter( 'accessible_forms_user_can_form', true, (int) $form_id, $cap, (int) $user_id ); } /** A saved rule is a JSON list of positive decimal user IDs; damaged rules deny access. */ public static function restriction( $raw ) { if ( ! is_string( $raw ) ) { return null; } // Keep objects distinct from lists, and large IDs distinct from floating-point numbers. $users = json_decode( $raw, false, 512, JSON_BIGINT_AS_STRING ); if ( ! is_array( $users ) ) { return null; } $ids = array(); foreach ( $users as $user ) { if ( ! ( is_int( $user ) || is_string( $user ) ) || ! preg_match( '/^0*[1-9][0-9]*$/D', (string) $user ) ) { return null; } $ids[] = ltrim( (string) $user, '0' ); } return $ids; } /** Saved restrictions are enforced even when their editing add-on is absent. */ public static function access_scope( $user_id ) { global $wpdb; // Match the same JSON list grammar as restriction(). REGEXP works on the // supported MySQL/MariaDB versions without requiring JSON SQL functions. $space = '[ ' . "\t\r\n" . ']*'; $id = '("0*[1-9][0-9]*"|[1-9][0-9]*)'; $empty = '^' . $space . '\\[' . $space . '\\]' . $space . '$'; $list = '^' . $space . '\\[' . $space . '(' . $id . '(' . $space . ',' . $space . $id . ')*)?' . $space . '\\]' . $space . '$'; $member = '(\\[|,)' . $space . '("0*' . (int) $user_id . '"|' . (int) $user_id . ')' . $space . '(,|\\])'; return array( array( "NOT EXISTS (SELECT 1 FROM {$wpdb->postmeta} acl WHERE acl.post_id = form_id AND acl.meta_key = '" . self::ACCESS_META . "' AND acl.meta_value <> '' AND acl.meta_value NOT REGEXP %s AND (acl.meta_value NOT REGEXP %s OR acl.meta_value NOT REGEXP %s))" ), array( $empty, $list, $member ), ); } public static function entry( $entry, $cap = 'read_accessible_form_entries' ) { return is_array( $entry ) && self::form( $entry['form_id'], $cap ); } /** Add a SQL-level scope; counts, lists and exports all use the same boundary. */ public static function query( array $args = array(), $cap = 'read_accessible_form_entries' ) { $args['_access_cap'] = $cap; return $args; } public static function remove() { self::$restore_roles = true; foreach ( wp_roles()->role_objects as $role ) { foreach ( array_merge( self::forms(), array_keys( self::labels() ) ) as $cap ) { $role->remove_cap( $cap ); } } } }