Changeset 14847
- Timestamp:
- 04/28/2026 06:41:41 AM (6 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/api.wordpress.org/public_html/dotorg/slack/subgroup.php
r14836 r14847 341 341 return; 342 342 343 case 'rotate_subgroup': 344 ack_and_finish(); 345 346 $rotate_id = $action['value'] ?? ''; 347 $parent_id = $meta['parent'] ?? ''; 348 $parent_name = $meta['parent_name'] ?? ''; 349 $root_view = $meta['root_view'] ?? ''; 350 351 if ( $rotate_id && $parent_id && $parent_name ) { 352 rotate_subgroup( $rotate_id, $parent_id, $parent_name, $user_id, $view_id, $root_view ); 353 } 354 return; 355 343 356 case 'open_create_modal': 344 357 $parent_id = $meta['parent'] ?? ''; … … 386 399 387 400 $new_name = trim( $values['name']['value']['value'] ?? '' ); 388 $prefix = $parent_name. '-';401 $prefix = derive_prefix_name( $parent_name ) . '-'; 389 402 390 403 if ( strpos( $new_name, $prefix ) !== 0 || strlen( $new_name ) <= strlen( $prefix ) ) { … … 459 472 $selected_users = $values['users']['value']['selected_users'] ?? []; 460 473 461 $prefix = $parent_name. '-';474 $prefix = derive_prefix_name( $parent_name ) . '-'; 462 475 if ( strpos( $name, $prefix ) !== 0 || strlen( $name ) <= strlen( $prefix ) ) { 463 476 header( 'Content-Type: application/json' ); … … 559 572 $new_archived = trim( $values['new_name_for_archived']['value']['value'] ?? '' ); 560 573 561 $prefix = $parent_name. '-';574 $prefix = derive_prefix_name( $parent_name ) . '-'; 562 575 563 576 if ( $action === 'rename' ) { … … 623 636 ] ); 624 637 625 finalize_create( $new_id, $original_name, $creator, $parent_id, $ user_id, $purpose, $topic, $strategy, $selected_users, $root_view, false );638 finalize_create( $new_id, $original_name, $creator, $parent_id, $parent_name, $user_id, $purpose, $topic, $strategy, $selected_users, $root_view, false ); 626 639 return; 627 640 } … … 704 717 if ( $root_view && $parent_name ) { 705 718 refresh_home_view( $root_view, [ 'id' => $parent_id, 'name' => $parent_name ], $user_id ); 719 } 720 } 721 722 /* 723 * Rotate a subgroup: rename the existing channel with a year suffix, archive 724 * it, then create a fresh channel with the same name (purpose/topic carried 725 * over) seeded with just the triggering user. Useful when a long-running 726 * channel accumulates context that's no longer relevant — e.g. annual events. 727 */ 728 function rotate_subgroup( $channel_id, $parent_id, $parent_name, $user_id, $manage_view_id, $root_view ) { 729 $info = api_call( 'conversations.info', [ 'channel' => $channel_id ] ); 730 $orig = $info['channel'] ?? null; 731 if ( ! $orig ) { 732 return; 733 } 734 735 $orig_name = $orig['name']; 736 $purpose = $orig['purpose']['value'] ?? ''; 737 $topic = $orig['topic']['value'] ?? ''; 738 $year = pick_archive_year( $orig ); 739 740 // Try `name-YEAR`, then `name-YEAR-2`, etc. on collision. Slack caps 741 // channel names at 80 chars; trim the base if needed. 742 $rename = null; 743 for ( $i = 1; $i <= 9; $i++ ) { 744 $candidate = $orig_name . '-' . $year . ( $i > 1 ? "-{$i}" : '' ); 745 if ( strlen( $candidate ) > 80 ) { 746 $tail = '-' . $year . ( $i > 1 ? "-{$i}" : '' ); 747 $candidate = substr( $orig_name, 0, 80 - strlen( $tail ) ) . $tail; 748 } 749 $rename = api_call( 'conversations.rename', [ 750 'channel' => $channel_id, 751 'name' => $candidate, 752 ], SUBGROUP_USER_TOKEN ); 753 if ( ! empty( $rename['ok'] ) ) { 754 $archived_name = $candidate; 755 break; 756 } 757 if ( ( $rename['error'] ?? '' ) !== 'name_taken' ) { 758 break; 759 } 760 } 761 762 if ( empty( $rename['ok'] ) ) { 763 api_call( 'chat.postEphemeral', [ 764 'channel' => $parent_id, 765 'user' => $user_id, 766 'text' => sprintf( 'Could not rotate `%s`: %s', $orig_name, $rename['error'] ?? 'unknown' ), 767 ] ); 768 return; 769 } 770 771 $arch = api_call( 'conversations.archive', [ 772 'channel' => $channel_id, 773 ], SUBGROUP_USER_TOKEN ); 774 if ( empty( $arch['ok'] ) ) { 775 // Roll back the rename so the channel is left as we found it. 776 api_call( 'conversations.rename', [ 777 'channel' => $channel_id, 778 'name' => $orig_name, 779 ], SUBGROUP_USER_TOKEN ); 780 api_call( 'chat.postEphemeral', [ 781 'channel' => $parent_id, 782 'user' => $user_id, 783 'text' => sprintf( 'Could not archive `%s` during rotate: %s', $archived_name, $arch['error'] ?? 'unknown' ), 784 ] ); 785 return; 786 } 787 788 $new = api_call( 'conversations.create', [ 789 'name' => $orig_name, 790 'is_private' => true, 791 ], SUBGROUP_USER_TOKEN ); 792 if ( empty( $new['ok'] ) ) { 793 api_call( 'chat.postEphemeral', [ 794 'channel' => $parent_id, 795 'user' => $user_id, 796 'text' => sprintf( 'Rotated `%s` → `%s` and archived, but creating the replacement failed: %s', $orig_name, $archived_name, $new['error'] ?? 'unknown' ), 797 ] ); 798 invalidate_channel_lists(); 799 return; 800 } 801 802 $new_id = $new['channel']['id']; 803 $creator = $new['channel']['creator'] ?? ''; 804 805 if ( $purpose ) { 806 api_call( 'conversations.setPurpose', [ 'channel' => $new_id, 'purpose' => $purpose ], SUBGROUP_USER_TOKEN ); 807 } 808 // Roll the topic year forward — "WCUS 2024 design" becomes "WCUS 2025 809 // design" on the rotated channel. First match only, digit-bounded so we 810 // don't mangle dates like 20240501. No-op if the topic has no year. 811 $new_topic = $topic 812 ? preg_replace( '/(?<!\d)' . preg_quote( $year, '/' ) . '(?!\d)/', (string) ( (int) $year + 1 ), $topic, 1 ) 813 : ''; 814 if ( $new_topic ) { 815 api_call( 'conversations.setTopic', [ 'channel' => $new_id, 'topic' => $new_topic ], SUBGROUP_USER_TOKEN ); 816 } 817 818 $invitees = array_values( array_diff( array_unique( [ $user_id, SUBGROUP_BOT_USER_ID ] ), [ $creator ] ) ); 819 if ( $invitees ) { 820 api_call( 'conversations.invite', [ 821 'channel' => $new_id, 822 'users' => implode( ',', $invitees ), 823 ], SUBGROUP_USER_TOKEN ); 824 } 825 826 invalidate_channel_lists(); 827 invalidate_members( $new_id ); 828 invalidate_members( $channel_id ); 829 830 // Don't link the archived channel — non-members see a strikethrough 831 // dead link. New channel is fine to link. 832 api_call( 'chat.postMessage', [ 833 'channel' => $parent_id, 834 'text' => sprintf( '<@%s> rotated `%s`: archived as `%s`, fresh <#%s> created.', $user_id, $orig_name, $archived_name, $new_id ), 835 ] ); 836 837 $parent = [ 'id' => $parent_id, 'name' => $parent_name ]; 838 api_call( 'views.update', [ 839 'view_id' => $manage_view_id, 840 'view' => json_encode( build_manage_view( $parent, $user_id, $root_view ) ), 841 ] ); 842 if ( $root_view ) { 843 refresh_home_view( $root_view, $parent, $user_id ); 706 844 } 707 845 } … … 830 968 } 831 969 970 /* 971 * Channels ending in -team are private workspaces for a (often public) base 972 * channel — `#wcus-team` for `#wcus`. Subgroups should follow the base 973 * channel's name (`wcus-foo`), not the team's (`wcus-team-foo`), so users in 974 * the public base channel discover them naturally. Notifications still go to 975 * the channel where /subgroup was invoked. 976 */ 977 function derive_prefix_name( $channel_name ) { 978 if ( substr( $channel_name, -5 ) === '-team' ) { 979 return substr( $channel_name, 0, -5 ); 980 } 981 return $channel_name; 982 } 983 832 984 function get_subgroups( $parent_name, $include_archived = false ) { 833 $prefix = $parent_name. '-';985 $prefix = derive_prefix_name( $parent_name ) . '-'; 834 986 $out = []; 835 987 foreach ( list_all_private_channels( $include_archived ) as $g ) { … … 982 1134 [ 983 1135 'type' => 'button', 1136 'text' => [ 'type' => 'plain_text', 'text' => 'Rotate' ], 1137 'action_id' => 'rotate_subgroup', 1138 'value' => $g['id'], 1139 'confirm' => [ 1140 'title' => [ 'type' => 'plain_text', 'text' => 'Rotate channel?' ], 1141 'text' => [ 'type' => 'mrkdwn', 'text' => "Archive <#{$g['id']}> with a year suffix and create a fresh `{$g['name']}` with only you as a member." ], 1142 'confirm' => [ 'type' => 'plain_text', 'text' => 'Rotate' ], 1143 'deny' => [ 'type' => 'plain_text', 'text' => 'Cancel' ], 1144 ], 1145 ], 1146 [ 1147 'type' => 'button', 984 1148 'text' => [ 'type' => 'plain_text', 'text' => 'Archive' ], 985 1149 'style' => 'danger', … … 1060 1224 1061 1225 function build_rename_view( $parent, $channel_id, $current_name, $user_id, $manage_view_id, $root_view_id ) { 1062 $prefix = $parent['name']. '-';1226 $prefix = derive_prefix_name( $parent['name'] ) . '-'; 1063 1227 1064 1228 return [ … … 1102 1266 1103 1267 function build_create_view( $parent, $user_id, $root_view_id ) { 1104 $prefix = $parent['name']. '-';1268 $prefix = derive_prefix_name( $parent['name'] ) . '-'; 1105 1269 1106 1270 return [ … … 1197 1361 } 1198 1362 1363 function pick_archive_year( $channel ) { 1364 // Prefer a year named in the topic/purpose ("WordCamp US 2024") so the 1365 // archived name reflects what the channel was *for*, not when it was 1366 // archived. Fall back to creation year, then current year. 1367 $text = ( $channel['topic']['value'] ?? '' ) . ' ' . ( $channel['purpose']['value'] ?? '' ); 1368 if ( preg_match( '/(?<!\d)(20\d{2})(?!\d)/', $text, $m ) ) { 1369 return $m[1]; 1370 } 1371 if ( ! empty( $channel['created'] ) ) { 1372 return date( 'Y', (int) $channel['created'] ); 1373 } 1374 return date( 'Y' ); 1375 } 1376 1199 1377 function build_conflict_view( $archived, $ctx ) { 1200 $prefix = $ctx['parent_name']. '-';1201 $suffix = '- archived-' . date( 'Ymd');1378 $prefix = derive_prefix_name( $ctx['parent_name'] ) . '-'; 1379 $suffix = '-' . pick_archive_year( $archived ); 1202 1380 $suggested = $ctx['original_name'] . $suffix; 1203 1381 if ( strlen( $suggested ) > 80 ) {
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)