1+ <?php
2+
3+ namespace Takuya \BacklogApiClient \Backup \Copy \Traits ;
4+
5+ use function Takuya \Utils \array_each_with_key ;
6+
7+ trait CopyIssue {
8+
9+ use CopyComment;
10+
11+ public function copyIssueList ($ src_project_id ,$ dst_project_id ){
12+ foreach ( $ this ->src_cli ->issue_ids ($ src_project_id ) as $ issue_id ) {
13+ $ this ->copyIssue ($ issue_id ,$ dst_project_id );
14+ }
15+ }
16+ public function copyIssue ( $ src_issue_id , $ dst_project_id ) {
17+ // ただし、コメント・課題の作成者はAPIの制限で変更が不可能。
18+ $ src_issue = $ this ->src_cli ->getIssue ( $ src_issue_id );
19+ // ユーザー・種別・マイルストーンを一致させる。
20+ $ file_ids = $ this ->getIdMapping ( 'sharedFiles ' );
21+ $ user_ids = $ this ->getIdMapping ( 'userIds ' );
22+ $ type_ids = $ this ->getIdMapping ( 'typeIds ' );
23+ $ version_ids = $ this ->getIdMapping ( 'versionIds ' );
24+
25+
26+ // 課題をコピー
27+ $ data = $ this ->formatIssue ( $ src_issue );
28+ $ data = $ this ->remapiId ( $ data , 'issueTypeId ' , $ type_ids );
29+ $ data = $ this ->remapiId ( $ data , 'versionsId ' , $ version_ids );
30+ $ data ['projectId ' ] = $ dst_project_id ;
31+ $ dst_issue = $ this ->dst_cli ->addIssue ( $ data );
32+ // 共有ファイルをリンクし直し
33+ $ this ->copyLinkSharedFiles ( $ src_issue , $ dst_issue , $ file_ids );
34+ // 添付ファイルをコピー
35+ $ this ->copyIssueAttachments ( $ src_issue , $ dst_issue );
36+ // 課題の状態を更新して合わせる。
37+ $ dst_issue = $ this ->updateIssueAttributes ( $ src_issue , $ dst_issue );
38+ // コメントをコピーする
39+ $ this ->copyCommentList ( $ src_issue , $ dst_issue );
40+ // スターをコピー
41+
42+
43+ return $ dst_issue ;
44+ }
45+
46+ public function getIdMapping ( $ name ) {
47+ // TODO 変数依存を切る。メソッドを作ってメソッド内部で、API取得して比較する。
48+ return $ this ->id_mapping [$ name ];
49+ }
50+
51+ protected function formatIssue ( object $ issue_api_result , $ add_user_name = true ,$ assignee ='' ) {
52+ if ( $ add_user_name ) {
53+ $ issue_api_result = $ this ->addUserInfoIntoBody ( $ issue_api_result );
54+ }
55+
56+ $ issue = (array )$ issue_api_result ;
57+ $ keys = [
58+ //'id',
59+ //"issueKey",
60+ //"keyId",
61+ "summary " ,
62+ //"parentIssueId",//todo
63+ "description " ,
64+ "startDate " ,
65+ "dueDate " ,
66+ "estimatedHours " ,
67+ "actualHours " ,
68+ "issueType " ,
69+ "category " ,
70+ "versions " ,
71+ "priority " ,
72+ "assignee " ,// ユーザIDが不一致になる可能性がある。
73+ //"resolution", //
74+ //"status", //
75+ //"milestone",//
76+ //"createdUser",
77+ //"created",
78+ //"updatedUser",
79+ //"updated",
80+ // TODO parentIssueId をどうするか。
81+ // TODO attachment, customField
82+ ];
83+ $ map_entry = [
84+ "assignee " => fn ( $ e ) => $ e ['id ' ] ?? $ e ,
85+ 'category ' => fn ( $ x ) => array_map ( fn ( $ e ) => $ e ['id ' ], $ x ),
86+ 'issueType ' => fn ( $ e ) => $ e ['id ' ] ?? $ e ,
87+ 'versions ' => fn ( $ x ) => array_map ( fn ( $ e ) => $ e ['id ' ], $ x ),
88+ 'priority ' => fn ( $ e ) => $ e ['id ' ] ?? $ e ,
89+ 'startDate ' => fn ( $ e ) => substr ( $ e , 0 , 10 ),
90+ 'dueDate ' => fn ( $ e ) => substr ( $ e , 0 , 10 ),
91+ ];
92+ $ map_key = [
93+ "assignee " => "assigneeId " ,
94+ 'category ' => 'categoryId ' ,
95+ 'issueType ' => 'issueTypeId ' ,
96+ 'versions ' => 'versionsId ' ,
97+ 'priority ' => 'priorityId ' ,
98+ ];
99+ $ issue = json_decode ( json_encode ( $ issue ), JSON_OBJECT_AS_ARRAY );
100+ $ issue = array_filter ( $ issue , fn ( $ k ) => in_array ( $ k , $ keys ), ARRAY_FILTER_USE_KEY );
101+ array_each_with_key ( $ map_entry , function ( $ k , $ f ) use ( &$ issue ) { $ issue [$ k ] = $ f ( $ issue [$ k ] ); } );
102+ array_each_with_key ( $ map_key , function ( $ old , $ new ) use ( &$ issue ) {
103+ $ issue [$ new ] = $ issue [$ old ];
104+ unset( $ issue [$ old ] );
105+ } );
106+ if (!empty ($ assignee )){
107+ $ issue ->assignee = $ assignee ;
108+ }
109+ return $ issue ;
110+ }
111+
112+ protected function addUserInfoIntoBody ( object $ issue_api_result ) {
113+ $ issue = $ issue_api_result ;
114+ $ creator = $ issue ->createdUser ->name ;
115+ $ created = substr ( $ issue ->created , 0 , 10 ).' ' .substr ( $ issue ->created , 11 , 5 );
116+ $ updator = $ issue ->updatedUser ->name ;
117+ $ updated = substr ( $ issue ->updated , 0 , 10 ).' ' .substr ( $ issue ->created , 11 , 5 );
118+ // 失われる情報を本文に残す
119+ $ footer = sprintf ( <<<EOS
120+
121+
122+
123+ ----
124+ 作成 ( %s | %s )
125+ 更新 ( %s | %s )
126+
127+ EOS , $ creator , $ created , $ updator , $ updated );
128+
129+ $ issue ->description = $ issue ->description .$ footer ;
130+ return $ issue ;
131+ }
132+
133+ protected function remapiId ( $ data , $ key , $ mapping ) {
134+ if ( !is_array ( $ data [$ key ] ) ) {
135+ $ data [$ key ] = $ mapping [$ data [$ key ]];
136+ }
137+ if ( is_array ( $ data [$ key ] ) ) {
138+ foreach ( $ mapping as $ old_id => $ new_id ) {
139+ foreach ( $ data [$ key ] as $ idx => $ value ) {
140+ if ( $ value == $ old_id ) {
141+ $ data [$ key ][$ idx ] = $ new_id ;
142+ }
143+ }
144+ }
145+ }
146+ return $ data ;
147+ }
148+
149+ protected function copyLinkSharedFiles ( $ src_issue , $ dst_issue , $ file_ids ) {
150+ if ( empty ( $ src_issue ->sharedFiles ) ) {
151+ return ;
152+ }
153+ $ ids = array_map ( fn ( $ e ) => $ e ->id , $ src_issue ->sharedFiles );
154+ foreach ( $ ids as $ old_id ) {
155+ $ new_id = $ file_ids [$ old_id ];
156+ $ this ->dst_cli ->linkSharedFilesToIssue ( $ dst_issue ->id , ['fileId ' => [$ new_id ]] );
157+ }
158+ }
159+
160+ protected function copyIssueAttachments ( $ src_issue , $ dst_issue ) {
161+ if ( empty ( $ src_issue ->attachments ) ) {
162+ return [];
163+ }
164+ $ mapping = [];
165+ foreach ( $ src_issue ->attachments as $ src_attachment ) {
166+ $ part = [
167+ 'name ' => "file " ,
168+ 'contents ' => $ this ->src_cli ->getIssueAttachment ( $ src_issue ->id , $ src_attachment ->id ),
169+ "filename " => $ src_attachment ->name ,
170+ ];
171+ $ param = ['multipart ' => [$ part ]];
172+ $ result = $ this ->dst_cli ->postAttachmentFile ( $ param );
173+ $ mapping [$ src_attachment ->id ] = $ result ->id ;
174+ }
175+ $ params = ['attachmentId ' => array_values ( $ mapping )];
176+ $ this ->dst_cli ->updateIssue ( $ dst_issue ->id , $ params );
177+ return $ mapping ;
178+ }
179+
180+ protected function updateIssueAttributes ( object $ src_issue , object $ dst_issue ) {
181+ $ params = [];
182+ if ( $ src_issue ->status ->id != $ dst_issue ->status ->id ) {
183+ $ params ['statusId ' ] = $ src_issue ->status ->id ;
184+ }
185+ if ( $ src_issue ->resolution ?->id != $ dst_issue ->resolution ?->id ) {
186+ $ params ['resolution ' ] = $ src_issue ->resolution ->id ;
187+ }
188+
189+ return !empty ( $ params ) ? $ this ->dst_cli ->updateIssue ( $ dst_issue ->id , $ params ) : $ dst_issue ;
190+ }
191+
192+
193+ }
0 commit comments