| @@ -890,9 +890,12 @@ | ||
| 890 | 890 | /** |
| 891 | 891 | * The agent's trigger row for a given invocation source, if any. |
| 892 | 892 | * |
| 893 | 893 | * Source slugs on the invoke route map 1:1 onto trigger kinds |
| 894 | - * (`chat`, `drag`, `send-to`). | |
| 894 | + * (`chat`, `drag`, `send-to`). The row is context for the invocation | |
| 895 | + * filter; it does not decide which capabilities the invocation gate | |
| 896 | + * requires, which is every capability on every trigger (see | |
| 897 | + * `openstation_agent_user_can_invoke_agent()`). | |
| 895 | 898 | * |
| 896 | 899 | * @param int $agent_user_id Agent user id. |
| 897 | 900 | * @param string $source Invocation source slug. |
| 898 | 901 | * @return array|null Trigger row, or null when the agent declares none |
| @@ -908,43 +911,65 @@ | ||
| 908 | 911 | return null; |
| 909 | 912 | } |
| 910 | 913 | |
| 911 | 914 | /** |
| 912 | - * Whether the current user may invoke THIS agent through THIS source. | |
| 915 | + * Whether the current user may invoke THIS agent. | |
| 913 | 916 | * |
| 914 | 917 | * The route-level `openstation_agents_user_can_invoke()` check is |
| 915 | 918 | * site-wide — it answers "may this user invoke agents at all". This is |
| 916 | 919 | * the per-agent half: a trigger may declare a `capability` in its |
| 917 | - * config, and until it is enforced here the field is decorative. The | |
| 918 | - * Triggers pane collects it and the store persists it, so an | |
| 919 | - * administrator restricting an agent to `manage_options` has every | |
| 920 | - * reason to believe it took effect. | |
| 920 | + * config, and the Triggers pane collects it and the store persists it, | |
| 921 | + * so an administrator restricting an agent to `manage_options` has | |
| 922 | + * every reason to believe it took effect. | |
| 921 | 923 | * |
| 922 | - * An agent with no trigger for the source, or a trigger that declares | |
| 923 | - * no capability, is left to the route-level check — requiring a | |
| 924 | + * The caller must hold EVERY capability declared on ANY of the agent's | |
| 925 | + * triggers, whichever source the request names. The source is supplied | |
| 926 | + * by the client (the invoke route takes it as a request parameter), so | |
| 927 | + * it describes how the request says it arrived, not what it is allowed | |
| 928 | + * to reach: a capability scoped to one trigger kind would be satisfied | |
| 929 | + * by naming another. A capability configured on an agent is therefore | |
| 930 | + * a property of the agent. | |
| 931 | + * | |
| 932 | + * An agent whose triggers declare no capability (including one with no | |
| 933 | + * triggers at all) is left to the route-level check — requiring a | |
| 924 | 934 | * configured trigger would lock out every agent created before triggers |
| 925 | 935 | * were set up, which is all of them by default. |
| 926 | 936 | * |
| 927 | 937 | * @param int $agent_user_id Agent user id. |
| 928 | - * @param string $source Invocation source slug. | |
| 938 | + * @param string $source Invocation source slug the request names | |
| 939 | + * (`chat`, `drag`, `send-to`). Context for | |
| 940 | + * the filter only; it does not select which | |
| 941 | + * capabilities apply. | |
| 929 | 942 | * @return bool |
| 930 | 943 | */ |
| 931 | 944 | function openstation_agent_user_can_invoke_agent( $agent_user_id, $source = 'chat' ) { |
| 932 | - $trigger = openstation_agent_trigger_for_source( $agent_user_id, $source ); | |
| 933 | - $capability = ''; | |
| 934 | - if ( is_array( $trigger ) && isset( $trigger['config']['capability'] ) ) { | |
| 935 | - $capability = trim( (string) $trigger['config']['capability'] ); | |
| 945 | + $can = true; | |
| 946 | + foreach ( openstation_agent_get_triggers( (int) $agent_user_id ) as $row ) { | |
| 947 | + if ( ! isset( $row['config']['capability'] ) || ! is_scalar( $row['config']['capability'] ) ) { | |
| 948 | + continue; | |
| 949 | + } | |
| 950 | + $capability = trim( (string) $row['config']['capability'] ); | |
| 951 | + if ( '' !== $capability && ! current_user_can( $capability ) ) { | |
| 952 | + $can = false; | |
| 953 | + break; | |
| 954 | + } | |
| 936 | 955 | } |
| 937 | 956 | |
| 938 | - $can = '' === $capability || current_user_can( $capability ); | |
| 939 | - | |
| 940 | 957 | /** |
| 941 | 958 | * Filter whether the current user may invoke a specific agent. |
| 942 | 959 | * |
| 943 | - * @param bool $can Whether invocation is allowed. | |
| 960 | + * @param bool $can Whether invocation is allowed: the | |
| 961 | + * caller holds every capability | |
| 962 | + * declared on any of the agent's | |
| 963 | + * triggers. | |
| 944 | 964 | * @param int $agent_user_id Agent user id. |
| 945 | - * @param string $source Invocation source slug. | |
| 946 | - * @param array|null $trigger The matching trigger row, if any. | |
| 965 | + * @param string $source Invocation source slug the request | |
| 966 | + * names. Client-supplied on the invoke | |
| 967 | + * route, so context rather than proof | |
| 968 | + * of how the request arrived. | |
| 969 | + * @param array|null $trigger The trigger row whose kind matches | |
| 970 | + * `$source`, if any. Context only: it | |
| 971 | + * is not what decided `$can`. | |
| 947 | 972 | */ |
| 948 | 973 | return (bool) apply_filters( |
| 949 | 974 | 'openstation_agent_user_can_invoke_agent', |
| 950 | 975 | $can, |
| @@ -949,9 +974,9 @@ | ||
| 949 | 974 | 'openstation_agent_user_can_invoke_agent', |
| 950 | 975 | $can, |
| 951 | 976 | (int) $agent_user_id, |
| 952 | 977 | (string) $source, |
| 953 | - $trigger | |
| 978 | + openstation_agent_trigger_for_source( $agent_user_id, $source ) | |
| 954 | 979 | ); |
| 955 | 980 | } |
| 956 | 981 | |
| 957 | 982 | // --------------------------------------------------------------------------- |
| @@ -1094,9 +1119,12 @@ | ||
| 1094 | 1119 | 'openstation_agent_invalid_name', |
| 1095 | 1120 | __( 'Agent name cannot be empty.', 'desktop-mode' ) |
| 1096 | 1121 | ); |
| 1097 | 1122 | } |
| 1098 | - if ( $name !== (string) $user->display_name ) { | |
| 1123 | + // Compared as plain text: the stored name carries entities | |
| 1124 | + // (`&`), and the client sends back the decoded one it was | |
| 1125 | + // given on every save. Raw, that reads as a rename. | |
| 1126 | + if ( openstation_plain_text_title( $name ) !== openstation_plain_text_title( $user->display_name ) ) { | |
| 1099 | 1127 | $changed['name'] = array( |
| 1100 | 1128 | 'from' => (string) $user->display_name, |
| 1101 | 1129 | 'to' => $name, |
| 1102 | 1130 | ); |