foldershare-8.x-1.2/src/ManagePaths.php
src/ManagePaths.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 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | <?php namespace Drupal\foldershare; use Drupal\user\Entity\User; use Drupal\foldershare\Utilities\FormatUtilities; use Drupal\foldershare\Entity\FolderShare; use Drupal\foldershare\Entity\Exception\ValidationException; use Drupal\foldershare\Entity\Exception\NotFoundException; /** * Manage entity paths for the FolderShare module. * * @ingroup foldershare */ final class ManagePaths { /*--------------------------------------------------------------------- * * Parse paths. * *---------------------------------------------------------------------*/ /** * Parses a path into its components. * * Paths have the form: * * Each component is semi-optional and has defaults: * - SCHEME://UID/ refers to the root items of user UID. * - SCHEME://ACCOUNT/ refers to the root items of user ACCOUNT. * - SCHEME:/PATH assumes the current user. * - SCHEME:/ refers to the root items of the current user. * - //UID/PATH defaults to the "personal" scheme of user UID. * - //ACCOUNT/PATH defaults to the "personal" scheme of user ACCOUNT. * - /PATH defaults to the "personal" scheme of the current user. * - / defaults to the "personal" scheme of the current user. * * Malformed paths throw exceptions: * - SCHEME:///PATH. * - SCHEME:///. * - SCHEME://. * - SCHEME:. * - SCHEME. * - :///PATH. * - ://PATH. * - :/PATH. * - :///. * - :// * - :/. * - ///PATH. * - ///. * - //. * * The SCHEME selects a category of content from the current user's * point of view. The following values are supported: * - "personal" = the user's own content. * - "public" = the site's public content. * * The SCHEME is automatically converted to lower case. All other * parts of a path are case sensitive. * * If no SCHEME is provided, the default is "personal". * * The UID and ACCOUNT indicate the owner of the content. The UID must * be numeric. It is not an error for the UID to be invalid - but no * content will be found if it is. The ACCOUNT must be valid is is looked * up to get the corresponding UID. * * If no UID or ACCOUNT is provided, the default is the current user. * * The PATH, starting with '/', gives a chain of folder names, starting * with a root item and ending with a file or folder name. A minimal * path is '/' alone, which refers to the list of root items in SCHEME. * For instance, "personal:/" prefers to user's own list of root items * and any root items shared with them. * * The UID or ACCOUNT are needed to disambiguate among multiple root items * available to the current user. Among a user's own root items, this is * never a problem because root item names must be unique for a user. * But when root items are shared with another user or the public, the * set of shared or public root items may include multiple root items * from different users, combined into the same namespace. It becomes * possible for there to be two root items named "ABC" in a user's * shared list of root items. In this case, the UID or ACCOUNT of one * of the "ABC" root items is needed to determine which one is desired. * * @param string $path * The path in the form SCHEME://UID/PATH or SCHEME://ACCOUNT/PATH, * where the SCHEME, UID, ACCOUNT, and PATH are each optional, but * certain combinations that skip them yield a malformed syntax that * causes an exception to be thrown. * * @return array * Returns an associative array with keys 'scheme', 'uid', and 'path' * that contain the parts of the path, or the defaults if a part was not * provided. If the path includes an ACCOUNT, that ACCOUNT is looked up * and the UID for the account returned in the array. * * @throws \Drupal\foldershare\Entity\Exception\ValidationException * Throws an exception if the path is malformed: * - The incoming path is empty. * - The SCHEME does not have a known value. * - A UID is provided, but it is invalid. * - An ACCOUNT is provided, but it is invalid. * - A UID or ACCOUNT is provided, but there is no path after it. * - The path does not start with '/'. * * @internal * All text handling here must be multi-byte character safe. * @endinternal */ public static function parsePath(string $path ) { // // Look for SCHEME // --------------- // A path may begin with a SCHEME and colon. if ( empty ( $path ) === TRUE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The folder path is empty.' ), t( 'Please enter a path with folder names separated by the "/" character.' ))); } $parts = mb_split( ':' , $path , 2); $uid = NULL; if ( count ( $parts ) === 2) { // SCHEME found. Convert it to lower case and use the remainder // of the string as the path. $scheme = mb_convert_case( $parts [0], MB_CASE_LOWER); $path = $parts [1]; switch ( $scheme ) { case FolderShareInterface::PERSONAL_SCHEME: case FolderShareInterface::PUBLIC_SCHEME: break ; default : throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The folder path uses an invalid scheme "@name".' , [ '@name' => $scheme , ]), t( 'Paths optionally may start with "personal://" or "public://" to indicate personal or public content.' ))); } } else { // No SCHEME found. Use default. $scheme = FolderShareInterface::PERSONAL_SCHEME; } // // Look for UID or ACCOUNT // ----------------------- // Following the SCHEME may be '//' and a user UID or ACCOUNT name. $slashslash = mb_substr( $path , 0, 2); if ( $slashslash === '//' ) { // UID or ACCOUNT found. Extract it up to the next '/'. $endOfUser = mb_stripos( $path , '/' , 2); if ( $endOfUser === FALSE) { // No further '/' found after the UID. Malformed. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The folder path is missing a top-level folder name.' ), t( 'Please enter a path with folder names separated by the "/" character.' ))); } // Determine if the value is a positive integer user ID or a // string account name. $userIdOrAccount = mb_substr( $path , 2, ( $endOfUser - 2)); if (mb_ereg_match( '[0-9]?' , $userIdOrAccount ) === TRUE) { // It's an integer user ID. $uid = (int) intval ( $userIdOrAccount ); // Make sure the UID is valid. $user = User::load( $uid ); if ( $user === NULL) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The user ID "@id" is not recognized.' , [ '@id' => $uid , ]), t( 'Please check that the integer ID is correct for an existing user account.' ))); } } else { // It's a string account name. Look up the account. $user = user_load_by_name( $userIdOrAccount ); if ( $user === FALSE) { // Unknown user! throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The user account "@id" is not recognized.' , [ '@id' => $userIdOrAccount , ]), t( 'Please check that the name is correct for an existing user account.' ))); } $uid = (int) $user ->id(); } // The rest of incoming path is the actual folder path. $path = mb_substr( $path , $endOfUser ); } else { $user = \Drupal::currentUser(); $uid = (int) $user ->id(); } // // Look for PATH // ------------- // The path is the remainder after the optional SCHEME, UID, or ACCOUNT. // Insure that it starts with a '/'. $slash = mb_substr( $path , 0, 1); if ( $slash !== '/' ) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The folder path is missing a top-level folder name.' ), t( 'Please enter a path with folder names separated by the "/" character.' ))); } // Clean the path by removing any embedded '//' or a trailing '/'. $cleanedPath = mb_ereg_replace( '%//%' , '/' , $path ); if ( $cleanedPath !== FALSE) { $path = $cleanedPath ; } $cleanedPath = mb_ereg_replace( '%/?$%' , '' , $path ); if ( $cleanedPath !== FALSE) { $path = $cleanedPath ; } return [ 'scheme' => $scheme , 'uid' => $uid , 'path' => $path , ]; } /** * Returns the entity ID identified by a path. * * Paths have the form: * * where SCHEME is the name of a root item list ("public" or "personal"), * UID is the user ID or account name, and PATH is a folder path. * * The path is broken down into a list of ancestors and each ancestor * looked up, starting with the root item. The indicated child is * found in that ancestor, and so forth down to the last entity on the * path. That entity is returned. * * @param string $path * The path in the form SCHEME://UID/PATH, or one of its subforms. * * @return int * Returns the entity ID of the last entity on the path. * * @throws \Drupal\foldershare\Entity\Exception\ValidationException * Throws an exception if the path is malformed. * @throws \Drupal\foldershare\Entity\Exception\NotFoundException * Throws an exception if the path contains a root item, parent * folder, or child that could not be found. * * @see self::parsePath() * * @internal * All text handling here must be multi-byte character safe. * @endinternal */ public static function findPathItemId(string $path ) { // // Parse path // ---------- // Parse the path into SCHEME, UID, ACCOUNT, and PATH components, or their // defaults. Throw an exception if the path is malformed. $components = self::parsePath( $path ); // Split the path into a chain of folder names, ending with a // file or folder name. Since the path must start with '/', the // first returned part will be empty. Ignore it. if ( $components [ 'path' ] === '/' ) { throw new NotFoundException(FormatUtilities::createFormattedMessage( t( 'The folder path is missing a top-level folder name.' ), t( 'Please enter a path with folder names separated by the "/" character.' ))); } $parts = mb_split( '/' , $components [ 'path' ]); array_shift ( $parts ); // // Find root item // -------------- // The root item name is the first in the list. Use it and a possible // user ID to look up candidate root items based upon the scheme. $rootName = array_shift ( $parts ); // Get a list of matching root items. $uid = $components [ 'uid' ]; switch ( $components [ 'scheme' ]) { default : case FolderShareInterface::PERSONAL_SCHEME: // If no UID is given, default to the current user. if ( $uid === NULL || $uid < 0) { $uid = \Drupal::currentUser()->id(); } // Find all root item IDs with the given name. Include disabled // items, but not hidden items. $ownedRootItemIds = FolderShare::findAllRootItemIds( $uid , $rootName , TRUE, FALSE); // Find all shared root item IDs with the given name. Include // disabled items, but not hidden items. $sharedRootItemIds = FolderShare::findAllSharedRootItemIds( FolderShareInterface::ANY_USER_ID, $uid , $rootName , TRUE, FALSE); $rootItemIds = array_merge ( $ownedRootItemIds , $sharedRootItemIds ); break ; case FolderShareInterface::PUBLIC_SCHEME: // If no UID is given, default to ANY_USER_ID to get public // owned by anyone. if ( $uid === NULL) { $uid = FolderShareInterface::ANY_USER_ID; } // Find all public root item IDs with the given name. Include // disabled items, but not hidden items. $rootItemIds = FolderShare::findAllPublicRootItemIds( $uid , $rootName ); break ; } if ( empty ( $rootItemIds ) === TRUE) { throw new NotFoundException(FormatUtilities::createFormattedMessage( t( '"@path" could not be found.' , [ '@path' => $path , ]), t( 'Please check that the file and folder path is correct.' ))); } if ( count ( $rootItemIds ) > 1) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The folder path "@path" is ambiguous.' , [ '@path' => $path , ]))); } $id = (int) reset( $rootItemIds ); // // Follow descendants // ------------------ // The remaining parts of the path must be descendants of the root item. // Follow the path downwards. foreach ( $parts as $name ) { $id = FolderShare::findNamedChildId( $id , $name ); if ( $id === FALSE) { throw new NotFoundException(FormatUtilities::createFormattedMessage( t( '"@path" could not be found.' , [ '@path' => $path , ]), t( 'Please check that the file and folder path is correct.' ))); } } return $id ; } } |