foldershare-8.x-1.2/src/Controller/UserAutocompleteController.php
src/Controller/UserAutocompleteController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | <?php namespace Drupal\foldershare\Controller; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Drupal\foldershare\Settings; use Drupal\foldershare\Utilities\UserUtilities; /** * Provides a user name autocomplete service. * * The user name autocomplete service is intended for use by forms that * require the user to type in a user or account name in order to * select a user. There are two primary forms that may use this: * - The change owner form that prompts for the account of the new owner * of a selected item. * - The share form that prompts for the account of a user to be granted * shared access to a file or folder. * * This autocomplete service is available on the route * "entity.foldershare.userautocomplete", defined in the module's routing file. * * Required URL arguments include: * - 'q=NAMEFRAGMENT' to provide a string to look up as part of a user's * account name, display name, or email address. * * Optional URL arguments include: * - 'excludeUids=UIDLIST' to provide a comma-separated list of integer user IDs * to NOT include in the returned autocomplete results. This may be used to * exclude users in a 'Share' form that have already been granted access. * - 'excludeBlocked=1' to to prevent the returned list from including blocked * users. * * The returned user list always excludes users with IDs in the 'excludeUids' * list, if any. * * The return list optionally excludes blocked users, if 'excludeBlocked' is 1. * * The returned user list is empty if: * - User autocomplete has been disabled for the module. * - The name fragment is empty. * - The name fragment does not match anything. * * The returned list is an array, formatted as JSON, sorted by user name. * Each entry is an array with 'value' and 'label' fields. The 'value' field * is the user account name, and the 'label' a label to show in an * autocomplete menu. Labels have one of these forms: * - Name only. * - Name with email address. * - Name with masked email address. * * The label style is determined by a module setting. * * The name is the account display name, and the email address the account's * email address (if any). When the masked form is used, the email address * is masked with '*'s for all name field characters, except the first and * last (e.g. "e*****e@example.com"). * * @ingroup foldershare */ class UserAutocompleteController extends ControllerBase { /*--------------------------------------------------------------------- * * Constants. * *---------------------------------------------------------------------*/ /** * The default maximum number of results to return. * * @var int */ const MAXIMUM_NUMBER_OF_RESULTS = 10; /*-------------------------------------------------------------------- * * Fields - dependency injection. * *--------------------------------------------------------------------*/ /** * The User entity storage manager, set at construction time. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $userStorage ; /*-------------------------------------------------------------------- * * Construction. * *--------------------------------------------------------------------*/ /** * Constructs a new page. */ public function __construct( EntityStorageInterface $userStorage ) { $this ->userStorage = $userStorage ; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container ) { return new static ( $container ->get( 'entity_type.manager' )->getStorage( 'user' )); } /*--------------------------------------------------------------------- * * Autocomplete. * *---------------------------------------------------------------------*/ /** * Responds to a text field's autocomplete request using a user name fragment. * * Auto-complete uses the given user name fragment to find all user names * that are similar to the name. If configured using the FolderShare admin * settings form, auto-complete may also look at email addresses to find * a good match. * * The returned list is intended for the 'Share' form to indicate users * with which to share content. There may already be users listed in the * form, and returning them in the auto-complete would be redundant. To * skip those users, an optional excludeUids list omits them from the returned * results. * * @param \Symfony\Component\HttpFoundation\Request $request * The HTTP request, including two parameters: * - 'q' has the text field input to auto-complete. * - 'excludeUids' has a list of integer UIDs to exclude. * - 'excludeBlocked' is 0 (FALSE) or 1 (TRUE) to indicate whether blocked * user accounts should be excluded. * * @return \Symfony\Component\HttpFoundation\JsonResponse * The JSON response that contains an array of autocomplete content. Each * entry in the JSON array is for a user. Each user entry is itself an * array with 'value' and 'label' string values. The label is intended to * be shown in an autocomplete menu, and the value is the value for that * label. */ public function autocomplete(Request $request ) { // // Get parameters. // --------------- // 'q' is the query input from the text field. It must not be empty. // 'excludeUids' is the optional list of user IDs to skip in the returned // results. It can be missing or empty. $userNameFragment = $request ->get( 'q' , NULL); $excludeUids = $request ->get( 'excludeUids' , []); $excludeBlocked = $request ->get( 'excludeBlocked' , FALSE); $excludeBlocked = boolval( $excludeBlocked ); if ( empty ( $userNameFragment ) === TRUE) { // No user name fragment given. Return an empty response. return new JsonResponse([]); } // // Get auto-complete style. // ------------------------ // The following styles are recognized: // - 'none'. // - 'name-only'. // - 'name-email'. // - 'name-masked-email'. $autocompleteStyle = Settings::getUserAutocompleteStyle(); if ( $autocompleteStyle === 'none' ) { // Auto-complete disabled. Return an empty response. return new JsonResponse([]); } $matchEmail = FALSE; if ( $autocompleteStyle === 'name-email' || $autocompleteStyle === 'name-masked-email' ) { $matchEmail = TRUE; } // // Get a list of similar users. // ---------------------------- // Match the given name fragment against user account names and user // display names, if possible. Optionally match against user email // addresses. Optionally exclude the given list of user IDs and // blocked users. $uids = UserUtilities::findSimilarUsers( $userNameFragment , $matchEmail , $excludeBlocked , $excludeUids , self::MAXIMUM_NUMBER_OF_RESULTS); if ( empty ( $uids ) === TRUE) { // No match. return new JsonResponse([]); } // // Build returned JSON. // -------------------- // Loop through the returned UIDs. For each one, we need: // - the display name. // - the email address (for appropriate auto-complete styles). // // If needed, the email address must be masked. $results = []; foreach ( $uids as $uid ) { // Load the user. $user = $this ->userStorage->load( $uid ); if ( $user === NULL) { // Invalid user. continue ; } // Get the user's display name. If there is no full name, this falls // back to the account name. $userDisplayName = $user ->getDisplayName(); // Get the user's email address, if needed. Mask it, if needed. if ( $autocompleteStyle === 'name-email' || $autocompleteStyle === 'name-masked-email' ) { $userEmail = $user ->getEmail(); if ( $autocompleteStyle === 'name-masked-email' && empty ( $userEmail ) === FALSE) { $userEmail = $this ->maskEmail( $userEmail ); } if ( empty ( $userEmail ) === TRUE) { // The account has no email address, or it is malformed. // Skip it. $label = $this ->t( '@userDisplayName' , [ '@userDisplayName' => $userDisplayName , ]); } else { $label = $this ->t( '@userDisplayName (@userEmail)' , [ '@userDisplayName' => $userDisplayName , '@userEmail' => $userEmail , ]); } } else { $label = $this ->t( '@userDisplayName' , [ '@userDisplayName' => $userDisplayName , ]); } $results [] = [ 'value' => $user ->getAccountName(), 'label' => $label , ]; unset( $user ); } return new JsonResponse( $results ); } /** * Returns a masked version of a given email address. * * Masking keeps the first and last characters of the name portion of * the email address, and all of the rest of the email address. The * intervening letters are replaced with '*'. * * @param string $email * The email address to mask. * * @return string * Returns the masked email address. */ private function maskEmail(string $email ) { if ( empty ( $email ) === TRUE) { // Missing email address. return '' ; } // Email addresses are UTF-8 strings that may include non-ASCII characters // in both the name and domain parts of the address. Proper string handling // then requires that we use the PHP mb_* functions for multi-byte strings. // // Split the address into name and domain parts. $parts = mb_split( '@' , $email ); if ( count ( $parts ) !== 2) { // Malformed email address either has no '@' or too many. // Masking is not defined in this case, so return nothing. return '' ; } // Create a masked name with the original first and last characters, // and the rest replaced with '*'. $masked = mb_substr( $email , 0, 1); $len = mb_strlen( $parts [0]) - 2; for ( $i = 0; $i < $len ; $i ++) { $masked .= '*' ; } $masked .= mb_substr( $email , $len + 1, 1); return $masked . '@' . $parts [1]; } } |